Localize a Nuxt 3 app: complete guide with Localingos

Nuxt 3 with @nuxtjs/i18n is the cleanest server-rendered Vue i18n stack available — locale routing, SEO meta generation, and hreflang annotations all out of the box. This guide combines it with Localingos for the translation pipeline. About 15 minutes start to finish.

Step 1 — Install

npm install @nuxtjs/i18n
npm install -g @localingos/cli
localingos login

Step 2 — Configure @nuxtjs/i18n

nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    defaultLocale: 'en',
    strategy: 'prefix_except_default',  // English at /, others at /es/, /de/...
    locales: [
      { code: 'en', iso: 'en-US', file: 'en.json' },
      { code: 'es', iso: 'es-ES', file: 'es.json' },
      { code: 'de', iso: 'de-DE', file: 'de.json' },
      { code: 'fr', iso: 'fr-FR', file: 'fr.json' },
      { code: 'ja', iso: 'ja-JP', file: 'ja.json' },
    ],
    langDir: 'i18n/locales/',
    lazy: true,
    detectBrowserLanguage: {
      useCookie: true,
      cookieKey: 'i18n_locale',
      fallbackLocale: 'en',
      redirectOn: 'root',  // only auto-redirect on /, not deep links (SEO-safe)
    },
  },
});

Three things to understand here:

  • strategy: 'prefix_except_default' — English at /pricing, Spanish at /es/pricing. The recommended setup for SEO.
  • lazy: true — each locale's JSON is fetched on demand. Only the active locale ships in the initial bundle.
  • redirectOn: 'root' — auto-redirects first-time visitors only on the homepage, not on deep links. Critical: deep-link redirects break crawlers and SEO.

Step 3 — Source of truth

i18n/locales/en.json:

{
  "welcome": "Welcome, {name}",
  "cart": "no items in cart | one item in cart | {count} items in cart"
}

Vue's | pluralization syntax.

Step 4 — Configure Localingos

localingos.json:

{
  "projectId": "your-project-id",
  "source": { "path": "i18n/locales/en.json", "locale": "en" },
  "targets": {
    "path": "i18n/locales/{locale}.json",
    "locales": ["es", "de", "fr", "ja", "pt-BR", "zh-CN", "ko"]
  },
  "placeholders": ["{name}", "{count}"]
}
localingos sync

Step 5 — Use in components

<script setup lang="ts">
const { t } = useI18n();
defineProps<{ userName: string; itemCount: number }>();
</script>

<template>
  <header>
    <h1>{{ t('welcome', { name: userName }) }}</h1>
    <p>{{ t('cart', itemCount) }}</p>
  </header>
</template>

Step 6 — Locale-aware links

Use Nuxt's built-in helper to generate URLs in the active locale:

<template>
  <NuxtLinkLocale to="/pricing">{{ t('nav.pricing') }}</NuxtLinkLocale>
</template>

When the active locale is es, this generates /es/pricing. When it's en, /pricing. Saves manual URL construction.

Step 7 — SEO meta and hreflang

Nuxt i18n generates hreflang automatically when you use useLocaleHead:

<script setup lang="ts">
const head = useLocaleHead({ addSeoAttributes: true });
useHead(head);
</script>

This injects per-page <link rel="alternate" hreflang="..."> for every configured locale and a <link rel="canonical">. Google sees the right localized variant and treats /pricing as canonical with /es/pricing as an alternate.

Step 8 — Language switcher

<script setup lang="ts">
const { locale, locales, setLocale } = useI18n();
const switchLocaleTo = (newLocale: string) => setLocale(newLocale);
</script>

<template>
  <select :value="locale" @change="e => switchLocaleTo((e.target as HTMLSelectElement).value)">
    <option v-for="l in locales" :key="l.code" :value="l.code">{{ l.code.toUpperCase() }}</option>
  </select>
</template>

setLocale() updates the URL to the locale-prefixed path automatically — /pricing/es/pricing. Plays nicely with browser back/forward.

Step 9 — Automate sync in CI

# .github/workflows/i18n.yml
name: i18n-sync
on:
  push: { branches: [main], paths: ['i18n/locales/en.json'] }
jobs:
  sync:
    runs-on: ubuntu-latest
    permissions: { contents: write, pull-requests: write }
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm install -g @localingos/cli
      - run: localingos sync
        env: { LOCALINGOS_API_KEY: '${{ secrets.LOCALINGOS_API_KEY }}' }
      - uses: peter-evans/create-pull-request@v6
        with:
          branch: i18n/auto-sync
          title: 'chore(i18n): sync translations'
          commit-message: 'chore(i18n): sync translations'

Production checklist

  • Static generation (nuxt generate). Each locale gets its own pre-rendered HTML — no extra work needed.
  • Server-side rendering. Locale detected from cookie/header before render; no hydration mismatch.
  • Sitemap. Use @nuxtjs/sitemap alongside i18n — it auto-generates per-locale entries with hreflang.
  • RTL. Set dir in useLocaleHead({ addDirAttribute: true }).

Wrap up

A Nuxt 3 app with locale routing, SEO meta, hreflang, and a CI translation pipeline — all in about 15 minutes. The prefix_except_default strategy plus auto-generated alternates is the most SEO-friendly setup available for a Vue stack.

Free tier covers typical Nuxt site string corpora end to end.