Posted in Web Development • •
Build Multilingual Laravel App - The Right Way
How to configure a Laravel application to support multiple languages
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 variations | Objective |
---|---|
https://ourapp.com/en/page | English language page for displaying the content in English |
https://ourapp.com/bn/page | Bangla language page for displaying the content in Bangla |
https://ourapp.com | Default 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 SetDefaultLocaleForUrls
middleware, 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.