DEV Community

Daiyrbek Artelov
Daiyrbek Artelov

Posted on • Edited on

How to use Laravel translations in JS (vue) files?

After some research on this topic, I found out that none of the existing solutions satisfy my needs. Most of them required either recompilation of JS assets or running some artisan command after editing or adding new translations and I don't like that approach. Maybe there are already some solutions, but I remember that I have seen something that I need in Laravel Nova.

So I checked the sources of Laravel Nova and found out that Laravel translations were loaded as JSON from translation files and then passed to the Blade template. In the Blade it was a simple assignment to global config variable.

The problem with that solution was, that it loaded only JSON translations and Laravel also has support for PHP phrases.

After some googling, I found an article, where the author showed how to load PHP language phrases to JS.

I mixed both approaches from Laravel Nova sources and from the article above and in the end I got, I think, the simplest way to use Laravel translation strings in JS files.

First of all create a translations service provider:

<?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
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        Cache::rememberForever('translations', function () {
            $translations = collect();

            foreach (['en', 'kg', 'ru'] as $locale) { // suported locales
                $translations[$locale] = [
                    'php' => $this->phpTranslations($locale),
                    'json' => $this->jsonTranslations($locale),
                ];
            }

            return $translations;
        });
    }

    private function phpTranslations($locale)
    {
        $path = resource_path("lang/$locale");

        return collect(File::allFiles($path))->flatMap(function ($file) use ($locale) {
            $key = ($translation = $file->getBasename('.php'));

            return [$key => trans($translation, [], $locale)];
        });
    }

    private function jsonTranslations($locale)
    {
        $path = resource_path("lang/$locale.json");

        if (is_string($path) && is_readable($path)) {
            return json_decode(file_get_contents($path), true);
        }

        return [];
    }
}
Enter fullscreen mode Exit fullscreen mode

Register it in config/app.php file:

'providers' => [
    // your other providers
    App\Providers\TranslationServiceProvider::class,
],
Enter fullscreen mode Exit fullscreen mode

Then you have to pass translation strings to JS in blade template. I did it in the default layouts/app.blade.php file:

<script>
    window._locale = '{{ app()->getLocale() }}';
    window._translations = {!! cache('translations') !!};
</script>
Enter fullscreen mode Exit fullscreen mode

Now you need some js function to retrieve translations and apply replacements. To do that I have created a trans.js file:

module.exports = {
    methods: {
        /**
         * Translate the given key.
         */
        __(key, replace) {
            let translation, translationNotFound = true

            try {
                translation = key.split('.').reduce((t, i) => t[i] || null, window._translations[window._locale].php)

                if (translation) {
                    translationNotFound = false
                }
            } catch (e) {
                translation = key
            }

            if (translationNotFound) {
                translation = window._translations[window._locale]['json'][key]
                    ? window._translations[window._locale]['json'][key]
                    : key
            }

            _.forEach(replace, (value, key) => {
                translation = translation.replace(':' + key, value)
            })

            return translation
        }
    },
}

Enter fullscreen mode Exit fullscreen mode

It's a bit modified version of base.js from Laravel Nova which also loads PHP translations. In short, the logic is: First try to find translation string in PHP translations, if not found, then try to find in JSON translations. If translation was not found at all, then it will show the key itself.

And the last step is to include the method as a mixin:

Vue.mixin(require('./trans'))
Enter fullscreen mode Exit fullscreen mode

That's it. Now you can use translations in Vue components like so:

<template>
<div class="card">
    <div class="card-header">{{ __('Example Component') }}</div>

    <div class="card-body">
        {{ __("I'm an example component.") }}
    </div>
</div>
</template>

<script>
export default {
    mounted() {
        console.log(this.__('Component mounted.'))
    }
}
</script>
Enter fullscreen mode Exit fullscreen mode

With this solution, the only thing you have to do after editing/adding new translations is to run cache:clear artisan command. Laravel Nova (which we use in our projects) has packages, that allow executing such commands right from the admin panel, so that is not an issue at all.

Update 25.02.2020

