Mahe Karim
Mahe Karim

Reputation: 47

How to set "app.locale" by Session in every Request in Laravel?

I am developing a localization in Laravel 10. When I triggered to change the flag of Localization Then Error Show in Page.

Can anyone tell Just where is the problem?

Error Message

Call to undefined method App\Http\Middleware\LocalizationMiddleware::setLanguage() in Localization

Here is my LocalizationController Controller

public function setLanguage($locale)
{
    App::setLocale($locale);
    Session::put('locale', $locale);

    return redirect()->back();
}

Here is my LocalizationMiddleware

public function handle(Request $request, Closure $next): Response
{
    // Set Locale in this Middleware
    App::setLocale(session()->get('selected_language') ?? 'en');

    return $next($request);
}

Here is my route

Route::get('locale/{locale}',[LocalizationMiddleware::class, 'setLanguage'])->name('locale');

And here is my Blade code

<div class="dropdown ms-1 topbar-head-dropdown header-item">
  <button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
    <img id="header-lang-img" src="{{ asset('/') }}els/images/flags/us.svg" alt="Header Language" height="20" class="rounded">
  </button>
  <div class="dropdown-menu dropdown-menu-end">
    <!-- English language -->
    <a href="locale/en" class="dropdown-item notify-item language py-2" data-lang="en" title="English">
      <img src="{{ asset('/') }}els/images/flags/us.svg" alt="user-image" class="me-2 rounded" height="18">
      <span class="align-middle">English</span>
    </a>
    <!-- German Language -->
    <a href="{{ url('locale/de') }}" class="dropdown-item notify-item language" data-lang="gr" title="German">
      <img src="{{ asset('/') }}els/images/flags/germany.svg" alt="user-image" class="me-2 rounded" height="18"> <span class="align-middle">Deutsche</span>
    </a>
  </div>
</div>

I don't understand why is this happening.

Upvotes: -1

Views: 6104

Answers (2)

rozsazoltan
rozsazoltan

Reputation: 7532

I see some confusion regarding the use of different modules: Middleware, Controller

Middleware

The Middleware runs before the task associated with the route is executed.. This includes starting a Session or checking authentication for an /admin route.

Controller

The Controller is the last thing that runs when a route is called. For example, displaying a blade, saving a record in the database, querying some data, etc.

enter image description here

Setting the Language

Now, let's focus on the original problem: you want to set the language so that the appropriate language content is loaded on the website.

You want the page to switch to English when a link like /locale/en is called. So, you want to do this after the link is called: using a Controller is recommended.

You can pass variables to the Controller, so you can also pass the language.

app/Http/Controllers/LocalizationController.php

// ...

class LocalizationController extends Controller
{
    public function setLanguage (Request $request)
    {
         // Save selected Locale to current "Session"
         $locale = $request->locale ?? 'en';
         // App::setLocale($locale); --> There is no need for this here, as the middleware will run after the redirect() where it has already been set.
         $request->session()->put('locale', $locale);

         return redirect()->back();
     }
}

Then in the route, you need to call the setLanguage function of LocalizationController::class and pass the language.

routes/web.php

Route::get(
    'locale/{locale}',
    [LocalizationController::class, 'setLanguage']
)->name('locale');

If you want to solve it with middleware, you won't have the opportunity to pass such variables. You need to know that the set language is en from somewhere. It's a good idea to save the set language in the session because you can access the stored values in the middleware. However, be careful in the future not to look for the selected language as selected_language if you saved it as locale before.

app/Http/Middlewares/LocalizationMiddleware.php

// ...

class LocalizationMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        // Set Locale in this "Request"
        $locale = $request->session()->get('locale') ?? 'en';
        app()->setLocale($locale);

        return $next($request);
    }
}

How can you set a middleware to run for a specific route in Laravel?

By default, all middleware specified in the web middleware group declared in app/Http/Kernel.php will run for every route. (Why? See boot() function in app/Providers/RouteServiceProvider.php, where we load routes/web.php) So, you need to add your own LocalizationMiddleware to the web group as follows:

app/Http/Kernel.php

protected $middlewareGroups = [
    'web' => [
        // ...
        \App\Http\Middleware\LocalizationMiddleware::class,
    ],
    // ...
];

Summary

How will the code run?

  1. By default, there is no language set, so every route where LocalizationMiddleware is set will prioritize English language (see code).
  2. I choose the German language: de, in reality, I called the /locale/de route, which triggered the LocalizationController setLanguage function. In my session, the value of the locale key was set to de, and at the end of the function, I was redirected back to the previous link. So, as a user, I didn't notice anything.
  3. Due to the redirection, the previous link (from where I called the language change) is reloaded, but now the LocalizationMiddleware runs again where the value associated with the locale key is de, so the middleware overrides the default language to German, and finally, the content is loaded in German from the /lang folder.

Extra

If you store the language in a cookie instead of a session, the next time you visit the page, it will be loaded based on the selected language.
The session is deleted after X time of inactivity. (It is configured in config/session.php in lifetime and deleted by default after 2 hours)

Upvotes: 2

Khang Tran
Khang Tran

Reputation: 2315

In your route, it should be LocalizationController instead. Try:

Route::get('locale/{locale}',[LocalizationController::class, 'setLanguage'])->name('locale');

Upvotes: 1

Related Questions