文章出處

前言

 最近加入到新項目組負責前端技術預研和選型,其中涉及到一個熟悉又陌生的需求——國際化&本地化。熟悉的是之前的項目也玩過,陌生的是之前的實現僅僅停留在"有"的階段而已。趁著這個機會好好學習整理一下,為后面的技術選型做準備。
 本篇將于大家一起挽起袖子擼代碼:)

如何獲取Language tag?

 在實現本地化處理前,我們起碼先要獲取Language tag吧?那么獲取方式分為兩類
1.直接獲取瀏覽器的Language tag信息
 一般來說瀏覽器語言的版本標示著用戶所屬或所期待接收哪種語言文化風俗的內容,于是通過以下函數獲取瀏覽器的語言信息即可獲取language-tag

function getLang(){
  return navigator.language || navigator.browserLanguage
}

2.用戶選擇Language tag信息
 大家在瀏覽蘋果官網時也會發現以下界面,讓我們自行選擇language-tag。
Screenshot_from_2016_09_20_15_23_44
注意蘋果官網采用的是Server-driven Negotiation的機制提供本地化功能,和本篇主打前端實現有所區別。

 最適當的設置和獲取language-tag的方式當然就是上述兩種方式相結合啦!首先自動獲取瀏覽器的Language tag信息,并提供入口讓用戶自行選擇Language tag信息。

認識JavaScript Internationalization API

 有了本地化識別的根據(language tag)后,我們就可以開始實現本地化處理了,但從頭開始處理還累了,幸好H5為我們提供新的API來減輕我們的工作量。它們分別是處理排序的Intl.Collator,處理日期格式化的Intl.DateTimeFormat和處理數字/貨幣等格式化的Intl.NumberFormat

Intl.Collator

 用于字符排序.

new Intl.Collator([locales[, options]])
@param Array|String [locales] - language-tag字符串或數組
@param Array        [options] - 配置項

options的屬性及屬性值(如無特別說明則values第一個值為默認值)

@prop String localeMatcher
@desc   指定用于locale匹配的算法
@values 'best fit' | 'lookup'

@prop String usage
@desc   指定用途
@values 'sort' | 'search'

@prop String sensitivity
@desc   指定對比時是否忽略大小寫、讀音符號
@values 'base'    - 大小寫不敏感,讀音符號不敏感
        'accent'  - 除采用base規則外,讀音符號敏感
        'case'    - 除采用base規則外,大小寫敏感
        'variant' - base,accent和case的并集 

@prop Boolean ignorePunctuation
@desc   指定是否忽略標點符號
@values false | true

@prop Boolean numeric
@desc   指定是否將兩個數字字符轉換為數字類型再作比較
@values false | true

@prop String caseFirst 
@desc   指定是否以大寫或小寫作優先排序
@values 'false' | 'upper' | 'lower' 

實例方法

Intl.Collator.prototype.compare(a, b):Number
@desc 比較字符串a和字符串b,若a排在b前面則返回-1,等于則返回0,排在后面則返回1.

Intl.Collator.prototype.resolveOptions():Object
@desc 返回根據構造函數中options入參生成的最終采用的options

Intl.DateTimeFormat

 用于日期格式化輸出.

new Intl.DateTimeFormat([locales[, options]])
@param Array|String [locales] - language-tag字符串或數組
@param Array        [options] - 配置項

options的屬性及屬性值(如無特別說明則values第一個值為默認值)

@prop String localeMatcher
@desc   指定用于locale匹配的算法
@values 'best fit' | 'lookup'

