Laravel Localization – Multilingual

  • multilingual1

  • multilingual2

  • multilingual3

  • multilingual4

  • multilingual5

  • multilingual6


Source code: https://github.com/sunnygreentea/laravel-multilingual-recipes



Multilingual is a common demanded feature when building web applications, Laravel has a great feature “localization” which provides a convenient way to retrieve strings in various languages, allowing you to easily support multiple languages within various application.

This demo shows how to apply localization into a website, using recipes as an example. It mainly contains three sections:

(1) where and how to define strings and data need to be translated;

(2) how to config and change locale and how to do routes;

(3) how to switch language using language menu.


Section1: translation strings and dynamic data

There are two types of text displaying in the web pages: static strings and dynamic data stored in database, both must support multiple languages.

For static strings like a company slogan, Laravel provides a directory to store language translations: resources/lang directory. Within this directory there should be a subdirectory for each language supported by the application. All language files in each directory should return an array of keyed strings:

return [
    'welcome' => 'Welcome to our application',
];

An alternative way is using translation strings as keys. Translation files that use translation strings as keys are stored as JSON files in the resources/lang directory. In my case, I need a resources/lang/fr.json file for all French translations:

resources/lang/fr.json

{
"Home": "Accueil",
   ….
}

For data stored in database, I do not want to use Google translate to get translated data, so I added more columns into tables to store original data for each language, this way I just need to get the right data for the right language:

database/migrations/create_recipes_table.php

public function up()
    {
        Schema::create('recipes', function (Blueprint $table) {
            $table->id();
            $table->string('title_en');
            $table->string('title_fr');
            $table->text('ingradients_en');
            $table->text('ingradients_fr');
            $table->text('directions_en');
            $table->text('directions_fr');
            $table->timestamps();
        });
    }

Section 2: config locale and routing

Since I added locale (language) into all urls, the url structure becomes like /{lang}/recipes/1, I have to add a prefix and a middleware to the routes:

routes/web.php

Route::group([
	'prefix' => '{locale}', 
  	'where' => ['locale' => '[a-zA-Z]{2}'], 
  	'middleware' => 'setlocale'], function() {
  		Route::get('/', 'HomeController@index')->name('home');
    		Route::get('/recipes', 'RecipeController@index')->name('recipes');
    		Route::get('/recipes/{id}', 'RecipeController@recipe')->name('recipe');

});

Middleware:

app/Http/Middleware/SerLocale.php

the allowed languages are en and fr, for all other languages, it returns default language en:

public function handle($request, Closure $next)
    {        
        $locale = $request->segment(1);

        // Allowed locale: en and fr
        if (in_array($locale, ['en', 'fr'])) {
            app()->setLocale($locale);
        }
        // all others return locale en
        else {
            app()->setLocale('en');
        }
        return $next($request);
}

Then register middleware into Kernel.php, protected $routeMiddleware

app/Http/kernel.php

'setlocale' => \App\Http\Middleware\SetLocale::class

Next step is fetching and preparing data from controllers and displaying data from templates.

Since I have a prefix $locale in all the urls, the functions in controller have one more parameter $locale, where we can get current locale, recipe data stored in database is also modified according to current locale before passing into templates, so in the templates we do not need to worry about dynamic data. For static strings, Laravel will do the translation for us.

Here is the function to retrieve a single recipe data: I only need $recipe->title, $recipe-> ingredients, and $recipe-> directions in template file.

public function recipe ($locale, $id) 
    {
    	$recipe = Recipe::find($id);    	
    	$title_lang = "title_".$locale;
    	$recipe->title = $recipe->$title_lang;
    	$ingredients_lang = "ingredients_".$locale;
    	$recipe->ingredients = $recipe->$ingredients_lang;
    	$directions_lang = "directions_".$locale;
    	$recipe->directions = $recipe->$directions_lang;

    	return view('recipe', compact('locale', 'recipe'));
    }

Section 3: language switch menu

Until now if we change url from /en/recipes to /fr/recipes we can see all the text changes accordingly.

How about using a language menu to switch url instead of manually change? when switching url we want to “stay” in the same page, with translated text. To do this I added a helper function to change the locale segment of current url and return the new url:

function getLocaleChangerURL($locale)
{
	$baseUrl = URL::to('/');
        $uri = request()->segments();
       $uri[0] = $locale;    
       return $baseUrl.'/'.implode( '/', $uri);
}

Then in app/blade/php file, here is the language menu:

resources/views/layout/app.blade.php

<span class="navbar-text mr-5">
     <a class="btn @if ($locale=='en') btn-info @endif" href="{{getLocaleChangerURL('en')}}" class="mr-2"> English</a>
     <a class="mr-2 btn @if ($locale=='fr') btn-info @endif" href="{{getLocaleChangerURL('fr')}}">French</a>
</span>