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

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

今天星期五,又一個愉快的週末快到了,當我腳步輕快地回到家,準備擁抱女朋友的時候,卻發現,女朋友愁眉苦臉,望著電腦上一堆英文資料夾,不斷的唉聲嘆氣並嘟喃道:“好累啊”。於是,我湊近一看,只見她電腦上一堆英文資料夾,不斷的重複著複製檔名,然後放百度翻譯裡翻譯成中文,然後又把翻譯後的結果給檔案重新命名,這也太難受了吧。想到女朋友有難,我作為她的程式猿男朋友,怎麼能袖手旁觀,我陷入了沉思,突然想到用Node不就能做到她手上的那些操作嗎,於是說做就做,我立馬把女朋友拋在身後,開啟電腦開始行動,畢竟女人只會影響我拔劍的速度。

專案搭建

專案搭建依舊使用的是老組合,

Electron + Vue + Node

,這次就不講怎麼整合electron和vue了,具體可看 Electron+vue從零開始打造一個本地音樂播放器這篇文章。懶人可透過克隆我的模板檔案直接開發。

專案功能

明確要解決的幾個痛點:

要能自動翻譯

要能將翻譯好的名字自動重新命名

要能批次翻譯

希望能操作簡單,能拖拽一個目錄,或拖拽檔案

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

專案實現

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

專案實現並不複雜,逐一解決,有的放矢。

自動翻譯

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

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

註冊申請翻譯服務

要使用翻譯服務需要去百度翻譯開放平臺申請使用許可權,選擇通用翻譯服務就可以了,註冊成為開發者後,新建專案獲取appid,這個appid很重要,決定了是否能正確發起翻譯請求。

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

拼接翻譯API

透過檢視文件可知,通用翻譯API HTTP地址為

https://api。fanyi。baidu。com/api/trans/vip/translate

可攜帶下面這些引數

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

由於安全限制,需要生成簽名,簽名需要 按照 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事件獲取到所選擇資料夾的路徑

getFile(e) { this。path = e。target。files[0]。path;},

透過H5的拖拽API監聽drop事件,獲取到 DataTransfer 物件,DataTransfer 物件用於儲存拖動並且放下的資料。

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}`; });

最後

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

生不出人,我很抱歉!

原來這一切都是假的!!!那晚,淚浸溼了我的枕頭!!!