INDIE DISCOUNT
Independent developers get 50% discount on single user licenses for TP, PE and SI
Visit store

How to translate your Vue.js application with vue-i18n

2019-01-25 Andreas Löw

Overview

This tutorial guides you through adding translations to your Vue.js application using vue-i18n. The tutorials starts with a blank demo project which you'll create in the next step. You can of course apply the steps to your own project...

The second part of the tutorial explains how you can use BabelEdit to easily manage your translations. This includes fast pre-translation using machine translation, import and export of translations to work with translators and more.

Preparing a simple demo project

You can of course skip these steps if you've already installed vue-js...

Install vue-cli

yarn global add @vue/cli

Create the project

vue create vue-i18n-demo

Choose the default:

Start the demo project:

cd vue-i18n-demo
yarn serve

Open your browser and navigate to http://localhost:8080/ - you should see a vuejs welcome screen.

Adding vue-i18n

If you use vue-cli it's easy to add vue-i18n:

vue add i18n    

Now you have to answer some questions:

vue-i18n makes some changes to your project files. Let's review them. If you don't want to use vue-cli you can easily make the changes manually.

Files changed by vue-cli in your project

package.json

New dependencies:

vue-i18n

New dev-dependencies:

@kazupon/vue-i18n-loader
vue-cli-plugin-i18n

You can add these changes manually using yarn:

yarn add vue-i18n
yarn add --dev @kazupon/vue-i18n-loader vue-cli-plugin-i18n

.env

A new .env file appears in the project. It contains the default and fallback language configurations:

VUE_APP_I18N_LOCALE=en
VUE_APP_I18N_FALLBACK_LOCALE=en

vue.config.js

module.exports = {
  pluginOptions: {
    i18n: {
      locale: 'en',
      fallbackLocale: 'en',
      localeDir: 'locales',
      enableInSFC: true
    }
  }
}

src/components/HelloI18n.vue

This is a simple component that displays a simple message. It makes use of the new <i18n> section in the Single File Component. The new section contains the translation used in this file:

<template>
  <p>{{ $t('hello') }}</p>
</template>

<script>
export default {
  name: 'HelloI18n'
}
</script>

<i18n>
{
  "en": {
    "hello": "Hello i18n in SFC!"
  }
}
</i18n>

src/App.vue

App.vue requires some manual changes to render the new HelloI18n component instead of the HelloWorld component:

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloI18n/>
  </div>
</template>

<script>
import HelloI18n from "./components/HelloI18n";

