Nuxt: 国际化 i18n
最后更新:2026-08-26
MegaShop 要进入日本和全球市场,但所有页面只有中文。Alice 在日本看不到日元价格,日本用户搜不到日文商品名。Charlie 需要 i18n 让 MegaShop 支持中文/英文/日文三语切换,价格自动按货币格式化。
1. 你将学到
- @nuxtjs/i18n 模块配置:语言列表、默认语言、SEO URL 策略
- 翻译文件管理:lazy-loaded 翻译 + JSON 格式
- 运行时 API:useI18n() / t() / localePath() / switchLocalePath()
- SEO 与 i18n:hreflang 标签 + 替代语言 URL + Sitemap 多语言
- MegaShop 中文/英文/日文 + USD/JPY/CNY 多货币
2. 一个架构师的真实故事
(1) 痛点:单一语言限制全球化
MegaShop 只有中文页面,日本用户看不懂,美国用户搜不到英文商品名。价格全部显示人民币,Alice 在美国看到 "¥2,999" 不知道是日元还是人民币。
(2) @nuxtjs/i18n 的解法
Nuxt i18n 模块让每个页面自动支持多语言,URL 策略对 SEO 友好:
TEXT
📖 仅展示
/en/products/123 → English page, USD price
/ja/products/123 → Japanese page, JPY price
/zh/products/123 → Chinese page, CNY price
(3) 收益:全球市场覆盖
3 个月后,日本流量增长 200%,美国流量增长 150%,hreflang 标签让 Google 正确索引每个语言版本。
3. i18n 语言处理流程
(1) 语言检测 → 加载 → 渲染流程
flowchart TB
A[User Request] --> B{Detect Language}
B -->|Cookie| C[Use saved locale]
B -->|Browser Header| D[Match Accept-Language]
B -->|Default| E[Use defaultLocale: en]
C --> F[Load translation file]
D --> F
E --> F
F --> G[Set URL prefix]
G --> H[Render page with locale]
H --> I[Generate hreflang tags]
```
---
## 4. @nuxtjs/i18n 配置
### (1) 安装与基础配置
```bash
npm install @nuxtjs/i18n
▶ 示例:i18n 模块配置
TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{ code: 'en', name: 'English', file: 'en.json', currency: 'USD' },
{ code: 'zh', name: '中文', file: 'zh.json', currency: 'CNY' },
{ code: 'ja', name: '日本語', file: 'ja.json', currency: 'JPY' }
],
defaultLocale: 'en',
lazy: true,
langDir: 'locales/',
strategy: 'prefix_except_default', // / for en, /zh/..., /ja/...
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n-locale',
redirectOn: 'root'
}
}
})
输出:
TEXT
📖 仅展示
// 执行成功
(2) URL 策略对比
| 策略 | English URL | Chinese URL | SEO 效果 |
|---|---|---|---|
| prefix_except_default | /products | /zh/products | ✅ 推荐 |
| prefix | /en/products | /zh/products | ✅ 一致 |
| prefix_and_default | /en/products + /products | /zh/products | ⚠️ 重复 |
| no_prefix | /products | /products | ❌ 无法区分 |
4. 翻译文件管理
▶ 示例:翻译文件结构
TEXT
📖 仅展示
locales/
├── en.json # English
├── zh.json # Chinese
└── ja.json # Japanese
输出:
TEXT
📖 仅展示
执行成功
▶ 示例:英文翻译文件
JSON
{
"common": {
"home": "Home",
"products": "Products",
"cart": "Cart",
"search": "Search",
"signIn": "Sign In",
"signOut": "Sign Out"
},
"product": {
"addToCart": "Add to Cart",
"outOfStock": "Out of Stock",
"reviews": "{count} reviews",
"price": "Price: {amount} {currency}",
"freeShipping": "Free shipping on orders over {amount} USD"
},
"cart": {
"empty": "Your cart is empty",
"total": "Total: {amount}",
"checkout": "Proceed to Checkout",
"items": "{count} items"
}
}
输出:
JSON
{
"common": {
"home": "Home",
"products": "Products",
"cart": "Cart",
"search": "Search",
"signIn": "Sign In",
"signOut": "Sign Out"
},
"product": {
"addToCart": "Add to Cart",
"outOfStock": "Out of Stock",
"reviews": "{count} reviews",
"price": "Price: {amount} {currency}",
"freeShipping": "Free shipping on orders over {amount} USD"
},
"cart": {
"empty": "Your cart is empty",
"total": "Total: {amount}",
"checkout": "Proceed to Ch
▶ 示例:中文翻译文件
JSON
{
"common": {
"home": "首页",
"products": "商品",
"cart": "购物车",
"search": "搜索",
"signIn": "登录",
"signOut": "退出"
},
"product": {
"addToCart": "加入购物车",
"outOfStock": "缺货",
"reviews": "{count} 条评价",
"price": "价格:{amount} {currency}",
"freeShipping": "满 {amount} 元免运费"
},
"cart": {
"empty": "购物车为空",
"total": "合计:{amount}",
"checkout": "去结算",
"items": "{count} 件商品"
}
}
输出:
JSON
{
"common": {
"home": "首页",
"products": "商品",
"cart": "购物车",
"search": "搜索",
"signIn": "登录",
"signOut": "退出"
},
"product": {
"addToCart": "加入购物车",
"outOfStock": "缺货",
"reviews": "{count} 条评价",
"price": "价格:{amount} {currency}",
"freeShipping": "满 {amount} 元免运费"
},
"cart": {
"empty": "购物车为空",
"total": "合计:{amount}",
"checkout": "去结算",
"items": "{count} 件商品"
}
}
5. 运行时 API
▶ 示例:useI18n 在组件中使用
VUE
<template>
<div>
<h1>{{ t('common.products') }}</h1>
<button>{{ t('product.addToCart') }}</button>
<p>{{ t('product.reviews', { count: 1200 }) }}</p>
<p>{{ t('product.price', { amount: formattedPrice, currency: currentCurrency }) }}</p>
</div>
</template>
<script setup lang="ts">
const { t, locale, locales } = useI18n()
// Get current currency based on locale
const currentCurrency = computed(() => {
const current = locales.value.find(l => l.code === locale.value)
return current?.currency || 'USD'
})
const formattedPrice = computed(() => {
return new Intl.NumberFormat(locale.value, {
style: 'currency',
currency: currentCurrency.value
}).format(299.99)
})
</script>
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例:语言切换导航
VUE
<!-- components/LanguageSwitcher.vue -->
<template>
<div class="language-switcher">
<NuxtLink
v-for="loc in availableLocales"
:key="loc.code"
:to="switchLocalePath(loc.code)"
:class="{ active: loc.code === locale }"
>
{{ loc.name }}
</NuxtLink>
</div>
</template>
<script setup lang="ts">
const { locale, locales, t } = useI18n()
const switchLocalePath = useSwitchLocalePath()
const availableLocales = computed(() =>
(locales.value as any[]).filter(l => l.code !== locale.value)
)
</script>
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例:localePath 路由翻译
VUE
<template>
<nav>
<NuxtLink :to="localePath('/')">{{ t('common.home') }}</NuxtLink>
<NuxtLink :to="localePath('/products')">{{ t('common.products') }}</NuxtLink>
<NuxtLink :to="localePath('/cart')">{{ t('common.cart') }}</NuxtLink>
</nav>
</template>
<script setup lang="ts">
const localePath = useLocalePath()
</script>
输出:
TEXT
📖 仅展示
// 执行成功
6. SEO 与 i18n
(1) hreflang 标签自动生成
@nuxtjs/i18n 自动为每个页面生成 hreflang 标签:
HTML
<!-- Auto-generated by i18n module -->
<link rel="alternate" hreflang="en" href="https://megashop.com/products/123" />
<link rel="alternate" hreflang="zh" href="https://megashop.com/zh/products/123" />
<link rel="alternate" hreflang="ja" href="https://megashop.com/ja/products/123" />
<link rel="alternate" hreflang="x-default" href="https://megashop.com/products/123" />
▶ 示例:多语言 Sitemap
TYPESCRIPT
// nuxt.config.ts - i18n + sitemap integration
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n', '@nuxtjs/sitemap'],
i18n: {
locales: [
{ code: 'en', name: 'English', file: 'en.json' },
{ code: 'zh', name: '中文', file: 'zh.json' },
{ code: 'ja', name: '日本語', file: 'ja.json' }
],
defaultLocale: 'en'
},
sitemap: {
hostname: 'https://megashop.com',
i18n: {
locales: ['en', 'zh', 'ja'],
defaultLocale: 'en'
}
}
})
输出:
TEXT
📖 仅展示
// 执行成功
(2) i18n SEO 检查清单
| 检查项 | 要求 | 验证方法 |
|---|---|---|
| hreflang 标签 | 每个语言版本互指 | 查看源代码 |
| canonical URL | 指向当前语言版本 | 查看源代码 |
| x-default | 指向默认语言 | 查看源代码 |
| URL 包含语言前缀 | /zh/ /ja/ | 浏览器地址栏 |
| 翻译完整率 | > 95% | 自动化检查 |
7. 综合示例:MegaShop 多语言多货币
TYPESCRIPT
// composables/useLocalizedPrice.ts
export function useLocalizedPrice() {
const { locale, locales } = useI18n()
const currentCurrency = computed(() => {
const current = (locales.value as any[]).find(l => l.code === locale.value)
return current?.currency || 'USD'
})
function formatPrice(amount: number): string {
return new Intl.NumberFormat(locale.value, {
style: 'currency',
currency: currentCurrency.value
}).format(amount)
}
// Convert USD base price to local currency (simplified)
const rates: Record<string, number> = { USD: 1, CNY: 7.25, JPY: 149.5 }
function convertFromUSD(usdAmount: number): number {
const rate = rates[currentCurrency.value] || 1
return usdAmount * rate
}
function formatFromUSD(usdAmount: number): string {
return formatPrice(convertFromUSD(usdAmount))
}
return { currentCurrency, formatPrice, convertFromUSD, formatFromUSD }
}
VUE
<!-- components/ProductCard.vue -->
<template>
<div class="product-card">
<img :src="product.image" :alt="product.name" />
<h3>{{ product.name }}</h3>
<p>{{ formatFromUSD(product.price) }}</p>
<button>{{ t('product.addToCart') }}</button>
<p>{{ t('product.reviews', { count: product.reviewCount }) }}</p>
</div>
</template>
<script setup lang="ts">
const { t } = useI18n()
const { formatFromUSD } = useLocalizedPrice()
defineProps<{ product: any }>()
</script>
❓ 常见问题
Q lazy: true 和 lazy: false 有什么区别?
A lazy: true 按需加载翻译文件(切换语言时才加载),减少首屏体积。lazy: false 一次加载所有语言,切换即时但首屏慢。推荐 lazy: true。
Q 翻译文件用 JSON 还是 YAML?
A JSON 是默认格式,兼容性最好。YAML 更易读但需额外安装解析器。团队协作推荐 JSON,支持嵌套结构。
Q i18n 策略 prefix_except_default 会不会导致默认语言没有 URL 前缀?
A 是的,/ 是英文,/zh/ 是中文。这是 SEO 推荐策略——默认语言更短的 URL。如需一致可用 prefix 策略。
Q 运行时切换语言怎么持久化?
A i18n 模块内置 Cookie 持久化(detectBrowserLanguage.useCookie: true)。也可用 setLocaleCookie() 手动设置。
Q 多货币怎么处理——i18n 只管翻译?
A i18n 管语言翻译,货币需要单独处理。用 Intl.NumberFormat 格式化,汇率转换在 Composable 中实现。MegaShop 的 useLocalizedPrice 封装了两者。
Q 百万商品页的翻译怎么管理?
A 商品名/描述等动态数据在数据库中多语言存储(ProductTranslation 表),翻译文件只管 UI 文本。API 根据当前语言返回对应翻译。
📖 小节
- @nuxtjs/i18n 模块支持 lazy 加载、SEO URL 策略、浏览器语言检测
- 翻译文件用 JSON 格式,支持插值变量 {count}/{amount}
- useI18n().t() 翻译文本,localePath() 生成本地化路由,switchLocalePath() 切换语言
- hreflang 标签自动生成,Sitemap 多语言支持,Google 正确索引各语言版本
- 多货币用 Intl.NumberFormat + Composable 封装,商品数据多语言存数据库
📝 作业
- 基础题(难度⭐):安装 @nuxtjs/i18n,配置中英两种语言,实现基础 UI 翻译
- 进阶题(难度⭐⭐):添加日文语言 + 语言切换器 + localePath 路由翻译,验证 hreflang 标签
- 挑战题(难度⭐⭐⭐):实现 useLocalizedPrice Composable,支持 USD/CNY/JPY 三种货币自动转换和格式化,商品页根据语言显示对应货币价格
---|