12

从零到一开发vscode插件变量翻译

需求的起因是英语渣在开发的过程中经常遇到一个变量知道中文叫啥,但是英文单词可能就忘了,或者不知道,这个时候,我之前做法是打开浏览器,打开谷歌翻译,输入中文,复制英文,然后切回vscode,粘贴结果。实在是太麻烦了,年轻的时候还好,记性好,英文单词大部分都能记住,但随着年纪越来越大,头发越来越少,记性也是越来越差,上面的步骤重复的次数也越来越多,所以痛定思痛,就开发了这款插件。因为自己也是这几天从零开始学习的插件开发,所以本文完全的记录了一个小白开发的插件开发之路,内容主要是实战类的介绍,主要从四个方面介绍来完整的展示整个插件从功能设计到发布的完整历程。

  1. 功能设计
  2. 环境搭建
  3. 插件功能开发
  4. 插件打包发布

插件已经上线,vscode插件商店搜索Translate Variable即可体验,代码也开源了,源码戳这里

功能设计

功能主要是两个功能,中译英,其他语言翻译成中文

  1. 将中文变量替换为翻译后的英文变量,多个单词需要自动驼峰,解决的场景就是日常开发经常碰到的“英语卡壳”
  2. 划词翻译,自动将各种语言翻译成中文,这解决的场景是有时候看国外项目源代码的注释碰到不会的单词不知道啥意思影响效率

环境搭建

上手的第一步,环境搭建

  1. 安装脚手架, yogenerator-code,这两个工具可以帮助我们快速构建项目,详情可见 Github

    //安装
    yarn global add yo generator-code
  2. 安装vsce,vsce可用来将开发的代码打包成.vsix后缀的文件,方便上传至微软插件商店或者本地安装

    yarn global add vsce
  3. 生成并初始化项目,初始化信息根据自己情况填写

    //初始化生成项目
    yo code

    到这一步后,选择直接打开,Open with code

    image-20211230215507341打开后会自动建立一个工作区,并生成这些文件,可根据自己需要对文件进行删减,完成这步后,我们可以直接进行开发与调试了

    image-20211230215823987

    如何进行调试?

    运行与调试面板点击Run Extention,或者快捷键F5,mac可以直接点击触控栏的调试按钮

    image-20211230220226620

    打开后会弹出一个新的vscode窗口,这个新的窗口就是你的测试环境(扩展开发宿主),你做的插件功能就是在这个新的窗口测试,打印的消息在前一个窗口的调试控制台中,比如自带的例子,在我们新窗口 cmd/ctrl+shift+p后输入Hello world后会在前一个窗口的控制台打印一些信息

    image-20211230221707832

    到这里,开发准备环境就准备好了,下一步就是开始正式的插件功能开发

插件功能开发

插件开发中,有两个重要的文件,一个是 package.json,一个是 extention.js

重要文件说明

package.json

image-20211230222536296

  • activationEvents用来注册激活事件,用来表明什么情况下会激活extention.js中的active函数。常见的有onLanguage,onCommand...更多信息可查看vscode文档activationEvents
  • main表示插件的主入口
  • contributes用来注册命令(commands),绑定快捷键(keybindings),配置设置项(configuration)等等,更多可配置项可看文档
extention.js

extention.js主要作用是作为插件功能的实现点,通过active,deactive函数,以及vscode提供的api以及一些事件钩子来完成插件功能的开发

实现翻译功能

翻译这里主要是使用了3个服务,谷歌,百度翻译,有道翻译。

  1. 谷歌翻译使用@vitalets/google-translate-api,无需配置,但是需要自己能访问外网环境

    const translate = require('@vitalets/google-translate-api');
    async function getGoogleTransResult(text, opt) {
      const { from, to } = opt;
      try {
        const result = await translate(text, { from: from, to: to });
        console.log("[ 谷歌翻译 ]", result);
        return result.text;
      } catch (error) {
        console.log(error);
      }
    }
    
    module.exports = getGoogleTransResult
  1. 百度翻译,百度翻译的比较简单,申请服务,获得appid和key,然后构造请求url直接请求就行,不知道如何申请的,可查看我之前的一篇文章Electron+Vue从零开始打造一个本地文件翻译器进行申请

    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;
  2. 有道翻译需要申请应用id与应用key,新人注册提供50块免费券,但是用完后就是收费了,不太划算

    
    const config = require('../config/index.js');
    const { createHash } = require('crypto');
    const qs = require('querystring');
    const axios = require("axios");
    
    function hash(string) {
      return createHash('sha256').update(string).digest('hex');
    }
    
    function getInput(text) {
      if (!text) {
        return;
      }
      let input;
      const strLength = text.length;
      if (strLength <= 20) {
        input = text;
      } else {
        input = `${text.substring(0, 10)}${strLength}${text.substring(strLength - 10, strLength)}`;
      }
      return input;
    }
    
    async function getYouDaoTransResult(text, opt = {}) {
      const { from, to, appid, secretKey } = opt;
      const input = getInput(text);
      const salt = (new Date).getTime();
      const curtime = Math.round(new Date().getTime() / 1000);
      const str = `${appid}${input}${salt}${curtime}${secretKey}`;
      const sign = hash(str);
      const params = {
        q: text,
        appKey: appid,
        salt: salt,
        from: from,
        to: to,
        sign: sign,
        signType: "v3",
        curtime: curtime,
      }
      try {
        const res = await axios.post(config.youdaoBaseUrl, qs.stringify(params));
        console.log(`有道翻译结果 ${res.data.translation[0]}`);
        return res.data.translation[0];
      }
      catch (err) {
        console.log(error);
      }
    }
    
    module.exports = getYouDaoTransResult
    

获取选中的文本

使用事件钩子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插件商店,当然也能本地安装使用。

传入商店

发布到线上需要到微软插件商店管理页面,创建发布者信息,如果没有微软账号,需要去申请。

image-20211229224224632

创建完成后,选择发布到vscode商店

image-20211229224338826

本地安装

本地是可以直接安装.vsix后缀插件的,找到插件菜单

image-20211229224545688

选择从VSIX安装,安装上面打包的插件就好了

image-20211229224658287

最后

vscode的中文资料有点少,这次开发大多数时间都在看英文文档,以及上外网寻找资料,真的英语太重要了,后面得多学点英语了,希望后面我使用自己做的这个插件的次数会越来越少,项目已开源,使用说明与源码传送门


kerin
497 声望573 粉丝

前端菜鸟