22
votes

I am using a dropdown lists for the languages, consisting of english and dutch.

<form class="" action="{{url('/locale')}}" method="post">
            Locale:
              <select class="" name="locale" onchange="this.form.submit()">

                <option value="en" >English</option>
                <option value="du" >Dutch</option>
              </select>
          </form>

Then this is my routes.php,

Route::post('/locale', function(){

     \App::setLocale(Request::Input('locale'));

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

And it is not working.

In my project, the path is like this

resources/
 /du
   navigation.php
 /en
  /navigation.php

From the Dutch(du) 'navigation.php'

<?php
return [
  "home" => 'Home-test-dutch',  
];

and for the English(en) 'navigation.php'

<?php
return [
  "home" => 'Home',  
];
5
setLocale is not persistent, so changing it then redirecting will leave it the default. Look to using sessions (or the database) and middleware to make this change permanent for the user. I should note, if this is a public facing web site, you should use different urls for different languages. - Devon

5 Answers

38
votes

App::setLocale() is not persistent, and sets locale only for current request(runtime). You can achieve persistent in multiple ways (example of 2):

Route::post('/locale', function(){

     session(['my_locale' => app('request')->input('locale')]);

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

This will set session key with lang value from request for current user. Next create a Middleware to set locale based on user session language

<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Foundation\Application;

class Language {

    public function __construct(Application $app, Request $request) {
        $this->app = $app;
        $this->request = $request;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $this->app->setLocale(session('my_locale', config('app.locale')));
        
        return $next($request);
    }

}

This will get current session and if is empty will fallback to default locale, which is set in your app config.

In app\Http\Kernel.php add previously created Language middleware:

protected $middleware = [
   \App\Http\Middleware\Language::class,
];

As global middlware or just for web (based on your needs). I hope this helps, and gives an idea on how things are working.

Scenario №2 - Lang based on URL path Create an array with all available locales on your app inside app config

'available_locale' => ['fr', 'gr', 'ja'],

Inside the Middleware we will check the URL first segment en, fr, gr, cy if this segment is in available_locale, set language

public function handle($request, Closure $next)
{
      if(in_array($request->segment(1), config('app.available_locale'))){
            $this->app->setLocale($request->segment(1));
      }else{
            $this->app->setLocale(config('app.locale'));
      }
            
      return $next($request);
}

You will need to modify app\Providers\RouteServiceProvider for setting prefix to all your routes. so you can access them domain.com or domain.com/fr/ with French language Find: mapWebRoutes And add this to it: (before add use Illuminate\Http\Request;)

public function map(Request $request)
    {
        $this->mapApiRoutes();
        $this->mapWebRoutes($request);
    }
    protected function mapWebRoutes(Request $request)
    {
        $locale = null;
        if(in_array($request->segment(1), config('app.available_locale'))){
          $locale = $request->segment(1);
        }

        Route::group([
           'middleware' => 'web',
           'namespace' => $this->namespace,
           'prefix' => $locale
        ], function ($router) {
             require base_path('routes/web.php');
        });
    }

This will prefix all your routes with country letter like 'fr gr cy' except en for non-duplicate content, so is better to not add into available_locales_array

10
votes

I solved my problem from this article https://mydnic.be/post/laravel-5-and-his-fcking-non-persistent-app-setlocale

Thanks to the people who contributed the word 'non persistent'

4
votes

App::setLocale() is not persistent. I had a similar problem before so I created a middleware:

<?php

namespace App\Http\Middleware;

use Closure;

class SetLocale
{
     /**
      * Handle an incoming request.
      *
      * @param  \Illuminate\Http\Request  $request
      * @param  \Closure  $next
      * @return mixed
      */
    public function handle($request, Closure $next)
    {
        if (strpos($request->getHttpHost(), 'fr.') === 0) {
            \App::setLocale('fr');
        } else {
            \App::setLocale('en');
        }
        return $next($request);
    }
}

And I registered this middleware in app\Http\Kernel:

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

This script works for two domains: http://example.org (en) and http://fr.example.org (fr). As a middleware, it's called on every request, so the locale is always set as the right locale according to the url.

My routes looked like:

Route::group(['domain' => 'fr.' . config('app.root-domain')], function () {
    Route::get('a-propos', 'HomeController@about');
    // ...
}
Route::group(['domain' => config('app.root-domain')], function () {
    Route::get('about', 'HomeController@about');
    // ...
}

So it responds with the correct locale to:

And I use the same controller and same view, just 2 different routes + a global middleware.

Hope it will help, not sure it's the best solution BTW. This solution works without sessio, it matches with domain and/or routes. It has some advantages over session-based solutions:

  • No possible bugs due to session usage ("magic" language switch)
  • You can rewrite your routes. A french user may want to see "/mon-panier" and english user "/my-cart" in their url.
  • Better indexing in google (SEO), because you can have a real index by country with relevant content.
  • I use it in production!

It may have it's cons too.

2
votes

When user selects language from dropdown, call below route to save language in session

Using Vue.js

LocalizationComponent.vue change language

<template>
<ul>
    <li @click="changeLanguage('en')">
        <a href="javascript:void(0);">
            <img src="/images/flag-1.png"  alt="image description">
            <span>ENG</span>
        </a>
    </li>
    <li @click="changeLanguage('vn')">
        <a href="javascript:void(0);">
            <img src="/images/flag-2.png"  alt="image description">
            <span>Việt</span>
        </a>
    </li>
</ul>

<script>
    export default{
        data(){
            return {
                selected_language:'en',
            }
        },
         methods:{
            changeLanguage(language){
                this.axios.post('/change-locale',{language:language}).then( 
                (response) => {window.location.reload();
            }).catch((error) => {
                console.log(error.response.data.errors)
            });
            localStorage.setItem('selected_language',language);
        }
    }
}
</script>

routes/web.php

Route::post('/change-locale', 'HomeController@changeLocale');

//save locale to session

public function changeLocale(Request $request)
{
    $language = $request->language ?? 'en';
    session(['selected_language' =>$language]);
}

//create middleware to set locale

class Localization
{
    public function handle($request, Closure $next)
    {
        App::setLocale(session()->get('selected_language') ?? 'en');
        return $next($request);
    }
}

In app/Http/Kernel.php

 protected $middlewareGroups = [
    'web' => [
        ...other middelwares
        Localization::class,  //add your localization middleware here
    ],
    //...
];

Done...

0
votes

setLocale will only change the language for the rest of the request from that point on.

So to persist it, you could look into setting the selected language in a session (https://laravel.com/docs/session). Then you can create a middeware (https://laravel.com/docs/middleware) where you can check if a language is set in the session and then apply it for the request :)