国际化(i18n)多语言支持

当你的产品走向全球市场,国际化就从"锦上添花"变成了"必须具备"。但 i18n 远不只是"把文字翻译成英文"——它涉及路由策略、语言检测、日期/数字格式化、RTL 布局、SEO 优化、翻译工作流等一系列复杂问题。Next.js App Router 重塑了 i18n 的实现方式,本章从架构到细节,系统讲解如何在 Next.js 中构建生产级的多语言支持。

1. 国际化的本质问题

1.1 i18n vs L10n

两个经常混淆的概念:

概念全称含义类比
i18nInternationalization让应用能够支持多语言的技术架构造一辆左右舵都能开的车
L10nLocalization将应用适配到特定语言/文化的过程把方向盘装到右边(日本市场)

i18n 是开发者的工作(技术架构),L10n 是翻译者/运营的工作(内容适配)。两者需要同时进行但职责分明。

1.2 国际化的五个维度

维度内容示例
文本翻译UI 文案、按钮、标签"Submit" → "提交"
日期/时间日期格式、时区"4/12/2025" vs "2025-04-12"
数字/货币千分位、小数点、货币符号"$1,234.56" vs "¥1,234.56"
布局方向从左到右(LTR)vs 从右到左(RTL)英语 LTR,阿拉伯语 RTL
文化适配颜色含义、图标意义、内容合规红色在中国是喜庆,在西方可能是警告

1.3 Next.js App Router 的 i18n 变化

Next.js Pages Router 有内置的 i18n 路由支持(next.config.js 中的 i18n 配置)。但 App Router 移除了这个内置功能——取而代之的是更灵活的基于文件系统的方案。

特性Pages RouterApp Router
内置 i18n 路由next.config.js 配置❌ 需要手动实现或用库
语言前缀路由自动处理[locale] 动态段
语言检测自动(Accept-Language)Middleware 手动实现
Server ComponentsN/A✅ 完全支持
翻译加载客户端为主服务端加载(零客户端 JS)

好消息:App Router 的 i18n 虽然没有内置,但与 Server Components 的结合让翻译可以完全在服务端完成——零额外客户端 JavaScript,这在性能上是巨大的提升。

2. 方案选型

2.1 主流 i18n 方案对比

方案类型App Router 支持RSC 支持特点
next-intl专为 Next.js⭐⭐⭐⭐⭐✅ 原生2025 年首选,API 最优雅
next-i18nextNext.js 适配⭐⭐⭐⚠️ 需要适配Pages Router 时代的标准
react-i18nextReact 通用⭐⭐⭐⚠️ 需要适配生态最大,但不专为 Next.js
lingui编译时⭐⭐⭐⭐编译时提取,ICU 标准
Paraglide编译时⭐⭐⭐⭐极小 Bundle,Treeshaking
自建简单场景够用

2.2 选型建议

新项目 + App Router? → next-intl(2025 年无脑选)
已有 react-i18next? → 继续用,逐步迁移
追求最小 Bundle? → Paraglide
需要 ICU MessageFormat? → lingui
只有 2-3 种语言 + 简单文案? → 可以自建

为什么 next-intl 是首选

  1. 专为 App Router 设计——不是从 Pages Router 迁移过来的
  2. Server Components 原生支持——翻译在服务端完成,零客户端 JS
  3. 类型安全——翻译键自动补全
  4. 路由国际化内置——[locale] 路由 + Middleware 语言检测
  5. ICU MessageFormat——支持复数、性别、日期等复杂格式
  6. 活跃维护——作者 amannn 持续更新

3. 路由策略

3.1 三种路由模式

模式URL 示例优点缺点
路径前缀/en/about, /zh/aboutSEO 最友好,最明确URL 变长
子域名en.example.com, zh.example.com域名级别隔离DNS 配置复杂
Cookie/Header/about(根据 Cookie 切换语言)URL 简洁SEO 不友好,缓存困难

推荐:路径前缀模式。原因:

  • 搜索引擎可以明确区分不同语言的页面
  • 用户可以通过 URL 知道当前语言
  • 方便分享特定语言的链接
  • CDN 缓存友好(不同语言是不同 URL)

3.2 默认语言的 URL 处理

一个常见的选择:默认语言是否需要前缀?

策略默认语言(中文)URL其他语言(英文)URL适用场景
全部有前缀/zh/about/en/about一致性强,推荐
默认无前缀/about/en/aboutURL 简洁,但需要重定向

推荐全部有前缀——虽然 URL 多了一层,但逻辑统一,不需要处理"有前缀"和"无前缀"两套路由。

3.3 App Router 中的路由实现

使用 [locale] 动态段实现语言路由:

app/
├── [locale]/
│   ├── layout.tsx          ← 设置 html lang 属性
│   ├── page.tsx            ← 首页
│   ├── about/
│   │   └── page.tsx        ← 关于页
│   └── blog/
│       ├── page.tsx        ← 博客列表
│       └── [slug]/
│           └── page.tsx    ← 博客详情
└── not-found.tsx

所有页面都在 [locale] 目录下,locale 参数自动传递给每个页面组件。

3.4 Middleware 语言检测

Middleware 负责两件事:

  1. 语言检测:根据 Accept-Language 头、Cookie、URL 判断用户语言
  2. 路由重定向:将无前缀的 URL 重定向到正确的语言版本

语言检测优先级(从高到低):

1. URL 中的语言前缀(/en/about → 英文)
2. Cookie 中保存的语言偏好
3. Accept-Language 请求头
4. 默认语言(fallback)

4. 翻译文件管理

4.1 文件组织结构

按语言组织(推荐):

messages/
├── zh.json          ← 中文
├── en.json          ← 英文
├── ja.json          ← 日文
└── ko.json          ← 韩文

按模块 + 语言组织(大型项目):

messages/
├── zh/
│   ├── common.json
│   ├── auth.json
│   ├── dashboard.json
│   └── settings.json
├── en/
│   ├── common.json
│   ├── auth.json
│   ├── dashboard.json
│   └── settings.json

4.2 翻译键的命名规范

规范示例适用场景
嵌套结构auth.login.title模块化清晰
扁平结构auth_login_title简单项目
页面级别HomePage.hero.title按页面组织

推荐嵌套结构

{
  "common": {
    "submit": "提交",
    "cancel": "取消",
    "loading": "加载中..."
  },
  "auth": {
    "login": {
      "title": "登录",
      "email": "邮箱",
      "password": "密码",
      "forgot": "忘记密码?"
    }
  },
  "dashboard": {
    "welcome": "欢迎回来,{name}",
    "stats": {
      "users": "{count, plural, =0 {没有用户} =1 {1 个用户} other {# 个用户}}"
    }
  }
}

4.3 ICU MessageFormat

ICU MessageFormat 是国际化文本格式的国际标准,支持:

变量插值

"welcome": "欢迎,{name}"
→ 欢迎,张三

复数处理

"items": "{count, plural, =0 {没有项目} =1 {1 个项目} other {# 个项目}}"
→ 没有项目 / 1 个项目 / 5 个项目

性别处理

"pronoun": "{gender, select, male {他} female {她} other {Ta}}"

日期/数字格式

"date": "发布于 {date, date, medium}"
"price": "价格:{price, number, currency}"

为什么推荐 ICU MessageFormat

  • 国际标准(Unicode CLDR)
  • 翻译人员熟悉
  • 处理各语言的复数规则(英语 2 种,阿拉伯语 6 种)
  • 工具链支持好(翻译平台、Lint 工具)

4.4 翻译文件的类型安全

没有类型安全的翻译键是灾难——改了 JSON 中的键名,代码中不会报错,直到运行时才发现翻译缺失。

next-intl 提供类型安全:

  1. 定义翻译消息的类型
  2. global.d.ts 中声明
  3. 使用 useTranslations 时自动补全键名

这意味着:

  • 翻译键拼写错误 → 编译时报错
  • 重命名翻译键 → IDE 自动重构
  • 缺失翻译 → TypeScript 提示

5. Server Components 中的 i18n

5.1 零客户端 JS 的翻译

App Router 最大的 i18n 优势:翻译可以完全在服务端完成

Pages Router 时代:
  翻译 JSON → 打包到客户端 → 运行时加载 → 渲染
  问题:翻译文件增加 Bundle 大小

