Bonus material: Vue i18n tutorial (PDF version)
Get the ebookInternationalization is an important yet often missed step in software development. Software localization would not be possible without internationalization (i18n).
Setting up a Vue.js website with i18n support sounds daunting at first, but it’s actually easier than one might think. For this tutorial we will be using Vue I18n , a great package from the core Vue devs, to show you how to build a multi-langue app with locale switcher.
Please note that this article covers Vue 2 only! If you are looking for the Vue 3 tutorial, please check
the following article instead
.
Loosely based on the original article by Dobromir Hristov. Used with author’s permission.
In this article we are going to cover the following topics:
- Installing and setting up Vue I18n.
- Adding support for multiple languages.
- Storing and using translations.
- Implementing pluralization.
- Datetime and number localization.
- Switching the locale.
- Integrating Vue Router and making it work with multiple locales.
- Lazy load translation files based on the chosen locale.
- Read user preferred locale and adjust the default language based on it.
The source code is available on GitHub .
Initial Setup of the Vue.js App
In this Vue I18n tutorial we are going to utilize a special CLI so be sure to install it as follows:
npm install -g @vue/cli
Next, create a new demo project named
vue-i18n-demo
:
vue create vue-i18n-demo
I’ve chosen to use the default preset for this app (Babel and ESLint).
Lastly,
cd
into the app folder and install the Vue I18n plugin:
cd vue-i18n-demo vue add i18n
You will be asked some questions regarding the project setup. I’ve provided the following answers:
-
The locale of project localization —
en
-
The fallback locale of project localization —
ru
(you may choose any other language) -
The directory where store localization messages of project —
locales
-
Enable locale messages in Single file components —
N
After installing Vue I18n, the following operations will be performed for you:
-
Create a
vue.config.js
file in the project’s root. This file contains general settings for the I18n plugin (default language, fallback, etc.). -
Create an
.env
file in the project’s root. We will return to this file later. -
Create an
src/i18n.js
file. This file actually imports the necessary I18n modules and does the initial setup. -
Tweak
src/main.js
to add I18n functionality to the app. -
Create
src/locales
directory withen.json
andru.json
files which will store our translations.
Now the setup is done, and we can proceed to the next step!
Setting Up Lokalise
Set up your Vue.js website with i18n support to reach more customers
Grab a FREE Lokalise trial and start internationalizing your app so you can improve the multilingual customer experience of your website.Start now
Managing translations for multiple languages is a pretty complex task, especially for larger sites. Things become even more complex if you have limited proficiency in the supported languages. What should you do in such cases? Use a proper translation management system !
Meet Lokalise, the leading translation management service which allows you to easily manage translation files, collaborate with translators, order professional translations, enable integrations with 50+ tools, and much more. Throughout this article I will briefly explain how to get started with Lokalise. For now, you will need to perform the following steps:
- Grab your free trial (no credit card is required).
- Proceed to Personal profile > API tokens.
- Generate a new read-write token (make sure to keep it safe).
- Download CLIv2 and unpack it somewhere on your PC.
-
cd
into the created directory -
Create a new Lokalise project by running
lokalise2 project create --name VueJS --token YOUR_TOKEN_HERE --languages "[{\"lang_iso\":\"ru\"},{\"lang_iso\":\"en\"}]" --base-lang-iso "en"
. Adjust the supported languages as necessary. -
The above command is going to return an object with the project details. Copy the
project_id
as we will need it later.
The basic setup is done, and we can proceed to the next part!
Performing Simple Translations
Storing Vue Translations in JSON Files
To
get started with Vue I18n
, let’s see how to perform basic translations. For instance, we may want to greet our users on the main page of the site. To achieve that, add English translation to
locales/en.json
:
{ "main": { "welcome": "Hello from {company}!" } }
There are a few things to note here:
- Your translations are stored in simple JSON files. The key will be used in your code and automatically substituted with a proper value based on the chosen language.
- Keys can be nested (this is especially useful for larger sites with many translations).
-
Keys support interpolation: take a look at the
{company}
part in the example above. This placeholder will be replaced with an actual company name that we are going to provide in the code. If you are a fan of Ruby on Rails (like me), then you may also use Rails-style placeholders :%{company}
.
Now add Russian translations to the
locales/ru.json
file:
{ "main": { "welcome": "Вас приветствует {company}!" } }
Great! The translations are ready, and we can use them in our app.
Uploading Translation Files to Lokalise
After creating the initial translation files, we can upload them to the Lokalise project we created earlier. To achieve this, run the following commands:
lokalise2 file upload --lang-iso en --file "PATH_TO_PROJECT\src\locales\en.json" --project-id PROJECT_ID --token YOUR_TOKEN lokalise2 file upload --lang-iso ru --file "PATH_TO_PROJECT\src\locales\ru.json" --project-id PROJECT_ID --token YOUR_TOKEN
PROJECT_ID
is the identifier that you received in the previous step when creating a new project.
Using Vue I18n in Templates
Now let’s see how to utilize the created translations in our pages. In fact, all you need to do is utilize a
$t
function and pass a translation key to it. Let’s tweak the template within the
App.vue
file:
<template> <div id="app"> <p>{{ $t('main.welcome', {company: 'Lokalise'}) }}</p> </div> </template>
main.welcome
is the translation key. We are using dot (
.
) because the key is nested. The
company: 'Lokalise'
part provides the value for the
{company}
placeholder within the translation file.
We are not going to use the default
HelloWorld
component, so remove the following line from the
App.vue
file:
import HelloWorld from './components/HelloWorld.vue'
Also, remove
HelloWorld
from the
components
property and delete the
components/HelloWorld.vue
file.
Now you may boot the app:
npm run serve
Proceed to
localhost:8080
— the welcoming message should be displayed for you. In order to make sure that the Russian translation works as well, you can open the
i18n.js
file and set
'ru'
as the
locale
property:
export default new VueI18n({ locale: 'ru', // <------- fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', messages: loadLocaleMessages() })
Don’t forget to undo the above change once you’re done testing!
Downloading Translation Files from Lokalise
Once you have edited your translations on Lokalise, download them back into your project by running:
lokalise2 file download --unzip-to PROJECT_PATH\src\locales --format json --token YOUR_TOKEN --project-id PROJECT_ID
Changing the Language
So, our application is now multilingual, but there’s no option to actually switch between locales via the user interface. Let’s add this feature now!
I suggest creating a new component called
LocaleSwitcher
which is going to handle all switching logic. Create a new
src/components/LocaleSwitcher.vue
file:
<template> <ul> <li v-for="locale in locales" :key="locale" @click="switchLocale(locale)"> {{locale}} </li> </ul> </template>
This is a basic unordered list which is going to contain our supported locales. Once the list item is selected, we call up the
switchLocale()
method and perform the actual change.
The next step is to provide an array of supported locales. We can just hard-code them, but that is not very convenient. Instead, let’s take advantage of the
.env
file which I mentioned earlier. Add the following line:
VUE_APP_I18N_SUPPORTED_LOCALE=en,ru
Now use this environment variable to produce an array of locales within the
LocaleSwitcher.vue
:
<!-- your template here... --> <script> export default { name: 'LocaleSwitcher', data() { return { locales: process.env.VUE_APP_I18N_SUPPORTED_LOCALE.split(',') } } } </script>
process.env
enables us to access environment variables. Then we just take the required value and use
split()
to produce an array.
Lastly, add a
switchLocale()
method:
<script> export default { name: 'LocaleSwitcher', methods: { // <--------------------------- switchLocale(locale) { if (this.$i18n.locale !== locale) { this.$i18n.locale = locale; } } }, data() { return { locales: process.env.VUE_APP_I18N_SUPPORTED_LOCALE.split(',') } } } </script>
this.$i18n.locale
allows us to get and set the current locale of the app. We first check if the requested locale is different from the currently chosen one and update it if necessary.
To make the list elements look more like links, you may add the following scoped style to the component:
<style scoped> li { text-decoration: underline; color: #459CE7; cursor: pointer; } </style>
Once you are ready, use the new component within
App.vue
:
<template> <div id="app"> <LocaleSwitcher /> <!-- <------------------- --> <p>{{ $t('main.welcome', {company: 'Lokalise'}) }}</p> </div> </template> <script> import LocaleSwitcher from './components/LocaleSwitcher.vue' // <------------- export default { name: 'App', components: { LocaleSwitcher // <------------- } } </script>
Reload the application, proceed to
localhost:8080
and try clicking the switcher links. The welcoming message should be translated to the chosen language. Good job!
Exploring Vue I18n Features
Pluralization
One of the most common internationalization tasks is
pluralizing
the text properly. To demonstrate it in action, let’s show how many new messages the user has. Start with the English translation and provide the
new_message
key:
{ "main": { "welcome": "Hello from {company}!", "new_message": "no messages | one message | {count} messages" } }
Here we provide three possible translations separated with pipeline (
|
) which will be picked automatically, based on the passed number. The first translation (
no messages
) is optional and may be omitted.
Pluralization rules in English are quite simple as the word may have only two forms. For Russian, things become more complex:
{ "main": { "welcome": "Вас приветствует {company}!", "new_message": "нет сообщений | {count} сообщение | {count} сообщения | {count} сообщений" } }
Unfortunately, Vue I18n does not support complex pluralization out of the box.
It is quite simple to add this feature
though. Modify the
i18n.js
file like this:
import Vue from 'vue' import VueI18n from 'vue-i18n' const defaultImpl = VueI18n.prototype.getChoiceIndex // <------------ VueI18n.prototype.getChoiceIndex = function(choice, choicesLength) { // <------------ Add support for Russian pluralization // this === VueI18n instance, so the locale property also exists here if (this.locale !== 'ru') { return defaultImpl.apply(this, arguments) // default implementation } if (choice === 0) { return 0; } const teen = choice > 10 && choice < 20; const endsWithOne = choice % 10 === 1; if (!teen && endsWithOne) { return 1; } if (!teen && choice % 10 >= 2 && choice % 10 <= 4) { return 2; } return (choicesLength < 4) ? 2 : 3; } Vue.use(VueI18n) // other code goes here...
Basically, we are explaining what translation to choose based on the provided number. Pluralization algorithms for all languages may be found on the Unicode CLDR website .
With the above code in place, we may implement pluralization in the code. Add a new line to the
App.vue
file:
<template> <div id="app"> <LocaleSwitcher /> <p>{{ $t('main.welcome', {company: 'Lokalise'}) }}</p> <p>{{ $tc('main.new_message', 10) }}</p> <!-- <-------- add this --> </div> </template>
Note that in order to employ pluralization, you need to use a
$tc
function. It accepts the following three arguments:
- Translation key.
- The number to use in pluralization.
-
Optional object with the interpolation values. By default, the second argument will be provided as an interpolation value, but you may override it. For example:
$tc('main.new_message', 10, {count: "Not many"})
.
Date and Time
The next common feature that I would like to show you is
localization of date and time
. In order to support it, we need to provide one or more datetime formats within the
i18n.js
:
// other code goes here... const dateTimeFormats = { 'en': { short: { year: 'numeric', month: 'short', day: 'numeric' } }, 'ru': { short: { year: 'numeric', month: 'short', day: 'numeric' } } } export default new VueI18n({ locale: process.env.VUE_APP_I18N_LOCALE || 'en', fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', messages: loadLocaleMessages(), dateTimeFormats // <--------- make sure to add this line as well! })
For both locales we have added a new format called
short
:
- The year and day will be provided as numbers.
-
The name of the month will be displayed in a shortened form (for example,
Apr
).
You can add other formats as necessary by using the options listed in ECMA-402 .
Now, let’s localize the current date using the
short
format. Add a new line to the
App.vue
file as follows:
<template> <div id="app"> <LocaleSwitcher /> <p>{{ $t('main.welcome', {company: 'Lokalise'}) }}</p> <p>{{ $tc('main.new_message', 10) }}</p> <p>{{ $d(new Date(), 'short') }}</p> <!-- <----------------- --> </div> </template>
$d
is a function that will perform localization.
new Date()
returns the current date, whereas
short
is the name of our format. Sweet!
Working with Currency
The last Vue I18n feature that I am going to show you is
formatting numbers and representing them with the proper currencies
. To add currency formats, we need to tweak the
i18n.js
file again:
// other code goes here... const numberFormats = { 'en': { currency: { style: 'currency', currency: 'USD' } }, 'ru': { currency: { style: 'currency', currency: 'RUB' } } } export default new VueI18n({ locale: process.env.VUE_APP_I18N_LOCALE || 'en', fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', messages: loadLocaleMessages(), dateTimeFormats, numberFormats // <------ Make sure to add this line! })
So, for the English locale we’ll be using US dollars, whereas for Russian we’ll use roubles.
Next, add another new line to the
App.vue
file:
<template> <div id="app"> <LocaleSwitcher /> <p>{{ $t('main.welcome', {company: 'Lokalise'}) }}</p> <p>{{ $tc('main.new_message', 10) }}</p> <p>{{ $d(new Date(), 'short') }}</p> <p>{{ $n(100, 'currency') }}</p> <!-- <------------ --> </div> </template>
$n
is a function for localizing numbers. It accepts the actual number and the name of the format.
Vue I18n docs provide some more examples explaining how to add custom formatting for your numbers .
Setting the Language as a Route Parameter
At this point, our application is translated into two languages. However, we do not persist the chosen locale anywhere. Also, whenever a user visits our site, an English locale is being set by default. In this section we’ll see how to enhance our application further with the help of Vue Router .
Installing Vue Router
The first step is installing Vue Router itself:
vue add router
During the installation, choose to use history mode.
The above command is going to perform the following operations:
-
Tweak
main.js
to enable routing. -
Add sample router links and a
router-view
to theApp.vue
. -
Create a
router/index.js
file with initial configuration. -
Add two sample views (within the
views
folder).
Managing Views
What I would like to do next is tweak the
App.vue
file and update the sample views created for us in the previous section. Start by tweaking the
App.vue
. We are going to bring the language switcher back as well as introduce a new
MainMenu
component:
<template> <div id="app"> <LocaleSwitcher /> <MainMenu /> <router-view/> </div> </template> <script> import LocaleSwitcher from '@/components/LocaleSwitcher.vue' import MainMenu from '@/components/MainMenu.vue' export default { name: 'App', components: { LocaleSwitcher, MainMenu } } </script>
Next, create a new
components/MainMenu.vue
file which is going to contain the router links generated for us during router installation:
<template> <div id="nav"> <router-link to="/">{{ $t('menu.home') }}</router-link> | <router-link to="/about">{{ $t('menu.about') }}</router-link> </div> </template>
Add new translations for the links. English:
{ "menu": { // <------------ "home": "Home", "about": "About" }, "main": { "welcome": "Hello from {company}!", "new_message": "no messages | one message | {count} messages" }, "about": { "welcome": "Welcome to the About page!" } }
And Russian:
{ "menu": { // <--------------- "home": "Главная", "about": "О нас" }, "main": { "welcome": "Вас приветствует {company}!", "new_message": "нет сообщений | {count} сообщение | {count} сообщения | {count} сообщений" }, "about": { "welcome": "Добро пожаловать на страницу О нас!" } }
Now we need to tweak the sample views generated for us. The
views/Home.vue
will contain the translated messages which were displayed on the main page:
<template> <div class="home"> <p>{{ $t('main.welcome', {company: 'Lokalise'}) }}</p> <p>{{ $tc('main.new_message', 10) }}</p> <p>{{ $d(new Date(), 'short') }}</p> <p>{{ $n(100, 'currency') }}</p> </div> </template>
The
views/About.vue
will simply display a welcoming message:
<template> <div class="about"> <p>{{ $t('about.welcome') }}</p> </div> </template>
Provide translations for the
about.welcome
key:
"about": { "welcome": "Welcome to the About page!" }
"about": { "welcome": "Добро пожаловать на страницу О нас!" }
Adding Routes with Vue I18n Support
The views are ready, and we can proceed to our routes. Replace the contents of the
router/index.js
file with the following:
load()
is a small helper function that we will use to load the proper view based on the current router.
Now add the actual routes as follows:
// other code ... const routes = [{ path: '/:locale', component: { template: "<router-view></router-view>" }, children: [{ path: '', name: 'Home', component: load('Home') }, { path: 'about', name: 'About', component: load('About') } ] } ]
/:locale
is a variable part of the route which will equal either
en
or
ru
. For example:
localhost:8080/en/about
. There are also two child routes for our Home and About views. Home is the default page; therefore, its path is set to an empty string (it can be accessed with
locahost:8080/en
).
When the
/:locale
page is initially entered, we need to set a proper locale based on the parameter. Therefore, add a
beforeEnter
property:
const routes = [{ path: '/:locale', component: { template: "<router-view></router-view>" }, beforeEnter: (to, from, next) => { // <------------ const locale = to.params.locale; // 1 const supported_locales = process.env.VUE_APP_I18N_SUPPORTED_LOCALE.split(','); // 2 if (!supported_locales.includes(locale)) return next('en'); // 3 if (i18n.locale !== locale) { // 4 i18n.locale = locale; } return next() // 5 }, children: [ // children here... ] } ]
The logic is pretty simple:
-
We fetch the requested locale from the route by using
to.params.locale
. - We also generate an array of supported locales.
-
If the requested locale is not supported (that is, not found in the array), we set the locale to English and redirect the user to
localhost:8080/en
. - If the locale is supported and the current locale is different, we switch to the chosen language.
- Lastly, navigate the user to the page.
We may also handle all other routes and redirect the user to the default locale:
const routes = [{ path: '/:locale' // other code for the /:locale route }, { // <--------------- path: '*', redirect() { return process.env.VUE_APP_I18N_LOCALE; } } ]
Lastly, create the router:
// routes and other code ... const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
The routes are now configured properly!
The links within the
components/MainMenu.vue
file also require some tweaks:
<template> <div id="nav"> <router-link :to="{ name: 'Home', params: { lang: this.$i18n.locale }}"> {{ $t('menu.home') }} </router-link> <br> <router-link :to="{ name: 'About', params: { lang: this.$i18n.locale }}"> {{ $t('menu.about') }} </router-link> </div> </template>
Here we utilize named routes (
Home
and
About
respectively). These named routes were set in the previous step using the following line:
name: 'Home'
. For each route we also provide the currently set locale.
If you boot the server now, you will probably get an error saying that the template compiler is not available. To fix it, add the following property to the
vue.config.js
file:
module.exports = { runtimeCompiler: true, // <------- pluginOptions: { // other options... } }
The navigational links and language switcher should work now. One problem, however, is that after changing a language, the locale is not being updated in the URL. Let’s fix that as well!
Rewriting URL After Locale Switch
Updating a URL after a locale switch is actually a very simple thing to do. We need to add a single line to the
components/LocaleSwitcher.vue
file:
// your template... <script> export default { name: 'LocaleSwitcher', methods: { switchLocale(locale) { if (this.$i18n.locale !== locale) { this.$i18n.locale = locale; const to = this.$router.resolve({ params: {locale} }) // <-------------------- this.$router.push(to.location) // <-------------------- } } }, data() { // data... } } </script>
First, we are generating the proper URL based on the chosen locale. Then,
this.$router.push
will navigate the user to a new URL (it is possible to return to the previous one thanks to the history mode).
Now if you change the locale, the URL will be updated for you. Try playing around with the links to make sure everything works properly!
Introducing Lazy Loading
Our application is working but there is one important feature missing: lazy loading of translation files . Usually, loading translations for all languages in an overkill. Therefore, in this section we will see how to implement this feature.
Introducting Translation Plugin
Before implementing lazy loading, however, it would be a good idea to refactor our code. Specifically, I would like to extract all localization-related logic to a separate file. Therefore, let’s create a new
plugins/Translation.js
file:
import { i18n } from '../i18n' const Trans = { get defaultLocale () { return process.env.VUE_APP_I18N_LOCALE }, get supportedLocales() { return process.env.VUE_APP_I18N_SUPPORTED_LOCALE.split(',') }, get currentLocale() { return i18n.locale }, set currentLocale(locale) { i18n.locale = locale } } export { Trans }
There are three getters to read:
- The default locale
- The list of supported locales
- The currently set locale
Also, we have one setter to change the current language.
Now, let’s implement a method to change the language and load the necessary translation file:
const Trans = { // ... changeLocale(locale) { if (!Trans.isLocaleSupported(locale)) return Promise.reject(new Error('Locale not supported')) if (i18n.locale === locale) return Promise.resolve(locale) return Trans.loadLocaleFile(locale).then(msgs => { i18n.setLocaleMessage(locale, msgs.default || msgs) return Trans.setI18nLocaleInServices(locale) }) } } export { Trans }
This method is based on promises. If the locale is not supported, then we just reject with an error message. If the chosen locale is the same, we resolve the promise with that locale. Otherwise, load the corresponding translation file, pass translation messages to Vue I18n, switch the locale, and return it.
Here is the method to check if the requested locale is supported:
isLocaleSupported(locale) { return Trans.supportedLocales.includes(locale) }
Method to load a translation file:
Method to switch locale:
setI18nLocaleInServices(locale) { Trans.currentLocale = locale document.querySelector('html').setAttribute('lang', locale) return locale }
Note that it also updates the
lang
attribute of the
html
tag.
Let’s also add a special method that will be used in our routing:
routeMiddleware(to, from, next) { const locale = to.params.locale if (!Trans.isLocaleSupported(locale)) return next(Trans.defaultLocale) return Trans.changeLocale(locale).then(() => next()) }
This method has the same logic as the one provided in the
beforeEnter()
method, but it relies on the newly created plugin.
Lastly, I’d like to introduce a special method to generate localized routes. It is going to accept the route name:
i18nRoute(to) { return { ...to, params: {locale: this.currentLocale, ...to.params} } }
Our plugin is ready so we may refactor the code based on it!
Refactoring I18n Config
The next step is to update our
i18n.js
file to take advantage of the new
Translation
plugin. Here is a new version of this file:
import Vue from 'vue' import VueI18n from 'vue-i18n' import en from '@/locales/en.json' // <-------------- 1 VueI18n.prototype.getChoiceIndex = function(choice, choicesLength) { // ... } Vue.use(VueI18n) const dateTimeFormats = { // ... } const numberFormats = { // ... } export const i18n = new VueI18n({ // <-------------- 2 locale: process.env.VUE_APP_I18N_LOCALE || 'en', fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', messages: { en }, // <-------------- 3 dateTimeFormats, numberFormats })
Main things to note here:
- Initially, we are loading only the English translation file (as English is set as the default locale).
-
We are exporting an
i18n
constant instead of using justexport default
. -
messages
now contains only the imported English translations. -
loadLocaleMessages
function is removed because we have implemented our own loader inside theTranslation
plugin.
Refactoring Main File and Routes
Next, adjust the contents of the
main.js
file in the following way:
import Vue from 'vue' import App from './App.vue' import { i18n } from './i18n' // <------------- 1 import router from './router' import { Trans } from './plugins/Translation' Vue.prototype.$i18nRoute = Trans.i18nRoute.bind(Trans) // <------------- 2 Vue.config.productionTip = false new Vue({ i18n, router, render: h => h(App) }).$mount('#app')
There are two things to note here:
- Make sure to import all the necessary modules properly.
-
Allow
$i18nRoute
to be used in your templates.
Next, tweak the
router/index.js
file:
import Vue from 'vue' import VueRouter from 'vue-router' import { Trans } from '@/plugins/Translation' // <-------------- 1 function load(component) { // ... } Vue.use(VueRouter) const routes = [{ path: '/:locale', component: { template: "<router-view></router-view>" }, beforeEnter: Trans.routeMiddleware, // <---------------- 2 children: [ // ... ] }, { path: '*', redirect() { return Trans.defaultLocale; // <---------------- 3 } } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
Note the following:
-
We are now importing only the
Translation
plugin which contains all locale-related logic. -
beforeEnter
now delegates to therouteMiddleware
. This way we are making our code less cluttered and separate the logic into the proper files. - The default locale is also fetched using the new plugin.
Great job!
Refactoring Components
We are nearly done with all the refactoring. The last thing to take care of are our components. Start with the
components/LocaleSwitcher.vue
:
<template> <ul> <li v-for="locale in supportedLocales" :key="locale" @click="switchLocale(locale)"> <!-- <------------ 1 --> {{ $t('menu.' + locale) }} <!-- <------------ 2 --> </li> </ul> </template> <script> import { Trans } from '@/plugins/Translation' // <------------ 3 export default { name: 'LocaleSwitcher', computed: { <------------ 4 supportedLocales () { return Trans.supportedLocales }, }, methods: { switchLocale(locale) { if (this.$i18n.locale !== locale) { const to = this.$router.resolve({ params: {locale} }) return Trans.changeLocale(locale).then(() => { <------------ 5 this.$router.push(to.location) }) } } } } </script>
Main things to note:
-
We are using the
supportedLocales
attribute which is defined below. - We also translate the locale codes.
-
Don’t forget to load the
Translation
plugin. -
Introduce a new
supportedLocales
computed attribute which relies on theTranslation.supportedLocales
getter. - Locale is now also changed using our plugin.
New English translations:
{ "menu": { "en": "English", "ru": "Russian" // ... } // ... }
Russian translations:
{ "menu": { "en": "Английский", "ru": "Русский" // ... } // ... }
Finally, adjust the
components/MainMenu.vue
:
<template> <div id="nav"> <router-link :to="$i18nRoute({ name: 'Home' })"> {{ $t('menu.home') }} </router-link> <br> <router-link :to="$i18nRoute({ name: 'About' })"> {{ $t('menu.about') }} </router-link> </div> </template>
Here we are taking advantage of the
$i18nRoute()
helper to generate localized routes based on the currently set locale.
At this point you may boot your application and make sure everything is working!
Reading User Preferred Language
The very last thing I wanted to should you today is the ability to adjust the initial locale based on the user’s preferred language. Usually, browsers send preferred language in the request so we can take advantage of this fact. To get started, install axios library which will be used to work with the headers:
npm install axios
Import axios inside
plugins/Translation.js
file:
import axios from 'axios' // ...
Add a new method that will try to read the preferred locale and check whether it is supported:
getUserSupportedLocale() { const userPreferredLocale = Trans.getUserLocale() // <------------- 1 if (Trans.isLocaleSupported(userPreferredLocale.locale)) { // <------------- 2 return userPreferredLocale.locale } if (Trans.isLocaleSupported(userPreferredLocale.localeNoISO)) { // <------------- 3 return userPreferredLocale.localeNoISO } return Trans.defaultlocale // <------------- 4 }
The logic of this method is rather simple:
- We read the preferred locale.
- If the app supports this locale, return it.
- If the locale is unknown, try to strip the ISO code and check again.
- Finally, if both checks were unsuccessful, return the default locale.
Here is the implementation of the
getUserLocale()
method:
getUserLocale() { const locale = window.navigator.language || window.navigator.userLanguage || Trans.defaultLocale // <----------- 1 return { locale: locale, <-------------- 2 localeNoISO: locale.split('-')[0] <------------- 3 } }
- Try to read the preferred language. If the preferred language is not set, simply use the default locale.
-
Return full locale (for example,
en-us
) as the first attribute of the object. -
Return locale without the ISO part (for example,
en
) as the second attribute.
Having these two methods in place, adjust the
routeMiddleware
method:
routeMiddleware(to, from, next) { const locale = to.params.locale if (!Trans.isLocaleSupported(locale)) return next(Trans.getUserSupportedLocale()) // <----------- 1 return Trans.changeLocale(locale).then(() => next()) }
There is only one change. If the requested locale is not supported, we fetch the preferred locale.
Lastly, set the
Accept-Language
header
using axios:
setI18nLocaleInServices(locale) { Trans.currentLocale = locale axios.defaults.headers.common['Accept-Language'] = locale // <--------------- document.querySelector('html').setAttribute('lang', locale) return locale }
That’s all! Now the application will try to use the language preferred by the current visitor.
Conclusion
In this tutorial we have seen how to implement internationalization and localization using the Vue I18n solution. We have learned how to configure this library, perform translations, use pluralization, and localize datetime and numbers. On top of that, we’ve added a language switcher, enabled Vue Router, introduced lazy loading, and support for the user preferred locale. Not bad for a single article!
Of course, this demo app can be enhanced further. To get some inspiration you may check the example i18n-starter repo created by Dobromir Hristov. Also, you may be interested in a vue-i18n-extract tool which scans your project and extracts translations into JSON files. Don’t hesitate to post a comment if you have any additional questions or concerns.
Thank you for visiting us today and until the next time!
Further reading
- Lokalise API: What is it and how to use it?
- Lokalise API v2 documentation
- How to choose the best translation management system for your team and company
Set up your Vue.js website with i18n support to reach more customers
Grab a FREE Lokalise trial and start internationalizing your app so you can improve the multilingual customer experience of your website.Start now