這篇文章帶大家從0開發一個vscode變數翻譯插件,本文會從四個面向來完整的展示整個插件從功能設計到發布的完整歷程,希望對大家有所幫助!
需求的起因是英文渣在開發的過程中經常遇到一個變數知道中文叫啥,但是英文單字可能就忘了,或者不知道,這個時候,我之前做法是打開瀏覽器,打開谷歌翻譯,輸入中文,複製英文,然後切回vscode,貼上結果。
實在是太麻煩了,年輕的時候還好,記性好,英文單字大部分都能記住,但隨著年紀越來越大,頭髮越來越少,記性也是越來越差,上面的步驟重複的次數也越來越多,所以痛定思痛,就開發了這款外掛。
因為自己也是這幾天從零開始學習的插件開發,所以本文完全的記錄了一個小白開發的插件開發之路,內容主要是實戰類的介紹,主要從四個方面介紹來完整的展示整個插件從功能設計到發布的完整歷程。
功能設計
環境建置
外掛功能開發
#外掛程式打包發布
【推薦學習:《vscode入門教學》】
功能主要是兩個功能,中譯英,其他語言翻譯成中文
#將中文變數替換為翻譯後的英文變量,多個單字需要自動駝峰,解決的場景就是日常開發經常碰到的「英文卡殼」
#劃詞翻譯,自動將各種語言翻譯成中文,這解決的場景是有時候看國外專案原始碼的註解碰到不會的單字不知道啥意思影響效率
上手的第一步,環境建構
安裝鷹架, yo 與generator-code,這兩個工具可以幫助我們快速建立項目,詳情可見(https://github .com/Microsoft/vscode-generator-code)
//安装 yarn global add yo generator-code
#安裝vsce,vsce可用於將開發的程式碼打包成.vsix後綴的文件,方便上傳至微軟插件商店或本地安裝
yarn global add vsce
產生並初始化項目,初始化資訊根據自己情況填寫
//初始化生成项目 yo code
到這一步後,選擇直接打開,Open with code
開啟後會自動建立一個工作區,並產生這些文件,可依自己需求對文件進行刪減,完成這一步後,我們可以直接進行開發與調試了
如何調試?
去運行與調試面板點擊Run Extention,或者快捷鍵F5,mac可以直接點擊觸控列的調試按鈕
打開後會彈出一個新的vscode窗口,這個新的窗口就是你的測試環境(擴展開發宿主),你做的外掛程式功能就是在這個新的視窗測試,列印的訊息在前一個視窗的偵錯控制台中,例如自帶的例子,在我們新視窗cmd/ctrl shift p後輸入Hello world後會在前一個視窗的控制台列印一些資訊
到這裡,開發準備環境就準備好了,下一步就是開始正式的外掛功能開發
外掛程式開發中,有兩個重要的文件,一個是package.json ,一個是extention.js
重要檔案說明
##package.json
用來註冊啟動事件,用來表示在什麼情況下會啟動extention.js中的active函式。常見的有onLanguage,onCommand...更多資訊可查看vscode文檔activationEvents(https://code.visualstudio.com/api/references/activation-events)
表示外掛程式的主入口
contributes用来注册命令(commands),绑定快捷键(keybindings),**配置设置项(configuration)**等等,更多可配置项可看文档(https://code.visualstudio.com/api/references/contribution-points)
extention.js
extention.js主要作用是作为插件功能的实现点,通过active,deactive函数,以及vscode提供的api以及一些事件钩子来完成插件功能的开发
实现翻译功能
翻译这里主要是使用了两个服务,谷歌和百度翻译。
谷歌翻译参考了别人的做法,使用google-translate-token获取到token,然后构造请求url,再处理返回的body,拿到返回结果。这里还有一个没搞懂的地方就是请求url的生成很迷,不知道这一块是啥意思。
const qs = require('querystring'); const got = require('got'); const safeEval = require('safe-eval'); const googleToken = require('google-translate-token'); const languages = require('../utils/languages.js'); const config = require('../config/index.js'); // 获取请求url async function getRequestUrl(text, opts) { let token = await googleToken.get(text); const data = { client: 'gtx', sl: opts.from, tl: opts.to, hl: opts.to, dt: ['at', 'bd', 'ex', 'ld', 'md', 'qca', 'rw', 'rm', 'ss', 't'], ie: 'UTF-8', oe: 'UTF-8', otf: 1, ssel: 0, tsel: 0, kc: 7, q: text }; data[token.name] = token.value; const requestUrl = `${config.googleBaseUrl}${qs.stringify(data)}`; return requestUrl; } //处理返回的body async function handleBody(url, opts) { const result = await got(url); let resultObj = { text: '', from: { language: { didYouMean: false, iso: '' }, text: { autoCorrected: false, value: '', didYouMean: false } }, raw: '' }; if (opts.raw) { resultObj.raw = result.body; } const body = safeEval(result.body); // console.log('body', body); body[0].forEach(function(obj) { if (obj[0]) { resultObj.text += obj[0]; } }); if (body[2] === body[8][0][0]) { resultObj.from.language.iso = body[2]; } else { resultObj.from.language.didYouMean = true; resultObj.from.language.iso = body[8][0][0]; } if (body[7] && body[7][0]) { let str = body[7][0]; str = str.replace(/<b><i>/g, '['); str = str.replace(/<\/i><\/b>/g, ']'); resultObj.from.text.value = str; if (body[7][5] === true) { resultObj.from.text.autoCorrected = true; } else { resultObj.from.text.didYouMean = true; } } return resultObj; } //翻译 async function translate(text, opts) { opts = opts || {}; opts.from = opts.from || 'auto'; opts.to = opts.to || 'en'; opts.from = languages.getCode(opts.from); opts.to = languages.getCode(opts.to); try { const requestUrl = await getRequestUrl(text, opts); const result = await handleBody(requestUrl, opts); return result; } catch (error) { console.log(error); } } // 获取翻译结果 const getGoogleTransResult = async(originText, ops = {}) => { const { from, to } = ops; try { const result = await translate(originText, { from: from || config.defaultFrom, to: to || defaultTo }); console.log('谷歌翻译结果', result.text); return result; } catch (error) { console.log(error); console.log('翻译失败'); } } module.exports = getGoogleTransResult;
百度翻译,百度翻译的比较简单,申请服务,获得appid和key,然后构造请求url直接请求就行
不知道如何申请的,可查看我之前的一篇文章 Electron+Vue从零开始打造一个本地文件翻译器 进行申请
https://juejin.cn/post/6899581622471884813
const md5 = require("md5"); const axios = require("axios"); const config = require('../config/index.js'); axios.defaults.withCredentials = true; axios.defaults.crossDomain = true; axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded"; // 百度翻译 async function getBaiduTransResult(text = "", opt = {}) { const { from, to, appid, key } = opt; try { const q = text; const salt = parseInt(Math.random() * 1000000000); let str = `${appid}${q}${salt}${key}`; const sign = md5(str); const query = encodeURI(q); const params = `q=${query}&from=${from}&to=${to}&appid=${appid}&salt=${salt}&sign=${sign}`; const url = `${config.baiduBaseUrl}${params}`; console.log(url); const res = await axios.get(url); console.log('百度翻译结果', res.data.trans_result[0]); return res.data.trans_result[0]; } catch (error) { console.log({ error }); } } module.exports = getBaiduTransResult;
获取选中的文本
使用事件钩子onDidChangeTextEditorSelection,获取选中的文本
onDidChangeTextEditorSelection(({ textEditor, selections }) => { text = textEditor.document.getText(selections[0]); })
配置项的获取更新
通过vscode.workspace.getConfiguration获取到工作区的配置项,然后通过事件钩子onDidChangeConfiguration监听配置项的变动。
获取更新配置项
const { getConfiguration } = vscode.workspace; const config = getConfiguration(); //注意get里面的参数其实就是package.json配置项里面的contributes.configuration.properties.xxx const isCopy = config.get(IS_COPY); const isReplace = config.get(IS_REPLACE); const isHump = config.get(IS_HUMP); const service = config.get(SERVICE); const baiduAppid = config.get(BAIDU_APPID); const baiduKey = config.get(BAIDU_KEY); //更新使用update方法,第三个参数为true代表应用到全局 config.update(SERVICE, selectedItem, true);
监听配置项的变动
const { getConfiguration, onDidChangeConfiguration } = vscode.workspace; const config = getConfiguration(); //监听变动 const disposeConfig = onDidChangeConfiguration(() => { config = getConfiguration(); })
监听个别配置项的变动
const disposeConfig = onDidChangeConfiguration((e) => { if (e && e.affectsConfiguration(BAIDU_KEY)) { //干些什么 } })
获取当前打开的编辑器对象
vscode.window.activeTextEditor代表当前打开的编辑器,如果切换标签页,而没有设置监听,那么这个这个对象不会自动更新,所以需要使用onDidChangeActiveTextEditor来监听,并替换之前的编辑器对象
const { activeTextEditor, onDidChangeActiveTextEditor } = vscode.window; let active = activeTextEditor; const edit = onDidChangeActiveTextEditor((textEditor) => { console.log('activeEditor改变了'); //更换打开的编辑器对象 if (textEditor) { active = textEditor; } })
划词翻译悬浮提示
通过vscode.languages.registerHoverProvider注册一个Hover,然后通过activeTextEditor拿到选中的词语进行翻译,然后再通过new vscode.Hover将翻译结果悬浮提示
// 划词翻译检测 const disposeHover = vscode.languages.registerHoverProvider("*", { async provideHover(document, position, token) { const service = config.get(SERVICE); const baiduAppid = config.get(BAIDU_APPID); const baiduKey = config.get(BAIDU_KEY); let response, responseText; const selected = document.getText(active.selection); // 谷歌翻译 if (service === 'google') { response = await getGoogleTransResult(selected, { from: 'auto', to: 'zh-cn' }); responseText = response.text; } // 百度翻译 if (service === 'baidu') { response = await getBaiduTransResult(selected, { from: "auto", to: "zh", appid: baiduAppid, key: baiduKey }); responseText = response.dst; } // 悬浮提示 return new vscode.Hover(`${responseText}`); } })
替换选中的文本
获取到activeTextEditor,调用他的edit方法,然后使用回调中的replace
//是否替换原文 if (isReplace) { let selectedItem = active.selection; active.edit(editBuilder => { editBuilder.replace(selectedItem, result) }) }
复制到剪贴板
使用vscode.env.clipboard.writeText;
// 是否复制翻译结果 if (isCopy) { vscode.env.clipboard.writeText(result); }
驼峰处理
function toHump(str) { if (!str) { return } const strArray = str.split(' '); const firstLetter = [strArray.shift()]; const newArray = strArray.map(item => { return `${item.substring(0,1).toUpperCase()}${item.substring(1)}`; }) const result = firstLetter.concat(newArray).join(''); return result; } module.exports = toHump;
快捷键绑定
通过vscode.commands.registerCommand注册绑定之前package.json中设置的keybindings,需要注意的是registerCommand的第一个参数需要与keybindings的command保持一致才能绑定
registerCommand('translateVariable.toEN', async() => { //do something }) //package.json "keybindings": [{ "key": "ctrl+t", "mac": "cmd+t", "when": "editorTextFocus", "command": "translateVariable.toEN" }],
插件打包发布
打包
vsce package
打包后会在目录下生成.vsix后缀的插件
发布
插件发布主要是把打包的vsix后缀插件,传入微软vscode插件商店,当然也能本地安装使用。
传入商店
发布到线上需要到 微软插件商店管理页面(https://marketplace.visualstudio.com/manage/createpublisher),创建发布者信息,如果没有微软账号,需要去申请。
创建完成后,选择发布到vscode商店
本地安装
本地是可以直接安装.vsix后缀插件的,找到插件菜单
选择从VSIX安装,安装上面打包的插件就好了
vscode的中文資料有點少,這次開發大多數時間都在看英文文檔,以及上外網尋找資料,真的英語太重要了,後面多學點英文了,希望後面我使用自己做的這個插件的次數會越來越少,專案已開源,使用說明與原始碼傳送門(https://github.com/Kerinlin/translate-variable)
更多關於VSCode的相關知識,請造訪:vscode教學! !
以上是手把手從0教你開發一個vscode變數翻譯插件的詳細內容。更多資訊請關注PHP中文網其他相關文章!