简单实现Vue的i18N自动化

321 阅读1分钟

前置知识

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项目,直接脚手架生成,启动

image.png

image.png

二、确定要翻译的文件,读取文件内容

我这里就只写了一个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 中查看一下

image.png

五、编辑替换字符串,生成新文件,写入。

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就完成了

image.png

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) {
    })
})