Localizing a Laravel app using Vue.js and Inertia.js without any dependencies
Published: 29.10.19 Last Updated: 22.08.23
We are going to go through the steps to add localization to the PingCRM demo application created for the Interia.js framework. In this tutorial, we will be translating it without any extra packages or dependencies. As I'm currently residing in Germany we will be translating it into German. Hopefully, at the end of this, you will have a solid understanding of how to localize your app regardless of the languages you are targeting.
01: Pass properties to the view
The first thing we will set up is the variables that inertia.js will share with the views. The "locale" variable will contain the user's current language choice. The "language" variable will store the JSON translations used by the frontend.
// Located in Providers/AppServiceProvider.php
Inertia::share([
// ...
'locale' => function () {
return app()->getLocale();
},
'language' => function () {
return translations(
resource_path('lang/'. app()->getLocale() .'.json')
);
},
// ...
]);
02: Add translations PHP helper function
This function returns the JSON translation file used in the language variable above.
// Located in app/helpers.php
function translations($json)
{
if(!file_exists($json)) {
return [];
}
return json_decode(file_get_contents($json), true);
}
Next, we need to autoload this helpers file. To do this, add the following to your composer.json file. After adding it run the following terminal command "composer dump-autoload" in the root of your Laravel application.
// Located in composer.json
"autoload": {
// ...
"files": [
"app/helpers.php"
]
},
03: Language Switching component
Next, we need a Vue component so that the user can toggle their language preference. In the template tag, we are simply adding a flag icon with some margin-left. The flag will display the opposite country of the language they are currently using. This is perhaps a little bit confusing as if you are viewing it in English you would currently see a German flag. Since we are focusing on the technical aspects I'll leave that UI aspect for you to improve on or punt to your designer. The togglable_locale simply defines the language choice using the two-letter language code.
// Located in resources/js/Shared/LanguageSelector.vue
<template>
<div class="ml-4">
<inertia-link :href="route('language', [selectable_locale])">
<icon class="w-5 h-5" :name="selectable_locale" />
</inertia-link>
</div>
</template>
<script>
import Icon from '@/Shared/Icon'
export default {
components: {
Icon,
},
computed: {
selectable_locale() {
if(this.$page.locale == 'de') {
return 'en';
}
return 'de'
}
},
}
</script>
Next, we need to add the flags to the icon components using the below snippet. Note the name variable maps to the language code stored in the "locale" variable.
// Located in resources/Shared/Icon.vue
<svg v-else-if="name === 'en'" enable-background="new 0 0 512 512" version="1.1" viewBox="0 0 512 512" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><circle cx="256" cy="256" r="256" fill="#F0F0F0"/><g fill="#0052B4"><path d="m52.92 100.14c-20.109 26.163-35.272 56.318-44.101 89.077h133.18l-89.077-89.077z"/><path d="m503.18 189.22c-8.829-32.758-23.993-62.913-44.101-89.076l-89.075 89.076h133.18z"/><path d="m8.819 322.78c8.83 32.758 23.993 62.913 44.101 89.075l89.074-89.075h-133.18z"/><path d="m411.86 52.921c-26.163-20.109-56.317-35.272-89.076-44.102v133.18l89.076-89.075z"/><path d="m100.14 459.08c26.163 20.109 56.318 35.272 89.076 44.102v-133.18l-89.076 89.074z"/><path d="M189.217,8.819c-32.758,8.83-62.913,23.993-89.075,44.101l89.075,89.075V8.819z"/><path d="m322.78 503.18c32.758-8.83 62.913-23.993 89.075-44.101l-89.075-89.075v133.18z"/><path d="m370 322.78l89.075 89.076c20.108-26.162 35.272-56.318 44.101-89.076h-133.18z"/></g><g fill="#D80027"><path d="m509.83 222.61h-220.44v-220.44c-10.931-1.423-22.075-2.167-33.392-2.167-11.319 0-22.461 0.744-33.391 2.167v220.44h-220.44c-1.423 10.931-2.167 22.075-2.167 33.392 0 11.319 0.744 22.461 2.167 33.391h220.44v220.44c10.931 1.423 22.073 2.167 33.392 2.167 11.317 0 22.461-0.743 33.391-2.167v-220.44h220.44c1.423-10.931 2.167-22.073 2.167-33.392 0-11.317-0.744-22.461-2.167-33.391z"/><path d="m322.78 322.78l114.24 114.24c5.254-5.252 10.266-10.743 15.048-16.435l-97.802-97.802h-31.482v1e-3z"/><path d="m189.22 322.78h-2e-3l-114.24 114.24c5.252 5.254 10.743 10.266 16.435 15.048l97.802-97.804v-31.479z"/><path d="m189.22 189.22v-2e-3l-114.24-114.24c-5.254 5.252-10.266 10.743-15.048 16.435l97.803 97.803h31.481z"/><path d="m322.78 189.22l114.24-114.24c-5.252-5.254-10.743-10.266-16.435-15.047l-97.802 97.803v31.482z"/></g>
</svg>
<svg v-else-if="name === 'de'" enable-background="new 0 0 512 512" version="1.1" viewBox="0 0 512 512" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
<path d="M15.923,345.043C52.094,442.527,145.929,512,256,512s203.906-69.473,240.077-166.957L256,322.783 L15.923,345.043z" fill="#FFDA44"/>
<path d="M256,0C145.929,0,52.094,69.472,15.923,166.957L256,189.217l240.077-22.261C459.906,69.472,366.071,0,256,0z"/>
<path d="M15.923,166.957C5.633,194.69,0,224.686,0,256s5.633,61.31,15.923,89.043h480.155 C506.368,317.31,512,287.314,512,256s-5.632-61.31-15.923-89.043H15.923z" fill="#D80027"/>
</svg>
After the above has been done, import the following LanguageSelector component into your Shared/Layout.vue file. Attached is a screenshot of the changes made in that file and the adjustments to some tailwind styles.

04: Language setting endpoint
In the previous step, we have an inertia link to the "/language" route. For this route, we pass the desired language code and then set it in the session as below. If you had an app with users then you could alternatively update the language on the user model or in a user settings table.
// Located in routes/web.php
Route::get('language/{language}', function ($language) {
Session()->put('locale', $language);
return redirect()->back();
})->name('language');
05: Language Setting Middleware
Once we have set the language code in the session under the locale variable we then need to add middleware to check that value on each page request and make sure we pass the correct variable to the view as per Step 01.
// Located in Http/Middleware/SetLocale.php
namespace App\Http\Middleware;
use Closure;
class SetLocale
{
public function handle($request, Closure $next)
{
app()->setLocale(config('app.locale'));
if(session()->has('locale')) {
app()->setLocale(session('locale'));
}
return $next($request);
}
}
06: Register SetLocale Middleware
Next, register the middleware. Ensure it is registered after we have initialized Laravel's Session middleware.
// Located in Http/Kernel.php
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\SetLocale::class,
// ...
],
];
protected $middlewarePriority = [
// ...
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\SetLocale::class,
// ...
];
07: Register vue.js helper mixin file for translating
This registers our vue.js mixins required for translating the JSON file on the frontend.
// Location in resources/js/app.js
Vue.mixin(require('./base'))
08: Add helper mixins to base.js
Add the mixins below, the first method is for translating a normal string, the last is for translating a string with basic pluralization.
// Location in resources/js/base.js
module.exports = {
methods: {
/**
* Translate the given key.
*/
__(key, replace = {}) {
var translation = this.$page.language[key]
? this.$page.language[key]
: key
Object.keys(replace).forEach(function (key) {
translation = translation.replace(':' + key, replace[key])
});
return translation
},
},
}
09: Add JSON translation file
Next, we need to add the JSON translation file, in this case de.json
// Located in de.json
{
"My Profile": "Mein Profil",
"Manage Users": "Benutzer verwalten",
"Logout": "Abmeldung",
"Dashboard": "Dashboard",
"Organizations": "Unternehmen",
"Contacts": "Kontakte",
"Reports": "Berichte",
"Hey there! Welcome to Ping CRM, a demo app designed to help illustrate how :link works.": "Hey da! Willkommen bei Ping CRM, einer Demo-App, die helfen soll, zu veranschaulichen, wie :link funktioniert.",
"error": "Fehler"
}
10: Translate the application
Next, you will need to go through your application and translate it. You can see the attached screenshots for the files changed to get the Dashboard translated.


11: Finished Product
See the below video for the finished product. If you have been following along then you should have the following finished product.
12: Wrapping up
To finish this off and have the whole app translated the following tasks need to be completed.
All the strings in the resources/js/ would need to be translated and placed in de.json. You can use DeepL to translate if needed. If you are using mail notifications (e.g. password reset) those also need to be added to de.json. The contents of resources/lang/en need to be duplicated into resources/lang/de and translated.
13: Changes for Vue 3
If you are wanting to take this approach for Vue 3, then perhaps take a look at this additional comment on Github in reply to this post.
14: Changes for Laravel 9
If you are wanting to take this approach for Laravel 9, then perhaps take a look at this additional comment on Github in reply to this post.
Hope you enjoyed and if you have any questions don't hesitate to reach out on Twitter.
Have questions or want to stay up to date? Find me on Twitter Get notified of new posts