工作笔记八——vue项目的多语言/国际化插件vue-i18n详解
近一个月的时间都在忙离职和入职的事情,git上面的项目这几天才开始重新维护。修复了之前的几个issue,又加了几个新的功能组件的应用。今天刚好下午得空,觉得新项目会用到vue的国际化多语言,所以把vue-i18n这个组件的文档过了一遍,总结了一下,写了个小demo包含了基本上项目常用的需求,供参考。
组件git地址:vue-i18n的github
组件文档地址:vue-i18n的文档
安装与初体验
可以直接在html中直接引入:
<script src="https://unpkg.com/vue-i18n/dist/vue-i18n.js"></script>
如果你的项目是npm管理包的,那么也可以直接使用npm安装:
npm install vue-i18n
接下来,只要在main.js实例化组件并使用就可以了。
不过这里要注意的是,Vue.use(VueI18n) 要放在实例化之前,不然会报错。
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'zh',
messages:{
'zh':{
hello:'你好'
},
'en':{
hello:'hello'
}
}
});
new Vue({
el: '#app',
router,
i18n,
store,
template: '<App/>',
components: { App }
})
ok,我们可以新建一个页面来测试我们目前为止的结果如何了。
<template>
<div class="i18n">
<mt-header fixed title="国际化测试">
<router-link to="/tool" slot="left">
<mt-button icon="back">返回</mt-button>
</router-link>
</mt-header>
<div class="content">
{{ $t('hello') }}
</div>
</div>
</template>
<script></script>
<style></style>
现在看看运行结果:
完美~
在SPA应用的组件中使用
第一步看上去很好,但是我们很多的vue项目都是基于vue-cli的SPA应用,这种在main.js中引入国际化资源的方式显然给我们的开发带来不便。所以,我们需要将国际化资源放在每一个组件中,这样的代码应该是更加合理并且易于维护的。
要实现这样的效果,也很简单,首先你需要一个loader来处理这种场景:
npm i --save-dev @kazupon/vue-i18n-loader
安装完毕后,需要在webpack的配置中,把这个loader添加到.vue文件的解析中:
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
// you need to specify `i18n` loaders key with `vue-i18n-loader` (https://github.com/kazupon/vue-i18n-loader)
i18n: '@kazupon/vue-i18n-loader'
}
}
},
// ...
]
如果你是vue-cli的脚手架项目,可能这里的配置有些不一样,我当前版本的是这样的:
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
...
]
我们只需要沿着这个依赖关系找到vueLoaderconfig就可以了,在build/目录下,修改此处代码即可:
loaders: Object.assign(utils.cssLoaders({
sourceMap: sourceMapEnabled,
extract: isProduction
}),{
i18n: '@kazupon/vue-i18n-loader'
}),
这样就把vue-i18n-loader添加到了vue文件解析的loaders中了。现在我们可以在组件内使用国际化了:
<template>
<div class="i18n">
<mt-header fixed title="国际化测试">
<router-link to="/tool" slot="left">
<mt-button icon="back">返回</mt-button>
</router-link>
</mt-header>
<div class="content">
{{ $t('helloworld') }}
</div>
</div>
</template>
<i18n>
{
"en": {
"helloworld": "hello world!"
},
"zh": {
"helloworld": "你好,世界!"
}
}
</i18n>
<script></script>
<style></style>
看看效果:
这样我们编码的时候,就不用两头维护代码了。
但是这还不是最佳实践,因为项目中有些时候是有相同的文字或其他需要国际化处理的资源的,这时候如果每个组件都维护,就会出现很多重复的代码,不美观。所以,最好的方法是全局与局部(组件)共同使用。
要想达到这种效果,首先得修改i18n的构造器:
new VueI18n({
locale:'zh',
messages:{
zh: require('./common/i18n/app-zh.json'),
en: require('./common/i18n/app-en.json'),
jp: require('./common/i18n/app-jp.json')
}
})
可以看到,这里的messages引用了三个国际化的资源,如图所示:
我们来测试一下是否可行:
<template>
<div class="i18n">
<mt-header fixed :title="$t('title')">
<router-link to="/tool" slot="left">
<mt-button icon="back">返回</mt-button>
</router-link>
</mt-header>
<div class="content">
<div>来自全局:<h4>{{ $t("person.name") }}</h4></div>
<div>来自组件:<h4>{{ $t("helloworld") }}</h4></div>
</div>
</div>
</template>
<i18n>
{
"en": {
"helloworld": "hello world!"
},
"ja": {
"helloworld": "こんにちは、世界!"
},
"zh": {
"helloworld": "你好,世界!"
}
}
</i18n>
<script></script>
<style></style>
运行一下:
可以看到,全局的和组件内的国际化资源都正确的输出了~
使用说明
上面已经介绍了i18n的基本使用以及项目中的配置了,现在可以开始学习它的其他有趣且实用的功能了。
格式化
html格式化
简言之,就是可以在值中添加html标签,但是不推荐这种方式,因为会带来安全性问题(xss),建议使用组件插值(后面介绍)来实现类似需求。
......
<div>
输出html:<h4 v-html="$t('html')"></h4>
</div>
......
<i18n>
{
"en": {
"helloworld": "hello world!"
},
"ja": {
"helloworld": "こんにちは、世界!"
},
"zh": {
"helloworld": "你好,世界!",
"html": "湖人 <span style='color:blue'>总冠军</span>"
}
}
</i18n>
.....
运行结果:
命名格式化
简言之,就是传递一个命名参数过去给引用者。
...
<div>
命名格式化:<h4>{{ $t('named-formatting',{ name: '朱辰迪'}) }}</h4>
</div>
...
<i18n>
{
...
"zh":{
....
"named-formatting": "{name} 很好"
}
}
</i18n>
运行结果:
列表格式化
跟上面的命名格式化差不多,只是传入的参数有所不同:
<div>
列表格式化1:<h4>{{ $t('list-formatting',['朱','辰','迪']) }}</h4>
</div>
<div>
列表格式化2:<h4>{{ $t('list-formatting',{ 0:'朱',1:'辰',2:'迪'}) }}</h4>
</div>
...
<i18n>
{
...
"zh":{
....
"list-formatting": "{0} {1} {2} 很好呀~"
}
}
</i18n>
运行结果:
自定义格式化
这个有点复杂,要重写构造函数,后续有时间再研究,有兴趣的朋友可以看看文档。
复数形式
个人觉得这个用处不怎么大,不细说,看代码:
<div>
复数形式:
<h4>{{ $tc('apple',11, { count:11 } ) }}</h4>/
<h4>{{ $tc('apple',1) }}</h4>/
<h4>{{ $tc('apple',0 ) }}</h4>
</div>
....
<i18n>
{
...
"zh":{
....
"apple": "no apples | one apple | {count} apples"
}
}
</i18n>
运行结果:
日期格式化
感觉这个作用也不大,很少有对日期格式严格要求的(个人想法)。也不多介绍了,直接拷了官网的例子仅作参考:
还是一样先修改构造器:
let dateTimeFormats = {
'en-US': {
short: {
year: 'numeric', month: 'short', day: 'numeric'
},
long: {
year: 'numeric',
month: 'short',
day: 'numeric',
weekday: 'short',
hour: 'numeric',
minute: 'numeric'
}
},
'zh-CN': {
short: {
year: 'numeric', month: 'short', day: 'numeric'
},
long: {
year: 'numeric',
month: 'short',
day: 'numeric',
weekday: 'short',
hour: 'numeric',
minute: 'numeric',
hour12: true
}
}
}
return new VueI18n({
locale:lang,
fallbackLocale: 'en',
dateTimeFormats,
messages:{
zh: require('./common/i18n/app-zh.json'),
en: require('./common/i18n/app-en.json'),
jp: require('./common/i18n/app-jp.json')
}
})
使用:
<div>
日期国际化:
<h4>{{ $d(new Date(), 'short','en-US') }}</h4>
<h4>{{ $d(new Date(), 'long', 'zh-CN') }}</h4>
</div>
运行结果:
Fallback
什么是fallback呢?这里的意思是指,根据指定的locale没有找到对应的资源的情况下,使用的locale。这个要在构造函数中指定。比如我这里制定了”en”作为fallback策略。
new VueI18n({
locale:lang,
fallbackLocale: 'en',
messages:{
zh: require('./common/i18n/app-zh.json'),
en: require('./common/i18n/app-en.json'),
jp: require('./common/i18n/app-jp.json')
}
})
这里引用一个中文环境下没有的资源:
<div>fallback :<h4>{{ $t("fromEn") }}</h4></div>
...
{
"en":{
"fromEn": "this is from English"
},
"zh":{
//no fromeEn key
}
}
这样它就会自动根据 fallbackLocale 指定的语言来寻找对应的key,找到后输出。如果都没有找到,那么会原样输出key值:fromEn
使用自定义指令:v-t
这个使用了vue中的自定义指令,也就是将国际化用指令实现。就是将path传递给v-t指令就可以了,但是要注意,这个path要是 字符串 类型或者 对象 类型的。
字符串类型的,直接将key传递给指令即可。
对象类型的,path 代表了key,也可以使用args来传递参数。
<div>
v-t指令国际化-1:
<h4>
<p v-t="'hello'"></p>
</h4>
</div>
<div>
v-t指令国际化-2:
<h4>
<p v-t="{path,args:{param:'KOBE'}}"></p>
</h4>
</div>
...
<i18n>
{
"en": {
...
},
"ja": {
...
},
"zh": {
"hello": "你好,世界!",
"directive": "来自指令 参数:{param}",
...
}
}
</i18n>
运行结果:
组件插值(component interpolation)
这个在前文中也提到过了。当我们的国际化资源中包含html语言时,直接使用会导致xss安全问题。另外,这种方式也有其他的优点。
比如下例:
<p>I accept xxx <a href="/term">Terms of Service Agreement</a></p>
正常情况下,如果想对上述语句使用国际化,你可能会这么做:
<p>{{ $t('term1') }}<a href="/term">{{ $t('term2') }}</a></p>
<i18n>
{
en: {
term1: 'I Accept xxx\'s',
term2: 'Terms of Service Agreement'
}
}
</i18n>
但是可以看出,这种方式非常不优雅。我们可以试一下vue-i18n推荐的方式:
<div>
组件插值:
<i18n path="term" tag="label" for="tos">
<a href="#" target="_blank">{{ $t('tos') }}</a>
</i18n>
</div>
...
<i18n>
{
....
"zh":{
"tos": "服务条款",
"term": "我接受 xxx {0}."
}
}
</i18n>
运行结果:
关于这个插值的,还有一些高级用法,因为感觉平时不太可能会涉及到这块的东西,就不写了。想看的话可以参考:组件插值的高级用法
其他
除了上述中的常用特性以外,还有一些其他的功能,比如热替换,懒加载国际化资源,修改本地语言等,都是比较简单的操作,顺着文档复制粘贴就可以了~
以上代码均已提交至github:Jerry的github 如果对你有帮助,欢迎star~