# General Translation Knowledge Base: 什么是 ICU 消息格式？
URL: https://generaltranslation.com/zh/kb/icu-message-format.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: ICU 消息格式是一种语法，可在单个可翻译字符串中同时包含变量、复数形式、select 选择和序数，例如 {count, plural, one {# file} other {# files}}。每种语言只保留自身所需的 CLDR 复数类别：英语使用 one 和 other，波兰语则额外增加了 few 和 many。
Category: 按区域设置格式化
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

该语法源自 ICU (International Components for Unicode) 中的 MessageFormat 类。ICU 是 Unicode 联盟面向 C/C++ 和 Java 推出的一套国际化库。FormatJS 等 JavaScript 库也实现了同一套语法。由于后来又推出了重新设计的 MessageFormat 2，这一语法现在也被称为 MessageFormat 1。

## ICU 消息格式解决了什么问题？

用片段拼接句子的代码在英语中能正常工作，换成其他语言就会出问题。`'You have ' + n + ' files'` 把英语的语序和英语的两种复数形式写死在了代码里。而波兰语的同一句话需要四种形式：1 plik、2 pliki、5 plików 和 1,5 pliku。在许多语言中，带有性别的句子 (“She replied”或“He replied”) 会牵动句中其他词语的变化，序数词 (1st、2nd、3rd) 也有各自的规则。