App Router 时代:
  翻译 JSON → Server Component 直接读取 → 服务端渲染 → 发送 HTML
  优势:翻译 JSON 不会出现在客户端 Bundle 中

性能影响:对于一个有 1000 个翻译键、支持 5 种语言的应用,Pages Router 需要将翻译文件发送到客户端(可能 50-100KB),App Router 中这些完全是零客户端开销。

5.2 Server vs Client Components 中的翻译

场景组件类型翻译方式
页面标题、段落文本Server Component直接调用翻译函数
表单标签、静态文案Server Component直接调用翻译函数
交互反馈(Toast、确认框)Client Component通过 Provider 获取翻译
动态内容(搜索建议)Client Component通过 Provider 获取翻译

原则:尽量在 Server Components 中完成翻译,只在需要交互的 Client Components 中使用客户端翻译。

5.3 generateMetadata 的国际化

SEO 相关的 metadata 也需要国际化:

/zh/about → <title>关于我们</title>
/en/about → <title>About Us</title>

generateMetadata 中根据 locale 参数加载对应语言的翻译,生成正确的 title、description 和 Open Graph 信息。

同时需要设置 alternates.languages 告诉搜索引擎不同语言版本的 URL 关系:

<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/zh/about" />

6. 日期、数字与格式化

6.1 日期格式化

不同地区的日期格式差异巨大:

地区格式示例
中国yyyy年M月d日2025年4月12日
美国M/d/yyyy4/12/2025
英国d/M/yyyy12/4/2025
日本yyyy/M/d2025/4/12
德国d.M.yyyy12.4.2025

不要手动拼接日期字符串——使用 Intl.DateTimeFormat 或 i18n 库的格式化功能,它们会根据 locale 自动选择正确的格式。

6.2 数字与货币格式化

地区数字格式货币格式
中国1,234.56¥1,234.56
美国1,234.56$1,234.56
德国1.234,561.234,56 €
印度1,23,456.78₹1,23,456.78

注意德国用逗号做小数点、点做千分位,印度的千分位分隔规则也不同。

6.3 相对时间

"3 分钟前"、"昨天"、"上周" 在不同语言中的表达:

语言3 分钟前昨天
中文3 分钟前昨天
英文3 minutes agoyesterday
日文3 分前昨日
韩文3분 전어제

使用 Intl.RelativeTimeFormat 或 i18n 库的相对时间功能。

7. RTL(从右到左)支持

7.1 哪些语言需要 RTL

语言方向使用人口
阿拉伯语RTL~4 亿
希伯来语RTL~0.9 亿
波斯语RTL~1.1 亿
乌尔都语RTL~2.3 亿

如果你的产品面向中东市场,RTL 支持是必须的。

7.2 RTL 适配要点

要点说明Tailwind 实现
文本方向direction: rtldir="rtl" on &lt;html&gt;
布局翻转flexbox/grid 自动翻转大部分自动处理
内边距padding-leftpadding-rightps-4 / pe-4(logical properties)
外边距margin-leftmargin-rightms-4 / me-4
图标方向箭头等方向性图标需要翻转rtl:rotate-180
圆角border-radius 左右互换rounded-s-lg / rounded-e-lg

7.3 CSS Logical Properties

CSS Logical Properties 是 RTL 支持的基础——用"逻辑方向"(start/end)替代"物理方向"(left/right):

物理属性逻辑属性LTR 效果RTL 效果
margin-leftmargin-inline-start左侧右侧
margin-rightmargin-inline-end右侧左侧
padding-leftpadding-inline-start左侧右侧
text-align: lefttext-align: start左对齐右对齐
float: leftfloat: inline-start左浮动右浮动

Tailwind CSS v4 默认支持 logical properties:ps-4(padding-start)、me-2(margin-end)。

8. 翻译工作流

8.1 开发者 vs 翻译者的职责边界

职责开发者翻译者
设计翻译键结构
编写默认语言文案
翻译其他语言
Review 翻译质量协助
处理上下文不足提供截图/说明反馈疑问

8.2 翻译平台

平台特点价格适用场景
Crowdin最主流、GitHub 集成好免费(开源)开源项目
Lokalise开发者体验好付费商业项目
Phrase企业级、工作流完善付费大型团队
Transifex社区翻译支持好免费层社区翻译
AI 翻译GPT-4 / DeepL API按用量初始翻译 + 人工校对

