Skip to content

Nuxt Internationalization (i18n) — Complete Guide to @nuxtjs/i18n

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Nuxt Internationalization (i18n). We cover key concepts, practical examples, and best practices to help you master this topic.

Learn Nuxt i18n with @nuxtjs/i18n — configure multiple locales, manage translation files, implement locale switching, and optimize SEO for multilingual sites.

In this lesson, you'll understand how to add multi-language support to your Nuxt application so users can switch between languages with localized URLs, SEO tags, and translated content.

What You'll Learn

How to install and configure @nuxtjs/i18n, create translation files for multiple locales, implement automated locale detection and switching, generate localized routes, and optimize multilingual SEO with hreflang tags.

Why It Matters

Internationalization opens your application to global audiences. Multilingual sites rank better in local search results, increase user engagement by 40-60%, and are essential for reaching non-English-speaking markets.

Real-World Use

A global e-commerce platform supports 12 languages with Nuxt i18n, using locale-specific URLs, automated browser language detection, and SEO-optimized hreflang tags that increased organic traffic from non-English markets by 180%.

flowchart LR
    A[User Browser] --> B{Detect Locale}
    B -->|en| C[English Site]
    B -->|es| D[Spanish Site]
    B -->|fr| E[French Site]
    C --> F[en/about]
    D --> G[es/acerca-de]
    E --> H[fr/a-propos]
    F --> I[Localized SEO Tags]
    G --> I
    H --> I
    I --> J[hreflang Tags]
    style A fill:#00dc82,color:#fff

Installation

npm install @nuxtjs/i18n@next
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    locales: [
      { code: 'en', iso: 'en-US', name: 'English', file: 'en.json' },
      { code: 'es', iso: 'es-ES', name: 'Español', file: 'es.json' },
      { code: 'fr', iso: 'fr-FR', name: 'Français', file: 'fr.json' }
    ],
    defaultLocale: 'en',
    lazy: true,
    langDir: 'locales/',
    strategy: 'prefix_except_default'
  }
});

Translation Files

Create JSON files for each locale in the locales/ directory:

// locales/en.json
{
  "home": {
    "title": "Welcome to our website",
    "description": "Learn Nuxt with practical examples",
    "cta": "Get Started"
  },
  "nav": {
    "home": "Home",
    "about": "About",
    "contact": "Contact",
    "blog": "Blog"
  },
  "footer": {
    "copyright": "All rights reserved.",
    "language": "Language"
  }
}
// locales/es.json
{
  "home": {
    "title": "Bienvenido a nuestro sitio web",
    "description": "Aprende Nuxt con ejemplos prácticos",
    "cta": "Comenzar"
  },
  "nav": {
    "home": "Inicio",
    "about": "Acerca de",
    "contact": "Contacto",
    "blog": "Blog"
  },
  "footer": {
    "copyright": "Todos los derechos reservados.",
    "language": "Idioma"
  }
}

Using Translations in Templates

Access translations with the $t function or the useI18n composable:

<template>
  <div>
    <h1>{{ $t('home.title') }}</h1>
    <p>{{ $t('home.description') }}</p>
    <button>{{ $t('home.cta') }}</button>
  </div>
</template>

Using the composable in script:

<script setup>
const { t, locale, setLocale } = useI18n();

const pageTitle = computed(() => t('home.title'));
const pageDescription = computed(() => t('home.description'));
</script>

<template>
  <Head>
    <Title>{{ pageTitle }}</Title>
    <Meta name="description" :content="pageDescription" />
  </Head>
  <h1>{{ pageTitle }}</h1>
</template>

Expected output: The page renders content in the user's selected language, with localized SEO meta tags.

Locale Switching

Create a language switcher component:

<template>
  <select
    :value="locale"
    @change="switchLocale($event.target.value)"
    class="language-selector"
  >
    <option
      v-for="loc in locales"
      :key="loc.code"
      :value="loc.code"
    >
      {{ loc.name }}
    </option>
  </select>
</template>

<script setup>
const { locale, locales, setLocale } = useI18n();
const router = useRouter();

async function switchLocale(newLocale) {
  await setLocale(newLocale);
  // Router updates the path automatically with prefix strategy
}
</script>

Expected output: A dropdown that switches the entire page to the selected language, updating the URL prefix and all translated content.

Localized Routes with Parameters

Dynamic routes with localized slugs:

<script setup>
const { t, locale } = useI18n();
const route = useRoute();

