ICU (Unicode 国际化组件)

Unicode 国际化库,提供字符串排序、转换、格式化等多语言处理基础功能。

ICU (International Components for Unicode) 是由 Unicode 联盟开发和维护的国际化 (i18n) 库。提供 C/C++ 版 (ICU4C) 和 Java 版 (ICU4J),被操作系统、浏览器和编程语言运行时等众多平台采用作为多语言处理基础。Android、iOS、macOS、Windows、Chrome、Firefox 等日常使用的软件内部都在运行 ICU。

ICU 提供的功能涵盖广泛。在字符串的排序规则 (支持区域设置的排序) 方面,它能准确处理各语言不同的字母顺序。例如瑞典语中"ö"排在"z"之后,而德语中则被当作"o"的变体处理。在日期、数字和货币的格式化方面,它会进行把"2025/01/15"在英语圈显示为"January 15, 2025"、在德语圈显示为"15. Januar 2025"这样的转换。

文本边界检测 (BreakIterator) 是与字符计数关系尤为密切的功能。它能根据语言准确判定词的分界、句的分界以及换行位置。对于日文和中文这样不用空格分隔词语的语言,需要基于词典的解析,ICU 在内部完成这一处理。JavaScript 的 Intl.Segmenter API 就是把 ICU 的文本边界检测功能公开到 Web 上的产物。

不过,分词的结果依赖于词典,因此若把日语的词数计数交给 ICU,就需要事先确认结果。把"東京都に住んでいます"按词切分,会得到"東京 / 都 / に / 住 / んで / い / ます",其中混有与人的语感不同的分界。如果要制作把词数作为规格对外呈现的工具,应当在最初就决定是直接采用这套切分规则,还是另行准备形态素解析器。

Node.js 从 v13 开始默认同时附带 ICU 的完整数据集,Intl API 的内部实现使用了 ICU。在此之前的版本只包含部分 ICU 数据,存在某些区域设置无法正常工作的问题。浏览器中的 Intl.Collator、Intl.DateTimeFormat、Intl.NumberFormat 等 API 也以 ICU 为基础。

ICU 的排序规则算法 (UCA: Unicode Collation Algorithm) 通过多级比较来处理各区域设置不同的排序顺序。它是第 1 级比较基本字符、第 2 级比较重音符号、第 3 级比较大小写的层次结构。借助这一机制,可以根据应用程序的需求控制把"cafe"和"café"视为相同还是加以区别。在 JavaScript 中,与之对应的是 Intl.Collator 的 sensitivity 选项:设为 base 时两者被判定为相等 (比较结果为 0),设为 variant 时则作为不同的词排出顺序。搜索的匹配判定用较宽松的级别、列表的排列顺序用较严格的级别,这样区分使用是惯例做法。

在与字符计数的关联上,ICU 的书写素簇 (grapheme cluster) 分割功能很重要。要在包含表情符号家族序列 (👨‍👩‍👧‍👦) 或组合字符的文本中准确计算"用户所认知的 1 个字符",像 ICU 这样的高级文本处理库不可或缺。在单纯的码位数或字节数无法得到准确字符数的场景中,ICU 的功能才真正发挥价值。实际数 1 个家族表情符号时,会得出三种值:书写素簇为 1,码位为 7,JavaScript 的 length (UTF-16 单位) 为 11。采用哪种数法是规格的选择,而不是库的优劣。

使用 ICU 时,实务上的注意点有 2 个。一个是存在削减了 ICU 数据的构建版本。当运行环境只持有相当于英语的数据时,指定的区域设置会被默默替换为最接近的可用区域设置,不报错而只有排列顺序和格式发生变化。另一个是 ICU 及其引用的区域设置数据会被更新这一点。把格式化后的字符串本身固定为期望值的自动化测试,仅因运行环境的 ICU 版本不同就会失败。测试中不要直接比较格式化结果,而应验证比较结果的符号、分割数这类稳定的性质,这样更为稳固。

分享这篇文章