抽空搞一个VSCode插件为i18n文案编辑提效

为什么要搞VSCode插件

为什么想要在开发时做这样一个工具,是因为在没有这个工具之前的链路是这样的:

image.png

如果有这样一个VScode 插件可以做到:

image.png

这样就节省了切换vscode/chrome以及copy文案、在国际化页面里搜索文案的时间。 如果创建/编辑一个key能节省1分钟,创建100个key就能节省一个多小时的时间 🙃

每用一次mdsi18n插件就能延长1min的寿命。。。

插件实现

VScode插件开发指南

这个插件主要利用了插件提供的一下能力:

  • Command ○ 展示创建/编辑国际化文案的Webview ○ 查找选中文案对应的国际化key
  • HoverProvider - hover到国际化key展示value
  • Menu - 选中文案后右键菜单
  • webview - 创建、编辑国际化web页面

根据上述插件能力 贴一下核心代码逻辑:


// 声明HoverProvider
const hoverDisposable = languages.registerHoverProvider(
  ['typescript', 'javascript', 'typescriptreact', 'javascriptreact', 'json'], 	// registerHoverProvider第一个参数为当前provider生效的文件类型: ts、js、tsx、jsx、json
  {
    async provideHover(document: TextDocument, position: Position, token: CancellationToken ) {
      const line = document.lineAt(position).text; // 光标所在的行
      const keyRegExp = new RegExp(`[\`|\'|\"]${KEY_PREFIX}[^\'|\'|\"]*[\`|\'|\"]`, 'g');	// 匹配符合前缀的内容
      let positionMatch: RegExpMatchArray | null = line.match(keyRegExp);
      if (positionMatch && positionMatch.length) {
        let positionWord = document.getText(document.getWordRangeAtPosition(position, keyRegExp));
        positionWord = positionWord.replace(REPLACE_SPACE_REG, '$1');
        const mcm: MarkdownString = await getValueFromHoverText(positionWord);	// 这里返回的MarkdownString就是Hover之后展示的内容
        return new Hover(mcm);
      }
    }
  }
);

// -  然后在 vscode extension的统一入口 extension.ts中挂载 HoverProvider
// 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) {
	// This line of code will only be executed once when your extension is activated
  // ...
	context.subscriptions.push(hoverDisposable);
  // ...
}

// 还是基于上面HoverProvider,因为创建/编辑的入口都在Hover上,只是getValueFromHoverText这里需要处理返回内容
// 如果当前Hover的Key没有找到文案
export async function getValueFromHoverText(mdsKey: string): Promise<MarkdownString> {
  // ...
  const webViewArgs = { webviewType: "Create", data: { mdsKey }};	// command 携带参数传递给webview,参数必须经过如下处理
  const commandUri = Uri.parse(`command:${Command.showWebview}?${encodeURIComponent(JSON.stringify(webViewArgs))}`);	// 你细品
  let noKeyTip = `*没有找到对应文案* \n\n [创建Key](${commandUri} "创建key")`;
  const markdown = new MarkdownString(noKeyTip, true);
  markdown.isTrusted = true;	// 如果想让Hover执行command 一定要设置 isTrusted,否则vscode将不识别命令
  return markdown;
}

// 如果当前Hover的Key 已经存在国际化文案,提供编辑入口
export async function getValueFromHoverText(mdsKey: string): Promise<MarkdownString> {
  const markdown: MarkdownString = new MarkdownString();
  // ...
  const webViewArgs = {
    webviewType: "Edit",
    data: {
      mdsKey,
      zhCn: valueMap.get('Simplified Chinese'),
      enUs: valueMap.get('english'),
      tagName: handleTagName(data[0].tagName || ""),
      description: data[0].description || ""
    }
	};
  const commandUri = Uri.parse(`command:${Command.showWebview}?${encodeURIComponent(JSON.stringify(webViewArgs))}`);
  let noKeyTip = `&nbsp;&nbsp;[编辑文案](${commandUri} "编辑文案")`;
  markdown.appendMarkdown(noKeyTip);
  markdown.isTrusted = true;
  return markdown;
}

// 查找国际化Key 实际是一个Command,可以通过右键菜单,或者command + shift + p 唤起
export default class FindSelectionCommand {
  public registerCommand(context: ExtensionContext) {
    const editor: TextEditor | undefined = window.activeTextEditor;	// 当前vscode激活的编辑窗口,即光标所在的编辑器窗口
    context.subscriptions.push(commands.registerCommand(Command.findSelection, async (args: FindSelectionCommandArgument) => {
      if (!args || !editor) { return; }
      const text = editor.document.getText(editor.selection);	// 可以通过 editor.selection 直接获取当前所在编辑器选中的内容
      if (!text) {
        return;
      }
      await getMdsKeyByValue(text);	// 这里处理openApi请求, 然后通过quickPick的形式展示获取到文案的Key
    }));
  }
}

export default async function getMdsKeyByValue(value: string): Promise<void> {
  const itemList: QuickPickItem[] = [];
  // ... 
	// window.showQuickPick 返回的 promise中能获取到选中的option
  const selectedItem: any = await window.showQuickPick(itemList, {canPickMany: false, placeHolder: "未找到匹配的国际化Key"});
  const editor: TextEditor | undefined = window.activeTextEditor;
  editor?.edit(editBuilder => {
    editBuilder.replace(editor.selection, selectedItem.label);
  });
}


// -最后别忘了在 vscode extension的统一入口 extension.ts中注册命令
export function activate(context: vscode.ExtensionContext) {
  // ...
	new FindSelectionCommand().registerCommand(context);
  // ...
}