Software localization

A Guide to Localizing with Nuxt.js

Nuxt is a popular JavaScript framework that can be of great help in improving Google rankings. Learn how to use it in your applications in the most effective way possible.
Software localization blog category featured image | Phrase

Search engine optimization (SEO) is a core element of a strong global growth strategy. To be able to run your application on a global scale, you need to tear down this barrier right from the start.

Nuxt is a popular JavaScript framework that can significantly help your application with SEO to get better rankings on Google. Based on Vue.js, it supports both the Single Page Application (SPA) and Server Side Rendering (SSR) format.

Since it supports pre-rendering and Vue-meta out of the box, it has been a go-to framework for all the applications that need better SEO but still use the quite awesome Vue.js syntax and libraries.

In this end-to-end Nuxt.js tutorial, we'll show you how to internationalize your Nuxt.js app and make it fit for the global marketplace. Off we go!

🔗 Resource » Check out our Ultimate Guide to JavaScript Localization for a detailed insight into making JS applications ready for international users.

Getting started with our Nuxt.js tutorial

For this tutorial, we'll need the following libraries:

  1. create-nuxt-app – to scaffold our Nuxt application
  2. nuxt-i18n – to localize our application
Nuxt provides an easy way to scaffold the basic structure with various options by the create-nuxt-app utility. It is not mandatory to install this utility first, we could rather use npx and use it directly.

Let’s create a Nuxt.js app using the following command:

npx create-nuxt-app nuxt-intl

You can add any entries of your choice for Project name, Project description, and Author name. We can choose between npm and yarn. Go simply with the one you feel most comfortable with. It is a good practice to always use ESLint to detect syntax errors. I prefer using Prettier as it makes my code look properly formatted, and when multiple developers work on the same code, it forces them to use a particular format so that the code remains uniform.

My setup is as follows:The Only Nuxt.js Tutorial on I18n You'll Ever Need (Setup) | Phrase

After the installation is complete, navigate to the project directory and run the application:

cd nuxt-intl 

npm run dev

Open http://localhost:3000/ in your browser and you’ll see the default application page.

Localization basics

In this tutorial, we'll use a library called nuxt-i18n, based on vue-i18n, with some Nuxt-specific localization options. Before installing the nuxt-i18n package, let us go through the basics of localization and have a look at the variety of options available in this library.

We define key-value pairs for each language, where the key remains the same for all languages and the value is the translation in that language.

// English

{

  "hello": "Hello"

}

// French

{

  "hello": "Bonjour"

}

We have two translation options.

  1. Inside Vue Components
  2. External JSON or Js files.

Translations inside the Vue components

You also have an option to add translations directly from within the .vue files. These translations are mostly used for component-specific locales, which is totally fine for smaller applications. However, as your app grows over time, it will become quite difficult to maintain. The component size growth will lead to duplicate translations all over the place. Equally important, as you would most probably let translators take care of string translation – who most likely have no knowledge of Vue components – it is not ideal to have translation inside Vue components.

<i18n> 

{   

  "en": {     

    "hello": "hello!"   

  },   

  "fr": {     

    "hello": "Bonjour!"   

   } 

} 

</i18n> 

<template>

</template>

External JSON or JS files

We can have separate files for each language. It is more like a key-value pair file. Each language will have the same key corresponding to a particular translation. This is the most commonly used method to recommend and easy to maintain. This will not only prevent duplications as we can reference previous translations but also make proper JSON grouping based on features, etc.

//en.json 

{   

  "message": "Hello", 

  "login": {

    "username": "User Name",

    "password": "Password"

  }

}

Installing packages

Let’s install nuxt-i18n and configure it.

npm i nuxt-i18n

Since Nuxt does not have a CLI to automatically generate a configuration like vue-cli for vue-i18n, we have to add the following configuration to set up the plugin.

Let us create a config folder and our configuration in the i18n.js file.

import en from '../locales/en.json'

import fr from '../locales/fr.json'

export default {

  locale: 'en',

  fallbackLocale: 'en',

  messages: { en, fr }

}

In this configuration, we are using the external files to store our translation texts. For example, let us use English and French. We can all pass a default and fallback locale to the plugin. In the next step, we create these two external files to store our translations.

Create a locales directory in the root folder and two files, en.json and fr.json, which will contain all our translations.

