vscode多语言实现原理

vscode是默认显示英文,如何使其默认显示中文?跟踪了一段时间其代码,这里做下总结。

传统前端 App 多语言最简单的实现可以由一套响应式数据流管理系统来托管多语言文案,切换语言时通过数据流的变化使得界面根据文案重新渲染。但由于 VS Code 架构的复杂性,需要有一套能兼容 Electron 渲染窗口(Chromium)及 Node.js 进程的多语言方案。

和网页端语言切换功能有所不同,vscode并没有去实现响应式数据流管理系统,vscode的语言切换都是通过下载需要的语言包插件实现的,而且开发环境都是取默认值英文,到了生产才回去取对应语言包中的语言文字。事先我下载好了中文语言包插件并放入到了代码里面。

总入口

src/main.js
在app的ready事件后触发

async function onReady() {
    
    
	...
		const [, nlsConfig] = await Promise.all([mkdirpIgnoreError(codeCachePath), resolveNlsConfiguration()]);
		startup(codeCachePath, nlsConfig);
...
}

function startup(codeCachePath, nlsConfig) {
    
    
	...
	require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
    
    
		perf.mark('code/didLoadMainBundle');
	});
}

然后

const metaDataFile = path.join(__dirname, 'nls.metadata.json');
const locale = getUserDefinedLocale(argvConfig);
if (locale) {
    
    // zn-ch
	const {
    
     getNLSConfiguration } = require('./vs/base/node/languagePacks');
	nlsConfigurationPromise = getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale);
}

我们先看下nls.metadata.json文件,在源码里面这个文件并不存在,是打包之后动态生成的。
文件大致内容:

{
    
    
	"keys": {
    
    
		"vs/platform/terminal/node/ptyHostMain": [
			"ptyHost"
		],
		...
	},
	"messages": {
    
    
		"vs/platform/terminal/node/ptyHostMain": [
			"Pty Host"
		],
		"vs/code/electron-main/main": [
			"Main",
			"Another instance of {0} is already running as administrator.",
			"Please close the other instance and try again.",
			"Another instance of {0} is running but not responding",
			"Please close all other instances and try again.",
			"Warning: The --status argument can only be used if {0} is already running. Please run it again after {0} has started.",
			"Unable to write program user data.",
			"{0}\n\nPlease make sure the following directories are writeable:\n\n{1}",
			"&&Close"
		],
		"vs/code/electron-sandbox/processExplorer/processExplorerMain": [
			"Process Name",
			"CPU (%)",
			"PID",
			"Memory (MB)",
			"Kill Process",
			"Force Kill Process",
			"Copy",
			"Copy All",
			"Debug"
		],	
	...
	},
	"bundles": {
    
    
		"vs/workbench/workbench.desktop.main": [
			"vs/base/browser/ui/actionbar/actionViewItems",
			"vs/base/browser/ui/button/button",
			"vs/base/browser/ui/dialog/dialog",
			...
			]	
	...
	}
}			

这里再提到一个重要函数localize,
localize函数引至 src/vs/nls.js ,是编译后代码 原本代码已经看不出来了。猜测是通过json找到对应的key返回对应的值。

'title': localize('windowConfigurationTitle', "Window"),
  • keys 里是localize函数的 key 值
  • messages 则是localize函数的 value值
  • bundles 是所有文件

nls.metadata.json文件格式定义

build/azure-pipelines/upload-nlsmetadata.ts

interface NlsMetadata {
    
    
	keys: {
    
     [module: string]: string };
	messages: {
    
     [module: string]: string };
	bundles: {
    
     [bundle: string]: string[] };
}

const json: NlsMetadata = {
    
    
	keys: {
    
    },
	messages: {
    
    },
	bundles: {
    
    
		main: []
	}
};
for (const module of modules) {
    
    
	json.messages[module] = parsedJson[module].messages;
	json.keys[module] = parsedJson[module].keys;
	json.bundles.main.push(module);
}
parsedJson = json;

我们把它们合并成一个对象 效果就出来了

{
    
    
	'vs/workbench/electron-sandbox/desktop.contribution': {
    
    
		"newTab": "New Window Tab"
	}
	....
}

这里我提前下载好了中文语言包插件放入到了代码里面,我们看看里面格式

extensions/chinese-language-pack/translations/main.i18n.json

{
    
    
"vs/workbench/electron-sandbox/desktop.contribution": {
    
    
	...
	"newTab": "新建窗口标签页",
	...
}
}

发现这就是语言包文件的结构。

vscode在编译时分析了所有包含多语言调用的文件并生成了这个 nls.metadata.json 文件。

localize函数的调用顺序决定nls.metadata.json 内容排列顺序

// vs/path/to/code.ts
import * as nls from 'vs/nls';

