又一个欢快的周末快到了,当我脚步轻快的回到家,筹备拥抱女朋友的时候,却发现,女朋友愁眉不展,望着电脑上一堆英文文件夹,一直的豪言壮语并嘟喃道:” 好累啊 ”。于是,我凑近一看,只见她电脑上一堆英文文件夹,一直的反复着复制文件名,而后放百度翻译里翻译成中文,而后又把翻译后的后果给文件重命名,这也太难受了吧。想到女朋友有难,我作为她的程序猿男朋友,怎么能隔岸观火,我陷入了深思,忽然想到用 Node 不就能做到她手上的那些操作吗,于是说做就做,我立马把女朋友抛在身后,关上电脑开始口头,毕竟女人只会影响我拔剑的速度。
我的项目搭建
我的项目搭建仍旧应用的是老组合,Electron + Vue + Node,这次就不讲怎么整合 electron 和 vue 了,具体可看 Electron+vue 从零开始打造一个本地音乐播放器这篇文章。懒人可通过克隆我的模板文件间接开发,戳这里戳这里.
我的项目性能
明确要解决的几个痛点:
- 要能主动翻译
- 要能将翻译好的名字主动重命名
- 要能批量翻译
- 心愿能操作简略,能拖拽一个目录,或拖拽文件
需要明确了,上面咱们一步一步来一一解决。实现成果
我的项目实现
我的项目实现并不简单,逐个解决,对症下药。
主动翻译
测试过有道翻译,百度翻译,谷歌翻译。通过比照,最终抉择了百度翻译,百度翻译的通用翻译每月 200 万字符的收费,(QPS=10)还是很合乎我的需要的。
注册申请翻译服务
要应用翻译服务须要去百度翻译开放平台申请应用权限,抉择通用翻译服务就能够了,注册成为开发者后,新建我的项目获取 appid, 这个 appid 很重要,决定了是否能正确发动翻译申请。
拼接翻译 API
通过查看文档可知,通用翻译 API HTTP 地址为
https://api.fanyi.baidu.com/api/trans/vip/translate
可携带上面这些参数
因为平安限度,须要生成签名,签名须要 依照 appid+q+salt+ 密钥 的程序拼接失去字符串, 而后对字符串进行 md5 加密
const salt = parseInt(Math.random() * 1000000000);
const sign = md5(globalData.appid + q + salt + globalData.key);
生成签名后拼接申请的 URL
const params = encodeURI(`q=${q}&from=auto&to=${globalData.to}&appid=${globalData.appid}&salt=${salt}&sign=${sign}`
);
翻译性能代码
const md5 = require("md5");
var rp = require("request-promise");
const {globalData} = require("./config.js");
function translate(msg) {
const q = msg;
const salt = parseInt(Math.random() * 1000000000); // 加盐
const sign = md5(globalData.appid + q + salt + globalData.key); // 生成签名
const params = encodeURI(`q=${q}&from=auto&to=${globalData.to}&appid=${globalData.appid}&salt=${salt}&sign=${sign}`
);
const options = {uri: `https://fanyi-api.baidu.com/api/trans/vip/translate?${params}`,
};
return rp(options).then((res) => JSON.parse(res).trans_result);
}
module.exports = {translate,};
主体性能实现
主体性能分为:
- 批量翻译,反对向下递归翻译
- 拖拽不定量文件,或者拖拽文件夹翻译
- 重命名
批量翻译
要实现批量须要获取到指标文件夹门路,而后通过 fs.readdir 读取到目录下文件信息,遍历文件信息,如果是文件,对文件名和后缀进行宰割,而后再进行翻译操作,如果是目录,就执行递归操作。
// 读取目录中的所有文件 / 目录
fs.readdir(dirPath, (err, files) => {if (err) {
// throw err;
dialog.showMessageBox({
type: 'info',
title: '确认',
message: '请确认是否抉择了目录',
});
this.loading = false;
throw err;
}
files.forEach((fileItem) => {
// 遍历文件
fs.stat(fullPath, (err, stat) => {if (err) {throw err;}
// 判断是否为文件
if (stat.isFile()) {// 解决文件名} else if (stat.isDirectory()) {
// 递归翻译
this.startTrans(fullPath);
}
});
});
});
获取文件夹门路
获取文件夹门路有两种形式获取:
- 设置 input webkitdirectory directory 属性,而后监听 change 事件获取到所抉择文件夹的门路
<input @change="getFile" id="attach-project-file" type="file" webkitdirectory directory />
getFile(e) {this.path = e.target.files[0].path;
},
- 通过 H5 的拖拽 API 监听 drop 事件,获取到 DataTransfer 对象,DataTransfer 对象用于保留拖动并且放下的数据。
<div class="home" @drop.prevent="addFile" @dragover.prevent>
</div>
addFile(e) {
// 将伪数组转换成数组
this.droppedFiles = [...e.dataTransfer.files];
// 解决多个文件一起拖拽的状况
if (this.droppedFiles.length > 1) {
// 只有在同一个目录下能力多选,所以获取到第一个的父级目录就能够了
this.path = path.dirname(this.droppedFiles[0].path);
// 标记是否是多选
this.isDropMulti = true;
} else {this.path = this.droppedFiles[0].path;
}
},
宰割文件名和后缀
因为是翻译文件名,所以须要通过 path.extname 将文件名与后缀宰割开来,翻译后再将文件名从新组织,须要留神的是这里须要解决下文件名中的特殊字符,特殊字符会影响翻译后果,可能会导致翻译失败。
// 获取文件的后缀格局
const suffixName = path.extname(fileItem);
// 获取前缀
const initSubFileName = this.removeSymbol(fileItem.split(suffixName)[0]
);
// 移除文件名中的特殊字符
removeSymbol(fileName) {const reg = /[`~_!@#$^&*%()=|{}':;',.<>\\/?~!@#¥……&*()——|{}';:""'。,、?\s]/g;
const newFile = fileName.replace(reg, " ");
return newFile;
},
翻译文件名
对宰割好的文件名进行翻译,而后从新新的文件名称,这里要留神的是,因为百度翻译限度了(QPS=10),所以须要增加对翻译申请节流,限度其每秒不能超过 10 次。
节流函数
const {globalData} = require("./config.js");
const throttle = (function(delay = 1500) {const wait = [];
let canCall = true;
return function throttle(callback) {if (!canCall) {if (callback) wait.push(callback);
return;
}
callback();
canCall = false;
setTimeout(() => {
canCall = true;
if (wait.length) {throttle(wait.shift());
}
}, delay);
};
})(globalData.delay);
module.exports = {throttle};
翻译后重组文件名
throttle(() => {translate(initSubFileName).then(res => {if (res) {
// 如果有【】保留文件名, 如果没有就加上【】const target = this.checkName(res[0].dst);
// 拼接带后缀的文件名
const fullSuffixName = `${target}${suffixName}`;
// 翻译后的文件门路
const newPath = path.resolve(dirPath, fullSuffixName);
} else {
// 翻译失败
console.log("翻译接口服务出错");
dialog.showMessageBox({
type: "error",
title: "谬误",
message: "翻译接口服务出错"
});
}
});
});
重命名
重命名应用 node 自带的 fs.rename
fs.rename(oldPath, newPath, err => {if (err) {dialog.showErrorBox("谬误", "翻译失败,请敞开软件重试");
this.loading = false;
throw err;
}
console.log(`${initSubFileName} 已翻译成 ${fullSuffixName}`);
this.path = `${initSubFileName} 已翻译成 ${fullSuffixName}`;
});
最初
终于实现了,我伸了伸懒腰,我乐不可支的筹备去女朋友那邀功,不小心摔了一跤,我猛地起来,啊,还好,原来是一场梦!不对!按这么说,我的女朋友。。。。我忽然伤感起来,悲伤的关上了网抑云:
生不出人,我很道歉!
原来这一切都是假的!!!那晚,泪漫湿了我的枕头!!!源码在这