{

  "message": "Hello!"

}
{

  "message": "Bonjour"

}

Finally, let's add this library to our nuxt.config.js file so that Nuxt is aware of it. Now we can make use of it in our application.

import i18n from './config/i18n'

buildModules: [

 /* other modules */

 [

  'nuxt-i18n',

  {

    vueI18nLoader: true,

    defaultLocale: 'fr',

     locales: [

      {

         code: 'en',

         name: 'English'

      },

      {

         code: 'fr',

         name: 'Français'

      }

    ],

    vueI18n: i18n

  }

 ]

]

vueI18nLoader is optional. This will enable us to write translations in our Vue components. Let us check if the packages are functional: open pages/index.vue, and update the title as shown below:

<template>

  <div class="container">

      <h1 class="title">{{ $t('message') }}</h1>

      <!-- some code -->

  </div>

</template>

Here, $t() tells our Nuxt app to use the translation version of the key based on the selected language. Run the app again, and you can see our English message on the screen!

The Only Nuxt.js Tutorial on I18n You'll Ever Need (English screen message) | PhraseLet’s manually update the defaultLocale in our nuxt.config.js file to fr:
[

  'nuxt-i18n',

  {

    defaultLocale: 'fr',

     locales: [

      {

         code: 'en',

         name: 'English'

      },

      {

         code: 'fr',

         name: 'Français'

      }

    ],

    vueI18n: i18n

  }

]

We can now see our French message displayed.

The Only Nuxt.js Tutorial on I18n You'll Ever Need (French screen message) | PhraseOur setup is almost done. Since we are manually updating the code, it becomes tedious to change the locale every time. Let us create a dropdown to choose the language.

Create a components/LanguageInput.vue component with the following code:

<template>

  <div class="lang-dropdown">

    <select v-model="$i18n.locale">

      <option

        v-for="lang in $i18n.locales"

        :key="lang.code"

        :value="lang.code"

        >{{ lang.name }}</option

      >

    </select>

  </div>

</template>

<script>

export default {}

</script>

Let’s add this component to our pages/Index.html  file. Now we can easily switch between languages. Great! Let us deep dive into different ways of localization techniques the library offers and see an example for each one of them.

Named formatting

There are instances where not every word would need to be translated, such as proper nouns (first or family names, places), URLs, etc. To avoid duplicating them all in every locale file, we can apply the name-formatting technique.

{

  "copyrightMessage": "Copyright: All Rights Reserved { name }"

}

We can use it in our components as follows:

<p>{{ $t('copyrightMessage', { name: 'Phrase.com' }) }}</p>

If we have multiple text pieces that do not need translation, we can also use the list method.

{

  "welcomeMessage": "Welcome! {0}, Your Email: {1} has been verified successfully"

}
<p>{{ $t('copyrightMessage', ['Preetish HS', 'contact@preetish.in']) }}</p>

As each language has its own specific structure, this technique gives the translator the flexibility to place any value anywhere in a sentence.

Pluralization

It is oftentimes difficult to predict quantity without actual data. For example, you would see text pieces, such as style(s) or category(s), used when you cannot predict the number of items. This would work more or less, but we can take advantage of the library's features and apply proper pluralization. To achieve this, we can pass the quantity value, and the package will send the appropriate translation.

{

  "style": "Style | Styles"

}

We need to use the $tc() (Translation Count) instead of plain $t():

<p>{{ $tc('style', 1) }}</p>

<p>{{ $tc('style', 2) }}</p>
// output 

Style

Styles

Our countable translations look much better now!

If you need to perform even more complex counting, the library can help you with it too (see it in action in the example below). We can also have custom pluralization rules for languages that have different pluralization rules.

{

  "apple": "no apples | one apple | {count} apples",

  "banana": "no bananas | {n} banana | {n} bananas"

}
<p>{{ $tc('apple', 10, { count: 10 }) }}</p>

<p>{{ $tc('apple', 10) }}</p>

<p>{{ $tc('apple') }}</p>

<p>{{ $tc('apple', 0) }}</p>

 

<p>{{ $tc('banana') }}</p>

<p>{{ $tc('banana', 0) }}</p>

<p>{{ $tc('banana', 1, { n: 1 }) }}</p>

<p>{{ $tc('banana', 1) }}</p>

<p>{{ $tc('banana', 100, { n: 'too many' }) }}</p>

//output

10 apples

10 apples

one apple

no apples

 

1 banana

no bananas

1 banana

1 banana

too many bananas

Number localization

Different languages and regions represent numbers (currency amounts, date, time, etc.) in different ways. Some use comma-separated numbers, some space-separated numbers. Currency symbols can also be prefixed or suffixed. Because of all this, displaying numbers properly is essential if you want to have an impeccable user experience, acquire, and retain users.

To implement number formatting, let us create a new directory called formats and add a file numberFormats.js. Here we can define all our number formats.

export const numberFormats = {

  en: {

    currency: {

      style: 'currency',

      currency: 'USD'

    }

  },

  'en-IN': {

    currency: {

      style: 'currency',

      currency: 'INR'

    }

  },

  fr: {

    currency: {

      style: 'currency',

      currency: 'EUR'

    }

  }

}

Here, based on Intl.NumberFormat, the library will automatically fetch the currency format and symbol for us. Now we need to let our library know that we need to use these formats. Let us add this file to our setup method.

import { numberFormats } from '../formats/numberFormats'

import en from '../locales/en.json'

import fr from '../locales/fr.json'

export default {

  defaultLocale: 'en',

  fallbackLocale: 'en',

  numberFormats,

  messages: { en, fr }

}

We can now use these in our components. Here we use the $n() format which will let the library know that we are trying to access the number formats. We can explicitly pass the locale  to the method as shown below. If we don't pass, it will take the currently selected language. The number format for that language has to be defined in the numberFormats.js file so that it can work.

<p>{{ $n(1000000, 'currency') }}</p> 

<p>{{ $n(7000000, 'currency', 'en-IN') }}</p> 

<p>{{ $n(7000000, 'currency', 'fr') }}</p>
//output

$1,000,000.00

₹70,00,000.00

7 000 000,00 €

Currency looks much better with symbol and proper formatting, International number system for Dollars (USD) and Indian number system for Rupees (INR).

Date and time localization

Similar to number localization, date and time formats vary based on language and region. We follow the same procedure and create another file called dateTimeFormats.js to define our date and time formats for different languages.

Multiple date formats are used in different parts of the application. We can give specific names for these use cases, such as short, long, reportDateFormat, etc. For each of these, we can define our own formats.

For example:

  1. Last login time format: Sat, 20 Jun, 2020, 9:55 pm
  2. Transaction date format: Jun 27, 2020

The table below shows the properties we can have in a date-time input. The corresponding values show how that property will be displayed.

The Only Nuxt.js Tutorial on I18n You'll Ever Need (date time input properties) | Phrase

Reference: ECMA-International

export const dateTimeFormats = {

  en: {

    short1: {

      year: 'numeric',

      month: 'short',

      day: 'numeric'

    },

    long: {

      year: 'numeric',

      month: 'short',

      day: 'numeric',

      weekday: 'short',

      hour: 'numeric',

      minute: 'numeric'

    }

  },

  'en-IN': {

    short1: {

      year: 'numeric',

      month: 'short',

      day: 'numeric'

    },

    long: {

      year: 'numeric',

      month: 'short',

      day: 'numeric',

      weekday: 'short',

      hour: 'numeric',

      minute: 'numeric',

      hour12: true

    }

  }

}

To use these formats in our application, we use the $d() notation. Just like the number formats, if we don't explicitly pass the locale, the currently selected locale will be used. If the latter doesn't have a format defined, then the fallback language format will be used. So make sure to have formats defined for supported locales.

<p>{{ $d(new Date(), 'short') }}</p>

<p>{{ $d(new Date(), 'long', 'en-IN') }}</p>
//output

Jun 12, 2020

Mon, 12 Jun, 2020, 11:45 am

Just like our number formats, the library will automatically format the date and time based on International standards.

Nuxt-specific localization

The advantage of using nuxt-i18n over the regular vue-i18n are its additional options to localize routes, as well as a number of SEO-related benefits.

Localizing routes

Just by seeing our URL, we won't come to know in which language the application is currently used. We are dependent on local storage or cookies to save our locale preferences.

To understand this better, we need to create two more pages, About.vue and Contact.vue, and add the links to those pages to our index.vue page.

