返回

你用 Next.js i18n 的方式错了

General Translation avatarGeneral Translation
Ernest McCarter avatarErnest McCarter
guideinternationalizationnextjsi18ngt-nextapp-routertutorial

JavaScript 里的国际化已经形成了一种有缺陷的惯例:把所有面向用户的字符串都抽取到一个 JSON 文件中,为它们分配 key,然后在组件里引用这些 key。用 t('home.hero.title') 代替文本本身。你的 UI 在一处,你的内容在另一处。

这种做法当然是可行的。成千上万的应用都是这么发布的。但这并不是很好的开发体验。

在代码评审里看到 t('checkout.summary.total') 几乎什么都看不出来——你得打开一个 JSON 文件才能知道哪里变了。Key 得被想出来、划分命名空间并保持同步。过期的翻译会不断堆积,因为没人知道哪些 key 还在被使用。这个问题足够普遍,以至于专门为此诞生了一整类工具:自动补全 key 的 IDE 插件、用来校验 key 的类型生成器、用来标记未使用 key 的 linter。这些工具解决的是一个由糟糕范式设计创造出来的问题。

<T> 组件

文案不应该脱离它的使用位置。一个渲染标题、段落和按钮的组件,本身就应该是这些元素文案的唯一事实来源,而不是去代理某个存放在别处的字符串。当代码和翻译变成两套平行系统时,它们迟早会产生偏差——这是必然的。

如果这个库反过来工作——由它来适配你的代码,而不是要求你去重构代码,会怎样?这才是 i18n 应该有的样子。

import { T } from 'gt-next';

function Hero() {
  return (
    <T>
      <h1>将您的产品推向全球</h1>
      <p>无需重写应用即可覆盖全球市场。</p>
    </T>
  );
}

将 JSX 包裹在 <T> 组件中。英文文案就写在代码里原本的位置即可。当用户以西班牙语或日语访问时,<T> 内的内容会被自动翻译——连同结构和格式一并处理。

无需 key。无需 JSON 文件。无需交叉引用。你的代码就是唯一的事实来源。

设置

上面的语法出自 gt-next,一款用于 Next.js App Router 的开源 i18n 库。要开始使用,只需运行一条命令:

npx gtx-cli@latest init

设置向导 会安装依赖项,用 withGTConfig 包装你的 Next.js 配置,把 GTProvider 添加到根布局中,创建包含语言环境(locale)列表的 gt.config.json,为翻译热重载设置开发环境 API 密钥,并以交互方式配置基于 CDN(内容分发网络)的翻译存储——以上步骤都会通过交互方式完成。

完成这些步骤后,将内容包裹在 <T> 中,运行开发服务器,并使用 <LocaleSelector> 组件在不同语言之间切换:

import { LocaleSelector } from 'gt-next';

function Header() {
  return (
    <header>
      <nav>{/* ... */}</nav>
      <LocaleSelector />
    </header>
  );
}

在开发环境中会按需执行翻译,因此你可以立即以任意语言查看你的应用。

部署

在生产环境中,译文会预先生成。

  1. dash.generaltranslation.com 获取生产环境 API 密钥。生产密钥以 gtx-api- 开头(不同于本地使用的 gtx-dev- 密钥)。

  2. 在构建流程中添加 translate 步骤:

{
  "scripts": {
    "build": "npx gtx-cli translate --publish && next build"
  }
}

translate 命令会扫描你的代码库中所有 <T> 的使用,生成翻译并发布到 CDN(内容分发网络)。当你的应用构建时,每个语言环境都已就绪。

后续步骤