Handling translations in Laravel and InertiaJS applications

Constantin Druc ยท 09 Jul, 2021

[00:08] In the past I used different packages to help with the translations on the front-end, javascript side of things, but recently my approach has shifted to a more simplistic one. If we think about it, all we need to do is, collect and share all the translation strings with the frontend and then use some kind of javascript method to find and parse the one we need in a particular place.

[00:31] So we have three problems to solve: we need to collect all the translations- meaning that we need to go through all these files and grab the translation strings, make those translations accessible on the frontend, and finally, we need to create a javascript method to find and parse the correct translation.

[00:52] One place to share the translations would be inside the handle inertia request middleware, here under this share method. However, these props are sent with each and every request, and I wouldn't want to put something that rarely changes in here. Another alternative would be to create a blade component and use it inside our main app.blade layout. So let's do it.

[01:14] We can create a new blade component by opening the terminal,and typing php artisan make:component and then the component name, in our case, Translations. This will create a php class under app, view, components. Here it is, and the corresponding blade view, which we can find under resources views components, and here it is.

[01:44] Back to the translations class, we can see it has this render method we can use to prepare the translations and send them to the blade view. So let's initialize a translations array, and then pass it to the view. Now inside the view, to make those translations accessible in javascript we can create a global variable, and let's say translations, and we'll need to transform the translations array into json; and we can do that using the json directive.

[02:26] To check that everything is working, we can open our app.blade file and use the new component. All blade component tags start with an x, dash, and then the name of the component class, in our case, translations. Now we can go in the browser and view the page source, and here it is. We solved problem number two, which was sharing the translations with the frontend. Let's start working on number one, which is collecting the translation strings.

[02:59] Back to the component class, this translations array will be a combination between the php translations and the json translations. The php translations are the strings coming from the php files, while the json translations come from the language.json file, for example en.json. Usually I put all my translations inside the json files.

[03:30] To load the correct translations, we need to know the locale. We can grab that by calling app, and we'll use the facade, get locale. We can now use the locale to check if the language directory exists. We can do if file, and we'll use the facade, exists, resource path lang slash local. So this will check if this en directory exists. To grab all the files inside the language directory, we can do file all files resource path lang local. Now, this will return us an array of files, and we can put those files in a collection and then map through it.

[04:18] To grab the translation strings, we'll do collect, and then filter, because we only wanted the php files, and we can filter them using filter, and then return file get extension equals equals php, and this will make sure we only get the php files. Then we can do flat map, and the reason we are using flat map is because whenever we are parsing a php file the end result is an array, so we'll have an array of arrays. And we actually need an array of strings like these ones. To get the output of the php file we can do file, and we'll use the facade, get require and then the file path file get real path.

[05:18] So this will basically execute this file and get the outputs. However, some translation files contain nested arrays; like this validation.php file, for example. We can flatten those by using the Arr class, specifically the dot method. So we can do Arr dot, and then pass in the array. And of course we need to return this. Finally, we need to transform this collection into an array, so we'll call the to array method and assign it to the php translations variable.

[05:55] Moving on to the json translations we'll do pretty much the same thing. We'll check if the file exists, so if file exists resource path lang slash local.json, and then to get the file contents we can do file get and pass in the path, but we'll want these to be an associative array, so we'll have to do json decode, pass in the contents and true as the second argument.

[06:34] Now that we have both the php translations and the json translations we can merge them together into translations. So we can do array merge php translations, json translations. Nowm if I go back in the browser and refresh, here they are. All the translation strings collected and passed into a globally accessible javascript variable.

[07:00] Let's start working on our final step, which is creating a javascript method that will find and parse the translation we need. Let me close all these tabs and then I will go inside js and create a new directory called mixins, and a new file called translations.js. Here I will export an object called translations that will contain our method. The method name will be the same as in php, so underscore underscore, the first argument will be key, and the second one will be replacements; which will default to an empty object.

[07:48] Now, to find the translation we can do let translation equals window dot underscore translations of key and set the key as default just in case we don't find the translation. To test this out, let's register the mixin in app.js. So I'll go here and do import translations from mixin's translations, and then down here we can do mixin, and pass in translations. Let's open the dashboard page for example, and try to translate this header. And now I'll go to the en.json file and say dashboard equals welcome.

[08:43] Now if I go into browser and refresh, we have welcome instead of dashboard. But what if I want to pass it parameters like, let's say, name, and do something like name equals page props user name. If I go back in the browser and refresh, it will say dashboard column name. That's because this translation doesn't exist yet, so let's update it and say welcome column name, and if I go now in the browser and refresh, it says welcome column name. But we need to replace this with the currently authenticated username, so we'll need to go to translations and figure out how to make these replacements.

[09:36] We can start by grabbing the object keys using object.keys of replacements, and then loop through them. Let's say r from replacement and then translation equals translation replace, and we'll do column whatever r is, and then replacements of r. Let's give it a go. I'll go in the browser and refresh, and here it is: welcome dr daphne hurt a phd whatever. So that works.

[10:08] One last thing we could do is to cache the translations just so we don't parse the language files with every request. So I'll go inside the translations class and down here we'll do translations equals Cacche and I'll use the facade, remember forever, let's say translations underscore locale, then function, and we'll need the locale, so I'll do use locale and then grab all this, put it here, and return the translations.

[10:52] Now if I go in the browser and refresh, everything is still working. However, you do need to keep in mind that you'll have to clear the cache whenever you change the translations. So, for example, if I remove this, save, go to the browser and refresh, the translations are still there. So you need to do php artisan cache:clear, and now if I refresh again, the translations have been recompiled. So do that again, cache clear, refresh, here they are.

[11:28] And that's how I handle translations in inertia.js applications these days. One last secret I will show you is how to do this without losing your mind over missing translations. What I typically do is, I make sure to always use the translation method. So everywhere there's static text, there's a translation method being used. Then I install a small package I wrote called druc/laravel-langscanner, so I do composer require druc/laravel-langscanner, and then whenever I need to translate the application in a different language, I can just do php artisan langscanner nl, and passing the locale let's say nl for netherlands. And now, if I go on under languages, here is nl.json, with all the translation strings. And then I just pass this file to the client, they fill it in, send it back, and that's it.