如何实现国际化(i18n)

千里之行,始于足下

老子<<道德经>>

声明


本篇文章的源代码基于开源组件库 vant

需求分析


在开始实现功能之前我们先来看下,通用的i18n库的使用流程,如下所示:

i18n使用流程图

从上图可知,要想使用i18n得先导入这个包,然后使用Vue.use方法去install这个包,再进行初始化i18n实例,最后在new Vue的时候挂载到全局。

所以我们设计的时候需要提供一个 install 方法,用于初始化,也需要提供 localemessages 去标记当前语言类型和具体的语言对应的内容,最后挂载到Vue的全局实例上。继续往下走 -↓

设计实战


实现细节如下所示:

  1. 定义一个 install 方法,用于初始化。在这个方法中我们创建2个挂载在Vue原型上的响应式对象,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
import defaultMessages from './lang/zh-CN' // 对应的默认语言包
...

const proto = Vue.prototype
const defaultLang = 'zh-CN'
...
function install () {
...
Vue.util.defineReactive(proto, '$prefixLang', defaultLang)
Vue.util.defineReactive(proto, '$prefixMessages', {
[defaultLang]: defaultMessages,
})
}

从上可知,定义了2个私有属性 $prefixLang (对应locale) 和 $prefixMessages (对应messages) (注意为变量名带上自定义的 prefix) 在Vue的原型上,这样就达到了挂载到全局的目的,但是为了能够使之变为可响应式的对象,还需要利用 Vue.util.defineReactive 方法(这个方法没有在官网上暴露,需要通过分析源码时获知,更具体的解释见→附录←

  1. 定义一个 use 方法进行语言的切换。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import deepAssign from '../utils/deep-assign'
...

const proto = Vue.prototype

...

function use (lang, messages) {
proto.$prefixLang = lang
add({
[lang]: messages,
})
}

function add (messages = {}) {
// 将 messages 所有可枚举属性复制到 $prefixMessages 属性上
deepAssign(proto.$prefixMessages, messages)
}

从上可以,我们可以通过 use 方法并配合 deepAssign 方法 来进行当前语言和对应的内容的切换, deepAssign 的实现细节见 →附录←

  1. 为了能够在Vue组件模板中使用,同样需要创建一个 $t 函数,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

function get (object, path) {
const keys = path.split('.')
let result = object

keys.forEach(key => {
result = isDef(result[key]) ? result[key] : ''
})

return result
}

const camelizeRE = /-(\w)/g

function camelize (str) {
return str.replace(camelizeRE, (_, c) => c.toUpperCase())
}

// component mixin
export default {
computed: {
$t () {
const { name } = this.$options // 用于获取当前 Vue 实例的初始化选项
const prefix = name ? camelize(name) + '.' : ''

if (process.env.NODE_ENV !== 'production' && !this.$prefixMessages) {
console.warn('[XXX] Locale not correctly registered.')
return () => ''
}

const messages = this.$prefixMessages[this.$prefixLang]
return (path, ...args) => {
const message = get(messages, prefix + path) || get(messages, path)
return typeof message === 'function' ? message.apply(null, args) : message
}
},
},
}

从上可知,创建了一个可混入的 $t computed 属性, 然后在对应的Vue文件上使用 mixins 的方式混入(更多源码),这样我们就能在Vue组件模板上使用 $t 函数来做国际化了,如下所示:

1
2
3
<template>
<p>{{ $t('key') }}</p>
</template>

至此,如何实现国际化已经全部分析完毕。

附录: