Laravel localization: A step-by-step guide

In this tutorial, you will learn how to perform Laravel localization and present your application in multiple languages. You will learn how to work with translation files, perform pluralization, create a language switcher, and more.

Localization is the second phase of the Laravel internationalization (i18n) process. In Laravel i18n, an application is designed to fit various languages and cultures. Localization is adapting said internationalized applications to a specific language through translation.

So, shall we begin?

Huge thanks go to my colleague Andrejs Bekešs, who prepared the code samples and technical explanations for this article.

Prerequisites and assumptions

  • We will be using Laravel version 8.x in this tutorial.
  • This tutorial has been created with the assumption that you have the necessary knowledge of the PHP programming language and the Laravel framework.
  • Your domain is localhost. If not, then replace localhost with your domain name or IP address (depending on your installation).

Working with translation files

So, in Laravel, just like in many other frameworks, you will store translations for different languages in separate files. There are two ways to organize Laravel translation files:

  • An old approach which involves storing your files under the following path: resources/lang/{en,fr,ru}/{myfile.php}.
  • A new approach of having resources/lang/{fr.json, ru.json} files.

For languages that differ by territory, you should name the language directories/files according to ISO 15897. For example, for British English you would use en_GB rather than en-gb. In this article, we will focus on the second approach, but the same applies to the first one (with the exception of how translation keys are named and retrieved). Also, we’ll be using the default welcome.blade.php file as our playground.

Simple translations

Now, let’s head over to the resources/views/welcome.blade.php file and replace the contents of the body tag with our own, like so:

<body class="antialiased">
    <div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center py-4 sm:pt-0">
        <div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
            <div class="flex justify-center pt-8 sm:justify-start sm:pt-0">
                Welcome to our website
            </div>
        </div>
    </div>
</body>

We’ll start by preparing our welcome message for localization, which is really easy in Laravel. All you need to do is replace the “Welcome to our website” text with the following code: {{ __('Welcome to our website') }}. This will instruct Laravel to display “Welcome to our website” by default and look for translations of this string if a non-English language is set (we’ll get to this later on). English will be set as a default language of our app, so by default we will simply display the “Welcome to our website” text. If the locale is different, we’ll try to search for the corresponding translation that will be created in a moment.

Locales in Laravel

But how does Laravel know what the current language is or what languages are available in the application? It does this by looking at the locale setup in the config/app.php app. Open up this file and look for these two array keys:

/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/

'locale' => 'en',

/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/

'fallback_locale' => 'en',

The descriptions shown above the keys should be self-explanatory, but in short, the locale key contains the default locale of your application (at least, if no other locale was set in the code). The fallback_locale is the one being activated in the case that we set a non-existent locale in our application.

While we have this file open, let’s add a new key in here for our convenience later on by listing all the locales that our application is going to support. We’ll use this later when adding a locale switcher. However, this is an optional task as Laravel does not require us to do it.

/*
|--------------------------------------------------------------------------
| Available locales
|--------------------------------------------------------------------------
|
| List all locales that your application works with
|
*/

'available_locales' => [
  'English' => 'en',
  'Russian' => 'ru',
  'French' => 'fr',
],

Great! Now our application supports three languages: English, Russian, and French.

Translation files

Now that we have established all the locales we’ll work with, we can go ahead and move on to translating our default welcome message.

Let’s start by adding new localization files in the resources/lang folder. First, create a resources/lang/ru.json file and add the corresponding translations, as follows:

{
  "Welcome to our website": "Добро пожаловать на наш сайт"
}

Next, create a resources/lang/fr.json file:

{
  "Welcome to our website": "Bienvenue sur notre site"
}

As you can see, we’re always referencing the default message that we added in the welcome.blade.php file (which was {{ __('Welcome to our website') }}). The reason we don’t have to create an en.json file is because Laravel already knows that messages we pass in by default to the __() function are for our default locale, which is en (as explained in the section above).

Switching locales in Laravel

At this point, Laravel does not know how to switch locales so let’s perform translations directly inside the route for now. Edit the default welcome route as shown below:

Route::get('/{locale?}', function ($locale = null) {
    if (isset($locale) && in_array($locale, config('app.available_locales'))) {
        app()->setLocale($locale);
    }
    
    return view('welcome');
});

We can now visit our website, specifying any of the available languages as the first route segment: for example, localhost/ru or localhost/fr. You should see the localized content. In the case that you specify a non-supported locale or don’t specify a locale at all, Laravel will use en by default.

Middleware

Passing the locale for every site link might not be what you want and could look not quite so clean esthetically. That’s why we’ll perform language setup via a special language switcher and use the user session to display the translated content. Therefore, create a new middleware inside the app/Http/Middleware/Localization.php file or by running artisan make:middleware Localization.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Session;

class Localization
{
    /**
    * Handle an incoming request.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  \Closure  $next
    * @return mixed
    */
    public function handle(Request $request, Closure $next)
    {
        if (Session::has('locale')) {
            App::setLocale(Session::get('locale'));
        }
        return $next($request);
    }
}