@prop String timeZone 
@desc   指定被格式化的時間所在的時區
@values [IANA time zone database](https://www.iana.org/time-zones) such as "Asia/Shanghai", "Asia/Kolkata", "America    /New_York", "UTC"

@prop String timeZoneName
@desc   指定格式化后所顯示的時區樣式
@values 'short' | 'long'

@prop Boolean hour12
@desc   指定采用12小時制還是24小時制 
@values false | true
@default-value 由locales入參決定

@prop String year 
@desc 指定年份的樣式
@values 'numeric' | '2-digit' | 'narrow' | 'short' | 'long'

@prop String month
@desc 指定月份的樣式
@values 'numeric' | '2-digit' | 'narrow' | 'short' | 'long'

@prop String day
@desc 指定日期的樣式
@values 'numeric' | '2-digit'

@prop String hour 
@desc 指定小時的樣式
@values undefined | 'numeric' | '2-digit'

@prop String minute
@desc 指定分鐘的樣式
@values undefined | 'numeric' | '2-digit'

@prop String second
@desc 指定秒的樣式
@values undefined | 'numeric' | '2-digit'

@prop String weekday
@desc 指定周的樣式
@values 'narrow' | 'short' | 'long'

實例方法

Intl.Collator.prototype.format(a):String
@desc 格式化日期

Intl.DateTimeFormat.prototype.resolveOptions():Object
@desc 返回根據構造函數中options入參生成的最終采用的options

Intl.NumberFormat

 用于數字、貨幣格式化輸出.

new Intl.NumberFormat([locales[, options]])
@param Array|String [locales] - language-tag字符串或數組
@param Array        [options] - 配置項

options的屬性及屬性值(如無特別說明則values第一個值為默認值)

@prop String localeMatcher
@desc   指定用于locale匹配的算法
@values 'best fit' | 'lookup'

@prop String style
@desc   指定格式化的風格
@values 'decimal' | 'currency' | 'percent'
@remark 當style設置為currency后,屬性currency必須設置

@prop String currency
@desc   指定貨幣的格式化信息
@values 如"USD"表示美元, "EUR"表示歐元, "CNY"表示RMB.[Current currency & funds code first](http://www.currency-iso.org/en/home/tables/table-a1.html)

@prop String currencyDisplay
@desc   指定貨幣符號的樣式
@values 'symbol' | 'code' | 'name'

@prop Boolean useGrouping
@desc   指定是否采用如千位分隔符對數字進行分組
@values false | true

@prop Number minimumIntegerDigits
@desc   指定整數最小位數
@values 1-21

@prop Number maximumFractionDigits
@desc   指定小數部分最大位數
@values 0-20

@prop Number minimumFractionDigits
@desc   指定小數部分最小位數
@values 0-20

@prop Number maximumSignificantDigits
@desc   指定有效位最大位數
@values 1-21

@prop Number minimumSignificantDigits
@desc   指定有效為最小位數
@values 1-21

注意:minimumIntegerDigits,maximumFractionDigitsminimumFractionDigits為一組,而maximumSignificantDigitsminimumSignificantDigits為另一組,當設置maximumSignificantDigits后,minimumIntegerDigits這組的設置為全部失效。

 上述Intl接口并不是所有瀏覽器均支持,幸好有大牛已為了我們準備好polyfill了,但由于Intl.Collator所以來的規則和實現的代碼量較龐大,因此polyfill中僅僅實現了Intl.DateTimeFormat和Intl.NumberFormat接口而已,不過對于一般項目而言應該足矣。Intl polyfill
 另外,還對String,NumberDate的原型作擴展,以便我們使用Intl的三劍客!

String.prototype.localeCompare(compareString[, locales[, options]])
Number.prototype.toLocaleString([locales[, options]])
Date.prototype.toLocaleString([locales[, options]])
Date.prototype.toLocaleDateString([locales[, options]])
Date.prototype.toLocaleTimeString(([locales[, options]])

Format.js——用在生產環境的i18n庫

 說了這么多那我們怎么讓項目實現國際化/本地化呢?那當然要找個可靠的第三方庫啦——Format.js,它不僅提供字符串替換還提供日期、數字和貨幣格式化輸出的功能,而且各大前端框架都已將其作二次封裝,使用得心應手呢!
Screenshot_from_2016_09_20_15_34_43
要注意的是它依賴Intl.NumberFormat和Intl.DateTimeFormat,因此當瀏覽器部支持時需要polyfill一下。

var areIntlLocalesSupported = require('intl-locales-supported');

var localesMyAppSupports = [
    /* list locales here */
];

if (global.Intl) {
    // Determine if the built-in `Intl` has the locale data we need.
    if (!areIntlLocalesSupported(localesMyAppSupports)) {
        // `Intl` exists, but it doesn't have the data we need, so load the
        // polyfill and replace the constructors with need with the polyfill's.
        var IntlPolyfill = require('intl');
        Intl.NumberFormat   = IntlPolyfill.NumberFormat;
        Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
    }
} else {
    // No `Intl`, so use and load the polyfill.
    global.Intl = require('intl');
}

intl-locales-supported顧名思義就是檢查原生Intl是否支持特定的Language tag(如cmn-Hans),若不支持則使用Polyfill版本。
原生JavaScript使用示例:

<div id="msg"></div>
<script>
  const msgs = {en: {GREETING: 'Hello {name}'}
               ,fr: {GREETING: 'Bonjour {name}'}}  
  const locale = getLang()
  const msg = (msgs[locale] || msgs.en).GREETING
  const txt = new IntlMessageFormat(msg, locale)
  document.getElementById('msg').textContent = txt.format({name: 'fsjohnhuang'})
</script>

Polymer組件使用示例:

<link rel="import" href="./bower_components/app-localize-behavior/app-localize-behavior.html">
<dom-module id="x-demo">
  <template>
    <div>{{localize('name')}}</div>
  </template>
  <script>
    Polymer({
      is: 'x-demo',
      properties: {name: {type: String, value: 'fsjohnhuang'}},
      behaviors: [Polymer.AppLocalizeBehavior],
      attached: function(){
        this.loadResources(this.resolveUrl('./locales.json'))
      }
    })
  </script>
</dom-module>

locales.json

{"en": {"GREETING": "Hello {name}"}
,"fr": {"GREETING": "Bonjour {name}"}}

更多的玩法請參考官網吧!

總結

 項目中我們更多地是采用如Formatjs等上層i18n庫,而不是更底層的IntlAPI,但若想更好地實現國際化和本地化,我想了解Intl及其背后的規則是十分有必要的。
 另外細心的你會發現上文提到另一個概念——Server-driven Negotiation,到底它和國際化/本地化有什么關系呢?那么請期待下篇——《JS魔法堂:不完全國際化&本地化手冊 之 拓展篇》
 尊重原創,轉載請注明來自: http://www.cnblogs.com/fsjohnhuang/p/5911482.html ^_^肥仔John

感謝

Intl


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()