ICU

简介

国际化(i18n,“i”然后 18 个字母然后“n”)是处理与特定语言环境相关的数据的过程。

  • 表示五美元的数字 5 可能被格式化为

    • 美式英语中的“$5.00”,

    • 加拿大英语中的“US$5.00”,或者

    • 法语中的“5,00 $US”。

  • 电话簿中人员姓名列表的排序

    • 在英语中按字母顺序排列;但是

    • 在德语中,“ä”/“ö”/“ü”通常可以与“ae”/“oe”/“ue”互换,按字母顺序排列,但带变音符号的元音被视为其两个元音的对应物。

  • 代码为“CHF”的货币可能被格式化为

    • 英语中的“Swiss Franc”,但

    • 法语中的“franc suisse”。

  • Unix 时间 1590803313070 可能格式化为时间字符串

    • 美式英语中的“9:48:33 PM Eastern Daylight Time”,但

    • 德语中的“21:48:33 Nordamerikanische Ostküsten-Sommerzeit”。

i18n 包含的内容远不止这些,但您应该了解基本概念。

SpiderMonkey 和 Gecko 中的国际化

SpiderMonkey 通过ECMAScript 国际化 API和全局Intl对象实现了广泛的 i18n 功能。Gecko 需要 i18n 功能来实现文本整形、某些上下文中的排序操作以及其他各种功能。

SpiderMonkey 和 Gecko 使用ICU(Unicode 国际化组件)来实现许多低级 i18n 操作。(换行符,在intl/lwbrk中实现,是一个值得注意的例外。)Gecko 和 SpiderMonkey 还使用 ICU 对某些与 i18n 相关的操作(例如 Unicode 规范化)的实现。

ICU 日期/时间格式化功能需要广泛了解时区名称以及区域转换发生的时间。IANA tzdata数据库提供此信息。

最后需要注意的是:ICU 仔细依赖于精确的 Unicode 版本。SpiderMonkey 和 Gecko 的其他部分对精确的 Unicode 版本有单独的依赖关系。ICU 和相关组件的更新必须与这些更新同步,以便 SpiderMonkey 的整体以及包括 SpiderMonkey 在内的 Gecko 的整体同步升级到新的 Unicode 版本。[1]

使用 ICU 构建 SpiderMonkey 或 Gecko

SpiderMonkey 和 Gecko 可以使用intl/icu/source中定期更新的 ICU 副本(使用intl/tzdata/source中的时区数据)构建,也可以使用系统提供的 ICU 库构建(依赖于其自身的tzdata信息)。在配置时传递--with-system-icu以使用系统 ICU。(使用系统 ICU 将禁用某些Intl功能,例如历史上准确的时区计算,这些功能在没有精确控制的 ICU 的情况下无法轻松支持。)随着 Gecko 依赖于较新 ICU 版本中的功能和错误修复,ICU 版本要求会相当快地提高。如果您尝试使用不受支持的 ICU,您将收到构建错误。

SpiderMonkey 的Intl API 可以通过配置--with-intl-api(默认值)或--without-intl-api来构建或禁用。没有Intl API 的 SpiderMonkey 不需要 ICU。但是,如果您在没有Intl API 的情况下构建,则某些非Intl JavaScript 功能将不存在(String.prototype.normalize)或无法完全工作(例如,String.prototype.toLocale{Lower,Upper}Case将不尊重提供的语言环境,并且各种toLocaleString函数具有尽力而为的行为)。

在 SpiderMonkey 和 Gecko 中使用 ICU 功能

ICU 头文件被 Gecko 构建系统视为系统头文件,因此必须在config/system-headers.mozbuild中列出。希望使用 ICU 功能的代码可以使用#include "unicode/unorm.h"或类似的方法来做到这一点。

