# General Translation Knowledge Base: 应该如何命名 i18n 翻译键？
URL: https://generaltranslation.com/zh/kb/i18n-key-naming.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 应按文本出现的位置及其含义来命名键，例如 checkout.summary.title，而不是照搬英文措辞。即使英文相同，不同用途也应各用一个键：德语会把按钮上的 Open 译为 Öffnen，而把状态 Open 译为 Offen。
Category: 国际化
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

键是代码用来查找翻译的内部标识符，例如 `t('summary.title', { ns: 'checkout' })`。好的键名不会因文案修改而失效，能让译者知道文本出现在哪里，也便于找出废弃的键。以下约定适用于 i18next、react-i18next、Rails I18n、vue-i18n，以及任何读取键值形式 JSON 或 YAML 的库；示例采用 i18next 语法，已在 i18next 26.5.3 下运行验证。

## 1. 按功能或页面划分命名空间

每个键都应以其所属的产品区域开头，其次是组件，最后是元素。

* ✅ `checkout.summary.title`、`settings.billing.cancelButton`
* ❌ `title1`、`text_47`、`newString`

如果第一段对应一个单独的文件，它就是命名空间，而不属于键路径。例如，`summary.title` 存储在 `checkout.json` 中时，i18next 需要通过 `t('summary.title', { ns: 'checkout' })` 或 `t('checkout:summary.title')` 来查找；而 `t('checkout.summary.title')` 只会原样返回键字符串，因为它会去默认命名空间中查找 `checkout` 对象。

