这是我参与「第四届青训营 」笔记创作活动的第4天
前言
vite-plugin-monaco-editor-nls 这款插件在上周最近一次成功适配的monaco-editor版本还是 0.24.0
由于我的项目也需要用到monaco-editor,虽然默认的英文对开发人员来说没啥关系,
但是面向用户的国内产品,整个双语融合属实不太合理
于是开启一段迭代之旅
monaco-editor获取语言包的原理
大概浏览了一遍本插件之后,又去看了下隔壁家三个插件的实现
monaco-editor-nls、monaco-editor-esm-webpack-plugin、monaco-editor-esm-webpack-plugin
整理了一下思路
就拿 src/vs/base/browser/ui/inputbox/inputBox.ts 举例子
if (this.message.type === MessageType.ERROR) {
alertText = nls.localize('alertErrorMessage', "Error: {0}", this.message.content);
}
其中 nls.localize 就是在获取语言包内容并且格式化字符串
/**
* Localize a message.
*
* `message` can contain `{n}` notation where it is replaced by the nth value in `...args`
* For example, `localize('sayHello', 'hello {0}', name)`
*/
export function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string;
对应到语言包中的逻辑是这样
- 先获取当前文件相对路径,
vs/base/browser/ui/inputbox/inputBox - 第一个参数为description键值,
alertErrorMessage - 第二个参数为默认值
- 后续参数为需要格式化的变量
比如现在这一条对应到中文语言包中就是
这里只是 nls.localize 函数的一个重载,他还有另外一个重载
/**
* Localize a message.
*
* `message` can contain `{n}` notation where it is replaced by the nth value in `...args`
* For example, `localize({ key: 'sayHello', comment: ['Welcomes user'] }, 'hello {0}', name)`
*/
export function localize(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string;
这里的一个调用🌰是 src/vs/base/browser/ui/actionbar/actionViewItems.ts
if (this.options.keybinding) {
title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding);
}
这里对应寻找语言包的逻辑是
- 先获取当前文件相对路径,
vs/base/browser/ui/actionbar/actionViewItems - info.key为description的键值,titleLabel
- 第二个参数和后续参数都是一样的作用
实际上,如果不使用语言包,那么 nls.localize 的第一个参数是没有任何作用的。
插件原理简要分析
通过monaco-editor获取语言包的原理可以知道,
每次格式化语句的时候的必经之路是 nls.localize
所以只要在这个函数拦截下来,然后把我们需要的语言包加入在代码里面,
接着重写 localize 函数,实现我们自己的解析逻辑即可
问题定位 & 修复
前置小提示:
vite在开发dev阶段是使用esbuild进行依赖预构建
在prod打包阶段是使用rollup进行打包构建
构建打包插件,开始项目npm run dev。发现没有汉化成功
我的第一反应是想全局搜索一下语言包就没有加载进来
结果显示并没有加载进来,说明问题出现在依赖预构建阶段
在esbuild插件中打印一下修改的文件,看看我们的修改有没有命中目标文件
vite的esbuild插件是配置在 optimizeDeps.esbuildOptions.plugins 中的
所以我们找到 esbuildPluginMonacoEditorNls 这个函数,在每个load中打印一下命中文件的路径
重新运行一下 vite --force 强制重新预构建依赖
从结果来看全部都是清一色的222,说明上面替换nls.js没有成功
但问题是预构建肯定会加载nls.js这个毋庸置疑,为啥命中呐。
注意到windows的路径是反斜杠,但是过滤的正则只匹配的斜杠。
所以这里第一个bug,修改一下正则就好了
再次运行,可见已经按照预期命中了。 bug1
不过又有一个bug
搞不清是啥情况,于是追踪一下调用栈,找一找问题源头
拿到这个id editor.action.goToImplementation 去源码里面搜一下这个title字段是干嘛的
不过想想也能知道,我们只是修改了nls.js这个文件的代码,应该和里面的调用有关
问题就出在这里
title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations")
这一句localize的调用返回是undefined,但又怎么可能是undefined不是有设置默认值的吗
这里其实稍微看一下就能明白,用上面的参数去调用下面的函数就能得到undefined的结果
说明我们localize函数的修改没有生效,原本3个参数的函数还是没有变。
修改之后的localize的函数应该是4个参数
这里注意到上面的正则 const re = /(?:monaco-editor\/esm\/)(.+)(?=\.js)/;
又是只匹配了斜杠,并没有匹配反斜杠
改成 const re = /(?:monaco-editor[\/\\]esm[\/\\])(.+)(?=\.js)/; 再打包试试。 bug2
这下vite老哥暴躁了,打印一坨bug
把反斜杠改成斜杠再试试 bug3
这下可以出来了,不过汉化很不完整。这里只需要拿到最新的vscode-loc就行了
适配monaco-editor 0.34.0版本
更新最新版本的monaco之后,页面直接就打不开了。追踪一下调用栈看看是啥情况
这里看到他说 nls.getConfiguredDefaultLocale 这个是 void 0 应该是 nls.js 里面缺少了这个函数
我们去看下最新版本的nls源码是不是更新了什么东西
确实是的最新版的 src/vs/nls.ts 有两百多行
把插件里面的替换代码更新一下,这里要更新的是打包后的monaco代码example/node_modules/monaco-editor/esm/vs/nls.js,而不是vscdoe的源码
替换完成之后就成功进来了
自定义语言包内容
由于插件自带的语言包太老了,所以得更新一下语言包。但是各个版本的语言包又不一样。
所以这里我想做成和 monaco-editor-nls 一样的,能够使用自定义语言包
这里我们加一个可选参数 localData ,优先使用该参数即可
让我们看下效果。已经是完全汉化了
打包修复
上面提到的都是在预构建阶段。打包时候用到的rollup。
同样的插件也会切换到rollup的插件,也就是 plugins 字段中的插件
所以在打包时候也要和上面一样做同样的修复
- 正则替换
- 自定义语言包优先
- 替换代码修复
完成上面重复的一次修复之后就可以正常打包了
具体的效果可以看github page :Vite App (james-curtis.github.io)