Gecko 和 SpiderMonkey 代码可以使用 ICU 的稳定 C API (ICU4C)。这些函数是稳定的,并且在 ICU 更新时不应该更改。(ICU4C 的enum初始化程序并不总是稳定的:虽然初始化程序值是稳定的,但有时会添加新的初始化程序,可能是在#ifdef U_HIDE_DRAFT_API后面。对于详尽的switch,这可能是必要的,以在一些case周围添加#ifdef。)

强烈建议 Gecko 和 SpiderMonkey 不要使用 ICU 的 C++ API(不幸的是,包括所有智能指针类),因为 C++ API 没有提供 ICU4C 的兼容性保证。在极少数情况下,当不存在稳定选项时,我们容忍使用 C++ API。但 API 必须“看起来”相当稳定,我们通常希望与上游开始讨论添加一个稳定的 API 以供最终使用。使用namespace icu中的符号来访问 ICU C++ 功能。在您开始执行任何此类操作之前,请与当前引入的 ICU 所有者(目前是 Jeff Walden)进行沟通!

SpiderMonkey 和 Gecko 引入的 ICU

构建系统

ICU 的构建系统位于config/external/icuintl/icu/icu_sources_data.py中。我们生成一个与 Mozilla 兼容的构建系统,而不是使用 ICU 的构建系统。构建系统由 SpiderMonkey 和 Gecko 共享。

ICU 包含我们从未使用过的功能,因此我们不会天真地编译所有功能。我们从intl/icu/source/{common,i18n}/Makefile.in中提取要编译的文件列表,然后在更新 ICU 时应用手动维护的未使用文件列表(存储在intl/icu_sources_data.py中)。

语言环境和时区数据

ICU 包含大量原始语言环境数据:每个语言环境的格式化特征、每个语言环境的货币和语言等内容的字符串、本地化的时区说明符等等。这些数据存储在intl/icu/source/data中的人类可读文件中。intl/tzdata/source中的时区数据以部分编译的格式存储(其中一些仅部分人类可读)。

但是,正常的 Gecko 构建永远不会使用这些文件!相反,ICU 和tzdata数据都预编译成一个大型的、特定于字节序的icudtNNE.datNN = ICU 版本,E = 字节序)文件。[2]该文件被添加到config/external/icu/data/中,并被检入 Mozilla 代码库,以便直接集成到 Gecko/SpiderMonkey 构建中。出于大小考虑,只有小端版本被检入代码库。在构建过程中,根据需要将其转换为大端版本。

ICU 的语言环境数据涵盖所有 ICU 国际化功能,包括我们从未需要的功能。在编译icudtNNE.dat时,我们使用intl/icu/data_filter.json数据过滤器来调整语言环境数据的大小。删除过多的数据不会必然导致构建中断,因此,为了检测错误,我们拥有我们实际使用的语言环境数据的自动化测试非常重要。

ICU 和 CLDR 的本地修补

我们通常不修补我们的 ICU 副本,除非有迫切需要。当我们确实进行修补时,我们通常只应用经过审查并在上游合并的合理的小补丁(这样,当我们下次更新 ICU 时,我们的补丁就会过时)。

本地补丁存储在intl/icu-patches目录中。它们在 ICU 更新时应用,因此仅仅替换 ICU 文件不会在 ICU 更新之间保留更改。

修补 ICU 还允许修补 CLDR 的某些部分和用途,CLDR 是 ICU 操作的数据支持。请注意,这并不包括字符数据,字符数据是单独更新的,并且任何此类修补都不会影响任何其他 CLDR 用途。特别是,Fluent 本地化依赖于 Rust 箱,而 Rust 箱本身又直接依赖于与 ICU 分离的 CLDR 数据。任何 CLDR 补丁都应保持合理的小尺寸;添加对新语言环境支持等较大的更改应在上游完成。

更新引入的代码

更新引入的与 i18n 相关的代码的过程是自动化的。我们使用一系列 shell 和 Python 脚本来完成这项工作。

更新 ICU

新的 ICU 版本会在 icu-announce 邮件列表中公布。发布候选版本和正式版本都会在这里发布。当发布候选版本时,尝试更新 ICU 是一个好主意,以防存在一些严重的问题(尤其是那些难以通过本地补丁修复的问题)。

intl/update-icu.sh 将我们的 ICU 更新到指定的 ICU 版本:[3]

$ cd "$topsrcdir/intl"
$ # Ensure certain Python modules in the tree are accessible when updating.
$ export PYTHONPATH="$topsrcdir/python/mozbuild/"
$ #               <URL to ICU Git>                       <release tag name>
$ ./update-icu.sh https://github.com/unicode-org/icu.git release-67-1

但通常您希望更新到对应 ICU 维护分支的最新提交,以便获取发布后修复的错误。

$ cd "$topsrcdir/intl"
$ # Ensure certain Python modules in the tree are accessible when updating.
$ export PYTHONPATH="$topsrcdir/python/mozbuild/"
$ #               <URL to ICU Git>                       <maintenance name>
$ ./update-icu.sh https://github.com/unicode-org/icu.git maint/maint-67

