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

> Implement i18n for multiple languages in your Nuxt app using @nuxtjs/i18n. Configure translations, store locales, and use the useI18n composable easily.

- Repository: [Zyronon/TypeWords](https://github.com/zyronon/TypeWords)
- Tags: how-to-guide
- Published: 2026-09-03

---

**Configure `@nuxtjs/i18n` in [`nuxt.config.ts`](https://github.com/zyronon/TypeWords/blob/main/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](https://github.com/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`](https://github.com/zyronon/TypeWords/blob/main/nuxt.config.ts), `@nuxtjs/i18n` is added to the modules array alongside other project dependencies.

```ts
// 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/`.

```json
// 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`](https://github.com/zyronon/TypeWords/blob/main/nuxt.config.ts).

## Consume Translations with useI18n() in Components

Inside Vue components, import `useI18n` from `vue-i18n` to access translation utilities. The [`WordMetaPanel.vue`](https://github.com/zyronon/TypeWords/blob/main/WordMetaPanel.vue) component in TypeWords shows the standard pattern.

```vue
<!-- 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:

```vue
<!-- 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:

```ts
// 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`](https://github.com/zyronon/TypeWords/blob/main/i18n/locales/es.json)

```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`](https://github.com/zyronon/TypeWords/blob/main/nuxt.config.ts)

```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:

```ts
// 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/main/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/main/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/main/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/main/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/main/app/layouts/default.vue)](https://github.com/zyronon/TypeWords/blob/master/app/layouts/default.vue) | Global layout i18n integration |
| [`app/pages/setting.vue`](https://github.com/zyronon/TypeWords/blob/main/app/pages/setting.vue) | `<i18n-t>` usage for complex translations |

## Summary

- **Configuration:** Register `@nuxtjs/i18n` in [`nuxt.config.ts`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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.