React 国际化 i18n 完整方案:从文案抽取到动态语言切换

引言

国际化(i18n)是面向全球用户的 React 应用的「隐形工程」:不只是翻译几个按钮文案,还涉及日期、数字、货币、复数、时区、排版方向(RTL)与搜索引擎优化。本文将从 i18n 的复杂度模型讲起,系统梳理 React 国际化的标准流程:文案组织、react-i18next 接入、动态语言切换、服务端渲染的国际化、本地化(l10n)细节与自动化工具链。

一、国际化的复杂度模型

「把字符串抽出来翻译」只是国际化的冰山一角。一个完整的 i18n 体系需要处理四类差异:文本、数字、日期与排版。

国际化差异全景

flowchart TB
  classDef i1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
  classDef i2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
  classDef i3 fill:#e0f7fa,stroke:#00838f,color:#004d40

  A[i18n 差异模型] --> B[文本翻译<br/>文案资源]
  A --> C[数字格式化<br/>千分位/小数点]
  A --> D[日期时间<br/>时区/历法]
  A --> E[复数规则<br/>单复数形态]
  A --> F[排版方向<br/>LTR / RTL]

  B --> B1[多语言资源文件]
  C --> C1[Intl.NumberFormat]
  D --> D1[Intl.DateTimeFormat]
  E --> E1[plural 规则映射]
  F --> F1[dir 属性切换]

  click C1 "https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat" "Intl 文档"
  class A i1
  class B i1
  class C i2
  class D i2
  class E i2
  class F i2
  class B1 i3
  class C1 i3
  class D1 i3
  class E1 i3
  class F1 i3

常见的国际化误区

误区 后果 正确做法
硬编码字符串 无法翻译 全部走 i18n 资源
拼接句子「您有 N 条消息」 语序无法调整 用插值 + 复数
只翻 UI 不翻日期 体验割裂 Intl 格式化
忽略 RTL 阿拉伯语排版错乱 dir 属性 + 逻辑样式

二、文案组织:资源文件的设计

i18n 的基石是资源文件(翻译键值对)。组织方式直接影响维护成本与协作效率。

资源结构设计

flowchart LR
  classDef r1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
  classDef r2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
  classDef r3 fill:#e0f7fa,stroke:#00838f,color:#004d40

  A[locales 目录] --> B[zh-CN 中文]
  A --> C[en-US 英文]
  A --> D[ja-JP 日文]
  B --> E[common.json<br/>通用文案]
  B --> F[posts.json<br/>文章模块]
  B --> G[admin.json<br/>控制台]
  E --> H[命名空间 key 化]

  click H "https://react.i18next.com/latest/using-with-hooks" "i18next 使用文档"
  class A r1
  class B r2
  class C r2
  class D r2
  class E r3
  class F r3
  class G r3
  class H r2

资源文件示例

设计原则:按模块分文件(命名空间)、按功能分 key、插值用 {{var}} 占位、复数用 _plural 后缀——这些约定让翻译协作可控。

三、react-i18next 接入实战

react-i18next 是 React 生态最成熟的 i18n 方案,基于 i18next 核心,提供 useTranslation Hook 与 Trans 组件。

初始化流程

flowchart TB
  classDef e1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
  classDef e2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
  classDef e3 fill:#e0f7fa,stroke:#00838f,color:#004d40

  A[i18n 初始化] --> B[加载资源文件]
  B --> C[设置默认语言]
  C --> D{语言检测}
  D -->|浏览器| E[languageDetector]
  D -->|URL 参数| F[路由参数]
  D -->|存储| G[localStorage]
  E --> H[实例化 i18next]
  F --> H
  G --> H
  H --> I[I18nextProvider 包裹]
  I --> J[组件 useTranslation]

  click E "https://github.com/i18next/i18next-browser-languageDetector" "语言检测插件"
  class A e1
  class B e1
  class C e1
  class D e2
  class E e3
  class F e3
  class G e3
  class H e2
  class I e2
  class J e3

组件中使用

四、动态语言切换与持久化

语言切换不是「换一套文案」这么简单——它需要同时更新:当前语言、UI 渲染、日期格式、文档方向(dir)与 SEO 链接。

语言切换流程

sequenceDiagram
  participant U as 用户
  participant S as 切换按钮
  participant I as i18next
  participant D as 文档

  U->>S: 点击「English」
  S->>I: changeLanguage('en-US')
  I->>I: 加载 en-US 资源
  I->>I: 触发语言变化事件
  I->>D: 更新 lang 属性
  I->>D: 更新 dir 属性(若 RTL)
  D->>D: 重渲染全部消费组件
  Note over D: 更新本地存储与 URL

持久化与 SEO 的配合

