介绍 JavaScript 国际化 API 已翻译 100%

oschina 投递于 2014/12/13 07:52 (共 17 段, 翻译完成于 12-15)
阅读 7482
收藏 107
7
加载中

Firefox 29 是在半年之前发布的,所以这篇文章有点过时了。这里我先停一会,讨论一下该桌面版附带的国际化API(已经通过所有得测试)。 大多数的实现是由Norbert Lindenberg 所写,由我审查和维护。(不久Makoto Kato的工作就会将这些带到安卓中去;因为b2g的特殊困难,这可能要花挺长时间。现在仍在调整中。)

什么是国际化?

国际化(Internationalization的缩写是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);
}
开源中国七里香
开源中国七里香
翻译于 2014/12/13 08:33
1

JavaScript过去的i18n支持太糟糕

传统JS的i18n程序使用toLocaleString()方法进行格式化。结果字符串包含实现自身提供的所有细节:没有办法自己选择(你确实需要那种date格式的weekday吗?year是无关紧要的吗?)。即使包含对应的细节,格式也可能是错的,比如本期望是百分比但得到的是数字。而且你还不能选择一个区域设置(locale)。

对于排序,JS提供了基本没用的基于区域设置(locale-sensitive)的文本比较函数。localeCompare()确实存在,但是其接口根本不适合sort。而且还不允许选择区域设置,或者排序方式。

这些限制太糟了(当我认识到时,我非常吃惊!),因为需要i18n支持(通常是金融站点用于显示货币)的严谨web应用会把数据打包,发给服务器,服务器进行操作,然后发回客户端。数据往返服务器仅仅为了处理货币的数量。Yeesh。

douxingxiang
douxingxiang
翻译于 2014/12/13 22:51
1

新的JS国际化API

新的ECMAScript国际化API大大提升了JS的i18n能力。 它提供了大家所能想到的格式化date、数字,文本排序的方式。区域设置是可选的,如果请求的区域设置不支持可以回退。格式化请求可以指定具体要包含的组件。支持自定义的百分比、有效数字、货币格式。开放了大量排序选项用于文本排序。如果你关心性能,首要的操作是选择一个区域设置,然后处理选项参数,现在这个操作只会处理一次,而不是之前每次区域设置相关的操作执行时都会被执行一遍。

这不是说,这个API是万能药,而仅仅是"尽最大努力"。精确的输出几乎总是故意不指定的。一份实现可以仅支持 oj 区域设置(合法的),也可以忽略(几乎全部)提供的格式化选项。大多数实现都包含高质量的多区域支持,但并不保证有(尤其是资源限定的系统,如手机)。

在底层,Firefox的实现依赖于 Unicode 的国际化组件库(ICU),这个库又依赖 Unicode Common 区域数据仓库(CLDR)的区域数据集。我们的实现是自托管的:ICU之上的大部分实现用JS写的。在这个过程中,我们遇到了一些问题(我们从未如此大规模的自托管过),但基本上都不大。

douxingxiang
douxingxiang
翻译于 2014/12/14 12:08
1

Intl 接口(不是数字1,是字母l)

i18n 存在于 Intl 对象之上。Intl 包含3个构造函数:Intl.Collator, Intl.DateTimeFormat, 和Intl.NumberFormat。每个构造函数创建一个对象,这个对象提供相关操作、高效地为这些操作缓存区域设置和选项。按以下方式创建对象:

var ctor = "Collator"; // 或其他 
var instance = new Intl[ctor](locales, options);

locales 是个字符串,指定单个语言标签,或者包含多个语言标签的类数组对象。语言标签如下面的字符串:en(普通英语),de-AT(奥地利德语),zh-Hant-TW(台湾使用的繁体中文)。语言标签可以包含一个“Unicode扩展”,形式为-u-key1-value1-key2-value2..., 其中每个key是“扩展key”。不同的构造函数对此进行具体解释。

opions 是个对象,其属性(如果不存在,就赋值为undefined)决定格式化器(formatter)和整理器(collator)的行为。精确的解释由构造函数决定。

douxingxiang
douxingxiang
翻译于 2014/12/14 12:22
2

给定区域信息和选项,实现会尝试生成近似理想行为的最接近行为。Firefox 支持用于整理(collation)的400+区域,用于date/time和数字格式化的600+区域,所以很可能(但不保证)你想要的区域是被支持的。

Intl 通常不保证某些特定行为。如果请求的区域不被支持,Intl 允诺“尽最大努力”的行为。即使区域是被支持的,行为也不是严格指定的。永远不要假设特定的选项集适用于某个特定格式。(围绕请求的组件)总体格式的用语可能因浏览器甚至浏览器的版本而不同。单个组件的格式是未指定的:weekday的短格式可以为“S”, “Sa”, 或“Sat”。Intl API并不用于公开精确的特定行为。

douxingxiang
douxingxiang
翻译于 2014/12/14 13:14
1

Date/time格式化

选项

date/time格式化的主要选项属性如下:

  • weekday, era
    "narrow", "short", or "long".  (era通常指历法系统中长于一年的分段,如现行日皇统治, 或者其他纪年法)

  • month
    "2-digit", "numeric", "narrow", "short", or "long"

  • year

  • day

  • hour, minute, second
    "2-digit" or "numeric"

  • timeZoneName
    "short" or "long"

  • timeZone
    区分大小写的"UTC"通过对应的toUTC进行格式化。有些值如"CEST"和"America/New_York"不是必须被支持的,它们确实在当前Firefox下没有效果。 

