UI 国际化

许多类型的数据需要格式化为特定区域设置的格式,或者需要特定区域设置的 API 操作。

Gecko 提供了一套丰富的区域设置感知 API,用于执行以下操作

  • 日期和时间格式化

  • 数字格式化

  • 搜索

  • 排序

  • 复数规则

  • 日历和区域设置信息

注意

大多数 API 由 Unicode 项目 CLDRICU 支持,并且专注于启用前端代码国际化,这意味着大多数 API 主要在 JavaScript 中可用,而 C++ 和 Rust 仅公开了一小部分。

JavaScript 国际化 API

数据国际化 API 在 JavaScript 标准 ECMA 402 中被形式化。所有主要的 JS 环境都支持这些 API。

最好查阅 MDN 文章以了解 Intl API 的当前状态。Mozilla 对该 API 具有出色的支持,并依赖它来满足其大部分需求。但是,在处理 Firefox UI 时,应使用 Services.intl 包装器。

Services.intl

Services.intl 是 JS Intl API 的扩展,在使用具有 chrome 权限的 Gecko 应用用户界面时应该使用。

该 API 提供与 Intl.* 相同的对象和方法,但会根据 Gecko 应用用户首选项对其进行微调,包括匹配操作系统首选项和其他 Web 内容公开的 JS Intl API 无法使用的区域设置选择。

例如,以下是如何使用常规 Intl.DateTimeFormat 进行区域设置感知日期格式化的示例

let rtf = new Intl.DateTimeFormat(navigator.languages, {
  year: "numeric",
  month: "long",
  day: "numeric"
});
let value = rtf.format(new Date());

它将在将日期格式化为用户区域设置方面做得很好,但它只能使用公开给 Web 的自定义位,这些位基于用户广播到 Web 的区域设置和任何其他设置。

但这忽略了可能告知格式化的信息位。

诸如 Intl.* 之类的公共 API 将无法查看操作系统的区域首选项。它还将尊重诸如“抵制指纹识别”之类的设置,方法是屏蔽其时区和区域设置。

在处理 Web 内容时,这是一个公平的权衡,但在大多数情况下,Gecko 应用程序的特权 UI 应该能够访问所有这些附加位,并且不受防指纹屏蔽的影响。

mozIntl 是一个简单的包装器,在其最简单的形式下工作方式完全相同。它公开在 Services.intl 对象上,并且可以使用就像常规的 Intl API 一样

let rtf = new Services.intl.DateTimeFormat(undefined, {
  year: "numeric",
  month: "long",
  day: "numeric"
});
let value = rtf.format(new Date());

不同之处在于,此 API 现在将使用为 Gecko 定义的区域设置集,并且还将尊重 Gecko 将从操作系统获取的其他区域首选项。

因此,在处理 Gecko 应用程序 UI 时,始终建议使用 Services.intl 包装器。

其他 API

除了包装 Intl API 之外,mozIntl 还通过为现有 API 提供许多功能,以及全新的 API。

许多这些扩展正在标准化过程中,但 Gecko 开发人员已经可以将其用于内部使用。

以下是当前扩展列表

mozIntl.DateTimeFormat

mozIntl 中的 DateTimeFormat 获取其他选项,为 API 提供更高的简单性和一致性。

  • timeStyledateStyle 可以取值 shortmediumlongfull。这些选项可以替换手动列出诸如 yeardayhour 等标记,并将为所选区域设置组成最自然的给定样式的日期或时间格式。

强烈建议使用 timeStyledateStyle 而不是列出标记,因为不同的区域设置可能使用不同的默认样式来显示相同的标记。

附加值是使用这些样式允许 mozIntl 查看操作系统模式,这使用户能够根据自己的喜好自定义这些模式。

使用示例

let dtf = new Services.intl.DateTimeFormat(undefined, {
  timeStyle: "short",
  dateStyle: "short"
});
let value = dtf.format(new Date());

这将选择最适合当前 Gecko 应用程序区域设置的区域设置,然后可能检查操作系统区域首选项自定义,为短日期+时间样式生成正确的模式并将日期格式化为它。

mozIntl.getCalendarInfo(locale)

该 API 将为给定的区域设置代码返回以下日历信息

  • firstDayOfWeek

    范围在 1=星期一到 7=星期日内的整数,指示日历中被认为是星期第一天的日期,例如 en-US 为 7,en-GB 为 1,bn-IN 为 7

  • minDays

    范围在 1 到 7 内的整数,指示一年中第一周所需的最小天数,例如 en-US 为 1,de 为 4

  • weekend

    一个数组,其值范围在 1=星期一到 7=星期日内,指示被认为是周末一部分的星期几,例如 en-US 和 en-GB 为 [6, 7],bn-IN 为 [7](请注意,“周末”不一定是两天)

这些信息位对于处理日历数据的任何 UI 尤其有用。

示例

// omitting the `locale` argument will make the API return data for the
// current Gecko application UI locale.
let {
  firstDayOfWeek,  // 1
  minDays,         // 4
  weekend,         // [6, 7]
  calendar,        // "gregory"
  locale,          // "pl"
} = Services.intl.getCalendarInfo();

