Add i18n and manage translations of a Vue.js powered website

Dobromir Hristov
Hypefactors
Published in
6 min readJun 28, 2018
Vue + Vue Router + Vue-i18n + Lokalise.co

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:

  1. Instantiate the plugin by providing a default locale, a fallback and an object containing all the translation strings.
Setup i18n

2. Tell our App about that instance

Pass i18n instance to Vue app

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 .

Simple language switcher

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.

  1. Check if the URL contains the language parameter
  2. Check if the language is supported by the app. If not redirect to a supported one.
  3. Check if the language in the param is the default one. Don’t do nothing if it is.
  4. (Optional) async load the language strings
  5. 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.

Setup routes file

Next initialize the Vue Router with the routes defined above.

Instantiate Router

Finally pass the Router instance to the main Vue instance.

Pass router instance to App

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:

  1. the route you want to go to — to
  2. the one you come from — from
  3. callback to call when you are done — next

Read more about it in the VueRouter Route Guards docs:

Initial Router guard

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.

Async load json files

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.

Separate language files

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?

--

--

Dobromir Hristov
Hypefactors

I am a JavaScript developer with a passion for tech.