# General Translation Knowledge Base: 应该自建 i18n，还是使用 General Translation？
URL: https://generaltranslation.com/zh/kb/general-translation-vs-in-house-i18n.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 手写的 i18n（每种语言一个字典，外加一个简单的 t() 辅助函数）足以应对一两种语言、少量文案的场景。随着应用规模扩大，你会发现自己在重新实现复数规则、后备内容和文件同步，而且译文仍然得自己产出。General Translation 的开源库负责渲染，General Translation 则根据你的代码生成并更新译文。
Category: 国际化
Last updated: 2026-10-16
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-16

国际化 (i18n) 完全可以自己实现：每种语言一个消息对象，一个按键查找并填入变量的 `t()` 辅助函数，一个区域设置切换器，再加上处理数字和日期的内置 `Intl` API。如果只有一两种语言、几十条字符串，这通常就够用了；让编程智能体添加一种语言时，它往往也会写出这样的代码。真正随应用一起膨胀的，是辅助函数之外的种种工作：复数规则、缺失的键、让每个语言文件与代码保持同步，以及最根本的——把每一条译文产出来。General Translation 的开源库承担其中运行时的那部分工作，General Translation 则根据你的代码生成并更新译文。

## 何时手写 i18n 就够用

* **无第三方依赖。** 字典和辅助函数都由你自己维护，每一行代码都在你的掌控之中。
* **格式化交给平台处理。** `Intl.NumberFormat`、`Intl.DateTimeFormat`、`Intl.PluralRules` 和 `Intl.Locale` 均[内置于现代浏览器和 Node.js](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) 中，可按区域设置格式化货币、日期并确定复数类别。
* **文案少且稳定。** 原型、内部工具，或只额外支持一种语言且由团队成员自行撰写译文的网站，用不着更复杂的方案。
* **复数规则与英语相同的语言。** 英语和德语都只使用 [`one` 和 `other`](https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html) 两种类别。

## 规模扩大后会出什么问题

下面是一个典型的初始版本，其中加入了波兰语字典：

```js title="i18n.mjs"
const messages = {
  en: {
    greeting: 'Hello, {name}!',
    files: { one: '{count} file', other: '{count} files' },
    checkout: 'Checkout',
  },
  pl: {
    greeting: 'Cześć, {name}!',
    files: { one: '{count} plik', other: '{count} pliki' },
    // “checkout” 尚未翻译
  },
};

export function t(locale, key, vars = {}) {
  let message = messages[locale][key];
  if (typeof message === 'object') {
    message = vars.count === 1 ? message.one : message.other;
  }
  return message.replace(/\{(\w+)\}/g, (_, name) => vars[name]);
}

console.log(t('pl', 'greeting', { name: 'Ania' }));
console.log([1, 2, 5].map((count) => t('pl', 'files', { count })).join(', '));
console.log(t('pl', 'checkout'));
```

使用 Node.js 24 运行：

```text title="Output"
Cześć, Ania!
1 plik, 2 pliki, 5 pliki
TypeError: Cannot read properties of undefined (reading 'replace')
```

这里同时出现了两个 bug。&quot;5 pliki&quot; 是错误的 (波兰语应为 &quot;5 plików&quot;) ，而未翻译的 `checkout` 键会导致渲染崩溃。平台已经提供了修复这两个问题所需的工具：

```js title="intl.mjs"
// 各语言的复数类别
for (const locale of ['en', 'ja', 'pl', 'ar']) {
  console.log(locale, new Intl.PluralRules(locale).resolvedOptions().pluralCategories.join(' '));
}
const pl = new Intl.PluralRules('pl');
console.log([1, 2, 5, 22].map((n) => `${n} → ${pl.select(n)}`).join(', '));

// 货币和日期
const date = new Date(Date.UTC(2026, 9, 16));
for (const locale of ['en-US', 'de-DE', 'ja-JP']) {
  const price = new Intl.NumberFormat(locale, { style: 'currency', currency: 'EUR' }).format(1234.5);
  const day = new Intl.DateTimeFormat(locale, { dateStyle: 'long', timeZone: 'UTC' }).format(date);
  console.log(locale, price, day);
}

// 文本方向
console.log(new Intl.Locale('ar').getTextInfo().direction);
```

```text title="Output"
en one other
ja other
pl one few many other
ar zero one two few many other
1 → one, 2 → few, 5 → many, 22 → few
en-US €1,234.50 October 16, 2026
de-DE 1.234,50 € 16. Oktober 2026
ja-JP €1,234.50 2026年10月16日
rtl
```