mozIntl.DisplayNames(locales, options)

DisplayNames API 用于检索国际化 API 中可用的各种术语。mozIntl.DisplayNames 扩展了标准 Intl.DisplayNames 以另外提供日期时间类型的本地化。

该 API 采用区域设置回退链列表和一个选项对象,该对象可以包含两个键

  • style 可以取值 narrowshortabbreviatedlong

  • type 可以取值 languagescriptregioncurrencyweekdaymonthquarterdayPerioddateTimeField

示例

let dateTimeFieldDisplayNames = new Services.intl.DisplayNames(undefined, {
  type: "dateTimeField",
});
dateTimeFieldDisplayNames.resolvedOptions().locale = "pl";
dateTimeFieldDisplayNames.of("year") = "rok";

let monthDisplayNames = new Services.intl.DisplayNames(undefined, {
  type: "month", style: "long",
});
monthDisplayNames.of(1) = "styczeń";

let weekdaysDisplayNames = new Services.intl.DisplayNames(undefined, {
  type: "weekday", style: "short",
});
weekdaysDisplayNames.of(1) = "pon";

let dayPeriodsDisplayNames = new Services.intl.DisplayNames(undefined, {
  type: "dayPeriod", style: "narrow",
});
dayPeriodsDisplayNames.of("am") = "AM";

mozIntl.RelativeTimeFormat(locales, options)

可用于将间隔或日期格式化为相对时间的文本表示形式的 API,例如 5 分钟前2 天后

此 API 正在标准化过程中,并且在其原始形式下不会处理任何选择最佳单元的计算。它旨在仅提供一种格式化值的方法。

mozIntl 包装器扩展了功能,提供计算并允许用户获取增量的当前最佳文本表示形式。

示例

let rtf = new Services.intl.RelativeTimeFormat(undefined, {
  style: "long", // "narrow" | "short" | "long" (default)
  numeric: "auto", // "always" | "auto" (default)
});

let now = Date.now();
rtf.formatBestUnit(new Date(now - 3 * 1000 * 60)); // "3 minutes ago"

选项 numeric 的值默认为 auto,这意味着在可能的情况下,格式化程序将使用特殊的文本术语,例如 昨天去年 等。

这些值需要原始 Intl.* API 无法提供的特定计算。例如,昨天 需要算法不仅知道时间增量,还知道 现在 是哪一天。如果现在是上午 10 点,则 15 小时前可能是昨天,但如果现在是晚上 11 点,则仍然是今天

因此,未来的 Intl.RelativeTimeFormat 将使用always 作为默认值,因为诸如15 hours ago之类的术语独立于当前时间。

注意

在当前形式下,API 仅应用于格式化独立值。如果没有额外的首字母大写规则,它不能在句子中自由使用。

mozIntl.getLanguageDisplayNames(locales, langCodes)

返回格式化以显示的语言名称列表的 API。

示例

let langs = getLanguageDisplayNames(["pl"], ["fr", "de", "en"]);
langs === ["Francuski", "Niemiecki", "Angielski"];

mozIntl.getRegionDisplayNames(locales, regionCodes)

返回格式化以显示的地区名称列表的 API。

示例

let regs = getRegionDisplayNames(["pl"], ["US", "CA", "MX"]);
regs === ["Stany Zjednoczone", "Kanada", "Meksyk"];

mozIntl.getLocaleDisplayNames(locales, localeCodes)

返回格式化以显示的地区名称列表的 API。

示例

let locs = getLocaleDisplayNames(["pl"], ["sr-RU", "es-MX", "fr-CA"]);
locs === ["Serbski (Rosja)", "Hiszpański (Meksyk)", "Francuski (Kanada)"];

mozIntl.getAvailableLocaleDisplayNames(type)

返回给定类型可用的区域设置显示名称代码列表的 API。可用的类型为:“language”,“region”。

示例

let codes = getAvailableLocaleDisplayNames("region");
codes === ["au", "ae", "af", ...];

最佳实践

处理数据国际化时最重要的最佳实践是在尽可能接近实际 UI 的地方执行它;在 UI 显示之前。

这种做法的原因是国际化数据被认为是“不透明的”,这意味着任何代码都不应该尝试对其进行操作。延迟解析还可以增加数据以当前区域设置选择进行格式化,而不是过早地格式化和缓存的可能性。

切勿尝试搜索、连接或以任何其他方式更改 API 的输出非常重要。一旦格式化,对输出唯一要做的就是将其呈现给用户。

测试

以上内容在测试环境中也很重要。尝试编写测试以验证使用国际化数据的 UI 输出是一个常见错误。

用于创建数据格式化版本的基础数据集可能会并且将会随着时间推移而发生变化,这既是由于数据集改进,也是由于语言和地区偏好的变化。这意味着尝试验证精确输出的测试将需要更高水平的维护,并且会保持脆弱性。

大多数 API 提供特殊方法,例如 resolvedOptions,应改为使用该方法来验证输出是否符合预期。

未来扩展

如果您发现需要当前不支持的其他国际化 API,您可以在这里验证 API 建议是否已经在进行中,并在组件 Core::Internationalization 中提交错误以请求它。