<template>

  <div>

    <nuxt-link to="/about">{{ $t('about') }}</nuxt-link>

    <nuxt-link to="/contact">{{ $t('contact') }}</nuxt-link>

  </div>

</template>

Now, if we navigate to the about page, irrespective of the language choice, it opens localhost:3000/about. It would be much better to have a locale prefixed on our URL (localhost:3000/fr/about). To achieve this, there are two strategies:

  • Prefix without default: Our default language,en, will not be prefixed, whereas other languages will, e.g. for French, the about page will have the URL:localhost:3000/fr/.
  • Prefix: With this strategy, all the languages including our default language will be prefixed.
  • Prefix and default: This strategy combines the previous two strategies. i.e. the default language will have both a prefixed and non-prefixed version.

By default, nuxt-i18n would already create prefixed routes for us. Without any additional code, it would apply the third strategy.

//routes generated

[

 {

    path: "/",

    component: _3237362a,

    name: "index___en"

  },

  {

    path: "/fr/",

    component: _3237362a,

    name: "index___fr"

  },

  {

    path: "/about",

    component: _71a6ebb4,

    name: "about___en"

  },

  {

    path: "/fr/about",

    component: _71a6ebb4,

    name: "about___fr"

  },

 {

    path: "/contact",

    component: _71a6ebb4,

    name: "about___en"

  },

  {

    path: "/fr/contact",

    component: _71a6ebb4,

    name: "about___fr"

  }

]

If we navigate to localhost:3000/fr/about, it would automatically load French translations on our about page. However, this will not happen if we navigate through <nuxt-links>. To make this work, we pass a localePath() function with the relevant page name. localePath() is provided by the nuxt-i18n library, which returns the localized path of the passed in route.

<nuxt-link :to="localePath('about')">{{ $t('about') }}</nuxt-link>

<nuxt-link :to="localePath('contact')">{{ $t('contact') }}</nuxt-link>

Default translations will work as it as, when you change the translation to French and then click on about it will now open localhost:3000/fr/about. the about page

If the default language is selected, the route path will remain unchanged. Clicking on about will open to localhost:3000/about. If you now change the translation to French and then click on about, it will open to localhost:3000/fr/about.

To have the prefix "lang" for our default language, we need to pass a strategy attribute to our Nuxt configuration.

// nuxt.config.js

['nuxt-i18n', {

  strategy: 'prefix'

}]

Now load localhost:3000, and it would automatically redirect to localhost:3000/en. If you want to remove the prefixed URLs for the default language, set the strategy to prefix_except_default. This will make the URLs, such as localhost:3000/en, non-functional.

Search engine optimization (SEO)

If the seo attribute is set to true during initialization, the library performs various SEO improvements. We also need to pass ISO for each locale, which the library uses to perform these optimizations:

  1. Updating the lang attribute in HTML tag
  2. hreflang generation for anchor tags <a href="" hreflang="<selected-locale-iso>">
  3. Open graph locale tags such as og:locale and og:locale:alternate

Let us update our plugin initialization to get these optimizations automatically made for us. This will wrap up our Nuxt.js tutorial.

[

   'nuxt-i18n',

   {

     strategy: 'prefix',

     defaultLocale: 'en',

     seo: true,

     locales: [

       {

         code: 'en',

         name: 'English',

         iso: 'en-US'

       },

       {

         code: 'fr',

         name: 'Français',

         iso: 'fr-FR'

       }

     ],

     vueI18n: i18n

   }

 ]

Wrapping up out Nuxt.js tutorial on localization

Internationalization and localization with nuxt-i18n will make your Nuxt app accessible for users in any corner of the world. We hope this step-by-step tutorial could help you kick it off right. To learn more techniques for localizing Vue/Nuxt apps, visit the official vue-i18n docs and nuxt-i18n docs.

If you feel ready and want to let your team localize at scale, check out Phrase, the most comprehensive localization solution on the market. The Phrase Localization Suite features a flexible API and CLI, and a beautiful web platform for your translators. With GitHub, GitLab, and Bitbucket sync, Phrase does the heavy lifting in your localization pipeline, so you can stay focused on the code you love.

Check out all Phrase features for developers and see for yourself how it can help you take your apps global.