前端通用國際化解決方案

文章首發於我的blog,歡迎你們關注。javascript

DI18n

前端通用國際化解決方案css

背景

前端技術突飛猛進,技術棧繁多。之前端框架來講有React, Vue, Angular等等,再配以webpack, gulp, Browserify, fis等等構建工具去知足平常的開發工做。同時在平常的工做當中,不一樣的項目使用的技術棧也會不同。當須要對部分項目進行國際化改造時,因爲技術棧的差別,這時你須要去尋找和當前項目使用的技術棧相匹配的國際化的插件工具。好比:html

  • vue + vue-i18n
  • angular + angular-translate
  • react + react-intl
  • jquery + jquery.i18n.property

等等,同時可能有些頁面沒有使用框架,或者徹底是沒有進行工程化的靜態前端頁面。前端

爲了減小因爲不一樣技術棧所帶來的學習相關國際化插件的成本及開發過程當中可能遇到的國際化坑,在嘗試着分析前端國際化所面臨的主要問題及相關的解決方案後,我以爲是可使用更加通用的技術方案去完成國際化的工做。vue

國際化所面臨的問題

1.語言翻譯java

  • 靜態文案翻譯(前端靜態模板文案)
  • 動態文案翻譯(server端下發的動態數據)

2.樣式node

  • 不一樣語言文案長度不同形成的樣式錯亂
  • 圖片的替換

3.map表維護react

4.第三方服務jquery

  • SDK

5.本地化webpack

  • 貨幣單位
  • 貨幣匯率
  • 時間格式

6.打包方案

  • 運行時
  • 編譯後

解決方案

在平常的開發過程中,遇到的最多的須要國際化的場景是:語言翻譯,樣式,map表維護打包方案。接下來針對這幾塊內容並結合平常的開發流程說明國際化的通用解決方案。

首先來看下當前開發環境可能用的技術棧:

1.使用了構建工具

  • webpack
  • gulp
  • fis
  • browserify
  • ...

基於這些構建工具,使用:

  • Vue
  • Angular
  • React
  • Backbone
  • ...
  • 未使用任何framework

2.未使用構建工具

  • 使用了jqueryzepto等類庫
  • 原生js

其中在第一種開發流程當中,可用的國際化的工具可選方案較多:

從框架層面來看,各大框架都會有相對應的國際化插件,例如:vue-i18n, angular-translate, react-intl等,這些插件能夠無縫接入當前的開發環節當中。優勢是這些框架層面的國際化插件使用靈活,能夠進行靜態文案的翻譯,動態文案的翻譯。缺點就是開發過程當中使用不一樣的框架還須要去學習相對應的插件,存在必定的學習成本,同時在業務代碼中可能存在不一樣語言包判斷邏輯。

從構建工具層面來看, webpack有相對應的i18n-webpack-plugin, gulpgulp-static-i18n等相應的插件。這些插件的套路通常都是在你自定義map語言映射表,同時根據插件定義好的須要被編譯的代碼格式,而後在代碼的編譯階段,經過字符串匹配的形式去完成靜態文案的替換工做。這些插件僅僅解決了靜態文案的問題,好比一些樣式,圖片替換,class屬性,以及動態文案的翻譯等工做並無作。
事實上,這些插件在編譯過程當中對於樣式圖片替換, class屬性等替換工做是很是容易完成的,而動態文案的翻譯由於缺乏context,因此不會選擇使用這些編譯插件去完成動態文案的翻譯工做。相反,將動態文案的翻譯放到運行時去完成應該是更加靠譜的。

可是換個角度,拋開基於這些構建工具進行開發的框架來講,構建工具層面的國際化插件能夠很好的抹平使用不一樣框架的差別,經過將國際化的過程從運行時轉到編譯時,在編譯的過程當中就完成大部分的國際化任務,下降學習相對應國際化插件的成本,同時在構建打包環節可實現定製化。不過也存在必定的缺點,就是這些構建工具層面的國際化插件只能完成一些基本的靜態文案的翻譯,由於缺乏context,並不能很好的去完成動態文案的翻譯工做,它比較適用於一些純靜態,偏展現性的網頁。

在第二種開發流程當中,可以使用的國際化工具較少,大多都會搭配jquery這些類庫及相對應的jquery.i18ni18next等插件去完成國際化。

綜合不一樣的構建工具,開發框架及類庫,針對不一樣的開發環境彷佛是能夠找到一個比較通用的國際化的方案的。

這個方案的大體思路就是:經過構建工具去完成樣式, 圖片替換, class屬性等的替換工做,在業務代碼中不會出現過多的因國際化而多出的變量名,同時使用一個通用的翻譯函數去完成靜態文案動態文案的翻譯工做,而不用使用不一樣框架提供的相應的國際化插件。簡單點來講就是:

  • 依據你使用的構建工具 + 一個通用的翻譯函數去完成前端國際化

首先,這個通用的語言翻譯函數: di18n-translate。它所提供的功能就是靜態和動態文案的翻譯, 不依賴開發框架及構建工具。