每处修复都不大，但积少成多：

* **复数形式。** `n === 1` 对波兰语 (四个类别) 和阿拉伯语 (六个类别) 并不适用，因此辅助函数需要借助 [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules)，而且每个字典都要为每个类别各提供一个字符串。 (参见[如何在 React 中处理复数形式？](/kb/how-to-handle-plurals-in-react)) 
* **语序。** 借助 `{name}` 这样的占位符，每种语言都可以自行调整值的位置；字符串拼接则做不到：`t('by') + ' ' + author` 会把名字固定放在末尾，而日语的写法是“Ana 著”。
* **格式化。** `{count}` 插入的是原始数字，因此辅助函数还需要 [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat)，才能在波兰语中将 12,500 显示为“12 500”。
* **缺失的键。** 辅助函数需要一条后备链 (依次为目标语言、源语言、键本身) ，否则用户会看到 `undefined`，甚至导致程序崩溃。
* **保持文件同步。** 没有任何机制会提醒你 `pl` 缺少 `checkout`，或者还停留在上个月的措辞，直到用户亲眼看到。
* **从右向左书写的语言。** 阿拉伯语和希伯来语需要在页面上设置 `lang` 和 `dir="rtl"`，并采用镜像布局。
* **搜索引擎。** 如果语言切换器把用户的选择保存在 state 或 cookie 中，所有语言就会共用同一个 URL，而 Google 建议每种语言使用独立的 URL ([管理多语言网站](https://developers.google.com/search/docs/specialty/international/managing-multi-regional-sites)) 。 (参见[如何为多语言 Next.js 应用设置 SEO？](/kb/multilingual-nextjs-seo)) 
* **译文本身。** 这项成本是任何辅助函数都省不掉的：总得有人写出“plików”以及每种语言中的其他所有字符串，而且每次英文内容变更，都得再来一遍。

等到辅助函数能处理复数类别、后备内容和数字格式化时，你其实已经写出了一个国际化库的核心，而译文依然得另行准备。

## General Translation 的做法

General Translation 保留了手动实现方式中行之有效的部分 (可读的文案和平台自带的格式化功能) ，同时接管那些会不断膨胀的部分。使用 `gt-react` (Next.js 项目则使用 `gt-next`) 时，文案直接写在组件内的 [`<T>`](/docs/react/reference/components/t) 中，因此无需维护任何字典或辅助函数：

```tsx title="Inbox.tsx"
import { T, Plural, Num, Var } from 'gt-react';

export function Inbox({ name, count }: { name: string; count: number }) {
  return (
    <T>
      <h1>Welcome back, <Var>{name}</Var></h1>
      <Plural
        n={count}
        singular={<p>You have <Num>{count}</Num> unread message.</p>}
        plural={<p>You have <Num>{count}</Num> unread messages.</p>}
      />
    </T>
  );
}
```

[`<Plural>`](/docs/react/reference/components/plural) 依据各区域设置的 CLDR 复数规则 (与 `Intl.PluralRules` 背后的规则相同) 选择分支，因此根本不需要手写 `n === 1` 这类容易出错的判断。[`<Num>`](/docs/react/reference/components/num) 会按读者的区域设置格式化数量，而 [`<Var>`](/docs/react/reference/components/var) 中的值永远不会发送到 General Translation API。下面是使用 `gt-react` 11.4.12 在服务器端渲染该组件的结果，其中的西班牙语翻译是为了演示而手动填入 [`gt generate`](/docs/cli/reference/commands/generate) 模板的 (已移除 React 的文本分隔符) ：

```html title="Output"
<!-- en, count = 12500 -->
<h1>Welcome back, Ana</h1><p>You have 12,500 unread messages.</p>
<!-- es, count = 12500 -->
<h1>Hola de nuevo, Ana</h1><p>Tienes 12.500 mensajes sin leer.</p>
```

当某个区域设置缺少某个块的译文时，[`<T>`](/docs/react/reference/components/t) 会渲染源内容，而不是返回 `undefined` 或报错。

更大的不同在于，译文由 General Translation 负责生成：

* **结合上下文生成。** [`npx gt translate`](/docs/cli/reference/commands/translate) 会将每个 [`<T>`](/docs/react/reference/components/t) 块连同其元素、变量、复数分支以及 [`context`](/docs/react/reference/components/t#context) 备注 (如有) 一并发送，并为每个区域设置写入一个文件。词汇表用于锁定术语，自定义提示词则为共享同一上下文分组的所有项目统一语气。 (参见[为翻译定义上下文](/docs/platform/dashboard/guides/defining-context-for-translations)。) 
* **保持同步。** 每次运行只翻译新增和变更的内容，其余部分直接复用已有译文。你可以在 CI 中运行它，也可以让 General Translation 的 AI 智能体 [Locadex](/docs/platform/locadex/quickstart) 按你设定的触发条件自动创建拉取请求。对于 Next.js 和 React 应用，Locadex 还能将现有文案包裹在 [`<T>`](/docs/react/reference/components/t) 中。
* **人工审校。** 你的团队可以在仪表板中编辑译文。 (参见[审校和编辑译文](/docs/platform/dashboard/guides/reviewing-translations)。) 
* **文本方向与路由。** [`useLocaleDirection`](/docs/react/reference/hooks/use-locale-direction) 对阿拉伯语返回 `'rtl'`；`gt-next` 则可借助其[中间件](/docs/react/nextjs/app-router-middleware)，为每个区域设置提供独立的 URL。

这些 JavaScript 库采用 MIT 许可证；只要自行提供译文，无需 General Translation 账户即可使用。如果应用配置为从应用包加载全部译文，则在运行时不会调用 General Translation。如需生成译文，可使用托管服务，按[用量计费](https://generaltranslation.com/pricing)。

而在规模的另一端，Profound 借助 General Translation 将其平台和文档发布到 47 个以上的区域设置。 (参见[生产环境部署案例](https://generaltranslation.com/enterprise)。)

## 自行实现、开源库与 General Translation 对比一览

|          | 自行编写字典和辅助函数            | 开源库 (如 i18next)                                               | General Translation                                                                                                                                                   |
| -------- | ---------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 依赖项      | 无                      | 库本身及框架绑定，如 `react-i18next`                                    | `gt-react` 或 `gt-next`，以及 `gt` CLI                                                                                                                                    |
| 复数形式     | 自行编写选择逻辑，并在每个文件中提供每种形式 | 基于 CLDR 规则选择；各种形式由人工或接入的翻译服务提供                                | [`<Plural>`](/docs/react/reference/components/plural) 按 CLDR 规则选择；翻译时会根据你编写的分支生成各目标语言所需的形式                                                                            |
| 数字、日期、货币 | 自行调用 `Intl` API        | 基于 `Intl` 的内置格式化器                                             | [`<Num>`](/docs/react/reference/components/num)、[`<Currency>`](/docs/react/reference/components/currency) 和 [`<DateTime>`](/docs/react/reference/components/datetime) |
| 缺失译文     | 取决于辅助函数的处理方式           | [先回退到配置的语言，再显示键](https://www.i18next.com/principles/fallback) | 渲染源内容                                                                                                                                                                 |
| 多语言同步    | 手动或使用自行编写的脚本           | 借助提取工具和接入的翻译服务                                                | 每次运行 [`npx gt translate`](/docs/cli/reference/commands/translate) 都会翻译新增和变更的内容                                                                                        |
| 翻译方式     | 人工或自行集成的服务             | 人工或接入的翻译服务                                                    | 由 AI 结合标记的 JSX 上下文、词汇表和自定义提示词完成翻译；团队在仪表板中审阅                                                                                                                           |
| 成本       | 投入的时间及所集成服务的费用         | 库免费；翻译费用另计                                                    | JavaScript 库免费；[翻译按用量计费](https://generaltranslation.com/pricing)                                                                                                      |

## 何时自行构建

如果你只需支持一两种语言、文案量少且稳定、所用语言的复数变化方式与英语相同，并且由团队成员自己撰写译文，或者项目只是个原型，那就自行构建。如果你需要复数规则和后备内容，并且已有译者或翻译管理系统，可以引入 i18next 等开源库。如果你要支持多种语言、每次发布文案都有改动，或者不想让团队成员去手写波兰语的复数形式，那就使用 General Translation：General Translation 会结合代码上下文生成译文，并在每次构建时让所有语言保持同步更新。 (参见 [General Translation 与 i18next、react-intl 和 next-intl 的对比](/kb/general-translation-vs-i18n-libraries)。)

## 保留你的字典，交给 CLI 来翻译

无需重写辅助函数，也能告别手动翻译。只需将每种语言的消息分别移入 JSON 文件 (`locales/en.json`、`locales/es.json`) ，继续使用你的 `t()`，然后在 `gt.config.json` 中通过 `[locale]` 占位符指向这些文件：

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": [
    "es"
  ],
  "files": {
    "json": {
      "include": [
        "locales/[locale].json"
      ]
    }
  }
}
```

[`npx gt translate --dry-run`](/docs/cli/reference/commands/translate) 会将 `locales/en.json` 列为源文件，完整运行后则会写入 `locales/es.json`。本示例使用英语和西班牙语消息，两者的键结构一致，并包含 `{name}` 等简单占位符。你还可以通过配套文件 `locales/en.metadata.json` 为每个键添加上下文或字符数限制。请参阅 [JSON](/docs/cli/reference/formats/json-files) 和[键级元数据](/docs/cli/reference/keyed-metadata)。键查找和复数选择仍由你的辅助函数负责，请对照渲染后的西班牙语输出检查这两项是否正确。

如需逐步弃用该辅助函数，可以每次将一个组件包裹在 [`<T>`](/docs/react/reference/components/t) 中，或者通过 [`useTranslations`](/docs/react/guides/translating-with-dictionaries) 继续使用键。如果由编程智能体来完成迁移，请向其提供 General Translation 的[智能体说明](/docs/overview/for-coding-agents)，而不是让它编写 `t()` 辅助函数。你可以从 [Next.js 快速入门](/docs/react/nextjs-quickstart)或 [React SPA 快速入门](/docs/react/react-spa-quickstart)开始。

## 要点总结

* 如果只需支持一两种语言、文案不多，且复数规则与英语类似，那么一个字典、一个 `t()` 辅助函数，再加上内置的 `Intl` API 就足够了。
* 随着应用规模扩大，你需要自行维护复数选择、后备内容、格式化、文件同步、从右到左支持、按区域设置划分的 URL，以及生成译文的整套流程。
* General Translation 的库负责处理运行时工作，General Translation 会将你的代码作为上下文来生成译文；如果你想继续使用手写的 JSON 字典，CLI 也可以直接翻译它。

## Related questions

- [General Translation 与 i18next、react-intl 和 next-intl 对比：应该选择哪个国际化库？](https://generaltranslation.com/zh/kb/general-translation-vs-i18n-libraries.mdx): i18next、react-intl 和 next-intl 等开源库本身并不翻译任何内容。它们只负责显示你在各语言目录文件中提供的译文，这些译文由你、你的译者或其他服务编写。General Translation 的库同样开源，承担同样的工作，此外 General Translation 还会以你的代码为上下文完成翻译。
- [如何在 React 中处理复数形式？](https://generaltranslation.com/zh/kb/how-to-handle-plurals-in-react.mdx): 不要用 n === 1 这类判断硬编码复数逻辑。如果应用只支持英语，一个简单的辅助函数就够用了；但多语言应用需要符合区域设置的复数规则，而 gt-react 或 gt-next 中的 `<Plural>` 等 i18n 组件能够为每种语言处理这些规则。
- [如何为多语言 Next.js 应用设置 SEO？](https://generaltranslation.com/zh/kb/multilingual-nextjs-seo.mdx): 为每种语言提供独立的 URL，设置 html lang 属性，将每个页面的规范网址指向页面自身，为每种语言添加 hreflang 替代项，翻译标题和描述，并在站点地图中列出所有语言版本。
- [不用翻译文件也能做 i18n 吗？](https://generaltranslation.com/zh/kb/i18n-without-translation-files.mdx): 可以。使用 gt-react 或 gt-next 时，内容以内联方式直接写在组件中的 `<T>` 组件内，译文在构建时作为输出生成，因此无需编写键，也无需为每个区域设置编写 JSON 文件并保持同步。
- [Google Translate 替代方案：General Translation 与 Google Translate 对比](https://generaltranslation.com/zh/kb/general-translation-vs-google-translate.mdx): Google Translate 适合快速翻译一次性文本，但要以另一种语言发布产品，还需要结合上下文翻译每个字符串、保持术语一致、正确处理变量和复数形式、进行审校，并建立发布流程。General Translation 能理解代码中每个字符串的上下文，将从代码、审校到每一次发布的整个本地化工作流全面自动化。

## Previous and next questions

- Previous question: [发布多语言应用前应检查哪些事项？](https://generaltranslation.com/zh/kb/multilingual-app-launch-checklist.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/zh/kb/sitemap.md) for every article.