8.3 翻译工作流自动化

开发者添加/修改翻译键
    ↓
PR 合并到 main
    ↓
CI 自动同步到翻译平台(Crowdin/Lokalise)
    ↓
翻译者在平台中翻译
    ↓
翻译完成 → 平台自动创建 PR(包含翻译文件更新)
    ↓
开发者 Review + 合并
    ↓
部署上线

这个流程实现了:

  • 开发者不需要手动管理翻译文件
  • 翻译者不需要接触代码
  • 翻译进度可追踪
  • 缺失翻译自动检测

8.4 AI 辅助翻译策略

2025 年的实际做法:AI 初始翻译 + 人工校对

步骤工具质量
初始翻译GPT-4 / DeepL API80-90% 可用
术语统一术语表(Glossary)确保专业词汇一致
人工校对翻译者/母语者修正语境和语气
持续维护AI + 人工混合新增文案用 AI,定期人工审查

9. SEO 与搜索引擎优化

9.1 多语言 SEO 清单

检查项说明实现方式
hreflang 标签告诉搜索引擎不同语言版本&lt;link rel="alternate"&gt;
lang 属性HTML 文档语言声明&lt;html lang="zh"&gt;
✅ 语言化 URL不同语言使用不同 URL/zh/about vs /en/about
✅ 翻译 metadatatitle、description 需要翻译generateMetadata
✅ Open Graph 翻译社交分享卡片需要翻译og:title, og:description
✅ Sitemap 多语言每种语言的 URL 都要包含sitemap.xml 生成
✅ 规范 URL避免重复内容canonical + hreflang

9.2 hreflang 详解

hreflang 告诉搜索引擎:"这个页面有多个语言版本,请根据用户语言展示正确的版本。"

<!-- 在 /zh/about 页面中 -->
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="ja" href="https://example.com/ja/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/zh/about" />

x-default 指定当用户语言不匹配任何版本时的默认页面。

9.3 多语言 Sitemap

<url>
  <loc>https://example.com/zh/about</loc>
  <xhtml:link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
  <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about" />
</url>
<url>
  <loc>https://example.com/en/about</loc>
  <xhtml:link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
  <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about" />
</url>

10. 常见陷阱与最佳实践

10.1 常见陷阱

陷阱问题解决方案
硬编码文本代码中直接写中文/英文所有文本都走翻译系统
拼接翻译t('hello') + name用 ICU 变量 &#123;name&#125;
假设文本长度翻译后可能变长/变短弹性布局,不固定宽度
忽略复数规则英语 2 种,阿拉伯语 6 种用 ICU plural
未翻译的图片图片中的文字无法翻译用 CSS/SVG 而非图片中的文字
忽略时区服务端和客户端时区不一致始终用 UTC 存储,展示时转换

10.2 最佳实践

  1. 一开始就考虑 i18n——后期改造成本是前期的 10 倍
  2. 翻译键用英文描述而非编号——auth.login.title 而非 key_001
  3. 提供翻译上下文——给翻译者截图或说明,避免歧义
  4. 不要过度翻译——品牌名、产品名、技术术语可以保留原文
  5. 测试极端情况——德语单词特别长,日语没有空格分词
  6. 自动检测缺失翻译——CI 中检查所有语言的翻译完整性

本章小结

  • 方案选型:2025 年 next-intl 是 Next.js App Router 的首选 i18n 方案
  • 路由策略:路径前缀模式(/zh/about)SEO 最友好,推荐所有语言都有前缀
  • Server Components 优势:翻译在服务端完成,零客户端 JS 开销
  • 翻译文件管理:嵌套 JSON 结构 + ICU MessageFormat + TypeScript 类型安全
  • 格式化:日期、数字、货币使用 Intl API,不要手动拼接
  • RTL 支持:使用 CSS Logical Properties(ps-4/me-2),Tailwind v4 原生支持
  • 翻译工作流:AI 初始翻译 + 人工校对 + 翻译平台自动化同步
  • SEO 关键hreflang 标签 + 翻译 metadata + 多语言 Sitemap
  • 核心原则:一开始就设计 i18n 架构,后期改造成本极高