从零开始开发 VS Code 插件之 Translator Helper

本文目录

  • Translator Helper 介绍

  • 开发概述

  • 创建第一个VS Code Extension

  • 需求分析

  • 操作文本

  • 调用Google Translation API

  • 实现核心功能

  • 配置命令

  • 插件配置

  • 测试插件

  • 打包插件

  • 发布插件

  • CI/CD

  • Icon及README

Translator Helper 介绍

微软 Docs 网站上线之后,我发现很多中文内容是由机器翻译的,可读性比较差。2017 年开始我参与了中文文档的本地化工作,对机器翻译的文本进行校对。Docs 的内容全部托管在 GitHub 上,参与者可以 fork 仓库后进行修改,然后提交 PR。在此过程中,我写了一个一键翻译的 VS Code Extension,极大的提高了翻译效率。圣诞节假期正好有空,完善了一下代码,并添加了图标、README 等内容,正式发布到 VS Code 的 Marketplace 里了。在此介绍一下该插件的开发过程,希望更多人能参与到本地化工作中,为其他开发者提供高质量的中文内容。

之前我写过一篇如何参与 Docs 本地化的文章,详见: 如何向微软 Docs 和本地化社区提交翻译贡献

还有一篇文章介绍了这个插件的使用方式: 提高文档翻译效率神器:VS Code 插件之 Translator Helper

在开发此插件之前,为了方便翻译,我一般是把段落复制到 Google 翻译里,翻译成中文复制粘贴过来,再手动修改。但选择>复制>粘贴的过程非常无趣,于是想找一个简单的方法自动完成这个动作。但是找遍了 VS Code Markedplace 里的翻译插件,大都是在状态栏提示翻译,或悬浮框显示翻译,没有一个能完成这个动作。于是只好自己动手写一个了。好在 VS Code 提供了非常完善的开发文档,我花了两三个小时就完成了主要功能的开发。其实对一个完善的插件来说,找 icon、写文档、做示例也相当费时间,于是拖到最近才正式发布。

我现在的做法是,同时开两个 VS Code,一个是英文原版的仓库,一个是中文仓库,两边对照看。使用 Translator Helper 可以一键翻译指定段落并插入到英文文本后面,人工校对修改一下即可,翻译效率大大提高。再也不用在 VS Code 和浏览器之间来回复制粘贴了。

将光标定位在一个段落的任意位置,按 Alt + T ,即可将当前段落自动翻译成中文并插入到该段落后面。

然后可 以人 工对部分 语句进行调整即可。 翻译后的文本也会自动选中,方便进行复制粘贴等操作。

使用了这个小工具后,翻译文档的速度大大提高了。

求五星好评啊

开发概述

开发一个 VS Code Extension 并不难,但开发一个好的Extension不容易。让我们还是从Hello World开始吧。VS Code 官网提供了详细的开发文档:https://code.visualstudio.com/api。

首先我们要确定我们要实现的功能属于哪个分类:

  • Theming [1] :使用颜色或图标主题改变 VS Code 的外观

  • Extending the Workbench [2] :在 UI 中添加自定义组件、视图

  • Webview Guide [3] :创建 WebView 来显示 HTML/CSS/JS 构造的自定义页面

  • Language Extensions Overview [4] :支持新的编程语言

  • Debugger Extension Guide [5] :支持调试某种运行时

不同的分类在开发中有不同的侧重点,使用到的 API 也不一样。比如我们要实现的翻译功能,属于 Extending the Workbench 这一类。接下来给大家介绍一下 Translator Helper 是怎么开发出来的。

创建第一个Extension

可以在这里找到详细的入门教程:https://code.visualstudio.com/api/get-started/your-first-extension

首先,使用 npm 安装 YeomanVS Code Extension Generator :

npm install -g yo generator-code

导航到要创建 Extension 的目录,使用如下命令创建项目:

yo code

CLI 会询问一些问题。第一个问题是这样的:

创建项目

这里要根据插件的具体功能来选择。对于我们的 hello world 项目,可以选择使用 TypeScript 来开发。完整的输入输出如下所示:

# ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? HelloWorld
### Press <Enter> to choose default for all options below ###

# ? What's the identifier of your extension? helloworld
# ? What's the description of your extension? LEAVE BLANK
# ? Initialize a git repository? Yes
# ? Which package manager to use? npm

code ./helloworld

这样就创建了一个名为 helloworld 的 VS Code 插件,并使用 VS Code 打开了项目。可以直接按 F5 来运行,这样会打开一个新的加载了该插件的 VS Code 窗口。

F1Ctrl+Shift+P ,输入 Hello World 命令,可以看到右下角弹出了一个消息框并显示 Hello World 。这说明插件已经正常工作了。

