How to translate your Vue.js (v2) application with vue-i18n
Overview
This version of the tutorial covers Vue.js v2. We also have a newer version that covers Vue.js v3: How to translate your Vue.js app with vue-i18n
This tutorial guides you through adding translations to your Vue.js application using vue-i18n. The tutorial 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:
- babel
- eslint
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. You need vue-i18n version 8 for this.
yarn add vue-i18n@8
Now you have to answer some questions:
- The locale of project localization:
en
- The fallback locale of project localization:
en
- The directory where store localization messages of project. It's stored under
src
directory:locales
- Enable locale messages in Single file components ?
y
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 !!"
}
Where 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 bunch of options where you can keep the translations. Here are some pros and 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.
That's 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 syntax errors in your files when you get them back.
- 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.
- Vue.js single file components aka .vue files
Single file components can contain a
<i18n>
-section with the translation data.While it seems to be a good idea to make the components self-contained it turns out to be a bad idea in the long run. I am discussing this issue in more detail here: Why using i18n sections in vue.js single files components is a bad idea.
My recommendation: Don't use .vue files to store your 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 are working on a bigger project.
So, what should I do?
I'd recommend using .vue files for small projects 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:
- the folder src/locales/*.js with a bunch of language files
- the single file component section
<i18n>
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!"
}
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>
Translating attribute values
You can pass translated attribute values to sub components using this way:
<template>
<sub-component :title="$t('hello')"></sub-component>
</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:
id
id of the translationcount
the number of items for pluralizationparameters
parameters to add to the text (optional)
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>