Background information

I am developing a multilingual Drupal 8 commerce site where only authenticated users (customers) can place orders (anonymous users can't place orders). Customers can edit their preferred language using the default language field on their account. After an order is placed, another user with a special role (webmaster) has to prepare the order and physically fill a box with the contents of the order. When the webmaster is ready, he performs a transition on the order changing it's state. This transition triggers an e-mail that is sent to the customer, informing him that his order is ready for shipment. This e-mail is triggered through an event subscriber that reacts on the shipping transition of orders. The e-mail is formed by rendering a custom template.


A customer who's preferred language is Dutch, has placed an order. A webmaster is using the website in English and sets the order 'ready for shipment'. The customer gets an English e-mail while he should get a Dutch e-mail.


The code that triggers the e-mail:

/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $event->getEntity();

$to = $order->getCustomer()->getEmail();
$langcode = $order->getCustomer()->getPreferredLangcode();
$subject = $this->t('Your order #@order_number is packed', ['@order_number' => $order->getOrderNumber()], ['langcode' => $langcode]);

$build = [
  '#theme' => 'commerce_order_shipped_mail',
  '#subject' => $subject,
  '#order_number' => $order->getOrderNumber(),
  '#order_date' => $this->dateFormatter->format($order->getCompletedTime(), 'default_date_only'),
  '#order_url' => Url::fromRoute('entity.commerce_order.user_view', [
    'user' => $order->getCustomer()->id(),
    'commerce_order' => $order->id(),
  ], ['absolute' => TRUE]),

$mail = $this->mailManager->mail('order_shipped_mail', $to, $subject, $build, $langcode);

The mail function of the mail manger (wrapper):

$name = \Drupal::config('system.site')->get('name');
$email = \Drupal::config('system.site')->get('mail');
$from = '"' . $name . '" <' . $email . '>';

$params = [
  'headers' => [
    'Content-Type' => 'text/html; charset=UTF-8; format=flowed; delsp=yes',
    'Content-Transfer-Encoding' => '8Bit',
    'From' => $from,
  'from' => $from,
  'subject' => $subject,
  'body' => $this->renderer->renderRoot($body),

$langcode = $langcode ?: $this->languageManager->getDefaultLanguage()->getId();

return $this->mailManager->mail('XXX_general', $key, $to, $langcode, $params);

The custom template:

        <h2>{{ 'Shipping confirmation'|t }}</h2>
        <p>{{ 'Dear customer,'|t }}</p>
            {% trans %}
            Your order #{{ order_number }} of {{ order_date }} has been handed over to XXX.<br />
            You will receive an email from XXX with the tracking information of your package.
            {% endtrans %}

        <p><a href="{{ order_url }}" target="{{ 'Order details'|t }}">{{ 'Order details'|t }}</a></p>

        <p>The XXX team</p>

As you can see a custom template is rendered that contains the body of the e-mail.

Actual question

How can I temporarily change the current language during a request so rendering happens in the correct language? I know I can give a langcode to a t-function but my rendered order URL would still be an English URL so this doesn't fix the problem. I have tried suggestions from here, and I thought the LanguageNegotiation Plugin would do the trick, but it doesn't.

Please excuse me if my question is malformed or if I provided to little information.

  • Hello, I have got the same usecase - did you find the solution by now? May 27, 2020 at 9:58

1 Answer 1


See How to progragrammatically get NodeType translated value in D8?, that example is actually based on user_mail() which has exactly the same use case, to send things like password reset e-mails in the language that the user set in his profile.

That will however only work for stored configuration. I think you can't easily change the active interface language, what you can do is explicitly pass the language to each t() call, also inside twig, at least for t().

The only thing I can think of right now that might allow to override it is do a fake negotiator, set that with setNegotiator() on the language manager, then at the end set back the original one.

  • I tried your suggestions and I had my hopes up for the fake/custom language negotiator but couldn't get it to work (I tried the suggestion from the linked post). I'll try to fiddle with it some more. Another approach is to stop using a custom template and put my e-mail in a config object so i can use the user_mail() approach. But even then I would have to create custom tokens to insert my dynamic data and I thought there where multilingual issues about that as well.
    – stefdewa
    Oct 12, 2018 at 7:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.