乐趣区

关于electron:ElectronVue从零开始打造一个本地文件翻译器

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

最初

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

生不出人,我很道歉!

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

退出移动版