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 definingcode(URL identifier),language(BCP 47 tag),file(JSON filename), andname(display label)defaultLocale– Fallback language when no locale is detectedstrategy– 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$tfor template consistency)locale– Current locale as reactive reflocales– 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/i18ninnuxt.config.tswithlocales,defaultLocale, andstrategyoptions - Resources: Store translations as JSON files in
i18n/locales/with consistent key naming - Runtime: Use
useI18n()composable for$ttranslations andlocaleswitching - Advanced: Leverage
<i18n-t>for HTML-embedded translations andlazy: truefor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →