• 技术文章 >开发工具 >VSCode

    手把手从0教你开发一个vscode变量翻译插件

    青灯夜游青灯夜游2022-01-13 18:57:15转载1583

    本篇文章带大家从0开发一个vscode变量翻译插件,本文会从四个方面来完整的展示整个插件从功能设计到发布的完整历程,希望对大家有所帮助!

    需求的起因是英语渣在开发的过程中经常遇到一个变量知道中文叫啥,但是英文单词可能就忘了,或者不知道,这个时候,我之前做法是打开浏览器,打开谷歌翻译,输入中文,复制英文,然后切回vscode,粘贴结果。

    实在是太麻烦了,年轻的时候还好,记性好,英文单词大部分都能记住,但随着年纪越来越大,头发越来越少,记性也是越来越差,上面的步骤重复的次数也越来越多,所以痛定思痛,就开发了这款插件。

    因为自己也是这几天从零开始学习的插件开发,所以本文完全的记录了一个小白开发的插件开发之路,内容主要是实战类的介绍,主要从四个方面介绍来完整的展示整个插件从功能设计到发布的完整历程。

    1-1.gif

    【推荐学习:《vscode入门教程》】

    功能设计

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

    环境搭建

    上手的第一步,环境搭建

    2.png

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

    3.png

    如何进行调试?

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

    4.png

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

    5.png

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

    插件功能开发

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

    重要文件说明

    package.json

    6.png

    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),创建发布者信息,如果没有微软账号,需要去申请。

    7.png

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

    8.png

    本地安装

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

    9.png

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

    10.png

    最后

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

    更多关于VSCode的相关知识,请访问:vscode教程!!

    以上就是手把手从0教你开发一个vscode变量翻译插件的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    上一篇:分享两个VSCode终端中的实用小技巧 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • VSCode怎么自定义设置主题和代码颜色• 手把手带你开发一个vscode百度翻译插件• vscode中html需要安装的插件可以有哪些• 分享30款好看的VSCode主题,值得收藏!• 在VSCode中写Markdown也太爽了吧!
    1/1

    PHP中文网