前言
我们公司一般有早上知识分享的规定,那天有个同事分享了如何通过Node脚本实现国际化替换 。
起因是这样的,有一个已经成熟的项目了,突然被要求实现中英文切换。前端中英文切换基本上就是通过
vue-i18n 来实现(不熟悉的可以看一下 使用vue-i18n实现中英文切换) 。
如果在项目刚开始开发的时候就开始做,问题自然不大;但是如果是一个完成的项目做,那工作量有多大可想而知。中英文词库已经做好了,但是替换是一个很艰巨的问题,如果是我的话我可能就傻傻的用 vscode 提供的全局替换功能挨个替换了。
然后就是开头说的那样,同事写了个node脚本 来做这件事,关键这个同事还是java后台。突然认识到了差距,突然绝对自己好傻逼,知识白学了。
不过还好,我之前不会,但是我写完这篇博客后,那我就会了,思路也打开了。感谢这个同事的分享(毕竟很多同事都是随便分享点没什么用的知识)
实现
言归正传,我会基于同事的分享来学习,后面会分享出来一个简单demo
初始化项目
npm init
创建相应文件夹
script
目录下存放脚本和词典库,src
目录就相当于我们项目的业务代码
词库
在script
目录下创建一个js文件来存放词库
module.exports = {
'entertainment news': '娱乐新闻',
'home news': '国内新闻',
'message settings': '消息设置',
'privacy settings': '隐私设置'
}
脚本
既然要搞就不能单纯搞个简单的,当然是弄个问答式的。毕竟咱们才是前端吗。
这里推荐一个库:https://github.com/SBoudrias/Inquirer.js,网上挺多教程的可以自己百度
安装
npm install --save inquirer@^8.0.0
简单使用
//引入问答库
const inquirer = require('inquirer');
// 问题
var questions = [
{
type: 'input',
name: 'name',
message: "你叫什么名字?"
}
]
inquirer.prompt(questions).then(answers => {
console.log(`你好 ${answers['name']}!`)
})
处理词库
首先处理这个字典,key改为value,value改为key,便于我们快速判断字典中是否包含当前中文词组。
let jsonData = require('./word-stock')
/**
* 字典处理,将key变成value,value变成key便于检索
*/
//key全部为中文的map
let cnKeyMap = new Map();
//key包含特殊字符的map
let otherKeyMap = new Map();
function genMap() {
for (const key in jsonData) {
//是否全中文
if (/^[\u4e00-\u9fa5]+$/.test(jsonData[key])) {
cnKeyMap.set(jsonData[key], key)
} else {
otherKeyMap.set(jsonData[key], key)
}
}
console.log("cnKeyMapSize:" + cnKeyMap.size)
console.log("otherKeyMap:" + otherKeyMap.size)
}
文件替换逻辑
/**
* 中英文替换的脚本
*/
//引入问答库
const inquirer = require("inquirer");
//引入fs模块
const fs = require("fs");
let jsonData = require("./word-stock");
/**
* 字典处理,将key变成value,value变成key便于检索
*/
//key全部为中文的map
let cnKeyMap = new Map();
//key包含特殊字符的map
let otherKeyMap = new Map();
function genMap() {
for (const key in jsonData) {
//是否全中文
if (/^[\u4e00-\u9fa5]+$/.test(jsonData[key])) {
cnKeyMap.set(jsonData[key], key);
} else {
otherKeyMap.set(jsonData[key], key);
}
}
console.log("cnKeyMapSize:" + cnKeyMap.size);
console.log("otherKeyMap:" + otherKeyMap.size);
}
/**
* 是否包含中文
* @param {*} str
* @returns
*/
function isChinese(str) {
var reg = /[\u4e00-\u9fa5]/;
return reg.test(str);
}
/**
* 是否是注解 左侧有/*或//或<!--,
* 在他们之后出现的中文就不用替换了
* @param {*} str
* @returns
*/
function isNotes(str) {
let first = str.trimStart();
return (
first.startsWith("/*") ||
str.indexOf("//") != -1 ||
first.startsWith("<!--")
);
}
inquirer.prompt([
{
type: "input",
name: "folder",
message: "请输入完整的文件路径?",
}
]).then((answers) => {
//文件路径
let folder = answers["folder"];
fs.access(folder, fs.constants.F_OK, (err) => {
if (err) {
console.log(`你好, ${folder}不是文件夹或文件!`);
} else {
inquirer.prompt([
{
type: "rawlist",
name: "fileType",
message: "请选择要处理的文件类型",
choices: ["All", "vue", "js"],
},
]).then((answers) => {
//处理字典值
genMap()
//文件类型
let fileType = answers["fileType"];
replaceWord(folder, fileType);
})
}
});
});
/**
* 单词替换
* dir:目录
* type:需要处理的文件类型
*/
function replaceWord(dir, type) {
// 记录匹配和没有匹配到的文字
let matchKey = ''
let noMatchKey = '';
//读取当前目录
fs.readdir(dir, (err, files) => {
if (err) {
console.error("读取失败:" + err);
return;
}
//遍历当前目录下的所有文件
files.forEach((filename) => {
//拼接文件路径
let filepath = dir + "/" + filename;
//判断文件类型
fs.stat(filepath, (err, stats) => {
if (err) {
console.error("读取失败:" + err);
return;
}
if (stats.isDirectory()) {
//文件夹,继续遍历
replaceWord(filepath, type);
} else {
// 文件,读取指定类型的文件
let pattList = {
All: new RegExp(/[vue,js]$/),
vue: new RegExp(/vue$/),
js: new RegExp(/js$/)
}
if (pattList[type].test(filename)) {
//如果是指定格式的文件,读取文件并替换词组
fs.readFile(filepath, 'utf-8', (err, data) => {
//console.log("文件:" + filepath);
if (err) {
console.error("读取失败:" + err);
return;
}
let lines = [];
// 按行读取
data.split('\n').forEach((line, index) => {
//如果是注释或者这行都没有中文就可以下一行了
if (isNotes(line) || !isChinese(line)) {
lines.push(line);
return;
}
// 首先全词匹配特殊key,如"已开票(元)"这类正则写不来,就循环的看这行有没有这种词就好了,有了就换掉
otherKeyMap.forEach((val, key) => {
// replaceAll好像只有15.0.0及以上存在,这个自行百度
line = line.replace(new RegExp(key, 'g'), (match, index) => {
// 判断前边是否有注解,从头开始截取,如果是注解则截取到字符串里一定存在注解符号
if (isNotes(line.slice(0, index))) {
//注解不替换
return match
}
//将匹配到关键字替换为英文
matchKey += '文件' + filepath + ':' + key + "->" + otherKeyMap.get(match) + "\n";
return otherKeyMap.get(match)
})
})
//匹配中文key,使用正则查找行内的中文词组,有了就替换
line = line.replace(/[\u4e00-\u9fa5]+/g, (match, index) => {
if (cnKeyMap.has(match)) {
// 注解不提货
if (isNotes(line.slice(0, index))) {
return match
}
//非注解替换,并记录日志
matchKey += '文件' + filepath + ':' + match + "->" + cnKeyMap.get(match) + "\n";
return cnKeyMap.get(match)
} else {
noMatchKey += '文件' + filepath + ':' + match + "\n";
}
return match
})
lines.push(line);
})
//存储日志
// console.log(" 匹配字符:\n" + matchKey);
// console.error(" 未匹配字符:\n" + noMatchKey);
let matchlogpath = './log/匹配.txt'
let nomatchlogpath = './log/未匹配.txt'
fs.appendFile(matchlogpath, matchKey, 'utf-8', err => {
if (err) {
console.log("追加失败!")
return
}
})
fs.appendFile(nomatchlogpath, noMatchKey, 'utf-8', err => {
if (err) {
console.log("追加失败!")
return
}
})
//将多行数据重新拼接起来
let content = lines.join('\n')
//将替换后的文件重新写入文件
fs.writeFile(filepath, content, (err) => {
if (err) {
console.error(`文件${filepath}写入失败:${err}`)
return
}
console.log(`文件${filepath}替换成功`)
})
})
}
}
});
});
})
}
效果图
注: 关于inquirer
的使用没太搞明白,由于文件读取是异步的,导致文件读取还没完成就自动进入第二个提问了,因此目前只能将提问给拆开。
打包
如果只是单纯的一个js
文件,是可以直接运行的,但是当你引入了第三方包后,就没法直接运行,否则就会如下图
所以需要将所引入的依赖一块进行打包。查了一下最简单的是esbuild
,一行代码就可以搞定
安装
npm install esbuild
添加命令
"build": "./node_modules/.bin/esbuild ./script/index.js --bundle --minify --outfile=dist.js --platform=node"
大体意思就是将依赖和项目的启动文件一起打包到dist.js
文件中,因此只需要主要
// 启动文件,你项目的启动文件,路径要填写正确
./script/index.js
// 打包后的文件
dist.js
打包
npm run build
打包后的文件
该文件是可以直接进行运行的
源码下载
关注公众号回复关键词:问答式的node脚本