mate64
mate64

Reputation: 10082

Translate queued mails (localization)

I am looking for a working solution, to translate queued emails in . Unfortunately, all emails use the default locale (defined under app.locale).

Let's assume, we have two emails in the pipeline, one for an English en user and another for an Japanese jp user.

What data should I pass to the Mail facade to translate (localize) the queued emails?

  // User model
  $user = User:find(1)->first();

  Mailer::queue($email, 'Party at Batman\'s cave (Batcave)', 'emails.party-invitation', [

    ...

    'locale' => $user->getLocale(), // value: "jp", but does not work
    'lang' => $user->getLocale(), // value: "jp", but does not work
    'language' => $user->getLocale(), // value: "jp", but does not work
  ]);

Upvotes: 8

Views: 5410

Answers (7)

Igor Simic
Igor Simic

Reputation: 540

using preferredLocale() is the "official" way to go.

you can implement HasLocalePreference and add method preferredLocale() to the notifiable model (as @Peter-M suggested):

use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Model implements HasLocalePreference
{
    /**
     * Get the user's preferred locale.
     *
     * @return string
     */
    public function preferredLocale()
    {
        return $this->locale;
    }
}

and then you just send the notification to that model and locale will be automatically applied to your email template

$user->notify(new InvoicePaid($invoice));

This also works with queued mail notifications.

read more here: https://floyk.com/en/post/how-to-change-language-for-laravel-email-notifications

Upvotes: 0

kendepelchin
kendepelchin

Reputation: 2994

I have been struggling to get this done in a more efficient way. Currently I have it set up like this. Hopefully this helps someone in the future with this issue:

// Fetch the locale of the receiver.
$user = Auth::user();
$locale = $user->locale;
Mail::queue('emails.welcome.template', ['user' => $user, 'locale' => $locale], function($mail) use ($user, $locale) {
     $mail->to($user->email);
     $mail->subject(
          trans(
               'mails.subject_welcome',
               [], null, $locale
          )
     );
});

And use the following in your template:

{{ trans('mails.welcome', ['name' => ucfirst($user['first_name'])], null, $locale) }}

Note: do not forget to restart your queue

Upvotes: 9

Peter-M
Peter-M

Reputation: 145

Laravel 5.7.7 introduced the HasLocalePreference interface to solve this issue.

class User extends Model implements HasLocalePreference
{
    public function preferredLocale() { return $this->locale; }
}

Now if you use Mail::to() function, Laravel will send with the correct locale.

Also introduced was the Mail chainable locale() function.

Mail::to($address)->locale($locale)->send(new Email());

Upvotes: 2

christoph
christoph

Reputation: 31

In Laravel 5.6 is a locale function added to Mailable: $infoMail->locale('jp'); Mail::queue($infoMail);

Upvotes: 3

Amade
Amade

Reputation: 3998

Here's a solution that worked for me. In my example, I am processing the booking on a queue, using a dedicated Job class.

When you dispatch a job on a queue, pass the locale, you want your email in. For example:

ProcessBooking::dispatch($booking, \App::getLocale());

Then, in your job class (ProcessBooking in my example) store the locale in a property:

protected $booking;
protected $locale;

public function __construct(Booking $booking, $locale)
{
    $this->booking = $booking;
    $this->locale = $locale;
}

And in your handle method temporarily switch locales:

public function handle()
{
    // store the locale the queue is currently running in
    $previousLocale = \App::getLocale();

    // change the locale to the one you need for job to run in
    \App::setLocale($this->locale);

    // Do the stuff you need, e.g. send an email
    Mail::to($this->booking->customer->email)->send(new NewBooking($this->booking));

    // go back to the original locale
    \App::setLocale($previousLocale);
}

Why do we need to do this?

Queued emails get the default locale since queue workers are separate Laravel apps. If you ever ran into a problem, when you cleared your cache, but queued 'stuff' (e.g. emails) still showed the old content, it's because of that. Here's an explanation from Laravel docs:

Remember, queue workers are long-lived processes and store the booted application state in memory. As a result, they will not notice changes in your code base after they have been started. So, during your deployment process, be sure to restart your queue workers.

Upvotes: 0

pinguinjkeke
pinguinjkeke

Reputation: 522

If your emails inherits the built-in Illuminate\Mail\Mailable class you can build your own Mailable class that will take care of translations.

Create your own SendQueuedMailable class that inherits from Illuminate\Mail\SendQueuedMailable. Class constructor will take current app.location from config and remember it in a property (because laravel queues serializes it). In queue worker it will take the property back from config and set the setting to the current environment.

<?php

namespace App\Mail;

use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Contracts\Mail\Mailable as MailableContract;
use Illuminate\Mail\SendQueuedMailable as IlluminateSendQueuedMailable;

class SendQueuedMailable extends IlluminateSendQueuedMailable
{
    protected $locale;

    public function __construct(MailableContract $mailable)
    {
        parent::__construct($mailable);

        $this->locale = config('app.locale');
    }

    public function handle(MailerContract $mailer)
    {
        config(['app.locale' => $this->locale]);
        app('translator')->setLocale($this->locale);

        parent::handle($mailer);
    }
}

Then, create your own Mail class that inherits from Illuminate\Mail\Mailable

<?php

namespace App\Mail;

use Illuminate\Contracts\Queue\Factory as Queue;
use Illuminate\Mail\Mailable as IlluminateMailable;

class Mailable extends IlluminateMailable
{
    public function queue(Queue $queue)
    {
        $connection = property_exists($this, 'connection') ? $this->connection : null;

        $queueName = property_exists($this, 'queue') ? $this->queue : null;

        return $queue->connection($connection)->pushOn(
            $queueName ?: null, new SendQueuedMailable($this)
        );
    }
}

And, voila, all your queued mailables inherited from App\Mailable class automatically will take care of current locale.

Upvotes: 3

Roark
Roark

Reputation: 1122

I'm faced with the same problem

Without extending the queue class the quickest / dirtiest solution would be to create an email template for each locale.

Then when you create the queue, select the local template i.e.

Mail::queue('emails.'.App::getLocale().'notification', function($message) 
{
    $message->to($emails)->subject('hello');
});

If the local is set to IT (italian) This will load the view emails/itnotification.blade.php

As I said... Dirty!

Since this method is actually horrible I came across this answer And its working for me, you actually send the entire translated html version of the email in a variable to the queue and have a blank blade file that echos out the variable when the queue runs.

Upvotes: -1

Related Questions