← MessageFormat 2 · 给人看的消息怎么写 / 为什么不能直接拼字符串 待审核 1 / 8
why · 为什么需要它

为什么不能直接拼字符串

给「You have 3 new messages」这句话做多语言,看起来只是把名词复数、把数字插进去。可一旦面对几十种语言的复数规则、语序差异、性别一致、还要在句中加粗 / 加链接,朴素做法很快失控。MF2 是 Unicode 给这个老问题的新标准答案。本页先看清旧方案各自卡在哪,再看 MF2 怎么收口。

1 · 朴素拼接:语序一换就错位

"You have " + count + " messages" 这种拼接,把语序焊死在了代码里。可不同语言的主谓宾、修饰语位置千差万别;翻译时译者拿到的是被切碎的片段,既看不懂上下文,也无法调整顺序。

拼接的根本问题:句子结构属于语言,不该写死在代码里。一旦拼接,译者就失去了重排语序、按本地习惯调整的能力——这是国际化里最常见也最隐蔽的 bug 来源。

2 · printf / 占位符:解决插值,无法处理复数

printf("You have %d messages", n) 让译者能整句翻译、还能用位置参数调序,是一大进步。但它对复数无能为力:英语 1→message、其余→messages;而波兰语有 one/few/many/other 四档,阿拉伯语多达六档。靠 %dif 手写,既写不全也维护不动。

3 · ICU MessageFormat 1:能力够了,语法难读

上一代标准 ICU MessageFormat(MF1)把复数 / 选择内联进一套花括号语法,能力终于完整。代价是深层嵌套:逻辑和文本搅在一起,括号一多译者就容易写错,自定义格式化器的扩展点也很有限。

MF2 把「选择逻辑」(声明 + .match)与「各分支文本」(平铺的变体)分离,而非嵌套——读、改、翻都更可靠。
同一条「N 条消息」 写法
ICU MF1 {count, plural, one {You have # message} other {You have # messages}}
MF2 .input {$count :number} · .match $count · one {{…}} · * {{…}}

4 · MF2 怎么收口

这条同样的消息,在 MF2 里这样写,并在此实时运行:

它带来的几样东西,后面各页逐一展开:

能力 MF2 怎么做 详见
插值 {$name},自动 bidi 隔离 消息
本地化格式 :number / :date / :currency … 一套可移植选项 函数
复数 / 性别 / 序数 平铺变体 + 多选择子,* 作为 fallback 选择
逻辑复用 .input / .local 声明一次处处用 声明
富文本 {#b}…{/b} 抽象标记,不绑 HTML 标记
健壮性 运行期错误回退、不中断 错误

它不是又一个 JS 库,而是 Unicode 标准。同一条 MF2 消息能跑在 ICU4C / ICU4J / JavaScript 等多种实现上,并正进入 ECMA-402(Intl.MessageFormat 提案)。译文资产因此可跨平台、跨语言栈复用,而不是绑死在某个框架的私有格式里。