// Fetch localized page data based on slug
const { data: page } = await useAsyncData('page', () => {
  return queryContent(`/blog/${route.params.slug}`).findOne();
});

// Generate localized paths for SEO
const canonicalPath = computed(() => {
  return locale.value === 'en'
    ? route.path
    : `/${locale.value}${route.path.replace(/^\/[a-z]{2}/, '')}`;
});
</script>

<template>
  <article>
    <h1>{{ page.title }}</h1>
    <div>{{ page.description }}</div>
  </article>
</template>

Expected output: Blog posts with locale-specific slugs like /blog/my-post (English) and /es/blog/mi-articulo (Spanish).

SEO with hreflang Tags

Automatic hreflang tags for all locales:

<script setup>
const { locale, locales, t } = useI18n();
const route = useRoute();

// Generate hreflang links
const hreflangLinks = computed(() => {
  return locales.value.map(loc => ({
    hid: `alternate-hreflang-${loc.code}`,
    rel: 'alternate',
    href: loc.code === 'en'
      ? `https://example.com${route.path}`
      : `https://example.com/${loc.code}${route.path}`,
    hreflang: loc.iso
  }));
});
</script>

<template>
  <Head>
    <link
      v-for="link in hreflangLinks"
      :key="link.hid"
      :rel="link.rel"
      :href="link.href"
      :hreflang="link.hreflang"
    />
  </Head>
</template>

Expected output: HTML <link> tags telling search engines which URLs serve which languages, preventing duplicate content issues.

Common Mistakes

  1. Using prefix Strategy without considering SEO: The prefix strategy (/en/about, /es/acerca) is best for SEO. The prefix_except_default strategy avoids a prefix for the default locale but creates URL inconsistencies.

  2. Not providing translation keys for all locales: A missing key in any locale causes a fallback to the default locale or a raw key display. Always keep translation files synchronized across all locales.

  3. Forgetting to localize SEO meta tags: Search engines read <title> and <meta description> tags. These must be translated too, not just the visible page content.

  4. Hardcoding locale-specific content in components: Text outside $t() calls is not translated. Always use translation keys for user-facing text, including button labels, error messages, and placeholders.

  5. Not testing locale persistence: Without proper configuration, the selected locale resets on page reload. Use detectBrowserLanguage with useCookie to persist the user's choice.

Practice Questions

  1. What does strategy: 'prefix_except_default' mean? Answer: All locales except the default get a URL prefix (/es/about). The default locale uses clean URLs (/about). This balances SEO and URL cleanliness.

  2. How does Lazy Loading of translations improve performance? Answer: With lazy: true, translation files load only when the user switches to that locale. The initial bundle is smaller because it doesn't include all languages.

  3. What is the purpose of hreflang tags? Answer: They tell search engines which language versions of a page exist, preventing duplicate content penalties and showing the correct language in search results.

  4. How do you access the current locale in a component? Answer: Use const { locale } = useI18n() or $i18n.locale in templates. Both provide the current active locale code.

Challenge

Build a multilingual documentation site with: three locales (English, Spanish, French), localized content files organized by locale, a language switcher that persists in a cookie, localized slugs for each document, hreflang tags in the page head, and automatic locale detection based on browser preference.

Mini Project

Create a portfolio site in at least two languages with: full page translation using JSON files, localized navigation and footer, a language switcher dropdown, locale-specific URLs with prefix strategy, hreflang SEO tags, translated meta descriptions and Open Graph tags, and a cookie-based locale persistence.

FAQ

Can I change the locale without reloading the page?

: Yes. setLocale() updates the locale reactively. Components re-render with new translations. The route updates based on your strategy configuration.

How do I handle date and number formatting per locale?

: Use $d() for dates and $n() for numbers. These functions format values according to locale conventions. Define format options in i18n config.

Does @nuxtjs/i18n work with static generation?

: Yes. With strategy: 'prefix' or 'prefix_except_default', the generator creates separate HTML files for each locale. Static sites serve all language versions without a server.

What happens when a translation key is missing?

: The module falls back to the default locale's value, then to the raw key. Enable warnMissingMessages in development to catch missing keys early.

How do I add RTL (right-to-left) language support?

: Configure locale direction with dir: 'rtl' in the locale definition. Use the locale watcher to toggle a class on the HTML element for RTL-specific CSS.

What's Next

Learn about Nuxt Authentication and Authorization to add user login, session management, and protected routes to your Nuxt application.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro