Add i18n and manage translations of a Vue.js powered website
Setting up a Vue.js website with internationalization (i18n) support sounds daunting at first, but it’s actually easier than one might think. For this tutorial we will be using VueI18n, a great package by one of the Vue core team devs.
TLDR:
Checkout the vue-i18n-starter repo for a simplified Vue app with i18n support.
Initial setup
You need to do two main steps:
- Instantiate the plugin by providing a default locale, a fallback and an object containing all the translation strings.
2. Tell our App about that instance
That’s all it takes to have the app i18n ready. Now you can just use {{ $t(‘hello’) }}
inside Vue component templates and you will get Hello.
Changing the language
Changing the language is as simple as setting locale
in the $i18n
property on the Vue instance.
this.$i18n.locale = 'fr'
Now it should display hello
in the language you just set, in this case french.
The actual format of the locale does not matter, as it will just search for the same key in the messages
object, you can have pt_BR
, pt-br
, pt_br
etc.
If you set a language that is not loaded in the messages
, it will show the fallback language’s translation. If the translation is not available in the fallback it will just return the key, in this case hello
.
Keys can be deeply nested if needed, like pages.login.title
.
With that said, lets create a basic language switcher, that loops our supported languages and on click, sets the selected language to $i18n.locale
.
Setting the language as a route parameter
Setting it up slightly depends on how you want your URLs to look like. The most popular choice is to have the language param as the first segment of the URL www.website.com/en.
If you are not using Vue Router to route your app, you would need to figure this next step yourself, but the idea should be the same.
- Check if the URL contains the language parameter
- Check if the language is supported by the app. If not redirect to a supported one.
- Check if the language in the param is the default one. Don’t do nothing if it is.
- (Optional) async load the language strings
- Change the language in the App
Making it work with Vue Router
First you need to tell the Router about the lang parameter. Define a top level route in your Vue Router routes like shown below. All pages will be children of this route.
Next initialize the Vue Router with the routes defined above.
Finally pass the Router instance to the main Vue instance.
Essentially we are creating a page whose route is a dynamic property that every time we assume is the language property.
The router can’t distinguish between website.com/en
and website.com/about
as both would fall into the same path matcher.
As mentioned above, all pages would need to be nested inside the children
key of the top level component. About Page would become website.com/en/about
and it will get rendered inside the router-view
of that top level page.
If you need a page to be matched without the lang in the URL, you have to define it above the lang
page definition we just added.
Checking if the lang
param is valid and supported by the App can be done inside the page’s beforeEnter
guard. It is a function that receives 3 parameters:
- the route you want to go to — to
- the one you come from — from
- callback to call when you are done — next
Read more about it in the VueRouter Route Guards docs:
Test the app. Go to website.com
and you should get redirected to website.com/en
.
Async loading translation strings
But what about more languages?
Well, we could load all our language strings in the messages object at once and it would work.
But what if we support 10 languages and each of them has 500 keys? Or 10000 keys? We would be pushing unnecessary code for all our users to load upfront, which they wont probably ever need. The more time it takes for your app to load, the more users may just quit.
What we could do is only provide the English keys by default and on demand asynchronously load the rest. If you are using Webpack this is super easy to do with the dynamic import function.
We will do two things.
- Load the translation strings on demand (async)
- Feed the strings into Vue-i18n
Lets add this to the beforeEnter
guard.
Now we need to extract all of the translation strings into separate json
files inside the lang
folder. It is a top level folder. This means the English ones as well. To make our code a bit cleaner we will just import the English strings and feed them to the i18n
instance.
We are using the path @/lang
which is an alias for the top-level folder. If you are using vue-cli its already setup for you. If not, then can do ../lang/${lang}.json
.
Using vue-i18n outside Vue components
The way we extracted i18n instance into its own i18n.js
file actually gives us the opportunity to use it in many places, where the Vue instance isn’t available and we cant use this.$i18n
. Such places are:
- Vuex store
- Route guards
- beforeRouteEnter Vue hooks — the Vue instance is not not ready yet, so you can’t use
this
there.
Cleaning it up
It all works, but there is some repetition and the route guard is a bit nasty. If you want to change the lang
param in the html tag or set the axios Accept-language
header, those would need to fit into the beforeEnter guard too.
What about checking for supported languages? Those are defined in a few places. Not really DRY.
What you could do is extract all those repetitions and fit them inside a helper service file that you can then import and use. You could also extract this into Vuex actions, but because it will not be used inside Vue components that often, it doesn’t make sense. That would also make them async.
Check out the example i18n-starter repo to see how you could refactor this to a more clean approach.
Bonus: Managing translation strings using Lokalise
Managing translation strings is hard work. Having to sync all language strings all the time is a drag.
Fortunately there is a super cool service to the rescue — Lokalise.com. Its a payed service, that has a free limited plan, but for the amount of time you are saving its well worth it. The free plan has 1 user and 500 keys, which is OK to try it out, even enough for smaller projects.
You can upload the English file as the original source, assign translators, then just download everything into separate .json
files when ready. It even detects if someone messed up a {placeholder}
in the translations. How cool is that?
You can also use their CLI tool to make syncing up and down even easier.
Checkout npm run lokalise:down
command in package.json
for info on how to make the sync-down work.
That’s it!
p.s. if you thoroughly enjoy stuff like this why not check our job openings at Hypefactors?