組件庫設計實戰系列:國際化方案

放眼全球,中國總體的互聯網技術實力毫無疑問僅次於美國並領先剩餘全部的國家一大截。但若是咱們非要找出一箇中國互聯網公司作得不夠優秀的地方,那麼產品國際化必定是其中之一。雖然咱們也擁有諸如 AliExpress,天貓國際等成功案例,但不得不說大部分中國公司在選擇出海後,都沒有可以收穫到與預期相匹配的回報。這其中緣由天然不少,然而缺少一套能夠平臺化,產品化的通用國際化方案一直都是其中一個很是重要的緣由。前端

曾經筆者也天真地認爲國際化不過是幾個 json 文件的鍵值對匹配,但在深刻了解了一些產品的國際化需求後,筆者才意識到要作一套好的國際化方案並無那麼簡單。web

服務端國際化

對於前端工程師而言,國際化所要面臨的第一個挑戰就是,並非全部的數據均可以在前端作國際化。常見的例子如電商類產品的貨品或商家信息,這些都是有強更新需求,須要存儲在後端數據庫中,經過產品後臺進行更新的。若是一個商品要銷往美國,德國,法國,西班牙,泰國,印度尼西亞,而運營人員又只想維護一套以中文爲基準的商品信息,那麼這類數據的國際化咱們就須要將其作在服務端。數據庫

咱們固然能夠麻煩後端工程師幫助咱們根據每一個請求的域名或 HTTP header 中的 content-language 來返回不一樣表中的翻譯,但若是你是一位致力於向全棧方向發展的前端工程師,不妨能夠嘗試將國際化這一需求服務化,使用 Node.js 來封裝一個國際化中間件,在每一個請求返回前對其返回值進行翻譯處理。npm

由於每一個公司的技術架構不一樣,咱們暫且略過技術細節不表。但咱們須要知道的是,相較於前端國際化,後端接口的國際化其實更爲關鍵與重要。由於這涉及到咱們是否能將咱們的核心數據以用戶可理解的語言展示出來,而國際化也毫不僅僅是將幾個字符串翻譯爲對應語言那樣簡單。json

哪些數據須要作國際化

在討論具體的國際化方案以前,咱們首先要明確一個問題,那就是產品中的哪些數據是須要作國際化的。redux

簡而言之,除去後端返回的數據,全部在前端渲染的單詞,語句,以及嵌套在其中的數據,都須要作相應的國際化。對應到代碼層面,須要保證代碼中沒有任何一行硬編碼的字符串與符號。不管是大到一個區塊標題,仍是小到一個確認按鈕的文案,全部的展現信息都須要作國際化。後端

鍵值對匹配與多語言支持

回到前端,讓咱們從最簡單的國際化場景提及。bash

例以下拉列表輸入框中的「選擇」佔位符,假設咱們須要同時將其翻譯爲英文與法文,首先咱們須要引入兩個語言文件:前端工程師

// en-US.json
{
  "web_select": "Select"
}

// fr-FR.json
{
  "web_select": "Sélectionner"
}複製代碼

並提供一個全局的 localeUtil.js,支持傳入語言類型與 key 值,並返回相應的翻譯。數據結構

這裏提供兩點最佳實踐。

一是將不一樣語言的翻譯存在獨立的 json 文件中。雖然咱們可使用嵌套的數據結構將全部翻譯都存儲在一個 locale.json 裏面,但考慮到生產環境中語言文件通常都是按需加載的,因此根據不一樣的語言存在對應的獨立的的 json 文件中顯然是一個更好的選擇。

二是同一語言中 key 值的命名,一樣不建議採起嵌套的結構。扁平化的語言文件可讀性更強,取值時的效率也更高,同時也可使用下劃線來區別不一樣的層級,如 web_homepage_banner_title,即平臺_頁面_模塊_值,固然具體狀況也能夠按需調整。

模板匹配與條件運算符

瞭解了最簡單的場景,咱們再來考慮一個複雜些的用例。

在顯示商品價格時,爲了可擴展性等多方面的考慮,後端在設計表結構時,是不會將商品價格直接存儲爲字符串的,而是拆分爲貨幣符號(string 類型)及價格(float 類型)。而在前端顯示時,咱們常常會遇到要將其渲染爲一句促銷語的場景,如:

2017年9月1日前購買,只需100元。
複製代碼

對於時間類數據的國際化方案,咱們這裏先暫時按下不表,有興趣的同窗能夠研究一下 moment.js 的實現,moment.js 也是目前前端屆日期國際化的表明。

因爲100元是一個動態的變量,因此咱們的 localeUtil.js 還須要支持傳入變量,這裏一個經常使用的調用能夠爲:

localeGet(
  'en-US', // locale
  'web_merchantPage_item_promotion', // key
  { currency: item.currency, promoPrice: item.promoPrice }, // variable
);複製代碼

語言文件中的模板能夠爲:

"web_merchantPage_item_promotion": "Before YYYY/MM/DD, purchase at {currency} {price}."複製代碼

另外一個常見的場景爲英文名詞的單複數問題,這裏咱們選擇經過條件運算符的思路來解:

優惠將於3天后結束。
複製代碼
"web_merchantPage_item_promotion_condition": "Promotion will end in {count, =1{# day} other{# days}}",複製代碼

數據國際化

除去日期,貨幣外,數字也是字符串以外另外一個國際化的難點,咱們來看下面這個例子。

阿里巴巴向印度尼西亞電商網站 Tokopedia 注資11億美金。
Alibaba leads $1.1b investment in Indonesia’s Tokopedia.
複製代碼

這裏咱們須要將「11億美金」翻譯爲「$1.1b」,爲了達到這一目的,咱們首先須要在各個語言文件中創建對應語言的基礎單位 mapping,如:

// zh-CN
"hundred": "百",
"thousand": "千",
"ten_thousand": "萬",
"million": "百萬",
"hundred_million": "億",
"billion": "十億",

// en-US
"hundred": "hundred",
"thousand": "thousand",
"thousand_abbr": "k",
"million": "million",
"million_abbr": "m",
"billion": "billion",
"billion_abbr": "b",複製代碼

而後咱們須要實現一個能夠將浮點數進行純數字與單位轉換的函數,返回純數字與所使用語言的單位 key 值:

function formatNum(num, isAbbr = false) {
  ...
  return {
    number: number, // 1.1
    unit: unit, // "billion_abbr"
  }
}複製代碼

接着就能夠調用 localeGet 來得到相應的翻譯:

localeGet(
  'en-US',
  'news_tilte',
  {
   number: 1.1,
   unit: localeGet('billion_abbr'),
   currency: localeGet('currency_symbol'),
  },
)複製代碼

語言文件中的模板以下:

// zh-CN
"news_tilte": "阿里巴巴向印度尼西亞電商網站 Tokopedia 注資{number}{unit}{currency}。"

// en-US
"news_tilte: "Alibaba leads {currency}{number}{unit} investment in Indonesia's Tokopedia."複製代碼

在整個過程當中,咱們能夠抽象出兩種解決問題的思路。

一是拆分並抽象出基礎數據,如單位等。

二是靈活運用模板與變量,將其調整爲最符合當地用戶閱讀習慣的翻譯。

相似的思想也能夠推廣處處理日期,小數,分數,百分數等。

React 下的國際化方案

正如前文中所提到的,按需加載語言文件是國際化方案中必要的一環。簡而言之,咱們能夠在項目的入口文件中加載所需的語言文件,但考慮到總體項目的統一性,咱們最好能夠將語言文件掛載在全局 redux store 下的一個分支,以使得每一個頁面均可以經過 props 方便地進行取值。並且,在 redux store 的層面加載語言文件,能夠保證全部頁面使用的都是同一份語言文件,後續也不須要在 localeGet 函數中傳入具體的 locale 值。

示例代碼以下:

import enUS from 'i18n/en-US.json';

function updateIntl(locale = 'en-US', file = enUS) {
  store.dispatch({
    type: 'UPDATE_INTL',
    payload: {
      locale,
      file,
    },
  });
}複製代碼

這樣咱們就能夠方便地將語言文件掛載在 redux store 的一個分支下:

const mapStateToProps = (state) => ({
  intl: state.intl,
});

// usage
localeGet(this.props.intl, 'web_select');

// with defaultValue to prevent undefined return
localeGet(this.props.intl, 'web_select', 'Select');複製代碼

其餘

除了上述提到的這些問題以外,在生產環境中咱們還須要注意如下兩點:

  • HTML 轉義字符
  • 特殊語言的 unicode 轉碼,如簡體中文,繁體中文,泰語等

正如開篇時提到的,國際化是一個通用的系統性工程,以上提到的這些點也不免掛一漏萬,更多的最佳實踐還須要在實際開發工做中持續提煉,總結,沉澱。

小結

對於任何一家但願開拓國際市場的公司來講,產品國際化都是一個剛需。從技術人員的角度來說,咱們固然能夠知足於一個 Node.js 中間件或一個前端的 npm 包來通用地解決這一問題。但事實上,咱們還能夠再向前一步,那就是將國際化這個服務作成一個完整的 SASS 產品,這方面成功的案例如:OneSky

OneSky 所提供的額外功能,如雲端存儲,多文件類型支持,多人實時翻譯協做等,每個功能單拿出來都又是一個新的領域,而這也正是服務產品化的難點所在。

舉例來講,前文中提到的國際化方案,都是默認全部翻譯工做已經完成且 json 化完畢,能夠直接 import 到項目中使用,而這也就是技術人員常常會陷入的一個思惟盲區。翻譯數量龐大的語言文件自己就是一件很是困難的事情,如何讓身處世界各地的非技術背景的翻譯人員進行協做並方便生產環境中的產品實時更新語言文件,這些問題都只有在把國際化服務作成一個成熟的商業產品以後纔會被考慮到。

事實上,目前在各大互聯網公司中,技術服務產品化已經成爲了一股不可阻擋的趨勢,許多技術出身的工程師都已經開始意識到一套只有技術人員才能理解並使用的解決方案是不夠的,只有將這些「高深莫測」的技術服務產品化,傻瓜化,纔可以打開一片更大的戰場,使技術真正服務於商業產品並在現實世界中產生更大的價值。

相關文章
相關標籤/搜索