export default {
  name: 'app',
  components: {
    HelloI18n
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

src/i18n.js

This file imports JSON files with translations from the locales folder and initialises VueI18n:

import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

function loadLocaleMessages () {
  const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
  const messages = {}
  locales.keys().forEach(key => {
    const matched = key.match(/([A-Za-z0-9-_]+)\./i)
    if (matched && matched.length > 1) {
      const locale = matched[1]
      messages[locale] = locales(key)
    }
  })
  return messages
}

export default new VueI18n({
  locale: process.env.VUE_APP_I18N_LOCALE || 'en',
  fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
  messages: loadLocaleMessages()
})

src/main.js

import Vue from 'vue'
import App from './App.vue'
import i18n from './i18n'

Vue.config.productionTip = false

new Vue({
  i18n,
  render: h => h(App)
}).$mount('#app')

src/locales/en.json

This is a json message file containing translations for English:

{
  "message": "hello i18n !!"
}

How to keep your translations: An important decision

The translation works like this: You have an ID for the texts that you want to present in your application and a file contains the translations for each ID.

Vue.js offers you a whole bunch of options how you can keep these translations:

Here are some pros an cons for each variant:

inline as data in javascript code

This is what the documentation of vue-i18n uses in its examples. The advantage is that the examples are self-contained in one file.

Thats nice for a simple example — but it's a nightmare when it comes to real projects especially if you work with a translator. Those guys are quite unhappy with editing js source code — believe me! And you really don't want to correct all the errors when you get the files back.

My recommendation: Don't use inline translations!

js files

These files contain the translations as an exported data set. It's your choice put one or multiple languages in one file.

The advantage is that these files can easily be imported using require/import. The separation of program code and translations is a step in the right direction but still not a good solution.

My recommendation: Don't use js files for translations.

JSON files

Similar to the js files but more portable. You can still import/require them directly in your code or load them from a server.

You can easily validate JSON and YAML files and there are tools that can help you manage the translations. With an editor like BabelEdit it's impossible to create invalid files.

You can keep one or many languages in one file — but I'd really recommend using one file per language. It's easier for the translator if he can reference other translations. This increases the consistency of your app — e.g. did you use "delete", "remove" or "discard".

My recommendation: Use JSON files if you have a bigger project.

Vue.js single file components aka .vue files

Single file components can contain a <i18n>-section with the translation data.

The big advantage is that the translation is only available within that scope and does not interfere with other components. It also makes the components self-contained with allows simple sharing across multiple projects.

The disadvantage is that they are hard to maintain and it's almost impossible to work with a translation agency without proper tools.

BabelEdit comes to help here: You can edit the .vue files in one place and export/import the translations for external translators.

Another disadvantage is that things become unhandy if you have many strings and languages. E.g. 5 languages and 10 strings increase your file by about 60 lines.

My recommendation: Use .vue files if you want to re-use your components.

So, what should I do?

I'd recommend using .vue files for small project with portable components, .json files for bigger projects. You'll see both options during this tutorial which should give you enough knowledge to make your choice.

Adding translations

The demo project now contains both recommended methods for storing your translations:

While it is possible to use both ways at the same time, I'd really recommend making a decision for one of the methods.

The main difference between the JSON files and the SFC section is that the translations in the JSON files are globally available whereas the SFC sections are only available in the component's scope.

The translation in the SFC wins if a translation is present in both, the JSON and the SFC. The SCF method is great if you want to re-use your components in different projects.

.vue files

In the HelloI18n.vue you can add more translations by extending the <i18n> section:

<i18n>
{
  "en": {
    "hello": "Hello i18n in SFC!",
    "welcome": "Welcome!",
    "yes-button": "Yes",
    "no-button": "No!"
  },
  "de": {
    "hello": "Hallo i18n in SFC!",
    "welcome": "Willkommen!",
    "yes-button": "Ja",
    "no-button": "Nein!"
  }
}
</i18n>

JSON files

If you prefer JSON add the new translations to the en.json and create a new de.json for the German translation:

src/locales/en.json

{
    "hello": "Hello i18n in SFC!",
    "welcome": "Welcome!",
    "yes-button": "Yes",
    "no-button": "No!"
}

src/locales/de.json

{
    "hello": "Hallo i18n in SFC!",
    "welcome": "Willkommen!",
    "yes-button": "Ja",
    "no-button": "Nein!"
}

Spending time updating your translations?

BabelEdit is the translation editor for your vue-i18n project.

See all translations at the same time. Save hours of editing JSON or VUE files. Easy data exchange with translation agencies.

Using translations in your templates

Simple text

To use a translation use the $t() function with a translation id as parameter:

<template>
  <p>{{ $t('hello') }}</p>
</template>

Text with parameters

You can of course add parameters to your translation texts:

{
    "hello": "Hallo {name}!",
}
<template>
  <p>{{ $t('hello', { name: 'Vue.js'}) }}</p>
</template>

Pluralization

Pluralization is about changing the text depending on the values.

Use the $tc(id,count,parameters) function to set your translation with the following parameters:

Singular and plural

Add a | to separate singular and plural:

{
    "text": "flower | flowers",
}
<template>
  <p>{{ $tc('text', 2) }}</p>
</template>

Zero, one or many

Add a | to separate singular and plural:

{
    "wolves": "no wolf | one wolf | a pack of wolves",
}
<template>
  <p>{{ $tc('wolves', 1) }}</p>
</template>

vue-i18n automatically replaces {count} and {n} in the translation with the number passed to the $tc() function:

{
    "wolves": "no wolf | one wolf | a pack of {n} wolves",
}
<template>
  <p>{{ $tc('wolves', 5) }}</p>
</template>

You can also pass a parameter object with named items as 3rd parameter:

{
    "wolves": "no wolf | one wolf | a pack of {numberOfWolves} wolves",
}
<template>
  <p>{{ $tc('wolves', 5, {numberOfWolves: 5}) }}</p>
</template>

Structuring your translations

You can use nested objects in your translation files to group your translations:

{
    "main-screen": {
        "title": "Main screen",
        "description": "This is the main screen..."
    },
    "about-screen": {
        "title": "About screen",
        "description": "That's what this is all about...."
    },
}

Use a . to access the sub-elements:

<template>
  <p>{{ $t('about-screen.title') }}</p>
</template>

Referencing other translations

You can use links to include other translations in your texts. This is helpful if you want to re-use translations:

{
    "common": {
        "appname": "Vue-Demo",
        "yes": "Yes",
        "no": "No"
    },
    "exit-dialog": {
        "title": "Exit screen",
        "text": "Do you really want to exit @:common.appname ?",
        "button-1": "@:common.yes",
        "button-2": "@:common.no"
    },
}

Switching locales

Switching the locale is quite simple: Just assign the new locale to the locale property of the VueI18n you created in src/i18n.js.

This code gives you a simple combo-box that you can use to switch between languages:

<template>
  <div class="locale-changer">
    <select v-model="$i18n.locale">
      <option v-for="(lang, i) in langs" :key="`Lang${i}`" :value="lang">{{ lang }}</option>
    </select>
  </div>
</template>

<script>
export default {
  name: 'locale-changer',
  data () {
    return { langs: ['en', 'de'] }
  }
}
</script>

Did you like the tutorial? Please share!