前端通用国际化解决方案

文章首发于我的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 同窗,以前和他有过几回关于国际化的探讨,同时关于编译时这块的内容,他的有篇文章(请戳我)也给了我一些比较好的思路。

相关文章
相关标签/搜索