关键的代码都在 extension.ts 文件里。打开该文件看一下:

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

	// Use the console to output diagnostic information (console.log) and errors (console.error)
	// This line of code will only be executed once when your extension is activated
	console.log('Congratulations, your extension "helloworld" is now active!');

	// The command has been defined in the package.json file
	// Now provide the implementation of the command with registerCommand
	// The commandId parameter must match the command field in package.json
	let disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
		// The code you place here will be executed every time your command is executed

		// Display a message box to the user
		vscode.window.showInformationMessage('Hello World!');
	});

	context.subscriptions.push(disposable);
}

// this method is called when your extension is deactivated
export function deactivate() {}

代码很简单,就是使用 vscode.commands.registerCommand() 方法注册了一个命令,然后使用 vscode.window.showInformationMessage() 方法来显示右下角的消息框。

需求分析

我们的需求是,按某个快捷键,将当前段落翻译成中文,然后插入到该段落之后。让我们来查一下 VS Code支持的API。

在https://code.visualstudio.com/api/references/vscode-api这个页面可以查看所有VS Code 支持的 API。我们要操作当前的编辑器界面,实现自动选择段落、插入文本等操作,所以我找到了 TextEditor 这个对象:https://code.visualstudio.com/api/references/vscode-api#TextEditor:

TextEditor API

这个对象的属性和方法应该可以完成我们的工作。

这样思路就比较清楚了,注册快捷键和命令,选择当前光标所在的段落,发送文本到 Google 翻译 API 进行翻译,将返回的文本插入到段落之后。

操作文本

我封装了一个 DocService 来完成选择段落、插入文本等与编辑器操作相关的功能:

class DocService {
	editor: vscode.TextEditor | undefined;

	setCurrentEditor(): void {
		this.editor = vscode.window.activeTextEditor;
	}

	getParagraph(): string {
		if (this.editor !== undefined) {
			let startLine = this.editor.selection.start.line;
			let endLine = this.editor.selection.end.line;
			const endCharacter = this.editor.document.lineAt(endLine).text.length;
			this.editor.selection = new vscode.Selection(startLine, 0, startLine, endCharacter);
			var paragraph = this.editor.selection;
			let result = this.editor.document.getText(paragraph);
			if (result !== undefined) {
				return result;
			}
			else {
				return '';
			}
		} else {
			return '';
		}
	}

	getSelectionText(): string {
		if (this.editor !== undefined) {
			return this.editor.document.getText(this.editor.selection);
		} else {
			return '';
		}
	}

