# General Translation Knowledge Base: 不用翻译文件也能做 i18n 吗？
URL: https://generaltranslation.com/zh/kb/i18n-without-translation-files.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 可以。使用 gt-react 或 gt-next 时，内容以内联方式直接写在组件中的 `<T>` 组件内，译文在构建时作为输出生成，因此无需编写键，也无需为每个区域设置编写 JSON 文件并保持同步。
Category: 国际化
Last updated: 2026-05-07
Authors: Jackie Chen, Ernest McCarter
Published: 2026-05-07

凡是给 JavaScript 应用做过国际化的人，都熟悉这套工作流：安装一个国际化库，创建 `en.json` 文件，把组件中所有面向用户的字符串抽取出来，给每个字符串分配一个键，再在原来字符串的位置引用这个键。然后，每支持一种语言，就复制一份 JSON 文件：`es.json`、`fr.json`、`ja.json`。

起初还好。30 个字符串，3 种语言，90 个条目。

可应用会越做越大。六个月后，你有了 400 个字符串和 12 种语言，十几个文件里共有 4,800 个条目。某位开发者新增了一个功能，写了五个新字符串，却忘了更新其中三个翻译文件。一直没人发现，直到东京的某位用户在日语界面里看到了英文后备内容。于是有人提议：要不买一套翻译管理系统吧。

<Video src="https://assets.gtx.dev/i18n-no-translation.mp4" />

## Video

The video is narrated in English, with English captions.

[Can I do i18n without translation files?](https://caxecubb9c64cezv.public.blob.vercel-storage.com/films/kb-i18n-without-translation-files.mp4) (46秒)

General Translation lets you stop writing translation files with `gt-react` or `gt-next`. Run `npx gt init`, write the English inside a `<T>` component, then add `npx gt translate` to your build script so every build writes the translations.

## 文件管理的代价

翻译文件带来的维护负担，会随着支持语言数量的增加而不断加重。改动一个字符串，就得同步更新十几个文件。久而久之，这些文件之间难免出现不一致；要让它们保持同步，就离不开工具、自动化或严格的规范。

围绕这一问题涌现出的各种变通方案，本身就说明了一切：自动生成翻译键的 VS Code 扩展、根据 TypeScript 接口校验 JSON 的类型生成器、检查未使用键的 linter，以及验证所有区域设置是否同步的 CI 检查。如果一个工作流本身运转良好，没人会费力为它开发这类工具。

UI 里写的是一套内容，翻译文件里写的是另一套，而把两者维系在一起的，是基于键的映射：

```tsx
function CheckoutSummary({ itemCount, discount }) {
  const { t } = useTranslation('checkout');

  return (
    <div>
      <h2>{t('summary.title')}</h2>
      <p>{t('summary.item_count', { count: itemCount })}</p>
      {discount && <p>{t('summary.discount_applied', { percent: discount })}</p>}
      <p>{t('summary.total_label')}</p>
      <small>{t('summary.tax_notice')}</small>
    </div>
  );
}
```

要弄清这个组件渲染的内容，你得打开 `checkout.json`，找到 `summary` 命名空间，再交叉比对五个键。一旦组件多达数百个，代码审查就成了在文件之间来回跳转的苦差事。

ICU 消息语法更是雪上加霜。`i18next`、`react-intl` 和 `next-intl` 等库在处理复数形式、性别和插值时所用的格式，本身就是一门嵌在 JSON 字符串里的小型语言。`{count, plural, one {# item} other {# items}}` 中哪怕放错一个花括号，也要到运行时才会暴露出来。虽然有专门检查这类问题的 linter，但大多数团队都不用。相比之下，内联代码在你输入的那一刻，就会受到 TypeScript 编译器和 IDE 的检查。

## 直接翻译代码

如果根本不提取字符串，又会怎样？

```tsx
import { T } from 'gt-react';

function CheckoutSummary({ itemCount, discount }) {
  return (
    <T>
      <div>
        <h2>Order Summary</h2>
        <p>You have {itemCount} items in your cart.</p>
        {discount && <p>{discount}% discount applied!</p>}
        <p>Total (before tax):</p>
        <small>Tax calculated at checkout.</small>
      </div>
    </T>
  );
}
```

[`gt-react`](/docs/react/react-quickstart) 中的 [`<T>`](/docs/react/reference/components/t) 组件用于将一段 JSX 标记为待翻译内容。英文内容直接保留在你的组件中。当用户使用西班牙语访问时，[`<T>`](/docs/react/reference/components/t) 中的内容会被替换为对应的西班牙语版本，结构和格式也会原样保留。

不需要 `t('checkout.summary.title')`，不需要 `en.json`，也不需要为每个区域设置单独维护 JSON 文件并保持同步。

## 译文即构建产物

译文在部署时生成。[GT CLI](/docs/cli/reference/commands/translate) 会扫描你的代码库，找出所有 [`<T>`](/docs/react/reference/components/t) 组件中的内容，并为每种目标语言生成译文。生成的结果存放在已加入 gitignore 的目录中，就像编译后的 CSS 或打包后的 JavaScript 一样。

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["es", "fr", "ja", "de", "ko", "zh"],
  "files": {
    "gt": {
      "output": "public/_gt/[locale].json"
    }
  }
}
```

```txt title=".gitignore"
public/_gt/
```

在开发环境中，翻译按需进行：修改字符串，刷新页面，立刻就能看到日文版本。在生产环境中，所有内容都预先生成，并通过 CDN 分发。翻译文件依然保存在磁盘上，但它们只是构建产物，无需你手动编写或维护。

## 实际工作中有何变化

这种差异更多体现在日常工作中，而不是架构图上。

一个为页面新增章节的 PR，读起来就是一个为页面新增章节的 PR。审查者看到的是实际文字，而不是 `checkout.summary.discount_applied_notice`。代码审查时，也不必再对照 JSON 文件逐行比对。

重构也没那么痛苦了。无论是重命名组件、移动组件，还是将其拆分成多个部分，都不成问题。由于没有需要重新映射的键，译文会自动跟随内容。添加一种语言只需改一行配置。删除组件后，它的译文会悄然停止生成，而不会在 15 个文件里留下一堆没人愿意清理的孤立键。

## 国际化库的发展方向

整个生态系统正在转向内联字符串。早期的 国际化库 (如 `i18next` 和 `react-intl`) 诞生于机器翻译尚不可行的年代，每个字符串都需要交给人工译者处理，因此用字典作为交换格式是合理的。如今这一限制已不复存在，而维护一套并行字符串文件所付出的开发体验代价也越来越难以自圆其说。

`next-intl` 在字典模式之外，新增了[支持自动提取的实验性内联消息](https://next-intl.dev/docs/usage/extraction)。[Lingui 的编译器](https://lingui.dev/ref/swc-plugin)会在构建时从内联标签模板中提取消息。[Paraglide](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) 则另辟蹊径，将消息文件编译为按区域设置划分、支持 tree-shaking 的函数。这些方案各有不同，但都在让内容更贴近组件。GT 则将这一思路贯彻到底：你的 JSX 就是唯一的事实来源，翻译只是编译过程中的一个步骤。

## 权衡取舍

以内联方式编写内容时，你用的是自己的母语。组件结构、句式、UI 流程，无一不体现出你用英语 (或其他源语言) 思考的方式。而像 `next-intl` 这样基于字典的方案，在设计上就更加语言无关，因为组件中从不包含任何语言的真实句子，只有一个指向别处的键。

但大多数开发者在构建 UI 时，本来就是用某一种语言在思考。布局、文案、按钮标签，最初都是用英语构思出来的。无论字符串是内联编写还是放在 JSON 文件中，这种倾向早已融入设计之中。我们认为，i18n 框架应当适应你实际的工作方式：按自然的方式构建应用，把翻译交给框架处理，而不是为了语言中立而把内容抽象成键。

## 快速入门

```bash
npx gt@latest init
```

初始化设置向导会自动配置你的项目、安装依赖项，并为开发环境启用翻译热重载。完整步骤请参阅 [CLI 快速入门](/docs/cli/quickstart)。

完成后，用 [`<T>`](/docs/react/reference/components/t) 包裹内容，启动开发服务器，再使用 [`<LocaleSelector>`](/docs/react/reference/components/locale-selector) 组件切换语言：

```tsx
import { LocaleSelector } from 'gt-react';

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

