Initializing vue-i18n is really straight forward, create a new instance, add locale messages, add fallback lang, provide it to your Vue instance and you are done.

import Vue from 'vue'
import VueI18n from 'vue-i18n'
import messages from '../lang'

export const i18n = new VueI18n({
  locale: 'en', // set locale
  fallbackLocale: 'en',
  messages // set locale messages

// eslint-disable-next-line no-unused-vars
const app = new Vue({
  el: '#app',

Time to go grab a coffee right?

Well not quite. You see, as fine as this is, in time your translation files will grow, you may support more languages and its not cool to force the user to download all those translations without them actually needing them.

This is a problem I had a few weeks ago while working at Hypefactors on and its new upcoming internationalization. I think we managed to solve it in a really slick way.

If you are using Webpack, and you should, there is a really easy way to load your language files asynchronously, via the not so new import() syntax.

Lets start by explaining our file structure.

Its best to have a folder called lang  inside the src  folder of our project, there we will keep all our translation files.


Now loading and setting the language is as easy as doing

import { i18n } from '@/global/i18n'
import axios from 'axios'

const loadedLanguages = ['en'] // our default language that is prelaoded 

export function loadLanguageAsync (lang) {
  if (i18n.locale !== lang) {
    if (!loadedLanguages.contains(lang)) {
      return import(/* webpackChunkName: "lang-[request]" */ `@/lang/${lang}`).then(msgs => {
        i18n.setLocaleMessage(lang, msgs.default)
        return setI18nLanguage(lang)
    return Promise.resolve(setI18nLanguage(lang))
  return Promise.resolve(lang)

function setI18nLanguage (lang) {
  i18n.locale = lang
  axios.defaults.headers.common['Accept-Language'] = lang
  document.querySelector('html').setAttribute('lang', lang)
  return lang

Woooow what happened here?

Ok so lets go from top to bottom.

We are importing our vue-i18n instance that we exported in the beginning, only difference is we extracted it into its own file, nothing special there.

Declare a loadedLanguages array keep track of our loaded languages.

Create a loadLanguageAsync function that we export so we can use it pretty much everywhere we want. vue-router is a good place, most precisely the beforeEach hook to load the language based on the current lang.

Inside our function we check if our locale is the same as the one we are trying to change, if not we proceed. We also check if our language is actually been loaded yet, if not we tell Webpack via its import function to load the language file dynamically based on the parameter lang we have passed to the loadLanguageAsync` function. The weird comment inside the function is what is called a Magic comment, to let Webpack set names to async chunks.

Because import is an async function that returns a promise, we have to handle it as such by using then.

After the file is loaded, we set the language on vue-i18n via the setLocaleMessage` method. Note the msgs.default, this is because we are doing a default export there, so this is required. (Vue actually does this under the hood for its SFC files).

We add the current language to the loadedLangauges array and we invoke the setI18nLanguage function which is self explanatory, sets the newly changed language in vue-i18n, axios and the html lang attribute.

If however, our language has been loaded already, we do a Promise.resolve and set the language inside it. Doing this is kind of needed as we will be using this whole function as an async one and we need it to always return a promise.

Another way of doing this is wrapping the whole function body in a Promise but I think this is a bit cleaner.

Ok so are we done?

Well not really, we haven’t used it yet 🙂

Lets pretend we have vue-router setup and ready and we want to load the language based on the current language.

// i18n-setup.js
import router from '@/routes'
router.beforeEach((to, from, next) => {
  const lang = to.params.lang
  loadLanguageAsync(lang).then(() => next())

Note the loadLanguageAsync(lang).then(() => next()), if you just do .then(next) you will get redirected to the home page every time because next takes a path parameter, and as we return the loaded language from loadLangaugeAsync things will not go the way we want.

And we are done. We are loading language files async and vue-router will wait until the file is ready to continue routing. The default language should always be preloaded.