⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

3.8.4 国际化 i18n

一句话破题

国际化是让代码支持多语言的架构,而不是把中文翻译成英文那么简单。

核心价值

国际化 (i18n = internationalization, 首尾字母间18个字母) 是把文本从代码中抽离出来,让产品能够支持任意语言,而无需修改代码。

Next.js + next-intl 方案

bash
npm install next-intl

目录结构

src/
├── app/
│   └── [locale]/
│       ├── layout.tsx
│       └── page.tsx
├── messages/
│   ├── en.json
│   └── zh.json
└── i18n.ts

配置文件

ts
// i18n.ts
import { getRequestConfig } from 'next-intl/server'

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`./messages/${locale}.json`)).default
}))

// middleware.ts
import createMiddleware from 'next-intl/middleware'

export default createMiddleware({
  locales: ['en', 'zh'],
  defaultLocale: 'zh'
})

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)']
}

翻译文件

json
// messages/zh.json
{
  "common": {
    "submit": "提交",
    "cancel": "取消",
    "loading": "加载中...",
    "error": "出错了"
  },
  "home": {
    "title": "欢迎来到我们的网站",
    "description": "这是一个示例描述"
  },
  "auth": {
    "login": "登录",
    "logout": "退出",
    "welcome": "欢迎回来,{name}!"
  }
}

// messages/en.json
{
  "common": {
    "submit": "Submit",
    "cancel": "Cancel",
    "loading": "Loading...",
    "error": "Something went wrong"
  },
  "home": {
    "title": "Welcome to our website",
    "description": "This is a sample description"
  },
  "auth": {
    "login": "Login",
    "logout": "Logout",
    "welcome": "Welcome back, {name}!"
  }
}

在组件中使用

tsx
// app/[locale]/page.tsx
import { useTranslations } from 'next-intl'

export default function HomePage() {
  const t = useTranslations('home')
  const tCommon = useTranslations('common')
  
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
      <button>{tCommon('submit')}</button>
    </div>
  )
}

// 带参数
function WelcomeMessage({ name }: { name: string }) {
  const t = useTranslations('auth')
  
  return <p>{t('welcome', { name })}</p>
  // 输出: "欢迎回来,张三!" 或 "Welcome back, Zhang San!"
}

复数和性别

json
// messages/en.json
{
  "cart": {
    "items": "{count, plural, =0 {No items} one {# item} other {# items}}",
    "total": "Total: {price}"
  }
}

// messages/zh.json
{
  "cart": {
    "items": "{count}件商品",
    "total": "总计: {price}"
  }
}
tsx
function CartSummary({ count, total }: { count: number; total: string }) {
  const t = useTranslations('cart')
  
  return (
    <div>
      <p>{t('items', { count })}</p>
      <p>{t('total', { price: total })}</p>
    </div>
  )
}
// count=0: "No items" / "0件商品"
// count=1: "1 item" / "1件商品"  
// count=5: "5 items" / "5件商品"

日期和数字格式化

tsx
import { useFormatter } from 'next-intl'

function FormattedContent() {
  const format = useFormatter()
  
  const date = new Date()
  const price = 1234.56
  
  return (
    <div>
      {/* 日期格式化 */}
      <p>{format.dateTime(date, { dateStyle: 'long' })}</p>
      {/* en: "January 15, 2024" / zh: "2024年1月15日" */}
      
      {/* 相对时间 */}
      <p>{format.relativeTime(date)}</p>
      {/* en: "2 hours ago" / zh: "2小时前" */}
      
      {/* 数字格式化 */}
      <p>{format.number(price, { style: 'currency', currency: 'CNY' })}</p>
      {/* en: "CN¥1,234.56" / zh: "¥1,234.56" */}
    </div>
  )
}

语言切换

tsx
'use client'

import { useLocale } from 'next-intl'
import { useRouter, usePathname } from 'next/navigation'

function LanguageSwitcher() {
  const locale = useLocale()
  const router = useRouter()
  const pathname = usePathname()
  
  const switchLocale = (newLocale: string) => {
    const newPath = pathname.replace(`/${locale}`, `/${newLocale}`)
    router.push(newPath)
  }
  
  return (
    <select 
      value={locale} 
      onChange={(e) => switchLocale(e.target.value)}
      aria-label="选择语言"
    >
      <option value="zh">中文</option>
      <option value="en">English</option>
    </select>
  )
}

服务端组件中使用

tsx
// app/[locale]/page.tsx
import { getTranslations } from 'next-intl/server'

export async function generateMetadata({ params: { locale } }) {
  const t = await getTranslations({ locale, namespace: 'home' })
  
  return {
    title: t('title'),
    description: t('description'),
  }
}

export default async function Page() {
  const t = await getTranslations('home')
  
  return <h1>{t('title')}</h1>
}

翻译键管理

tsx
// 使用 TypeScript 类型安全
// 创建类型定义

// types/i18n.d.ts
import en from '../messages/en.json'

type Messages = typeof en

declare global {
  interface IntlMessages extends Messages {}
}

AI 协作指南

核心意图:让 AI 帮你实现国际化架构。

需求定义公式

  • 框架选择:Next.js + [next-intl/react-i18next]
  • 支持语言:[中文/英文/日文...]
  • 功能需求:[日期格式化/复数/性别]

示例 Prompt

请帮我设置 Next.js + next-intl 国际化:
1. 支持中文和英文
2. 基于 URL 路径切换语言 (/zh, /en)
3. 包含语言切换组件
4. 配置日期和货币格式化
5. 提供完整的目录结构和配置文件

验收清单

  • [ ] 文本从代码中抽离
  • [ ] 支持语言切换
  • [ ] 日期/数字正确格式化
  • [ ] SEO 元数据多语言支持
  • [ ] 翻译键有 TypeScript 类型