	insertText(text: string): void {
		if (this.editor !== undefined) {
			let end = this.editor.selection.end;
			this.editor.edit(editBuilder => {
				editBuilder.insert(end, '\n');
				editBuilder.insert(end, text);
			}).then(success => {
				if (success && this.editor !== undefined) {
					let end = this.editor.selection.end;
					this.editor.selection = new vscode.Selection(end, end);
					let startLine = this.editor.selection.start.line;
					let endLine = this.editor.selection.end.line;
					const endCharacter = this.editor.document.lineAt(endLine).text.length;
					this.editor.selection = new vscode.Selection(startLine, 0, startLine, endCharacter);
				}
			});
		}
	}

调用 Google Translation API

这个就是一般的发送 Http 请求了。可以使用一个 npm 包 @vitalets/google-translate-api 来实现。代码如下:

class GoogleTranslationService implements ITranslatorService {
	async translate(text: string, source: string, target: string): Promise<string> {
		const service = googleTranslate;
		let result = await service(text, { from: source, to: target });
		return result.text;
	}
}

实现核心功能

现在我们可以把这些功能组合起来了。照着 Hello World 例子里的代码抄一个:

let translateInsert = vscode.commands.registerCommand('translatorHelper.translateInsert', async () => {
		// The code you place here will be executed every time your command is executed
		docService.setCurrentEditor();
		const text = docService.getParagraph();
		try {
			if (text.trim() !== '') {
				let result = await servie.translate(text, source, target);
				docService.insertText(result);
			}
		} catch (error) {
			vscode.window.showErrorMessage(`Error occurs. ${error.message}`);
		}
	});

代码很容易理解,首先调用 DocService 获取当前段落,自动选择段落文本,然后发送到 Google 翻译 API,把返回的翻译文本插入到该段落之后。

配置命令

只在代码里注册命令是无法起作用的,我们还需要修改 package.json 文件来配置新添加的命令。VS Code 插件开发中有一个很重要的概念叫 Contribution Points [6] ,详细的 API 可参考此文档:https://code.visualstudio.com/api/references/contribution-points。Contribution Points 是在 package.json 文件中的一系列配置,用来声明插件的一些属性,快捷键、配置项等也需要在这里进行配置。部分代码如下:

"activationEvents": [
        "onCommand:translatorHelper.translateInsert"
    ]

以及:

"contributes": {
        "commands": [{
                "command": "translatorHelper.translateInsert",
                "title": "Translate & Insert"
            }
        ],
        "keybindings": [{
                "command": "translatorHelper.translateInsert",
                "key": "alt+t",
                "when": "editorTextFocus"
            }
        ],
        ...

可以看到,我添加了名为 translatorHelper.translateInsert 的命令,与注册命令代码中的命令名称是一致的。同时还添加了一个 keybindings ,当按下 Alt+T 的时候会触发这个命令。

  • contributes.commands : https://code.visualstudio.com/api/references/contribution-points#contributes.commands
  • contributes.keybindings : https://code.visualstudio.com/api/references/contribution-points#contributes.keybindings

插件配置

翻译的语言应该是可配置的,因此我们需要添加几个配置项。需要用到的是 contributes.configuration 。VS Code 把所有插件的配置项统一进行管理,提供了统一的 API 来读取。在 package.jsoncontributes 节里添加如下代码:

"configuration": {
            "title": "Translator Helper",
            "properties": {
                "translatorHelper.api": {
                    "type": "string",
                    "default": "google",
                    "enum": [
                        "google",
                        "google-cn"
                    ],
                    "enumDescriptions": [
                        "Google Translation API.",
                        "Google Translation API for Chinese users."
                    ],
                    "description": "Specify the api to translate the text."
                },
                "translatorHelper.sourceLanguage": {
                    "type": "string",
                    "default": "en",
                    "description": "The source language to be translated."
                },
                "translatorHelper.targetLanguage": {
                    "type": "string",
                    "default": "zh-CN",
                    "description": "The target language."
                }
            }
        }

配置项可以是文本、枚举或其他类型,还可以设置默认值、给枚举添加描述等,这样在下拉列表框里就可以看到详细的枚举项描述。详细的配置方式可参考:https://code.visualstudio.com/api/references/contribution-points#contributes.configuration。

在代码中,使用如下方式读取配置:

let config = vscode.workspace.getConfiguration("translatorHelper");
	const api = config.api;
	const source = config.sourceLanguage;
	const target = config.targetLanguage;

这样一个基本的插件就开发好了。实际效果如本文开头图片所示。

测试插件

在一个程序的完整开发流程中,测试也是重要的一个环节。在 DevOps 的整个流程中,良好的测试能够保证程序的功能质量。所以我们还需要添加测试。具体步骤可参考此文档:https://code.visualstudio.com/api/working-with-extensions/testing-extension。

默认的项目模板已经自带了完整的测试用例,存放在 src/test 目录下。所以我们只需要修改测试用例的部分代码即可。我在 src/test/suite 目录中,添加了一个默认的 markdown 文件 test.md ,里面写了个"Hello World"。在运行测试的时候,自动运行 VS Code 载入这个文件,并调用命令翻译。部分代码如下:

test("Should get the correct translation then insert it.", async () => {
		const uri = vscode.Uri.file(
			path.join(__dirname + testFileLocation)
		);
		const document = await vscode.workspace.openTextDocument(uri);
		const editor = await vscode.window.showTextDocument(document);
		// Make sure the file is fully loaded before interacting with it.
		await sleep(200);
		vscode.commands.executeCommand('extension.translateInsert').then(result => {
			assert.equal(editor.document.getText(editor.selection).indexOf('你好') > -1, true);
		});
	});

这段代码会自动载入文本,并调用该命令进行翻译,然后获取新插入的文本,对比翻译后的内容,符合要求即说明翻译功能正常。

打包插件

在我们开发前端应用时,都会使用某些打包工具压缩代码,减小体积。对于 VS Code 插件来说,这也是一个很重要的要求。载入大量零散的 js 文件速度会比较慢。VS Code 建议使用 webpack 来打包。详细过程可参考:https://code.visualstudio.com/api/working-with-extensions/bundling-extension

首先要安装 webpack:

npm i --save-dev webpack webpack-cli

还要安装 ts-loader 来让 webpack 支持 TypeScript:

npm i --save-dev ts-loader

安装 webpack 后会在项目中添加一个 webpack.config.js 文件。此外还要根据文档来修改 package.json 的内容,使用 webpack 来进行打包:

"scripts": {
        "vscode:prepublish": "webpack --mode production",
        "webpack": "webpack --mode development",
        "webpack-dev": "webpack --mode development --watch",
        "test-compile": "tsc -p ./",
        "compile": "tsc -p ./",
        "watch": "tsc -watch -p ./",
        "pretest": "npm run compile",
        "test": "node ./out/test/runTest.js",
        "deploy": "vsce publish --yarn"
    },

有前端开发经验的同学应该比较熟悉了,此处不再赘述。

发布插件

在发布之前,要安装 vsce 工具。这是一个用来打包、发布、管理 VS Code 插件的一个命令行工具。使用以下命令安装:

npm install -g vsce

在正式发布之前,还需要在 Marketplace 中注册一个账户,获取 Access Token,才能用 vsce 来发布。注册方式可参考:https://code.visualstudio.com/api/working-with-extensions/publishing-extension

后面的步骤还有创建 publisher 等。因为文档已经详细描述了过程,此处就不复制了。 需要注意, 如果没有创建 Publisher 的话,直接使用命令发布是无法成功的。

CI/CD

每次手工发布还是比较繁琐的,最好集成 Azure DevOps 来做 CI/CD。可以参考此文档来建立 Pipeline:https://code.visualstudio.com/api/working-with-extensions/continuous-integration。

这里需要注意的是,我们需要用到 Access Token 来进行发布,但这是一个敏感信息,所以需要在 Pipeline 里创建一个 Variable 来保存这个 Token,再在 YAML 文件里调用。

在Pipeline中创建Variable

YAML 代码如下:

trigger:
  branches:
    include: ['master']
  tags:
    include: ['*']

strategy:
  matrix:
    linux:
      imageName: 'ubuntu-16.04'
    mac:
      imageName: 'macos-10.13'
    windows:
      imageName: 'vs2017-win2016'

pool:
  vmImage: $(imageName)

steps:

- task: NodeTool@0
  inputs:
    versionSpec: '8.x'
  displayName: 'Install Node.js'

- bash: |
    /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
    echo ">>> Started xvfb"
  displayName: Start xvfb
  condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))