The previous solution was working fine only on single locale. Thanks to @morpheus_ro to pointing out. Now the solution covers all the locales specified in the app.

Top comments (31)

Collapse
 
morpheus_ro profile image
Alecs • Edited

hey,

thank you for the nice article but i think this is not correct.
I implemented it as described and I have this error

[Vue warn]: Error in render: "TypeError: _.forEach is not a function"

I tried to import that from odash and still there is another error... Also where so you put the Vue.mixin(require('./trans')) ? I've put it after the registration of components in app.js. Is this correct?

Collapse
 
4unkur profile image
Daiyrbek Artelov

Hi @Alecs,

Make sure you have lodash imported and globally available. I have my imports in /resources/js/bootstrap.js file which is then imported in the app.js file:

import _ from 'lodash'
window._ = _
Enter fullscreen mode Exit fullscreen mode

I've put it after the registration of components in app.js. Is this correct?

Yes, I have them right there in bootstrap.js file after components registration.

Collapse
 
morpheus_ro profile image
Alecs • Edited

hey @4unkur ,

thank you for the reply first of all.
I was having the require bootstrap commented out.

I have one last question and that is what is the idea behind this hole scripts/logic? we cache the translations one time for life and then use it in the app? how can a person extend this to switch the languages if only one language is cached?

Thread Thread
 
sandman83 profile image
Sandman • Edited

@Alecs:
I solved this problem by combining this solution with the solution described here:
laraveldaily.com/multi-language-ro...

Then, the problem of once-in-life caching is still there, however, you have a middleware, which you can use for all necessary routes.
I moved the content of the TranslationServiceProvider's boot method into the handle method of the SetLocale middleware, this granted the calls to Cache::rememberForever in a regular manner.

My technical debt:
I observed, that
$tmpLoc = app()->getLocale();
$tmpSeg = $request->segment(1);
behave not as expected inside the handle method of SetLocal. I.e. while
$tmpSeg = $request->segment(1);
behaves as expected,
$tmpLoc = app()->getLocale();
sometimes overtakes and is set to the current language even before the line
app()->setLocale($request->segment(1));

The consequence is, that the cache is not refreshed in all circumstances it should be. However, I added another field to the cache wherein I track the current language manually. I check this field inside the handle method instead of app()->getLocale(), compare it to $request->segment(1) and if they do not match I call Cache::forget('translations');

So, in the end, I assume, the middleware handle method is the proper place to apply the translation, but there are calls somewhere, which change the app localization even before the middleware is called.

Thread Thread
 
4unkur profile image
Daiyrbek Artelov

@Alecs

Thanks for pointing out. I was busy and not able to check it until now and I can confirm that the solution is not working as expected. I'll probably update the article, but currently, I have no idea how can I solve it. Do you have any ideas?

@Sandman

Your solution seems to be in the right direction, I'll check that too.

Thanks.

Thread Thread
 
4unkur profile image
Daiyrbek Artelov

@morpheus_ro , @sandman83
Please check the updated version guys

Collapse
 
aefika profile image
Luis Nicho

I think it is better to import the single function you need from lodash instead of importing the whole library: window._forEach = require('lodash/forEach');

Collapse
 
anejjar profile image
anejjar Elhoucine

Hello bro,
i'm trying to use your solution but i got some errors like
error
this error comes from this block of code
if (translationNotFound) {
translation = window._translations[window._locale]['json'][key]
? window._translations[window._locale]['json'][key]
: key
}

Collapse
 
abalest14 profile image
abalest14

i have the same error how did you solved?
thanks

Collapse
 
anejjar profile image
anejjar Elhoucine • Edited

you should just check if window._translations is set or not
dev-to-uploads.s3.amazonaws.com/i/...

Thread Thread
 
abalest14 profile image
abalest14

thanks this fixed the console error. Another problem i get window._translations undefined and con't understand what i make wrong is like is not passing window._translations from blade to JS. You have some advise?

Thread Thread
 
anejjar profile image
anejjar Elhoucine

sceenshot
did you put this in app.blade.php, but sometimes the {!! cache('translations') !!} is not returning any data so the window._translations is null

Thread Thread
 
r4nd3l profile image
Matt Miller

Do you have any updates so far? I've struggling with that particular issue and it's occur totally random. I would very appreciate if so!

Thread Thread
 
anejjar profile image
anejjar Elhoucine

i used same package but i found a lot of issus and i have updated the code many times to work for me. and i dont know how to explain my method to you unless i see your code and the error your are having

Collapse
 
ulimn profile image
Ulimn • Edited

Exactly the solution I was looking for! Thank you!

P.S.: I had to extend this to handle locale switching, but it was only a few lines of code.

Collapse
 
4unkur profile image
Daiyrbek Artelov

You are welcome!

Can you share those lines?

Collapse
 
patricnox profile image
PatricNox

Great guide, it's super beneficial to know and also works great, thank you for this!

I did however need to adjust the provider a little, to automatically detect which languages the transliteration should support. I achieved this by recursively scanning every folder within resources/lang, and get the locales from there.

App/Providers/TranslationServiceProvider.php

/**
 * Bootstrap the application services.
 *
 * @return void
 */
public function boot()
{
    Cache::rememberForever('translations', function () {
        $translations = collect();
        $locales = array_map(
            function($dir) {
                return basename($dir);
            }, glob('../../resources/lang/*')
        );

        foreach ($locales as $locale) {
            $translations[$locale] = [
                'php' => $this->phpTranslations($locale),
                'json' => $this->jsonTranslations($locale),
            ];
        }

        return $translations;
    });
}
Enter fullscreen mode Exit fullscreen mode

This enables me to not having to edit the provider every time we add support for a new language.

Collapse
 
parapente profile image
Theofilos Intzoglou

In case you are using json files which should be inside the /resources/lang/ directory, you should add the GLOB_ONLYDIR option to the glob function in the code above.

Collapse
 
adovbos profile image
Aleksandr Dovbos

On my last project I did yml translations => json file and injection in i18n-vuex(for vee-validate and etc.). But for new project I think your approach is perfect. Thank you for sharing new way, happy coding:)

Collapse
 
4unkur profile image
Daiyrbek Artelov

You are welcome

Collapse
 
barraxas profile image
bArraxas

Great post !
I had just need edit this line to be supported by windows too :

Before : $path = resource_path("lang/$locale");
After : $path = resource_path("lang" . DIRECTORY_SEPARATOR . $locale);

(markdown of the hell, 10 minutes lost for nothing...)

Collapse
 
jalowin profile image
jalowin

Hi, I am using this code in my app ans It's all Ok, but I need to know how can I translate the placeholder for a input

this code in laravel crash the compilation

Thanks for all

Collapse
 
closerdesign profile image
Juan Carlos Rodríguez

Hey guys, it seems like I'm in the right path but I still have two questions because it is not working for me yet. 1. How to call the trans.js file? In the app.js? 2. Where exactly should I add the mixin? Thank you!

Collapse
 
pietrozb123 profile image
Pietro Zuntini

Hi @4unkur ,

I've used your code for about 3 years now and I'm very grateful for your solution!

I'm trying to upgrade my Laravel 9 app to Laravel 11, but when using your service provider, I'm now getting the following error:

php artisan serve

RuntimeException

This database engine does not support upserts.

at vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php:1308
1304▕ * @throws \RuntimeException
1305▕ /
1306▕ public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
1307▕ {
➜ 1308▕ throw new RuntimeException('This database engine does not support upserts.');
1309▕ }
1310▕
1311▕ /
*
1312▕ * Prepare the bindings for an update statement.

  +7 vendor frames 

8 app/Providers/TranslationServiceProvider.php:22
Illuminate\Support\Facades\Facade::__callStatic("rememberForever")
+7 vendor frames

16 [internal]:0
Illuminate\Foundation\Application::Illuminate\Foundation{closure}(Object(App\Providers\TranslationServiceProvider), "App\Providers\TranslationServiceProvider")

Do you know what could be the issue? Would you have a working example for Laravel 11?

Thank you!

Collapse
 
semchishinv profile image
Vladimir S

Thanks man!