Vue I18n 解决方案 肯定是可行的,但快速实施还是有难度。历史原因,可能我们的前端项目一开始根本没有考虑要国际化的问题。项目后期再搞国际化。页面很多,逐个文件去提取词条再逐个替换,体力活呀,一天做 100 个应该所快的了。而且机械操作难免出错。能做个批量处理来解放大家的双手吗?具有强大工具能力的 Node.js 登场了。
我们的目标是将工程中所有的 JavaScript 和 Vue 文件中的中文词条提取出来。思路是这样的。使用 Node.js 遍历读取每个 JavaScript 和 Vue 文件,然后逐行读取每个文件,用正则表达式匹配中文词组,再将匹配的词组逐行写入一个文本文件。词组的前面按规则要加词条键值。为了方便,我们以 KEY1、KEY2...KEYN
来固定位置,后期可以手动改为有意义的键值。熟悉 Node.js 的 fs
模块 API 的话,脚本不难写。有几个要注意的地方:
文件过滤
需要过滤掉 JavaScript 和 Vue 之外的文件。一种方法是读取分析文件后缀名来过滤。另外也可以根据目录名来判断。前提是项目的目录命名很规范。比如员代码都放 src
目录下,测试代码放 test
目录下,资源都放在 assets
目录下,样式都放 styles
目录下。有了这个约束代码也就好写了,而且可以看出目录名过滤运行效率更高。
// 排除目录
const excluded = ['assets', 'styles', 'css', 'i18n']
if (excluded.includes(path.basename(filePath))) return;
去除 stylel 内容
Vue 文件 style
标签中的文本直接干掉。放在第一个处理出于性能考虑。
匹配 style
/<style.*?>[\s\S]*?<\/style>/g
去除代码中的注释
也就是不能提取注释中的中文词组。逐行读取前要去掉注释行,这样后续处理性能也更高效。这一步很关键,要用到比较复杂的正则表达式。
- 匹配 htm 注释
/<!--[\s\S]*?-->/g
- 匹配 /**/ 和 // 注释
/\/\/.*|\s*\/\*[\s\S]*?\*\/\s*/g
难理解的话,可以分解成两个正则表达式。相当于先匹配 // 注释
/\/\/[\s\S].*?/g
再匹配 /**/ 注释
/\*\*([\s\S]*?)\*\/g
去除 console 输出
console
输出的中文词组也要干掉,这个正则表达式不难:
/\*\*([\s\S]*?)\*\/g
词条不重复
ES6 可以使用使用 Set 来去重。
const content = 'export default {\n\t' +
[...set].map((item, index) => `KEY${index + 1}: '${item}'`).join(',\n\t') +
'\n}'
排除已有的词条
如果要多次提取,还要考虑筛选已有的词条。完整 i18n-generator.js
脚本如下:
const fs = require('fs');
const path = require('path');
// 输出文件名
const outPutFile = 'i18n.js';
// 需要遍历的文件目录
const filePath = path.resolve('src');
// 多语言文件目录
const ZHPath = path.resolve(filePath, 'locales/zh-CN.js');
let values = [];
// 去重
let set = new Set();
// 同步读取已有的词条
try {
const data = fs.readFileSync(path.resolve(ZHPath));
data.toString().split(/\r|\n/g).forEach(line => {
if (/.+:\s'[\u4e00-\u9fa5]+/g.test(line)) {
const result = line.replace(/[',]/g, '')
.replace(/{\d}/g, '').split(':')
if (result)
values.push(result[1].trim());
}
})
} catch (e) {
console.log(e);
}
console.info('开始生成词条')
seek(filePath);
console.info('生成词条完毕')
// 调用文件遍历方法
function seek(filePath) {
// 排除目录
const excluded = ['assets', 'styles', 'css', 'i18n']
if (excluded.includes(path.basename(filePath))) return;
// 根据文件路径读取文件,返回文件列表
const files = fs.readdirSync(filePath)
// 遍历读取到的文件列表
files.forEach(filename => {
// 获取当前文件的绝对路径
const filedir = path.join(filePath, filename);
// 根据文件路径获取文件信息
const stats = fs.statSync(filedir)
// 是文件
const isFile = stats.isFile();
// 是文件夹
const isDir = stats.isDirectory();
if (isFile) {
try {
const data = fs.readFileSync(filedir, 'utf8');
const lines = data.toString()
.replace(/<style.*?>[\s\S]*?<\/style>/g, '') // 匹配 style
.replace(/<!--[\s\S]*?-->/g, '') // 匹配 html 注释
.replace(/\/\/.*|\s*\/\*[\s\S]*?\*\/\s*/g, '') // 匹配 /**/ 的注释
.replace(/console[\s\S]+?\);?/g, '') // 匹配 console 注释
.split(/\r|\n/); // 按回车分割成字符串数组
lines.forEach(line => {
const str = line.trim();
if (!str) return;
// 保留中文、常用标点符号
const result = str.match(/[\u4e00-\u9fa5?!、,。“”]+/g);
if (Array.isArray(result)) {
result.forEach(value => {
// 同步筛选已有的词条及标点词条
if (!values.includes(value) && /[\u4e00-\u9fa5]+/.test(value)) {
set.add(value);
}
})
}
})
const content = 'export default {\n\t' +
[...set].map((item, index) => `KEY${index + 1}: '${item}'`).join(',\n\t') +
'\n}'
try {
fs.writeFileSync(outPutFile, content, { flags: 'w+' });
} catch (e) {
console.log(e);
}
} catch (e) {
console.log(e);
}
}
if (isDir) {
// 递归:如果是文件夹,就遍历该文件夹下面的文件
seek(filedir);
}
})
}
最终导出的词条如下:
export default {
KEY1: '主页',
KEY2: '产品',
KEY3: '意见反馈',
KEY4: '联系我们',
KEY5: '菜单',
KEY6: '面包屑'
...
}
提示:我们使用了同步的方式操作文件,牺牲了部分性能。但使用异步方式的话,不能保证每次返回的结果是一样的,也就无法做后续的持续提取替换工作。另外,KEYN
作为键值还是可行的。有时我们为了追求完美,但耗去了更多时间。
将 i18n-generator.js
放在项目根目录下,执行 node i18n-generator
就生成 src 下所有的多语言词条。当然,我们也可以修改 filePath
变量值来生成特定目录下所有词条。如果更灵活,可以使用 Interface 类来编写一个 CLI 工具。
最后就是替换的事了,写脚本不好实现。因为我们不知道该中文词组是 template
中的还是 script
中的,而且,使用了占位或参数的词条是很难用脚本处理的。所以建议用 IDE 或编辑器的正则表达式查找替换功能就行了。
注意:JavaScript 和 Vue 文件之间,Vue 文件内部模板和脚本之间对应的 i18n 语法是不一样的
总结
所以说工程化思想在前端开发架构中越来越重要,用好了可以大大提高我们开发人员的生产率。
评论 (0)