更新 ICU 还会更新语言标签注册表(记录正确实现 Intl 功能所需的语言标签语义)。因此,在运行此脚本后,可能需要更新 SpiderMonkey 的语言标签处理 [4]。请参见下文讨论 make_intl_data.pylangtags 模式。

update-icu.sh 旨在实现可重复性,而不是无人值守的可运行性。它下载 ICU 源代码,修剪各种不相关的文件,用新文件替换 intl/icu/source,然后以固定的顺序盲目应用本地补丁。

通常,本地补丁无法应用,或者必须应用新的补丁才能成功构建。在这种情况下,您必须手动编辑 update-icu.sh,使其仅在应用某些补丁后中止,手动进行必要的更改,手动生成新的/更新的补丁文件,然后小心地重新尝试更新。(过去更新过 ICU 的人,通常是 jwalden 和 anba,遵循这个笨拙的过程,并且没有很好的改进方法。)

任何时候更新 ICU,您都需要完全重新构建您正在构建的 SpiderMonkey 或 Gecko。对于 SpiderMonkey,请删除您的对象目录并从头开始重新配置。对于 Gecko,请更改顶级 CLOBBER 文件中的消息。

更新 tzdata

ICU 包含一个 tzdata 的副本,但该副本是 ICU 版本最终确定时当前的 tzdata 版本。时区数据变化频率远高于此:每次某个国家/地区的立法机构或独裁者决定更改时区时,都会发生变化。[5] tz-announce 邮件列表会在发生更改时发布公告。(请注意,我们无法在发布时立即更新:必须更新 ICU 的 icu-data 存储库,然后才能更新我们的 tzdata。)

