Posted in Web Development

Build Multilingual Laravel App - The Right Way

How to configure a Laravel application to support multiple languages

Build Multilingual Laravel App - The Right Way
Photo by Edurne Tx / Unsplash

Nowadays, most of the web applications and web sites have worldwide user base. To serve contents and services in their local language, it's necessary to have multiple versions of site in different languages. In Laravel based applications, the framework has several awesome helpers to support multiple locale but, often the default one is not the most SEO friendly one. We need to tweak a bit to have separate URLs for different locales. This approach will help us index all of the pages from all supported languages in search engines.

In this tutorial, we will implement our application to support two languages, English en and Bangla bn:

URLs with language variationsObjective
https://ourapp.com/en/pageEnglish language page for displaying the content in English
https://ourapp.com/bn/pageBangla language page for displaying the content in Bangla
https://ourapp.comDefault page which redirects to default locale of the app

Prepare the translation files

The default translation files for English language can be found at resources/lang directory of the Laravel application's source. Each of the languages will have their separate directory for translation files. Along with the default en directory, let's create a new directory resources/lang/bn. Also, we can provide translations for simple sentences directly into a JSON file. Create a file named bn.json at resources/lang directory. Then, provide translations into JSON format:

{
    "Welcome": "স্বাগতম",
    "Home": "হোম",
    "Blog": "ব্লগ",
    "About Us": "আমাদের সম্পর্কে"
}

Define the routes

We want the routes to be like ourapp/locale/page where locale will be the language prefix (en, bn, fr, ru etc). So that, we have to set a locale/language prefix in all of the routes in our application. In web.php file:

Route::group([
    'prefix' => '{locale}',
    'where' => ['locale' => 'en|bn']
], function() {
    Route::get('/','PagesController@home')->name('home');
    Route::get('blog','BlogController@index')->name('blog.index');
    Route::get('blog/{slug}','BlogController@show')->name('blog.show');
});

Here, the {locale} will be en or bn, in the where section, we have set constraints, so that locale should always be en or bn. This way we can have following types of language specific routes for our application:

  • ourapp.com/en/about
  • ourapp.com/bn/about
  • ourapp.com/en/blog
  • ourapp.com/bn/blog

and so on...

Make Language Middleware

Now, create a middleware to set application's current language based on the URL. For example, if a user visits https://ourapp.com/en/blog the middleware will set English (en) as the app locale. On the other hand, if a user visits https://ourapp.com/bn/blog the middleware will set Bangla (bn) as the locale and so on for other languages.

Create SetLocale middleware:

php artisan make:middleware SetLocale

In the app\Http\Middleware\SetLocale.php file:

public function handle($request, Closure $next)
{
    if(!is_null($request->locale))
        app()->setLocale($request->locale);

    return $next($request);
}

Now, register the middleware in app\Http\Kernel.php file's middlewareGroups array:

\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SetLocale::class, // add this one

Displaying translations in the front end

In our blade template files, we can display the translations we have previously set in the bn.json file by using global __() helper function of Laravel. Let's say we want to show Welcome in the English version and স্বাগতম in the Bangla version of our resources/views/index.blade.php file. Then we have to use the translation helper function:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ __('Welcome') }}</title>
</head>
<body>
    <p>
        {{ __('Welcome') }}
    </p>
</body>
</html>

Generate routes in blade files

One challenge we have to deal with is, generating routes using route() helper function of Laravel. This function accepts named routes and also some route parameters. For example, we have a blog.show route

Route::get('blog/{slug}','BlogController@show')->name('blog.show');

which shows a single blog post. This route accepts a slug parameter. To generate a route for a single blog, we can use route() helper:

<a href="{{ route('blog.show', ['slug'=> 'Blog Slug goes here']) }}">Blog Title</a>

But, this will generate an exception as we have defined another route parameter named locale in our web.php route definition. We can avoid the error by adding another parameter for the current locale of our app:

<a href="{{ route('blog.show', ['slug'=> 'Blog Slug goes here', app()->getLocale() ]) }}">Blog Title</a>

But, adding the locale parameter in every calls of route() function is pretty cumbersome and prone to error. We can avoid this by adding a middleware which will set the locale parameter's value by default. Let's generate a middleware:

php artisan make:middleware SetDefaultLocaleForUrls

In the app\Http\Middleware\SetDefaultLocaleForUrls.php file:

<?php

namespace App\Http\Middleware;
use Illuminate\Support\Facades\URL;
use Closure;

class SetDefaultLocaleForUrls
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        URL::defaults(['locale' => $request->locale]);

        return $next($request);
    }
}

You can read official documentation on setting a default value for route helper here. Now, add the middleware in Kernel.php file's routeMiddleware array:

'set.default.url.locale' => \App\Http\Middleware\SetDefaultLocaleForUrls::class,

and add the middleware in our previously defined route group:

Route::group([
    'prefix' => '{locale}',
    'where' => ['locale' => 'en|bn'],
    'middleware' => ['set.default.url.locale']
    ...

Following this approach, we will encounter an issue while our app will be sending verification email to newly registered users. The verification route generator doesn't go through SetDefaultLocaleForUrlsmiddleware, thus the locale parameter won't be set. One easy workaround is to keep authentication routes out of the route group we defined earlier. In web.php file, put this line outside the route group:

Auth::routes(['verify'=>true]);

Add language switcher and SEO

Now we need to add a language switcher in our blade template of the navigation bar or somewhere else. First, we have to get all the URL parameters from the route and replace the locale parameter with the alternate language (If current locale is en, we need to have bn in the language switcher). Inside the <head> tag of the master or common layout:

<head>
    @php
        $ROUTE_PARAMS = Route::current()->parameters();
        $ROUTE_PARAMS['locale'] = (app()->getLocale() == 'bn') ? 'en' : 'bn';
        $ALTERNATE_LOCALE_ROUTE = route(Route::currentRouteName(), $ROUTE_PARAMS);
    @endphp
</head>

If the current page's URL is https://ourapp.com/en/blog/laravel-is-awesome, we will have https://ourapp.com/bn/blog/laravel-is-awesome in our $ALTERNATE_LOCALE_ROUTE variable. Most importantly, all of the route parameters will be well preserved in the URL. You can use this variable to generate our language switcher anywhere you wish:

<a href="{{ $ALTERNATE_LOCALE_ROUTE }}">Switch Language</a>

We obviously want to index pages from all of the available languages (English and Bangla in this tutorial) in Google search engine. According to Google developer doc we can do this by using hreflang elements.

Add <link rel="alternate" hreflang="lang_code"... > elements to your page header to tell Google all of the language and region variants of a page

For our application, we can add this element inside <head> tag of master layout by using our good old $ALTERNATE_LOCALE_ROUTE variable and $ROUTE_PARAMS['locale'] :

<link rel="alternate" hreflang="{{ $ROUTE_PARAMS['locale'] }}" href="{{ $ALT_LOCALE_ROUTE }}" />

This more or less sums up the whole multilingual application building process in Laravel.

Disclaimer: This tutorial is based on Laravel 7.x. For other Laravel versions, you may need to change few things.