flowchart LR
  classDef s1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
  classDef s2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
  classDef s3 fill:#e0f7fa,stroke:#00838f,color:#004d40

  A[语言选择] --> B[localStorage 持久化]
  B --> C[下次访问直接命中]
  A --> D[URL 路径前缀<br/>/zh-cn/ /en/]
  D --> E[SEO 友好 可分享]
  D --> F[浏览器语言兜底]

  click D "https://www.i18next.com/principles/design" "i18next 设计原则"
  class A s1
  class B s2
  class C s3
  class D s2
  class E s3
  class F s3

实践建议:SSR 应用用「URL 前缀 + 服务端协商」方案(如 /zh-cn/posts),对 SEO 与缓存最友好;纯 SPA 用 localStorage + 浏览器检测即可。

五、数字、日期与复数的本地化

即使文案全部翻译,日期与数字格式的错误同样会让用户感到「这不是给我的应用」。Intl API 是本地化的标准答案。

Intl 格式化

flowchart TD
  classDef f1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
  classDef f2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
  classDef f3 fill:#e0f7fa,stroke:#00838f,color:#004d40

  A[本地化格式化] --> B[数字<br/>Intl.NumberFormat]
  A --> C[日期<br/>Intl.DateTimeFormat]
  A --> D[货币<br/>Intl.NumberFormat 货币样式]
  A --> E[复数<br/>i18next plural]

  B --> B1[中文 1,234.5<br/>德语 1.234,5]
  C --> C1[中文 2024年1月1日<br/>英文 Jan 1, 2024]
  D --> D1[¥1,234 / $1,234]
  E --> E1[1 条消息 / 2 条消息]

  click C1 "https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat" "DateTimeFormat 文档"
  class A f1
  class B f1
  class C f1
  class D f1
  class E f1
  class B1 f3
  class C1 f3
  class D1 f3
  class E1 f3

React 中的格式化组件

六、RTL 排版与语言切换的视觉适配

阿拉伯语、希伯来语等从右向左排版。RTL 的正确实现依赖逻辑属性而非物理属性。

RTL 适配策略

flowchart TB
  classDef t1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
  classDef t2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
  classDef t3 fill:#e0f7fa,stroke:#00838f,color:#004d40

  A[RTL 适配] --> B[文档级<br/>html dir=rtl]
  A --> C[样式级<br/>逻辑属性]
  A --> D[组件级<br/>图标与箭头方向]

  B --> B1[浏览器自动镜像布局]
  C --> C1[margin-inline-start 替代 margin-left]
  C --> C2[inset-inline 替代 left/right]
  D --> D1[箭头图标按 dir 翻转]

  click C1 "https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin-inline-start" "逻辑属性文档"
  class A t1
  class B t1
  class C t2
  class D t2
  class B1 t3
  class C1 t3
  class C2 t3
  class D1 t3

要点:布局尽量使用逻辑属性(inline/block 系);图标方向性(箭头、chevron)用 CSS 按 [dir="rtl"] 翻转;文本对齐用 text-align: start

七、自动化工具链

i18n 工程化的最后一块拼图是自动化:文案抽取、缺失检查、翻译协作与 CI 门禁。

工具链架构

flowchart LR
  classDef a1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
  classDef a2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
  classDef a3 fill:#e0f7fa,stroke:#00838f,color:#004d40

  A[源码中的翻译调用] --> B[自动抽取<br/>i18next-parser]
  B --> C[资源文件更新]
  C --> D{缺失检查}
  D -->|翻译缺失| E[CI 拦截]
  D -->|齐全| F[构建通过]
  C --> G[翻译平台同步<br/>Crowdin / Lokalise]

  click B "https://github.com/i18next/i18next-parser" "i18next-parser 文档"
  class A a1
  class B a1
  class C a2
  class D a2
  class E a3
  class F a3
  class G a2

推荐实践:i18next-parser 自动从源码抽取 key;CI 中检查「新增 key 是否所有语言都有翻译」;翻译平台(Crowdin 等)对接外部译者工作流。

八、总结

React 国际化的完整体系可以浓缩为五层:

  1. 文案层:资源文件按命名空间组织,插值与复数约定清晰;
  2. 接入层:react-i18next 初始化 + useTranslation 使用;
  3. 切换层:changeLanguage + 持久化 + URL 前缀(SSR/SEO);
  4. 本地化层:Intl 格式化数字日期货币 + RTL 逻辑属性;
  5. 工程化层:自动抽取、缺失门禁、翻译平台协作。

实践建议:国际化要「早做」,不要等项目写完再补——抽文案的成本随项目膨胀指数增长;从第一天就用 t() 包裹所有用户可见文本。下一篇我们将探讨 React 无障碍访问 a11y 的完整实践。