Curious to see how it works?
Try Lokalise for Free 14-day trial. No credit card required.Hello and welcome to this Nuxt i18n guide that will cover translating your Nuxt.js app into multiple languages. We’ll cover everything you need to know to make your Nuxt.js app multilingual. We’ll start by setting up the i18n package and managing translation files. You’ll learn how to switch languages, localize routes, and save language preferences.
We’ll also dive into supporting right-to-left languages, handling pluralization, and localizing dates, times, and currencies. Let’s get started on making your app truly global!
The source code for this article can be found on GitHub .
min-height: calc(100vh - 40px); text-align: center; // padding-top: 20px; /* Adjusted to add spacing from the top */ font-size: 3em; margin-bottom: 0.5em; font-size: 1.5em; html[dir="rtl"] { .container { direction: rtl; .language-switcher, .global-menu { margin-bottom: 20px; margin: 0 10px; padding: 5px 10px; text-decoration: none; color: #000; &.router-link-active { font-weight: 800; button { margin-top: 20px; padding: 10px 20px; font-size: 1em; cursor: pointer; locales: [ { code: 'en', iso: 'en-US', name: 'English', file: 'en.json' }, { code: 'fr', iso: 'fr-FR', name: 'Français', file: 'fr.ts' }, { code: 'ar', iso: 'ar-AR', name: 'العربية', file: 'ar.json', dir: 'rtl' } defaultLocale: "en", strategy: "prefix_except_default", langDir: 'locales/', lazy: true, vueI18n: './i18n/i18n.config.ts'Let’s cover main settings:
export default defineI18nLocale(async locale => { // Simulate fetching translations from an API return { "welcome": "Bienvenue!", "message": "Ce tutoriel vous est proposé par {name}",Also it’s even possible to perform fetch requests to return translations from the API.
Start your development server if it’s not already running, open your browser and navigate to
http://localhost:3000
. You should see the “Welcome!” message and the paragraph translated based on the default locale (English).Adding language switcher
Currently we don’t provide an option to set the locale, so let’s take care of it now.
Simple switcher
Create a
components
folder in the project root and add aLanguageSwitcher.vue
inside:<template> <div class="language-switcher"> <NuxtLink v-for="locale in availableLocales" :key="locale.code" :to="switchLocalePath(locale.code)" :class="{ active: currentLocale === locale.code }" {{ locale.name }} </NuxtLink> </template> <script setup> import { useI18n } from 'vue-i18n' import { computed } from 'vue' import { useSwitchLocalePath } from '#imports' const { locale, locales } = useI18n() const switchLocalePath = useSwitchLocalePath() const availableLocales = computed(() => { return locales.value.filter(i => i.code !== locale.value) const currentLocale = computed(() => locale.value) </script>Then use this new component inside
app.vue
file:<template> <LanguageSwitcher /> <NuxtPage /> </template> <script setup> import LanguageSwitcher from '~/components/LanguageSwitcher.vue' </script>Nice!
Saving language preferences and supporting RTL languages
As mentioned, i18n plugin can detect the preferred locale and store it in cookies. However, you can also code this functionality manually. Plus, we will need to properly handle right-to-left languages.
Therefore, let’s adjust our language switcher in the following way:
<template> <div class="language-switcher"> <NuxtLink v-for="locale in availableLocales" :key="locale.code" :to="switchLocalePath(locale.code)" @click="setLanguagePreference(locale.code)" :class="{ active: currentLocale === locale.code }" {{ locale.name }} </NuxtLink> </template> <script setup> import { useI18n } from 'vue-i18n' import { computed, watch, onMounted } from 'vue' import { useSwitchLocalePath } from '#imports' const { locale, locales } = useI18n() const switchLocalePath = useSwitchLocalePath() const availableLocales = computed(() => { return locales.value.filter(i => i.code !== locale.value) const setLanguagePreference = (code) => { localStorage.setItem('preferredLanguage', code) const currentLocale = computed(() => locale.value) const updateDirAttribute = (newLocale) => { const currentLocale = locales.value.find(l => l.code === newLocale) document.documentElement.setAttribute('dir', currentLocale?.dir || 'ltr') watch(locale, (newLocale) => { updateDirAttribute(newLocale) onMounted(() => { const savedLanguage = localStorage.getItem('preferredLanguage') if (savedLanguage && locales.value.some(locale => locale.code === savedLanguage)) { locale.value = savedLanguage updateDirAttribute(locale.value) </script>Key points here:
dir
attribute: Whenever the locale changes, we update the dir
(direction) attribute accordingly. For example, when the Arabic version is requested, the HTML tag will have the dir
attribute set to rtl
. As long as our styles specify direction: rtl
for this case, the text should be displayed properly.onMounted
hook: We use the onMounted
hook to set the preferred locale if it’s found in local storage.
The
localePath
will properly handle localized routes for you.
Now you can use this component in your pages, for example, in
about.vue
:
<template> <div class="container"> <GlobalMenu /> <h1>{{ $t('aboutTitle') }}</h1> <p>{{ $t('aboutDescription') }}</p> </template> <script setup> import GlobalMenu from '~/components/GlobalMenu.vue' </script>
Do the same for the
index.vue
file.
Finally, provide translations. Here are the
en.json
translations:
fr.json
:
ar.json
:
Great job! You’ve now set up localized routes and translations for your Nuxt.js app.
Next, I’ll show you how to interpolate custom values in your translations.
Let’s adjust the welcoming message in
index.vue
like this:
<p>{{ $t('message', { name: 'Lokalise' }) }}</p>
The
{ name: 'Lokalise' }
is interpolation: we basically say that the
name
variable should contain the
Lokalise
string.
To use this variable in your translation, simply provide its name in the curly brackets:
"message": "This tutorial is brought to you by {name}"You can do the same for all other languages.
Now let’s see how to work with pluralization. First, let’s add a button on the main page that shows how many times it has been pressed:
<template> <div class="container"> <GlobalMenu /> <h1>{{ $t('welcome') }}</h1> <p>{{ $t('message', { name: 'Lokalise' }) }}</p> <button @click="incrementCount">{{ $t('buttonPressed', { count: count }) }}</button> </template>
You can see that here we use interpolation once again. Depending on the
count
we will display one of the messages.
Now update the script for the main page:
import { ref } from 'vue' import GlobalMenu from '~/components/GlobalMenu.vue' const count = ref(0) const incrementCount = () => { count.value++Next let’s provide English translation:
"buttonPressed": "You've pressed the button {count} time | You've pressed the button {count} times"The pipe
|
character serves as a separator and provides two plural forms: when the button has been pressed once and when it has been pressed multiple times or zero times.Now French translations:
"buttonPressed": "Vous avez appuyé sur le bouton {count} fois | Vous avez appuyé sur le bouton {count} fois"With Arabic translations, things are somewhat more complex because it has more than two plural forms. To overcome this problem, let’s write a custom pluralization rule. Create a new
i18n/plurals.ts
file:export const arabicPlurals = (choice: number): number => { if (choice === 0) { return 0 // Zero times if (choice === 1) { return 1 // One Time if (choice === 2) { return 2 // Two Times if (choice >= 3 && choice <= 10) { return 3 // Many Times (3-10) return 4 // 11 and aboveAlternatively you can use intl plural rules.
Now import these rules and use it inside the
i18n/i18n.config.ts
:import { arabicPlurals } from "./plurals" export default defineI18nConfig(() => ({ legacy: false, locale: 'en', fallbackLocale: 'en', pluralRules: { "ar": arabicPlurals,Finally, provide translations inside
"buttonPressed": "لم يتم الضغط على الزر | مرة واحدة | مرتين | {count} مرات | {count} مرة"ar.json
:With these steps, you’ve learned how to use placeholders for dynamic values and handle pluralization in multiple languages, including languages with complex pluralization rules like Arabic.
Localizing date and time
Datetime localization in Nuxt can be performed easily. First, let’s add two new paragraphs to
index.vue
:<template> <div class="container"> <GlobalMenu /> <h1>{{ $t('welcome') }}</h1> <p>{{ $t('message', { name: 'Lokalise' }) }}</p> <button @click="incrementCount">{{ $t('buttonPressed', { count: count }) }}</button> <p>{{ $t('currentDate', { date: $d(currentDate, 'short') }) }}</p> <p>{{ $t('deadline', { dateTime: $d(deadline, 'long') }) }}</p> </template>We will display the current date and a random deadline in two different formats: long and short. Note that we use the
$d
function to localize dates.Now update the script in the same file:
import { ref } from 'vue' import GlobalMenu from '~/components/GlobalMenu.vue' const count = ref(0) const currentDate = new Date() const deadline = new Date(currentDate) deadline.setDate(currentDate.getDate() + 7) const incrementCount = () => { count.value++We will need to create custom datetime formats, so add an
i18n/datetime.ts
file:export const datetimeFormats = { en: { short: { year: 'numeric', month: 'short', day: 'numeric' long: { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' fr: { short: { year: 'numeric', month: 'short', day: 'numeric' long: { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' ar: { short: { year: 'numeric', month: 'long', day: 'numeric', long: { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', } as const;Feel free to further adjust these per-locale rules as needed.
Use the rules in the
i18n.config.ts
file:import { arabicPlurals } from "./plurals" import { datetimeFormats } from "./datetime" export default defineI18nConfig(() => ({ legacy: false, locale: 'en', fallbackLocale: 'en', pluralRules: { "ar": arabicPlurals, datetimeFormats,Now add English translations:
"currentDate": "Current date: {date}", "deadline": "Deadline: {dateTime}""currentDate": "Date actuelle: {date}", "deadline": "Date limite: {dateTime}"
fr.json
:And finally
"currentDate": "التاريخ الحالي: {date}", "deadline": "الموعد النهائي: {dateTime}"ar.json
:That’s it!
Localizing numbers and currencies
Let’s also demonstrate how to localize numbers in Nuxt.js. Add a new paragraph to the main page:
Add a new paragraph to the main page:
<template> <div class="container"> <GlobalMenu /> <h1>{{ $t('welcome') }}</h1> <p>{{ $t('message', { name: 'Lokalise' }) }}</p> <button @click="incrementCount">{{ $t('buttonPressed', { count: count }) }}</button> <p>{{ $t('currentDate', { date: $d(currentDate, 'short') }) }}</p> <p>{{ $t('deadline', { dateTime: $d(deadline, 'long') }) }}</p> <p>{{ $t('unitsPrice', { units: $n(2.56, 'decimal'), price: $n(1245, 'currency') }) }}</p> </template>We will display a text saying that this number of some random units costs this much. Make sure to use the
$n
function for numbers localization.Now let’s create
i18n/numbers.ts
file with custom number formats:export const numberFormats = { en: { currency: { style: 'currency', currency: 'USD' decimal: { style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2 fr: { currency: { style: 'currency', currency: 'EUR' decimal: { style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2 ar: { currency: { style: 'currency', currency: 'AED' decimal: { style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2 } as const;As you can see, we can provide formats for regular numbers and for currencies (you can also provide separate rules for percentages and other cases).
Now use these formats inside
i18n.config.ts
:import { arabicPlurals } from "./plurals" import { datetimeFormats } from "./datetime" import { numberFormats } from "./numbers" export default defineI18nConfig(() => ({ legacy: false, locale: 'en', fallbackLocale: 'en', pluralRules: { "ar": arabicPlurals, datetimeFormats, numberFormats,Provide English translations:
"unitsPrice": "{units} units cost {price}""unitsPrice": "{units} unités coûtent {price}"
fr.json
:And finally
"unitsPrice": "{units} وحدات تكلف {price}"ar.json
:SEO and meta tags localization
Another important step is to properly localize the contents in the head tag, specifically your meta tags. Let’s see how to achieve that.
First, let’s adjust the
script
in theindex.vue
file:import { ref, computed } from 'vue' import { useI18n } from 'vue-i18n' import GlobalMenu from '~/components/GlobalMenu.vue' const { t, locale } = useI18n() const count = ref(0) const currentDate = new Date() const deadline = new Date(currentDate) deadline.setDate(currentDate.getDate() + 7) const incrementCount = () => { count.value++ // Setting up localized meta tags const metaTitle = computed(() => t('meta.title')) const metaDescription = computed(() => t('meta.description')) useHead({ title: metaTitle.value, meta: [ name: 'description', content: metaDescription.value property: 'og:title', content: metaTitle.value property: 'og:description', content: metaDescription.value property: 'og:locale', content: locale.valueWe utilize
useHead()
function to translate title, description and ogmeta
tags.Now let’s provide English translations:
"meta": { "title": "Welcome to Our Site", "description": "This is the best site ever made for tutorials.""meta": { "title": "Bienvenue sur notre site", "description": "C'est le meilleur site jamais créé pour les tutoriels."
fr.json
:And
"meta": { "title": "مرحبًا بكم في موقعنا", "description": "هذا هو أفضل موقع تم إنشاؤه على الإطلاق للحصول على الدروس."ar.json
:You can use similar approach on other pages of your site. Great!
Use Lokalise for Nuxt i18n
As your app grows, managing your translations becomes increasingly complex. If you need support for more locales, hiring a professional or using AI for translation becomes essential. That’s why we’ve created the Lokalise translation management system. It does all the heavy lifting for you and provides many features, including collaborative access, the ability to hire translators, use AI, and integrate with third-party tools and services like GitHub, Figma, Asana, and many others.
To get started, grab your free trial. Follow the wizard’s instructions to create your first project. Then proceed to the Upload page and upload your translation files. Once done, you can modify your texts, add more languages, utilize AI to translate more data, and so on. When you’re ready, simply proceed to the Download page and export your texts back to the project.
That’s it! To learn more about the platform you can refer to our docs or watch the free onboarding course covering all platform features.
Conclusion
That’s it for today! We have seen how to perform Nuxt i18n easily with the help of the i18n module. We’ve covered all the main features and approaches, and hopefully by now you’re ready to put your knowledge into practice.
If you want more, feel free to browse our collection of i18n tutorials aimed at developers. Specifically, you might be interested in our Vue i18n tutorial.
Thank you for staying with me, and until next time!
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.Talk to one of our localization specialists
Book a call with one of our localization specialists and get a tailored consultation that can guide you on your localization path.
Get a demoRelated posts
Behind the scenes of localization with one of Europe’s leading digital health providers