const value = nls.localize('key1', 'Message1');
const value2 = nls.localize('key2', 'Message2');

// 在生成的 nls.matada.json 中应该是

{
    
    
 "keys": {
    
    
 "vs/path/to/code": [
 "key1",
 "key2"
        ]
    },
 "messages": {
    
    
 "vs/path/to/code": [
 "Message1",
 "Message2"
        ]
    }
}

正常我们下载vscode中文包后不会立即生效,而是要窗口重新reload,此时会在代码根目录生成 languagepacks.json文件,这个我也在打包前提前放进来了。
其大致内容:

{
    
    
	"zh-cn": {
    
    
		"hash": "1a7316c624e1dd66d26cf581ee0fb4ca",
		"extensions": [
			{
    
    
				"extensionIdentifier": {
    
    
					"id": "ms-ceintl.chinese-language-pack",
					"uuid": "e4ee7751-6514-4731-9cdb-7580ffa9e70b"
				},
				"version": "1.66.3"
			}
		],
		"translations": {
    
    
			"vscode": "extensions/chinese-language-pack/translations/main.i18n.json",
			...
		},
	}
}			

好了 我们重新回到main.js

locale 修改为 zn-ch, 其值有哪些 我们看这里 https://source.chromium.org/chromium/chromium/src/+/main:ui/base/l10n/l10n_util.cc

...
"uz",        // Uzbek
"vi",        // Vietnamese
"wa",        // Walloon
"wo",        // Wolof
"xh",        // Xhosa
"yi",        // Yiddish
"yo",        // Yoruba
"zh",        // Chinese
"zh-CN",     // Chinese (China)
"zh-HK",     // Chinese (Hong Kong)
"zh-TW",     // Chinese (Taiwan)
"zu",        // Zulu

我们到 src/vs/base/node/languagePacks.js文件里面,这里我对两个地方进行了改写

function getLanguagePackConfigurations(userDataPath) {
    
    // languagepacks.json复制到自己安装包,在自己安装包里面加载
	/**
	 * @author lichangwei
	 * @todo 多语言处理
	 * @date 2022-10-27
	 */
	// const configFile = path.join(userDataPath, 'languagepacks.json');
	/**
	 * mac : /Users/liuchongyang/site/vscode/.build/electron/律动.app/Contents/MacOS/Electron
	 * wins: D:\site\xinchengkeji\vscode-new\.build\electron\律动.exe
	 */
	// console.log(11111, app.getPath('exe'))
	const isWindows = (process.platform === 'win32');
	const isDarwin = (process.platform === 'darwin');
	let appPath = '';
	if (isDarwin) {
    
    
		appPath = path.dirname(path.dirname(app.getPath('exe')));
	} else {
    
    
		appPath = path.dirname(app.getPath('exe'));
	}
	const configFile = path.join(appPath, 'Resources', 'app', 'languagepacks.json');
	try {
    
    
		return nodeRequire(configFile); // require.__$__nodeRequire
	} catch (err) {
    
    
		// Do nothing. If we can't read the file we have no
		// language pack config.
	}
	return undefined;
}
	const configs = getLanguagePackConfigurations(userDataPath); // 加载到了languagePacks.js内容
	if (!configs) {
    
    
		return defaultResult(initialLocale);
	}
	locale = resolveLanguagePackLocale(configs, locale); // zh-cn
	if (!locale) {
    
    
		return defaultResult(initialLocale);
	}
	/**
	 * @author lichangwei
	 * @todo 多语言处理
	 * @date 2022-10-27
	 */
	const packConfig = configs[locale];
	const isDarwin = (process.platform === 'darwin');
	let appContentPath = '';
	if (isDarwin) {
    
    
		appContentPath = path.dirname(path.dirname(app.getPath('exe')));
	} else {
    
    
		appContentPath = path.dirname(app.getPath('exe'));
	}
	const appPath = path.join(appContentPath, 'Resources', 'app');
	for (let item in packConfig['translations']) {
    
    
		const itemPath = packConfig['translations'][item];
		const joinPath = path.join(appPath, itemPath);
		if (!itemPath.includes(appPath)) {
    
    
			packConfig['translations'][item] = joinPath;
		}
	}
	const languagepacksPath = path.join(appContentPath, 'Resources', 'app', 'languagepacks.json');
	// 这里对languagePacks.js里面的文件路径进行了重写,相对路径改成了绝对路径
	writeFile(languagepacksPath, JSON.stringify(configs));

这里为什么要对languagePacks.js重写呢?因为languagePacks.js里面原来是相对路径,这里会找不到多语言文件,所以改成了绝对路径

到这里软件启动后就可以优先显示中文了

猜你喜欢

转载自blog.csdn.net/woyebuzhidao321/article/details/131495834