介绍 JavaScript 国际化 API

Firefox 29 在半年前发布,所以这篇文章已经很久没有更新了。不过我想停下来讨论一下在那个版本中首次发布的国际化API(并且通过了所有测试!)。Norbert Lindenberg编写了大部分实现,我进行了审查,现在维护着它。(Makoto Kato的工作应该很快将其带到 Android 上;b2g 可能会因为一些特定于 b2g 的障碍而花费更长时间。敬请期待。)

什么是国际化?

国际化(简称i18n — i,18 个字符,n)是指以一种能够轻松适应来自不同地区、使用不同语言的用户群体的应用程序编写应用程序的过程。很容易在不经意间假设用户来自同一个地方并使用同一种语言,从而犯下错误,尤其是在你甚至不知道自己做出了假设的情况下。

function formatDate(d)
{
  // Everyone uses month/date/year...right?
  var month = d.getMonth() + 1;
  var date = d.getDate();
  var year = d.getFullYear();
  return month + "/" + date + "/" + year;
}

function formatMoney(amount)
{
  // All money is dollars with two fractional digits...right?
  return "$" + amount.toFixed(2);
}

function sortNames(names)
{
  function sortAlphabetically(a, b)
  {
    var left = a.toLowerCase(), right = b.toLowerCase();
    if (left > right)
      return 1;
    if (left === right)
      return 0;
    return -1;
  }

  // Names always sort alphabetically...right?
  names.sort(sortAlphabetically);
}

JavaScript 的历史 i18n 支持很差

传统JS 中的 i18n 感知格式使用各种 toLocaleString() 方法。生成的字符串包含实现选择提供的任何细节:无法选择和选择(您是否需要在该格式化的日期中包含星期几?年份是否无关紧要?)。即使包含了正确的细节,格式也可能错误e.g.十进制时需要百分比。而且你不能选择区域设置。

至于排序,JS 几乎没有提供任何有用的区域设置敏感文本比较(排序)函数。localeCompare() 存在,但接口非常笨拙,不适合与sort 一起使用。它也没有允许选择区域设置或特定排序顺序。

这些限制非常糟糕,以至于——当我得知时非常惊讶!——需要 i18n 功能的严肃 Web 应用程序(最常见的是显示货币的金融网站)将打包数据,将其发送到服务器,让服务器执行操作,然后将其发送回客户端。仅为了格式化金额而进行服务器往返。真是太糟糕了。

一个新的 JS 国际化 API

新的ECMAScript 国际化 API 极大地提高了 JavaScript 的 i18n 功能。它提供了格式化日期和数字以及排序文本所需的所有花饰。区域设置是可选择的,如果请求的区域设置不受支持,则会回退。格式化请求可以指定要包含的特定组件。支持百分比、有效数字和货币的自定义格式。公开许多排序选项以用于排序文本。如果您关心性能,现在可以一次性完成选择区域设置和处理选项的预先工作,而不是每次执行依赖区域设置的操作时都进行一次。

也就是说,该 API 不是万能药。该 API 仅提供“尽力而为”。精确的输出几乎总是故意未指定。实现可以合法地仅支持oj 区域设置,或者可以忽略(几乎所有)提供的格式化选项。大多数实现将对许多区域设置提供高质量的支持,但这并非保证(尤其是在资源受限的系统上,例如移动设备)。

在幕后,Firefox 的实现依赖于Unicode 国际组件 库(ICU),而 ICU 又依赖于Unicode 通用语言环境数据存储库CLDR)语言环境数据集。我们的实现是自托管的:ICU 之上的大部分实现都是用 JavaScript 本身编写的。我们一路走来遇到了一些障碍(我们以前从未自托管过如此庞大的东西),但都没有什么大不了的。

Intl 接口

i18n API 位于全局 Intl 对象上。Intl 包含三个构造函数:Intl.CollatorIntl.DateTimeFormatIntl.NumberFormat。每个构造函数都创建一个对象,公开相关的操作,有效地为操作缓存区域设置和选项。创建这样的对象遵循以下模式

var ctor = "Collator"; // or the others
var instance = new Intl[ctor](locales, options);

<var>locales</var> 是一个字符串,指定一个语言标签 或包含多个语言标签的类数组对象。语言标签是类似 en(一般英语)、de-AT(奥地利使用的德语)或 zh-Hant-TW(台湾使用的汉语,使用繁体中文)的字符串。语言标签也可以包含“Unicode 扩展”,格式为 -u-key1-value1-key2-value2...,其中每个键都是一个“扩展键”。各种构造函数对这些进行特殊解释。

