Mastering Internationalization: A Guide to Localization in Vue Apps with Vue I18n

A Guide to Localization in Vue Apps with Vue I18n

Mastering Internationalization: A Guide to Localization in Vue Apps with Vue I18n

Introduction

Localization is the process of adapting your Application to the culture and language of users in a target market. Localization plays a huge role in making your website or service more appealing to the end users.

For any end user, nothing's more pleasing than experiencing the services in the local language hence Localisation is proven to add value to the application by boosting the user experience and engagement and plays a key role in gaining reach and credibility in the global market.

This article guides you through concepts of localisation in the Vue.js application using Vue I18n one of the popular localisation libraries and how to implement useful features like lazy loading locales and getting the browser language preference of the user and persisting the user locale preference through building a demo Vue app.

This article deals with

  • Scaffolding a Vue project using Vue CLI.

  • Adding Vue I18n as a dependency in the project.

  • Creating constants for localisation setup.

  • Adding locales for supported languages.

  • Creating the i18n plugin and installing it globally in the Vue app.

  • Creating utils to handle all localisation-related operations.

  • Adding basic UI components for the demo app and adding localisation to the content.

App demo gif

Let's code!!!

Scaffolding a Vue project using Vue CLI

One of the effortless and efficient ways to scaffold a Vue application template is to use Vue CLI.

A basic Vue 3 object API template was used in this project

vue create vue-localization-demo

install Vue I18n as a dependency

Vue I18n is one of the most popular and easy-to-integrate internationalization plugins available for Vue applications.

npm install vue-i18n

Creating constants for localisation setup.

Create constants folder in src and add localisationConstants.js file with default, fallback and supported locales.

// src/constants/localisationConstants.js

export const DEFAULT_LANGUAGE = "en";
export const FALLBACK_LANGUAGE = "en";
export const SUPPORTED_LANGUAGES = ["en", "es"];

Adding locales for supported languages.

Create locales folder in src and add locales for all the supported languages.

Add file names as <locale-code>.js. example: en.json, es.json, etc.

// en.json
{
    "heading": "Vue Localization Demo",
    "subHeading": "What is localization?",
    "demoText": "Localization is the adaptation of a product or service to meet the needs of a particular language, culture or desired population's 'look-and-feel.' A successfully localized service or product is one that appears to have been developed within the local culture."
}
// es.json
{
    "heading": "Demostración de localización de Vue",
    "subHeading": "¿Qué es la localización?",
    "demoText": "La localización es la adaptación de un producto o servicio para satisfacer las necesidades de un idioma en particular, una cultura o la 'apariencia' deseada de la población. Un servicio o producto localizado con éxito es aquel que parece haber sido desarrollado dentro de la cultura local."
}

Creating the i18n plugin and installing it globally in the Vue app.

Create plugins folder in src and add i18n.js file with the below content in it.

Static loading of all locales at the initial render of the app increases load time and is not optimal. So the best practice is to lazy load the locale messages i.e loading them as the user changes the locale.

// src/plugins/i18n.js
import { createI18n } from "vue-i18n/index";
// importing locale messages for en locale
import enMessages from "../locales/en.json";
import {
  DEFAULT_LANGUAGE,
  FALLBACK_LANGUAGE,
} from "../constants/localisationConstants.js";

// createI18n creates i18n plugin
const i18n = createI18n({
  locale: DEFAULT_LANGUAGE, // set i18n locale
  fallbackLocale: FALLBACK_LANGUAGE, // fallback locale
  messages: { en: enMessages }, // set locale messages
});

export { i18n };

Visit docs to explore in detail all available config options for createI18n.

Now as our i18n plugin is created, by using app.use() function we can install it globally in the created Vue app and then it can be accessed as this.$i18n inside the components.

// main.js

import { createApp } from "vue";
import App from "./App.vue";
import { i18n } from "@/plugins/i18n";

