5

I configured the Symfony mailer to send emails with messenger.

https://symfony.com/doc/current/mailer.html#sending-messages-async

I have my emails in two languages and I rely on the requests to detect the language, but now the emails are not translated.

How can I get the messages to be translated in the language detected in the request?

In my controller:

$mailer->send(
            $user->email,
            $this->translator->trans('mails.recover.subject'),
            'email/client/password-recovery.html.twig',
            compact('user', 'hash', 'target')
        );

Template:

{% extends 'email/base.html.twig' %}

{% block content %}
    <h2>{{ 'mails.recover.header' | trans({'%name%': user.name}) }}</h2>

    <p style="margin: 25px 0;">
        {{ 'mails.recover.text1' | trans({'%url%': url('default')}) | raw }}
    </p>

// More code

Messenger config:

framework:
    messenger:
        # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
        # failure_transport: failed

        transports:
            # https://symfony.com/doc/current/messenger.html#transport-configuration
            async: '%env(MESSENGER_TRANSPORT_DSN)%'
            # failed: 'doctrine://default?queue_name=failed'
            # sync: 'sync://'

        routing:
            # Route your messages to the transports
            # 'App\Message\YourMessage': async
            'Symfony\Component\Mailer\Messenger\SendEmailMessage':  async

Looking better the subject of the mail if it is translated correctly, the body of the mail does not

If I remove the line

'Symfony\Component\Mailer\Messenger\SendEmailMessage':  async

in messegner config, translation work.

yivi
  • 42,438
  • 18
  • 116
  • 138
Conde
  • 785
  • 15
  • 31
  • It seems like the message is generated first and the async part only sends the already "rendered" content it via SMTP, so it schould work like if you send the mail directly. How do you translate? Are you using the Translation component? – vstm Jun 24 '20 at 17:55
  • Yes, my template looks like this: {{ 'mails.base.about.header'|trans }} – Conde Jun 24 '20 at 18:05
  • would you please show more code? specifically the part in php were you send the message – Jakumi Jun 24 '20 at 19:06
  • @Jakumi I have updated the question. – Conde Jun 24 '20 at 19:25
  • 1
    I'm a bit lost, usually MailerInterface::send() receives at most 2 arguments (RawMessage and Envelope). Apparently you use some helper class or something. And I assume that helper class generates a TemplatedEmail or something, and the async service uses a "fresh" twig to actually render the email body, hence only the default language. try rendering the body with `$this->renderView(template, context)` (controller default function) – Jakumi Jun 24 '20 at 19:44

4 Answers4

8

The problem you have is that the Symfony Translator component gets the user's locale form the incoming request, and when sending your mails asynchronously by the time the mail is actually sent the request is long finished and gone, and then the context the message consumer (command line) and there is no request locale information.

There are two solutions for this:

First Option: You pass the values already translated to the template (which is what you are doing with the email subject).

E.g. something like this:

$mailer->send(
        $user->email,
        $this->translator->trans('mails.recover.subject'),
        'email/client/password-recovery.html.twig',
            [
                'user' => $user,
                'hash' => $hash,
                'target' => $target,
                'labels' => [
                  'header' => $this->translator
                                   ->trans('mails.recover.subject', [ 'name' => $user->getName()]),
                  'text1'  => $this->translator
                                    ->trans('mails.recover.text1', ['url', => $defaulUrl])
            ]
);

And then in your template you use the values directly:

{% extends 'email/base.html.twig' %}

{% block content %}
    <h2>{{ texts.header }}</h2>
    <p style="margin: 25px 0;">{{ texts.text1 }}</p>
{% endblock %}

This would be my preferred approach, since it makes the template as dumb as possible and easy to reuse in different contexts. The templates themselves do not need to know anything not pertaining the actual rendering of its content.

Second Option: Let the templating system know what user locale you want to translate to:

$mailer->send(
        $user->email,
        $this->translator->trans('mails.recover.subject'),
        'email/client/password-recovery.html.twig',
            [
                'user'   => $user,
                'hash'   => $hash,
                'target' => $target,
                'requestLocale' =>  $locale
                // get the locale from the request
                // (https://symfony.com/doc/current/translation/locale.html)
            ]
);

Then you use the received locale in the filter you are using, as described here:

<h2>{{ 'mails.recover.header' | trans({'%name%': user.name}, 'app', requestLocale) }}</h2>

While I prefer the first one, playing with either option should let you get your desired results.

yivi
  • 42,438
  • 18
  • 116
  • 138
  • Very good answer, I just want to point out that you can also use trans function and pass the locale: `

    {% trans with {'%name%': user.name} from 'app' into requestLocale %}mails.recover.header{% endtrans %}

    ` Source: https://symfony.com/doc/current/reference/twig_reference.html#id2
    – jawira Jun 18 '21 at 16:34
0

My solution wich works also for url_absolute in the twig.

$message = (new TemplatedEmail())
...

$renderer = new BodyRenderer($this->twig);
$renderer->render($message);
$this->mailer->send(new RawMessage($message->toString()),Envelope::create($message));

With Environment $twig and MailerInterface $mailer

MrFreezer
  • 1,627
  • 1
  • 10
  • 8
-1

The "Second Option: Let the templating system know what user locale you want to translate to:" is the best solution as long as the email content requires no translations.

If you have complex emails with a lot of text a better approach is to work with multiple templates and let application set locale decide which email template must be used.

Or in the template email.

{% if language is defined %}
    {# Language still specified #}    
{% else %}
    {% set language = 'ne' %}
{% endif %}

{% if language == "en" %}
    <h1>Ticket for - <code>{{ activity.name }}</code> by participant <code>{{ buyer.name }}</code> </h1>
{% else %}
    <h1>Kaartje voor - <code>{{ activity.name }}</code> door deelnemer <code>{{ buyer.name }}</code> </h1>
{% endif %}
Jan
  • 35
  • 4
-2

Whilst all these answers here are interesting, none of them worked properly for me and consisted in "extra" variables like locale needing to be passed into the context.

In my case, using the latest Symfony this was enough to set in the email template:

{% trans_default_domain 'emails' %}

The simply calling:

{{ 'emails.whatevermessage'|trans|raw }}

Works like a charm.

Mecanik
  • 1,539
  • 1
  • 20
  • 50