前置知识
vue-i18n 插件的使用,和其他vue插件使用没有什么区别,local设置语言,message 设置语言包, 在文件中使用的时候 用$t包裹就可以了
// main.ts
import VueI18n from 'vue-i18n'Vue.use(VueI18n)
const i18n = new VueI18n({
local: 'cn', // 设置语言
messages // 语言包
})
new Vue({
el: '#app',
...
i18n
})// messages 大概的使用格式
{
cn: { name: '名字' },
us: { name: 'Name' }
}
//xxx.vue
// html 需要使用 {{}} 将 name包装起来
{{$t('name')}}
// js
$t('name')
为了更好的让读者理解,我简易的从下面几个步骤进行剖析,实现一个简易的i18n自动化脚本
一、搭建一个vue项目,直接脚手架生成,启动
二、确定要翻译的文件,读取文件内容
我这里就只写了一个app.vue,也就是上述内容,工程化时,可以使用 glob.sync 去获取文件,然后读取出文件内容
/** 获取文件内容 */ const fileContent = fs.readFileSync(path.resolve(__dirname, '../src/App.vue'), { encoding: 'utf-8' });
三、通过 vue-template-compiler 将文件转化 为对象形式
const vueCompiler = require('vue-template-compiler');
/** 获取文件内容 */
const fileContent = fs.readFileSync(path.resolve(__dirname, '../src/App.vue'), { encoding: 'utf-8' });
/** 通过vue-template-compiler 将其识别为SFC */
const vueSfc = vueCompiler.parseComponent(fileContent)
console.log(vueSfc)
四、解析html内容 获取ast树,从ast树中得到每个文本节点的起始位置
/** 通过pase5解析Html 获得html的 ast */
const ast = parse5.parse(vueSfc.template.content, {
sourceCodeLocationInfo: true
})
const textArr = flattenTree(ast.childNodes)
.filter(f => f.nodeName === '#text' && containsZH_CN(f.value))
.map(m => ({ start: m.sourceCodeLocation?.startOffset + 1, end: m.sourceCodeLocation?.endOffset, ...m }))
/* 将树结构转化为一维数组 */
function flattenTree(arr) {
let result = [];
arr.forEach(item => {
result.push(item);
if (item.childNodes && item.childNodes.length > 0) {
result = result.concat(flattenTree(item.childNodes));
}
});
return result;
}
ast-explorer 中查看一下
五、编辑替换字符串,生成新文件,写入。
let travesTemplate = vueSfc.template.content;
textArr.reverse().forEach(f => {
travesTemplate = travesTemplate.slice(0, f.start - 1) + `{{$t('${f.value}')}}` + travesTemplate.slice(f.end)
})
let finallyFile = vueSfc.source.slice(0, vueSfc.template.start) + travesTemplate + vueSfc.source.slice(vueSfc.template.end)
来看看结果吧,不到一百行,一个简易的 自动化i18n就完成了
const containsZH_CN = function (val) {
return /[\u4e00-\u9fa5]/.test(val)
}
const vueCompiler = require('vue-template-compiler');
const fs = require('fs');
const path = require('path');
const parse5 = require('parse5');
const prettier = require('prettier');
const translate = require('./i18n/translate/translator');
/** 获取文件内容 */
const fileContent = fs.readFileSync(path.resolve(__dirname, '../src/App.vue'), { encoding: 'utf-8' });
/** 通过vue-template-compiler 将其识别为SFC */
const vueSfc = vueCompiler.parseComponent(fileContent);
/** 通过pase5解析Html 获得html的 ast */
const ast = parse5.parse(vueSfc.template.content, {
sourceCodeLocationInfo: true
})
const textArr = flattenTree(ast.childNodes)
.filter(f => f.nodeName === '#text' && containsZH_CN(f.value))
.map(m => ({ start: m.sourceCodeLocation?.startOffset + 1, end: m.sourceCodeLocation?.endOffset, ...m }))
let travesTemplate = vueSfc.template.content;
textArr.reverse().forEach(f => {
travesTemplate = travesTemplate.slice(0, f.start - 1) + `{{$t('${f.value}')}}` + travesTemplate.slice(f.end)
})
let finallyFile = vueSfc.source.slice(0, vueSfc.template.start) + travesTemplate + vueSfc.source.slice(vueSfc.template.end)
function flattenTree(arr) {
let result = [];
arr.forEach(item => {
result.push(item);
if (item.childNodes && item.childNodes.length > 0) {
result = result.concat(flattenTree(item.childNodes));
}
});
return result;
}
fs.writeFileSync(path.resolve(__dirname, '../src/App.vue'), finallyFile)
const languageKey = textArr.reverse().map(m => [m.value, m.value])
const chineseLangueKeyObj = Object.fromEntries(languageKey)
const formatedLocales = prettier.format(JSON.stringify(chineseLangueKeyObj, null, ' '), {
printWidth: 120,
singleQuote: true,
tabWidth: 4,
parser: 'json',
});
fs.writeFile(path.resolve(__dirname, '../i18n/zh-CN.json'), formatedLocales, function (err, data) {
})
let englishLangueKeyObj = {}
let count = 0;
let promise = new Promise((resolve, reject) => {
(async function () {
for (let item of Object.keys(chineseLangueKeyObj)) {
result = await translate(item)
englishLangueKeyObj[item] = result.dst;
count++
if (count === Object.keys(chineseLangueKeyObj).length) {
resolve()
}
}
})()
})
promise.then(() => {
const formatedEnglishLocales = prettier.format(JSON.stringify(englishLangueKeyObj, null, ' '), {
printWidth: 120,
singleQuote: true,
tabWidth: 4,
parser: 'json',
});
fs.writeFile(path.resolve(__dirname, '../i18n/en-US.json'), formatedEnglishLocales, function (err, data) {
})
})