// use function of createApp to register it globally.
createApp(App).use(i18n).mount("#app");

Create a utils folder in src and add translation.js and paste the following code into it.

Here are a few useful functions to automate localisation operations. please go through the attached comments to understand what each function does.

// translation.js
import {
  DEFAULT_LANGUAGE,
  SUPPORTED_LANGUAGES,
} from "../constants/localisationConstants";
import { i18n } from "../plugins/i18n";

// By default english locale messages are loaded.
const loadedLocales = ["en"];
const i18nGlobal = i18n.global;

//* set current locale in i18n
function setCurrentLocale(locale) {
  i18nGlobal.locale = locale;
}

//* Set i18n locale to default locale and loads its locale messages
export async function getDefaultLocaleAndLoadMessages() {
    await loadLocaleMessages(DEFAULT_LANGUAGE);
    setCurrentLocale(DEFAULT_LANGUAGE);
  }
}

//* updates locale in i18n
function setI18nLocaleInServices(locale) {
  setCurrentLocale(locale);
  return locale;
}

//* loads locale messages based on the locale
async function loadLocaleMessages(locale) {
  if (!isLocaleSupported(locale))
    return Promise.reject(new Error("Locale not supported"));
  if (!loadedLocales.includes(locale)) {
    const msgs = await loadLocaleFile(locale);
    i18nGlobal.setLocaleMessage(locale, msgs.default || msgs);
    loadedLocales.push(locale);
  }
}

//* handles the change in locale
export async function changeLocale(locale) {
  if (!isLocaleSupported(locale))
    return Promise.reject(new Error("Locale not supported"));
  if (i18nGlobal.locale === locale) return Promise.resolve(locale);

  if (!loadedLocales.includes(locale)) {
    //* lazy loading locale messages
    const msgs = await loadLocaleFile(locale);
    i18nGlobal.setLocaleMessage(locale, msgs.default || msgs);

    //* saving locale in loadedLocales
    loadedLocales.push(locale);
  }

  //* setting current locale on i18n
  return setI18nLocaleInServices(locale);
}

//* load the messages file based on locale
function loadLocaleFile(locale) {
  return import(`../locales/${locale}.json`);
}

//* check if locale is supported
function isLocaleSupported(locale) {
  return SUPPORTED_LANGUAGES.includes(locale);
}

To load the browser-preferred locale as the app reloads and to persist the user-preferred locale in local storage modify the translation.js as shown below

// add getBrowserPrefLocale functions to the above code
// get user locale from browser language preference
function getBrowserPrefLocale() {
  let locale =
    window.navigator.language ||
    window.navigator.userLanguage ||
    DEFAULT_LANGUAGE;

  return {
    locale: locale,
    localeNoISO: locale.split("-")[0],
  };
}

// Modify the getDefaultLocaleAndLoadMessages function
// get browser preferred locale and load locale messages
export async function getDefaultLocaleAndLoadMessages() {
  const userPreferredBrowserLocale = getBrowserPrefLocale();
 // get the locale saved in the local storage
  const localSavedLocale = localStorage.getItem("locale");

  if (userPreferredBrowserLocale === DEFAULT_LANGUAGE) {
    if (localSavedLocale === DEFAULT_LANGUAGE) return;
    localStorage.setItem("locale", DEFAULT_LANGUAGE);
    return;
  }

  let browserPrefLocale = "";
  if (isLocaleSupported(userPreferredBrowserLocale.locale)) {
    browserPrefLocale = userPreferredBrowserLocale.locale;
  } else if (isLocaleSupported(userPreferredBrowserLocale.localeNoISO)) {
    browserPrefLocale = userPreferredBrowserLocale.localeNoISO;
  } else {
    browserPrefLocale = DEFAULT_LANGUAGE;
  }

  //* retrieving any user saved local i.e local storage
  //* if yes lazy load local saved locale messages
  //* if no lazy load browser preferred language locale
  if (!localSavedLocale) {
    localStorage.setItem("locale", browserPrefLocale);
    await loadLocaleMessages(browserPrefLocale);
    setCurrentLocale(browserPrefLocale);
  } else {
    await loadLocaleMessages(localSavedLocale);
    setCurrentLocale(localSavedLocale);
  }
}

