Localization With Laravel and Vuejs

Everyone who has built a multi-language application in Laravel knows a couple of ways of translating a string. Options are {{ __(‘apple’) }}, {{ trans(‘apple’) }}, {{ trans_choice(‘apple’, 2) }} or @trans(‘apple’).

Lately, Laravel embraces VueJS and you might – like me – be building your frontend not soley with blade templates anymore but adding a VueJS component here and there. This is fine and as long as you use an inline-template where you write your HTML code directly in the *.blade.php file instead in the <template> block of your VueJS component, you can interweave Laravels Blade syntax with VueJS.

Let say you want to migrate that form to a VueJS component and you end up with the following code:

At least now you realize you can neither use {{ route() }} nor any of the translation helpers mentioned above. Not even {!! csrf_field() }} is working anymore.

Damn! VueJS should be fun, but so far it gets more complicated.

Lets fix this quickly:

That is what we had first, but … wait … now we hardcoded the before translated strings like Edit and Update. How we get the translation into the component? Well, we could add a prop for every label, but that would be cumbersome and very annoying, tedious work.

One note on the routes: For the sake of keeping this post simple, I choose that way. I am aware of tightenco/ziggy which is described in full detail here.

The first solution I came across was a very easy and nearly out-of-the-box working solution described in an article by Serhii Matrunchyk on Medium. The only negative point I spotted, was the lacking of pluralization support. In Laravel the trans_choice('apples', 2) helps you to get the correct translation out of a language file where you defined something like:

'apples' => 'There is one apple|There are many apples',

In the article that wasn’t covered but for the time beeing it was close enough, so I went with it until I recognized a weird behavior; while one component shows the translated labels another component on the same page doesn’t.

What the heck?!

What happend? The component which shows the translation contains some dropdowns. After loading the component, VueJS fetches their options from an API and assigns them to the model inside created(){}, meaning VueJS rerenders the component after the initial render. With the rerendering the translated labels where displayed too. The other component contains just some input fields, therefore it does not get redrawn.

Looking for another approach

Ok, back to Google and looking for something else. I can’t be the first one who just wants to translate some strings inside a VueJS component. Luckily I am not.

After a couple of minutes, I found martinlindhe/laravel-vue-i18n-generator, a Laravel package which relies on vue-i18n for the translation part but uses the language files stored in resource/lang. So this prevents you from maintaining two languages files; one for the backend and one for the frontend. Nice!

The package provides an Artisan command to generate a vue-i18n compatible file which is stored to resources/assets/js/vue-i18n-locales.generated.js.

Setting this up was easy:

Unfortunately, that did not work either. I got the following error as soon as I used the $t() translation helper in my component.

Cannot translate the value of keypath ‘message.hello’. Use the value of keypath as default.

Dude! Will I ever be able to translate something in a VueJS component? I went to the Issue Tracker of vue-i18n and read all related reports. For what I understood, the error occurs because the Vue instance gets loaded before the localization is ready. In one thread somebody pointed to another i18n lib called vuex-i18n.

Mhh, could I just use the generated file from laravel-vue-i18n-generator with vuex-i18n? Turns out I can — with a few adjustments to the setup.

Finally I got my strings translated, here is the component from above.

I was happy, because the translated labels were now shown on every component, but I was not satisfied. I had just accomplished the nearly same behavior what I had before, but now with one extra Laravel package and a 3rd-party i18n library which is not supported by laravel-vue-i18n-generator.

Furthermore, pluralization still does not work as the README of the package mentioned.

I went back to the repo of vuex-i18n and was surprised to found out that they support plural forms. In contrary to Laravel and vue-i18n, which both using the pipe | char to separate singular from plural, vuex-i18n uses three colons :::.

To get things done, I unceremoniously did a search’n’replace on the aforementioned generated file, replacing all pipes with three colons and … oh look … I got also pluralization working. Very good!

Add support for vuex-i18n

Ok, now I have everything in place, but I could’t let it go. Knowing that every time I update the language files, I need to generate the file for the frontend and replace the pipe with the colons is the ground zero of weird errors in the long run. So I asked martinlindhe, the maintainer of laravel-vue-i18n-generator, if he would be fine with adding support for vuex-i18n. He was. And after some saturday morning hacking, I pushed a pull request which gets merged the next day and released into a new version. Thanks to martinlindhe for the quick reaction and merge.

Try martinlindhe/laravel-vue-i18n-generator and happy translating.