<var>options</var> 是一个对象,其属性(或通过评估为 undefined 的不存在)确定格式化程序或排序器如何运行。它的确切解释由各个构造函数决定。

给定语言环境信息和选项,实现将尝试尽可能地产生最接近“理想”行为的行为。Firefox 支持 400 多种语言环境的排序和 600 多种语言环境的日期/时间和数字格式,因此您可能关心的语言环境很可能(但不能保证)得到支持。

Intl 通常不提供任何关于特定行为的保证。如果请求的语言环境不受支持,Intl 允许尽力而为的行为。即使语言环境受支持,行为也没有严格指定。不要假设特定的一组选项对应于特定的格式。整个格式(涵盖所有请求的组件)的措辞可能因浏览器而异,甚至因浏览器版本而异。各个组件的格式未指定:short 格式的星期几可能是“S”、“Sa”或“Sat”。Intl API 不打算公开完全指定的行为。

日期/时间格式化

选项

日期/时间格式化的主要选项属性如下

weekdayera
"narrow""short""long"。(era 通常指的是日历系统中比年份更长的划分:BC/AD现任日本天皇的统治时期,或其他。)
month
"2-digit""numeric""narrow""short""long"
year
day
hourminutesecond
"2-digit""numeric"
timeZoneName
"short""long"
timeZone
不区分大小写的 "UTC" 将相对于UTC 进行格式化。类似 "CEST""America/New_York" 的值不一定受支持,并且目前在 Firefox 中不起作用。

这些值不会映射到特定的格式:请记住,Intl API 几乎从不指定确切的行为。但意图是 "narrow""short""long" 会产生大小相对应的输出——例如,“S”或“Sa”、“Sat”和“Saturday”。(输出可能模棱两可:Saturday 和 Sunday 都会产生“S”。)"2-digit""numeric" 映射到两位数的数字字符串或完整长度的数字字符串——例如,“70”和“1970”。

最终使用的选项在很大程度上是请求的选项。但是,如果您没有专门请求任何 weekday/year/month/day/hour/minute/second,则 year/month/day 将添加到您提供的选项中。

除了这些基本选项之外,还有一些特殊选项

hour12
指定小时是否为 12 小时格式或 24 小时格式。默认值通常取决于区域设置。(有关午夜是零基还是十二基以及是否显示前导零的详细信息也取决于区域设置。)

还有两个特殊属性,localeMatcher(接受 "lookup""best fit")和 formatMatcher(接受 "basic""best fit"),两者都默认为 "best fit"。这些会影响如何选择正确的区域设置和格式。这些的使用案例有点深奥,所以您可能应该忽略它们。

以语言环境为中心的选项

DateTimeFormat 还允许使用自定义的日历和数字系统进行格式化。这些细节实际上是语言环境的一部分,因此它们在语言标签中的 Unicode 扩展中指定。

例如,泰国使用的泰语的语言标签为 th-TH。回想一下,Unicode 扩展的格式为 -u-key1-value1-key2-value2...。日历系统键为 ca,数字系统键为 nu。泰语数字系统的值为 thai,汉语日历系统的值为 chinese。因此,要以这种整体方式格式化日期,我们将包含这两个键/值对的 Unicode 扩展附加到语言标签的末尾:th-TH-u-ca-chinese-nu-thai

有关各种日历和数字系统的更多信息,请参阅完整的 DateTimeFormat 文档

示例

创建 DateTimeFormat 对象后,下一步是使用它通过方便的 format() 函数来格式化日期。方便的是,此函数是一个绑定函数:您不必直接在 DateTimeFormat 上调用它。然后向它提供一个时间戳或Date 对象。

将所有内容放在一起,以下是一些如何为特定用途创建 DateTimeFormat 选项的示例,以及 Firefox 中的当前行为。

var msPerDay = 24 * 60 * 60 * 1000;

// July 17, 2014 00:00:00 UTC.
var july172014 = new Date(msPerDay * (44 * 365 + 11 + 197));

让我们为美国使用的英语格式化一个日期。让我们包括两位数的月份/日期/年份,加上两位数的小时/分钟,以及一个简短的时区以明确该时间。(在另一个时区中,结果显然会不同。)

