The principle of vscode multilingual implementation

Vscode displays English by default, how to make it display Chinese by default? After tracking its code for a period of time, here is a summary.

The simplest implementation of traditional front-end App multilingual can be managed by a set of responsive data flow management system to host multilingual copywriting. When switching languages, the interface will be re-rendered according to the copywriting through the change of data flow. However, due to the complexity of the VS Code architecture, it is necessary to have a multi-language solution compatible with the Electron rendering window (Chromium) and the Node.js process.

Different from the language switching function on the web page, vscode does not implement a responsive data flow management system. The language switching of vscode is realized by downloading the required language pack plug-in, and the default value of the development environment is English. Then go back to get the language text in the corresponding language pack. I downloaded the Chinese language pack plugin in advance and put it into the code.

General entrance

src/main.js
is triggered after the app's ready event

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');
	});
}

Then

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);
}

Let's look at nls.metadata.jsonthe file first. This file does not exist in the source code, but is dynamically generated after packaging.
General content of the file:

{
    
    
	"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",
			...
			]	
	...
	}
}			

Another important function is mentioned here localize,
the localize function leads to src/vs/nls.js, because the original code of the compiled code is no longer visible . The guess is to find the corresponding key through json and return the corresponding value.

'title': localize('windowConfigurationTitle', "Window"),
  • keys is the key value of the localize function
  • messages is the value of the localize function
  • bundles are all files

nls.metadata.json file format definition

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;

We merge them into one object and the effect comes out

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

Here I downloaded the Chinese language pack plug-in in advance and put it into the code, let's take a look at the format inside

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

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

Found that this is the structure of the language pack file.

Vscode analyzes all files containing multilingual calls at compile time and generates this nls.metadata.json file.

The order in which the localize function is called determines the order in which the content of nls.metadata.json is arranged

// 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"
        ]
    }
}

Normally, after we download the vscode Chinese package, it will not take effect immediately, but the window needs to be reloaded. At this time, the languagepacks.json file will be generated in the root directory of the code. I also put this in advance before packaging.
Its general content:

{
    
    
	"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",
			...
		},
	}
}			

Ok, let's go back to main.js

The locale is changed to zn-ch, let’s see the values ​​here 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

Let's go to the src/vs/base/node/languagePacks.js file, here I have rewritten two places

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));

Why rewrite languagePacks.js here? Because languagePacks.js used to be a relative path, multilingual files would not be found here, so it was changed to an absolute path

After the software is started here, Chinese can be displayed first

Guess you like

Origin blog.csdn.net/woyebuzhidao321/article/details/131495834