Using Laravel’s Localization in JS

Laravel provides an awesome and easy to use translation system. When we render our content on the back-end only, there is almost nothing to do but translate the strings in every language we need. But what if our app is a SPA and we still want to use the translations what Laravel provides? We can work around a bit to solve this issue.

Localization with Laravel

In modern web apps, it’s almost a requirement to provide internationalization (I18n) for the seamless and easier use. On the back-end side, we have an easy job, all we have to do, is to get familiar with the translation system and use it!

We can store our language files in the resources/langs directory. By default, we have an en folder where the language files are stored. We can add new languages to the system by copy the files in a folder what named by the ISO 639-1 code of the language. For example Hungarian is hu, Romanian is ro, French is fr and so on.

To get the current language, we can use the config(‘app.locale’) function or the App::getLocale() method. Also to set the language we can use the App::setLocale($lang) method, where the parameter is the correct ISO 639-1 code of the language.

The translator automatically gets the currently set language and retrieves the text what we need. We can use the trans() function and the @lang directive to translate the desired strings of the given key.

// Function
trans('auth.failed');

// Blade Directive
@lang('auth.failed');

We stop here because this post is not about back-end translation. But for sure you have a lot of options, like pluralization, JSON based translations and so on. Read the documentation to learn more about the API and the features.

Push the Translations Into a JS Object

We need to make our translations accessible on our front-end. There are many solutions to do that, we chose what we found the simplest in this case.

First of all, create a new service provider called TranslationServiceProvider to generate a JSON of all the translations. Then we should cache the results because it’s not changing often and it’s good to pay attention to the performance. As the last step, we need to print the JSON out and assign to the window object.

// app/Providers/TranslationServiceProvider.php

<?php 

namespace App\Providers; 

use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\File; 
use Illuminate\Support\Facades\Cache; 
use Illuminate\Support\ServiceProvider; 

class TranslationServiceProvider extends ServiceProvider 
{ 
    /** 
     * The path to the current lang files. 
     * 
     * @var string 
     */ 
    protected $langPath; 

    /** 
     * Create a new service provider instance. 
     * 
     * @return void 
     */ 
    public function __construct() 
    { 
        $this->langPath = resource_path('lang/'.App::getLocale());
    }

    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        Cache::rememberForever('translations', function () {
            return collect(File::allFiles($this->langPath))->flatMap(function ($file) {
                return [
                    ($translation = $file->getBasename('.php')) => trans($translation),
                ];
            })->toJson();
        });
    }
}
Don’t forget to register your provider in the config/app.php!

The code above makes nothing, but scan the directory named of the current language, then push the contents into a collection instance, make some modification and as a result generate a JSON format of the collection. Then we cache the result to make it accessible anywhere and to make it faster later.

There is nothing left but push the cached translations to the front-end. We can do just before the closing body tag.

<script>
    window.translations = {!! Cache::get('translations') !!};
</script>

Now we have the same translations on back-end and front-end too. But still, we need a translator on the JS side as well to get the proper strings by the given key.

The Translator Implementation in JS

Let’s stop here and think a bit about the functionality what we need here. Write a list of the features, it may help.

  1. We need the basic features of Laravel’s translator
  2. We want to retrieve a string paired with the given key
  3. We want to replace placeholders
  4. We want to pluralize

Start with the basics, let’s retrieve a string matched with the given key. Then try to replace the placeholders if we can. The placeholders have a special syntax, all of them starts with an : . It makes our life a bit easier to have this convention, we can replace them easily.

To make the code familiar we follow Laravel’s naming conventions.

function trans(key, replace = {})
{
    let translation = key.split('.').reduce((t, i) => t[i] || null, window.translations);

    for (var placeholder in replace) {
        translation = translation.replace(`:${placeholder}`, replace[placeholder]);
    }

    return translation;
}

We accept a key (like auth.failed, pagination.next), and an object, where the placeholder is the key without the colon and the value is the string what we need. For example:

{
   attempts: 30,
   attribute: 'Name'
}
Note, we could use lodash (_) to get values of an object, also to replace strings. For more complex things it’s cannot be avoided.

It’s working well, so we can move on the pluralization part. Laravel uses the trans_choice() to pluralize strings. The first parameter is the key, and the second one is the count. If the second argument is bigger than 1, the function returns the pluralized version. We need to separate the singular and the plural versions with an | character. For example:

[
    'attempts' => 'Be careful, you have :attempts attempt left.|You still have :attempts attempts left.',
]

So we need to do some extra work, but basically, we can copy-paste the code we have in the trans() function. We need to determine if the count is bigger than one and return with the proper part of the translation.

function trans_choice(key, count = 1, replace = {})
{
    let translation = key.split('.').reduce((t, i) => t[i] || null, window.translations).split('|');

    translation = count > 1 ? translation[1] : translation[0];

    for (var placeholder in replace) {
        translation = translation.replace(`:${placeholder}`, replace[placeholder]);
    }

    return translation;
}

Summary

Now we have a fully functional translation tool that uses the same source what Laravel provides. No need for AJAX requests or any special things, it’s simple and works well. Also, it’s very easy to integrate with Vue or other frameworks.

You can find the whole code what we use at this GitHub repo.

If you have an idea how to improve or extend it, please let us know! Thank you!