ICU 消息格式把所有这些情况都放进同一个字符串。译者能看到包含所有分支的完整句子，可以按自己语言的习惯改写，并根据语法需要增删分支。正因如此，[ICU 用户指南](https://unicode-org.github.io/icu/userguide/format_parse/messages/)建议在每个分支中都写出完整的句子：译者看不到完整的句子，就无法修正片段的语法。

## 语法与可运行示例

以下所有示例均在 Node.js 24 上使用 FormatJS 的 [`intl-messageformat`](https://formatjs.github.io/docs/intl-messageformat) 12.1.4 运行。消息只需解析一次，之后传入值即可格式化：

```js
import { IntlMessageFormat } from 'intl-messageformat';

const msg = new IntlMessageFormat(
  '{count, plural, =0 {You have no messages.} one {You have # message.} other {You have # messages.}}',
  'en'
);

msg.format({ count: 0 });    // 'You have no messages.'
msg.format({ count: 1 });    // 'You have 1 message.'
msg.format({ count: 1234 }); // 'You have 1,234 messages.'
```

### 参数

`{name}` 用于插入一个值：`Hello, {name}!` 传入 `{ name: 'Ada' }` 后会得到“Hello, Ada!”。带类型的参数会按区域设置对值进行格式化，例如 `Total: {price, number, ::currency/EUR}` 传入 `1234.5` 后，在英语中会得到“Total: €1,234.50”。

### plural、`#` 与精确匹配

`{count, plural, ...}` 会根据区域设置的复数类别选择分支：`zero`、`one`、`two`、`few`、`many` 或 `other`。在分支内，`#` 代表该数字，并按区域设置进行格式化；例如德语消息 `{count, plural, one {Sie haben # Nachricht.} other {Sie haben # Nachrichten.}}` 会输出 &quot;Sie haben 1.234 Nachrichten.&quot;。`=0` 这类精确匹配的优先级高于复数类别，上面的示例正是借此显示 &quot;no messages&quot;，而不是 &quot;0 messages&quot;。

下面是同一个波兰语句子，改用波兰语所需的分支：

```text
{count, plural, one {# plik} few {# pliki} many {# plików} other {# pliku}}
```

使用 `pl` 时，1、2、5、22 和 1.5 会分别格式化为 &quot;1 plik&quot;、&quot;2 pliki&quot;、&quot;5 plików&quot;、&quot;22 pliki&quot; 和 &quot;1,5 pliku&quot;。

### select

`select` 根据字符串值选择分支，`other` 分支则兜底匹配其余所有情况：

```text
{gender, select,
  female {She replied to your comment.}
  male {He replied to your comment.}
  other {They replied to your comment.}}
```

`{ gender: 'female' }` 会输出 &quot;She replied to your comment.&quot;，`{ gender: 'nonbinary' }` 会输出 &quot;They replied to your comment.&quot;。请在每个分支中写出完整的句子：在其他语言中，性别不仅会影响代词，还可能影响动词的形式。

### selectordinal

`selectordinal` 使用序数规则，英语的序数规则分为四类：

```text
{place, selectordinal,
  one {You finished #st.}
  two {You finished #nd.}
  few {You finished #rd.}
  other {You finished #th.}}
```

名次为 1、2 和 11 时，输出分别为 `You finished 1st.`、`You finished 2nd.` 和 `You finished 11th.`。3、4、22 和 103 对应的后缀分别为 `rd`、`th`、`nd` 和 `rd`。

### offset

`offset:1` 会先将数字减一，再选择类别并输出 `#`，非常适合“你和其他 N 人”这类消息。而精确匹配比较的仍是原始值：

```text
{guests, plural, offset:1
  =0 {Nobody is going.}
  =1 {{host} is going.}
  one {{host} and # other person are going.}
  other {{host} and # other people are going.}}
```

当 `host: 'Mia'` 时，客人数为 0、1、2 和 5 时分别输出 &quot;Nobody is going.&quot;、&quot;Mia is going.&quot;、&quot;Mia and 1 other person are going.&quot; 和 &quot;Mia and 4 other people are going.&quot;

### 嵌套参数

参数可以相互嵌套。ICU 指南建议将 `select` 放在外层、`plural` 放在内层，并在每个分支中写出完整的句子：

```text
{gender, select,
  female {{count, plural, one {She has # new follower} other {She has # new followers}}}
  other {{count, plural, one {They have # new follower} other {They have # new followers}}}}
```

### 撇号与转义

ASCII 撇号用于对语法字符进行转义引用，连续两个撇号则输出一个撇号。自 ICU 4.8 起，单个撇号只有紧挨在需要转义的字符之前时，才会开启一段引用文本，因此英文中常见的缩写形式 (如 don&#39;t) 可以正常使用：

```text
It's {name}'s turn. Type '{name}' to insert a name. I''m here.
```

传入 `{ name: 'Ada' }` 时，输出为 `It's Ada's turn. Type {name} to insert a name. I'm here.`。在 plural 中，`'#'` 会输出字面量 `#`。FormatJS 的 [ICU 语法指南](https://formatjs.github.io/docs/core-concepts/icu-syntax)建议在人类可读的文本中使用印刷体撇号 (’) ，这样就能直接避开这个问题。

## 什么是 CLDR 复数类别？

这些类别来自 [Unicode CLDR 复数规则](https://cldr.unicode.org/index/cldr-spec/plural-rules)，JavaScript 的 `Intl.PluralRules` 也基于同一套数据。下表列出了 `Intl.PluralRules` 对几种语言给出的结果：

| 语言                     | 基数类别                                    |
| ---------------------- | --------------------------------------- |
| 英语 (`en`)              | `one`、`other`                           |
| 波兰语 (`pl`) 、俄语 (`ru`)  | `one`、`few`、`many`、`other`              |
| 阿拉伯语 (`ar`)            | `zero`、`one`、`two`、`few`、`many`、`other` |
| 日语 (`ja`)              | `other`                                 |

`one` 并不只代表 1：在俄语中，21 也属于 `one`。只有在确实指数字 1 本身时，才应使用 `=1`。如需了解在 React 中如何在组件层面处理这一问题，请参阅[如何在 React 中处理复数形式？](/kb/how-to-handle-plurals-in-react)。

## 常见问题

* **语法错误暴露得晚。** 缺少的花括号在解析之前只是普通字符串，直到解析时 `intl-messageformat` 才会抛出 `EXPECT_ARGUMENT_CLOSING_BRACE` 异常。请在 CI 中解析或 lint 消息，或者对其进行预编译。
* **缺少 `other` 分支。** 如果 plural 没有 `other`，`intl-messageformat` 会抛出 `MISSING_OTHER_CLAUSE` 异常；FormatJS 对 `select` 也同样要求提供 `other`。
* **分支与目标语言不匹配。** 英文消息包含 `one` 和 `other`；波兰语翻译还需要 `few` 和 `many`，而日语翻译只需要 `other`。你的翻译流程必须能够增删分支，而不只是翻译分支内的文本。
* **句子碎片化。** `{count} {count, plural, one {file} other {files}} deleted` 只给了译者一堆需要重新排序的零散片段。请在每个分支中写出完整的句子。
* **方言混用。** i18next 的默认语法是 `{{name}}`，复数键形如 `key_one`，而不是 ICU。如果团队想在 i18next 中使用 ICU，可以添加 [`i18next-icu`](https://github.com/i18next/i18next-icu) 插件。

## 哪些库支持 ICU 消息格式？

* **ICU4J 和 ICU4C** 是官方参考实现，分别基于 Java 和 C/C++。
* **FormatJS**：[`intl-messageformat`](https://formatjs.github.io/docs/intl-messageformat) 是 react-intl 底层使用的解析器和格式化器，支持数字和日期 skeleton (例如 `::currency/EUR`) 以及富文本标签。 (参见 [General Translation 与 react-intl 对比](/kb/general-translation-vs-react-intl)。) 
* **messageformat**：[`@messageformat/core`](https://messageformat.github.io/messageformat/) 可将 ICU 消息编译为 JavaScript 函数；而 `messageformat` 软件包的第 4 版已改为实现 MessageFormat 2。
* **i18next-icu**：基于 `intl-messageformat` 构建的 i18next 插件，可让 i18next 改用 ICU 语法。
* **Lingui**：其[宏](https://lingui.dev/ref/macro)会在编译时将 JSX 和标签模板转换为 ICU MessageFormat 消息。
* **next-intl**：其[消息采用 ICU 语法](https://next-intl.dev/docs/usage/messages)。

## MessageFormat 2 有哪些变化？

MessageFormat 2 (MF2) 是 Unicode 联盟 (Unicode Consortium) 重新设计的版本，引入了新语法：包括 `.input`、`.match` 等声明，以及用双花括号包裹的模式。其规范为 [Unicode LDML 第 9 部分](https://www.unicode.org/reports/tr35/tr35-messageFormat.html)，已标记为稳定文档 (部分函数仍标注为 Draft) ，并已内置于 ICU4C 和 ICU4J 中。在 JavaScript 方面，TC39 的 [`Intl.MessageFormat` 提案](https://github.com/tc39/proposal-intl-messageformat)目前处于 Stage 1，尚未纳入 JavaScript 标准；`messageformat` 4.x 软件包是它的 polyfill。用 MF2 编写的复数示例如下：

```text
.input {$count :number}
.match $count
0   {{You have no messages.}}
one {{You have {$count} message.}}
*   {{You have {$count} messages.}}
```

使用 `messageformat` 4.1.0 运行时，对于 0、1 和 1234，它分别输出 &quot;You have no messages.&quot;、&quot;You have 1 message.&quot; 和 &quot;You have 1,234 messages.&quot;。MF2 消息与 MessageFormat 1 字符串互不兼容，而上文提到的其他库读取的都是 MessageFormat 1 格式。

## General Translation 如何处理 ICU

在 `gt-react` 和 `gt-next` 中，[`useGT`](/docs/react/reference/hooks/use-gt) 会返回一个函数，该函数接收一条 ICU 消息及其对应的变量值：

```tsx title="Inbox.tsx"
import { useGT } from 'gt-react';

export function Inbox({ count }: { count: number }) {
  const gt = useGT();
  return (
    <p>
      {gt('{count, plural, =0 {You have no messages.} one {You have # message.} other {You have # messages.}}', {
        count,
        $context: 'Unread message count in the inbox',
      })}
    </p>
  );
}
```

[`npx gt translate`](/docs/cli/reference/commands/translate) 会提取这些字符串，并以 `$context`、你的词汇表和自定义提示词作为上下文，为每个目标区域设置生成译文。在源区域设置下，`gt-react` 11.4.12 输出的三个结果与上文 `intl-messageformat` 示例完全相同。

* **字典。** 通过 [`useTranslations`](/docs/react/reference/hooks/use-translations) 读取的条目可以使用 ICU 格式化，例如 `{dollars, number, ::currency/USD}`。
* **现有 JSON 消息目录。** CLI 会根据 `package.json` 检测 next-intl 和 i18next (包括 `i18next-icu`) ，并在翻译其 JSON 文件时保留该库的语法。 (参见 [JSON 文件](/docs/cli/reference/formats/json-files)。) 
* **仅格式化，不翻译。** Core 库的 [`formatMessage`](/docs/platform/core/reference/utility-functions/formatting/format-message) 可按指定区域设置格式化 ICU 消息。
* **代码检查。** `@generaltranslation/react-core-linter` 中的 `static-string` 规则会报告格式错误的 ICU 消息。 (参见 [Lint 规则](/docs/react/reference/lint-rules#static-string)。) 

并非所有 API 都会解析 ICU：`gt-node` 的 [`tx`](/docs/node/reference/functions/tx) 会将花括号视为普通文本。在 JSX 中，只需在 [`<T>`](/docs/react/reference/components/t) 内使用 [`<Plural>`](/docs/react/reference/components/plural) 和 [`<Branch>`](/docs/react/reference/components/branch)，即可处理同样的复数和选择场景，无需手写 ICU 语法。

## 要点总结

* ICU 消息格式可将变量、`plural`、`select`、`selectordinal` 和 `offset` 整合到单个可翻译字符串中，用 `#` 表示格式化后的数字，并支持 `=0` 这类精确匹配。
* 复数类别源自 CLDR：英语有 2 种基数形式，波兰语和俄语有 4 种，阿拉伯语有 6 种，日语只有 1 种，因此译文必须相应地增加或删减分支。
* 每个分支都应写出完整的句子，始终包含 `other`，并在发布到生产环境之前验证语法。
* MessageFormat 2 已是稳定的 Unicode 规范，并内置于 ICU4C 和 ICU4J 中，但 `Intl.MessageFormat` 目前仍处于 TC39 Stage 1 提案阶段。
* General Translation 的 [`useGT`](/docs/react/reference/hooks/use-gt) 在 `gt-react` 和 `gt-next` 中均支持 ICU 字符串，[`npx gt translate`](/docs/cli/reference/commands/translate) 则负责生成译文。

## Related questions

- [如何在 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 便会生成每种目标语言所需的形式。
- [react-intl 替代方案：General Translation 与 react-intl 对比](https://generaltranslation.com/zh/kb/general-translation-vs-react-intl.mdx): General Translation 让 React 团队在保留 ICU 消息语法的同时，获得译文生成、共享术语和仪表板审校能力。react-intl 提供格式化功能和 FormatJS 目录工具；General Translation 还能直接生成译文。
- [如何翻译 i18n JSON 文件？](https://generaltranslation.com/zh/kb/translate-json-files.mdx): 只翻译 JSON 区域设置文件中的字符串值。非复数键、{{name}} 之类的占位符以及标签都应保持不变，但复数键会因语言而异：例如波兰语需要 inbox_few 和 inbox_many。请使用能识别文件格式的工具或 API，然后先解析并验证输出，再进行审校。
- [i18next 替代方案：General Translation 与 i18next 对比](https://generaltranslation.com/zh/kb/general-translation-vs-i18next.mdx): 如果你希望将文案内联在 React 组件中，并借助其 JSX 结构和产品上下文生成译文，请选择 General Translation。你也可以继续使用 i18next，并用 General Translation 翻译现有的目录。i18next 负责消息渲染和复数形式选择，译文则需要由单独的服务生成。
- [应该如何命名 i18n 翻译键？](https://generaltranslation.com/zh/kb/i18n-key-naming.mdx): 应按文本出现的位置及其含义来命名键，例如 checkout.summary.title，而不是照搬英文措辞。即使英文相同，不同用途也应各用一个键：德语会把按钮上的 Open 译为 Öffnen，而把状态 Open 译为 Offen。

## Previous and next questions

- Previous question: [什么是 hreflang？如何设置？](https://generaltranslation.com/zh/kb/what-is-hreflang.mdx)
- Next question: [什么是伪本地化？](https://generaltranslation.com/zh/kb/pseudo-localization.mdx)

## Sitemap

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