var options =
  { year: "2-digit", month: "2-digit", day: "2-digit",
    hour: "2-digit", minute: "2-digit",
    timeZoneName: "short" };
var americanDateTime =
  new Intl.DateTimeFormat("en-US", options).format;

print(americanDateTime(july172014)); // 07/16/14, 5:00 PM PDT

或者让我们为葡萄牙语做类似的事情——理想情况下是巴西使用的,但如果需要,葡萄牙语也可以。让我们选择一个更长的格式,包括完整的年份和拼写的月份,但将其设置为 UTC 以便于移植。

var options =
  { year: "numeric", month: "long", day: "numeric",
    hour: "2-digit", minute: "2-digit",
    timeZoneName: "short", timeZone: "UTC" };
var portugueseTime =
  new Intl.DateTimeFormat(["pt-BR", "pt-PT"], options);

// 17 de julho de 2014 00:00 GMT
print(portugueseTime.format(july172014));

瑞士每周火车时刻表如何以紧凑的、UTC 格式显示?我们将尝试从最流行到最不流行的官方语言,以选择最有可能被阅读的语言。

var swissLocales = ["de-CH", "fr-CH", "it-CH", "rm-CH"];
var options =
  { weekday: "short",
    hour: "numeric", minute: "numeric",
    timeZone: "UTC", timeZoneName: "short" };
var swissTime =
  new Intl.DateTimeFormat(swissLocales, options).format;

print(swissTime(july172014)); // Do. 00:00 GMT

或者让我们尝试一个日本博物馆中绘画中描述性文字中的日期,使用日本日历,包括年份和纪元

var jpYearEra =
  new Intl.DateTimeFormat("ja-JP-u-ca-japanese",
                          { year: "numeric", era: "long" });

print(jpYearEra.format(july172014)); // 平成26年

对于完全不同的东西,一个更长的日期,用于在泰国使用的泰国语中——但使用泰国数字系统和中国日历。(Firefox 等高质量实现将把纯 th-TH 视为 th-TH-u-ca-buddhist-nu-latn,推断泰国典型的佛教日历系统和拉丁数字 0-9。)

var options =
  { year: "numeric", month: "long", day: "numeric" };
var thaiDate =
  new Intl.DateTimeFormat("th-TH-u-nu-thai-ca-chinese", options);

print(thaiDate.format(july172014)); // ๒๐ 6 ๓๑

撇开日历和数字系统部分,这相对简单。只需选择您的组件及其长度即可。

数字格式

选项

用于数字格式的主要选项属性如下

style
"currency""percent""decimal"(默认值),用于格式化该类型的数值。
currency
三位字母的货币代码,例如 USDCHF。如果 style"currency",则必需,否则无意义。
currencyDisplay
"code""symbol""name",默认为 "symbol""code" 将在格式化字符串中使用三位字母的货币代码。"symbol" 将使用货币符号,例如 $ 或 £。"name" 通常使用货币的某种拼写版本。(Firefox 目前仅支持 "symbol",但此问题将很快得到解决。)
minimumIntegerDigits
从 1 到 21(含)的整数,默认为 1。结果字符串将用零填充,直到其整数部分至少包含这么多位数。(例如,如果该值为 2,格式化 3 可能生成“03”。)
minimumFractionDigitsmaximumFractionDigits
从 0 到 20(含)的整数。结果字符串将至少有 minimumFractionDigits 位小数,且不超过 maximumFractionDigits 位小数。如果 style"currency",则默认最小值取决于货币(通常为 2,很少为 0 或 3),否则为 0。百分比的默认最大值为 0,小数的默认最大值为 3,货币的默认最大值取决于货币。
minimumSignificantDigitsmaximumSignificantDigits
从 1 到 21(含)的整数。如果存在,这些选项将覆盖上面的整数/小数位控制,以确定格式化数字字符串中最小/最大有效位数,这取决于准确指定数字所需的位数。(请注意,在 10 的倍数中,有效位数可能不明确,例如“100”,它可以有一位、两位或三位有效位数。)
useGrouping
布尔值(默认为 true),确定格式化字符串是否将包含分组分隔符(例如,英语中的千位分隔符“,”)。

NumberFormat 还识别深奥的、大多可忽略的 localeMatcher 属性。

以语言环境为中心的选项

