好久没上掘金了,之前一直在忙着新业务,没时间(其实自己清楚这些都只是借口,就是犯懒了),今天又带来了一个小插件,用于提取项目中所有的i18n语言文本并且翻译生成所有语种文件,如果觉得翻译接口翻的不好的可以手动修改,插件不会覆盖已有的翻译语言,完美兼容旧项目代码,希望可以提升些工作效率,早点下班,哈哈哈。
背景
因为新项目全都是需要中英双语的,之前的同事都是自己手写如this.$t(“项目”)的形式,然后在中文和英文语言配置文件中写入语言配置,新需求下来,我手都快写断了,然而测试还在不停的报有些地方漏掉了没翻译,于是便做了个插件自动生成语言配置文件了,早点下班不香吗。
想看代码的可以直接前往 pikaz-translate仓库
思路
插件思路很简单,首先递归遍历需要翻译的目录,找出目录下面的所有文件,然后提取所有文件中的内容,通过正则匹配获取类似this.$t(""),i18n.t("")的i18n插件写法中的文本,然后生成json文件,并且通过翻译接口将中文json文件,翻译成英文json文件,话不多说,上代码。
递归目录下的所有文件并提取文本
const path = require('path') const fs = require('fs') // node执行路径 const dirPath = process.cwd() // 语言json文件key const langKey = [] /** * @description: 文件夹遍历 * @param {content} content/文件夹路径 * @return {type} */ const fileTra = (content) => { //根据文件路径读取文件,返回文件列表 return new Promise((resolve, reject) => { fs.readdir(content, async function (err, files) { if (err) { console.warn(err) reject(err) } else { //遍历读取到的文件列表 for (let i = 0; i < files.length; i++) { //获取当前文件的绝对路径 const filedir = path.join(content, files[i]) //根据文件路径获取文件信息 const isFile = await fileRead(filedir) // 如果是文件夹,递归遍历该文件夹下面的文件 if (!isFile) { await fileTra(filedir) } else { // 读取文件 const fileContent = fs.readFileSync(filedir, 'utf-8') // 提取i18n语言文字 const lang = getTranslateKey(fileContent) lang.forEach((item) => { if (langKey.indexOf(item) === -1 || item === '') { langKey.push(item) } }) } } resolve(files) } }) }) } /** * @description: 匹配t('')或t("")里的内容 * @param {type} * @return {type} */ const getTranslateKey = (source) => { let result = [] const reg = /(\$|\.)t\((\'|\")([^\)\'\"]+)(\'|\")(,([^\)\'\"]+))?\)/gm let matchKey while ((matchKey = reg.exec(source))) { result.push(matchKey[3]) } return result } /** * @description: 判断是文件还是文件夹 * @param {String} filedir/文件路径 * @return {type} */ const fileRead = (filedir) => { return new Promise((resolve, reject) => { fs.stat(filedir, function (err, stats) { if (err) { console.warn('获取文件stats失败') reject(err) } else { //文件 const isFile = stats.isFile() // 文件夹 const isDir = stats.isDirectory() if (isFile) { resolve(true) } if (isDir) { resolve(false) } } }) }) } /** * @description: 将中文json文件翻译写入英文json文件 * @param {String} zh/中文语言文件路径 * @param {String} en/英文语言文件路径 * @param {String} lang/语言种类,默认英文 * @return {type} */ const pikazI18nTranslate = async (zh, en, lang = 'en') => { const zhPath = path.join(dirPath, zh) let zhJson = fs.readFileSync(zhPath, 'utf-8') zhJson = zhJson ? JSON.parse(zhJson) : {} const enPath = path.join(dirPath, en) let enJson = fs.readFileSync(enPath, 'utf-8') enJson = enJson ? JSON.parse(enJson) : {} const key = [] Object.keys(zhJson).forEach((k) => { if (Object.keys(enJson).indexOf(k) === -1) { key.push(k) } }) for (let i = 0; i < key.length; i++) { const e = await translate(key[i], lang) enJson[key[i]] = e } return new Promise((resolve, reject) => { const err = fs.writeFileSync(enPath, JSON.stringify(enJson), 'utf8') if (err) { reject(err) } resolve() }) }
翻译中文json文件为英文
const axios = require('axios') /** * @description: 翻译接口 * @param {String} zh/翻译文本 * @param {String} lang/翻译语种 * @param {String} i/请求次数,超过三次不再请求 * @return {type} */ const translate = (zh, lang) => { return new Promise((resolve, reject) => { axios .get('http://fanyi.youdao.com/translate', { params: { doctype: 'json', type: lang, i: zh, }, }) .then(async (res) => { resolve(res.data.translateResult[0][0].tgt) }) .catch((err) => { reject(err) }) }) } /** * @description: 将中文json文件翻译写入英文json文件 * @param {String} zh/中文语言文件路径 * @param {String} en/英文语言文件路径 * @param {String} lang/语言种类,默认英文 * @return {type} */ const pikazI18nTranslate = async (zh, en, lang = 'en') => { const zhPath = path.join(dirPath, zh) let zhJson = fs.readFileSync(zhPath, 'utf-8') zhJson = zhJson ? JSON.parse(zhJson) : {} const enPath = path.join(dirPath, en) let enJson = fs.readFileSync(enPath, 'utf-8') enJson = enJson ? JSON.parse(enJson) : {} const key = [] Object.keys(zhJson).forEach((k) => { if (Object.keys(enJson).indexOf(k) === -1) { key.push(k) } }) for (let i = 0; i < key.length; i++) { const e = await translate(key[i], lang) enJson[key[i]] = e } return new Promise((resolve, reject) => { const err = fs.writeFileSync(enPath, JSON.stringify(enJson), 'utf8') if (err) { reject(err) } resolve() }) }
完成
主要逻辑大致就是这样,需要注意的点有有几个:
第一:项目中的i18n语言文本最好使用中文,如this.$t(“使用中文”),这样语义化更好,也更方便查看项目以及生成语言配置文件,如果旧项目中使用的是英文,也不用担心,只需要调用提取文本的函数pikazI18nLang,将文本提取出来,再自己写入对应的中英文即可,当然,会比较麻烦,但是旧项目,也只能忍了。。。
第二:翻译接口使用的是公开的翻译接口,对于同一个ip请求频率有做限制,如果一次翻译不成功,可以试着执行第二次,或者开代理更换自己的ip,当然,想要追求更好的翻译质量,付费的翻译api当然是更好的了,可以fork本项目自己简单的改一下翻译函数即可。
快速使用小诀窍
在vscode编辑器中,打开用户片段
选择新建全局代码片段文件,起一个文件名比如i18n
在片段文件中写入
{
"Print to $t" : {
"prefix" : "t" , "body" : [ "$$t('$1')" , ] , "description" : "Log output to $t" } }
使用效果如下,输入t,回车选择第一个代码片段即可输出$t(’’),配合本插件使用,就这个feel倍儿爽:
效果
需提取的目标目录下文件中的i18n文本
在一个js文件中调用插件函数
执行该脚本文件
最后效果:
生成的中文json文件内容
生成的英文json文件内容
项目完整代码
本插件已经上传至npm,直接搜索pikaz-translate即可,也有一如既往比较完善的文档,以及示例代码,使用起来也不难。
最后
七夕了,祝愿大家七夕快乐,单身狗只能继续coding了,苦中作乐,哈哈。