//* function to handle change in locale
export async function changeLocale(locale) {
 ...
 ...
  if (i18nGlobal.locale === locale) return Promise.resolve(locale);
 // persists the user preferred locale in local storage
  localStorage.setItem("locale", locale);
 ...
 ...
}

Adding basic UI components for the demo app and adding localisation to the content

Now as our localisation plugins, utils and locales are ready it's time to create some UI components to implement localisation and I have added very minimally styled UI to demonstrate localisation.

Creating a language switcher component

Key concepts

  • $i18n.locale is used to get the current locale.

  • changeLocale is the util function to change the current locale.

Create components folder in src and add the LanguageSwitcher component to toggle between the supported locales.

<template>
  <div>
    <button
      :style="
        $i18n.locale === 'en'
          ? 'background:green; border-right:none; padding:2px; font-size:18px; color:white; width:100px; height:45px;'
          : 'background:light-gray; border-right:none; padding:2px; font-size:18px; width:100px; height:45px;'
      "
      @click="handleChangeLocale('en')"
    >
      English
    </button>
    <button
      :style="
        $i18n.locale === 'es'
          ? 'background:green; border-left:none; padding:2px; font-size:18px; color:white; width:100px; height:45px;'
          : 'background:light-gray; border-left:none; padding:2px; font-size:18px; width:100px; height:45px;'
      "
      @click="handleChangeLocale('es')"
    >
      Espanol
    </button>
  </div>
</template>

<script>
import { changeLocale } from "../utils/Translation";
export default {
  name: "LocalisationDemo",
  methods: {
   // changing locale when language switcher is toggled
    handleChangeLocale(newLocale) {
      if (this.$i18n.locale !== newLocale) {
        changeLocale(newLocale);
      }
      return;
    },
  },
};
</script>

Modify the app.js file to implement text localisation

Key concepts

  • Replace your static text with $t("locale-key-for-static-text") to implement text localisation and to learn more about different concepts of localisation like formatting, DateTime localisation, number localisation etc refer to docs

  • Add getDefaultLocaleAndLoadMessages() inside the created hook to set the default or user-preferred locale as the current locale and load the messages accordingly on initial render and reload.

Modify the App.js content to the below code.

// App.js
<template>
  <div class="main_wrapper">
    <div class="heading_wrapper">
      <h1 style="color: green">{{ $t("heading") }}</h1>
      <LanguageSwitcher />
    </div>
    <div>
      <h2>{{ $t("subHeading") }}</h2>
      <h3 class="localised_text">{{ $t("demoText") }}</h3>
    </div>
  </div>
</template>

<script>
import LanguageSwitcher from "./components/LanguageSwitcher.vue";
import { getDefaultLocaleAndLoadMessages } from "./utils/Translation";

export default {
  name: "App",
  components: {
    LanguageSwitcher,
  },
  created() {
    getDefaultLocaleAndLoadMessages();
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 60px;
}
.main_wrapper {
  width: 800px;
  height: 600px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center;
  box-shadow: 0.3rem 0.3rem 0.6rem #a1a1a1, -0.2rem -0.2rem 0.5rem #e8e8e8;
}
.heading_wrapper {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.localised_text {
  line-height: 40px;
  max-width: 700px;
  color: #2c3e50;
}
</style>

Conclusion

Localization is In this article I tried to cover some basic concepts and best practices of localisation from scaffolding a Vue app to localising it and these util functions and folder structure provided are generic and can be used to localise your existing Vue projects.

You can access the complete code for the above demo from github. Feel free to contact me and drop your views and queries in the comments section below.

Hope you find this article useful. Thanks and happy learning!