正如 DateTimeFormat 在 Unicode 扩展中使用 nu 键支持自定义数字系统一样,NumberFormat 也支持。例如,中国使用的汉语的语言标记为 zh-CN。汉语十进制数字系统的值为 hanidec。要格式化这些系统的数字,我们将 Unicode 扩展附加到语言标记:zh-CN-u-nu-hanidec

有关指定各种数字系统的完整信息,请参阅完整的 NumberFormat 文档

示例

NumberFormat 对象具有一个 format 函数属性,就像 DateTimeFormat 对象一样。并且,与那里一样,format 函数是一个绑定函数,可以在独立于 NumberFormat 的情况下使用。

以下是一些如何为特定用途创建 NumberFormat 选项的示例,以及 Firefox 的行为。首先,让我们格式化一些钱,用于中国使用的汉语,特别使用汉语十进制数字(而不是更常见的拉丁数字)。选择 "currency" 样式,然后使用人民币(元)代码,默认情况下进行分组,并使用通常的小数位数。

var hanDecimalRMBInChina =
  new Intl.NumberFormat("zh-CN-u-nu-hanidec",
                        { style: "currency", currency: "CNY" });

print(hanDecimalRMBInChina.format(1314.25)); // ¥ 一,三一四.二五

或者让我们格式化一个美国风格的汽油价格,它在千分位上有奇怪的 9,用于美国使用的英语。

var gasPrice =
  new Intl.NumberFormat("en-US",
                        { style: "currency", currency: "USD",
                          minimumFractionDigits: 3 });

print(gasPrice.format(5.259)); // $5.259

或者让我们尝试用阿拉伯语格式化一个百分比,用于埃及。确保百分比至少有两级小数。(请注意,此示例和所有其他RTL 示例在 RTL 上下文中可能以不同的顺序显示,例如٤٣٫٨٠٪ 而不是 ٤٣٫٨٠٪。)

var arabicPercent =
  new Intl.NumberFormat("ar-EG",
                        { style: "percent",
                          minimumFractionDigits: 2 }).format;

print(arabicPercent(0.438)); // ٤٣٫٨٠٪

或者假设我们要为阿富汗使用的波斯语格式化内容,并且我们想要至少两位整数和不超过两位小数。

var persianDecimal =
  new Intl.NumberFormat("fa-AF",
                        { minimumIntegerDigits: 2,
                          maximumFractionDigits: 2 });

print(persianDecimal.format(3.1416)); // ۰۳٫۱۴

最后,让我们格式化一定数量的巴林第纳尔,用于巴林使用的阿拉伯语。与大多数货币不同,巴林第纳尔分为千分之一(fils),因此我们的数字将有三位小数。(再次注意,明显的视觉顺序应持保留态度。)

var bahrainiDinars =
  new Intl.NumberFormat("ar-BH",
                        { style: "currency", currency: "BHD" });

print(bahrainiDinars.format(3.17)); // د.ب.‏ ٣٫١٧٠

整理

选项

用于整理的主要选项属性如下

usage
"sort""search"(默认为 "sort"),指定此 Collator 的预期用途。(search 整理器可能希望将更多字符串视为等效,而 sort 整理器则不会。)
sensitivity
"base""accent""case""variant"。这会影响整理器对具有相同“基本字母”但具有不同重音/变音符和/或大小写的字符的敏感程度。(基本字母取决于语言环境:“a”和“ä”在德语中具有相同的基本字母,但在瑞典语中是不同的字母。)"base" 敏感度仅考虑基本字母,忽略修改(因此,对于德语,“a”、“A”和“ä”被视为相同)。"accent" 考虑基本字母和重音,但忽略大小写(因此,对于德语,“a”和“A”相同,但“ä”与两者不同)。"case" 考虑基本字母和大小写,但忽略重音(因此,对于德语,“a”和“ä”相同,但“A”与两者不同)。最后,"variant" 考虑基本字母、重音和大小写(因此,对于德语,“a”、“ä, “ä” 和 “A” 都不同)。如果 usage"sort",则默认值为 "variant";否则,它取决于语言环境。
numeric
布尔值(默认为 false),确定在排序时是否考虑字符串中嵌入的完整数字。例如,数字排序可能会产生 "F-4 Phantom II", "F-14 Tomcat", "F-35 Lightning II";非数字排序可能会产生 "F-14 Tomcat", "F-35 Lightning II", "F-4 Phantom II"
caseFirst
"upper""lower""false"(默认值)。确定在排序时如何考虑大小写:"upper" 将大写字母放在首位("B", "a", "c"),"lower" 将小写字母放在首位("a", "c", "B"),而 "false" 完全忽略大小写("a", "B", "c")。(注意:Firefox 目前忽略此属性。)
ignorePunctuation
布尔值(默认为 false),确定在执行比较时是否忽略嵌入的标点符号(例如,以便 "biweekly""bi-weekly" 比较相同)。