前缀可以告诉译者文本出现在什么位置，也便于将目录拆分为多个文件。i18next 将这些文件称为[命名空间](https://www.i18next.com/principles/namespaces)，并建议按视图、功能或懒加载模块进行拆分，另外再设一个 `common` 命名空间，用于存放“保存”“取消”之类的通用标签。在 React 中，react-i18next 通过 `useTranslation('checkout')` 加载命名空间，而 [`keyPrefix` 选项](https://react.i18next.com/latest/usetranslation-hook)可以简化层级较深的路径。

## 2. 描述含义，而非措辞

即使市场团队改写了文案，键也应当依然准确。

* ✅ `auth.login.submit`
* ❌ `auth.login.clickHereToSignInNow`

日后按钮文字改为“Continue”时，第一个键依然准确；而第二个键描述的却是已不复存在的文本，迟早会有人去重命名它，届时所有引用都会失效，对应的译文也会变成孤立条目。

## 3. 按上下文分配键，而不是按英文字符串分配

英文中写法相同的两个字符串，翻译时往往并不相同。按钮上的 &quot;Open&quot; 是动词，而作为工单状态的 &quot;Open&quot; 是形容词。德语中前者译为 *Öffnen*，后者译为 *Offen*。

* ✅ `files.actions.open` 和 `tickets.status.open`
* ❌ 两处共用同一个 `common.open`

共享的 `common` 命名空间应只用于在任何场景下含义都完全一致的标签，例如“取消”按钮。基于源文本的系统则借助上下文解决同样的问题：GNU gettext 的 [`msgctxt`](https://www.gnu.org/software/gettext/manual/html_node/Contexts.html) 可以让同一个英文字符串拥有两种译文。

## 4. 嵌套或扁平 JSON 二选一，切勿混用

在 `checkout.json` 中，i18next 会把 `summary.title` 解析为路径，因为 `keySeparator` 的默认值是 `.`。两种结构都能用；选定一种即可，让所有文件、审阅者和工具都遵循同一约定。

```json title="locales/en/checkout.json (nested)"
{
  "summary": {
    "title": "Order summary",
    "taxNotice": "Tax is calculated at checkout."
  }
}
```

```json title="locales/en/checkout.json (flat)"
{
  "summary.title": "Order summary",
  "summary.taxNotice": "Tax is calculated at checkout."
}
```

对于扁平文件，i18next 的[配置文档](https://www.i18next.com/overview/configuration-options)建议将 `keySeparator` 设为 `false`。此外，还应选定一种字母大小写风格 (`camelCase` 或 `snake_case` 分段) ，并在所有文件中统一使用。

## 5. 使用库的复数和上下文后缀

i18next 通过后缀选择复数形式，且要求变量必须命名为 `count`。当 count 为 0 时，`_zero` 条目会覆盖该语言的默认规则；序数则需添加 `_ordinal` 后缀：

```json title="locales/en/checkout.json"
{
  "cart": {
    "itemCount_zero": "Your cart is empty",
    "itemCount_one": "{{count}} item",
    "itemCount_other": "{{count}} items"
  }
}
```

`t('cart.itemCount', { count: 0 })` 返回 &quot;Your cart is empty&quot;，传入 1 返回 &quot;1 item&quot;，传入 5 返回 &quot;5 items&quot;。复数类别更多的语言还会添加 `itemCount_few` 和 `itemCount_many` 等键。 (各语言的复数类别请参阅 [i18next 复数形式文档](https://www.i18next.com/translation-function/plurals)和[什么是 ICU 消息格式？](/kb/icu-message-format)。) 

* ✅ `itemCount_one`、`itemCount_other`，以及用于区分性别的 [上下文](https://www.i18next.com/translation-function/context)键，如 `friend_male` 和 `friend_female`
* ❌ 自创的后缀，如 `itemCount_singular` 和 `itemCount_plural`，库不会选用这些键

而基于 ICU 消息格式的库 (react-intl、next-intl、Lingui) 则会将所有复数形式放在同一个键下的一条消息中。

## 6. 切勿用多个键拼接句子

不同语言的语序各不相同，句子一旦拆分到多个键中，就无法正确翻译。

* ❌ `t('account.hello') + name + t('account.welcomeBack')`
* ✅ 使用一个带占位符的键：`"account.welcome": "Hello, {{name}}! Welcome back."`

如果消息中包含数量，每个复数形式同样要保留完整的句子，并使用约定 5 中的后缀。

[GNU gettext 手册](https://www.gnu.org/software/gettext/manual/html_node/Preparing-Strings.html)针对其目录也给出了相同的规则：翻译完整的句子，并使用格式化字符串，而不是拼接字符串。

## 7. 在稳定键与以源文本作为键之间做出选择

并非所有系统都需要另起名称。真正的问题在于内部标识符由谁来掌控：

| 方式                         | 示例                                       | 优势                   | 代价                                     |
| -------------------------- | ---------------------------------------- | -------------------- | -------------------------------------- |
| 语义化键 (i18next、Rails I18n)  | `t('summary.title', { ns: 'checkout' })` | 修改文案无需改动代码；键本身就标明了位置 | 需要有人为每个键命名、归档和清理                       |
| 以源文本作为键 (gettext)          | `_("Order summary")`                     | 代码可读性好；无需命名步骤        | 修改英文文本会产生新条目；文本相同引发的冲突需借助 `msgctxt` 解决 |
| 根据内联文本生成 ID (FormatJS)     | `defaultMessage="Order summary"`         | 消息与使用位置放在一起；ID 自动生成  | 需要构建插件和提取步骤                            |

如果文案在代码库之外管理 (例如由内容团队在 TMS 中编辑字符串) ，或者 Web 与移动端应用共用同一个目录，请使用语义化键。如果文案由开发者编写，并且你希望在代码审查中直接看到用户最终看到的文字，请使用源文本或内联消息。

## 8. 不要在 URL、分析和逻辑中使用键

键和翻译后的字符串只用于显示。不要把 `t('checkout:cta')` 用作分析事件名称 (否则每个区域设置上报的都是不同的事件) ，不要用键来构建路由 slug，也不要根据 `label === t('status.open')` 做条件分支。应改用固定的事件名称、由你掌控的 slug，以及枚举或状态码。这样，即使重命名键或修改翻译，也不会影响仪表板、链接或条件判断。

## 9. 自动清理未使用和缺失的键

目录会逐渐与代码脱节：组件删除后，其键仍然残留；新增的键则在未翻译的情况下就已发布。建议在 CI 中运行检查工具。

* **i18next**：[`i18next-cli`](https://github.com/i18next/i18next-cli) 取代了已弃用的 `i18next-parser`。在一个小型测试项目中，`npx i18next-cli status --unused` 报告了一个孤立的 `oldPromoBanner` 键，且未修改任何文件；随后运行 `npx i18next-cli extract` 删除了该键 (其 `removeUnusedKeys` 选项默认为 `true`) ，并为每个键写入了空的德语条目，包括 `itemCount_one` 和 `itemCount_other`。只要存在缺失的译文，`npx i18next-cli status` 就会报错退出；`rename-key` 则可在源文件和区域设置文件中统一重命名某个键。静态分析无法识别运行时动态拼接的键，例如 ``t(`status.${code}`)``，因此 `extract` 会将其视为未使用并删除。请在 `preservePatterns` 选项中列出这些前缀 (例如 `'status.*'`) ，或在代码中逐一写明每个键。
* **Rails**：i18n-tasks 会根据你的 Ruby 和 ERB 代码报告缺失和未使用的键。 (参见 [General Translation 与 i18n-tasks 对比](/kb/general-translation-vs-i18n-tasks)。)

## General Translation 如何处理键

使用 `gt-react` 或 `gt-next` 中的 [`<T>`](/docs/react/reference/components/t) 组件时，英文文本直接写在组件中，也就无需再为键命名：

```tsx title="CheckoutSummary.tsx"
import { T } from 'gt-react';

export function CheckoutSummary() {
  return (
    <T>
      <h2>Order summary</h2>
      <small>Tax is calculated at checkout.</small>
    </T>
  );
}
```

[`npx gt translate`](/docs/cli/reference/commands/translate) 只会提取源代码中仍然存在的内容，因此删除某个组件后，就不必再手动维护它的键。不过，&quot;Open&quot; 的不同含义仍然需要上下文来区分：`<T context="Ticket status">Open</T>`，或配合 [`useGT`](/docs/react/reference/hooks/use-gt) 使用 `gt('Open', { $context: 'Ticket status' })`。在 [`<T>`](/docs/react/reference/components/t) 上设置可选的 [`id`](/docs/react/reference/components/t#id) 属性，或在翻译函数中传入 `$id` 选项，即可为条目指定一个稳定的内部标识符，供翻译编辑器使用。 (参见 [不使用翻译文件也能实现 i18n 吗？](/kb/i18n-without-translation-files)。) 

如果团队希望使用键，也可以继续保留。只需编写一个源字典 (例如 `dictionary.json`) ，并通过 [`useTranslations`](/docs/react/reference/hooks/use-translations) 读取即可；同一条命令也会翻译该字典。若查找的 id 不在字典中，则会抛出异常，而不是渲染出原始键。 (参见 [使用字典进行翻译](/docs/react/guides/translating-with-dictionaries)。) 现有的 i18next 或 next-intl JSON 文件可直接按原样翻译；配套的 [`.metadata.json` 文件](/docs/cli/reference/keyed-metadata) 还可以为每个键添加上下文或字符数限制，相当于约定 3 在基于键方案中的等效做法。

## 要点总结

* 按位置和含义命名键 (`checkout.summary.title`) ，切勿按文案或位置命名 (`text_47`) 。
* 作为动词的“Open”和作为状态的“Open”应使用不同的键；只有含义完全相同时才共用键。
* 严格遵循库规定的复数后缀：i18next 使用 `_one`、`_other` 和 `_zero`，而 ICU 库则将所有复数形式放在同一条消息中。
* 在 CI 中运行 `i18next-cli status --unused` 或 i18n-tasks，避免无用键和缺失键越积越多。
* General Translation 的 [`<T>`](/docs/react/reference/components/t) 完全不需要键；如果团队仍想使用键，可以选择搭配 [`useTranslations`](/docs/react/reference/hooks/use-translations) 的词典模式。

## Related questions

- [不使用翻译文件能实现 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 还能通过拉取请求让译文保持最新。
- [General Translation 与 i18n-tasks：应该选择哪一个？](https://generaltranslation.com/zh/kb/general-translation-vs-i18n-tasks.mdx): i18n-tasks 是一个 Ruby gem，用于查找缺失和未使用的 Rails I18n 键、整理区域设置文件，还能借助 Google Translate、DeepL、OpenAI、Yandex 或 watsonx 补全缺失的键。General Translation 则结合你的词汇表、自定义提示词和仪表板审校，翻译完整的区域设置文件。两者分工不同，许多团队完全可以搭配使用。
- [i18next 替代方案：General Translation 与 i18next 对比](https://generaltranslation.com/zh/kb/general-translation-vs-i18next.mdx): 如果你希望将文案内联在 React 组件中，并借助其 JSX 结构和产品上下文生成译文，请选择 General Translation。你也可以继续使用 i18next，并用 General Translation 翻译现有的目录。i18next 负责消息渲染和复数形式选择，译文则需要由单独的服务生成。
- [什么是 ICU 消息格式？](https://generaltranslation.com/zh/kb/icu-message-format.mdx): ICU 消息格式是一种语法，可在单个可翻译字符串中同时包含变量、复数形式、select 选择和序数，例如 {count, plural, one {# file} other {# files}}。每种语言只保留自身所需的 CLDR 复数类别：英语使用 one 和 other，波兰语则额外增加了 few 和 many。
- [如何找出未翻译的硬编码字符串？](https://generaltranslation.com/zh/kb/find-hardcoded-strings.mdx): 在 CI 中运行 linter，标记面向用户的字符串字面量，例如 JSX 可使用 eslint-plugin-i18next 的 no-literal-string 规则，Android 可使用 Android Lint 的 HardcodedText 检查，Rails 可使用 erb_lint 的 HardCodedString linter。然后切换到伪区域设置，逐一浏览应用，找出静态检查无法追踪到的文本。

## Previous and next questions

- Previous question: [如何让网站支持多语言？](https://generaltranslation.com/zh/kb/make-website-multilingual.mdx)
- Next question: [语言切换器最佳实践：用户体验、SEO 与无障碍访问](https://generaltranslation.com/zh/kb/language-switcher.mdx)

## Sitemap

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