Files
gh-lttr-claude-marketplace-…/skills/nuxt/references/nuxt-i18n.md
2025-11-30 08:38:06 +08:00

758 lines
15 KiB
Markdown

# Nuxt I18n Reference
**Last Updated:** 2025-11 (v10.2.0)
**Check:** `@nuxtjs/i18n` in package.json
Nuxt I18n v10 provides internationalization (i18n) for Nuxt applications with auto-imports, locale routing, SEO support, and integration with Vue I18n v11.
## Installation & Setup
```bash
pnpm add @nuxtjs/i18n
```
**nuxt.config.ts:**
```typescript
export default defineNuxtConfig({
modules: ["@nuxtjs/i18n"],
i18n: {
locales: [
{ code: "en", iso: "en-US", name: "English", file: "en.json" },
{ code: "fr", iso: "fr-FR", name: "Français", file: "fr.json" },
{ code: "es", iso: "es-ES", name: "Español", file: "es.json" },
],
defaultLocale: "en",
langDir: "locales/",
strategy: "prefix_except_default", // or 'prefix', 'no_prefix'
detectBrowserLanguage: {
useCookie: true,
cookieKey: "i18n_redirected",
redirectOn: "root",
},
},
})
```
## Directory Structure
```
locales/
├── en.json
├── fr.json
└── es.json
```
**Example locale file (en.json):**
```json
{
"welcome": "Welcome",
"hello": "Hello {name}",
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"products": {
"title": "Our Products",
"description": "Browse our collection"
}
}
```
## Basic Usage
### Translation in Templates
```vue
<template>
<div>
<!-- Simple translation -->
<h1>{{ $t("welcome") }}</h1>
<!-- With parameters -->
<p>{{ $t("hello", { name: "John" }) }}</p>
<!-- Nested keys -->
<nav>
<NuxtLink to="/">{{ $t("nav.home") }}</NuxtLink>
<NuxtLink to="/about">{{ $t("nav.about") }}</NuxtLink>
</nav>
<!-- Pluralization -->
<p>{{ $t("items", { count: 5 }) }}</p>
<!-- Number formatting -->
<p>{{ $n(1000, "currency") }}</p>
<!-- Date formatting -->
<p>{{ $d(new Date(), "long") }}</p>
</div>
</template>
```
### Translation in Script
```vue
<script setup lang="ts">
const { t, locale, locales, setLocale } = useI18n()
// Get translation
const welcomeMessage = t("welcome")
// With parameters
const greeting = t("hello", { name: "John" })
// Current locale
console.log(locale.value) // 'en'
// Available locales
console.log(locales.value) // [{ code: 'en', ... }, ...]
// Change locale
async function switchLanguage(code: string) {
await setLocale(code)
}
</script>
```
## Routing
### Route Strategies
**prefix_except_default** (recommended):
- Default locale: `/about`
- Other locales: `/fr/about`, `/es/about`
**prefix**:
- All locales: `/en/about`, `/fr/about`, `/es/about`
**no_prefix**:
- All locales use same path: `/about`
- Locale detected from cookie/browser
### Locale Links
```vue
<script setup lang="ts">
const localePath = useLocalePath()
const switchLocalePath = useSwitchLocalePath()
</script>
<template>
<div>
<!-- Link with current locale -->
<NuxtLink :to="localePath('/')">
{{ $t("nav.home") }}
</NuxtLink>
<NuxtLink :to="localePath('/about')">
{{ $t("nav.about") }}
</NuxtLink>
<!-- Switch locale links -->
<NuxtLink :to="switchLocalePath('fr')"> Français </NuxtLink>
<NuxtLink :to="switchLocalePath('es')"> Español </NuxtLink>
<!-- With named routes -->
<NuxtLink :to="localePath({ name: 'products-id', params: { id: '123' } })">
Product
</NuxtLink>
</div>
</template>
```
### Programmatic Navigation
```vue
<script setup lang="ts">
const localePath = useLocalePath()
const router = useRouter()
async function goToAbout() {
await router.push(localePath("/about"))
}
async function goToProduct(id: string) {
await navigateTo(localePath({ name: "products-id", params: { id } }))
}
</script>
```
## Composables
### useI18n
Main composable for translations:
```vue
<script setup lang="ts">
const {
t, // Translation function
locale, // Current locale ref
locales, // Available locales
setLocale, // Change locale function
n, // Number formatting
d, // Date formatting
tm, // Translation messages
te, // Translation exists check
getLocaleCookie, // Get locale cookie
} = useI18n()
// Check if translation exists
if (te("optional.message")) {
console.log(t("optional.message"))
}
// Get all translations for a key
const navTranslations = tm("nav")
console.log(navTranslations) // { home: 'Home', about: 'About', ... }
</script>
```
### useLocalePath
Generate localized paths:
```vue
<script setup lang="ts">
const localePath = useLocalePath()
// Simple path
const homePath = localePath("/")
// With route object
const productPath = localePath({
name: "products-id",
params: { id: "123" },
query: { tab: "reviews" },
})
// With specific locale
const frenchPath = localePath("/about", "fr")
</script>
```
### useSwitchLocalePath
Generate paths for locale switching:
```vue
<script setup lang="ts">
const switchLocalePath = useSwitchLocalePath()
const route = useRoute()
// Get path for switching to French on current page
const frenchPath = switchLocalePath("fr")
// Custom route
const customPath = switchLocalePath("es", "/about")
</script>
```
### useRouteBaseName
Get route name without locale prefix:
```vue
<script setup lang="ts">
const getRouteBaseName = useRouteBaseName()
const route = useRoute()
// Current route: /fr/products/123
const baseName = getRouteBaseName(route) // 'products-id'
</script>
```
### useBrowserLocale
Detect browser locale:
```vue
<script setup lang="ts">
const browserLocale = useBrowserLocale()
console.log(browserLocale) // 'en-US'
</script>
```
## Common Patterns
### Language Switcher
```vue
<script setup lang="ts">
const { locale, locales, setLocale } = useI18n()
const switchLocalePath = useSwitchLocalePath()
const availableLocales = computed(() =>
locales.value.filter((l) => l.code !== locale.value),
)
</script>
<template>
<div class="flex gap-2">
<NuxtLink
v-for="loc of availableLocales"
:key="loc.code"
:to="switchLocalePath(loc.code)"
class="px-3 py-1 rounded hover:bg-gray-100"
>
{{ loc.name }}
</NuxtLink>
</div>
</template>
```
### Dropdown Language Switcher
```vue
<script setup lang="ts">
const { locale, locales } = useI18n()
const switchLocalePath = useSwitchLocalePath()
const currentLocale = computed(() =>
locales.value.find((l) => l.code === locale.value),
)
</script>
<template>
<UDropdown>
<template #trigger>
<UButton>
{{ currentLocale?.name }}
</UButton>
</template>
<div class="p-2">
<NuxtLink
v-for="loc of locales"
:key="loc.code"
:to="switchLocalePath(loc.code)"
class="block px-4 py-2 hover:bg-gray-100"
>
{{ loc.name }}
</NuxtLink>
</div>
</UDropdown>
</template>
```
### SEO with I18n
```vue
<script setup lang="ts">
const { locale, locales, t } = useI18n()
const route = useRoute()
const switchLocalePath = useSwitchLocalePath()
useSeoMeta({
title: t("seo.title"),
description: t("seo.description"),
ogLocale: locale.value,
ogTitle: t("seo.ogTitle"),
ogDescription: t("seo.ogDescription"),
})
useHead({
htmlAttrs: {
lang: locale.value,
},
link: [
// Alternate language links for SEO
...locales.value.map((loc) => ({
rel: "alternate",
hreflang: loc.iso,
href: `https://example.com${switchLocalePath(loc.code)}`,
})),
],
})
</script>
```
### Per-Page Translations
```vue
<script setup lang="ts">
const { t } = useI18n({
useScope: "local",
})
</script>
<template>
<div>
<h1>{{ t("title") }}</h1>
<p>{{ t("description") }}</p>
</div>
</template>
<i18n lang="json">
{
"en": {
"title": "About Us",
"description": "Learn more about our company"
},
"fr": {
"title": "À propos",
"description": "En savoir plus sur notre entreprise"
}
}
</i18n>
```
### Dynamic Content Translation
```vue
<script setup lang="ts">
const { data: products } = await useFetch("/api/products")
const { locale } = useI18n()
// Assuming API returns translations
const localizedProducts = computed(() =>
products.value?.map((product) => ({
...product,
name: product.translations[locale.value]?.name || product.name,
description:
product.translations[locale.value]?.description || product.description,
})),
)
</script>
<template>
<div v-for="product of localizedProducts" :key="product.id">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
</div>
</template>
```
### Lazy Loading Translations
For large translation files:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
i18n: {
lazy: true,
langDir: "locales/",
locales: [
{ code: "en", file: "en.json" },
{ code: "fr", file: "fr.json" },
{ code: "es", file: "es.json" },
],
},
})
```
## Number & Date Formatting
### Number Formatting
Define formats in config:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
i18n: {
numberFormats: {
en: {
currency: {
style: "currency",
currency: "USD",
},
decimal: {
style: "decimal",
minimumFractionDigits: 2,
},
},
fr: {
currency: {
style: "currency",
currency: "EUR",
},
decimal: {
style: "decimal",
minimumFractionDigits: 2,
},
},
},
},
})
```
Usage:
```vue
<template>
<div>
<!-- Currency -->
<p>{{ $n(1234.56, "currency") }}</p>
<!-- en: $1,234.56 -->
<!-- fr: 1 234,56 -->
<!-- Decimal -->
<p>{{ $n(123456.789, "decimal") }}</p>
<!-- en: 123,456.79 -->
<!-- fr: 123 456,79 -->
</div>
</template>
```
### Date Formatting
Define formats in config:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
i18n: {
datetimeFormats: {
en: {
short: {
year: "numeric",
month: "short",
day: "numeric",
},
long: {
year: "numeric",
month: "long",
day: "numeric",
weekday: "long",
},
},
fr: {
short: {
year: "numeric",
month: "short",
day: "numeric",
},
long: {
year: "numeric",
month: "long",
day: "numeric",
weekday: "long",
},
},
},
},
})
```
Usage:
```vue
<script setup lang="ts">
const date = new Date("2025-01-04")
</script>
<template>
<div>
<!-- Short format -->
<p>{{ $d(date, "short") }}</p>
<!-- en: Jan 4, 2025 -->
<!-- fr: 4 janv. 2025 -->
<!-- Long format -->
<p>{{ $d(date, "long") }}</p>
<!-- en: Saturday, January 4, 2025 -->
<!-- fr: samedi 4 janvier 2025 -->
</div>
</template>
```
## Pluralization
**Translation file:**
```json
{
"items": "no items | one item | {count} items",
"cart": "You have {n} item in your cart | You have {n} items in your cart"
}
```
**Usage:**
```vue
<template>
<div>
<p>{{ $t("items", 0) }}</p>
<!-- "no items" -->
<p>{{ $t("items", 1) }}</p>
<!-- "one item" -->
<p>{{ $t("items", 5) }}</p>
<!-- "5 items" -->
<p>{{ $t("cart", { n: 1 }) }}</p>
<!-- "You have 1 item in your cart" -->
<p>{{ $t("cart", { n: 3 }) }}</p>
<!-- "You have 3 items in your cart" -->
</div>
</template>
```
## TypeScript Support
### Typed Translations
```typescript
// types/i18n.ts
export interface LocaleMessages {
welcome: string
hello: (params: { name: string }) => string
nav: {
home: string
about: string
contact: string
}
}
```
Usage:
```vue
<script setup lang="ts">
import type { LocaleMessages } from "~/types/i18n"
const { t } = useI18n<LocaleMessages>()
// TypeScript will check keys and parameters
const welcome = t("welcome")
const hello = t("hello", { name: "John" })
const navHome = t("nav.home")
</script>
```
## API Routes with I18n
```typescript
// server/api/products.get.ts
export default defineEventHandler(async (event) => {
const locale = getCookie(event, "i18n_redirected") || "en"
const products = await db.products.findMany({
include: {
translations: {
where: { locale },
},
},
})
return products.map((product) => ({
...product,
name: product.translations[0]?.name || product.name,
description: product.translations[0]?.description || product.description,
}))
})
```
## Best Practices
1. **Use prefix_except_default strategy** - Better UX for default locale users
2. **Enable browser detection** - Auto-redirect to user's preferred language
3. **Provide language switcher** - Always visible on every page
4. **Use lazy loading for large apps** - Load translations on demand
5. **Organize translations by feature** - Use nested keys (`nav.home`, `products.title`)
6. **Include locale in SEO** - Use `hreflang` links and `og:locale`
7. **Handle missing translations** - Provide fallback locale
8. **Use placeholders consistently** - `{name}` for simple, `{n}` for pluralization
9. **Test all locales** - Verify layout with longer translations (German, French)
10. **Keep keys consistent** - Same structure across all locale files
## Troubleshooting
### Translations Not Loading
Check:
1. `@nuxtjs/i18n` in `modules` array
2. Locale files in correct `langDir` path
3. File names match `file` property in config
4. JSON is valid (no trailing commas)
### Routes Not Localized
1. Verify `strategy` is set correctly
2. Check `defaultLocale` matches one of your locales
3. Ensure you're using `localePath()` for links
### Browser Detection Not Working
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
i18n: {
detectBrowserLanguage: {
useCookie: true,
cookieKey: "i18n_redirected",
redirectOn: "root",
alwaysRedirect: true,
fallbackLocale: "en",
},
},
})
```
### Missing Translations
Enable warnings in development:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
i18n: {
compilation: {
strictMessage: false,
},
vueI18n: "./i18n.config.ts",
},
})
```
```typescript
// i18n.config.ts
export default {
legacy: false,
locale: "en",
missingWarn: true,
fallbackWarn: true,
}
```
## v10 Changes from v9
### Key Updates
1. **Vue I18n v11** - Upgraded from v10 with JIT compilation as default
2. **Improved Nuxt 4 Support** - Better compatibility with Nuxt 4
3. **Custom Routes** - Use `definePageMeta` for per-page locale configuration
4. **Server-Side Redirects** - Improved server-side redirection behavior
5. **Strict SEO** - Experimental strict SEO head management
6. **Fixed Behaviors** - `strategy` and `redirectOn` combinations now work as expected
### Migration Notes
- `$tc()` API integrated into `$t()` (from Vue I18n v10→v11 upgrade)
- JIT compilation now default (no need for `jit` option)
- New directory structure: i18n files resolved from `<rootDir>/i18n` (configurable with `restructureDir`)
- Context functions require `$` prefix: use `$localePath()` not `localePath()` in templates
## Official Resources
- **Documentation:** https://i18n.nuxtjs.org
- **Migration Guide:** https://i18n.nuxtjs.org/docs/guide/migrating
- **API Reference:** https://i18n.nuxtjs.org/api
- **Vue I18n Docs:** https://vue-i18n.intlify.dev
- **Examples:** https://i18n.nuxtjs.org/examples
- **GitHub:** https://github.com/nuxt-modules/i18n
---
**Note:** This reference covers Nuxt I18n v10 (latest as of 2025-11) with Vue I18n v11. For v9 projects, consult the migration guide.