為 ModelArts JupyterLab 擼一個漢化插件
?如何持續(xù)輸出?是一直困擾著我的難題。想來想去,原來是因為沒有輸入,對,就是每天沒有學(xué)習(xí)新的知識;造成的后果也不言而喻--工作七年了經(jīng)驗值接近零!今天偶然看到一個小工具的源碼,打開了我的思路,因此,我想為 ModelArts JupyterLab 打造一款專屬 Chrome 插件!當(dāng)然此方法理論上適合任何定制化漢化(或多語言)插件,請看 Copy 攻城獅如何 Copy !
?
背景概要
什么是 ModelArts?
ModelArts 是華為云一站式 AI 開發(fā)平臺,涵蓋了 AI 應(yīng)用開發(fā)中數(shù)據(jù)準(zhǔn)備、訓(xùn)練、評估、部署全流程和端、邊、云全場景;小白開發(fā)者可以借助 ModelArts 輕輕松松從零到一開發(fā)屬于自己的 AI 應(yīng)用,像 Copy 攻城獅本大獅使用 ModelArts 不費吹灰之力就完成了“螞蟻牙黑”的視頻合成[1];大神工程師使用 ModelArts 能“煉丹好好煉丹煉好丹”,也不乏一些高端應(yīng)用,比如 @Tianyi_Li 實現(xiàn)的“自動駕駛[2]”。當(dāng)然,借力 ModelArts,AI 在各行各業(yè)實現(xiàn)了賦能,比如通過 ModelArts 自動學(xué)習(xí)能力構(gòu)建并完善聲音模型實現(xiàn)雨林的盜伐監(jiān)聽,實現(xiàn)了保護雨林的 AI 應(yīng)用;借助 ModelArts 的海量數(shù)據(jù)預(yù)處理及智能標(biāo)注、大規(guī)模分布式訓(xùn)練及深度學(xué)習(xí),實現(xiàn)優(yōu)質(zhì)的個性化內(nèi)容精準(zhǔn)觸達(dá)……如果您想初步了解 ModelArts,希望我的歷史博文發(fā)揮作用--云享 MindTalks 第十三期:ModelArts 讓 AI 應(yīng)用開發(fā)更簡單[3]。什么是 JupyterLab?
JupyterLab 是Project Jupyter[4]的下一代基于 Web 的用戶界面,用于 Jupyter Notebook、代碼和數(shù)據(jù)的基于 Web 的交互式開發(fā)環(huán)境,而Project Jupyter[5]的存在是為了開發(fā)跨多種編程語言的交互式計算的開源軟件,開放標(biāo)準(zhǔn)和服務(wù)。常常用在數(shù)據(jù)科學(xué)、科學(xué)計算和機器學(xué)習(xí)中的各種工作流程,因此也經(jīng)常被用作 AI 應(yīng)用開發(fā)及數(shù)據(jù)分析的輔助工具。而 ModelArts JupyterLab 是常用的開發(fā)環(huán)境,您可以用來學(xué)習(xí)機器學(xué)習(xí)中的數(shù)學(xué)[6](PS:本大獅計劃跟老齊前輩學(xué)習(xí)學(xué)習(xí)),可以玩玩爬蟲[7]或者數(shù)據(jù)可視化……不過對于“English is very pool”的我,面對 JupyterLab 全英文的操作界面,尤其是我開了 Google 翻譯插件之后,我徹底懵圈了!看法?跑?吉特?神翻譯啊!怎么辦?
機緣巧合
有時候靈感就來源于“跨界”。最近計劃實現(xiàn)一個野生動物保護的公益小程序,需要自己畫原型并設(shè)計 UI,了解到目前最流行最推薦的工具是 「Figma[8]」(是念費伽馬嗎?),當(dāng)然,同樣是全英文,不過國內(nèi)硬核玩家已經(jīng)編寫了漢化小插件--FigmaCN[9],這是中文 Figma 插件,由設(shè)計師人工翻譯校驗。通過下載查看 FigmaCN 的源碼,我了解到原來“開發(fā)”漢化的 Chrome 插件這么簡單,于是就有了漢化 ModelArts JupyterLab 的想法,剛好也查了資料,了解到目前 JupyterLab 3.0+已經(jīng)有中文插件可以使用,是由一位熱心老外貢獻(xiàn)的--jupyterlab-language-pack-zh-CN[10],不過似乎體驗還有待優(yōu)化。不過,ModelArts 上的 JupyterLab 暫時是 2.0 的版本,因此我萌生了自己制作漢化插件的想法,希望通過漢化之后的 JupyterLab 學(xué)習(xí)更多的技巧。Just Do IT!
文檔先行
套用一句老話:“道路千萬條,文檔第一條”。開發(fā)不看文檔,那是真的牛;開發(fā)不寫文檔,也是真的牛!當(dāng)我想開發(fā) Chrome 插件的時候,第一條路可能最合適的就是看官方的開發(fā)文檔了--擴展程序開發(fā)文檔[11]。據(jù)我多年的 HW 開發(fā)經(jīng)驗,閱讀文檔時,您可能就會看到一個“Hello World”;當(dāng)然,Chrome 插件開發(fā)也不例外。開發(fā) Chrome 插件您可能需要掌握 WEB 開發(fā)的三大劍--HTML、CSS、JavaScript,根據(jù)文檔,并參考開源庫GoogleChrome/chrome-extensions-samples[12],開始我們的 Chrome 插件“Hello World 之旅"(順便給官方文檔提了一個**PR[13]**)。
目前 Chrome 插件的主程序已經(jīng)更新到 V3 版本了,使用 MV3 的擴展將在安全性,隱私性和性能方面得到增強;此外還可以使用 MV3 中采用的更多現(xiàn)代開放式 Web 技術(shù),例如 Service Workers 和 Promise。因此,我們基于第三代 Chrome 插件開發(fā)。
先新建一個目錄,取名為Hello World,包含三個文件--background.js、hello.html、manifest.json,從后綴名可以看出一些端倪,.json是配置文件也是插件的描述文件,.js是邏輯代碼文件,.html是頁面文件;這是一個簡單的插件雛形。具體代碼如下:
簡單的代碼,實現(xiàn)的功能就是安裝完插件就打開 hello.html ,要想預(yù)覽效果需要將本地瀏覽器的插件應(yīng)用開啟開發(fā)者模式。官方文檔給出的步驟如下:
① chrome://extensions 在瀏覽器中導(dǎo)航到。您還可以通過點擊多功能框右上角的 Chrome 菜單,將鼠標(biāo)懸停在“更多工具”上,然后選擇擴展程序來訪問此頁面。
② 選中“開發(fā)人員模式”旁邊的框。
③ 單擊“加載解壓縮的擴展名”,然后為“ Hello 擴展名”擴展名選擇目錄。

編寫插件
我們先來看看 MV3 規(guī)定的 manifest.json 改如何編寫,打開文檔mv3/manifest[14],您就能看到一份 json,必填的字段:
"manifest_version": 3, // 使用MV3必填 3
"name": "My Extension", // 插件名稱
"version": "versionString", // 插件版本
其他的參數(shù)按需填寫,其中我比較關(guān)心的是圖標(biāo)及其他文件加載,了解到 icons 字段可以定義圖標(biāo),content_scripts可以注入內(nèi)容腳本,background可注入后臺腳本,值得注意的是 MV3 相比 MV2,不再無法加載 JavaScript 或 Wasm 文件之類的遠(yuǎn)程代碼,也就是說我們所有的邏輯必須在本地完成(暫時這么理解)。因此,我的配置文件如下:
{
"name": "StartAI",
"version": "0.1",
"manifest_version": 3,
"short_name": "StartAI",
"description": "ModelArts 工具集,JupyterLab 漢化插件,學(xué)AI就用 ModelArts,學(xué)AI就上 huaweicloud.ai。",
"homepage_url": "http://huaweicloud.ai",
"icons": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["*://notebook01-modelarts-cnnorth4.huaweicloud.com/*"],
"js": ["js/content.js"],
"run_at": "document_end",
"all_frames": true
}
]
}
重點介紹一下content_scripts,這里配置的是我們的核心邏輯,這段配置的大概含義是:當(dāng)瀏覽器訪問的 url 匹配到 matches 字段中的網(wǎng)站時,等待頁面加載完成,執(zhí)行我們的核心邏輯。通過上面的配置文件,也大概猜的出我們這個插件的目錄結(jié)構(gòu):
hello-world
├─ images
│ ├─ icon128.png
│ ├─ icon16.png
│ ├─ icon24.png
│ ├─ icon32.png
│ └─ icon48.png
├─ js
│ └─ content.js
├─ background.js
└─ manifest.json
background.js中寫的是安裝插件成功時打開一個新的 tab,顯示我們想要顯示的內(nèi)容,如引導(dǎo)頁:
chrome.runtime.onInstalled.addListener(async () => {
// 此處可打開本地頁面
// let url = chrome.runtime.getURL("hello.html");
// let tab = await chrome.tabs.create({ url });
// console.log(`Created tab ${tab.id}`);
chrome.tabs.create({
url: "http://huaweicloud.ai",
});
});

點擊插件中心 Update 按鈕之后,就會打開我們設(shè)置的網(wǎng)頁,當(dāng)匹配到我們預(yù)設(shè)的網(wǎng)站時,插件就被激活,明顯會看到圖標(biāo)被點亮。看了半天,似乎發(fā)現(xiàn)原來 Chrome 插件開發(fā)這么簡單!
接著就是核心邏輯--漢化,思路是通過MutationObserver[15]監(jiān)聽 DOM 何時更改,DOM 的任何變動,比如節(jié)點的增減、屬性的變動、文本內(nèi)容的變動,這個 API 都可以捕獲到,因此當(dāng)我們在頁面上進(jìn)行操作時,都能實時動態(tài)翻譯我們匹配好的內(nèi)容。代碼實現(xiàn)如下:
// contant.js
// 定義臨時空數(shù)組
let allData = [];
// 中英文匹配
const jsonData = {
// "": {
// "domain": "jupyterlab",
// "language": "zh-CN",
// "plural_forms": "nplurals=1; plural=0;",
// "version": "3.0.0b4"
// },
"\nConnected: %1": [
"\n\u5df2\u8fde\u63a5: %1"
],
"\nCreated: %1": [
"\n\u521b\u5efa\u65f6\u95f4\uff1a%1"
],
// …… 此處省略 2400 行,詳見 https://github.com/jupyterlab/language-packs
}
// 處理數(shù)據(jù),形如:[["File", "文件"],["Edit","編輯"]]
allData = Object.keys(jsonData).map(item=>([item,jsonData[item][0]]))
// 引入觀察器 MutationObserver
let MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
// 觀察器的配置(需要觀察什么變動)
let MutationObserverConfig = {
childList: true,
subtree: true,
attributeFilter: ["data-label"],
characterData: true,
};
// 創(chuàng)建一個觀察器實例并傳入回調(diào)函數(shù)
let observer = new MutationObserver(function (mutations) {
let treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ALL,
{
acceptNode: function (node) {
if (node.nodeType === 3 || node.hasAttribute("data-label")) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_SKIP;
}
},
},
false
);
// 映射中文
let dataMap = new Map();
for (i in jsonData) {
if (i && !dataMap.has(i)) {
dataMap.set(i, jsonData[i][0]);
}
}
allData.forEach(([key, val]) => {
if (key && !dataMap.has(key)) {
dataMap.set(key, val);
}
});
let currentNode = treeWalker.currentNode;
// 替換
while (currentNode) {
if (currentNode.nodeType === 3) {
let key1 = currentNode.textContent;
if (dataMap.has(key1)) currentNode.textContent = dataMap.get(key1);
} else {
let key2 = currentNode.getAttribute("data-label");
if (dataMap.has(key2))
currentNode.setAttribute("data-label", dataMap.get(key2));
}
currentNode = treeWalker.nextNode();
}
});
// 開始觀察目標(biāo)節(jié)點
observer.observe(document.body, MutationObserverConfig);
最終的效果還算滿意,基本能夠使用全部已有的翻譯數(shù)據(jù),當(dāng)然,源數(shù)據(jù)中存在未翻譯項,比如["Notebook":["NoteBook"]],這樣的數(shù)據(jù)會導(dǎo)致插件異常,從而使頁面卡死,具體什么原因還沒排查到,大概是因為死循環(huán)了,無意中踩過了這個坑。

發(fā)布
最后的發(fā)布,我不太愿意談太多,主要不想給 Google 打廣告,因為發(fā)布需要象征性的收取 5 刀,因此您可能需要全球信用卡。具體步驟如下:上傳應(yīng)用程序的過程很簡單:
將應(yīng)用程序的根目錄(包含 manifest.json 文件的文件夾)壓縮為.zip 文件。 交錢開通資格。 去到控制臺單擊添加新項目。 接受條款和服務(wù)協(xié)議。 使用“選擇文件”對話框在系統(tǒng)中查找.zip 文件并選擇。 其他信息維護及測試。

截止發(fā)稿,Copy 攻城獅翻箱倒柜還沒找著信用卡,就先到這,待 Notebook 一鍵分享功能開發(fā)完畢之后再注冊發(fā)布。
小結(jié)
總得來說,收獲不少。一是解決了當(dāng)前的需求,二是產(chǎn)出了一篇“水文”,三是貢獻(xiàn)了一個 PR(Markdown 特供)。
源碼地址:hu-qi/StartAI[16] (https://github.com/hu-qi/StartAI[17])
Reference
“螞蟻牙黑”的視頻合成: https://bbs.huaweicloud.com/blogs/245794
[2]自動駕駛: https://bbs.huaweicloud.com/blogs/226039
[3]云享 MindTalks 第十三期:ModelArts 讓 AI 應(yīng)用開發(fā)更簡單: https://bbs.huaweicloud.com/blogs/246212
[4]Project Jupyter: https://jupyter.org/
[5]Project Jupyter: https://jupyter.org/
[6]機器學(xué)習(xí)中的數(shù)學(xué): http://www.itdiffer.com/
[7]玩玩爬蟲: https://bbs.huaweicloud.com/blogs/174634
[8]Figma: https://figma.com/
[9]FigmaCN: https://cn.figma.cool/
[10]jupyterlab-language-pack-zh-CN: https://github.com/jupyterlab/language-packs
[11]擴展程序開發(fā)文檔: https://developer.chrome.com/docs/extensions/
[12]GoogleChrome/chrome-extensions-samples: https://github.com/GoogleChrome/chrome-extensions-samples
[13]PR: https://github.com/GoogleChrome/developer.chrome.com/pull/521
[14]mv3/manifest: https://developer.chrome.com/docs/extensions/mv3/manifest/
[15]MutationObserver: https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
[16]hu-qi/StartAI: https://github.com/hu-qi/StartAI
[17]https://github.com/hu-qi/StartAI: https://github.com/hu-qi/StartAI

