VueJS - Internationalization
Tags: vue, i18n, javascript, beginners
Published: 2021-06-25 17:42:50

Since the Internet's boom, more and more web applications are released each day (approximately 576k new websites per day*). Currently, there are over 1.7 billion websites* on the Internet and many of them are accessed by people from all around the world.

Probably, if you create a website, you will want it to be accessed by as many people as possible. To make that happen and also provide a nice experience for your users, you will have to think about how to deliver it in different languages.

_* Source: [Website Setup](https://websitesetup.org/news/how-many-websites-are-there)_

## TL;DR
In this article I'm going to show you how to:

- create a multi-language website using [VueJS](https://vuejs.org/) and [Vue i18n](https://kazupon.github.io/vue-i18n/);
- implement a language switcher;
- use the `vuex-persistedstate` package to avoid losing state when reloading the page;

---

### Internationalization (i18n)

Let's start with some basic concepts.

If you're still not aware of Internationalization or what i18n really means, here is its official definition:

> Internationalization is the design and development of a product, application or document content that enables easy localization for target audiences that vary in culture, region, or language.
_(Source: [W3.org](https://www.w3.org/International/questions/qa-i18n#i18n))_

Internationalization is often written i18n (English), where 18 is the number of letters between "i" and "n" in the English word (cool, right?!).

---

## Vue i18n

If you perform a quick search on the internet you will find some solutions to implement i18n to your website or application built using VueJS.
Probably, the most famous (and easy-to-use) one is [Vue i18n](https://kazupon.github.io/vue-i18n/), an open source plugin for VueJS that provides a very friendly API to implement translation to different target languages in your website.

### Installation

Assuming you've already created your VueJS project (take a [look at this article](https://dev.to/vcpablo/vuejs-one-more-way-of-structuring-your-project-13mj) if you don't now where to start from), the first step to begin using the plugin is installing it. In a terminal window, go to the root directory of your project and run the following command:

```shell
yarn add vue-i18n --save
```

_You might use NPM as well, depending on your project configuration._

### Configuration

The Vue i18n package works in a very simple way.
You can set several configurations, but these are the basic ones required for your project to work:

- the initial language: the language loaded by default;
- the messages: a simple JSON object that contains the messages (translation keys) used for each one of the languages;

---

First, create the folder structure that will hold everything together:

1. Create a folder called `i18n` in the `src` directory of your project.

2. Within the `i18n` folder, create an `index.js` file and a folder called `messages`.

This is how this first `index.js` file will look like:

```javascript
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import messages from './messages'

Vue.use(VueI18n)

export default new VueI18n({
  locale: 'en',
  messages
})
```

3. In this example, we're going to build a part of an application that needs to be delivered in three different languages: English (default), Spanish and Brazilian Portuguese. 

Within the `messages` folder, create three folders named `en`, `es` and `pt-BR` and, inside each one of them (that represents a different language), create two files: one named `menu.js` and another named `index.js` .

The files will look like this:

**English**

```javascript
// /src/i18n/messages/en/menu.js

export default {
  home: 'Home',
  about: 'About',
  contact: 'Contact'
}

```


```javascript
// /src/i18n/messages/en/index.js

import menu from './menu'

export default {
  menu
}
```


**Spanish**

```javascript
// /src/i18n/messages/es/menu.js

export default {
  home: 'Pagina de Inicio',
  about: 'Acerca de',
  contact: 'Contacto'
}
```

```javascript
// /src/i18n/messages/es/index.js

import menu from './menu'

export default {
  menu
}
```

**Portuguese (Brazil)**

```javascript
// /src/i18n/messages/pt-BR/menu.js

export default {
  home: 'Início',
  about: 'Sobre',
  contact: 'Contato'
}
```

```javascript
// /src/i18n/messages/pt-BR/index.js

import menu from './menu'

export default {
  menu
}
```

If you need, you may create more levels within the `messages` object to organise them better. Like this:

```javascript
export default {
  links: {
    home: {
      label: 'Home',
      help: 'Click here to go to home page'
    },
    about: {
      label: 'About',
      help: 'Click here to know more about us'
    },
    contact: {
      label: 'Contact',
      help: 'Click here to go to reach out to us'
    }

  }
}
```

4. Still inside the `messages` folder, create an `index.js` file like this:

```javascript
import en from './en'
import es from './es'
import ptBR from './pt-BR'

export default {
  en,
  es,
  'pt-BR': ptBR
}
```

5. In the `main.js` file, import the `i18n` package and set it to the Vue instance:

```javascript
import App from './App.vue'
import i18n from './i18n' 

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

Now your application is ready to take advantage of the vue-i18n plugin. Let's create a simple scenario to use it.

### Implementation

We're going to implement a language switcher and put it into a navbar at the top of the page. This switcher will be responsible to set the current _locale_ of the application using Vuex + Vuex Persisted State.

To make things easier, I chose to use [Bootstrap Vue](https://bootstrap-vue.org/). If you don't know it yet, it's worth taking a look. It provides all of the [Bootstrap](https://getbootstrap.com/) components, wrapped into Vue components :)

Before creating the component itself, we're going to structure a basic [Vuex](https://vuex.vuejs.org/) module that will be responsible for managing the language state, we'll also make use of the [Vuex Persisted State](https://github.com/robinvdvleuten/vuex-persistedstate) plugin, to easily store the state in the local storage so that, when refreshing the page, the user does not lose its selected language.

1. To add `Vuex Persist` in your project, run the following command in the root directory of your project:

```shell
yarn add  vuex-persistedstate --save
```

2. Create a file named `index.js` and a folder named `store` inside the `src` directory. 

3. Create folder named `modules` within `store`.

4. Create a file named `locale.js` inside the `modules` folder and implement it like this:

```javascript
// src/store/modules/locale.js

export default {
  namespaced: true,
  state: {
    locale: 'en'
  },
  mutations: {
    setLocale(state, locale) {
      state.locale = locale
    }
  }
}
```

This is how the `store/index.js` will look like:

```javascript
// src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'

import locale from './modules/locale'

const persistedState = createPersistedState({
  key: 'vuejs-vue-i18n',
  paths: ['locale']
})

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    locale
  },
  plugins: [persistedState]
})

```

5. Now, let's implement the `LanguageSwitch.vue` component. It will hold all of the available languages and it will use Vuex helpers functions to update the current language:

```html
<!-- src/components/LanguageSwitcher.vue (template) -->
<template>
  <b-nav-item-dropdown :text="currentLocale" right>
    <b-dropdown-item
      :disabled="isCurrentLocale('en')"
      @click="onSetLocale('en')"
    >
      EN
    </b-dropdown-item>
    <b-dropdown-item
      :disabled="isCurrentLocale('es')"
      @click="onSetLocale('es')"
    >
      ES
    </b-dropdown-item>
    <b-dropdown-item
      :disabled="isCurrentLocale('pt-BR')"
      @click="onSetLocale('pt-BR')"
    >
      PT-BR</b-dropdown-item
    >
  </b-nav-item-dropdown>
</template>
```

```javascript
// src/components/LanguageSwitcher.vue (script)

<script>
import { mapState, mapMutations } from 'vuex'

export default {
  name: 'LanguageSwitcher',
  computed: {
    ...mapState('locale', ['locale']),
    currentLocale() {
      return this.locale.toUpperCase()
    }
  },
  created() {
    this.$i18n.locale = this.locale
  },
  methods: {
    ...mapMutations('locale', ['setLocale']),
    onSetLocale(locale) {
      this.$i18n.locale = locale
      this.setLocale(locale)
    },
    isCurrentLocale(locale) {
      return this.locale === locale
    }
  }
}
</script>
```

6. Now, let's create a simple `Navbar.vue` component to put the recently created `LanguageSwitcher` one.
Notice that, in this case, we use the global `$t` helper provided by the Vue i18n plugin to get the proper translation we need to display according to the current locale.
It's very simple to use, all you need to do, it call it passing a translation key as argument.

Example:

```html
{{ $t('translation.key') }}
```

You may also use directly in the `script` section of your components, if needed:

```javascript
{
  computed: {
    label() {
       // For this work, you have to create a file named `common.js` inside the folder of each language and export it in its respective `index.js` file.
       return this.$t('common.label')
    }
  },
  methods: {
    getTitle() {
       return this.$t('common.title')
    }
  }
}
```

This is how our `Navbar.vue` component will look like:

```html
<!-- src/components/Navbar.vue (template) -->
<template>
  <b-navbar toggleable="lg" type="dark" variant="primary">
    <b-navbar-brand>VueJS vue-i18n</b-navbar-brand>

    <b-navbar-toggle target="nav-collapse" />

    <b-collapse id="nav-collapse" is-nav>
      <b-navbar-nav>
        <b-nav-item :to="{ name: 'Home' }">
          {{ $t('navbar.home') }}
        </b-nav-item>
        <b-nav-item :to="{ name: 'About' }">
          {{ $t('navbar.about') }}
        </b-nav-item>
        <b-nav-item :to="{ name: 'Contact' }">
          {{ $t('navbar.contact') }}
        </b-nav-item>
      </b-navbar-nav>

      <b-navbar-nav class="ml-auto">
        <LanguageSwitcher />
      </b-navbar-nav>
    </b-collapse>
  </b-navbar>
</template>
```

```javascript
<!-- src/components/Navbar.vue (script) -->
<script>
import LanguageSwitcher from '@/components/LanguageSwitcher/LanguageSwitcher'

export default {
  name: 'Navbar',
  components: {
    LanguageSwitcher
  }
}
</script>
```

7. We're going to create a `Layout.vue` component that will hold the `Navbar` and will be used within the Views we are going to create next:

```html
<!-- src/views/Layout.vue (template) -->
<template>
  <b-row>
    <b-col>
      <Navbar />
      <b-container>
        <slot />
      </b-container>
    </b-col>
  </b-row>
</template>
```

```javascript
// src/views/Layout.vue (script)

<script>
import Navbar from '@/components/Navbar'

export default {
  name: 'Layout',
  components: {
    Navbar
  }
}
</script>
```

8. For everything to work properly, we need to create the views, implement the `Layout` component into them and add them to the `router/index.js` file.
In this section, the most important thing is to use the global `$t` helper, provided by the Vue i18n package 

**src/components/Home.vue**

```
<template>
  <Layout>
    <h1>{{ $t('navbar.home') }}</h1>
  </Layout>
</template>
```
```javascript
<script>
import Layout from './Layout'

export default {
  name: 'HomeView',
  components: {
    Layout
  }
}
</script>
```

**src/components/About.vue**

```html
<template>
  <Layout>
    <h1>{{ $t('navbar.about') }}</h1>
  </Layout>
</template>
```

```javascript
<script>
import Layout from './Layout'

export default {
  name: 'AboutView',
  components: {
    Layout
  }
}
</script>
```

**src/components/Contact.vue**
```html
<template>
  <Layout>
    <h1>{{ $t('navbar.contact') }}</h1>
  </Layout>
</template>
```

```javascript
<script>
import Layout from './Layout'

export default {
  name: 'ContactView',
  components: {
    Layout
  }
}
</script>
```

In order to use nested translation keys, the process is simple, since Vue I18n works  with the full translation key path, like this:

```html
<template>
  <Layout>
    <h1>{{ $t('navbar.links.contact.label') }}</h1>
  </Layout>
</template>
```

**src/router/index.js**

```javascript
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About')
  },
  {
    path: '/contact',
    name: 'Contact',
    component: () => import('@/views/Contact')
  }
]

const router = new VueRouter({
  mode: 'history',
  routes
})

export default router
```

This is how the application should work after the full implementation:

<a href="https://www.loom.com/share/f0ed53a41f0a42b59676af6f4ff6f06b"> <img src="https://cdn.loom.com/sessions/thumbnails/f0ed53a41f0a42b59676af6f4ff6f06b-with-play.gif"> <p>VueJS Internationalization Sample Video</p> </a>

You can find the fully-working source code in [this link](https://github.com/vcpablo/vuejs2-vue-i18n)!

### Other features

Besides translating simple pieces of text, Vue I18n also provides other useful features like:

- [Formatting Interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting)

- [Pluralization](https://kazupon.github.io/vue-i18n/guide/pluralization.html)

- [Date Time Localization](https://kazupon.github.io/vue-i18n/guide/datetime.html)

- [Number Localization](https://kazupon.github.io/vue-i18n/guide/number.html)

You may explore the [website](https://kazupon.github.io/vue-i18n/) to find out more about the tool.

---

I hope you liked it.
Please, comment and share!

Cover image by [Ben White](https://unsplash.com/@benwhitephotography)