在开发环境中，翻译按需进行，你可以立即以任意语言查看应用。在生产环境中，译文则是预先生成的。请在构建流程中添加 translate 步骤，并在托管服务商处设置 `GT_PROJECT_ID` 和 `GT_API_KEY`：

```json title="package.json"
{
  "scripts": {
    "build": "npx gt translate && next build"
  }
}
```

[`translate`](/docs/cli/reference/commands/translate) 命令会扫描你的代码库，找出所有使用 [`<T>`](/docs/react/reference/components/t) 的地方并生成翻译，确保应用构建时每个区域设置都已准备就绪。

`gt-react` 是开源的。如果使用 Next.js App Router，请选择 [`gt-next`](/docs/react/nextjs-quickstart)；如果使用 React Native，请选择 [`gt-react-native`](/docs/react/react-native-quickstart)。

## Related questions

- [如何在 JSX 中翻译条件文本？](https://generaltranslation.com/zh/kb/translate-conditional-jsx.mdx): 与其分别翻译三元表达式的两个分支，不如将整句包裹在 `<T>` 组件中，并用 `<Branch>` 表达条件。这样译者能看到完整的上下文，并能根据每种语言调整句子语序。
- [如何在 React 中处理复数形式？](https://generaltranslation.com/zh/kb/how-to-handle-plurals-in-react.mdx): 不要用 n === 1 这类判断硬编码复数逻辑。如果应用只支持英语，一个简单的辅助函数就够用了；但多语言应用需要符合区域设置的复数规则，而 gt-react 或 gt-next 中的 `<Plural>` 等 i18n 组件能够为每种语言处理这些规则。
- [对开发者来说，最好的本地化软件是什么？](https://generaltranslation.com/zh/kb/best-localization-software.mdx): 选择一套能覆盖整条流水线的系统——从代码中的国际化库，到翻译和投递——而不是把独立的库、翻译管理系统和翻译供应商拼凑在一起。

## Previous and next questions

- Previous question: [对开发者来说，最好的本地化软件是什么？](https://generaltranslation.com/zh/kb/best-localization-software.mdx)
- Next question: [Crowdin 替代方案：General Translation 与 Crowdin 对比](https://generaltranslation.com/zh/kb/general-translation-vs-crowdin.mdx)

## Sitemap

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