Nuxt: 国际化 i18n

最后更新:2026-08-26

MegaShop 要进入日本和全球市场,但所有页面只有中文。Alice 在日本看不到日元价格,日本用户搜不到日文商品名。Charlie 需要 i18n 让 MegaShop 支持中文/英文/日文三语切换,价格自动按货币格式化。

1. 你将学到


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) 语言检测 → 加载 → 渲染流程

100%
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 根据当前语言返回对应翻译。

📖 小节


📝 作业

  1. 基础题(难度⭐):安装 @nuxtjs/i18n,配置中英两种语言,实现基础 UI 翻译
  2. 进阶题(难度⭐⭐):添加日文语言 + 语言切换器 + localePath 路由翻译,验证 hreflang 标签
  3. 挑战题(难度⭐⭐⭐):实现 useLocalizedPrice Composable,支持 USD/CNY/JPY 三种货币自动转换和格式化,商品页根据语言显示对应货币价格

---|

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