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!Contents
create-nuxt-app
– to scaffold our Nuxt applicationnuxt-i18n
– to localize our application
create-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.
- Inside Vue Components
- External JSON or Js files.
Translations inside the Vue components
<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).
Date and time localization
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:
- Last login time format:
Sat, 20 Jun, 2020, 9:55 pm
- 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.
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
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:
- Updating the
lang
attribute in HTML tag hreflang
generation for anchor tags<a href="" hreflang="<selected-locale-iso>">
- Open graph locale tags such as
og:locale
andog: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 } ]
Comments