返回

你把 Next.js i18n 做错了

Team avatarTeam
Ernest McCarter avatarErnest McCarter
guideinternationalizationnextjsi18ngt-nextapp-routertutorial

JavaScript 中的国际化已经形成了一种有缺陷的惯例:把所有面向用户的字符串都提取到 JSON 文件里,给它们分配一个键,再在组件中引用这个键。也就是写 t('home.hero.title'),而不是直接写文本本身。你的 UI 在一个地方,内容却在另一个地方。

这种做法确实可行。成千上万的应用都是这样上线的。但它并不能带来良好的开发者体验。

在代码审查里看到 t('checkout.summary.total'),你根本不知道它对应的是什么——必须打开 JSON 文件才能看出改了什么。键名需要人为设计、加命名空间,还得保持同步。过时的翻译会不断堆积,因为没人知道哪些键还在被使用。这个问题普遍到什么程度?甚至已经催生出了一整类专门管理它的工具:自动提示键名的 IDE 扩展、校验键名的类型生成器、标记未使用键的 linter。这些工具,本质上是在解决一个由糟糕范式设计出来的问题。

<T> 组件

内容不应脱离其使用位置。一个负责渲染标题、段落和按钮的组件,应该是这些元素文案的唯一真实来源——而不该只是一个指向别处所存字符串的中转层。当代码和翻译变成两套并行系统时,它们迟早会偏离。无一例外。

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

import { T } from 'gt-next';

function Hero() {
  return (
    <T>
      <h1>Ship your product worldwide</h1>
      <p>Reach every market without rewriting your app.</p>
    </T>
  );
}

将你的 JSX 包裹在 <T> 中。英文文本会保留在你写下的位置。当用户以西班牙语或日语访问时,<T> 内的内容就会被翻译——包括结构、格式等所有内容。

无需键。无需 JSON 文件。无需交叉引用。你的代码就是真实来源。

设置

上述语法来自 gt-next,它是一个面向 Next.js App Router 的开源 i18n 库。只需一条命令即可开始使用:

npx gt@latest init

设置向导会安装依赖项,用withGTConfig包装你的 Next.js 配置,将GTProvider添加到根布局中,创建包含你的区域设置的gt.config.json,为翻译热重载设置开发 API 密钥,并配置 CDN 翻译存储——全程都可通过交互方式完成。

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

import { LocaleSelector } from 'gt-next';

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

在开发环境中,翻译会 on-demand 进行,因此你可以立即查看应用在任何语言下的效果。

部署

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

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

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

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

translate 命令会扫描你的代码库中所有 <T> 的用法,生成翻译并发布到 CDN。这样,在应用构建时,所有区域设置都已准备就绪。

后续步骤