WebDevChallenges Logo
WebDevChallenges

Nuxt.js Internationalization without route param

Updated June 5, 21
How to use i18n internationalization in Nuxt.js without storing the prefered language as route parameter

Nuxt.js Internationalization without route param

The code is available in a Github repository

image

There is an example in the official Nuxt.js documentation on how to implement i18n (Internationalization) in Nuxt.js.

This example shows you how to setup i18n in Nuxt.js so that you can add a parameter to your routes which determines the language of the page (e.g. /en/about for english and /fr/about for french).

However the disadvantage of putting the language in the route is that if for e.g. a french user shares the page to an english user, the english user will see the page in french and then has to switch the language.

The advantage of this approach is tho, that the site can be generated statically with Nuxt.js.

An alternative

If you do not need to generate your site statically, there is an alternative approach, which I will guide you through in this post:

  • Omit putting the language in the route
  • Use a cookie to let the backend know your prefered language
  • Use the accept-language header as fallback if no prefered language is set in the cookies
  • If the prefered language in the cookie or the accept-language header is not available, fallback to a specified language

Here is how

Initialize a new Project

Create a new nuxt project and install the dependencies

vue init nuxt-community/starter-template <project-name>
cd <project-name>
npm install

Install i18n

Install the vue-i18n npm package

npm install vue-i18n --save

Create an i18n plugin

Create the i18n plugin in plugins/i18n.js with the following content

import Vue from 'vue';
import VueI18n from 'vue-i18n';

Vue.use(VueI18n);

export default ({ app, store }) => {
  // Set i18n instance on app
  // This way we can use it in middleware and pages asyncData/fetch
  app.i18n = new VueI18n({
    locale: store.state.locale,
    fallbackLocale: 'en',
    messages: {
      'de': require('~/locales/de.json'),
      'en': require('~/locales/en.json'),
      'ru': require('~/locales/ru.json')
    }
  });

  app.i18n.path = (link) => {
    if (app.i18n.locale === app.i18n.fallbackLocale) {
      return `/${link}`;
    }

    return `/${app.i18n.locale}/${link}`;
  }
}

Create the store

Create the vuex store in store/index.js with the following content

export const state = () => ({
  locales: [
    {
      code: 'de',
      name: 'DE'
    },
    {
      code: 'en',
      name: 'EN'
    },
    {
      code: 'ru',
      name: 'RU'
    }
  ],
  locale: 'en'
});

export const mutations = {
  SET_LANG(state, locale) {
    if (state.locales.find(el => el.code === locale)) {
      state.locale = locale
    }
  }
};
  • The locales array holds our available locales as javascript objects which both have a code and a name property
    • The code will be sent to the backend eventually to determine the language
    • The name will be used in the frontend in our Language Switcher
  • The locale property holds the currently active language
  • The SET_LANG mutation can be used to set the currently active language

Create the middleware

Create the middleware in middleware/i18n.js with the following content

export default function ({ isHMR, app, store, route, params, req, error, redirect }) {
  if (isHMR) { // ignore if called from hot module replacement
    return;
  }

  if (req) {
    if (route.name) {
      let locale = null;

      // check if the locale cookie is set
      if (req.headers.cookie) {
        const cookies = req.headers.cookie.split('; ').map(stringCookie => stringCookie.split('='));
        const cookie = cookies.find(cookie => cookie[0] === 'locale');

        if (cookie) {
          locale = cookie[1];
        }
      }

      // if the locale cookie is not set, fallback to accept-language header
      if (!locale) {
        locale = req.headers['accept-language'].split(',')[0].toLocaleLowerCase().substring(0, 2);
      }

      store.commit('SET_LANG', locale);
      app.i18n.locale = store.state.locale;
    }
  }
};
  • This middleware
    • Checks if the locale cookie is set
    • If no locale cookie is set, use the accept-language header
    • Uses the SET_LANG mutation to set the currently active language depending on the previous checks

Adjust the nuxt.config.js

Add the following to your nuxt.config.js to implement the installed i18n library, the created middleware and the plugin

build: {
  vendor: ['vue-i18n'],
},
router: {
  middleware: 'i18n'
},
plugins: ['~/plugins/i18n.js'],

Create the locale files

  • Create the file locales/de.json with the following content

{
  "test": "german"
}
  • Create the file locales/en.json with the following content

{
  "test": "english"
}
  • Create the file locales/ru.json with the following content

{
  "test": "russian"
}

Display strings and create language switcher

Now we want the pages/index.vue file to do the following

  • Display the test language key defined in the locales files
  • Iterate through the locales array from the store
    • Display the name attribute of the locale object
    • Attaches a click listener to each locale which will execute the switchLanguage function
    • Adds the active class if the locale is the currently active one
  • The switchLanguage function will
    • Set the locale cookie to the selected locale’s code value
    • Reload the page

Add the following to your pages/index.vue file

<template>
  <div class="language-switcher">
    <div class="languages-label">{{ $t('test') }}: </div>
    <div class="languages">
      <div
        class="language"
        v-for="el in locales"
        :key="el.code"
        :class="{ active: (el.code === locale) }"
        @click="switchLanguage(el.code)"
      >
        <span>{{ el.name }}</span>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    computed: {
      locales() { return this.$store.state.locales },
      locale() { return this.$store.state.locale }
    },
    methods: {
      switchLanguage (localeCode) {
        document.cookie = `locale=${localeCode}`;
        location.reload();
      }
    }
  }
</script>

<style>
  .language-switcher {
    display: flex;
    padding: 1rem;
  }
  .languages {
    display: flex;
    justify-content: flex-end;
  }

  .language {
    padding-left: .25rem;
    cursor: pointer;
  }
  .language.active {
    text-decoration: underline;
  }

  .language:hover span {
    text-decoration: underline;
  }

  .language:not(:last-child):after {
    content: '|';
    padding-left: .25rem;
  }
</style>

Test it

Run npm run dev to try it out.

On the first visit, you should see the value of the test key of your language (because of the fallback to use accept-language header).

If your language however is not available, it will fallback to english.

You can switch the language by clicking on one of the displayed locale names. This will set the locale cookie and will therefore be persisted.