案例 MF2 随 CLDR 47 转正:数据模型、MF1 语法对照与迁移验证

2026-10-23 00:03:33

MF2 随 CLDR 47 转正:数据模型、MF1 语法对照与迁移验证

MessageFormat 2.0(现称 Unicode MessageFormat,MF2)在 CLDR 47(2025 年 3 月发布)从 Final Candidate 升级为 Stable,规范作为 TR35 的一部分发布,稳定性政策也写进了规范性内容。CLDR 47 已并入 ICU 77。

规范状态与实现状态不是一回事。ICU4J 的 com.ibm.icu.message2、ICU4C 的 icu::message2::MessageFormatter 自 ICU 76 起仍是 tech preview,官方文档明确标注 "Technical Preview API",函数集合会随规范调整;JavaScript 侧的 messageformat 4.0 实现了 MF2 语法,并提供 ECMA-402 提案 runtime API 的 polyfill。

  • 规范仓库:https://github.com/unicode-org/message-format-wg
  • 站点与交互式示例:https://messageformat.unicode.org/
  • ICU 文档 MF2 页:https://unicode-org.github.io/icu/userguide/format_parse/messages/mf2.html
  • npm:https://www.npmjs.com/package/messageformat

消息分两类

MF2 分 simple message 与 complex message。

simple message 只有一个 pattern,首字符有约束,首尾空白有意义并计入文本。

complex message 以带点前缀的关键字(声明)或带双花括号的 quoted pattern 开头,结构是「若干声明 + complex body」,首尾空白无意义。complex body 要么是一个 quoted pattern {{ ... }},要么是 matcher。

声明有三种:

  • .input {$count :number} 把外部变量注解成带函数的值
  • .local $x = {|表达式|} 定义局部变量
  • .match $count 开始 matcher,后面跟至少一个 selector,再跟若干 variant

matcher 的四条语义约束

matcher 是否合法由数据模型判定,不满足即抛 Data Model Error:

  1. Variant Key Mismatch:每个 variant 的 key 数量必须等于 selector 数量
  2. Missing Fallback Variant:至少有一个 variant 的所有 key 都是 *
  3. Missing Selector Annotation:每个 selector 必须是变量,且直接或间接引用一个带函数的声明
  4. Duplicate Variant:variant 的 key 列表必须唯一(字面量按字符串值比较,不按写法)

错误分两层,这点在迁移时经常被混在一起:

层级触发条件性质
Syntax Error不满足文法解析期
Data Model Error满足文法但不满足上述语义语法合法、语义不合法

第二类容易漏,因为它不会在解析阶段暴露。

例子

.input {$count :number}
.match $count
0   {{ No items. }}
one {{ 1 item. }}
*   {{ {$count} items. }}

标记:This is {#bold}important{/bold}.,MF2 不赋予标记语义,如何渲染由开发者决定。

字面量:My name is {|John Doe|}.,用成对竖线包裹;也常出现在选项值里:year=|2-digit|。

转义:\{、\} 表示字面的花括号。MF2 用反斜杠转义,不再是 MF1 的单引号。

函数:

{$ts :time}
{$val :number skeleton=(.####)}
{(123456789.531) :number}

第三行是编译期常量。函数以 : 前缀,后接 identifier 与若干 option;option 形式是 identifier = (literal | variable),同名 option 重复会报 Duplicate Option Name,option 顺序无关。

稳定级别

CLDR 47 明确:默认函数中只有 :string、:number、:integer 三个是 stable,其余(含 datetime 等)为 Draft。实现者与使用方都要按 Draft 对待,升级时给 Draft 函数留回归用例。

与 MF1 的四处硬差异

维度MF1MF2
占位符{0} / {name}变量必须带 $:{$name},不再有位置参数
复数内联语法 {n, plural, one{...} other{...}}.input 声明 + .match matcher + {{}} variant,靠 variant key 匹配;没有 # 特殊符号,数量自己写 {$count}
转义与字面量单引号包裹字面文本,'' 表示单引号反斜杠转义 \{、\},字面量用 |...| 包裹
兜底other 是类别* 是兜底 key;缺失直接是 Data Model Error,不是运行时降级

另外 MF1 的 select 在 MF2 里统一并入 .match,按字面量 key 匹配(函数用 :string)。

bidi 与测试

CLDR 47 修订了 MF2 的 Default Bidi Strategy,增加对 bidi 隔离符与标记的支持。直接后果是格式化输出里可能插入隔离控制字符,字符串全等断言、长度断言、快照测试都可能失败。

处理方式是先明确比对口径:按码点比对,还是只比对可见文本。具体插入哪些字符随实现与策略配置不同,需在自己的 ICU 版本上实测,不要照搬别处的期望值。

迁移检查清单

  • 运行时版本:Java/C++ 侧走 message2 API 仍是 tech preview,API 与函数集合会变。不要和 MF1 的 MessageFormat 混在同一个资源文件里,两条链路并存,不是互相替换的关系。
  • 前端:messageformat 4.0 提供 MF2 语法与转换工具,可从 MF1 语法转换,但转换后要人工检查 matcher 的 fallback 与 literal。
  • 选择器:选择器函数必须在声明里注解,否则触发 Missing Selector Annotation。
  • 函数依赖:只依赖 stable 的 :string / :number / :integer;用到 Draft 函数要在升级时留回归用例。
  • 错误收集:Syntax Error 与 Data Model Error 分开收集,后者是语法合法但语义不合法,最容易漏。
复制全文 生成海报 MessageFormat MF2 i18n 国际化 ICU CLDR 本地化

推荐文章

程序员茄子在线接单