文章首发于个人blog,迎接人人关注。
DI18n
前端通用国际化处置惩罚计划
背景
前端手艺一日千里,手艺栈繁多。以前端框架来讲有React
, Vue
, Angular
等等,再配以webpack
, gulp
, Browserify
, fis
等等构建东西去满足一样平常的开辟事情。同时在一样平常的事情当中,差别的项目运用的手艺栈也会不一样。当须要对部份项目举行国际化革新时,由于手艺栈的差别,这时刻你须要去寻觅和当前项目运用的手艺栈相婚配的国际化的插件东西。比方:
-
vue
+vue-i18n
-
angular
+angular-translate
-
react
+react-intl
-
jquery
+jquery.i18n.property
等等,同时能够有些页面没有运用框架,或许完全是没有举行工程化的静态前端页面。
为了削减由于差别手艺栈所带来的进修相干国际化插件的本钱及开辟历程当中能够碰到的国际化坑,在尝试着剖析前端国际化所面对的重要题目及相干的处置惩罚计划后,我以为是能够运用越发通用的手艺计划去完成国际化的事情。
国际化所面对的题目
1.言语翻译
- 静态案牍翻译(前端静态模板案牍)
- 动态案牍翻译(
server
端下发的动态数据)
2.款式
- 差别言语案牍长度不一样形成的款式紊乱
- 图片的替代
3.map表保护
4.第三方效劳
- SDK
5.本地化
- 钱银单元
- 钱银汇率
- 时刻花样
6.打包计划
- 运行时
- 编译后
处置惩罚计划
在一样平常的开辟历程当中,碰到的最多的须要国际化的场景是:言语翻译
,款式
,map表保护
及打包计划
。接下来针对这几块内容并连系一样平常的开辟流程申明国际化的通用处置惩罚计划。
起首来看下当前开辟环境能够用的手艺栈:
1.运用了构建东西
webpack
gulp
fis
browserify
- …
基于这些构建东西,运用:
Vue
Angular
React
Backbone
- …
- 未运用任何
framework
2.未运用构建东西
- 运用了
jquery
或zepto
等类库 - 原生
js
个中在第一种开辟流程当中,可用的国际化的东西可选计划较多:
从框架层面来看,各大框架都邑有相对应的国际化插件,比方:vue-i18n
, angular-translate
, react-intl
等,这些插件能够无缝接入当前的开辟环节当中。长处是这些框架层面的国际化插件运用天真,能够举行静态案牍的翻译,动态案牍的翻译。瑕玷就是开辟历程当中运用差别的框架还须要去进修相对应的插件,存在肯定的进修本钱,同时在营业代码中能够存在差别言语包推断逻辑。
从构建东西层面来看, webpack
有相对应的i18n-webpack-plugin
, gulp
有gulp-static-i18n
等响应的插件。这些插件的套路平常都是在你自定义map
言语映射表,同时根据插件定义好的须要被编译的代码花样,然后在代码的编译阶段,经由过程字符串婚配的情势去完成静态案牍的替代事情。这些插件仅仅处置惩罚了静态案牍的题目,比方一些款式
,图片替代
,class
属性,以及动态案牍的翻译
等事情并没有做。
事实上,这些插件在编译历程当中关于款式
,图片替代
, class属性
等替代事情是异常轻易完成的,而动态案牍的翻译
由于缺乏context
,所以不会挑选运用这些编译插件去完成动态案牍的翻译事情。相反,将动态案牍的翻译
放到运行时去完成应该是越发靠谱的。
然则换个角度,抛开基于这些构建东西举行开辟的框架来讲,构建东西层面的国际化插件能够很好的抹平运用差别框架的差别,经由过程将国际化的历程从运行时转到编译时,在编译的历程当中就完成大部份的国际化使命,下降进修相对应国际化插件的本钱,同时在构建打包环节可完成定制化。不过也存在肯定的瑕玷,就是这些构建东西层面的国际化插件只能完成一些基础的静态案牍的翻译,由于缺乏context
,并不能很好的去完成动态案牍的翻译事情,它比较适用于一些纯静态,偏展现性的网页。
在第二种开辟流程当中,可运用的国际化东西较少,大多都邑搭配jquery
这些类库及相对应的jquery.i18n
或i18next
等插件去完成国际化。
综合差别的构建东西,开辟框架及类库,针对差别的开辟环境似乎是能够找到一个比较通用的国际化的计划的。
这个计划的大抵思绪就是:经由过程构建东西去完成款式
, 图片替代
, 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>
这个时刻运用了一个webpack
的preloader
: locale-path-loader,它的作用就是在编译编译前,就经由过程webpack
完成言语环境的设置事情,在你的营业代码中不会涌现过量的关于言语环境变量以及很好的处置惩罚了运行时作为css
的background
的图片替代事情, 详细的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
}
}]
}]
}
....
}
经由webpack
的preloader
处置惩罚后,被插进去到页面中的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 同砚,之前和他有过频频关于国际化的讨论,同时关于编译时这块的内容,他的有篇文章(请戳我)也给了我一些比较好的思绪。