
Global business
Software localization
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
, 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.
create-nuxt-app
– to scaffold our Nuxt applicationnuxt-i18n
– to localize our applicationcreate-nuxt-app
utility. It is not mandatory to install this utility first, we could rather use npx
and use it directly.
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.
cd nuxt-intl npm run dev
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.
<i18n> { "en": { "hello": "hello!" }, "fr": { "hello": "Bonjour!" } } </i18n> <template> </template>
//en.json { "message": "Hello", "login": { "username": "User Name", "password": "Password" } }
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.
<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.
[ 'nuxt-i18n', { defaultLocale: 'fr', locales: [ { code: 'en', name: 'English' }, { code: 'fr', name: 'Français' } ], vueI18n: i18n } ]
<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
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.
{ "copyrightMessage": "Copyright: All Rights Reserved { name }" }
We can use it in our components as follows:
<p>{{ $t('copyrightMessage', { name: 'Phrase.com' }) }}</p>
{ "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.
{ "style": "Style | Styles" }
:
<p>{{ $tc('style', 1) }}</p> <p>{{ $tc('style', 2) }}</p>
// output Style Styles
{ "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
export const numberFormats = { en: { currency: { style: 'currency', currency: 'USD' } }, 'en-IN': { currency: { style: 'currency', currency: 'INR' } }, fr: { currency: { style: 'currency', currency: 'EUR' } } }
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 } }
<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).
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:
Sat, 20 Jun, 2020, 9:55 pm
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.
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.
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.
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:
en
, will not be prefixed, whereas other languages will, e.g. for French, the about page will have the URL:localhost:3000/fr/
.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
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.
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:
lang
attribute in HTML taghreflang
generation for anchor tags <a href="" hreflang="<selected-locale-iso>">
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-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 } ]
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.
Last updated on January 15, 2023.