Files
lc_frontend/build/monacoEditor/nls/index.ts
2025-10-17 10:31:13 +08:00

393 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'node:fs'
import path from 'node:path'
import type { Plugin } from 'vite'
import MagicString from 'magic-string'
export enum Languages {
bg = 'bg',
cs = 'cs',
de = 'de',
en_gb = 'en-gb',
es = 'es',
fr = 'fr',
hu = 'hu',
id = 'id',
it = 'it',
ja = 'ja',
ko = 'ko',
nl = 'nl',
pl = 'pl',
ps = 'ps',
pt_br = 'pt-br',
ru = 'ru',
tr = 'tr',
uk = 'uk',
zh_hans = 'zh-hans',
zh_hant = 'zh-hant',
}
export interface Options {
locale: Languages;
localeData?: Record<string, any>
}
/**
* 在vite中dev模式下会使用esbuild对node_modules进行预编译导致找不到映射表中的filepath
* 需要在预编译之前进行替换
* @param options 替换语言包
* @returns
*/
export function esbuildPluginMonacoEditorNls(options: Options): any {
options = Object.assign({ locale: Languages.en_gb }, options)
const CURRENT_LOCALE_DATA = getLocalizeMapping(options.locale, options.localeData)
return {
name: 'esbuild-plugin-monaco-editor-nls',
setup(build) {
build.onLoad({ filter: /esm[/\\]vs[/\\]nls\.js/ }, async () => {
return {
contents: getLocalizeCode(CURRENT_LOCALE_DATA),
loader: 'js',
}
})
build.onLoad(
{ filter: /monaco-editor[/\\]esm[/\\]vs.+\.js/ },
async args => {
return {
contents: transformLocalizeFuncCode(
args.path,
CURRENT_LOCALE_DATA,
),
loader: 'js',
}
},
)
},
}
}
/**
* 使用了monaco-editor-nls的语言映射包把原始localize(data, message)的方法替换成了localize(path, data, defaultMessage)
* vite build 模式下使用rollup处理
* @param options 替换语言包
* @returns
*/
export default function (options: Options): Plugin {
options = Object.assign({ locale: Languages.en_gb }, options)
const CURRENT_LOCALE_DATA = getLocalizeMapping(options.locale, options.localeData)
return {
name: 'rollup-plugin-monaco-editor-nls',
enforce: 'pre',
load(filepath) {
if (/esm[/\\]vs[/\\]nls\.js/.test(filepath)) {
return getLocalizeCode(CURRENT_LOCALE_DATA)
}
},
transform(code, filepath) {
if (
/monaco-editor[/\\]esm[/\\]vs.+\.js/.test(filepath)
&& !/esm[/\\]vs[/\\].*nls\.js/.test(filepath)
) {
const re = /monaco-editor[/\\]esm[/\\](.+)(?=\.js)/
if (re.exec(filepath) && code.includes('localize(')) {
let path = RegExp.$1
path = path.replaceAll('\\', '/')
code = code.replace(/localize\(/g, `localize('${path}', `)
return {
code: code,
/** 使用magic-string 生成 source map */
map: new MagicString(code).generateMap({
includeContent: true,
hires: true,
source: filepath,
}),
}
}
}
},
}
}
/**
* 替换调用方法接口参数,替换成相应语言包语言
* @param filepath 路径
* @param CURRENT_LOCALE_DATA 替换规则
* @returns
*/
function transformLocalizeFuncCode(
filepath: string,
_CURRENT_LOCALE_DATA: string,
) {
let code = fs.readFileSync(filepath, 'utf8')
const re = /monaco-editor[/\\]esm[/\\](.+)(?=\.js)/
if (re.exec(filepath)) {
let path = RegExp.$1
path = path.replaceAll('\\', '/')
code = code.replace(/localize\(/g, `localize('${path}', `)
}
return code
}
/**
* 获取语言包
* @param locale 语言
* @param localeData
* @returns
*/
function getLocalizeMapping(locale: Languages, localeData: Record<string, any> | undefined = undefined) {
if (localeData) return JSON.stringify(localeData)
const locale_data_path = path.join(__dirname, `./locale/${locale}.json`)
return fs.readFileSync(locale_data_path) as unknown as string
}
/**
* 替换代码
* @param CURRENT_LOCALE_DATA 语言包
* @returns
*/
function getLocalizeCode(CURRENT_LOCALE_DATA: string) {
return `
/* ---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
let isPseudo = typeof document !== 'undefined' && document.location && document.location.hash.indexOf('pseudo=true') >= 0
const DEFAULT_TAG = 'i-default'
function _format(message, args) {
let result
if (args.length === 0) {
result = message
} else {
result = message.replace(/{(\\d+)}/g, (match, rest) => {
const index = rest[0]
const arg = args[index]
let result = match
if (typeof arg === 'string') {
result = arg
} else if (typeof arg === 'number' || typeof arg === 'boolean' || arg === void 0 || arg === null) {
result = String(arg)
}
return result
})
}
if (isPseudo) {
// FF3B and FF3D is the Unicode zenkaku representation for [ and ]
result = \`\uFF3B\${result.replace(/[aeiou]/g, '$&$&')}\uFF3D\`
}
return result
}
function findLanguageForModule(config, name) {
let result = config[name]
if (result) {
return result
}
result = config['*']
if (result) {
return result
}
return null
}
function endWithSlash(path) {
if (path.charAt(path.length - 1) === '/') {
return path
}
return \`\${path}/\`
}
async function getMessagesFromTranslationsService(translationServiceUrl, language, name) {
const url = \`\${endWithSlash(translationServiceUrl) + endWithSlash(language)}vscode/\${endWithSlash(name)}\`
const res = await fetch(url)
if (res.ok) {
const messages = await res.json()
return messages
}
throw new Error(\`\${res.status} - \${res.statusText}\`)
}
function createScopedLocalize(scope) {
return function(idx, defaultValue) {
const restArgs = Array.prototype.slice.call(arguments, 2)
return _format(scope[idx], restArgs)
}
}
function createScopedLocalize2(scope) {
return (idx, defaultValue, ...args) => ({
value: _format(scope[idx], args),
original: _format(defaultValue, args),
})
}
// export function localize(data, message, ...args) {
// return _format(message, args);
// }
// ------------------------invoke----------------------------------------
export function localize(path, data, defaultMessage, ...args) {
var key = typeof data === 'object' ? data.key : data;
var data = ${CURRENT_LOCALE_DATA} || {};
var message = (data[path] || data?.contents?.[path] || {})[key];
if (!message) {
message = defaultMessage;
}
return _format(message, args);
}
// ------------------------invoke----------------------------------------
/**
* @skipMangle
*/
export function localize2(data, message, ...args) {
const original = _format(message, args)
return {
value: original,
original,
}
}
/**
* @skipMangle
*/
export function getConfiguredDefaultLocale(_) {
// This returns undefined because this implementation isn't used and is overwritten by the loader
// when loaded.
return undefined
}
/**
* @skipMangle
*/
export function setPseudoTranslation(value) {
isPseudo = value
}
/**
* Invoked in a built product at run-time
* @skipMangle
*/
export function create(key, data) {
var _a
return {
localize: createScopedLocalize(data[key]),
localize2: createScopedLocalize2(data[key]),
getConfiguredDefaultLocale: (_a = data.getConfiguredDefaultLocale) !== null && _a !== void 0 ? _a : _ => undefined,
}
}
/**
* Invoked by the loader at run-time
* @skipMangle
*/
export function load(name, req, load, config) {
var _a
const pluginConfig = (_a = config['vs/nls']) !== null && _a !== void 0 ? _a : {}
if (!name || name.length === 0) {
// TODO: We need to give back the mangled names here
return load({
localize: localize,
localize2: localize2,
getConfiguredDefaultLocale: () => {
var _a
return (_a = pluginConfig.availableLanguages) === null || _a === void 0 ? void 0 : _a['*']
},
})
}
const language = pluginConfig.availableLanguages ? findLanguageForModule(pluginConfig.availableLanguages, name) : null
const useDefaultLanguage = language === null || language === DEFAULT_TAG
let suffix = '.nls'
if (!useDefaultLanguage) {
suffix = \`\${suffix}.\${language}\`
}
const messagesLoaded = messages => {
if (Array.isArray(messages)) {
messages.localize = createScopedLocalize(messages)
messages.localize2 = createScopedLocalize2(messages)
} else {
messages.localize = createScopedLocalize(messages[name])
messages.localize2 = createScopedLocalize2(messages[name])
}
messages.getConfiguredDefaultLocale = () => {
var _a
return (_a = pluginConfig.availableLanguages) === null || _a === void 0 ? void 0 : _a['*']
}
load(messages)
}
if (typeof pluginConfig.loadBundle === 'function') {
pluginConfig.loadBundle(name, language, (err, messages) => {
// We have an error. Load the English default strings to not fail
if (err) {
req([\`\${name}.nls\`], messagesLoaded)
} else {
messagesLoaded(messages)
}
})
} else if (pluginConfig.translationServiceUrl && !useDefaultLanguage) {
(async() => {
var _a
try {
const messages = await getMessagesFromTranslationsService(pluginConfig.translationServiceUrl, language, name)
return messagesLoaded(messages)
} catch (err) {
// Language is already as generic as it gets, so require default messages
if (!language.includes('-')) {
console.error(err)
return req([\`\${name}.nls\`], messagesLoaded)
}
try {
// Since there is a dash, the language configured is a specific sub-language of the same generic language.
// Since we were unable to load the specific language, try to load the generic language. Ex. we failed to find a
// Swiss German (de-CH), so try to load the generic German (de) messages instead.
const genericLanguage = language.split('-')[0]
const messages = await getMessagesFromTranslationsService(pluginConfig.translationServiceUrl, genericLanguage, name);
// We got some messages, so we configure the configuration to use the generic language for this session.
(_a = pluginConfig.availableLanguages) !== null && _a !== void 0 ? _a : pluginConfig.availableLanguages = {}
pluginConfig.availableLanguages['*'] = genericLanguage
return messagesLoaded(messages)
} catch (err) {
console.error(err)
return req([\`\${name}.nls\`], messagesLoaded)
}
}
})()
} else {
req([name + suffix], messagesLoaded, err => {
if (suffix === '.nls') {
console.error('Failed trying to load default language strings', err)
return
}
console.error(\`Failed to load message bundle for language \${language}. Falling back to the default language:\`, err)
req([\`\${name}.nls\`], messagesLoaded)
})
}
}
`
}