又一个欢快的周末快到了,当我脚步轻快的回到家,筹备拥抱女朋友的时候,却发现,女朋友愁眉不展,望着电脑上一堆英文文件夹,一直的豪言壮语并嘟喃道:"好累啊"。于是,我凑近一看,只见她电脑上一堆英文文件夹,一直的反复着复制文件名,而后放百度翻译里翻译成中文,而后又把翻译后的后果给文件重命名,这也太难受了吧。想到女朋友有难,我作为她的程序猿男朋友,怎么能隔岸观火,我陷入了深思,忽然想到用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}`; });
最初
终于实现了,我伸了伸懒腰,我乐不可支的筹备去女朋友那邀功,不小心摔了一跤,我猛地起来,啊,还好,原来是一场梦!不对!按这么说,我的女朋友。。。。我忽然伤感起来,悲伤的关上了网抑云:
生不出人,我很道歉!
原来这一切都是假的!!!那晚,泪漫湿了我的枕头!!!源码在这