这些值并不映射到特定格式:记住Intl API几乎不指定精确的行为。Intl的目的,举例来说是"narrow", "short", 和"long"生成对应大小的“S”/“Sa”, “Sat”, 和“Saturday”(输出可能不太准确,因为Saturday和Sunday都可以生成“S”)。  "2-digit"和"numeric"映射到2位数字的字符串或者全长度的数字字符串,如“70”和“1970”。

最终使用的选项大部分是请求的选项。但是,如果你不指定请求的 weekday/year/month/day/hour/minute/second,那么 year/month/day 将会被加入到你提供的选项。

douxingxiang
douxingxiang
翻译于 2014/12/14 13:34
1

除此之外,还有些特殊的选项:

  • hour12
    指定hour采用12小时还是24小时格式。默认通常是依赖于区域设置的(某些细节,如午夜是0点,还是12点,以及是否存在前导0,都是依赖于区域设置的)。

还有另外2种特殊属性,localeMatcher (可选"lookup"或"best fit") 和formatMatcher (可选"basic"或"best fit"),两者默认值都是"best fit"。这些会影响正确的区域设置和格式的选取。它们的用例可能比较难懂,就不赘述了。

区域设置相关选项

DateTimeFormat也允许通过自定义历法和数字系统来格式化。具体细节存在于区域设置,所以它们可以在语言标签的Unicode扩展中找到。

douxingxiang
douxingxiang
翻译于 2014/12/14 17:12
2

例如,泰国的泰语中语言标签为th-TH。回一下Unicode扩展的格式-u-key1-value1-key2-value2....  历法系统的key是ca, 数字系统的key时nu。泰语数字系统值为thai,中文历法系统值为chinese。因此用大体这样的方式来格式化date,我们把包含这些key/value对的Unicode追加到语言标签上去:th-TH-u-ca-chinese-nu-thai。

关于更多历法和数字系统的信息,查看DateTimeFormat的完整文档

举例

创建DateTimeFormat对象后,下一步是通过方便的format()函数来格式化date。更方便的是,这个函数是有界函数(bound function):你不必在DateTimeFormat上直接调用。之后给它传递一个时间戳或者Date对象。

douxingxiang
douxingxiang
翻译于 2014/12/14 17:27
1

总结一下,下文是如何为特定用途创建DateTimeFormat选项的例子(在当前Firefox执行行为下)。

var msPerDay = 24 * 60 * 60 * 1000; 

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

我们来格式化美国英语的date。我们先创建一个2位数字的month/day/year, 加上2位数字的hours/minutes, 还有一个短时区来确定这个时间。(结果肯定因不同时区而明显不同)

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

或者类似的,对葡萄牙语,最好是巴西使用的葡语,但是用在葡萄牙作品中的。格式会稍长,因为包含完整的year和正式拼写的month,但是因移植性要转成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

或者我们尝试某个日本博物馆里的一幅画中的描述性文本中的date,这个date使用日本的year和era历法。

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

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

对一些完全不同的、更长的date,用于泰国泰语,但是使用泰语数字系统和中国历法。(类似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 ๓๑

撇开历法和数字系统,还是很简单的。只要选取自己的组件和长度。

douxingxiang
douxingxiang
翻译于 2014/12/14 22:51
1

数字格式化

选项

数字格式化的主要选项属性有:

  • style
    "currency", "percent", or "decimal" (默认值).

  • currency
    3字母货币代码,如USD、CHF。需要style是"currency", 不然没有意义

  • currencyDisplay
    "code", "symbol", or "name", 默认为"symbol".  "code"使用格式字符串的3字母货币代码。"symbol"使用货币符号,如$或£。"name"通常使用某些正式拼写版本的货币。(Firefox 目前仅支持"symbol", 这个问题不就就修复)

  • minimumIntegerDigits
    范围1到21(包含)的整数,默认为1。结果字符串的整数部分如果没有这么长,它前面会用0来填充。  (若这个值为2,那么3的格式化形式为“03”。)

  • minimumFractionDigits, maximumFractionDigits
    0-20(包含)的整数。结果字符串至少minimumFractionDigits, 至多maximumFractionDigits个有效数字。如果style是"currency",默认最小值跟货币有关(通常是2,很少0或者3),不然就是0。默认最大值,百分比是0,数字是3,货币的最大值跟货币有关。

  • minimumSignificantDigits, maximumSignificantDigits
    1-21(包含)的整数。如果有,它们将会覆盖上文关于整数/分数的对数字的控制,而由它们以及数字的要求长度,共同确定格式化的数字字符串中最小/最大有效数字的值。 (注意对10的倍数,有效数字可能不准确,如100,它的1,2,3位有效数字。)

  • useGrouping
    布尔值(默认true)决定格式化字符串是否包含分组分隔符(如,英语的千分隔符“,”)。

NumberFormat还识别难懂的、大多数可忽略的localeMatcher属性。

douxingxiang
douxingxiang
翻译于 2014/12/14 23:25
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(9)

douxingxiang
douxingxiang

引用来自“Soy_meng”的评论

不是字母l,是数字1
intl吗
c
caoglish
感觉更糟糕了,赶紧谁写个第三方插件让咱用用
Soy_meng
Soy_meng
不是字母l,是数字1
rom1c丶
rom1c丶
+++1
郭振华oo
郭振华oo
++1
贾珣
贾珣
+1
cgcgbcbc
cgcgbcbc

引用来自“开源中国匿名会员”的评论

i18n在js上还是很糟糕
+1
开源中国匿名会员
开源中国匿名会员
i18n在js上还是很糟糕
douxingxiang
douxingxiang
从源站拷贝博文到本站,有些格式,尤其是代码格式会不一致;还有些列表的层次关系有问题@红薯
返回顶部
顶部