How to Implement i18n with @nuxtjs/i18n for Multiple Languages in a Nuxt App

Configure @nuxtjs/i18n in nuxt.config.ts, store translations in JSON files under i18n/locales/, and consume them via useI18n() composable in your Vue components.

The TypeWords project demonstrates a complete, production-ready implementation of multilingual support using the official @nuxtjs/i18n module. This guide walks through the exact architecture used in the zyronon/TypeWords repository, from configuration to runtime usage.

Configure the @nuxtjs/i18n Module in nuxt.config.ts

The foundation of i18n in a Nuxt app starts with module registration and locale definition. In nuxt.config.ts, @nuxtjs/i18n is added to the modules array alongside other project dependencies.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt', '@unocss/nuxt', 'unplugin-icons/nuxt',
            '@vue-macros/nuxt', '@nuxtjs/i18n', '@nuxt/image'],
  i18n: {
    locales: [
      { code: 'en', language: 'en-US', file: 'en.json', name: 'English' },
      { code: 'zh', language: 'zh-CN', file: 'zh.json', name: '中文' },
    ],
    defaultLocale: 'zh',
    strategy: 'no_prefix',
  },
})

Key configuration options:

  • locales – Array of locale objects defining code (URL identifier), language (BCP 47 tag), file (JSON filename), and name (display label)
  • defaultLocale – Fallback language when no locale is detected
  • strategy – Routing behavior; 'no_prefix' serves all content from root URLs without language prefixes

Other valid strategies include 'prefix_except_default' (prefix non-default locales) and 'prefix' (prefix all locales). Choose based on your SEO and UX requirements.

Structure Translation Files in i18n/locales/

Each supported language requires a dedicated JSON file containing key-value pairs for all UI strings. The TypeWords project stores these files in i18n/locales/.

// i18n/locales/en.json
{
  "app_name": "Type Words",
  "home_word_practice": "Word Practice",
  "phrases": "Phrases",
  "synonyms": "Synonyms",
  "import_overwrite_warning": "{0} This will replace all existing data.",
  "danger": "Danger"
}

Best practices for translation files:

  • Use dot notation or flat keys consistently (TypeWords uses flat keys like "home_word_practice")
  • Keep keys descriptive and hierarchical when possible
  • Store interpolation placeholders with numbered or named parameters

Adding a new language requires only two steps: create the JSON file and register it in nuxt.config.ts.

Consume Translations with useI18n() in Components

Inside Vue components, import useI18n from vue-i18n to access translation utilities. The WordMetaPanel.vue component in TypeWords shows the standard pattern.

<!-- app/components/word/WordMetaPanel.vue -->
<script setup lang="ts">
import { useI18n } from 'vue-i18n'

const { t: $t } = useI18n()
</script>

<template>
  <div class="word-meta">
    <div class="label">{{ $t('phrases') }}</div>
    <div class="label">{{ $t('synonyms') }}</div>
  </div>
</template>

Destructuring patterns from useI18n():

  • t – Translation function (aliased as $t for template consistency)
  • locale – Current locale as reactive ref
  • locales – List of available locales

For translations containing HTML or Vue components, use the <i18n-t> component with a keypath attribute:

<!-- app/pages/setting.vue -->
<i18n-t keypath="import_overwrite_warning" tag="span">
  <strong>{{ $t('danger') }}</strong>
</i18n-t>

The keypath references the translation key, and child elements replace numbered placeholders ({0}, {1}, etc.) in order.

Switch Languages Programmatically

Implement language switching by updating the reactive locale value:

// In any component or composable
const { locale } = useI18n()

function changeLanguage(lang: string) {
  locale.value = lang // 'en', 'zh', 'es', etc.
}

This change triggers reactive updates across all $t calls and persists according to your module configuration (localStorage, cookie, or URL).

Add a New Language: Complete Example

To add Spanish support to the TypeWords setup:

Step 1: Create i18n/locales/es.json

{
  "app_name": "Type Words",
  "home_word_practice": "Práctica de palabras",
  "phrases": "Frases",
  "synonyms": "Sinónimos",
  "import_overwrite_warning": "{0} Esto reemplazará todos los datos existentes.",
  "danger": "Peligro"
}

Step 2: Register in nuxt.config.ts

locales: [
  { code: 'en', language: 'en-US', file: 'en.json', name: 'English' },
  { code: 'zh', language: 'zh-CN', file: 'zh.json', name: '中文' },
  { code: 'es', language: 'es-ES', file: 'es.json', name: 'Español' },
],

The new language becomes immediately available for selection and translation.

Enable Lazy Loading for Large Applications

For applications with substantial translation files, enable on-demand loading:

// nuxt.config.ts
i18n: {
  lazy: true,
  langDir: 'i18n/locales/',
  locales: [
    { code: 'en', language: 'en-US', file: 'en.json', name: 'English' },
    { code: 'zh', language: 'zh-CN', file: 'zh.json', name: '中文' },
  ],
  defaultLocale: 'zh',
  strategy: 'no_prefix',
}

lazy: true defers fetching locale files until needed, reducing initial bundle size.

Source Files and Implementation Details

File Purpose
[nuxt.config.ts](https://github.com/zyronon/TypeWords/blob/master/nuxt.config.ts) Module registration and locale metadata
[i18n/locales/en.json](https://github.com/zyronon/TypeWords/blob/master/i18n/locales/en.json) English translation resource
[i18n/locales/zh.json](https://github.com/zyronon/TypeWords/blob/master/i18n/locales/zh.json) Chinese translation resource
[app/components/word/WordMetaPanel.vue](https://github.com/zyronon/TypeWords/blob/master/app/components/word/WordMetaPanel.vue) Component usage of useI18n()
[app/layouts/default.vue](https://github.com/zyronon/TypeWords/blob/master/app/layouts/default.vue) Global layout i18n integration
app/pages/setting.vue <i18n-t> usage for complex translations

Summary

  • Configuration: Register @nuxtjs/i18n in nuxt.config.ts with locales, defaultLocale, and strategy options
  • Resources: Store translations as JSON files in i18n/locales/ with consistent key naming
  • Runtime: Use useI18n() composable for $t translations and locale switching
  • Advanced: Leverage <i18n-t> for HTML-embedded translations and lazy: true for performance optimization

Frequently Asked Questions

How do I detect the user's browser language automatically?

The @nuxtjs/i18n module includes automatic language detection when configured with detectBrowserLanguage options. Set useCookie: true and cookieKey: 'i18n_redirected' in your i18n config to persist detected preferences. The module checks navigator.language and matches against your defined locales, falling back to defaultLocale when no match exists.

Can I use different column delimiters or formats for locale files?

Yes, @nuxtjs/i18n supports multiple file formats beyond JSON. Configure vueI18n.loader to use YAML, JSON5, or custom loaders. For YAML support, install @intlify/unplugin-vue-i18n and update your nuxt.config.ts to specify file extensions and loader options per locale entry.

What's the difference between strategy: 'no_prefix' and 'prefix_except_default'?

no_prefix serves all locales from root URLs without distinguishing paths—useful when language is determined by domain or user session. prefix_except_default adds locale prefixes to non-default languages (e.g., /en/about) while keeping the default locale at root (/about). Choose based on SEO requirements and whether you need language-specific URL indexing.

How do I handle pluralization and interpolation in translations?

Use ** ICU MessageFormat ** syntax in your JSON files. For pluralization, define pipe-separated forms: "item_count": "No items | One item | {count} items". Pass context objects to $t: $t('item_count', { count: items.length }). The module automatically selects the correct plural form based on the locale's pluralization rules and the provided count value.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →