寫個vscode外掛, 幫助閱讀i18n專案的程式碼

lulu_up發表於2021-10-06

寫個vscode外掛, 幫助閱讀i18n專案的程式碼

image.png

介紹

     作者當前參與的專案是面向全球使用者的, 專案免不了要做國際化處理, 哪怕只是一個簡單的ok按鈕ok文案都要翻譯成不同國家的語言展示, 所以在程式碼裡面往往我們不可以直接命名為ok, 而是可能命名為user_base_plan_edit_ok_button這類的名字, 並且為了區分i18n與其它命名, 我們團隊會採用全大寫的方式類似USER_BASE_PLAN_EDIT_OK_BUTTON, 而這樣的程式碼多了之後閱讀起來會讓眼睛乾澀, 類似下面圖裡的虛擬碼:

image.png

     為了解決這個不好閱讀的問題我比較預期的效果是, 當滑鼠懸浮在目標文字身上時會浮現出一個彈框, 而這個彈框至少要告訴我這個詞語的中文或英文意思, 效果如下:

image.png

     可以看出這個外掛如果做好了不一定只能用在i18n這個只是點上, 可以用在很多方面, 比如某個code103993283這個code具體代表什麼含義可以做個外掛展示出來。

一: 初始化vscode專案

     沒做過vscode外掛的同學推薦先讀讀我寫的入門教程:

     這次做的外掛是針對我當前工程的, 沒有廣泛應用能力所以就沒釋出到vscode官方, 開發好後打包發到公司內網與群裡就ok了。

     建立專案: 這裡專案名暫定叫i18n2c

   yo code

image.png

二: 初始化架構

     extension.js檔案我們清理乾淨:

const vscode = require("vscode");

function activate(context) {
  vscode.window.showInformationMessage("i18n翻譯外掛載入完成");
}

module.exports = {
  activate,
};

     修改package.json檔案: 設定當資源載入完就開始載入我們的外掛

{
// ...

  "activationEvents": [
    "onStartupFinished"
  ],

// ...
}

     點選f5開啟除錯模式, 出現下圖就證明外掛可以正常啟動了:
image.png

三: 初始化hover檔案

     我們把hover後的邏輯都放在src裡面:

image.png

簡單改造一下extension.js, 把hover方法加進去:

const vscode = require("vscode");
const hover = require("./src/hover");

function activate(context) {
  vscode.window.showInformationMessage("i18n翻譯外掛載入完成");

  context.subscriptions.push(hover);
}

module.exports = {
  activate,
};

     hover.js 匯出懸停時的處理方法

const vscode = require("vscode");

module.exports = vscode.languages.registerHoverProvider("*", {
  provideHover(document, position) {
    return new vscode.Hover(`i18n外掛前來助你渡劫`);
  },
});

     當你懸停在任何位置的時候效果如下:

image.png

四: i18n目標文案的識別

     每個團隊的技術方案都不一樣, 這裡我只針對我們團隊的方案進行設計, 希望起到一個拋磚引玉的目的:

     我們團隊的i18n文案都是大寫, 並且使用下劃線相互連結, 而我發現全域性除了i18n文案外沒有字串裡超過三個下劃線, 並且i18n文案至少四個下劃線, 所以當前我使用的是判斷字串裡面是否含有超過三個 '_'

const vscode = require("vscode");

module.exports = vscode.languages.registerHoverProvider("*", {
  provideHover(document, position) {
    const word = document.getText(document.getWordRangeAtPosition(position));

    if (word.split("_").length > 3) {
      return new vscode.Hover(`i18n外掛前來助你渡劫`);
    }

  },
});

上面的word就是我們獲取到的被懸停的文字

五: 如何翻譯成中文

     上面我們找到了要被翻譯的文案, 這裡我們就要研究如何翻譯這個文案了, 一般針對i18n都會有個翻譯的字典包, 我們團隊的就是一個json檔案, 不同的是每個key是小寫的, 大致的樣子如下:

image.png

     我要做的就是讀取這個檔案, 然後將要被翻譯的文案轉換為小寫, 再去匹配一下就ok了。

const vscode = require("vscode");
const fs = require("fs");

module.exports = vscode.languages.registerHoverProvider("*", {
  provideHover(document, position) {
    const word = document.getText(document.getWordRangeAtPosition(position));

    let jsonCN = JSON.parse(
      fs.readFileSync("/xxxx/xxxxx/langs/zh-CN.json")
    );

    if (word.split("_").length > 3) {
      return new vscode.Hover(`i18n外掛前來助你渡劫:
- 中文: ${jsonCN[word.toLocaleLowerCase()]}
      `);
    }

  },
});

image.png

     我們的i18n字典都是放在同一個資料夾裡面的, 所以我只需要知道這個資料夾即可, 預設取出裡面的中文與英文文案, 改造一下吧。

const vscode = require("vscode");
const fs = require("fs");

module.exports = vscode.languages.registerHoverProvider("*", {
  provideHover(document, position) {
    const word = document.getText(document.getWordRangeAtPosition(position));

    if (word.split("_").length > 3) {
      const i18nPath = "/xxxx/xxxxx/langs";
      const jsonUrlCN = i18nPath + "/zh-CN.json";
      const jsonUrlEN = i18nPath + "/en.json";
      let jsonCN = JSON.parse(fs.readFileSync(jsonUrlCN));
      let jsonEN = JSON.parse(fs.readFileSync(jsonUrlEN));
      return new vscode.Hover(`i18n外掛前來助你渡劫:
- 中文: ${jsonCN[word.toLocaleLowerCase()]}
- 英文: ${jsonEN[word.toLocaleLowerCase()]}
      `);
    }
  },
});

image.png

這裡的i18nPath當然可以由使用者來配置啦, 接下來我們就把它研究。

六: setting配置

     我們翻譯字典檔案所在的位置肯定不能寫死在外掛裡面, 很多時候需要使用者自己來設定, 這時我們就需要vscode的setting的概念了, 具體如下圖所示:

image.png

第一步: 初始化配置項

我們來到package.json檔案新增setting配置項:

{
  "contributes": {
    "configuration": {
      "type": "object",
      "title": "i18n翻譯配置",
      "properties": {
        "vscodePluginI18n.i18nPath": {
          "type": "string",
          "default": "",
          "description": "翻譯檔案的位置"
        },
        "vscodePluginI18n.open": {
          "type": "boolean",
          "default": true,
          "description": "開啟18n翻譯"
        }
      }
    }
  },
}

type設定為布林會自動生成CheckBox還挺方便的。

第二步: 初始外掛讀取配置

extension.js檔案:

const vscode = require("vscode");

function activate(context) {
  const i18nPath = vscode.workspace
    .getConfiguration()
    .get("vscodePluginI18n.i18nPath");
  const open = vscode.workspace.getConfiguration().get("vscodePluginI18n.open");

  if (open && i18nPath) {
    vscode.window.showInformationMessage("i18n翻譯外掛已就位");
    const hover = require("./src/hover");
    context.subscriptions.push(hover);
  }
}

module.exports = {
  activate,
};

這裡要注意, 如果const hover = require("./src/hover"); 寫在最上方, 可能會導致無法關閉懸停翻譯效果, 這個裡面有坑我們一會詳細說說。

第三步: 懸停時讀取路徑

hover.js檔案:

const vscode = require("vscode");
const fs = require("fs");

module.exports = vscode.languages.registerHoverProvider("*", {
  provideHover(document, position) {
    const i18nPath = vscode.workspace
      .getConfiguration()
      .get("vscodePluginI18n.i18nPath");
    const open = vscode.workspace
      .getConfiguration()
      .get("vscodePluginI18n.open");

    if (i18nPath && open) {
      const word = document.getText(document.getWordRangeAtPosition(position));

      if (word.split("_").length > 3) {
        const i18nPath = "/xxxxx/xxxx/langs";
        const jsonUrlCN = i18nPath + "/zh-CN.json";
        const jsonUrlEN = i18nPath + "/en.json";
        let jsonCN = JSON.parse(fs.readFileSync(jsonUrlCN));
        let jsonEN = JSON.parse(fs.readFileSync(jsonUrlEN));
        return new vscode.Hover(`i18n外掛前來助你渡劫:
- 中文: ${jsonCN[word.toLocaleLowerCase()]}
- 英文: ${jsonEN[word.toLocaleLowerCase()]}
      `);
      }
    }
  },
});

七: 何時判斷是否開啟外掛

     如果在extension.js檔案裡面引入了hover模組, 則就算我們判斷了使用者未開啟i18n翻譯流程也會走到hover模組, 但是如果我們在判斷使用者是否開啟i18n翻譯後進行引入hover模組就會造成, 如果使用者改變配置開始翻譯無法及時響應, 需要使用者重啟一下, 所以具體這裡如何設計需要大家酌情啦。

八: 提示彈框

     如果使用者開啟了i18n翻譯但是沒配置路徑, 那麼我們其實可以出現一個彈框提示使用者是否要來填寫這個翻譯字典的路徑, 類似下圖的效果:

image.png

extension.js檔案裡面使用showInformationMessage這個方法, 但是要增加兩個引數:

const vscode = require("vscode");

function activate(context) {
  const i18nPath = vscode.workspace
    .getConfiguration()
    .get("vscodePluginI18n.i18nPath");
  const open = vscode.workspace.getConfiguration().get("vscodePluginI18n.open");

  if (open && i18nPath) {
    vscode.window.showInformationMessage("i18n翻譯外掛已就位");
    const hover = require("./src/hover");
    context.subscriptions.push(hover);
  } else if (open && !i18nPath) {
    vscode.window
      .showInformationMessage("是否設定翻譯檔案路徑", "是", "否")
      .then((result) => {
           if (result === "是") {

           }
      });
  }
}

module.exports = {
  activate,
};

九: 選擇檔案

     當使用者點選'是'的時候我們需要藉助vscode提供的api進行檔案的選擇, 並且拿到檔案的返回值進行主動的設定操作:

vscode.window.showOpenDialog 方法, 他有四個主要的引數:

  1. canSelectFiles 是否可選檔案
  2. canSelectFolders 是否可選資料夾
  3. canSelectMany 是否可以多選
  4. openLabel 提示文案
  5. 返回值是陣列, 第一個元素的path屬性, 就是檔案的全域性路徑

image.png

最後再用update方法主動更新一下全域性的配置就可以啦。

vscode.window
      .showInformationMessage("是否設定翻譯檔案路徑", "是", "否")
      .then((result) => {
        if (result === "是") {
          vscode.window
            .showOpenDialog({
              canSelectFiles: false, // 是否可選檔案
              canSelectFolders: true, // 是否可選資料夾
              canSelectMany: false, // 是否可以選擇多個
              openLabel: "請選擇翻譯資料夾",
            })
            .then(function (res) {
              vscode.workspace
                .getConfiguration()
                .update("vscodePluginI18n.i18nPath", res[0].path, true);
            });
        }
      });
  }

end

     這次就是這樣, 希望與你一起進步。

相關文章