还有那个 localeMatcher 属性,您可能可以忽略。

以语言环境为中心的选项

在语言环境的 Unicode 扩展中指定的作为 Collator 主要选项的部分是 co,它选择要执行的排序类型:电话簿(phonebk)、字典(dict)以及许多其他类型

此外,knkf 键可以(可选)重复 options 对象的 numericcaseFirst 属性。但不能保证语言标记中支持它们,而且 options 比语言标记组件更清晰。因此,最好仅通过 options 调整这些选项。

这些键值对与在 DateTimeFormatNumberFormat 中包含的方式相同;有关如何在语言标记中指定这些内容,请参阅那些部分。

示例

Collator 对象具有一个 compare 函数属性。此函数接受两个参数 <var>x</var><var>y</var>,如果 <var>x</var><var>y</var> 小,则返回一个小于零的数字;如果 <var>x</var> 等于 <var>y</var>,则返回 0;如果 <var>x</var> 大于 <var>y</var>,则返回一个大于零的数字。与 format 函数一样,compare 是一个绑定函数,可以提取出来单独使用。

让我们尝试对一些德语姓氏进行排序,用于德国使用的德语。德语中实际上有两种不同的排序顺序:电话簿排序和字典排序。电话簿排序强调发音,就好像在排序之前,将“ä”、“ö”等扩展为“ae”、“oe”等。

var names =
  ["Hochberg", "Hönigswald", "Holzman"];

var germanPhonebook = new Intl.Collator("de-DE-u-co-phonebk");

// as if sorting ["Hochberg", "Hoenigswald", "Holzman"]:
//   Hochberg, Hönigswald, Holzman
print(names.sort(germanPhonebook.compare).join(", "));

一些德语单词在变位时会增加变音符,因此在字典中,忽略变音符进行排序是有道理的(除非排序单词仅因变音符不同:schonschön 之前)。

var germanDictionary = new Intl.Collator("de-DE-u-co-dict");

// as if sorting ["Hochberg", "Honigswald", "Holzman"]:
//   Hochberg, Holzman, Hönigswald
print(names.sort(germanDictionary.compare).join(", "));

或者让我们对包含各种错误(大小写不同、随机重音和变音符号、额外连字符)的 Firefox 版本列表进行排序,以美国使用的英语为准。我们希望在尊重版本号的情况下进行排序,因此进行数字排序,以便将字符串中的数字进行比较,而不是按字符进行比较。

var firefoxen =
  ["FireFøx 3.6",
   "Fire-fox 1.0",
   "Firefox 29",
   "FÍrefox 3.5",
   "Fírefox 18"];

var usVersion =
  new Intl.Collator("en-US",
                    { sensitivity: "base",
                      numeric: true,
                      ignorePunctuation: true });

// Fire-fox 1.0, FÍrefox 3.5, FireFøx 3.6, Fírefox 18, Firefox 29
print(firefoxen.sort(usVersion.compare).join(", "));

最后,让我们进行一些语言环境感知的字符串搜索,忽略大小写和重音,同样以美国使用的英语为准。

// Comparisons work with both composed and decomposed forms.
var decoratedBrowsers =
  [
   "A\u0362maya",  // A͢maya
   "CH\u035Brôme", // CH͛rôme
   "FirefÓx",
   "sAfàri",
   "o\u0323pERA",  // ọpERA
   "I\u0352E",     // I͒E
  ];

var fuzzySearch =
  new Intl.Collator("en-US",
                    { usage: "search", sensitivity: "base" });

function findBrowser(browser)
{
  function cmp(other)
  {
    return fuzzySearch.compare(browser, other) === 0;
  }
  return cmp;
}