npm install di18n-translate
// 模塊化寫法
  const LOCALE = 'en'
  const DI18n = require('di18n-translate')
  const di18n = new DI18n({
    locale: LOCALE,     // 語言環境 
    isReplace: false,   // 是否開始運行時(適用於沒有使用任何構建工具開發流程) 
    messages: {         // 語言映射表 
      en: {
        你好: 'Hello, {person}'
      },
      zh: {
        你好: '你好, {person}'
      }
    }
  })

  di18n繼承於一個翻譯類,提供了2個方法`$t`, `$html`:
 
  di18n.$t('你好', {person: 'xl'})   // 輸出: Hello, xl
  di18n.$html(htmlTemp)   // 傳入字符串拼接的dom, 返回匹配後的字符串,具體示例可見下文

// 外鍊形式
  <script src="./lib/di18n-translate/index.js"></script>
  <script>
    const LOCALE = 'en'
    const di18n = new DI18n({
      locale: LOCALE,
      isReplace: false,
      messages: {
        // 語言包
      }
    })
  </script>

這個時候你只須要將這個通用的翻譯函數以適當的方式集成到你的開發框架當中去。

接下來會結合具體的不一樣場景去說明下相應的解決方案:

使用MVVM類的framework

使用了MVVM類的framework時,能夠藉助framework幫你完成view層的渲染工做, 那麼你能夠在代碼當中輕鬆的經過代碼去控制class的內容, 以及不一樣語言環境下的圖片替換工做.

例如vue, 示例(1):

main.js文件:

window.LOCALE = 'en'
app.vue文件:
  <template>
    <p class="desc"
      :class="locale"   // locale這個變量去控制class的內容
      :style="{backgroundImage: 'url(' + bgImg + ')'}"  // bgImg去控制背景圖片的路徑
    ></p>
    <img :src="imgSrc"> // imgSrc去控制圖片路徑
  </template>

  <script>
    export default {
      name: 'page',
      data () {
        return {
          locale: LOCALE,
          imgSrc: require(`./${LOCALE}/img/demo.png`),
          bgImg: require(`./${LOCALE}/img/demo.png`)
        }
      }
    }
  </script>

這個時候你再加入翻譯函數,就能夠知足大部分的國際化的場景了,如今在main.js中添加對翻譯函數di18n-translate的引用:

main.js文件:

import Vue from 'vue'

window.LOCALE = 'en'
const DI18n = require('di18n-translate')
const di18n = new DI18n({
    locale: LOCALE,       // 語言環境
    isReplace: false,   // 是否進行替換(適用於沒有使用任何構建工具開發流程)
    messages: {         // 語言映射表
      en: {
        你好: 'Hello, {person}'
      },
      zh: {
        你好: '你好, {person}'
      }
    }
  })

Vue.prototype.d18n = di18n

翻譯函數的基本使用, 固然你還可使用其餘的方式集成到你的開發環境當中去:

app.vue文件:
  <template>
    <p class="desc"
      :class="locale"   // locale這個變量去控制class的內容
      :style="{backgroundImage: 'url(' + bgImg + ')'}"  // bgImg去控制背景圖片的路徑
    ></p>
    <img :src="imgSrc"> // imgSrc去控制圖片路徑
    <p>{{title}}</p>
  </template>

  <script>
    export default {
      name: 'page',
      data () {
        return {
          locale: LOCALE,
          imgSrc: require(`./${LOCALE}/img/demo.png`),
          bgImg: require(`./${LOCALE}/img/demo.png`),
          title: this.di18n.$t('你好')
        }
      }
    }
  </script>

使用mvvm framework進行國際化,上述方式應該是較爲合適的,主要是藉助了framework幫你完成view層的渲染工做, 而後再引入一個翻譯函數去完成一些動態文案的翻譯工做

這種國際化的方式算是運行時處理,不論是開發仍是最終上線都只須要一份代碼。

固然在使用mvvm framework的狀況下也是能夠不借助framework幫咱們完成的view層的這部分的功能,而經過構建工具去完成, 這部分的套路能夠參見下午的示例3

未使用mvvm框架,使用了構建工具(如webpack/gulp/browserify/fis)

使用了前端模板

國際化的方式和上面說的使用mvvm框架的方式一致,由於有模板引擎幫你完成了view層的渲染.因此對於樣式圖片class屬性的處理能夠和上述方式一致, 動態文案的翻譯需引入翻譯函數。

這種國際化的方式也算是運行時處理,開發和最終上線都只須要一份代碼。

沒有使用前端模板

由於沒用使用前端模板,便少了對於view層的處理。這個時候你的DOM結構多是在html文件中一開始就定義好的了,也多是藉助於webpack這樣能容許你使用模塊化進行開發,經過js動態插入DOM的方式。

接下來咱們先說說沒有藉助webpack這樣容許你進行模塊化開發的構建工具,DOM結構直接是在html文件中寫死的項目。這種狀況下你失去了對view層渲染能力。那麼這種狀況下有2種方式去處理這種狀況。

第一種方式就是能夠在你本身的代碼中添加運行時的代碼。大體的思路就是在DOM層面添加屬性,這些屬性及你須要翻譯的map表所對應的key值:

示例(2):

html文件:

<div class="wrapper" i18n-class="${locale}">
    <img i18n-img="/images/${locale}/test.png">
    <input i18n-placeholder="你好">
    <p i18n-content="你好"></p>
  </div>

運行時:

<script src="[PATH]/di18-translate/index.js"></script>
  <script>
    const LOCALE = 'en'
    const di18n = new DI18n({
      locale: LOCALE,
      isReplace: true,   // 開啓運行時
      messages: {
        en: {
          你好: 'Hello'
        },
        zh: {
          你好: '你好'
        }
      }
    })
  </script>

最後html會轉化爲:

<div class="wrapper en">
    <img src="/images/en/test.png">
    <input placeholder="Hello">
    <p>Hello</p>
  </div>

第二種方式就是藉助於構建工具在代碼編譯的環節就完成國際化的工做,以webpack爲例:

示例(3):

html文件:

<div class="wrapper ${locale}">
    <img src="/images/${locale}/test.png">
    <p>$t('你好')</p>
  </div>

這個時候使用了一個webpackpreloader: locale-path-loader,它的做用就是在編譯編譯前,就經過webpack完成語言環境的配置工做,在你的業務代碼中不會出現過多的關於語言環境變量以及很好的解決了運行時做爲cssbackground的圖片替換工做, 具體的locale-path-loader文檔請戳我

使用方法:

npm install locale-path-loader

webpack 1.x 配置:

module.exports = {
    ....
    preLoaders: [
      {
        test: /\.*$/,
        exclude: /node_modules/,
        loaders: [
          'eslint',
          'locale-path?outputDir=./src/common&locale=en&inline=true'
        ]
      } 
    ]
    ....
  }

webpack 2 配置:

module.exports = {
    ....
    module: {
      rules: [{
        test: /\.*$/,
        enforce: 'pre',
        exclude: /node_modules/,
        use: [{
          loader: 'locale-path-loader',
          options: {
            locale: 'en',
            outputDir: './src/common',
            inline: true
          }
        }]
      }]
    }
    ....
  }

通過webpackpreloader處理後,被插入到頁面中的DOM最後成爲:

<div class="wrapper en">
    <img src="/images/en/test.png">
    <p>Hello</p>
  </div>

可是使用這種方案須要在最後的打包環節作下處理,由於經過preloader的處理,頁面已經被翻譯成相對應的語言版本了,因此須要經過構建工具以及改變preloader的參數去輸出不一樣的語言版本文件。固然構建工具不止webpack這一種,不過這種方式處理的思路是一致的。
這種方式屬於編譯時處理,開發時只須要維護一份代碼,可是最後輸出的時候會輸出不一樣語言包的代碼。固然這個方案還須要服務端的支持,根據不一樣語言環境請求,返回相對應的入口文件。關於這裏使用webpack搭配locale-path-loader進行分包的內容可參見vue-demo:

|--deploy
  |   |
  |   |---en
  |   |    |--app.js
  |   |    |--vendor.js
  |   |    |--index.html
  |   |---zh
  |   |    |--app.js
  |   |    |--vendor.js
  |   |    |--index.html
  |   |---jp
  |   |    |--app.js
  |   |    |--vendor.js
  |   |    |--index.html
  |   |----lang.json

接下來繼續說下藉助構建工具進行模塊化開發的項目, 這些項目可能最後頁面上的DOM都是經過js去動態插入到頁面當中的。那麼,很顯然,能夠在DOM被插入到頁面前便可以完成靜態文案翻譯樣式, 圖片替換, class屬性等替換的工做。

示例(4):
html文件:

<div class="wrapper ${locale}">
    <img src="/images/${locale}/test.png">
    <p>$t('你好')</p>
  </div>

js文件:

let tpl = require('html!./index.html')
  let wrapper = document.querySelector('.box-wrapper')
  
  // di18n.$html方法即對你所加載的html字符串進行replace,最後相對應的語言版本
  wrapper.innerHTML = di18n.$html(tpl)

最後插入到的頁面當中的DOM爲:

<div class="wrapper en">
    <img src="/images/en/test.png">
    <p>Hello</p>
  </div>

這個時候動態翻譯再借助引入的di18n上的$t方法

di18n.$t('你好')

這種開發方式也屬於運行時處理,開發和上線後只須要維護一份代碼。

沒有使用任何framework構建工具的純靜態,偏展現性的網頁

這類網頁的國際化,能夠用上面提到的經過在代碼中注入運行時來完成基本的國際化的工做, 具體內容能夠參見示例(2)以及倉庫中的html-demo文件夾。

語言包map表的維護

建議將語言包單獨新建文件維護,經過異步加載的方式去獲取語言包.

項目地址(若是以爲文章不錯,請不要吝嗇你的star~~)

請戳我

最後須要感謝 @kenberkeley 同窗,以前和他有過幾回關於國際化的探討,同時關於編譯時這塊的內容,他的有篇文章(請戳我)也給了我一些比較好的思路。

相關文章
相關標籤/搜索