因此,在您更新 ICU 之后或出现新的 tzdata 版本时,您都需要更新我们导入的 tzdata 文件。(如果您确实需要更新时区数据,请注意,您还需要额外更新 SpiderMonkey 的时区处理,这将在下面进一步介绍。)这还会适当地更新 config/external/icu/data/icudtNNE.dat。(如果您刚刚运行了 update-icu.sh,它会警告您需要执行此操作。[6]

首先,确保您的系统上有一个可用的 icupkg[7] 然后运行 update-tzdata.sh 脚本以更新 intl/tzdataicudtNNE.dat

$ cd "$topsrcdir/intl"
$ ./update-tzdata.sh 2020a # or whatever the latest release is

如果必须在主干上更新 tzdata,则几乎肯定需要将更新移植到 Beta 和 ESR。不要尝试移植文字补丁;只需运行此处记录的相应命令即可。

更新 SpiderMonkey Intl 数据

SpiderMonkey 本身不能盲目调用 ICU 来执行每个 i18n 操作,因为有时 ICU 的行为偏离了 Web 规范的要求。因此,当 ICU 更新时,我们也必须更新 SpiderMonkey 本身(包括各种生成的测试)。此类更新是使用 js/src/builtin/make_intl_data.py 的各种模式执行的。

更新 SpiderMonkey 时区处理

ECMAScript 国际化 API 要求根据 IANA 语义解释时区标识符(America/New_YorkAntarctica/McMurdo 等)。不幸的是,ICU 并没有精确地实现这些语义。(有关详细信息,请参阅 js/src/builtin/intl/SharedIntlData.h 中的注释。)因此,SpiderMonkey 必须根据 IANA 中存在但 ICU 中不存在的内容,以及 ICU 中存在但 IANA 中不存在的内容进行某些预处理和后处理。

使用 make_intl_data.pytzdata 模式更新时区信息。

$ cd "$topsrcdir/js/src/builtin/intl"
$ # make_intl_data.py requires yaml.
$ export PYTHONPATH="$topsrcdir/third_party/python/PyYAML/lib3/"
$ python3 ./make_intl_data.py tzdata

tzdata 模式接受两个通常不需要的可选参数。

  • ``–tz`` 将使用来自包含原始 tzdata 源代码的本地 tzdata/ 目录的数据(请注意,这与 intl/tzdata/source 中的内容不同)。它可能有助于调试更新期间出现的问题。

  • ``–ignore-backzone`` 将省略 1970 年之前的时间信息。SpiderMonkey 和 Gecko 默认包含此信息。但是,由于(根据故意策略)1970 年之前的信息的可靠性不如 1970 年之后的数据,并且回溯区域数据存在大小成本,因此 SpiderMonkey 嵌入或自定义 Gecko 构建可能会决定省略它。

更新 SpiderMonkey 语言标签处理

语言标签(ende-CHar-u-ca-islamicc 等)是指定本地化特征的主要方法。ECMAScript 国际化 API 支持某些依赖于语言标签注册表当前状态的操作(存储在 Unicode 通用语言环境数据存储库 CLDR 中,这是一个包含所有特定于语言环境特征的存储库),该注册表指定子标签语义。

  • Intl.getCanonicalLocalesIntl.Locale 必须用其首选形式替换别名子标签。例如,ar-u-ca-islamic-civil 使用首选的伊斯兰历子标签,而 ar-u-ca-islamicc 使用别名。

  • Intl.Locale.prototype.maximizeIntl.Locale.prototype.minimize 接受一个语言标签,并向其添加或删除“可能”的子标签。例如,de 最有可能指在德国使用拉丁文字体的德语,因此它最大化为 de-Latn-DE,反之,de-Latn-DE 最小化为简单的 de

这些决策会随着时间的推移而变化:随着国家/地区的更改 [8]、习俗的更改、语言在地区的流行程度的变化等。

使用 make_intl_data.pylangtags 模式将语言标签信息更新到与 ICU 使用的相同 CLDR 版本。

$ cd "$topsrcdir/js/src/builtin/intl"
$ # make_intl_data.py requires yaml.
$ export PYTHONPATH="$topsrcdir/third_party/python/PyYAML/lib3/"
$ python3 ./make_intl_data.py langtags

使用的 CLDR 版本将打印在 CLDR 敏感的生成文件的标题中。例如,intl/components/src/LocaleGenerated.cpp 当前以以下内容开头:

// Generated by make_intl_data.py. DO NOT EDIT.
// Version: CLDR-37
// URL: https://unicode.org/Public/cldr/37/core.zip
更新 SpiderMonkey 货币支持

货币在其首选格式中使用不同的小数位数。大多数货币使用两位小数;少数货币不使用小数位或使用其他一些数字。货币小数位由 ISO 维持,必须随着货币首选小数位的更改或出现不使用两位小数的新货币而更新。

货币更新并不常见,因此很少需要更新货币信息。新闻稿 定期发送有关更改的更新。

使用 make_intl_data.pycurrency 模式更新货币小数位信息。

$ cd "$topsrcdir/js/src/builtin/intl"
$ # make_intl_data.py requires yaml.
$ export PYTHONPATH="$topsrcdir/third_party/python/PyYAML/lib3/"
$ python3 ./make_intl_data.py currency
更新 SpiderMonkey 测量格式支持

Intl API 支持将数字格式化为度量单位(例如,“17 米”或“每秒 42 米”)。它指定了必须支持的单位列表,我们在 js/src/builtin/intl/SanctionedSimpleUnitIdentifiers.yaml 中集中记录了这些单位,我们验证这些单位是否受 ICU 支持,并从中生成支持文件。

如果 Intl 支持的单位列表被更新,则需要进行两个单独的更改。

首先,必须更新 intl/icu/data_filter.json 以合并新单位的本地化字符串。这些字符串存储在 icudtNNE.dat 中,因此您需要重新更新 ICU(并且如果自上次 ICU 更新以来已更新,可能还需要重新导入 tzdata)以重写该文件。

其次,使用 make_intl_data.pyunits 模式更新 SpiderMonkey 中的单位处理和相关测试。

$ cd "$topsrcdir/js/src/builtin/intl"
$ # make_intl_data.py requires yaml.
$ export PYTHONPATH="$topsrcdir/third_party/python/PyYAML/lib3/"
$ python3 ./make_intl_data.py units
更新 SpiderMonkey 数字系统支持

Intl API 还支持以各种数字系统格式化数字(例如,使用拉丁数字的“123”或使用汉语十进制数字的“一二三”)。我们必须支持的数字系统列表存储在 js/src/builtin/intl/NumberingSystems.yaml 中。我们验证这些数字系统是否受 ICU 支持,并从中生成支持文件。

当需要更新支持的数字系统列表时,使用 numbering 模式运行 make_intl_data.py 以更新它以及 SpiderMonkey 中的相关测试。

$ cd "$topsrcdir/js/src/builtin/intl"
$ # make_intl_data.py requires yaml.
$ export PYTHONPATH="$topsrcdir/third_party/python/PyYAML/lib3/"
$ python3 ./make_intl_data.py numbering