官网:kazupon.github.io/vue-i18n/zh…
开始
npm install vue-i18n
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
0、项目结构
// index为i18n的声明文件,en/zh分别是各个语言的文件夹,内部再根据业务模块划分翻译模块,再import到语言文件下的index中,可对象嵌对象地引用。
├── i18n
│ ├── en
│ │ ├── otherModule.ts
│ │ └── index.ts
│ ├── index.ts
│ └── zh
│ ├── otherModule.ts
│ └── index.ts
1、弹窗组件中使用
// minxin注入i18n
Vue.use({
install(Vue, options) {
Vue.mixin({
beforeCreate() {
// @Popup 根元素
// @ts-ignore
this._i18n ??= i18n;
}
});
}
});
2、支持在js/ts文件中使用
// i18n/index文件中 将i18n对象包一层new Vue,导出
const i18n = new VueI18n({
locale: initLang ?? 'zh',
messages: {zh: zhI18n, en: enI18n}
});
function useI18n() {
return {
i18n: new Vue({i18n}),
current: i18n.locale,
setLanguage,
switchLanguage() {
if (i18n.locale === 'en') {
setLanguage('zh');
} else {
setLanguage('en');
}
}
};
}
export {useI18n};
// js文件中使用, 结构出i18n对象,即可使用i18n.$t()
import {useI18n} from '../i18n/index';
const {i18n} = useI18n();
console.log(i18n.$t('xxx'))
常用语法
1、template中使用
// 插值
<div>{{ $t('xx.x') }}<div/>
// 绑定属性
<div :title="$t('xxx.xx')"></div>
2、vue-js中使用
this.$t('xxx') ;
3、js中使用
import i18n from '../i18n/index'
i18n.$t('xxx.xx')
4、传递参数
// define
xxx: `传递{attrName}参数啊`
// use
this.$t('xxx', {attrName: yourValue});
5、传递参数并做判断
// 场景: 英文单复数有加s和不加s的区别,first,second, xth的区别
// define
value: ctx => `${ctx.named('n') > 1 ? '' : ''}`
this.$t('value', {n: yourValue});
6、$tc
单复数时使用
一个变量可对应多个值
常见问题
0、npm包内如何使用i18n?
// 场景:业务使用到的包内部没有翻译
.dist目录下:
├── i18n
│ ├── en.js
│ ├── index.js
│ └── zh.js
├── index.js
// i18n/index.js
import Lang from '@ncfe/nc.lang';
import Vue from '@ncfe/nc.vue';
import VueI18n from '@ncfe/nc.vue-i18n';
Vue.use(VueI18n);
import en from './en';
import zh from './zh';
const i18n = new VueI18n({
locale: Lang.get() || 'zh',
messages: {
zh,
en
}
});
const MyI18N = new Vue({i18n});
export default MyI18N
// 在需要使用的地方,import i18n,使用i18n.$t即可
// export i18 给业务层切换语言
1、npm包内使用了i18n如何动态切换?
//将npm包内的i18n对象导出,import到你的i18n的index文件内,再将其写在你的i18n的切换语言函数内执行。
import npmI18n from 'yourNpm';
setLanguage(lang: string, cache: boolean) {
npmI18n.$i18n.locale = lang;
}
// 方案2: 切换语言时,使用$emit(),$on()事件发布
2、npm包内部使用的i18n翻译不符合业务需求如何替换?
/**
* 暴露给业务用与更新 i18n 的方法
* @param {string} locale 语言
* @param {<{key: string, value: string}>} data 需要被变更的 i18n 对象
*/
export function updateI18nValue(locale, data) {
if (typeof data !== 'object') return;
if (locale === 'en' || locale === 'zh') {
const messages = locale === 'en' ? en : zh;
Object.entries(data).forEach(([key, value]) => {
if (key === 'string' && value === 'string') {
messages[key] = value;
}
});
i18n.setLocaleMessage(locale, {...messages, ...data});
}
}
3、引用javaScript等代码写的组件如何动态切换语言?
// 场景:富文本编辑器的语言动态切换
//强刷组件,在外层组件强刷
<cpm :key="freshKey"><cpm/>
@Watch('language')
switchLanguage() {
this.freshKey = Symbal();
}
4、后端接口返回的msg翻译(设置cookie)?
//翻译由后端接口提供,设置cookie标识语言(设置path为需要*)
// 在切换语言的函数中一起执行
setLanguage(lang, cache) {
document.setCookie(`lang=${lang}`)
}
5、接口不提供翻译的数据怎么翻译?
// 场景: 。。。
// 写一个映射表
export const judgeTitleMap = {
等待判题: 'Waiting for grades',
正在编译: 'Compiling',
正在运行: 'Running'
}
export const judgeMemoMap = {
'Test the code': '自测运行',
'Memory overload': '内存超限',
"The evaluation system hasn't yet evaluated this submitted codes. Please wait.": '评测系统还没有评测到这个提交,请稍候',
'The evaluation system is compiling. Results will come later.': '评测系统正在编译,稍候会有结果'
}
// 缓存中文描述
that.resultZH = JSON.parse(JSON.stringify(oResult.result));
that.result = oResult.result;
// 转换英文描述
if (that.result && that.result.desc && that.result.judgeReplyDesc) {
if (that.language === 'en' && that.language && that.result && that.result.desc && that.result.judgeReplyDesc) {
that.result.judgeReplyDesc = judgeTitleMap[that.result.judgeReplyDesc] || that.result.judgeReplyDesc;
for (let i in judgeMemoMap) {
if (that.result.desc.indexOf(judgeMemoMap[i]) !== -1) {
that.result.desc = that.result.desc.replace(judgeMemoMap[i], i);
}
if (that.result.memo.indexOf(judgeMemoMap[i]) !== -1) {
that.result.memo = that.result.memo.replace(judgeMemoMap[i], i);
}
}
}
}
// 中文作为对象key,英文作为value,找不到则返回默认值
if (that.language === 'en') {
judgeReplyDesc = judgeTitleMap[item.judgeReplyDesc] || item.judgeReplyDesc;
}
6、初始化随浏览器语言
// navigator.language API获取浏览器语言,并在init i18n时候使用
function getSystemLang() {
return navigator.language === 'en' ? 'en' : 'zh';
}
7、常量引用导致无法动态切换?
// 场景1、
// 在constant.ts文件中定义常量,非const也一样
const map = {
a: i18n.$t('xxx'),
b: i18n.$t('xxx')
}
// 在其他地方,如vue中import {map}
//map的数据渲染到页面上,切换语言时,map不能动态切换,
解决方案1:
不使用i18n.$t,手动写一份其他语言的map;
const map = {
a: '中文'
}
const mapEn = {
a: 'chinese'
}
将两份map同时import进来,通过language来判断使用哪份map;
// 场景2
//某i18n变量在函数内使用,并return一个字符串出来,页面使用该return的值渲染,切换语言时,无法动态切换;
解决方案1:
watch监听 language的切换,并再次调用改函数即可。
8、外部接口/组件数据如何翻译?
// 强制转换
if(language === 'en' && msg === '我爱你') msg = 'I Love You'
9、ui适配
细节提示
1、英文模式使用模版字符串
模版字符串可保留前后空格。拼接的英文句子之间要有一个空格。
2、ts提示类型不对
1、 this.$t('xxx') as string 类型断言
2、 `${this.$t('xxx')}` 模版字符串转换
3、被组件断开的拼接句子可以使用或语法
// 其他情况尽量少用,容易出错,切tc写法和t写法不一致容易遗漏
{{ $tc('xxx', 1) }} <div></div> {{ $tc('xxx', 2) }}
4、在无法获取vuex的language状态时,可只有导入i18n,获取locale获取语言
原理解析
1、$t函数
在 Vue原型上注册t。
// 在 Vue 的原型中挂载 $t 方法,把 VueI18n 对象实例的方法都注入到 Vue 实例上
Vue.prototype.$t = function (key: Path, ...values: any): TranslateResult {
const i18n = this.$i18n
return i18n._t(key, i18n.locale, i18n._getMessages(), this, ...values)
}
_t函数内部调用_translate函数,并传入messages,locale,和key
messages结构如图所示,同个传入的locale,i18n的locale找不到语言时,则使用兜底语言fallbackLocale从而获取对于语言的翻译。
_translate内部调用_interpolate,返回翻译结果。
若message语言对象内部又拆分了对个对象模块,则传入的key也需要多层调用,知道获取到字符串或返回字符串的函数。
调用parse$1,解析key字符串,成一个数组,再遍历改数组,来一层一层读取message。
function parse$1 (path) {
var keys = [];
var index = -1;
var mode = BEFORE_PATH;
var subPathDepth = 0;
var c;
var key;
var newChar;
var type;
var transition;
var action;
var typeMap;
var actions = [];
actions[PUSH] = function () {
if (key !== undefined) {
keys.push(key);
key = undefined;
}
};
actions[APPEND] = function () {
if (key === undefined) {
key = newChar;
} else {
key += newChar;
}
};
actions[INC_SUB_PATH_DEPTH] = function () {
actions[APPEND]();
subPathDepth++;
};
actions[PUSH_SUB_PATH] = function () {
if (subPathDepth > 0) {
subPathDepth--;
mode = IN_SUB_PATH;
actions[APPEND]();
} else {
subPathDepth = 0;
if (key === undefined) { return false }
key = formatSubPath(key);
if (key === false) {
return false
} else {
actions[PUSH]();
}
}
};
function maybeUnescapeQuote () {
var nextChar = path[index + 1];
if ((mode === IN_SINGLE_QUOTE && nextChar === "'") ||
(mode === IN_DOUBLE_QUOTE && nextChar === '"')) {
index++;
newChar = '\' + nextChar;
actions[APPEND]();
return true
}
}
while (mode !== null) {
index++;
c = path[index];
if (c === '\' && maybeUnescapeQuote()) {
continue
}
type = getPathCharType(c);
typeMap = pathStateMachine[mode];
transition = typeMap[type] || typeMap['else'] || ERROR;
if (transition === ERROR) {
return // parse error
}
mode = transition[0];
action = actions[transition[1]];
if (action) {
newChar = transition[2];
newChar = newChar === undefined
? c
: newChar;
if (action() === false) {
return
}
}
if (mode === AFTER_PATH) {
return keys
}
}
}
通过key解析的数组,一层一层获取值
返回结果是对象/数组,则直接返回对象/数组。如果是string/funtion则继续调用_render函数返回解析的翻译结果,否则返回null,控制台warning
如果是字符串,则执行插值函数_interpolate,传递参数为拿到的字符串,和插值values。
// 解析插值语法,判断{},拿到cout,放入tokens数组,类型是named,正常文字类型为text
最后将tokens拼接成最终的翻译文案。
type === named时,执行如下语句获取最终文案,为text则直接push
2、定义的翻译属性,若为函数:
创建一个ctx上下文,作为参数,执行message定义的函数。则调用该函数,并传入一个上下文对象作为参数,最终返回字符串。
总结:在vue原型上注册$t函数,并传入key路径,和values插值,当然内部还会获取i18n的locale语言状态和messages翻译对象。 通过messages[locale]即可获取对于语言的翻译对象。将key路径解析成一个数组,并遍历获取到messagees[locale][key]对应的string或者funtion,如果是string则,解析string内的插值语法,返回最终文案。如果是funtion则调用该funtion,并创建一个上下文为参数传入,最终返回一个string。
2、vue-i18n如何实现无刷新动态切换语言的?
// VueI18n 其实不是一个 Vue 对象,但是它在内部建立了 Vue 的实例对象 vm,所以很多的功能都是跟这个 vm 关联的
watchI18nData (): Function {
const self = this
return this._vm.$watch('$data', () => {
// 订阅的组件
const listeners = arrayFrom(this._dataListeners)
let i = listeners.length
while(i--) {
Vue.nextTick(() => {
listeners[i] && listeners[i].$forceUpdate()
})
}
}, { deep: true })
}
_initVM 方法中,我们会将翻译相关的数据 data 通过 new Vue 传递给 this._vm 实例。mixin注入berforMounted在vue实例中,若vue实例有i18n对象则收集依赖this,并在beforeDestroy中移除订阅者。当locale、插值对象等发生改变时,调用this.$forceUpdate更新视图。使用vue的watch响应式时,并创建类似vue响应式的发布订阅者模式。