Electron+Vue從零開始打造一個本地文件翻譯器

今天星期五,又一個愉快的週末快到了,當我腳步輕快的回到家,準備擁抱女友的時候,卻發現,女友愁眉苦臉,望着電腦上一堆英文文件夾,不斷的長吁短嘆並嘟喃道:"好累啊"。因而,我湊近一看,只見她電腦上一堆英文文件夾,不斷的重複着複製文件名,而後放百度翻譯裏翻譯成中文,而後又把翻譯後的結果給文件重命名,這也太難受了吧。想到女友有難,我做爲她的程序猿男友,怎麼能袖手旁觀,我陷入了沉思,忽然想到用Node不就能作到她手上的那些操做嗎,因而說作就作,我立馬把女友拋在身後,打開電腦開始行動,畢竟女人只會影響我拔劍的速度。javascript

項目搭建

項目搭建依舊使用的是老組合,Electron + Vue + Node,此次就不講怎麼整合electron和vue了,具體可看 Electron+vue從零開始打造一個本地音樂播放器這篇文章。懶人可經過克隆個人模板文件直接開發,戳這裏戳這裏.html

項目功能

明確要解決的幾個痛點:vue

  • 要能自動翻譯
  • 要能將翻譯好的名字自動重命名
  • 要能批量翻譯
  • 但願能操做簡單,能拖拽一個目錄,或拖拽文件

需求明確了,下面我們一步一步來逐個解決。實現效果java

D3CNdJ.gif

項目實現

項目實現並不複雜,逐一解決,有的放矢。node

自動翻譯

測試過有道翻譯,百度翻譯,谷歌翻譯。通過對比,最終選擇了百度翻譯,百度翻譯的通用翻譯每個月200萬字符的免費,(QPS=10)仍是很符合個人需求的。 DMVOc6.pnggit

註冊申請翻譯服務

要使用翻譯服務須要去百度翻譯開放平臺申請使用權限,選擇通用翻譯服務就能夠了,註冊成爲開發者後,新建項目獲取appid,這個appid很重要,決定了是否能正確發起翻譯請求。github

D1LVAK.png

拼接翻譯API

經過查看文檔可知,通用翻譯API HTTP地址爲web

https://api.fanyi.baidu.com/api/trans/vip/translateapi

可攜帶下面這些參數數組

D1jFns.png

因爲安全限制,須要生成簽名,簽名須要 按照 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}`;
            });
複製代碼

最後

終於完成了,我伸了伸懶腰,我興高采烈的準備去女友那邀功,不當心摔了一跤,我猛地起來,啊,還好,原來是一場夢!不對!按這麼說,個人女友。。。。我忽然傷感起來,悲傷的打開了網抑雲:

生不出人,我很抱歉!

原來這一切都是假的!!!那晚,淚浸溼了個人枕頭!!!源碼在這

相關文章
相關標籤/搜索