使用vscode-nls做VS Code插件的国际化 最近项目需要参考VS Code的插件国际化方案,网上的资料比较少,所以研究了一下,算是有点成果,记录一下。
VS Code插件的国际化,官方提供了两个工具 vscode-nls 和 vscode-nls-dev ,vscode-nls在源码中引入,对字符串进行翻译,vscode-nls-dev用于提取源码中需要翻译的字符串,生成指定格式的国际化文件。本文参考了官方提供的国际化示例,同样使用gulp自动化构建工具。
个人没有做过VS Code插件开发,所以以最简单的插件做示例,对普通Javascript、Typescript、webpack三种插件开发或打包方式做国际化,三者的区别主要在gulpfile.js的编写。
一、准备工作
准备个VS Code插件项目,安装vscode-nls、vscode-nls-dev、gulp三个包,后两个只在开发环境中使用。
二、步骤
- 源码中添加翻译代码 Typescript只是导入vscode-nls不同,其他一致。
const nls = require('vscode-nls');
const localize = nls.loadMessageBundle();
let text = localize('nlssample.hello', 'Hello!');
// 带有参数的翻译
text = localize('nlssample.time', 'Time: {0}!', Date.now());
或者使用config自定义配置:
const localize = nls.config({
locale:'zh-cn',
bundleFormat: nls.BundleFormat.standalone,
messageFormat: nls.MessageFormat.both
})();
国际化文件有三种存在方式
-
VS Code语言包形式,主要是微软自己的出的一些插件
-
所有文件的翻译信息合并到单一的bundle文件
-
所有文件的翻译信息独立存在,与源码保持同样目录结构
config的参数主要就是配置如何使用国际化文件的。其中bundleFormat决定是使用语言包还是插件自己的国际化文件,由于vscode-nls的限制,两者不能共存,messageFormat用于配置使用单文件还是多文件或者都允许,但优先单文件。
nls.config一般只需要在插件入口调用一次,该接口实际返回的也是loadMessageBundle。其他文件中需要调用loadMessageBundle,该方法加载js文件相对应的翻译信息并返回一个方法。vscode-nls-dev会在loadMessageBundle参数插入当前文件路径作为上下文。
- 编写gulpfile.js文件 Javascript 插件代码如下,其他类型文件参考个人的插件示例里的gulpfile.js:
const gulp = require('gulp');
const del = require('del');
const rename = require('gulp-rename');
const es = require('event-stream');
const nls = require('vscode-nls-dev');
// 支持的语言
const languages = [{ folderName: 'zh-cn', id: 'zh-cn' }];
const cleanout = function () {
return del(['out/**', 'package.nls.*.json']);
}
gulp.task('clean', cleanout);
// 源码
const sourcesNsl = function () {
var r = gulp.src(['./**/*.js', '!node_modules/**', '!gulpfile.js'])
.pipe(nls.rewriteLocalizeCalls())
.pipe(nls.createAdditionalLanguageFiles(languages, 'i18n', ''))
.pipe(nls.bundleMetaDataFiles('js-nls-sample', ''))
.pipe(nls.bundleLanguageFiles());
// 输出到out目录
return r.pipe(gulp.dest("out"));
};
// package.json
const packageNls = function () {
return gulp.src(['package.nls.json'], { allowEmpty: true })
.pipe(nls.createAdditionalLanguageFiles(languages, 'i18n'))
.pipe(gulp.dest('.'));
};
gulp.task('nls', gulp.series(cleanout, sourcesNsl, packageNls));
Typescript和Webpack参考 Git项目 。该文件有比较多的细节,如果项目有不一样的配置,可以自己修改看看每一步都做了什么。我简单说明一下单一国际化文件的生成过程,对应sourcesNsl定义的任务:
-
读取项目下相关的js文件
-
匹配localize的调用,获取消息id与默认的字符串,就是前两个参数,生成多个metadata文件
-
查找翻译好的字符串,与上一步的metadata文件生成翻译文件
-
合并所有metadata文件到单一文件
-
合并所有翻译文件到单一的国际化文件
- 翻译 上一步只是创建了任务用于生成单一国际化文件,执行任务生成的国际化文件仍旧是原始字符串,原因是第三步需要提前准备好与metadata文件目录结构一致的翻译文件。可以再建立一个任务用于提取生成。
// 提取需要翻译的字符串到i18n/base目录,方便翻译
const sourcesMsg = function () {
const suffix = '.i18n.json';
var r = gulp.src(['./**/*.js', '!node_modules/**', '!out/**', '!gulpfile.js'])
.pipe(nls.rewriteLocalizeCalls())
.pipe(nls.createKeyValuePairFile())
.pipe(es.through(function (file) {
// 仅处理.i18n.json
if (file.path.indexOf(suffix, file.path.length - suffix.length) !== -1) {
this.queue(file);
}
}))
.pipe(gulp.dest(`./i18n/base`));
return r;
};
// package.nls.json,结构一致,只需要拷贝一份
const packageMsg = function () {
var r = gulp.src(['package.nls.json'], { allowEmpty: true })
.pipe(rename({ basename: "package", suffix: ".i18n" }))
.pipe(gulp.dest(`./i18n/base`));
return r;
};
const messageTask = gulp.series(sourcesMsg, packageMsg);
gulp.task('message', messageTask);
与上一步一样,涉及到package.nls.json文件,这个文件是package.json里需要做的翻译,需要手动编写,gulp任务也只是拷贝了一份。 格式如下:
{
"jsnls.sample.description": "javascript extension nls example for VS Code"
}
- 运行任务 运行gulp message(或者自己配置tasks.json)提取翻译信息到指定目录。上面的任务默认输出到i18n/base目录,拷贝一份该目录,重命名为步骤2中支持的语言的folderName:
const languages = [{ folderName: 'zh-cn', id: 'zh-cn' }];
运行gulp nls,生成单一的翻译文件,同时会生成nls.metadata.json、nls.metadata.header.json、nls.bundle.json三个文件。
三、其他补充
- 普通Javascript插件
- 普通Javascript插件项目,gulp任务需要在js文件中插入上下文,重新输出到out目录,需要手动修改package.json的插件入口
- Typescript插件
-
个人对这类项目的Sourcemap概念不了解,gulp任务里涉及到gulp-sourcemaps相关的操作,开发者可以自己调整
-
如果gulp nls执行成功,生成了nls.bundle.zh-cn.json且内容为中文,但运行时还是原始字符串,可能是VS Code重新编译了项目,而没有重写输出的js文件。需要更改配置,可以参考官方的示例。
- Webpack插件
- Webpack项目是通过在webpack.config.js中配置module.rules实现重写js和提取翻译,所以gulp任务中没有 nls.rewriteLocalizeCalls(),增加以下配置:
{
loader: 'vscode-nls-dev/lib/webpack-loader',
options: {
base: path.join(__dirname, 'src')
}
}
- base为源码路径,拼接src是为了去掉翻译信息上下文的src 上面的配置会在输出目录生成多个metadata文件,生成单一的国际化文件需要借助这些文件:
const sourcesNsl = function () {
return gulp.src(['**/*.nls.metadata.json'], { base: "./out" })
.pipe(nls.createAdditionalLanguageFiles(languages, 'i18n', ''))
.pipe(nls.bundleMetaDataFiles('webpack-nls-sample', ''))
.pipe(nls.bundleLanguageFiles())
.pipe(gulp.dest(`./out`));
};
其中gulp.src第二个参数设置了base,是因为metadata文件内容不包含上下文信息,通过路径计算需要以out为相对路径 4. nls.bundle文件格式 实际生成的国际化文件并不是键值对格式:
{
"extension": [
"你好!",
"时间: {0}!"
],
"hello/message": [
"这是一个示例消息"
]
}
重写源码后,localize是通过索引定位的,metadata文件里可以看到固定的顺序。
- 多文件独立国际化 上面提到国际化文件有三种存在方式,除了语言包还有一种是多个翻译文件,比如对于extension.js文件,可以生成extension.nls.json、extension.nls.zh-cn.json文件,通过messageFormat配置支持多文件方式即可。
以普通Javascript插件为例,不需要执行bundleMetaDataFiles和bundleLanguageFiles:
const sourcesNsl = function () {
var r = gulp.src(['./**/*.js', '!node_modules/**', '!gulpfile.js'])
.pipe(nls.rewriteLocalizeCalls())
.pipe(nls.createAdditionalLanguageFiles(languages, 'i18n', ''));
// 输出到out目录
return r.pipe(gulp.dest("out"));
};
- VS Code语言包方式 这种方式不太可能,可以测试。 vscode-nls-dev导出的接口中没有用于生成语言包国际化文件格式的接口,上面说到nls.bundle格式不是键值对,语言包中的是键值对,可以自己尝试生成。
四、总结
就这些,又研究了一个没太大用的知识......
五、其他
-
国内开源:https://gitee.com/feiyangqingyun
-
国际开源:https://github.com/feiyangqingyun
-
项目大全:https://qtchina.blog.csdn.net/article/details/97565652
-
联系方式:微信 feiyangqingyun
-
官方店:https://shop114595942.taobao.com/