Eolica-Web / nova-locale-switcher

A simple locale switcher for Nova without any concrete localization implementation
MIT License
13 stars 11 forks source link

Set Locale #5

Closed alexander-code closed 2 years ago

alexander-code commented 2 years ago

Hi, great work.

One thing I noticed is that this only works when we have a locale column in the users table. But, what about when user decides to logout? Then at the login screen we don't have access to that.

A small tip is that the Nova::serving isn't enough, we have to use Nova::booted in order to have the translations available also at the login screen.

So, I thought that will be great if instead of updating and relying to a users' table field, to be able to play around with app()->setLocale() and therefore the login page issue will be solved because locale will be available across the project without any users table dependancy.

I gave it a try and replaced the code where locale column being updated (at onSwitchLocale) and used app()->setLocale($request->post('locale'));

No luck although, when I use dd(app()->getLocale()); I am always getting en, the default locale.

Any thoughts about what might going wrong and does not set the locale?

Thanks in advance.

dllobell commented 2 years ago

Hey @alexander-code!, thank you for asking.

The app()->setLocale($locale) method only sets the locale for the current request, on a subsequent request it gets back to the default locale configured in the app.php config file. So we need a way to persist the locale selected by the user so we can set it in every request using the Nova::serving event.

If you need to localize the login page, as you said, the example shown in the README is not enough, since we don't have the user of the current request.

One option I tried was to persist the locale in session, but when the user logs out the session gets invalidated losing the stored locale.

The other option I tried that worked is storing the selected locale in a cookie:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use Laravel\Nova\NovaApplicationServiceProvider;

class NovaServiceProvider extends NovaApplicationServiceProvider
{
    ...

    public function tools()
    {
        return [
            \Eolica\NovaLocaleSwitcher\LocaleSwitcher::make()
                ->setLocales(config('nova.locales'))
                ->onSwitchLocale(function (Request $request) {
                    $locale = $request->post('locale');

                    if (array_key_exists($locale, config('nova.locales'))) {
                        Cookie::queue('backend_locale', $locale);
                    }
                }),
        ];
    }
}

Now, for setting the locale in every request, if you want to use the Nova::booted event you will need to add the backend_locale cookie to the except array of the App\Http\Middleware\EncryptCookies middleware:

namespace App\Http\Middleware;

use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;

class EncryptCookies extends Middleware
{
    /**
     * The names of the cookies that should not be encrypted.
     *
     * @var array
     */
    protected $except = [
        'backend_locale',
    ];
}

This is due to the Nova::booted event to be executed before cookies are unencrypted.

Now, for setting the locale:

use Illuminate\Support\Facades\Cookie;
use Laravel\Nova\Nova;
use Laravel\Nova\NovaApplicationServiceProvider;

class NovaServiceProvider extends NovaApplicationServiceProvider
{
    public function boot()
    {
        parent::boot();

        Nova::booted(function() {
            if (Cookie::has('backend_locale')) {
                app()->setLocale(Cookie::get('backend_locale'));
            }
        });
    }
}

Another option, if you want the cookie to be encrypted, is using the Nova::serving event as always, but you will need to override the middleware applied to your nova authentication routes by adding the Laravel\Nova\Http\Middleware\DispatchServingNovaEvent middleware:

use Illuminate\Support\Facades\Cookie;
use Laravel\Nova\Http\Middleware\DispatchServingNovaEvent;
use Laravel\Nova\Nova;
use Laravel\Nova\NovaApplicationServiceProvider;

class NovaServiceProvider extends NovaApplicationServiceProvider
{
    public function boot()
    {
        parent::boot();

        Nova::serving(function() {
            if (Cookie::has('backend_locale')) {
                app()->setLocale(Cookie::get('backend_locale'));
            }
        });
    }

    protected function routes()
    {
        Nova::routes()
            ->withAuthenticationRoutes(['web', DispatchServingNovaEvent::class])
            ->withPasswordResetRoutes()
            ->register();
    }
}

Let me now if you can get it to work with this solution.

alexander-code commented 2 years ago

Hi, thank you very much for the thorough solution and explanation! I think will help others also in the future.

Cookie was one of the scenarios I was thinking as a workaround, I tried and it works properly, in fact I kept both of them, I am using locale field and cookie, the field in case that user wants to login from another pc so it will use the user's locale and cookie for login screen etc.