又一个欢快的周末快到了,当我脚步轻快的回到家,筹备拥抱女朋友的时候,却发现,女朋友愁眉不展,望着电脑上一堆英文文件夹,一直的豪言壮语并嘟喃道:"好累啊"。于是,我凑近一看,只见她电脑上一堆英文文件夹,一直的反复着复制文件名,而后放百度翻译里翻译成中文,而后又把翻译后的后果给文件重命名,这也太难受了吧。想到女朋友有难,我作为她的程序猿男朋友,怎么能隔岸观火,我陷入了深思,忽然想到用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);            }          });        });      });
获取文件夹门路

获取文件夹门路有两种形式获取:

  1. 设置input webkitdirectory directory属性,而后监听change事件获取到所抉择文件夹的门路
<input @change="getFile" id="attach-project-file" type="file" webkitdirectory  directory />getFile(e) {   this.path = e.target.files[0].path;},
  1. 通过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}`;            });

最初

终于实现了,我伸了伸懒腰,我乐不可支的筹备去女朋友那邀功,不小心摔了一跤,我猛地起来,啊,还好,原来是一场梦!不对!按这么说,我的女朋友。。。。我忽然伤感起来,悲伤的关上了网抑云:

生不出人,我很道歉!

原来这一切都是假的!!!那晚,泪漫湿了我的枕头!!!源码在这