This middleware will instruct Laravel to utilize the locale selected by the user if this selection is present in the session.

Since we need this operation to be run on every request, we also need to add it to the default middleware stack at app/http/Kernel.php for the web middleware group:

* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
  'web' => [
      \App\Http\Middleware\EncryptCookies::class,
      \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
      \Illuminate\Session\Middleware\StartSession::class,
      // \Illuminate\Session\Middleware\AuthenticateSession::class,
      \Illuminate\View\Middleware\ShareErrorsFromSession::class,
      \App\Http\Middleware\VerifyCsrfToken::class,
      \Illuminate\Routing\Middleware\SubstituteBindings::class,
      \App\Http\Middleware\Localization::class, /* <--- add this */
  ],

Changing routes

Next, we have to add a route to change locale. We’re using a closure route, but you can use exactly the same code inside your controller if you wish:

Route::get('language/{locale}', function ($locale) {
    app()->setLocale($locale);
    session()->put('locale', $locale);

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

Also, don’t forget to remove the previously added locale switching previously added in our default welcome route:

Route::get('/', function () {
    return view('welcome');
});

With this being done, the only way for the user to switch the currently set language is by entering localhost/language/{locale}. The locale selection will be stored inside the session and redirect users back to where they came from (check the Localization middleware). To test it out, head over to localhost/language/ru (as long as your session cookie is present in your browser), and you will see the translated content. You can freely move around the website or try to refresh the page and see that the selected language is being preserved.

The actual switcher

Now we need to create something that the user can click on to change the language instead of entering locale codes into the URL manually. To do this, we’ll add a very simple language control. Therefore, create a new resources/views/partials/language_switcher.blade.php file with the following code:

<div class="flex justify-center pt-8 sm:justify-start sm:pt-0">
    @foreach($available_locales as $locale_name => $available_locale)
        @if($available_locale === $current_locale)
            <span class="ml-2 mr-2 text-gray-700">{{ $locale_name }}</span>
        @else
            <a class="ml-1 underline ml-2 mr-2" href="language/{{ $available_locale }}">
                <span>{{ $locale_name }}</span>
            </a>
        @endif
    @endforeach
</div>

Include the newly created switcher into the “welcome” view:

<body class="antialiased">
    <div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center py-4 sm:pt-0">
        <div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
            @include('partials/language_switcher')
            <div class="flex justify-center pt-8 sm:justify-start sm:pt-0">
                {{ __('Welcome to our website') }}
            </div>
        </div>
    </div>
</body>

Open the app/Providers/AppServiceProvider.php file and add the code to be shared when our language switcher is composed. Specifically, we’ll share the current locale that can be accessed as {{ $current_locale }}.

* Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    view()->composer('partials.language_switcher', function ($view) {
        $view->with('current_locale', app()->getLocale());
        $view->with('available_locales', config('app.available_locales'));
    });
}

Advanced translation options in PHP Laravel

We will mostly be working with resources/views/welcome.blade.php, so everything needs to happen in our welcome view unless specified otherwise.

Parameters in translation strings

For instance, let’s greet our imaginary user (Amanda) instead of simply displaying a generic message:

{{ __('Welcome to our website, :Name', ['name' => 'amanda']) }}

Please note that we have used the name with a lowercase first letter, but the placeholder with an uppercase first letter. In this way, Laravel can help you capitalize the actual word automatically. This will occur if the placeholder starts with a capital letter, i.e., :Name produces “Amanda” or a full uppercase word,  :NAME, produces “AMANDA”.

Additionally, let’s update our resources/lang/fr.json and resources/lang/ru.json translation files, since right now we’ll only see the English version everywhere as the translation keys do not match the translations.

French:

{
  "Welcome to our website, :Name": "Bienvenue sur notre site, :Name"
}

Russian:

{
  "Welcome to our website, :Name": "Добро пожаловать на наш сайт, :Name"
}

Great job!

Pluralization

To see pluralization in action, let’s add a new paragraph of text. To perform pluralization, you have to use the trans_choice function instead of __(), for instance:

{{ __('Welcome to our website, :Name', ['name' => 'amanda']) }}
<br>
{{ trans_choice('There is one apple|There are many apples', 2) }}

As you can see, the plural forms are separated with a |.

Now, what if we need more plural forms? This is possible as well:

{{ trans_choice('{0} There :form no apples|{1} There :form just :count apple|[2,19] There :form :count apples', 24) }}

In this case, we allow the numbers 0, 1, from 2 to 19, and finally from 20 onwards. Of course, you can add as many rules as you need.

Next, what if we want placeholders in our plural forms? No problem with that either:

{{ trans_choice('{0} There :form no apples|{1} There :form just :count apple|[2,19] There :form :count apples', 24, ['form' => 'is']) }}

We can also use count passed into `trans_choice`, if needed, by using a special :count placeholder:

{{ trans_choice('{0} There :form no apples|{1} There :form just :count apple|[2,19] There :form :count apples', 1, ['form' => 'is']) }}

Finally, don’t forget to update your translation files with all the changes we made to the base translation.

Russian:

{
  "Welcome to our website, :Name": "Добро пожаловать на наш сайт, :Name",
  "{0} There :form no apples|{1} There :form just :count apple|[2,19] There :form :count apples": "{0} Нет яблок|{1} Только :count яблоко|[2,19] :count яблок"
}

French:

{    
  "Welcome to our website, :Name": "Bienvenue sur notre site, :Name",
  "{0} There :form no apples|{1} There :form just :count apple|[2,19] There :form :count apples": "{0} Il n'y a pas de pommes|{1} Il n'y :form :count pomme|[2,19] Il y :form :count pommes"
}

Working with localized dates in Laravel

To localize dates, we will be leveraging the power of Carbon, which is shipped with Laravel by default. Take a look at the Carbon documentation; you can do lots of cool things with it. For instance, we could invent our own locale with rules for date and time localization.

For our simple example, we will display the current date localized for the selected language. In our routes/web.php, let’s update the welcome page route and pass the localized date message over to our welcome view:

<?php
Route::get('/', function () {
    $today = \Carbon\Carbon::now()
        ->settings(
            [
                'locale' => app()->getLocale(),
            ]
        );

    // LL is macro placeholder for MMMM D, YYYY (you could write same as dddd, MMMM D, YYYY)
    $dateMessage = $today->isoFormat('dddd, LL');

    return view('welcome', [
        'date_message' => $dateMessage
    ]);
});

Let’s update resources/views/welcome.blade.php and add our date message, like so:

{{ __('Welcome to our website, :Name', ['name' => 'amanda']) }}
<br>
{{ trans_choice('{0} There :form :count apples|{1} There :form just :count apple|[2,19] There :form :count apples', 1, ['form' => 'is']) }}
<br>
{{ $date_message }}

Try switching languages on the localhost homepage — you’ll see that the dates are now localized, for example:

 

Formatting numbers and currencies with NumberFormatter

In different countries, people use different formats to represent numbers, for example:

  • The United States → 123,123.12
  • France → 123 123,12

Thus, to reflect these differences in your Laravel app, you could use NumberFormatter in the following way:

<?php
$num = NumberFormatter::create('en_US', NumberFormatter::DECIMAL);

$num2 = NumberFormatter::create('fr', NumberFormatter::DECIMAL);

You can even spell out the number in a particular language and display something like “one hundred twenty-three thousand one hundred twenty-three point one two”:

<?php
$num = NumberFormatter::create('en_US', NumberFormatter::SPELLOUT);

$num2 = NumberFormatter::create('fr', NumberFormatter::SPELLOUT);

On top of that, NumberFormatter enables you to localize currencies with ease, for example:

<?php
$currency1 = NumberFormatter::create('fr', NumberFormatter::CURRENCY);

$currency2 = NumberFormatter::create('en_US', NumberFormatter::CURRENCY);

So, for fr you will see Euros, while for en_US the currency will be US dollars.

Simplifying the translation process with Lokalise

The actual translation of all your text is probably the most time-consuming process of Laravel localization. However, finding a good translation management solution like Lokalise can rescue you from ending up with a lot of work on your hands. With Lokalise, the translation process can be carried out in just a few steps. Here are some basic guidelines on how to do it:

  • Grab your free trial to continue.
  • Next, install the Lokalise CLI. You can use it to create projects and upload and download translation files. Of course, there’s also a GUI available.
  • Open the Lokalise website and proceed to the “API tokens” section on your personal profile page. Generate a new read/write token.
  • Create a new translation project and set English as the base language.
  • Open project settings and copy the project ID.
  • Next, to upload your Laravel translation files to Lokalise, run: lokalise2 file upload --token <token> --project-id <project_id> --lang_iso en --file PATH/TO/PROJECT/resources/lang/en.json.
  • Return to the newly created Lokalise project. All your translation keys and values should be there. You can modify them as much as you like by editing, deleting, and adding new ones. You can filter the keys; for example, you can find the untranslated ones which is very convenient.
  • When you are ready, download the edited translations back to Laravel by running: lokalise2 file download --token <token> --project-id <project_id> --format json --dest PATH/TO/PROJECT/resources/lang.

Lokalise supports many platforms and formats. With its multiple features, you have the ability to order translations from professionals, upload screenshots in order to read text from them, and lots more. So, integrate your applications with Lokalise today and make your life a whole lot easier.

Conclusion

In this article, we have seen how to get started with Laravel localization. We’ve discussed how to perform translations, use placeholders, take advantage of pluralization, and how to add a language switcher. Hopefully, you found this article interesting and useful. Thank you for dropping by today and until the next time!

Related posts

Sign up to our newsletter

Get the latest articles on all things data delivered straight to your inbox.

Read also
Localization made easy. Why wait?
The preferred localization tool of 2000+ companies