基於webpack & gettext 的前端多語言方案

gettext 介紹

gettext 是GNU 提供的一套國際化與本地化處理的相關函數庫。大多數語言都有對應的gettext實現。本文主要使用jed 來實現gettext 一系列方法對應的功能。javascript

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 git@github.com:ezbuy/langs-loader.git --save-dev

複製代碼
  • 配置

須要修改webpack.config.js文件在module->rules(webpack 2.0)下面添加loader規則:html

{
    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區分不一樣語言版本的代碼打包。java

代碼中使用gettext系列函數

各端開發時使用各自實現的gettext方法去包裝須要實現多語言的文案。使用gettext函數包裝後,langs-util就能知道代碼中有須要多語言翻譯的地方,並能夠經過langs-util生成相關的pot及po文件。gettext方法底層咱們使用jed 來實現其對應功能。webpack

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 方法包裝一個須要翻譯的字符串。git

  • ngettext

能夠用於單複數處理,第一個參數傳入單數狀況下的翻譯,第二個參數傳入複數狀況下的翻譯,第三個傳入實際須要執行判斷的數據。github

  • sprintf

Jed內置提供了sprintf的支持,它是使用javascript-sprintf的sprintf函數的實現。web

經過gettext 一系列函數的配合,使用langs-util進行生成處理後就能生成對應的pot及po文件。typescript

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文件。shell

打包&發佈

各端打包方式都會有相應的差別,最終目的無非就是使線上代碼能夠按照不一樣語言的版本加載不一樣的語言文件。對於後臺等系統則可使用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 --msgid-bugs-address=wanglin@ezbuy.com -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文件。

相關文章
相關標籤/搜索