- bash: |
    echo ">>> Compile vscode-test"
    yarn && yarn compile
    echo ">>> Compiled vscode-test"
    cd sample
    echo ">>> Run sample integration test"
    yarn && yarn compile && yarn test
  displayName: Run Tests
  env:
    DISPLAY: ':99.0'

- bash: |
    echo ">>> Publish"
    yarn deploy -p $(VSCODE_MARKETPLACE_TOKEN)
  displayName: Publish
  condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))

这里使用了 $(VSCODE_MARKETPLACE_TOKEN) 来获取 Access Token的值。在trigger这样我们就实现了使用 Azure DevOps 来做自动发布,每次提交更改后,Pipelines 会自动编译,并发布到 Marketplace。

Icon 及 README

哦还有很重要的一个,插件的图标及 README 也是必须的。推荐一个免费工具 Lunacy ,在 Windows 10 商店里就可以下载,这个工具自带了一些图标,还可以下载包含 10 万多个图标的扩展包,足够你挑一个了:

Lunacy支持下载图标扩展包

将做好的icon文件放到项目目录里,在 package.json 文件中进行 配置:

"icon": "images/icon.png",

此外, 还需要在 package.json 中定义插件的id、显示名称、描述、版本、发布者、仓库地址、支持的VS Code版本、插件类别等等。还可以给插件添加几个badges,用来显示插件的各种状态,如Pipelines的编译状态、当前版本、下载量、评分等等。因为都比较直观,就不贴文件内容了。可参考此文档: https://code.visualstudio.com/api/references/extension-manifest

README 文件会显示在 Marketplace 的插件页面上,要修改自带的README文件模板详细说明插件的作用、使用方法、配置项等,最好制作几个 Gif 动画来展示插件可以做什么。制作 Gif 动画的工具很多,可自行搜索。我使用的是 SNAGIT。

小结

这只是一个非常非常简单的 VS Code 插件,但确实提高了我的工作效率。我还开发了一个支持 Word 的翻译插件,思路也是一样的。当手边没有趁手的工具时,何不自己打造一个呢,开发的过程还是非常有乐趣的。希望大家下载插件试用一下,给个五星好评就更好了哈哈 O(∩_∩)O 另外该项目也开源在 GitHub 了:https://github.com/yanxiaodi/vscode-translator-helper。欢迎加星啊

下一步计划是支持微软的Cognitive Services API,如果有兴趣的话欢迎参与,一起改进这个项目。

最后希望大家可以关注我的公众号:程序员在新西兰,了解新西兰码农的真实生活。感谢大家阅读。

了解新西兰IT行业真实码农生活

请长按上方二维码关注“程序员在新西兰”

推荐阅读

参考资料

[1]

Theming: https://code.visualstudio.com/api/extension-capabilities/theming

[2]

Extending the Workbench: https://code.visualstudio.com/api/extension-capabilities/extending-workbench

[3]

Webview Guide: https://code.visualstudio.com/api/extension-guides/webview

[4]

Language Extensions Overview: https://code.visualstudio.com/api/language-extensions/overview

[5]

Debugger Extension Guide: https://code.visualstudio.com/api/extension-guides/debugger-extension

[6]

Contribution Points: https://code.visualstudio.com/api/references/contribution-points

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章