# 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 则根据你的代码生成并更新译文。

## Video

The video is narrated in English, with English captions.

[Should you build your own i18n or use General Translation?](https://caxecubb9c64cezv.public.blob.vercel-storage.com/films/kb-general-translation-vs-in-house-i18n.mp4) (43秒)

Hand-rolled i18n, a dictionary per language and a t() helper, suits one or two languages, but as an app grows you rebuild plurals, fallbacks and file syncing, and still need translations. General Translation's libraries handle rendering, and General Translation generates and updates translations from your code: write copy inside `<T>` with counts in `<Plural>`, run `npx gt translate` before each build, or move your dictionary to JSON files for the CLI.

## 何时手写 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): 在 React 中处理复数形式时，应为每种语言的每个复数形式各准备一个句子，并按该语言的复数规则选出对应句子——英语有 2 种形式，俄语有 4 种，阿拉伯语有 6 种。gt-react 和 gt-next 提供的 General Translation `<Plural>` 组件只需接收你的源语言形式，General Translation 便会生成每种目标语言所需的形式。
- [如何为多语言 Next.js 应用设置 SEO？](https://generaltranslation.com/zh/kb/multilingual-nextjs-seo.mdx): 为每种语言提供带前缀的独立 URL，例如 /en/about 和 /es/about，然后为每种已发布的语言提供 html lang、自引用的规范网址、包含 x-default 的 hreflang 替代项、翻译后的 title 和 description，以及站点地图条目。将未发布的版本标记为 noindex。General Translation 的 gt-next 会将每种语言路由到各自的 URL，并通过 getGT 翻译标题。
- [不使用翻译文件能实现 i18n 吗？](https://generaltranslation.com/zh/kb/i18n-without-translation-files.mdx): 可以。借助 General Translation 的 gt-react 和 gt-next 库，你可以为 React 和 Next.js 实现 i18n，既无需翻译键，也不用为每种语言维护 JSON 文件。文案以内联方式写在 JSX 的 `<T>` 组件中，gt translate 命令会在构建时结合代码中的上下文，为每种语言生成译文。Locadex 还能通过拉取请求让译文保持最新。
- [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)
- Next question: [AI 翻译的准确度足以用于生产环境应用吗？](https://generaltranslation.com/zh/kb/is-ai-translation-accurate.mdx)

## Sitemap

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