print(decoratedBrowsers.findIndex(findBrowser("Firêfox"))); // 2
print(decoratedBrowsers.findIndex(findBrowser("Safåri")));  // 3
print(decoratedBrowsers.findIndex(findBrowser("Ãmaya")));   // 0
print(decoratedBrowsers.findIndex(findBrowser("Øpera")));   // 4
print(decoratedBrowsers.findIndex(findBrowser("Chromè")));  // 1
print(decoratedBrowsers.findIndex(findBrowser("IË")));      // 5

零碎

确定是否为特定语言环境提供对某些操作的支持,或者确定是否支持某种语言环境,这可能很有用。Intl 在每个构造函数上提供 supportedLocales() 函数,并在每个原型上提供 resolvedOptions() 函数,以公开此信息。

var navajoLocales =
  Intl.Collator.supportedLocalesOf(["nv"], { usage: "sort" });
print(navajoLocales.length > 0
      ? "Navajo collation supported"
      : "Navajo collation not supported");

var germanFakeRegion =
  new Intl.DateTimeFormat("de-XX", { timeZone: "UTC" });
var usedOptions = germanFakeRegion.resolvedOptions();
print(usedOptions.locale);   // de
print(usedOptions.timeZone); // UTC

传统行为

以前,ES5 toLocaleString 样式和 localeCompare 函数没有特定的语义,不接受特定的选项,而且基本上没有用。因此,i18n API 以 Intl 操作的形式对它们进行了重新定义。现在,每个方法都接受额外的尾随 localesoptions 参数,这些参数的解释方式与 Intl 构造函数相同。(但对于toLocaleTimeStringtoLocaleDateString,如果未提供选项,则使用不同的默认组件。)

对于不需要精确行为的简短使用,旧方法仍然可以使用。但是,如果您需要更多控制或多次进行格式化或比较,最好直接使用 `Intl` 原语。

结论

国际化是一个引人入胜的主题,其复杂性仅受人类交流的多样性所限制。国际化 API 解决了其中一小部分但非常有用的复杂性,使生成本地化敏感的 Web 应用程序变得更加容易。赶快去使用它吧!

