基於webpack & gettext 的前端多語言方案
gettext 是GNU 提供的一套國際化與本地化 處理的相關函式庫。大多數語言都有對應的gettext實現。本文主要使用jed 來實現gettext 一系列方法對應的功能。
pot/po檔案
- pot檔案 是po檔案的模板檔案,一般是通過 xgettext 程式生成出來的。
- po檔案 是根據pot檔案通過msginit程式,設定對應的國家語言生成用於填寫實際翻譯內容的檔案。
xgettext/msginit/msgmerge
- xgettext 程式可以掃描指定的程式碼檔案,取出其中gettext部分的內容生成對應的pot檔案。
- msginit 根據對應的pot檔案生成對應語言版本用於實際翻譯的po檔案。
- msgmerge 如果對應語言版本的po檔案存在的話,則需要使用msgmerge方式把pot檔案中新加入的一些msgid合併到po檔案當中。
多語言支援流程
安裝gettext
brew install gettext brew link gettext 複製程式碼
langs-loader 載入語言檔案
- 安裝
npm install [email protected]:ezbuy/langs-loader.git --save-dev 複製程式碼
- 配置
需要修改webpack.config.js檔案在module->rules(webpack 2.0)下面新增loader規則:
{ test: /\.pot$/i, use: [ "json", { loader: 'langs', options: {isLoadAll: isDev,format:"jed1.x", "fallback-to-msgid":true, code:langCode} } ] } 複製程式碼
- isLoadAll
isLoadAll表示會把所有語言版本的檔案通過po2json轉換成json,然後組合成一個json作為資料傳給引用的地方。
- code
code選項一般需要在程式碼釋出時指定,不同語言版本打包時通過傳入不同的code區分不同語言版本的程式碼打包。
程式碼中使用gettext系列函式
各端開發時使用各自實現的gettext方法去包裝需要實現多語言的文案。使用gettext函式包裝後,langs-util 就能知道程式碼中有需要多語言翻譯的地方,並可以通過langs-util 生成相關的pot及po檔案。gettext方法底層我們使用jed 來實現其對應功能。
import toi18n from "common/i18n"; const _ = toi18n(require("langs/index.pot")); const hiStr = _.gettext("Hi!"); const num = Math.ceil(Math.random() * 2); const numStr = _.ngettext("item", "items", num); // I like your red shirt. console.log(_.sprintf( "I like your %1$s %2$s.", 'red', 'shirt')); 複製程式碼
- gettext
一般使用 gettext 方法包裝一個需要翻譯的字串。
- ngettext
可以用於單複數處理,第一個引數傳入單數情況下的翻譯,第二個引數傳入複數情況下的翻譯,第三個傳入實際需要執行判斷的資料。
- sprintf
Jed內建提供了sprintf的支援,它是使用javascript-sprintf的sprintf函式的實現。
通過gettext 一系列函式的配合,使用langs-util 進行生成處理後就能生成對應的pot及po檔案。
langs-util gen -i src/ 複製程式碼
# langs/index.pot msgid "Hi!" msgstr "" msgid "item" msgid_plural "items" msgstr[0] "" msgstr[1] "" 複製程式碼
# langs/index.th.po msgid "Hi" msgstr "" msgid "item" msgid_plural "items" msgstr[0] "" msgstr[1] "" 複製程式碼
把生成的檔案交由對應的翻譯人員完成翻譯就可以重新放回到資料夾當中替換目標po檔案。
打包&釋出
各端打包方式都會有相應的差異,最終目的無非就是使線上程式碼可以按照不同語言的版本載入不同的語言檔案。對於後臺 等系統則可以使用isLoadAll的方式把所有語言版本的檔案都載入進來,通過i18n的包裝函式去從資料來源中拿出不同國家的多語言資料檔案。
cross-env LANG_CODE=th 複製程式碼
移動端釋出使用環境變數LANG_CODE 來區分要編譯成的語言版本。
const langCode = process.env.LANG_CODE { test: /\.pot$/i, use: [ "json", { loader: 'langs', options: {isLoadAll: isDev,format:"jed1.x", "fallback-to-msgid":true, code:langCode} } ] } 複製程式碼
生成完所有語言語言版本的靜態檔案後,需要讓瀏覽器能夠執行不同語言版本的js檔案。一種方式可以通過後臺程式讀出語言版本,再把正確語言版本的js路徑輸出到html當中。另外一種方式可以新建一個多語言loader的檔案,通過前端js程式碼動態插入正確語言版本的js程式碼(檔案打包的時候需要把各語言版本的js檔案路徑輸出到頁面之中)。
import {languageCode} from "common/constant"; const loadScript = (path: string) => { const script = window.document.createElement("script"); script.setAttribute("src", path); window.document.body.appendChild(script); return new Promise((resolve, reject) => { script.onload = resolve; script.onerror = reject; }); }; const {mainFilePath} = window; if (typeof mainFilePath === "string") { loadScript(mainFilePath); }else if (typeof mainFilePath !== "string") { loadScript(mainFilePath[languageCode] ); } 複製程式碼
langs-loader 實現
在loader程式碼中拿到require檔案的實際路徑,如果存在code選項的話則根據pot檔案找到對應code的po檔案,然後使用po2json轉換為對應格式的json檔案,最後再返回給引用的地方。如果存在isLoadAll選項並且isLoadAll選項為true的話,則會load 同名pot檔案的所有對應語言版本的po檔案,然後再通過po2json轉換組合成一個json檔案,再返回出去。
langs-util 實現
langs-util主要整合了xgettext/msginit/msgmerge等方法。傳入所需要解析的檔案或資料夾,生成對應的po及pot檔案。
const genPoFiles = (inputFilePaths: string | string[], potFilePath: string, langs: string[]) => { const potDirName = dirname(potFilePath); const filename = basename(potFilePath, extname(potFilePath)); const filePaths = typeof inputFilePaths === "string" ? inputFilePaths : inputFilePaths.join(" "); execOnlyErrorOutput(`xgettext --language=JavaScript --add-comments --sort-output --from-code=UTF-8 --no-location [email protected] -o ${potFilePath} ${filePaths}`); checkFileExists(potFilePath).then((ifPotFileExists) => { if (ifPotFileExists) { console.log(green(potFilePath)); writeFileSync(potFilePath, readFileSync(potFilePath).toString().replace("charset=CHARSET", "charset=UTF-8")); langs.forEach((lang) => { const poFilePath = join(potDirName, `${filename}${lang === "" ? "" : `.${lang}` }.po`); checkFileExists(poFilePath).then((ifExists) => { if (ifExists) { execOnlyErrorOutput(`msgmerge --output-file=${poFilePath} ${poFilePath} ${potFilePath}`); }else { execOnlyErrorOutput(`msginit --no-translator --input=${potFilePath} --locale=${lang} --output=${poFilePath}`); } }); }); } }); }; 複製程式碼
生成po檔案時,需要傳入需要給xgettext解析的檔案列表以及目標pot檔案路徑還有生成的多語言版本種類。xgettext會根據傳入的解析檔案生成新的pot檔案,如果生成成功則開始生成對應語言版本的po檔案,如果對應的po檔案存在則使用msgmerge更新po檔案,如果不存在則使用msginit建立新的po檔案。