Advanced localization techniques in Vue.js
Tags: vue, javascript, a11y
Published: 2020-02-05 16:06:20

---
title: Advanced localization techniques in Vue.js
published: true
date: 2020-02-03 14:00:19 UTC
tags: vue,javascript,a11y
canonical_url: https://blog.logrocket.com/advanced-localization-techniques-vue-js/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/8lhulitrdjq1w77rff6o.jpeg
---
**Written by [Preetish HS](https://blog.logrocket.com/author/preetishhs/)**✏️

Localization is a great way to make your web application more accessible to a wider audience and provide a better user experience. For businesses in particular, localization helps strengthen global presence, thus creating potential for greater revenue. Let’s look at some techniques to implement localization in Vue.js.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Getting set up

Let’s create a Vue application using the CLI.

```jsx
vue create localization-app
```

Select `vue-router` and `vuex`, as we will need them later.

After creating the project, let’s add our translation library, `vue-i18n`. We also have a Vue CLI package for that, so we can simply run the following:

```jsx
cd localization-app
vue add i18n
```

![Hello, World Example](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/02/hello-world-example.png?resize=611%2C490&ssl=1)

Since we installed the `vue-i18n` package, it automatically does all required setup. It also creates a `locale` folder, with `en.json` as our default language file.

```jsx
//en.json
{
  "hello": "hello i18n !!",
  "welcomeMessage": "Welcome to Advanced Localization techniques tutorial"
}
```

Let’s create one more file in the directory for French translations, `fr.json`, and add the following code:

```jsx
//fr.json
{
  "hello": "Bonjour i18n !!",
  "welcomeMessage": "Bienvenue dans le didacticiel sur les techniques de localisation avancées"
}
```

To use it in our component, open `App.vue`. There is some default code present, with `msg` being passed to the `<hello-world>` component. Let’s edit it as follows:

```jsx
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <HelloWorld :msg="$t('hello')" />
  </div>
</template>
```

In the `HelloWorld.vue` file, let’s remove some code and have minimal code for learning:

```jsx
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <HelloWorld :msg="$t('hello')" />
  </div>
</template>
```

Finally, move the `i18n.js` file in the root directory to the plugins directory for better structure. When you run the app, you’ll see `Hello i18n` in English. Since we haven’t set any preference, it takes the fallback language.

![Vue I18n Install And Setup](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/02/vue-i18n-install-setup.png?resize=730%2C97&ssl=1)

## Directory structure

We can have separate `json` files for different languages in the `locales` folder.

```jsx
src
|--plugins
|  |--i18n.js
|--locales
|  |--formats
|  |--en.json
|  |--fr.json
|  |--zh.json
|  |--de.json
      .
      .
```

## Translations directly in Vue component files

```jsx
<i18n>
  {
    "en": {
      "welcome": "Welcome!"
    },
    "fr": {
      "welcome": "Bienvenue"
    }
  }
</i18n>
```

We can have our component-specific translations in their own components. While this might seem like nice isolation from other locales, there are more cons than pros. It would work for small apps with fewer translations, but as the app starts getting big, we’ll soon run into problems, like:

1. You’ll wind up duplicating efforts. For example, the text `Welcome` might be used in multiple places (login screen, store page, etc.), and you’d have to write the same translations for each of these components
2. As the number of translations and languages increase, the component starts getting big and ugly.
3. Generally, developers don’t manage translations; there may be a language translation team with minimal coding experience. It becomes almost impossible for them to figure out the components and syntax to update translations.
4. You’re not able to share locales among different components.

I personally prefer using `.json` files for both small and big applications since it is much easier to maintain.

## Using the browser’s default language

We are using English as our default language now. If someone with their browser language set to French also sees the website in English, they have to manually change the language using the dropdown. For a better user experience, the application should automatically change its language based on the browser’s default language. Let’s see how this is done.

In the `i18n.js` file, let’s assign `navigator.language` (the browser’s default language) to `locale`. Browsers generally prefix the default language like `en-US` or `en-GB`. We just need the first part for our setup, hence we use `navigator.language.split('-')[0]`:

```jsx
// plugins/i18n.js
export default new VueI18n({
  locale:
    navigator.language.split('-')[0] || process.env.VUE_APP_I18N_LOCALE || 'en',
  fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
  messages: loadLocaleMessages()
})
```

But let’s say we do have region-specific modifications in the same language. We generally follow the naming convention where we suffix the region after the language (e.g., `en-US.json` , `en-GB.json`). To get the correct language for the region, we need to do a few more operations than before:

```jsx
function checkDefaultLanguage() {
  let matched = null
  let languages = Object.getOwnPropertyNames(loadLocaleMessages())
  languages.forEach(lang => {
    if (lang === navigator.language) {
      matched = lang
    }
  })
  if (!matched) {
    languages.forEach(lang => {
      let languagePartials = navigator.language.split('-')[0]
      if (lang === languagePartials) {
        matched = lang
      }
    })
  }
  return matched
}
export default new VueI18n({
  locale: checkDefaultLanguage() || process.env.VUE_APP_I18N_LOCALE || 'en',
  fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
  messages: loadLocaleMessages()
})
```

The `loadLocaleMessages()` method is already available by default; we make use of the same method to extract the filenames of our `json` files. Here, we get `['en-GB', en-US', 'fr']`. Then we write a method called `checkDefaultlanguage()`, where we first try to match the full name. If that’s unavailable, then we match just the first two letters. Great, this works!

Let’s consider another scenario. Say our default language is `fr`, and the browser language is `en-IN`. `en-IN` is not present in our language list, but showing French (the default language) doesn’t make much sense because we do have English from other regions. Though it’s not quite the same, it’s still better than showing a totally different language. We need to modify our code one more time to work for this scenario.

```jsx
function checkDefaultLanguage() {
  let matched = null
  let languages = Object.getOwnPropertyNames(loadLocaleMessages())
  languages.forEach(lang => {
    if (lang === navigator.language) {
      matched = lang
    }
  })
  if (!matched) {
    languages.forEach(lang => {
      let languagePartials = navigator.language.split('-')[0]
      if (lang === languagePartials) {
        matched = lang
      }
    })
  }
  if (!matched) {
    languages.forEach(lang => {
      let languagePartials = navigator.language.split('-')[0]
      if (lang.split('-')[0] === languagePartials) {
        matched = lang
      }
    })
  }
  return matched
}
export const selectedLocale =
  checkDefaultLanguage() || process.env.VUE_APP_I18N_LOCALE || 'en'
export const languages = Object.getOwnPropertyNames(loadLocaleMessages())
export default new VueI18n({
  locale: selectedLocale,
  fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
  messages: loadLocaleMessages()
})
```

Here we split both strings (i.e., the browser default and the JSON filenames) and finally match `en-IN` with `en-GB`, which is way better than showing French. I am also exporting a few constants, which we’ll be using later.

## Persisting language preference

Let’s manually change the language to French now using the dropdown we created. The texts get translated to French. Now refresh the page or close the tab and reopen it. The language is reset to English again!

This doesn’t make for good user experience. We need to store the user’s preference and use it every time the application is used. We could use `localStorage`, save and fetch every time, or we can use Vuex and the `vuex-persistedstate` plugin to do it for us.

Let’s do it the Vuex way. First we need to install the plugin:

```jsx
npm install --save vuex-persistedstate


//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import i18n, { selectedLocale } from '@/plugins/i18n'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    locale: selectedLocale
  },
  mutations: {
    updateLocale(state, newLocale) {
      state.locale = newLocale
    }
  },
  actions: {
    changeLocale({ commit }, newLocale) {
      i18n.locale = newLocale // important!
      commit('updateLocale', newLocale)
    }
  },
  plugins: [createPersistedState()]
})
```

Instead of using component state, let’s use `Vuex` to store and mutate the change in language. The `vuex-persistedstate` plugin will store the `locale` variable in `localStorage`. When it is set, every time the page reloads, it fetches this data from `localStorage`.

Now we need to link this data to our language selection dropdown.

```jsx
<template>
  <div class="lang-dropdown">
    <select v-model="lang">
      <option
        v-for="(lang, i) in languageArray"
        :key="`lang${i}`"
        :value="lang"
      >
        {{ lang }}
      </option>
    </select>
  </div>
</template>
<script>
import { languages } from '@/plugins/i18n'
export default {
  data() {
    return {
      languageArray: languages
    }
  },
  computed: {
    lang: {
      get: function() {
        return this.$store.state.locale
      },
      set: function(newVal) {
        this.$store.dispatch('changeLocale', newVal)
      }
    }
  }
}
</script>
```

Instead of hardcoding the language list, we are now importing it from the `i18n.js` file (we had exported this list before). Change the language and reload the page — we can see that the site loads with the preferred language. Great!

## Date/time localization

Different countries and regions have different time formats, and the names of days and months are, of course, written in their native languages. To localize the date and time, we need to pass another parameter, `dateTimeFormats`, while initializing `vue-i18n`.

Internally, the library uses [ECMA-402 Intl.DateTimeFormat](http://www.ecma-international.org/ecma-402/2.0/#sec-intl-datetimeformat-constructor), hence we need to write our format in the same standards to work. Create a file `dateTimeFormats.js` inside `src/locales/formats`:

```jsx
//locales/formats/dateTimeFormats.js
export const dateTimeFormats = {
  fr: {
    short: {
      day: 'numeric',
      month: 'short',
      year: 'numeric'
    },
    long: {
      weekday: 'short',
      day: 'numeric',
      month: 'short',
      year: 'numeric',
      hour: 'numeric',
      minute: 'numeric',
      hour12: true
    }
  },
  'en-US': {
    short: {
      year: 'numeric',
      month: 'short',
      day: 'numeric'
    },
    long: {
      year: 'numeric',
      month: 'short',
      day: 'numeric',
      weekday: 'short',
      hour: 'numeric',
      minute: 'numeric'
    }
  }
}
```

As shown above, we just need to mention the items such as `day`, `month`, etc., and the library does all the formatting and translation for us based on the locale selected.

## Reusing translations

As the app starts growing, our localization file contents also start growing. For better readability, we need to nest the translations in our JSON file based on the `categories` or `components`, depending on the application. Soon we’ll see a lot of repeated messages, or common words such as _username_, _hello_, or _click here_ appearing in many components.

```jsx
//en.json
{
 "homepage": {
    "hello": "hello i18n !!",
    "welcomeMessage": "Welcome to Advanced Localization techniques tutorial",
    "userName": "Username",
    "login": "Login"
  },
  "login": {
    "userName": "Enter Username",
    "password": "Enter Password",
    "login": "Login"
  },
  "forgotPassword": {
    "email": "Email",
    "continue": "Click to get recovery email",
    "submit": "Click to get Login"
  }
}
```

We can see that translations like `userName` and `login` have already started repeating. If we need to update one text, we have to update it at all places so that it reflects everywhere. In medium to large apps, we’ll have thousands of lines of translations in each `JSON` file. If we use translations from different nested objects in one component, it starts becoming hard to track and debug.

We should group them based on `Category` instead. Even then, we’ll still encounter some duplicates. We can reuse some translations by using links, like below:

```jsx
//en.json
{
 "homepage": {
    "hello": "hello i18n !!",
    "welcomeMessage": "Welcome to Advanced Localization techniques tutorial",
    "userName": "Username",
    "login": "Login"
  },
  "login": {
    "userName": "Enter @:homepage.userName",
    "password": "Enter Password",
    "login": "@:homepage.login"
  },
  "forgotPassword": {
    "email": "Email",
    "continue": "Click to get recovery @:forgotPassword.email",
    "submit": "Click to get @:login.login"
  }
}
```

## Using translations with `vue-router`

Right now, we can’t know in which language the website is being displayed just by seeing the URL `localhost:8080`. We need it to show something like `localhost:8080/fr`, i.e., when the user opens the root URL `localhost:8080`, we need to redirect them to `localhost:8080/fr`.

Also, when the user changes the language to English using the dropdown, we need to update the URL to `localhost:8080/en`. There are multiple ways to do this, but since we are already using Vuex to maintain our locale state, let’s use that to implement this feature.

Let’s create one more page called `About.vue` and add some content there. The `/router/index.js` file should look like this:

```jsx
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import App from '@/App.vue'
import { languages } from '@/plugins/i18n'
import store from '@/store'
import About from '@/views/About.vue'
Vue.use(VueRouter)
const routes = [
  {
    path: '/',
    name: 'root',
    beforeEnter(to, from, next) {
      next(store.state.locale)
    }
  },
  {
    path: '/:lang',
    component: App,
    beforeEnter(to, from, next) {
      let lang = to.params.lang
      if (languages.includes(lang)) {
        if (store.state.locale !== lang) {
          store.dispatch('changeLocale', lang)
        }
        return next()
      }
      return next({ path: store.state.locale })
    },
    children: [
      {
        path: '',
        name: 'home',
        component: Home
      },
      {
        path: 'about',
        name: 'about',
        component: About
      }
    ]
  }
]
const router = new VueRouter({
  mode: 'history',
  routes
})

export default router
```

We are first redirecting the request we get for root URL (`/`) to `/:lang` by passing the current locale `next(store.state.locale)`.

**Case 1** : Changing the URL manually to `localhost:8080/en-US`. Since our website supports `en-US`, this will call our store action to also change the language to English.

**Case 2** : We change the language using the dropdown. This should also update the URL. To do this, we need to watch the changes to our locale state in `App.vue`.

```jsx
export default {
  name: 'app',
  computed: mapState(['locale']),
  watch: {
    locale() {
      this.$router.replace({ params: { lang: this.locale } }).catch(() => {})
    }
  }
}
```

You can find the [GitHub repo for this project here](https://github.com/preetishhs/vue-localization-techniques).

## There we have it!

We learned some of the advanced ways to design localizations in an application. The vue-i18n documentation is also well maintained and a great resource to learn the features and concepts used for localization in Vue. Combining both techniques, we can build solid and efficient localization in our application so that it can cater a wider audience.

* * *
## Experience your Vue apps exactly how a user does 

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, [try LogRocket.](https://www2.logrocket.com/vue-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/t4ni6gomxqbkbxygmco0.png)

[LogRocket](https://www2.logrocket.com/vue-performance-monitoring) is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - [Start monitoring for free.](https://www2.logrocket.com/vue-performance-monitoring)
* * *

The post [Advanced localization techniques in Vue.js](https://blog.logrocket.com/advanced-localization-techniques-vue-js/) appeared first on [LogRocket Blog](https://blog.logrocket.com).