(特别感谢 Norbert Lindenberg、Anas El Husseini、Simon Montagu、Gary Kwong、Shu-yu Guo、Ehsan Akhgari、#mozilla.de 的成员以及可能被我遗漏的任何人 [抱歉!],他们为这篇文章提供了反馈或帮助我制作和批判示例。英语和德语示例是我知识的极限,如果没有他们的帮助,我会完全迷失在其他示例中。任何剩下的错误都归我负责。再次感谢!)

关于 Jeff Walden

自 ~2003 年起成为 Mozilla 用户,自 ~2006 年起成为 SpiderMonkey 黑客。涉猎 Gecko 的所有领域。通用标准专家。

更多 Jeff Walden 的文章…


14 条评论

  1. Vincent

    很棒的文章,谢谢!

    但是,你说

    通过方便的 `format()` 函数格式化日期。

    但后来

    var americanDateTime = new Intl.DateTimeFormat("en-US", options).format;

    那么它不应该是一个函数吗? :)

    2014 年 12 月 12 日 下午 12:12

    1. Toni

      作者后来调用了 `americanDateTime()`。
      所以 `format()` 确实是一个函数。

      2014 年 12 月 12 日 上午 9:21

    2. Jeff Walden

      一些示例执行 `var df1 = new Intl.DateTimeFormat(...); df1.format(...);`。另一些执行 `var df2 = new Intl.DateTimeFormat(...).format; df2(...);`。这是故意的,为了演示 `format` 属性是一个绑定函数:您可以在不将 `DateTimeFormat` 对象作为 `this` 传递的情况下调用它。数字格式化和排序示例也是如此。我在文章中也提到了这一点,但考虑到这样做很简单,在示例中进行演示似乎是合理的。

      2014 年 12 月 12 日 下午 11:32

      1. Vincent

        啊,抱歉,当然正确 :) 我通常用动词命名存储函数的变量,以便清楚地表明它是一个函数,这可能是我感到困惑的原因。

        2014 年 12 月 15 日 下午 12:20

  2. Jamon

    你的第一个例子

    var msPerDay = 24 * 60 * 60 * 1000;

    // 2014 年 7 月 17 日 00:00:00 UTC。
    var july172014 = new Date(msPerDay * (44 * 365 + 11 + 197));

    为什么不使用一个考虑闰年(以及其他因素)的 API 呢?这似乎是一个糟糕的示例。

    2014 年 12 月 14 日 下午 12:04

    1. Jeff Walden

      目的是指定一个 `Date`,其值不会依赖于用户的时区,很明显。据我快速浏览,没有简单的方法可以做到这一点。

      `new Date(2014, 7 - 1, 17)` 指定了本地时区的日期。这样做然后使用 `setUTC*` 方法进行调整很脆弱,因为时区偏移量对时间戳的影响很奇怪。

      这只剩下手动计算时间戳。不是理想的方法,但这不是示例的重点,所以足够好。

      2014 年 12 月 14 日 下午 2:06

  3. Jamon

    https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC

    2014 年 12 月 14 日 下午 2:32

    1. Jeff Walden

      说得通,我忘记了这一点。不过,我承认我不太喜欢写类似 `new Date(Date.UTC(...))` 的东西,因为里面重复提到了 `Date`。二十年前从 Java 继承来的 API 怪癖,好吧。

      2015 年 1 月 23 日 上午 5:39

  4. Claudio Cicali

    很棒的文章,但我一直认为文章中所说的 i18n 是 i10n(本地化),i18n 只是字符串的翻译 - 基本上就是这样。我错了嗎?

    2014 年 12 月 18 日 下午 12:52

    1. Hao Anh Le

      i18n 是准备工作,使代码能够接受任何语言环境;基本上,语言环境通用。L10n 指的是特定的语言/语言环境;实际的翻译、格式化等。

      2014 年 12 月 19 日 上午 10:43

    2. Thiago F

      你让我问自己同样的问题。

      ¨换句话说,i18n 使应用程序能够支持和满足多个语言环境的需求,从而“启用”l10n。正因为有了 i18n,我们才能够将其应用程序中的所有 Mozilla 项目本地化,并将 Web 打开给全世界!¨
      https://blog.mozilla.org/l10n/2011/12/14/i18n-vs-l10n-whats-the-diff/

      2014 年 12 月 20 日 上午 6:00

  5. ad

    只是一个问题,针对开发人员……如果我想查看 Firefox 源文件以了解这些方法的实现位置,应该在哪里查找?我只是 Firefox 开发新手。
    谢谢!

    2015 年 1 月 4 日 上午 9:43

    1. Jeff Walden

      抱歉回复延迟,最近我一直在忙,没有看到电子邮件中的新评论通知,我一直以为会通过电子邮件了解这里的消息。:-( 相关代码位于几个地方。

      对于执行排序和格式化的实际国际化代码,您应该查看 我们导入的 ICU 副本,位于 `intl/icu/source` 中。这目前是 ICU 52.1,但我计划很快更新到更新的 ICU。

      对于此处演示的各种国际化方法的大部分实现,您应该查看 `js/src/builtin/Intl.js`。只要有可能,我们都会努力实现具有有用步骤算法的规范,并添加注释来指示每个步骤的实现位置,以便希望这能帮助提高可读性。出于效率原因,我们尝试在创建格式化程序和排序器对象时尽可能延迟地执行所有操作,这可能会使该代码更难理解。请查看 `initializeIntlObject` 和 `resolveCollatorInternals` / `InitializeCollator` / `getCollatorInternals` 方法(以及格式化程序对象的相同版本),了解该代码。但我认为我们有足够的注释来解释这种策略,它并不完全不清楚在做什么。

      还有一些从源 CLDR 信息生成的表格 JS 数据。这些数据位于 `js/src/builtin/IntlData.js` 中,由 `js/src/builtin/make_intl_data.py` 生成。

      有时自托管代码需要来自 C++ 代码的特殊支持。我们还需要 C++ 来初始化 `Intl` 对象。此代码主要位于 `js/src/builtin/Intl.cpp` 中。各种自托管方法在 `js/src/vm/SelfHosting.cpp` 中与自托管基础设施挂钩。

      如果您对此有任何其他问题,请加入 Mozilla IRC 服务器上的 #jsapi 频道并提出问题。我们是一群友好的人,我们很乐意回答这类问题。通常,最好的提问时间是工作日,大约是美国西海岸或欧洲的工作时间,因为 JS 引擎黑客遍布整个地区。

      2015 年 1 月 23 日 上午 5:34

  6. JS

    前段时间发布了一篇关于 JavaScript 中的 I18n 的小文章 (http://javascript-html5-tutorial.com/i18n-in-javascript.html),但这一篇很棒!

    最佳!

    2015 年 1 月 8 日 上午 7:48

本文的评论已关闭。