Implement NLS without AMD loader (#214588)

This commit is contained in:
Benjamin Pasero 2024-06-28 11:55:48 +02:00 committed by GitHub
parent cb1514f9a6
commit f6f90e0163
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 1494 additions and 1503 deletions

View File

@ -1015,10 +1015,6 @@
"vs/base/common/*"
]
},
{
"target": "src/vs/workbench/{workbench.desktop.main.nls.js,workbench.web.main.nls.js}",
"restrictions": []
},
{
"target": "src/vs/{loader.d.ts,css.ts,css.build.ts,monaco.d.ts,nls.ts,nls.build.ts,nls.mock.ts}",
"restrictions": []

View File

@ -40,8 +40,6 @@
"**/Cargo.lock": true,
"src/vs/workbench/workbench.web.main.css": true,
"src/vs/workbench/workbench.desktop.main.css": true,
"src/vs/workbench/workbench.desktop.main.nls.js": true,
"src/vs/workbench/workbench.web.main.nls.js": true,
"build/**/*.js": true,
"out/**": true,
"out-build/**": true,

View File

@ -216,104 +216,105 @@ extends:
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
- stage: CompileCLI
dependsOn: []
jobs:
- ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}:
- job: CLILinuxX64
pool:
name: 1es-ubuntu-20.04-x64
os: linux
steps:
- template: build/azure-pipelines/linux/cli-build-linux.yml@self
parameters:
VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }}
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_LINUX: ${{ parameters.VSCODE_BUILD_LINUX }}
- ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
- stage: CompileCLI
dependsOn: []
jobs:
- ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}:
- job: CLILinuxX64
pool:
name: 1es-ubuntu-20.04-x64
os: linux
steps:
- template: build/azure-pipelines/linux/cli-build-linux.yml@self
parameters:
VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }}
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_LINUX: ${{ parameters.VSCODE_BUILD_LINUX }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true))) }}:
- job: CLILinuxGnuARM
pool:
name: 1es-ubuntu-20.04-x64
os: linux
steps:
- template: build/azure-pipelines/linux/cli-build-linux.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_LINUX_ARMHF: ${{ parameters.VSCODE_BUILD_LINUX_ARMHF }}
VSCODE_BUILD_LINUX_ARM64: ${{ parameters.VSCODE_BUILD_LINUX_ARM64 }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true))) }}:
- job: CLILinuxGnuARM
pool:
name: 1es-ubuntu-20.04-x64
os: linux
steps:
- template: build/azure-pipelines/linux/cli-build-linux.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_LINUX_ARMHF: ${{ parameters.VSCODE_BUILD_LINUX_ARMHF }}
VSCODE_BUILD_LINUX_ARM64: ${{ parameters.VSCODE_BUILD_LINUX_ARM64 }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE, true)) }}:
- job: CLIAlpineX64
pool:
name: 1es-ubuntu-20.04-x64
os: linux
steps:
- template: build/azure-pipelines/alpine/cli-build-alpine.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_ALPINE: ${{ parameters.VSCODE_BUILD_ALPINE }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE, true)) }}:
- job: CLIAlpineX64
pool:
name: 1es-ubuntu-20.04-x64
os: linux
steps:
- template: build/azure-pipelines/alpine/cli-build-alpine.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_ALPINE: ${{ parameters.VSCODE_BUILD_ALPINE }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }}:
- job: CLIAlpineARM64
pool:
name: 1es-mariner-2.0-arm64
os: linux
hostArchitecture: arm64
container: ubuntu-2004-arm64
steps:
- template: build/azure-pipelines/alpine/cli-build-alpine.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_ALPINE_ARM64: ${{ parameters.VSCODE_BUILD_ALPINE_ARM64 }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }}:
- job: CLIAlpineARM64
pool:
name: 1es-mariner-2.0-arm64
os: linux
hostArchitecture: arm64
container: ubuntu-2004-arm64
steps:
- template: build/azure-pipelines/alpine/cli-build-alpine.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_ALPINE_ARM64: ${{ parameters.VSCODE_BUILD_ALPINE_ARM64 }}
- ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}:
- job: CLIMacOSX64
pool:
name: Azure Pipelines
image: macOS-13
os: macOS
steps:
- template: build/azure-pipelines/darwin/cli-build-darwin.yml@self
parameters:
VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }}
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }}
- ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}:
- job: CLIMacOSX64
pool:
name: Azure Pipelines
image: macOS-13
os: macOS
steps:
- template: build/azure-pipelines/darwin/cli-build-darwin.yml@self
parameters:
VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }}
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}:
- job: CLIMacOSARM64
pool:
name: Azure Pipelines
image: macOS-13
os: macOS
steps:
- template: build/azure-pipelines/darwin/cli-build-darwin.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}:
- job: CLIMacOSARM64
pool:
name: Azure Pipelines
image: macOS-13
os: macOS
steps:
- template: build/azure-pipelines/darwin/cli-build-darwin.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }}
- ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}:
- job: CLIWindowsX64
pool:
name: 1es-windows-2019-x64
os: windows
steps:
- template: build/azure-pipelines/win32/cli-build-win32.yml@self
parameters:
VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }}
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }}
- ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}:
- job: CLIWindowsX64
pool:
name: 1es-windows-2019-x64
os: windows
steps:
- template: build/azure-pipelines/win32/cli-build-win32.yml@self
parameters:
VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }}
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
- job: CLIWindowsARM64
pool:
name: 1es-windows-2019-x64
os: windows
steps:
- template: build/azure-pipelines/win32/cli-build-win32.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }}
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
- job: CLIWindowsARM64
pool:
name: 1es-windows-2019-x64
os: windows
steps:
- template: build/azure-pipelines/win32/cli-build-win32.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }}
- stage: CustomSDL
dependsOn: []
@ -331,7 +332,8 @@ extends:
- stage: Windows
dependsOn:
- Compile
- CompileCLI
- ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
- CompileCLI
pool:
name: 1es-windows-2019-x64
os: windows
@ -422,7 +424,8 @@ extends:
- stage: Linux
dependsOn:
- Compile
- CompileCLI
- ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
- CompileCLI
pool:
name: 1es-ubuntu-20.04-x64
os: linux
@ -580,7 +583,8 @@ extends:
- stage: Alpine
dependsOn:
- Compile
- CompileCLI
- ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
- CompileCLI
pool:
name: 1es-ubuntu-20.04-x64
os: linux
@ -606,7 +610,8 @@ extends:
- stage: macOS
dependsOn:
- Compile
- CompileCLI
- ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
- CompileCLI
pool:
name: Azure Pipelines
image: macOS-13

View File

@ -16,13 +16,33 @@ const commit = process.env['BUILD_SOURCEVERSION'];
const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']);
function main() {
return new Promise((c, e) => {
es.merge(vfs.src('out-vscode-web-min/nls.metadata.json', { base: 'out-vscode-web-min' }), vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }))
const combinedMetadataJson = es.merge(
// vscode: we are not using `out-build/nls.metadata.json` here because
// it includes metadata for translators for `keys`. but for our purpose
// we want only the `keys` and `messages` as `string`.
es.merge(vfs.src('out-build/nls.keys.json', { base: 'out-build' }), vfs.src('out-build/nls.messages.json', { base: 'out-build' }))
.pipe(merge({
fileName: 'vscode.json',
jsonSpace: '',
concatArrays: true,
edit: (parsedJson, file) => {
if (file.base === 'out-build') {
if (file.basename === 'nls.keys.json') {
return { keys: parsedJson };
}
else {
return { messages: parsedJson };
}
}
}
})),
// extensions
vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' })).pipe(merge({
fileName: 'combined.nls.metadata.json',
jsonSpace: '',
concatArrays: true,
edit: (parsedJson, file) => {
if (file.base === 'out-vscode-web-min') {
if (file.basename === 'vscode.json') {
return { vscode: parsedJson };
}
// Handle extensions and follow the same structure as the Core nls file.
@ -72,13 +92,15 @@ function main() {
const key = manifestJson.publisher + '.' + manifestJson.name;
return { [key]: parsedJson };
},
}))
}));
const nlsMessagesJs = vfs.src('out-build/nls.messages.js', { base: 'out-build' });
es.merge(combinedMetadataJson, nlsMessagesJs)
.pipe(gzip({ append: false }))
.pipe(vfs.dest('./nlsMetadata'))
.pipe(es.through(function (data) {
console.log(`Uploading ${data.path}`);
// trigger artifact upload
console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=combined.nls.metadata.json]${data.path}`);
console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=${data.basename}]${data.path}`);
this.emit('data', data);
}))
.pipe(azure.upload({

View File

@ -24,79 +24,103 @@ interface NlsMetadata {
function main(): Promise<void> {
return new Promise((c, e) => {
es.merge(
vfs.src('out-vscode-web-min/nls.metadata.json', { base: 'out-vscode-web-min' }),
vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }),
vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }),
vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }))
.pipe(merge({
fileName: 'combined.nls.metadata.json',
jsonSpace: '',
concatArrays: true,
edit: (parsedJson, file) => {
if (file.base === 'out-vscode-web-min') {
return { vscode: parsedJson };
}
// Handle extensions and follow the same structure as the Core nls file.
switch (file.basename) {
case 'package.nls.json':
// put package.nls.json content in Core NlsMetadata format
// language packs use the key "package" to specify that
// translations are for the package.json file
parsedJson = {
messages: {
package: Object.values(parsedJson)
},
keys: {
package: Object.keys(parsedJson)
},
bundles: {
main: ['package']
}
};
break;
case 'nls.metadata.header.json':
parsedJson = { header: parsedJson };
break;
case 'nls.metadata.json': {
// put nls.metadata.json content in Core NlsMetadata format
const modules = Object.keys(parsedJson);
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);
const combinedMetadataJson = es.merge(
// vscode: we are not using `out-build/nls.metadata.json` here because
// it includes metadata for translators for `keys`. but for our purpose
// we want only the `keys` and `messages` as `string`.
es.merge(
vfs.src('out-build/nls.keys.json', { base: 'out-build' }),
vfs.src('out-build/nls.messages.json', { base: 'out-build' }))
.pipe(merge({
fileName: 'vscode.json',
jsonSpace: '',
concatArrays: true,
edit: (parsedJson, file) => {
if (file.base === 'out-build') {
if (file.basename === 'nls.keys.json') {
return { keys: parsedJson };
} else {
return { messages: parsedJson };
}
parsedJson = json;
break;
}
}
})),
// Get extension id and use that as the key
const folderPath = path.join(file.base, file.relative.split('/')[0]);
const manifest = readFileSync(path.join(folderPath, 'package.json'), 'utf-8');
const manifestJson = JSON.parse(manifest);
const key = manifestJson.publisher + '.' + manifestJson.name;
return { [key]: parsedJson };
},
}))
// extensions
vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }),
vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }),
vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' })
).pipe(merge({
fileName: 'combined.nls.metadata.json',
jsonSpace: '',
concatArrays: true,
edit: (parsedJson, file) => {
if (file.basename === 'vscode.json') {
return { vscode: parsedJson };
}
// Handle extensions and follow the same structure as the Core nls file.
switch (file.basename) {
case 'package.nls.json':
// put package.nls.json content in Core NlsMetadata format
// language packs use the key "package" to specify that
// translations are for the package.json file
parsedJson = {
messages: {
package: Object.values(parsedJson)
},
keys: {
package: Object.keys(parsedJson)
},
bundles: {
main: ['package']
}
};
break;
case 'nls.metadata.header.json':
parsedJson = { header: parsedJson };
break;
case 'nls.metadata.json': {
// put nls.metadata.json content in Core NlsMetadata format
const modules = Object.keys(parsedJson);
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;
break;
}
}
// Get extension id and use that as the key
const folderPath = path.join(file.base, file.relative.split('/')[0]);
const manifest = readFileSync(path.join(folderPath, 'package.json'), 'utf-8');
const manifestJson = JSON.parse(manifest);
const key = manifestJson.publisher + '.' + manifestJson.name;
return { [key]: parsedJson };
},
}));
const nlsMessagesJs = vfs.src('out-build/nls.messages.js', { base: 'out-build' });
es.merge(combinedMetadataJson, nlsMessagesJs)
.pipe(gzip({ append: false }))
.pipe(vfs.dest('./nlsMetadata'))
.pipe(es.through(function (data: Vinyl) {
console.log(`Uploading ${data.path}`);
// trigger artifact upload
console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=combined.nls.metadata.json]${data.path}`);
console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=${data.basename}]${data.path}`);
this.emit('data', data);
}))
.pipe(azure.upload({

View File

@ -152,7 +152,7 @@ steps:
- script: |
set -e
AZURE_STORAGE_ACCOUNT="ticino" \
AZURE_STORAGE_ACCOUNT="vscodeweb" \
AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \
AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \
AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \

View File

@ -86,7 +86,7 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => {
});
// Disable mangling for the editor, as it complicates debugging & quite a few users rely on private/protected fields.
const compileEditorAMDTask = task.define('compile-editor-amd', compilation.compileTask('out-editor-src', 'out-editor-build', true, { disableMangle: true }));
const compileEditorAMDTask = task.define('compile-editor-amd', compilation.compileTask('out-editor-src', 'out-editor-build', true, { disableMangle: true, preserveEnglish: true }));
const optimizeEditorAMDTask = task.define('optimize-editor-amd', optimize.optimizeTask(
{

View File

@ -58,6 +58,9 @@ const serverResources = [
'out-build/bootstrap-amd.js',
'out-build/bootstrap-node.js',
// NLS
'out-build/nls.messages.json',
// Performance
'out-build/vs/base/common/performance.js',
@ -82,6 +85,9 @@ const serverWithWebResources = [
// Include all of server...
...serverResources,
// NLS
'out-build/nls.messages.js',
// ...and all of web
...vscodeWebResourceIncludes
];

View File

@ -56,6 +56,8 @@ const vscodeResources = [
'out-build/bootstrap-amd.js',
'out-build/bootstrap-node.js',
'out-build/bootstrap-window.js',
'out-build/nls.messages.json',
'out-build/nls.keys.json',
'out-build/vs/**/*.{svg,png,html,jpg,mp3}',
'!out-build/vs/code/browser/**/*.html',
'!out-build/vs/code/**/*-dev.html',
@ -496,17 +498,12 @@ gulp.task(task.define(
core,
compileExtensionsBuildTask,
function () {
const pathToMetadata = './out-vscode/nls.metadata.json';
const pathToRehWebMetadata = './out-vscode-reh-web/nls.metadata.json';
const pathToMetadata = './out-build/nls.metadata.json';
const pathToExtensions = '.build/extensions/*';
const pathToSetup = 'build/win32/i18n/messages.en.isl';
return es.merge(
gulp.src([pathToMetadata, pathToRehWebMetadata]).pipe(merge({
fileName: 'nls.metadata.json',
jsonSpace: '',
concatArrays: true
})).pipe(i18n.createXlfFilesForCoreBundle()),
gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()),
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
).pipe(vfs.dest('../vscode-translations-export'));

View File

@ -41,7 +41,7 @@ function getTypeScriptCompilerOptions(src) {
options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 0 : 1;
return options;
}
function createCompile(src, build, emitError, transpileOnly) {
function createCompile(src, { build, emitError, transpileOnly, preserveEnglish }) {
const tsb = require('./tsb');
const sourcemaps = require('gulp-sourcemaps');
const projectPath = path.join(__dirname, '../../', src, 'tsconfig.json');
@ -71,7 +71,7 @@ function createCompile(src, build, emitError, transpileOnly) {
.pipe(util.loadSourcemaps())
.pipe(compilation(token))
.pipe(noDeclarationsFilter)
.pipe(util.$if(build, nls.nls()))
.pipe(util.$if(build, nls.nls({ preserveEnglish })))
.pipe(noDeclarationsFilter.restore)
.pipe(util.$if(!transpileOnly, sourcemaps.write('.', {
addComment: false,
@ -90,7 +90,7 @@ function createCompile(src, build, emitError, transpileOnly) {
}
function transpileTask(src, out, swc) {
const task = () => {
const transpile = createCompile(src, false, true, { swc });
const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { swc }, preserveEnglish: false });
const srcPipe = gulp.src(`${src}/**`, { base: `${src}` });
return srcPipe
.pipe(transpile())
@ -104,7 +104,7 @@ function compileTask(src, out, build, options = {}) {
if (os.totalmem() < 4_000_000_000) {
throw new Error('compilation requires 4GB of RAM');
}
const compile = createCompile(src, build, true, false);
const compile = createCompile(src, { build, emitError: true, transpileOnly: false, preserveEnglish: !!options.preserveEnglish });
const srcPipe = gulp.src(`${src}/**`, { base: `${src}` });
const generator = new MonacoGenerator(false);
if (src === 'src') {
@ -141,7 +141,7 @@ function compileTask(src, out, build, options = {}) {
}
function watchTask(out, build) {
const task = () => {
const compile = createCompile('src', build, false, false);
const compile = createCompile('src', { build, emitError: false, transpileOnly: false, preserveEnglish: false });
const src = gulp.src('src/**', { base: 'src' });
const watchSrc = watch('src/**', { base: 'src', readDelay: 200 });
const generator = new MonacoGenerator(true);

View File

@ -42,7 +42,14 @@ function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions {
return options;
}
function createCompile(src: string, build: boolean, emitError: boolean, transpileOnly: boolean | { swc: boolean }) {
interface ICompileTaskOptions {
readonly build: boolean;
readonly emitError: boolean;
readonly transpileOnly: boolean | { swc: boolean };
readonly preserveEnglish: boolean;
}
function createCompile(src: string, { build, emitError, transpileOnly, preserveEnglish }: ICompileTaskOptions) {
const tsb = require('./tsb') as typeof import('./tsb');
const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps');
@ -79,7 +86,7 @@ function createCompile(src: string, build: boolean, emitError: boolean, transpil
.pipe(util.loadSourcemaps())
.pipe(compilation(token))
.pipe(noDeclarationsFilter)
.pipe(util.$if(build, nls.nls()))
.pipe(util.$if(build, nls.nls({ preserveEnglish })))
.pipe(noDeclarationsFilter.restore)
.pipe(util.$if(!transpileOnly, sourcemaps.write('.', {
addComment: false,
@ -102,7 +109,7 @@ export function transpileTask(src: string, out: string, swc: boolean): task.Stre
const task = () => {
const transpile = createCompile(src, false, true, { swc });
const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { swc }, preserveEnglish: false });
const srcPipe = gulp.src(`${src}/**`, { base: `${src}` });
return srcPipe
@ -114,7 +121,7 @@ export function transpileTask(src: string, out: string, swc: boolean): task.Stre
return task;
}
export function compileTask(src: string, out: string, build: boolean, options: { disableMangle?: boolean } = {}): task.StreamTask {
export function compileTask(src: string, out: string, build: boolean, options: { disableMangle?: boolean; preserveEnglish?: boolean } = {}): task.StreamTask {
const task = () => {
@ -122,7 +129,7 @@ export function compileTask(src: string, out: string, build: boolean, options: {
throw new Error('compilation requires 4GB of RAM');
}
const compile = createCompile(src, build, true, false);
const compile = createCompile(src, { build, emitError: true, transpileOnly: false, preserveEnglish: !!options.preserveEnglish });
const srcPipe = gulp.src(`${src}/**`, { base: `${src}` });
const generator = new MonacoGenerator(false);
if (src === 'src') {
@ -166,7 +173,7 @@ export function compileTask(src: string, out: string, build: boolean, options: {
export function watchTask(out: string, build: boolean): task.StreamTask {
const task = () => {
const compile = createCompile('src', build, false, false);
const compile = createCompile('src', { build, emitError: false, transpileOnly: false, preserveEnglish: false });
const src = gulp.src('src/**', { base: 'src' });
const watchSrc = watch('src/**', { base: 'src', readDelay: 200 });

View File

@ -23,6 +23,7 @@ const fancyLog = require("fancy-log");
const ansiColors = require("ansi-colors");
const iconv = require("@vscode/iconv-lite-umd");
const l10n_dev_1 = require("@vscode/l10n-dev");
const REPO_ROOT_PATH = path.join(__dirname, '../..');
function log(message, ...rest) {
fancyLog(ansiColors.green('[i18n]'), message, ...rest);
}
@ -63,6 +64,17 @@ var BundledFormat;
}
BundledFormat.is = is;
})(BundledFormat || (BundledFormat = {}));
var NLSKeysFormat;
(function (NLSKeysFormat) {
function is(value) {
if (value === undefined) {
return false;
}
const candidate = value;
return Array.isArray(candidate) && Array.isArray(candidate[1]);
}
NLSKeysFormat.is = is;
})(NLSKeysFormat || (NLSKeysFormat = {}));
class Line {
buffer = [];
constructor(indent = 0) {
@ -265,67 +277,8 @@ function stripComments(content) {
});
return result;
}
function escapeCharacters(value) {
const result = [];
for (let i = 0; i < value.length; i++) {
const ch = value.charAt(i);
switch (ch) {
case '\'':
result.push('\\\'');
break;
case '"':
result.push('\\"');
break;
case '\\':
result.push('\\\\');
break;
case '\n':
result.push('\\n');
break;
case '\r':
result.push('\\r');
break;
case '\t':
result.push('\\t');
break;
case '\b':
result.push('\\b');
break;
case '\f':
result.push('\\f');
break;
default:
result.push(ch);
}
}
return result.join('');
}
function processCoreBundleFormat(fileHeader, languages, json, emitter) {
const keysSection = json.keys;
const messageSection = json.messages;
const bundleSection = json.bundles;
const statistics = Object.create(null);
const defaultMessages = Object.create(null);
const modules = Object.keys(keysSection);
modules.forEach((module) => {
const keys = keysSection[module];
const messages = messageSection[module];
if (!messages || keys.length !== messages.length) {
emitter.emit('error', `Message for module ${module} corrupted. Mismatch in number of keys and messages.`);
return;
}
const messageMap = Object.create(null);
defaultMessages[module] = messageMap;
keys.map((key, i) => {
if (typeof key === 'string') {
messageMap[key] = messages[i];
}
else {
messageMap[key.key] = messages[i];
}
});
});
const languageDirectory = path.join(__dirname, '..', '..', '..', 'vscode-loc', 'i18n');
function processCoreBundleFormat(base, fileHeader, languages, json, emitter) {
const languageDirectory = path.join(REPO_ROOT_PATH, '..', 'vscode-loc', 'i18n');
if (!fs.existsSync(languageDirectory)) {
log(`No VS Code localization repository found. Looking at ${languageDirectory}`);
log(`To bundle translations please check out the vscode-loc repository as a sibling of the vscode repository.`);
@ -335,8 +288,6 @@ function processCoreBundleFormat(fileHeader, languages, json, emitter) {
if (process.env['VSCODE_BUILD_VERBOSE']) {
log(`Generating nls bundles for: ${language.id}`);
}
statistics[language.id] = 0;
const localizedModules = Object.create(null);
const languageFolderName = language.translationId || language.id;
const i18nFile = path.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json');
let allMessages;
@ -344,87 +295,36 @@ function processCoreBundleFormat(fileHeader, languages, json, emitter) {
const content = stripComments(fs.readFileSync(i18nFile, 'utf8'));
allMessages = JSON.parse(content);
}
modules.forEach((module) => {
const order = keysSection[module];
let moduleMessage;
if (allMessages) {
moduleMessage = allMessages.contents[module];
let nlsIndex = 0;
const nlsResult = [];
for (const [moduleId, nlsKeys] of json) {
const moduleTranslations = allMessages?.contents[moduleId];
for (const nlsKey of nlsKeys) {
nlsResult.push(moduleTranslations?.[nlsKey]); // pushing `undefined` is fine, as we keep english strings as fallback for monaco editor in the build
nlsIndex++;
}
if (!moduleMessage) {
if (process.env['VSCODE_BUILD_VERBOSE']) {
log(`No localized messages found for module ${module}. Using default messages.`);
}
moduleMessage = defaultMessages[module];
statistics[language.id] = statistics[language.id] + Object.keys(moduleMessage).length;
}
const localizedMessages = [];
order.forEach((keyInfo) => {
let key = null;
if (typeof keyInfo === 'string') {
key = keyInfo;
}
else {
key = keyInfo.key;
}
let message = moduleMessage[key];
if (!message) {
if (process.env['VSCODE_BUILD_VERBOSE']) {
log(`No localized message found for key ${key} in module ${module}. Using default message.`);
}
message = defaultMessages[module][key];
statistics[language.id] = statistics[language.id] + 1;
}
localizedMessages.push(message);
});
localizedModules[module] = localizedMessages;
});
Object.keys(bundleSection).forEach((bundle) => {
const modules = bundleSection[bundle];
const contents = [
fileHeader,
`define("${bundle}.nls.${language.id}", {`
];
modules.forEach((module, index) => {
contents.push(`\t"${module}": [`);
const messages = localizedModules[module];
if (!messages) {
emitter.emit('error', `Didn't find messages for module ${module}.`);
return;
}
messages.forEach((message, index) => {
contents.push(`\t\t"${escapeCharacters(message)}${index < messages.length ? '",' : '"'}`);
});
contents.push(index < modules.length - 1 ? '\t],' : '\t]');
});
contents.push('});');
emitter.queue(new File({ path: bundle + '.nls.' + language.id + '.js', contents: Buffer.from(contents.join('\n'), 'utf-8') }));
});
});
Object.keys(statistics).forEach(key => {
const value = statistics[key];
log(`${key} has ${value} untranslated strings.`);
});
sortedLanguages.forEach(language => {
const stats = statistics[language.id];
if (!stats) {
log(`\tNo translations found for language ${language.id}. Using default language instead.`);
}
emitter.queue(new File({
contents: Buffer.from(`${fileHeader}
globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(nlsResult)};
globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(language.id)};`),
base,
path: `${base}/nls.messages.${language.id}.js`
}));
});
}
function processNlsFiles(opts) {
return (0, event_stream_1.through)(function (file) {
const fileName = path.basename(file.path);
if (fileName === 'nls.metadata.json') {
let json = null;
if (file.isBuffer()) {
json = JSON.parse(file.contents.toString('utf8'));
if (fileName === 'bundleInfo.json') { // pick a root level file to put the core bundles
try {
const json = JSON.parse(fs.readFileSync(path.join(REPO_ROOT_PATH, opts.out, 'nls.keys.json')).toString());
if (NLSKeysFormat.is(json)) {
processCoreBundleFormat(file.base, opts.fileHeader, opts.languages, json, this);
}
}
else {
this.emit('error', `Failed to read component file: ${file.relative}`);
return;
}
if (BundledFormat.is(json)) {
processCoreBundleFormat(opts.fileHeader, opts.languages, json, this);
catch (error) {
this.emit('error', `Failed to read component file: ${error}`);
}
}
this.queue(file);

View File

@ -16,6 +16,8 @@ import * as ansiColors from 'ansi-colors';
import * as iconv from '@vscode/iconv-lite-umd';
import { l10nJsonFormat, getL10nXlf, l10nJsonDetails, getL10nFilesFromXlf, getL10nJson } from '@vscode/l10n-dev';
const REPO_ROOT_PATH = path.join(__dirname, '../..');
function log(message: any, ...rest: any[]): void {
fancyLog(ansiColors.green('[i18n]'), message, ...rest);
}
@ -91,6 +93,19 @@ module BundledFormat {
}
}
type NLSKeysFormat = [string /* module ID */, string[] /* keys */];
module NLSKeysFormat {
export function is(value: any): value is NLSKeysFormat {
if (value === undefined) {
return false;
}
const candidate = value as NLSKeysFormat;
return Array.isArray(candidate) && Array.isArray(candidate[1]);
}
}
interface BundledExtensionFormat {
[key: string]: {
messages: string[];
@ -329,70 +344,8 @@ function stripComments(content: string): string {
return result;
}
function escapeCharacters(value: string): string {
const result: string[] = [];
for (let i = 0; i < value.length; i++) {
const ch = value.charAt(i);
switch (ch) {
case '\'':
result.push('\\\'');
break;
case '"':
result.push('\\"');
break;
case '\\':
result.push('\\\\');
break;
case '\n':
result.push('\\n');
break;
case '\r':
result.push('\\r');
break;
case '\t':
result.push('\\t');
break;
case '\b':
result.push('\\b');
break;
case '\f':
result.push('\\f');
break;
default:
result.push(ch);
}
}
return result.join('');
}
function processCoreBundleFormat(fileHeader: string, languages: Language[], json: BundledFormat, emitter: ThroughStream) {
const keysSection = json.keys;
const messageSection = json.messages;
const bundleSection = json.bundles;
const statistics: Record<string, number> = Object.create(null);
const defaultMessages: Record<string, Record<string, string>> = Object.create(null);
const modules = Object.keys(keysSection);
modules.forEach((module) => {
const keys = keysSection[module];
const messages = messageSection[module];
if (!messages || keys.length !== messages.length) {
emitter.emit('error', `Message for module ${module} corrupted. Mismatch in number of keys and messages.`);
return;
}
const messageMap: Record<string, string> = Object.create(null);
defaultMessages[module] = messageMap;
keys.map((key, i) => {
if (typeof key === 'string') {
messageMap[key] = messages[i];
} else {
messageMap[key.key] = messages[i];
}
});
});
const languageDirectory = path.join(__dirname, '..', '..', '..', 'vscode-loc', 'i18n');
function processCoreBundleFormat(base: string, fileHeader: string, languages: Language[], json: NLSKeysFormat, emitter: ThroughStream) {
const languageDirectory = path.join(REPO_ROOT_PATH, '..', 'vscode-loc', 'i18n');
if (!fs.existsSync(languageDirectory)) {
log(`No VS Code localization repository found. Looking at ${languageDirectory}`);
log(`To bundle translations please check out the vscode-loc repository as a sibling of the vscode repository.`);
@ -403,8 +356,6 @@ function processCoreBundleFormat(fileHeader: string, languages: Language[], json
log(`Generating nls bundles for: ${language.id}`);
}
statistics[language.id] = 0;
const localizedModules: Record<string, string[]> = Object.create(null);
const languageFolderName = language.translationId || language.id;
const i18nFile = path.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json');
let allMessages: I18nFormat | undefined;
@ -412,86 +363,38 @@ function processCoreBundleFormat(fileHeader: string, languages: Language[], json
const content = stripComments(fs.readFileSync(i18nFile, 'utf8'));
allMessages = JSON.parse(content);
}
modules.forEach((module) => {
const order = keysSection[module];
let moduleMessage: { [messageKey: string]: string } | undefined;
if (allMessages) {
moduleMessage = allMessages.contents[module];
let nlsIndex = 0;
const nlsResult: Array<string | undefined> = [];
for (const [moduleId, nlsKeys] of json) {
const moduleTranslations = allMessages?.contents[moduleId];
for (const nlsKey of nlsKeys) {
nlsResult.push(moduleTranslations?.[nlsKey]); // pushing `undefined` is fine, as we keep english strings as fallback for monaco editor in the build
nlsIndex++;
}
if (!moduleMessage) {
if (process.env['VSCODE_BUILD_VERBOSE']) {
log(`No localized messages found for module ${module}. Using default messages.`);
}
moduleMessage = defaultMessages[module];
statistics[language.id] = statistics[language.id] + Object.keys(moduleMessage).length;
}
const localizedMessages: string[] = [];
order.forEach((keyInfo) => {
let key: string | null = null;
if (typeof keyInfo === 'string') {
key = keyInfo;
} else {
key = keyInfo.key;
}
let message: string = moduleMessage![key];
if (!message) {
if (process.env['VSCODE_BUILD_VERBOSE']) {
log(`No localized message found for key ${key} in module ${module}. Using default message.`);
}
message = defaultMessages[module][key];
statistics[language.id] = statistics[language.id] + 1;
}
localizedMessages.push(message);
});
localizedModules[module] = localizedMessages;
});
Object.keys(bundleSection).forEach((bundle) => {
const modules = bundleSection[bundle];
const contents: string[] = [
fileHeader,
`define("${bundle}.nls.${language.id}", {`
];
modules.forEach((module, index) => {
contents.push(`\t"${module}": [`);
const messages = localizedModules[module];
if (!messages) {
emitter.emit('error', `Didn't find messages for module ${module}.`);
return;
}
messages.forEach((message, index) => {
contents.push(`\t\t"${escapeCharacters(message)}${index < messages.length ? '",' : '"'}`);
});
contents.push(index < modules.length - 1 ? '\t],' : '\t]');
});
contents.push('});');
emitter.queue(new File({ path: bundle + '.nls.' + language.id + '.js', contents: Buffer.from(contents.join('\n'), 'utf-8') }));
});
});
Object.keys(statistics).forEach(key => {
const value = statistics[key];
log(`${key} has ${value} untranslated strings.`);
});
sortedLanguages.forEach(language => {
const stats = statistics[language.id];
if (!stats) {
log(`\tNo translations found for language ${language.id}. Using default language instead.`);
}
emitter.queue(new File({
contents: Buffer.from(`${fileHeader}
globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(nlsResult)};
globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(language.id)};`),
base,
path: `${base}/nls.messages.${language.id}.js`
}));
});
}
export function processNlsFiles(opts: { fileHeader: string; languages: Language[] }): ThroughStream {
export function processNlsFiles(opts: { out: string; fileHeader: string; languages: Language[] }): ThroughStream {
return through(function (this: ThroughStream, file: File) {
const fileName = path.basename(file.path);
if (fileName === 'nls.metadata.json') {
let json = null;
if (file.isBuffer()) {
json = JSON.parse((<Buffer>file.contents).toString('utf8'));
} else {
this.emit('error', `Failed to read component file: ${file.relative}`);
return;
}
if (BundledFormat.is(json)) {
processCoreBundleFormat(opts.fileHeader, opts.languages, json, this);
if (fileName === 'bundleInfo.json') { // pick a root level file to put the core bundles
try {
const json = JSON.parse(fs.readFileSync(path.join(REPO_ROOT_PATH, opts.out, 'nls.keys.json')).toString());
if (NLSKeysFormat.is(json)) {
processCoreBundleFormat(file.base, opts.fileHeader, opts.languages, json, this);
}
} catch (error) {
this.emit('error', `Failed to read component file: ${error}`);
}
}
this.queue(file);

View File

@ -38,21 +38,11 @@ function clone(object) {
}
return result;
}
function template(lines) {
let indent = '', wrap = '';
if (lines.length > 1) {
indent = '\t';
wrap = '\n';
}
return `/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
define([], [${wrap + lines.map(l => indent + l).join(',\n') + wrap}]);`;
}
/**
* Returns a stream containing the patched JavaScript and source maps.
*/
function nls() {
function nls(options) {
let base;
const input = (0, event_stream_1.through)();
const output = input.pipe((0, event_stream_1.through)(function (f) {
if (!f.sourceMap) {
@ -70,7 +60,40 @@ function nls() {
if (!typescript) {
return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`));
}
_nls.patchFiles(f, typescript).forEach(f => this.emit('data', f));
base = f.base;
this.emit('data', _nls.patchFile(f, typescript, options));
}, function () {
for (const file of [
new File({
contents: Buffer.from(JSON.stringify({
keys: _nls.moduleToNLSKeys,
messages: _nls.moduleToNLSMessages,
}, null, '\t')),
base,
path: `${base}/nls.metadata.json`
}),
new File({
contents: Buffer.from(JSON.stringify(_nls.allNLSMessages)),
base,
path: `${base}/nls.messages.json`
}),
new File({
contents: Buffer.from(JSON.stringify(_nls.allNLSModulesAndKeys)),
base,
path: `${base}/nls.keys.json`
}),
new File({
contents: Buffer.from(`/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(_nls.allNLSMessages)};`),
base,
path: `${base}/nls.messages.js`
})
]) {
this.emit('data', file);
}
this.emit('end');
}));
return (0, event_stream_1.duplex)(input, output);
}
@ -79,6 +102,11 @@ function isImportNode(ts, node) {
}
var _nls;
(function (_nls) {
_nls.moduleToNLSKeys = {};
_nls.moduleToNLSMessages = {};
_nls.allNLSMessages = [];
_nls.allNLSModulesAndKeys = [];
let allNLSMessagesIndex = 0;
function fileFrom(file, contents, path = file.path) {
return new File({
contents: Buffer.from(contents),
@ -146,13 +174,6 @@ var _nls;
.filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral)
.filter(d => d.moduleSpecifier.getText() === '\'vs/nls\'')
.filter(d => !!d.importClause && !!d.importClause.namedBindings);
const nlsExpressions = importEqualsDeclarations
.map(d => d.moduleReference.expression)
.concat(importDeclarations.map(d => d.moduleSpecifier))
.map(d => ({
start: ts.getLineAndCharacterOfPosition(sourceFile, d.getStart()),
end: ts.getLineAndCharacterOfPosition(sourceFile, d.getEnd())
}));
// `nls.localize(...)` calls
const nlsLocalizeCallExpressions = importDeclarations
.filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport))
@ -206,8 +227,7 @@ var _nls;
value: a[1].getText()
}));
return {
localizeCalls: localizeCalls.toArray(),
nlsExpressions: nlsExpressions.toArray()
localizeCalls: localizeCalls.toArray()
};
}
class TextModel {
@ -262,14 +282,10 @@ var _nls;
.flatten().toArray().join('');
}
}
function patchJavascript(patches, contents, moduleId) {
function patchJavascript(patches, contents) {
const model = new TextModel(contents);
// patch the localize calls
lazy(patches).reverse().each(p => model.apply(p));
// patch the 'vs/nls' imports
const firstLine = model.get(0);
const patchedFirstLine = firstLine.replace(/(['"])vs\/nls\1/g, `$1vs/nls!${moduleId}$1`);
model.set(0, patchedFirstLine);
return model.toString();
}
function patchSourcemap(patches, rsm, smc) {
@ -307,14 +323,21 @@ var _nls;
}
return JSON.parse(smg.toString());
}
function patch(ts, moduleId, typescript, javascript, sourcemap) {
const { localizeCalls, nlsExpressions } = analyze(ts, typescript, 'localize');
const { localizeCalls: localize2Calls, nlsExpressions: nls2Expressions } = analyze(ts, typescript, 'localize2');
function parseLocalizeKeyOrValue(sourceExpression) {
// sourceValue can be "foo", 'foo', `foo` or { .... }
// in its evalulated form
// we want to return either the string or the object
// eslint-disable-next-line no-eval
return eval(`(${sourceExpression})`);
}
function patch(ts, typescript, javascript, sourcemap, options) {
const { localizeCalls } = analyze(ts, typescript, 'localize');
const { localizeCalls: localize2Calls } = analyze(ts, typescript, 'localize2');
if (localizeCalls.length === 0 && localize2Calls.length === 0) {
return { javascript, sourcemap };
}
const nlsKeys = template(localizeCalls.map(lc => lc.key).concat(localize2Calls.map(lc => lc.key)));
const nls = template(localizeCalls.map(lc => lc.value).concat(localize2Calls.map(lc => lc.value)));
const nlsKeys = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.key)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.key)));
const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value)));
const smc = new sm.SourceMapConsumer(sourcemap);
const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]);
// build patches
@ -323,16 +346,18 @@ var _nls;
const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end)));
return { span: { start, end }, content: c.content };
};
let i = 0;
const localizePatches = lazy(localizeCalls)
.map(lc => ([
{ range: lc.keySpan, content: '' + (i++) },
.map(lc => (options.preserveEnglish ? [
{ range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize('key', "message") => localize(<index>, "message")
] : [
{ range: lc.keySpan, content: `${allNLSMessagesIndex++}` }, // localize('key', "message") => localize(<index>, null)
{ range: lc.valueSpan, content: 'null' }
]))
.flatten()
.map(toPatch);
const localize2Patches = lazy(localize2Calls)
.map(lc => ({ range: lc.keySpan, content: '' + (i++) }))
.map(lc => ({ range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize2('key', "message") => localize(<index>, "message")
))
.map(toPatch);
// Sort patches by their start position
const patches = localizePatches.concat(localize2Patches).toArray().sort((a, b) => {
@ -352,34 +377,29 @@ var _nls;
return 0;
}
});
javascript = patchJavascript(patches, javascript, moduleId);
// since imports are not within the sourcemap information,
// we must do this MacGyver style
if (nlsExpressions.length || nls2Expressions.length) {
javascript = javascript.replace(/^define\(.*$/m, line => {
return line.replace(/(['"])vs\/nls\1/g, `$1vs/nls!${moduleId}$1`);
});
}
javascript = patchJavascript(patches, javascript);
sourcemap = patchSourcemap(patches, sourcemap, smc);
return { javascript, sourcemap, nlsKeys, nls };
return { javascript, sourcemap, nlsKeys, nlsMessages };
}
function patchFiles(javascriptFile, typescript) {
function patchFile(javascriptFile, typescript, options) {
const ts = require('typescript');
// hack?
const moduleId = javascriptFile.relative
.replace(/\.js$/, '')
.replace(/\\/g, '/');
const { javascript, sourcemap, nlsKeys, nls } = patch(ts, moduleId, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap);
const result = [fileFrom(javascriptFile, javascript)];
result[0].sourceMap = sourcemap;
const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(ts, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap, options);
const result = fileFrom(javascriptFile, javascript);
result.sourceMap = sourcemap;
if (nlsKeys) {
result.push(fileFrom(javascriptFile, nlsKeys, javascriptFile.path.replace(/\.js$/, '.nls.keys.js')));
_nls.moduleToNLSKeys[moduleId] = nlsKeys;
_nls.allNLSModulesAndKeys.push([moduleId, nlsKeys.map(nlsKey => typeof nlsKey === 'string' ? nlsKey : nlsKey.key)]);
}
if (nls) {
result.push(fileFrom(javascriptFile, nls, javascriptFile.path.replace(/\.js$/, '.nls.js')));
if (nlsMessages) {
_nls.moduleToNLSMessages[moduleId] = nlsMessages;
_nls.allNLSMessages.push(...nlsMessages);
}
return result;
}
_nls.patchFiles = patchFiles;
_nls.patchFile = patchFile;
})(_nls || (_nls = {}));
//# sourceMappingURL=nls.js.map

View File

@ -48,24 +48,11 @@ function clone<T extends object>(object: T): T {
return result;
}
function template(lines: string[]): string {
let indent = '', wrap = '';
if (lines.length > 1) {
indent = '\t';
wrap = '\n';
}
return `/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
define([], [${wrap + lines.map(l => indent + l).join(',\n') + wrap}]);`;
}
/**
* Returns a stream containing the patched JavaScript and source maps.
*/
export function nls(): NodeJS.ReadWriteStream {
export function nls(options: { preserveEnglish: boolean }): NodeJS.ReadWriteStream {
let base: string;
const input = through();
const output = input.pipe(through(function (f: FileSourceMap) {
if (!f.sourceMap) {
@ -87,7 +74,41 @@ export function nls(): NodeJS.ReadWriteStream {
return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`));
}
_nls.patchFiles(f, typescript).forEach(f => this.emit('data', f));
base = f.base;
this.emit('data', _nls.patchFile(f, typescript, options));
}, function () {
for (const file of [
new File({
contents: Buffer.from(JSON.stringify({
keys: _nls.moduleToNLSKeys,
messages: _nls.moduleToNLSMessages,
}, null, '\t')),
base,
path: `${base}/nls.metadata.json`
}),
new File({
contents: Buffer.from(JSON.stringify(_nls.allNLSMessages)),
base,
path: `${base}/nls.messages.json`
}),
new File({
contents: Buffer.from(JSON.stringify(_nls.allNLSModulesAndKeys)),
base,
path: `${base}/nls.keys.json`
}),
new File({
contents: Buffer.from(`/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(_nls.allNLSMessages)};`),
base,
path: `${base}/nls.messages.js`
})
]) {
this.emit('data', file);
}
this.emit('end');
}));
return duplex(input, output);
@ -99,11 +120,19 @@ function isImportNode(ts: typeof import('typescript'), node: ts.Node): boolean {
module _nls {
interface INlsStringResult {
export const moduleToNLSKeys: { [name: string /* module ID */]: ILocalizeKey[] /* keys */ } = {};
export const moduleToNLSMessages: { [name: string /* module ID */]: string[] /* messages */ } = {};
export const allNLSMessages: string[] = [];
export const allNLSModulesAndKeys: Array<[string /* module ID */, string[] /* keys */]> = [];
let allNLSMessagesIndex = 0;
type ILocalizeKey = string | { key: string }; // key might contain metadata for translators and then is not just a string
interface INlsPatchResult {
javascript: string;
sourcemap: sm.RawSourceMap;
nls?: string;
nlsKeys?: string;
nlsMessages?: string[];
nlsKeys?: ILocalizeKey[];
}
interface ISpan {
@ -120,7 +149,6 @@ module _nls {
interface ILocalizeAnalysisResult {
localizeCalls: ILocalizeCall[];
nlsExpressions: ISpan[];
}
interface IPatch {
@ -210,14 +238,6 @@ module _nls {
.filter(d => d.moduleSpecifier.getText() === '\'vs/nls\'')
.filter(d => !!d.importClause && !!d.importClause.namedBindings);
const nlsExpressions = importEqualsDeclarations
.map(d => (<ts.ExternalModuleReference>d.moduleReference).expression)
.concat(importDeclarations.map(d => d.moduleSpecifier))
.map<ISpan>(d => ({
start: ts.getLineAndCharacterOfPosition(sourceFile, d.getStart()),
end: ts.getLineAndCharacterOfPosition(sourceFile, d.getEnd())
}));
// `nls.localize(...)` calls
const nlsLocalizeCallExpressions = importDeclarations
.filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport))
@ -280,8 +300,7 @@ module _nls {
}));
return {
localizeCalls: localizeCalls.toArray(),
nlsExpressions: nlsExpressions.toArray()
localizeCalls: localizeCalls.toArray()
};
}
@ -351,17 +370,12 @@ module _nls {
}
}
function patchJavascript(patches: IPatch[], contents: string, moduleId: string): string {
function patchJavascript(patches: IPatch[], contents: string): string {
const model = new TextModel(contents);
// patch the localize calls
lazy(patches).reverse().each(p => model.apply(p));
// patch the 'vs/nls' imports
const firstLine = model.get(0);
const patchedFirstLine = firstLine.replace(/(['"])vs\/nls\1/g, `$1vs/nls!${moduleId}$1`);
model.set(0, patchedFirstLine);
return model.toString();
}
@ -410,16 +424,24 @@ module _nls {
return JSON.parse(smg.toString());
}
function patch(ts: typeof import('typescript'), moduleId: string, typescript: string, javascript: string, sourcemap: sm.RawSourceMap): INlsStringResult {
const { localizeCalls, nlsExpressions } = analyze(ts, typescript, 'localize');
const { localizeCalls: localize2Calls, nlsExpressions: nls2Expressions } = analyze(ts, typescript, 'localize2');
function parseLocalizeKeyOrValue(sourceExpression: string) {
// sourceValue can be "foo", 'foo', `foo` or { .... }
// in its evalulated form
// we want to return either the string or the object
// eslint-disable-next-line no-eval
return eval(`(${sourceExpression})`);
}
function patch(ts: typeof import('typescript'), typescript: string, javascript: string, sourcemap: sm.RawSourceMap, options: { preserveEnglish: boolean }): INlsPatchResult {
const { localizeCalls } = analyze(ts, typescript, 'localize');
const { localizeCalls: localize2Calls } = analyze(ts, typescript, 'localize2');
if (localizeCalls.length === 0 && localize2Calls.length === 0) {
return { javascript, sourcemap };
}
const nlsKeys = template(localizeCalls.map(lc => lc.key).concat(localize2Calls.map(lc => lc.key)));
const nls = template(localizeCalls.map(lc => lc.value).concat(localize2Calls.map(lc => lc.value)));
const nlsKeys = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.key)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.key)));
const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value)));
const smc = new sm.SourceMapConsumer(sourcemap);
const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]);
@ -430,18 +452,20 @@ module _nls {
return { span: { start, end }, content: c.content };
};
let i = 0;
const localizePatches = lazy(localizeCalls)
.map(lc => ([
{ range: lc.keySpan, content: '' + (i++) },
{ range: lc.valueSpan, content: 'null' }
]))
.map(lc => (
options.preserveEnglish ? [
{ range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize('key', "message") => localize(<index>, "message")
] : [
{ range: lc.keySpan, content: `${allNLSMessagesIndex++}` }, // localize('key', "message") => localize(<index>, null)
{ range: lc.valueSpan, content: 'null' }
]))
.flatten()
.map(toPatch);
const localize2Patches = lazy(localize2Calls)
.map(lc => (
{ range: lc.keySpan, content: '' + (i++) }
{ range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize2('key', "message") => localize(<index>, "message")
))
.map(toPatch);
@ -460,45 +484,39 @@ module _nls {
}
});
javascript = patchJavascript(patches, javascript, moduleId);
// since imports are not within the sourcemap information,
// we must do this MacGyver style
if (nlsExpressions.length || nls2Expressions.length) {
javascript = javascript.replace(/^define\(.*$/m, line => {
return line.replace(/(['"])vs\/nls\1/g, `$1vs/nls!${moduleId}$1`);
});
}
javascript = patchJavascript(patches, javascript);
sourcemap = patchSourcemap(patches, sourcemap, smc);
return { javascript, sourcemap, nlsKeys, nls };
return { javascript, sourcemap, nlsKeys, nlsMessages };
}
export function patchFiles(javascriptFile: File, typescript: string): File[] {
export function patchFile(javascriptFile: File, typescript: string, options: { preserveEnglish: boolean }): File {
const ts = require('typescript') as typeof import('typescript');
// hack?
const moduleId = javascriptFile.relative
.replace(/\.js$/, '')
.replace(/\\/g, '/');
const { javascript, sourcemap, nlsKeys, nls } = patch(
const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(
ts,
moduleId,
typescript,
javascriptFile.contents.toString(),
(<any>javascriptFile).sourceMap
(<any>javascriptFile).sourceMap,
options
);
const result: File[] = [fileFrom(javascriptFile, javascript)];
(<any>result[0]).sourceMap = sourcemap;
const result = fileFrom(javascriptFile, javascript);
(<any>result).sourceMap = sourcemap;
if (nlsKeys) {
result.push(fileFrom(javascriptFile, nlsKeys, javascriptFile.path.replace(/\.js$/, '.nls.keys.js')));
moduleToNLSKeys[moduleId] = nlsKeys;
allNLSModulesAndKeys.push([moduleId, nlsKeys.map(nlsKey => typeof nlsKey === 'string' ? nlsKey : nlsKey.key)]);
}
if (nls) {
result.push(fileFrom(javascriptFile, nls, javascriptFile.path.replace(/\.js$/, '.nls.js')));
if (nlsMessages) {
moduleToNLSMessages[moduleId] = nlsMessages;
allNLSMessages.push(...nlsMessages);
}
return result;

View File

@ -192,6 +192,7 @@ function optimizeAMDTask(opts) {
includeContent: true
}))
.pipe(opts.languages && opts.languages.length ? (0, i18n_1.processNlsFiles)({
out: opts.src,
fileHeader: bundledFileHeader,
languages: opts.languages
}) : es.through());

View File

@ -269,6 +269,7 @@ function optimizeAMDTask(opts: IOptimizeAMDTaskOpts): NodeJS.ReadWriteStream {
includeContent: true
}))
.pipe(opts.languages && opts.languages.length ? processNlsFiles({
out: opts.src,
fileHeader: bundledFileHeader,
languages: opts.languages
}) : es.through());

102
src/bootstrap-amd.js vendored
View File

@ -6,6 +6,10 @@
//@ts-check
'use strict';
/**
* @typedef {import('./vs/nls').INLSConfiguration} INLSConfiguration
*/
// Store the node.js require function in a variable
// before loading our AMD loader to avoid issues
// when this file is bundled with other files.
@ -31,16 +35,13 @@ globalThis._VSCODE_PACKAGE_JSON = require('../package.json');
const loader = require('./vs/loader');
const bootstrap = require('./bootstrap');
const performance = require('./vs/base/common/performance');
// Bootstrap: NLS
const nlsConfig = bootstrap.setupNLS();
const fs = require('fs');
// Bootstrap: Loader
loader.config({
baseUrl: bootstrap.fileUriFromPath(__dirname, { isWindows: process.platform === 'win32' }),
catchError: true,
nodeRequire,
'vs/nls': nlsConfig,
amdModulesPattern: /^vs\//,
recordStats: true
});
@ -52,13 +53,90 @@ if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) {
});
}
// Pseudo NLS support
if (nlsConfig && nlsConfig.pseudo) {
loader(['vs/nls'], function (/** @type {import('vs/nls')} */nlsPlugin) {
nlsPlugin.setPseudoTranslation(!!nlsConfig.pseudo);
});
//#region NLS helpers
/** @type {Promise<INLSConfiguration | undefined> | undefined} */
let setupNLSResult = undefined;
/**
* @returns {Promise<INLSConfiguration | undefined>}
*/
function setupNLS() {
if (!setupNLSResult) {
setupNLSResult = doSetupNLS();
}
return setupNLSResult;
}
/**
* @returns {Promise<INLSConfiguration | undefined>}
*/
async function doSetupNLS() {
performance.mark('code/fork/willLoadNls');
/** @type {INLSConfiguration | undefined} */
let nlsConfig = undefined;
/** @type {string | undefined} */
let messagesFile;
if (process.env['VSCODE_NLS_CONFIG']) {
try {
/** @type {INLSConfiguration} */
nlsConfig = JSON.parse(process.env['VSCODE_NLS_CONFIG']);
if (nlsConfig?.languagePack?.messagesFile) {
messagesFile = nlsConfig.languagePack.messagesFile;
} else if (nlsConfig?.defaultMessagesFile) {
messagesFile = nlsConfig.defaultMessagesFile;
}
// VSCODE_GLOBALS: NLS
globalThis._VSCODE_NLS_LANGUAGE = nlsConfig?.resolvedLanguage;
} catch (e) {
console.error(`Error reading VSCODE_NLS_CONFIG from environment: ${e}`);
}
}
if (
process.env['VSCODE_DEV'] || // no NLS support in dev mode
!messagesFile // no NLS messages file
) {
return undefined;
}
try {
// VSCODE_GLOBALS: NLS
globalThis._VSCODE_NLS_MESSAGES = JSON.parse((await fs.promises.readFile(messagesFile)).toString());
} catch (error) {
console.error(`Error reading NLS messages file ${messagesFile}: ${error}`);
// Mark as corrupt: this will re-create the language pack cache next startup
if (nlsConfig?.languagePack?.corruptMarkerFile) {
try {
await fs.promises.writeFile(nlsConfig.languagePack.corruptMarkerFile, 'corrupted');
} catch (error) {
console.error(`Error writing corrupted NLS marker file: ${error}`);
}
}
// Fallback to the default message file to ensure english translation at least
if (nlsConfig?.defaultMessagesFile && nlsConfig.defaultMessagesFile !== messagesFile) {
try {
// VSCODE_GLOBALS: NLS
globalThis._VSCODE_NLS_MESSAGES = JSON.parse((await fs.promises.readFile(nlsConfig.defaultMessagesFile)).toString());
} catch (error) {
console.error(`Error reading default NLS messages file ${nlsConfig.defaultMessagesFile}: ${error}`);
}
}
}
performance.mark('code/fork/didLoadNls');
return nlsConfig;
}
//#endregion
/**
* @param {string=} entrypoint
* @param {(value: any) => void=} onLoad
@ -82,6 +160,8 @@ exports.load = function (entrypoint, onLoad, onError) {
onLoad = onLoad || function () { };
onError = onError || function (err) { console.error(err); };
performance.mark('code/fork/willLoadCode');
loader([entrypoint], onLoad, onError);
setupNLS().then(() => {
performance.mark('code/fork/willLoadCode');
loader([entrypoint], onLoad, onError);
});
};

View File

@ -82,28 +82,23 @@
developerDeveloperKeybindingsDisposable = registerDeveloperKeybindings(disallowReloadKeybinding);
}
// Get the nls configuration into the process.env as early as possible
// @ts-ignore
const nlsConfig = globalThis.MonacoBootstrap.setupNLS();
let locale = nlsConfig.availableLanguages['*'] || 'en';
if (locale === 'zh-tw') {
locale = 'zh-Hant';
} else if (locale === 'zh-cn') {
locale = 'zh-Hans';
// VSCODE_GLOBALS: NLS
globalThis._VSCODE_NLS_MESSAGES = configuration.nls.messages;
globalThis._VSCODE_NLS_LANGUAGE = configuration.nls.language;
let language = configuration.nls.language || 'en';
if (language === 'zh-tw') {
language = 'zh-Hant';
} else if (language === 'zh-cn') {
language = 'zh-Hans';
}
window.document.documentElement.setAttribute('lang', locale);
window.document.documentElement.setAttribute('lang', language);
window['MonacoEnvironment'] = {};
/**
* @typedef {any} LoaderConfig
*/
/** @type {LoaderConfig} */
/** @type {any} */
const loaderConfig = {
baseUrl: `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out`,
'vs/nls': nlsConfig,
preferScriptTags: true
};
@ -147,13 +142,6 @@
// Configure loader
require.config(loaderConfig);
// Handle pseudo NLS
if (nlsConfig.pseudo) {
require(['vs/nls'], function (nlsPlugin) {
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
});
}
// Signal before require()
if (typeof options?.beforeRequire === 'function') {
options.beforeRequire(configuration);

135
src/bootstrap.js vendored
View File

@ -22,8 +22,6 @@
}(this, function () {
const Module = typeof require === 'function' ? require('module') : undefined;
const path = typeof require === 'function' ? require('path') : undefined;
const fs = typeof require === 'function' ? require('fs') : undefined;
const util = typeof require === 'function' ? require('util') : undefined;
//#region global bootstrapping
@ -118,141 +116,8 @@
//#endregion
//#region NLS helpers
/**
* @returns {{locale?: string, availableLanguages: {[lang: string]: string;}, pseudo?: boolean } | undefined}
*/
function setupNLS() {
// Get the nls configuration as early as possible.
const process = safeProcess();
/** @type {{ availableLanguages: {}; loadBundle?: (bundle: string, language: string, cb: (err: Error | undefined, result: string | undefined) => void) => void; _resolvedLanguagePackCoreLocation?: string; _corruptedFile?: string }} */
let nlsConfig = { availableLanguages: {} };
if (process && process.env['VSCODE_NLS_CONFIG']) {
try {
nlsConfig = JSON.parse(process.env['VSCODE_NLS_CONFIG']);
} catch (e) {
// Ignore
}
}
if (nlsConfig._resolvedLanguagePackCoreLocation) {
const bundles = Object.create(null);
/**
* @param {string} bundle
* @param {string} language
* @param {(err: Error | undefined, result: string | undefined) => void} cb
*/
nlsConfig.loadBundle = function (bundle, language, cb) {
const result = bundles[bundle];
if (result) {
cb(undefined, result);
return;
}
// @ts-ignore
safeReadNlsFile(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`).then(function (content) {
const json = JSON.parse(content);
bundles[bundle] = json;
cb(undefined, json);
}).catch((error) => {
try {
if (nlsConfig._corruptedFile) {
safeWriteNlsFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
}
} finally {
cb(error, undefined);
}
});
};
}
return nlsConfig;
}
/**
* @returns {typeof import('./vs/base/parts/sandbox/electron-sandbox/globals') | undefined}
*/
function safeSandboxGlobals() {
const globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {});
// @ts-ignore
return globals.vscode;
}
/**
* @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').ISandboxNodeProcess | NodeJS.Process | undefined}
*/
function safeProcess() {
const sandboxGlobals = safeSandboxGlobals();
if (sandboxGlobals) {
return sandboxGlobals.process; // Native environment (sandboxed)
}
if (typeof process !== 'undefined') {
return process; // Native environment (non-sandboxed)
}
return undefined;
}
/**
* @returns {import('./vs/base/parts/sandbox/electron-sandbox/electronTypes').IpcRenderer | undefined}
*/
function safeIpcRenderer() {
const sandboxGlobals = safeSandboxGlobals();
if (sandboxGlobals) {
return sandboxGlobals.ipcRenderer;
}
return undefined;
}
/**
* @param {string[]} pathSegments
* @returns {Promise<string>}
*/
async function safeReadNlsFile(...pathSegments) {
const ipcRenderer = safeIpcRenderer();
if (ipcRenderer) {
return ipcRenderer.invoke('vscode:readNlsFile', ...pathSegments);
}
if (fs && path && util) {
return (await util.promisify(fs.readFile)(path.join(...pathSegments))).toString();
}
throw new Error('Unsupported operation (read NLS files)');
}
/**
* @param {string} path
* @param {string} content
* @returns {Promise<void>}
*/
function safeWriteNlsFile(path, content) {
const ipcRenderer = safeIpcRenderer();
if (ipcRenderer) {
return ipcRenderer.invoke('vscode:writeNlsFile', path, content);
}
if (fs && util) {
return util.promisify(fs.writeFile)(path, content);
}
throw new Error('Unsupported operation (write NLS files)');
}
//#endregion
return {
enableASARSupport,
setupNLS,
fileUriFromPath
};
}));

View File

@ -6,6 +6,10 @@
//@ts-check
'use strict';
/**
* @import { IProductConfiguration } from './vs/base/common/product'
*/
// Delete `VSCODE_CWD` very early even before
// importing bootstrap files. We have seen
// reports where `code .` would use the wrong
@ -16,17 +20,29 @@ delete process.env['VSCODE_CWD'];
const bootstrap = require('./bootstrap');
const bootstrapNode = require('./bootstrap-node');
const product = require('../product.json');
// Enable portable support
/** @type {Partial<IProductConfiguration>} */
// @ts-ignore
bootstrapNode.configurePortable(product);
const product = require('../product.json');
const { resolveNLSConfiguration } = require('./vs/base/node/nls');
// Enable ASAR support
bootstrap.enableASARSupport();
async function start() {
// Signal processes that we got launched as CLI
process.env['VSCODE_CLI'] = '1';
// NLS
const nlsConfiguration = await resolveNLSConfiguration({ userLocale: 'en', osLocale: 'en', commit: product.commit, userDataPath: '', nlsMetadataPath: __dirname });
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfiguration); // required for `bootstrap-amd` to pick up NLS messages
// Load CLI through AMD loader
require('./bootstrap-amd').load('vs/code/node/cli');
// Enable portable support
// @ts-ignore
bootstrapNode.configurePortable(product);
// Enable ASAR support
bootstrap.enableASARSupport();
// Signal processes that we got launched as CLI
process.env['VSCODE_CLI'] = '1';
// Load CLI through AMD loader
require('./bootstrap-amd').load('vs/code/node/cli');
}
start();

View File

@ -8,7 +8,7 @@
/**
* @import { IProductConfiguration } from './vs/base/common/product'
* @import { NLSConfiguration } from './vs/base/node/languagePacks'
* @import { INLSConfiguration } from './vs/nls'
* @import { NativeParsedArgs } from './vs/platform/environment/common/argv'
*/
@ -109,27 +109,29 @@ protocol.registerSchemesAsPrivileged([
registerListeners();
/**
* Support user defined locale: load it early before app('ready')
* to have more things running in parallel.
* We can resolve the NLS configuration early if it is defined
* in argv.json before `app.ready` event. Otherwise we can only
* resolve NLS after `app.ready` event to resolve the OS locale.
*
* @type {Promise<NLSConfiguration> | undefined}
* @type {Promise<INLSConfiguration> | undefined}
*/
let nlsConfigurationPromise = undefined;
/**
* @type {String}
**/
// Use the most preferred OS language for language recommendation.
// The API might return an empty array on Linux, such as when
// the 'C' locale is the user's only configured locale.
// No matter the OS, if the array is empty, default back to 'en'.
const resolved = app.getPreferredSystemLanguages()?.[0] ?? 'en';
const osLocale = processZhLocale(resolved.toLowerCase());
const metaDataFile = path.join(__dirname, 'nls.metadata.json');
const locale = getUserDefinedLocale(argvConfig);
if (locale) {
const { getNLSConfiguration } = require('./vs/base/node/languagePacks');
nlsConfigurationPromise = getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale, osLocale);
const osLocale = processZhLocale((app.getPreferredSystemLanguages()?.[0] ?? 'en').toLowerCase());
const userLocale = getUserDefinedLocale(argvConfig);
if (userLocale) {
const { resolveNLSConfiguration } = require('./vs/base/node/nls');
nlsConfigurationPromise = resolveNLSConfiguration({
userLocale,
osLocale,
commit: product.commit,
userDataPath,
nlsMetadataPath: __dirname
});
}
// Pass in the locale to Electron so that the
@ -141,7 +143,7 @@ if (locale) {
// In that case, use `en` as the Electron locale.
if (process.platform === 'win32' || process.platform === 'linux') {
const electronLocale = (!locale || locale === 'qps-ploc') ? 'en' : locale;
const electronLocale = (!userLocale || userLocale === 'qps-ploc') ? 'en' : userLocale;
app.commandLine.appendSwitch('lang', electronLocale);
}
@ -161,15 +163,28 @@ app.once('ready', function () {
}
});
async function onReady() {
perf.mark('code/mainAppReady');
try {
const [, nlsConfig] = await Promise.all([
mkdirpIgnoreError(codeCachePath),
resolveNlsConfiguration()
]);
startup(codeCachePath, nlsConfig);
} catch (error) {
console.error(error);
}
}
/**
* Main startup routine
*
* @param {string | undefined} codeCachePath
* @param {NLSConfiguration} nlsConfig
* @param {INLSConfiguration} nlsConfig
*/
function startup(codeCachePath, nlsConfig) {
nlsConfig._languagePackSupport = true;
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || '';
@ -180,18 +195,6 @@ function startup(codeCachePath, nlsConfig) {
});
}
async function onReady() {
perf.mark('code/mainAppReady');
try {
const [, nlsConfig] = await Promise.all([mkdirpIgnoreError(codeCachePath), resolveNlsConfiguration()]);
startup(codeCachePath, nlsConfig);
} catch (error) {
console.error(error);
}
}
/**
* @param {NativeParsedArgs} cliArgs
*/
@ -643,35 +646,47 @@ function processZhLocale(appLocale) {
/**
* Resolve the NLS configuration
*
* @return {Promise<NLSConfiguration>}
* @return {Promise<INLSConfiguration>}
*/
async function resolveNlsConfiguration() {
// First, we need to test a user defined locale. If it fails we try the app locale.
// First, we need to test a user defined locale.
// If it fails we try the app locale.
// If that fails we fall back to English.
let nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined;
const nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined;
if (nlsConfiguration) {
return nlsConfiguration;
}
// Try to use the app locale. Please note that the app locale is only
// valid after we have received the app ready event. This is why the
// code is here.
// Try to use the app locale which is only valid
// after the app ready event has been fired.
/**
* @type string
*/
let appLocale = app.getLocale();
if (!appLocale) {
return { locale: 'en', osLocale, availableLanguages: {} };
let userLocale = app.getLocale();
if (!userLocale) {
return {
userLocale: 'en',
osLocale,
resolvedLanguage: 'en',
defaultMessagesFile: path.join(__dirname, 'nls.messages.json'),
// NLS: below 2 are a relic from old times only used by vscode-nls and deprecated
locale: 'en',
availableLanguages: {}
};
}
// See above the comment about the loader and case sensitiveness
appLocale = processZhLocale(appLocale.toLowerCase());
userLocale = processZhLocale(userLocale.toLowerCase());
const { getNLSConfiguration } = require('./vs/base/node/languagePacks');
nlsConfiguration = await getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale, osLocale);
return nlsConfiguration ?? { locale: 'en', osLocale, availableLanguages: {} };
const { resolveNLSConfiguration } = require('./vs/base/node/nls');
return resolveNLSConfiguration({
userLocale,
osLocale,
commit: product.commit,
userDataPath,
nlsMetadataPath: __dirname
});
}
/**
@ -689,7 +704,7 @@ function getUserDefinedLocale(argvConfig) {
return locale.toLowerCase(); // a directly provided --locale always wins
}
return argvConfig.locale && typeof argvConfig.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined;
return typeof argvConfig?.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined;
}
//#endregion

View File

@ -4,18 +4,36 @@
*--------------------------------------------------------------------------------------------*/
// @ts-check
'use strict';
/**
* @import { IProductConfiguration } from './vs/base/common/product'
*/
const path = require('path');
/** @type {Partial<IProductConfiguration>} */
// @ts-ignore
const product = require('../product.json');
const { resolveNLSConfiguration } = require('./vs/base/node/nls');
// Keep bootstrap-amd.js from redefining 'fs'.
delete process.env['ELECTRON_RUN_AS_NODE'];
async function start() {
if (process.env['VSCODE_DEV']) {
// When running out of sources, we need to load node modules from remote/node_modules,
// which are compiled against nodejs, not electron
process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'] = process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'] || path.join(__dirname, '..', 'remote', 'node_modules');
require('./bootstrap-node').injectNodeModuleLookupPath(process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']);
} else {
delete process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'];
// Keep bootstrap-amd.js from redefining 'fs'.
delete process.env['ELECTRON_RUN_AS_NODE'];
// NLS
const nlsConfiguration = await resolveNLSConfiguration({ userLocale: 'en', osLocale: 'en', commit: product.commit, userDataPath: '', nlsMetadataPath: __dirname });
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfiguration); // required for `bootstrap-amd` to pick up NLS messages
if (process.env['VSCODE_DEV']) {
// When running out of sources, we need to load node modules from remote/node_modules,
// which are compiled against nodejs, not electron
process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'] = process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'] || path.join(__dirname, '..', 'remote', 'node_modules');
require('./bootstrap-node').injectNodeModuleLookupPath(process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']);
} else {
delete process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'];
}
require('./bootstrap-amd').load('vs/server/node/server.cli');
}
require('./bootstrap-amd').load('vs/server/node/server.cli');
start();

View File

@ -4,6 +4,12 @@
*--------------------------------------------------------------------------------------------*/
// @ts-check
'use strict';
/**
* @import { IProductConfiguration } from './vs/base/common/product'
* @import { INLSConfiguration } from './vs/nls'
*/
/**
* @import { IServerAPI } from './vs/server/node/remoteExtensionHostAgentServer'
@ -11,9 +17,12 @@
const perf = require('./vs/base/common/performance');
const performance = require('perf_hooks').performance;
/** @type {Partial<IProductConfiguration>} */
// @ts-ignore
const product = require('../product.json');
const readline = require('readline');
const http = require('http');
const { resolveNLSConfiguration } = require('./vs/base/node/nls');
perf.mark('code/server/start');
// @ts-ignore
@ -42,8 +51,10 @@ async function start() {
const shouldSpawnCli = parsedArgs.help || parsedArgs.version || extensionLookupArgs.some(a => !!parsedArgs[a]) || (extensionInstallArgs.some(a => !!parsedArgs[a]) && !parsedArgs['start-server']);
const nlsConfiguration = await resolveNLSConfiguration({ userLocale: 'en', osLocale: 'en', commit: product.commit, userDataPath: '', nlsMetadataPath: __dirname });
if (shouldSpawnCli) {
loadCode().then((mod) => {
loadCode(nlsConfiguration).then((mod) => {
mod.spawnCli();
});
return;
@ -56,7 +67,7 @@ async function start() {
/** @returns {Promise<IServerAPI>} */
const getRemoteExtensionHostAgentServer = () => {
if (!_remoteExtensionHostAgentServerPromise) {
_remoteExtensionHostAgentServerPromise = loadCode().then(async (mod) => {
_remoteExtensionHostAgentServerPromise = loadCode(nlsConfiguration).then(async (mod) => {
const server = await mod.createServer(address);
_remoteExtensionHostAgentServer = server;
return server;
@ -249,13 +260,19 @@ async function findFreePort(host, start, end) {
return undefined;
}
/** @returns { Promise<typeof import('./vs/server/node/server.main')> } */
function loadCode() {
/**
* @param {INLSConfiguration} nlsConfiguration
* @returns { Promise<typeof import('./vs/server/node/server.main')> }
*/
function loadCode(nlsConfiguration) {
return new Promise((resolve, reject) => {
const path = require('path');
delete process.env['ELECTRON_RUN_AS_NODE']; // Keep bootstrap-amd.js from redefining 'fs'.
/** @type {INLSConfiguration} */
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfiguration); // required for `bootstrap-amd` to pick up NLS messages
// See https://github.com/microsoft/vscode-remote-release/issues/6543
// We would normally install a SIGPIPE listener in bootstrap.js
// But in certain situations, the console itself can be in a broken pipe state
@ -308,5 +325,4 @@ function prompt(question) {
});
}
start();

View File

@ -19,6 +19,7 @@
"typings/require.d.ts",
"typings/thenable.d.ts",
"typings/vscode-globals-product.d.ts",
"typings/vscode-globals-nls.d.ts",
"vs/loader.d.ts",
"vs/monaco.d.ts",
"vs/editor/*",

36
src/typings/vscode-globals-nls.d.ts vendored Normal file
View File

@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// AMD2ESM mirgation relevant
/**
* NLS Globals: these need to be defined in all contexts that make
* use of our `nls.localize` and `nls.localize2` functions. This includes:
* - Electron main process
* - Electron window (renderer) process
* - Utility Process
* - Node.js
* - Browser
* - Web worker
*
* That is because during build time we strip out all english strings from
* the resulting JS code and replace it with a <number> that is then looked
* up from the `_VSCODE_NLS_MESSAGES` array.
*/
declare global {
/**
* All NLS messages produced by `localize` and `localize2` calls
* under `src/vs` translated to the language as indicated by
* `_VSCODE_NLS_LANGUAGE`.
*/
var _VSCODE_NLS_MESSAGES: string[];
/**
* The actual language of the NLS messages (e.g. 'en', de' or 'pt-br').
*/
var _VSCODE_NLS_LANGUAGE: string | undefined;
}
// fake export to make global work
export { }

View File

@ -50,27 +50,35 @@ export function getWorkerBootstrapUrl(scriptPath: string, label: string): string
if (/^((http:)|(https:)|(file:))/.test(scriptPath) && scriptPath.substring(0, globalThis.origin.length) !== globalThis.origin) {
// this is the cross-origin case
// i.e. the webpage is running at a different origin than where the scripts are loaded from
const myPath = 'vs/base/worker/defaultWorkerFactory.js';
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321
const js = `/*${label}*/globalThis.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });importScripts(ttPolicy?.createScriptURL('${scriptPath}') ?? '${scriptPath}');/*${label}*/`;
const blob = new Blob([js], { type: 'application/javascript' });
return URL.createObjectURL(blob);
}
const start = scriptPath.lastIndexOf('?');
const end = scriptPath.lastIndexOf('#', start);
const params = start > 0
? new URLSearchParams(scriptPath.substring(start + 1, ~end ? end : undefined))
: new URLSearchParams();
COI.addSearchParam(params, true, true);
const search = params.toString();
if (!search) {
return `${scriptPath}#${label}`;
} else {
return `${scriptPath}?${params.toString()}#${label}`;
const start = scriptPath.lastIndexOf('?');
const end = scriptPath.lastIndexOf('#', start);
const params = start > 0
? new URLSearchParams(scriptPath.substring(start + 1, ~end ? end : undefined))
: new URLSearchParams();
COI.addSearchParam(params, true, true);
const search = params.toString();
if (!search) {
scriptPath = `${scriptPath}#${label}`;
} else {
scriptPath = `${scriptPath}?${params.toString()}#${label}`;
}
}
const factoryModuleId = 'vs/base/worker/defaultWorkerFactory.js';
const workerBaseUrl = require.toUrl(factoryModuleId).slice(0, -factoryModuleId.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321
const blob = new Blob([[
`/*${label}*/`,
`globalThis.MonacoEnvironment = { baseUrl: '${workerBaseUrl}' };`,
// VSCODE_GLOBALS: NLS
`globalThis._VSCODE_NLS_MESSAGES = ${JSON.stringify(globalThis._VSCODE_NLS_MESSAGES)};`,
`globalThis._VSCODE_NLS_LANGUAGE = ${JSON.stringify(globalThis._VSCODE_NLS_LANGUAGE)};`,
`const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });`,
`importScripts(ttPolicy?.createScriptURL('${scriptPath}') ?? '${scriptPath}');`,
`/*${label}*/`
].join('')], { type: 'application/javascript' });
return URL.createObjectURL(blob);
}
// ESM-comment-end
@ -136,8 +144,6 @@ class WebWorker extends Disposable implements IWorker {
}
});
}
}
export class DefaultWorkerFactory implements IWorkerFactory {

View File

@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
export const LANGUAGE_DEFAULT = 'en';
@ -22,13 +23,6 @@ let _platformLocale: string = LANGUAGE_DEFAULT;
let _translationsConfigFile: string | undefined = undefined;
let _userAgent: string | undefined = undefined;
interface NLSConfig {
locale: string;
osLocale: string;
availableLanguages: { [key: string]: string };
_translationsConfigFile: string;
}
export interface IProcessEnvironment {
[key: string]: string | undefined;
}
@ -89,13 +83,11 @@ if (typeof nodeProcess === 'object') {
const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG'];
if (rawNlsConfig) {
try {
const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
const resolved = nlsConfig.availableLanguages['*'];
_locale = nlsConfig.locale;
const nlsConfig: nls.INLSConfiguration = JSON.parse(rawNlsConfig);
_locale = nlsConfig.userLocale;
_platformLocale = nlsConfig.osLocale;
// VSCode's default language is 'en'
_language = resolved ? resolved : LANGUAGE_DEFAULT;
_translationsConfigFile = nlsConfig._translationsConfigFile;
_language = nlsConfig.resolvedLanguage || LANGUAGE_DEFAULT;
_translationsConfigFile = nlsConfig.languagePack?.translationsConfigFile;
} catch (e) {
}
}
@ -111,18 +103,10 @@ else if (typeof navigator === 'object' && !isElectronRenderer) {
_isLinux = _userAgent.indexOf('Linux') >= 0;
_isMobile = _userAgent?.indexOf('Mobi') >= 0;
_isWeb = true;
const configuredLocale = nls.getConfiguredDefaultLocale(
// This call _must_ be done in the file that calls `nls.getConfiguredDefaultLocale`
// to ensure that the NLS AMD Loader plugin has been loaded and configured.
// This is because the loader plugin decides what the default locale is based on
// how it's able to resolve the strings.
nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_')
);
_locale = configuredLocale || LANGUAGE_DEFAULT;
_language = _locale;
_platformLocale = navigator.language;
// VSCODE_GLOBALS: NLS
_language = globalThis._VSCODE_NLS_LANGUAGE || LANGUAGE_DEFAULT;
_locale = navigator.language.toLowerCase();
_platformLocale = _locale;
}
// Unknown environment
@ -178,7 +162,7 @@ export const userAgent = _userAgent;
/**
* The language used for the user interface. The format of
* the string is all lower case (e.g. zh-tw for Traditional
* Chinese)
* Chinese or de for German)
*/
export const language = _language;
@ -204,15 +188,16 @@ export namespace Language {
}
/**
* The OS locale or the locale specified by --locale. The format of
* the string is all lower case (e.g. zh-tw for Traditional
* Chinese). The UI is not necessarily shown in the provided locale.
* Desktop: The OS locale or the locale specified by --locale or `argv.json`.
* Web: matches `platformLocale`.
*
* The UI is not necessarily shown in the provided locale.
*/
export const locale = _locale;
/**
* This will always be set to the OS/browser's locale regardless of
* what was specified by --locale. The format of the string is all
* what was specified otherwise. The format of the string is all
* lower case (e.g. zh-tw for Traditional Chinese). The UI is not
* necessarily shown in the provided locale.
*/

View File

@ -1,25 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface NLSConfiguration {
locale: string;
osLocale: string;
availableLanguages: {
[key: string]: string;
};
pseudo?: boolean;
_languagePackSupport?: boolean;
}
export interface InternalNLSConfiguration extends NLSConfiguration {
_languagePackId: string;
_translationsConfigFile: string;
_cacheRoot: string;
_resolvedLanguagePackCoreLocation: string;
_corruptedFile: string;
_languagePackSupport?: boolean;
}
export function getNLSConfiguration(commit: string | undefined, userDataPath: string, metaDataFile: string, locale: string, osLocale: string): Promise<NLSConfiguration>;

View File

@ -1,264 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path="../../../typings/require.d.ts" />
//@ts-check
(function () {
'use strict';
/**
* @param {typeof import('path')} path
* @param {typeof import('fs')} fs
* @param {typeof import('../common/performance')} perf
*/
function factory(path, fs, perf) {
/**
* @param {string} file
* @returns {Promise<boolean>}
*/
function exists(file) {
return new Promise(c => fs.exists(file, c));
}
/**
* @param {string} file
* @returns {Promise<void>}
*/
function touch(file) {
return new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); });
}
/**
* @param {string} dir
* @returns {Promise<string>}
*/
function mkdirp(dir) {
return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
}
/**
* @param {string} location
* @returns {Promise<void>}
*/
function rimraf(location) {
return new Promise((c, e) => fs.rm(location, { recursive: true, force: true, maxRetries: 3 }, err => err ? e(err) : c()));
}
/**
* @param {string} file
* @returns {Promise<string>}
*/
function readFile(file) {
return new Promise((c, e) => fs.readFile(file, 'utf8', (err, data) => err ? e(err) : c(data)));
}
/**
* @param {string} file
* @param {string} content
* @returns {Promise<void>}
*/
function writeFile(file, content) {
return new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
}
/**
* @param {string} userDataPath
* @returns {Promise<object | undefined>}
*/
async function getLanguagePackConfigurations(userDataPath) {
const configFile = path.join(userDataPath, 'languagepacks.json');
try {
return JSON.parse(await readFile(configFile));
} catch (err) {
// Do nothing. If we can't read the file we have no
// language pack config.
}
return undefined;
}
/**
* @param {object} config
* @param {string | undefined} locale
*/
function resolveLanguagePackLocale(config, locale) {
try {
while (locale) {
if (config[locale]) {
return locale;
} else {
const index = locale.lastIndexOf('-');
if (index > 0) {
locale = locale.substring(0, index);
} else {
return undefined;
}
}
}
} catch (err) {
console.error('Resolving language pack configuration failed.', err);
}
return undefined;
}
/**
* @param {string | undefined} commit
* @param {string} userDataPath
* @param {string} metaDataFile
* @param {string} locale
* @param {string} osLocale
* @returns {Promise<import('./languagePacks').NLSConfiguration>}
*/
function getNLSConfiguration(commit, userDataPath, metaDataFile, locale, osLocale) {
const defaultResult = function (locale) {
perf.mark('code/didGenerateNls');
return Promise.resolve({ locale, osLocale, availableLanguages: {} });
};
perf.mark('code/willGenerateNls');
if (locale === 'pseudo') {
return Promise.resolve({ locale, osLocale, availableLanguages: {}, pseudo: true });
}
if (process.env['VSCODE_DEV']) {
return Promise.resolve({ locale, osLocale, availableLanguages: {} });
}
// We have a built version so we have extracted nls file. Try to find
// the right file to use.
// Check if we have an English or English US locale. If so fall to default since that is our
// English translation (we don't ship *.nls.en.json files)
if (locale && (locale === 'en' || locale === 'en-us')) {
return Promise.resolve({ locale, osLocale, availableLanguages: {} });
}
const initialLocale = locale;
try {
if (!commit) {
return defaultResult(initialLocale);
}
return getLanguagePackConfigurations(userDataPath).then(configs => {
if (!configs) {
return defaultResult(initialLocale);
}
const resolvedLocale = resolveLanguagePackLocale(configs, locale);
if (!resolvedLocale) {
return defaultResult(initialLocale);
}
locale = resolvedLocale;
const packConfig = configs[locale];
let mainPack;
if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') {
return defaultResult(initialLocale);
}
return exists(mainPack).then(fileExists => {
if (!fileExists) {
return defaultResult(initialLocale);
}
const packId = packConfig.hash + '.' + locale;
const cacheRoot = path.join(userDataPath, 'clp', packId);
const coreLocation = path.join(cacheRoot, commit);
const translationsConfigFile = path.join(cacheRoot, 'tcf.json');
const corruptedFile = path.join(cacheRoot, 'corrupted.info');
const result = {
locale: initialLocale,
osLocale,
availableLanguages: { '*': locale },
_languagePackId: packId,
_translationsConfigFile: translationsConfigFile,
_cacheRoot: cacheRoot,
_resolvedLanguagePackCoreLocation: coreLocation,
_corruptedFile: corruptedFile
};
return exists(corruptedFile).then(corrupted => {
// The nls cache directory is corrupted.
let toDelete;
if (corrupted) {
toDelete = rimraf(cacheRoot);
} else {
toDelete = Promise.resolve(undefined);
}
return toDelete.then(() => {
return exists(coreLocation).then(fileExists => {
if (fileExists) {
// We don't wait for this. No big harm if we can't touch
touch(coreLocation).catch(() => { });
perf.mark('code/didGenerateNls');
return result;
}
return mkdirp(coreLocation).then(() => {
return Promise.all([readFile(metaDataFile), readFile(mainPack)]);
}).then(values => {
const metadata = JSON.parse(values[0]);
const packData = JSON.parse(values[1]).contents;
const bundles = Object.keys(metadata.bundles);
const writes = [];
for (const bundle of bundles) {
const modules = metadata.bundles[bundle];
const target = Object.create(null);
for (const module of modules) {
const keys = metadata.keys[module];
const defaultMessages = metadata.messages[module];
const translations = packData[module];
let targetStrings;
if (translations) {
targetStrings = [];
for (let i = 0; i < keys.length; i++) {
const elem = keys[i];
const key = typeof elem === 'string' ? elem : elem.key;
let translatedMessage = translations[key];
if (translatedMessage === undefined) {
translatedMessage = defaultMessages[i];
}
targetStrings.push(translatedMessage);
}
} else {
targetStrings = defaultMessages;
}
target[module] = targetStrings;
}
writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target)));
}
writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations)));
return Promise.all(writes);
}).then(() => {
perf.mark('code/didGenerateNls');
return result;
}).catch(err => {
console.error('Generating translation files failed.', err);
return defaultResult(locale);
});
});
});
});
});
});
} catch (err) {
console.error('Generating translation files failed.', err);
return defaultResult(locale);
}
}
return {
getNLSConfiguration
};
}
if (typeof define === 'function') {
// amd
define(['path', 'fs', 'vs/base/common/performance'], function (/** @type {typeof import('path')} */ path, /** @type {typeof import('fs')} */ fs, /** @type {typeof import('../common/performance')} */ perf) { return factory(path, fs, perf); });
} else if (typeof module === 'object' && typeof module.exports === 'object') {
const path = require('path');
const fs = require('fs');
const perf = require('../common/performance');
module.exports = factory(path, fs, perf);
} else {
throw new Error('Unknown context');
}
}());

38
src/vs/base/node/nls.d.ts vendored Normal file
View File

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { INLSConfiguration } from 'vs/nls';
export interface IResolveNLSConfigurationContext {
/**
* Location where `nls.messages.json` and `nls.keys.json` are stored.
*/
readonly nlsMetadataPath: string;
/**
* Path to the user data directory. Used as a cache for
* language packs converted to the format we need.
*/
readonly userDataPath: string;
/**
* Commit of the running application. Can be `undefined`
* when not built.
*/
readonly commit: string | undefined;
/**
* Locale as defined in `argv.json` or `app.getLocale()`.
*/
readonly userLocale: string;
/**
* Locale as defined by the OS (e.g. `app.getPreferredSystemLanguages()`).
*/
readonly osLocale: string;
}
export function resolveNLSConfiguration(context: IResolveNLSConfigurationContext): Promise<INLSConfiguration>;

288
src/vs/base/node/nls.js Normal file
View File

@ -0,0 +1,288 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path="../../../typings/require.d.ts" />
//@ts-check
'use strict';
/**
* @import { INLSConfiguration, ILanguagePacks } from '../../nls'
* @import { IResolveNLSConfigurationContext } from './nls'
*/
(function () {
/**
* @param {typeof import('path')} path
* @param {typeof import('fs')} fs
* @param {typeof import('../common/performance')} perf
*/
function factory(path, fs, perf) {
//#region fs helpers
/**
* @param {string} path
*/
async function exists(path) {
try {
await fs.promises.access(path);
return true;
} catch {
return false;
}
}
/**
* @param {string} path
*/
function touch(path) {
const date = new Date();
return fs.promises.utimes(path, date, date);
}
/**
* @param {string} path
*/
function mkdirp(path) {
return fs.promises.mkdir(path, { recursive: true });
}
/**
* @param {string} path
*/
function rimraf(path) {
return fs.promises.rm(path, { recursive: true, force: true, maxRetries: 3 });
}
/**
* @param {string} path
*/
function readFile(path) {
return fs.promises.readFile(path, 'utf-8');
}
/**
* @param {string} path
* @param {string} content
*/
function writeFile(path, content) {
return fs.promises.writeFile(path, content, 'utf-8');
}
//#endregion
/**
* The `languagepacks.json` file is a JSON file that contains all metadata
* about installed language extensions per language. Specifically, for
* core (`vscode`) and all extensions it supports, it points to the related
* translation files.
*
* The file is updated whenever a new language pack is installed or removed.
*
* @param {string} userDataPath
* @returns {Promise<ILanguagePacks | undefined>}
*/
async function getLanguagePackConfigurations(userDataPath) {
const configFile = path.join(userDataPath, 'languagepacks.json');
try {
return JSON.parse(await readFile(configFile));
} catch (err) {
return undefined; // Do nothing. If we can't read the file we have no language pack config.
}
}
/**
* @param {ILanguagePacks} languagePacks
* @param {string | undefined} locale
*/
function resolveLanguagePackLanguage(languagePacks, locale) {
try {
while (locale) {
if (languagePacks[locale]) {
return locale;
}
const index = locale.lastIndexOf('-');
if (index > 0) {
locale = locale.substring(0, index);
} else {
return undefined;
}
}
} catch (error) {
console.error('Resolving language pack configuration failed.', error);
}
return undefined;
}
/**
* @param {string} userLocale
* @param {string} osLocale
* @param {string} nlsMetadataPath
* @returns {INLSConfiguration}
*/
function defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath) {
perf.mark('code/didGenerateNls');
return {
userLocale,
osLocale,
resolvedLanguage: 'en',
defaultMessagesFile: path.join(nlsMetadataPath, 'nls.messages.json'),
// NLS: below 2 are a relic from old times only used by vscode-nls and deprecated
locale: 'en',
availableLanguages: {}
};
}
/**
* @param {IResolveNLSConfigurationContext} context
* @returns {Promise<INLSConfiguration>}
*/
async function resolveNLSConfiguration({ userLocale, osLocale, userDataPath, commit, nlsMetadataPath }) {
perf.mark('code/willGenerateNls');
if (
process.env['VSCODE_DEV'] ||
userLocale === 'pseudo' ||
userLocale === 'en' || userLocale === 'en-us' ||
!commit ||
!userDataPath
) {
return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath);
}
try {
const languagePacks = await getLanguagePackConfigurations(userDataPath);
if (!languagePacks) {
return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath);
}
const resolvedLanguage = resolveLanguagePackLanguage(languagePacks, userLocale);
if (!resolvedLanguage) {
return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath);
}
const languagePack = languagePacks[resolvedLanguage];
const mainLanguagePackPath = languagePack?.translations?.['vscode'];
if (
!languagePack ||
typeof languagePack.hash !== 'string' ||
!languagePack.translations ||
typeof mainLanguagePackPath !== 'string' ||
!(await exists(mainLanguagePackPath))
) {
return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath);
}
const languagePackId = `${languagePack.hash}.${resolvedLanguage}`;
const globalLanguagePackCachePath = path.join(userDataPath, 'clp', languagePackId);
const commitLanguagePackCachePath = path.join(globalLanguagePackCachePath, commit);
const languagePackMessagesFile = path.join(commitLanguagePackCachePath, 'nls.messages.json');
const translationsConfigFile = path.join(globalLanguagePackCachePath, 'tcf.json');
const languagePackCorruptMarkerFile = path.join(globalLanguagePackCachePath, 'corrupted.info');
if (await exists(languagePackCorruptMarkerFile)) {
await rimraf(globalLanguagePackCachePath); // delete corrupted cache folder
}
/** @type {INLSConfiguration} */
const result = {
userLocale,
osLocale,
resolvedLanguage,
defaultMessagesFile: path.join(nlsMetadataPath, 'nls.messages.json'),
languagePack: {
translationsConfigFile,
messagesFile: languagePackMessagesFile,
corruptMarkerFile: languagePackCorruptMarkerFile
},
// NLS: below properties are a relic from old times only used by vscode-nls and deprecated
locale: userLocale,
availableLanguages: { '*': resolvedLanguage },
_languagePackId: languagePackId,
_languagePackSupport: true,
_translationsConfigFile: translationsConfigFile,
_cacheRoot: globalLanguagePackCachePath,
_resolvedLanguagePackCoreLocation: commitLanguagePackCachePath,
_corruptedFile: languagePackCorruptMarkerFile
};
if (await exists(commitLanguagePackCachePath)) {
touch(commitLanguagePackCachePath).catch(() => { }); // We don't wait for this. No big harm if we can't touch
perf.mark('code/didGenerateNls');
return result;
}
/** @type {[unknown, Array<[string, string[]]>, string[], { contents: Record<string, Record<string, string>> }]} */
// ^moduleId ^nlsKeys ^moduleId ^nlsKey ^nlsValue
const [
,
nlsDefaultKeys,
nlsDefaultMessages,
nlsPackdata
] = await Promise.all([
mkdirp(commitLanguagePackCachePath),
JSON.parse(await readFile(path.join(nlsMetadataPath, 'nls.keys.json'))),
JSON.parse(await readFile(path.join(nlsMetadataPath, 'nls.messages.json'))),
JSON.parse(await readFile(mainLanguagePackPath))
]);
/** @type {string[]} */
const nlsResult = [];
// We expect NLS messages to be in a flat array in sorted order as they
// where produced during build time. We use `nls.keys.json` to know the
// right order and then lookup the related message from the translation.
// If a translation does not exist, we fallback to the default message.
let nlsIndex = 0;
for (const [moduleId, nlsKeys] of nlsDefaultKeys) {
const moduleTranslations = nlsPackdata.contents[moduleId];
for (const nlsKey of nlsKeys) {
nlsResult.push(moduleTranslations?.[nlsKey] || nlsDefaultMessages[nlsIndex]);
nlsIndex++;
}
}
await Promise.all([
writeFile(languagePackMessagesFile, JSON.stringify(nlsResult)),
writeFile(translationsConfigFile, JSON.stringify(languagePack.translations))
]);
perf.mark('code/didGenerateNls');
return result;
} catch (error) {
console.error('Generating translation files failed.', error);
}
return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath);
}
return {
resolveNLSConfiguration
};
}
if (typeof define === 'function') {
// amd
define(['path', 'fs', 'vs/base/common/performance'], function (/** @type {typeof import('path')} */ path, /** @type {typeof import('fs')} */ fs, /** @type {typeof import('../common/performance')} */ perf) { return factory(path, fs, perf); });
} else if (typeof module === 'object' && typeof module.exports === 'object') {
// commonjs
const path = require('path');
const fs = require('fs');
const perf = require('../common/performance');
module.exports = factory(path, fs, perf);
} else {
throw new Error('vs/base/node/nls defined in UNKNOWN context (neither requirejs or commonjs)');
}
})();

View File

@ -53,4 +53,21 @@ export interface ISandboxConfiguration {
* Location of V8 code cache.
*/
codeCachePath?: string;
/**
* NLS support
*/
nls: {
/**
* All NLS messages produced by `localize` and `localize2` calls
* under `src/vs`.
*/
messages: string[];
/**
* The actual language of the NLS messages (e.g. 'en', de' or 'pt-br').
*/
language: string | undefined;
};
}

View File

@ -36,25 +36,14 @@
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/webPackagePaths.js"></script>
<script>
// Packages
const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location.origin).toString();
Object.keys(self.webPackagePaths).map(function (key, index) {
self.webPackagePaths[key] = `${baseUrl}/node_modules/${key}/${self.webPackagePaths[key]}`;
});
// Set up nls if the user is not using the default language (English)
const nlsConfig = {};
// Normalize locale to lowercase because translationServiceUrl is case-sensitive.
// ref: https://github.com/microsoft/vscode/issues/187795
const locale = localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase();
if (!locale.startsWith('en')) {
nlsConfig['vs/nls'] = {
availableLanguages: {
'*': locale
},
translationServiceUrl: '{{WORKBENCH_NLS_BASE_URL}}'
};
}
// AMD Loader
require.config({
baseUrl: `${baseUrl}/out`,
recordStats: true,
@ -66,14 +55,16 @@
throw new Error(`Invalid script url: ${value}`)
}
}),
paths: self.webPackagePaths,
...nlsConfig
paths: self.webPackagePaths
});
</script>
<script>
performance.mark('code/willLoadWorkbenchMain');
</script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.main.nls.js"></script>
<!-- always ensure built in english NLS messages -->
<script src="{{WORKBENCH_NLS_FALLBACK_URL}}"></script>
<!-- attempt to load NLS messages in case non-english -->
<script src="{{WORKBENCH_NLS_URL}}"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.main.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/code/browser/workbench/workbench.js"></script>
</html>

View File

@ -10,13 +10,12 @@ import { hostname, release } from 'os';
import { VSBuffer } from 'vs/base/common/buffer';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { isEqualOrParent } from 'vs/base/common/extpath';
import { Event } from 'vs/base/common/event';
import { parse } from 'vs/base/common/jsonc';
import { getPathLabel } from 'vs/base/common/labels';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network';
import { isAbsolute, join, posix } from 'vs/base/common/path';
import { join, posix } from 'vs/base/common/path';
import { IProcessEnvironment, isLinux, isLinuxSnap, isMacintosh, isWindows, OS } from 'vs/base/common/platform';
import { assertType } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
@ -496,24 +495,6 @@ export class CodeApplication extends Disposable {
return this.resolveShellEnvironment(args, env, false);
});
validatedIpcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => {
const uri = this.validateNlsPath([path]);
if (!uri || typeof data !== 'string') {
throw new Error('Invalid operation (vscode:writeNlsFile)');
}
return this.fileService.writeFile(uri, VSBuffer.fromString(data));
});
validatedIpcMain.handle('vscode:readNlsFile', async (event, ...paths: unknown[]) => {
const uri = this.validateNlsPath(paths);
if (!uri) {
throw new Error('Invalid operation (vscode:readNlsFile)');
}
return (await this.fileService.readFile(uri)).value.toString();
});
validatedIpcMain.on('vscode:toggleDevTools', event => event.sender.toggleDevTools());
validatedIpcMain.on('vscode:openDevTools', event => event.sender.openDevTools());
@ -529,26 +510,6 @@ export class CodeApplication extends Disposable {
//#endregion
}
private validateNlsPath(pathSegments: unknown[]): URI | undefined {
let path: string | undefined = undefined;
for (const pathSegment of pathSegments) {
if (typeof pathSegment === 'string') {
if (typeof path !== 'string') {
path = pathSegment;
} else {
path = join(path, pathSegment);
}
}
}
if (typeof path !== 'string' || !isAbsolute(path) || !isEqualOrParent(path, this.environmentMainService.cachedLanguagesPath, !isLinux)) {
return undefined;
}
return URI.file(path);
}
private onUnexpectedError(error: Error): void {
if (error) {

View File

@ -20,13 +20,12 @@
// Add a perf entry right from the top
performance.mark('code/didStartRenderer');
// Load workbench main JS, CSS and NLS all in parallel. This is an
// Load workbench main JS and CSS all in parallel. This is an
// optimization to prevent a waterfall of loading to happen, because
// we know for a fact that workbench.desktop.main will depend on
// the related CSS and NLS counterparts.
// the related CSS counterpart.
bootstrapWindow.load([
'vs/workbench/workbench.desktop.main',
'vs/nls!vs/workbench/workbench.desktop.main',
'vs/css!vs/workbench/workbench.desktop.main'
],
function (desktopMain, configuration) {

View File

@ -3,27 +3,8 @@
* 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';
interface INLSPluginConfig {
availableLanguages?: INLSPluginConfigAvailableLanguages;
loadBundle?: BundleLoader;
translationServiceUrl?: string;
}
export interface INLSPluginConfigAvailableLanguages {
'*'?: string;
[module: string]: string | undefined;
}
interface BundleLoader {
(bundle: string, locale: string | null, cb: (err: Error, messages: string[] | IBundledStrings) => void): void;
}
interface IBundledStrings {
[moduleId: string]: string[];
}
// VSCODE_GLOBALS: NLS
const isPseudo = globalThis._VSCODE_NLS_LANGUAGE === 'pseudo' || (typeof document !== 'undefined' && document.location && document.location.hash.indexOf('pseudo=true') >= 0);
export interface ILocalizeInfo {
key: string;
@ -35,30 +16,6 @@ export interface ILocalizedString {
value: string;
}
interface ILocalizeFunc {
(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string;
(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string;
}
interface IBoundLocalizeFunc {
(idx: number, defaultValue: null): string;
}
interface ILocalize2Func {
(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString;
(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString;
}
interface IBoundLocalize2Func {
(idx: number, defaultValue: string): ILocalizedString;
}
interface IConsumerAPI {
localize: ILocalizeFunc | IBoundLocalizeFunc;
localize2: ILocalize2Func | IBoundLocalize2Func;
getConfiguredDefaultLocale(stringFromLocalizeCall: string): string | undefined;
}
function _format(message: string, args: (string | number | boolean | undefined | null)[]): string {
let result: string;
@ -86,49 +43,6 @@ function _format(message: string, args: (string | number | boolean | undefined |
return result;
}
function findLanguageForModule(config: INLSPluginConfigAvailableLanguages, name: string) {
let result = config[name];
if (result) {
return result;
}
result = config['*'];
if (result) {
return result;
}
return null;
}
function endWithSlash(path: string): string {
if (path.charAt(path.length - 1) === '/') {
return path;
}
return path + '/';
}
async function getMessagesFromTranslationsService(translationServiceUrl: string, language: string, name: string): Promise<string[] | IBundledStrings> {
const url = endWithSlash(translationServiceUrl) + endWithSlash(language) + 'vscode/' + endWithSlash(name);
const res = await fetch(url);
if (res.ok) {
const messages = await res.json() as string[] | IBundledStrings;
return messages;
}
throw new Error(`${res.status} - ${res.statusText}`);
}
function createScopedLocalize(scope: string[]): IBoundLocalizeFunc {
return function (idx: number, defaultValue: null) {
const restArgs = Array.prototype.slice.call(arguments, 2);
return _format(scope[idx], restArgs);
};
}
function createScopedLocalize2(scope: string[]): IBoundLocalize2Func {
return (idx: number, defaultValue: string, ...args) => ({
value: _format(scope[idx], args),
original: _format(defaultValue, args)
});
}
/**
* Marks a string to be localized. Returns the localized string.
*
@ -160,10 +74,30 @@ export function localize(key: string, message: string, ...args: (string | number
/**
* @skipMangle
*/
export function localize(data: ILocalizeInfo | string, message: string, ...args: (string | number | boolean | undefined | null)[]): string {
export function localize(data: ILocalizeInfo | string /* | number when built */, message: string /* | null when built */, ...args: (string | number | boolean | undefined | null)[]): string {
if (typeof data === 'number') {
return _format(lookupMessage(data, message), args);
}
return _format(message, args);
}
/**
* Only used when built: Looks up the message in the global NLS table.
* This table is being made available as a global through bootstrapping
* depending on the target context.
*/
function lookupMessage(index: number, fallback: string | null): string {
// VSCODE_GLOBALS: NLS
const message = globalThis._VSCODE_NLS_MESSAGES?.[index];
if (typeof message !== 'string') {
if (typeof fallback === 'string') {
return fallback;
}
throw new Error(`!!! NLS MISSING: ${index} !!!`);
}
return message;
}
/**
* Marks a string to be localized. Returns an {@linkcode ILocalizedString}
* which contains the localized string and the original string.
@ -197,123 +131,107 @@ export function localize2(key: string, message: string, ...args: (string | numbe
/**
* @skipMangle
*/
export function localize2(data: ILocalizeInfo | string, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString {
const original = _format(message, args);
return {
value: original,
original
};
}
/**
*
* @param stringFromLocalizeCall You must pass in a string that was returned from a `nls.localize()` call
* in order to ensure the loader plugin has been initialized before this function is called.
*/
export function getConfiguredDefaultLocale(stringFromLocalizeCall: string): string | undefined;
/**
* @skipMangle
*/
export function getConfiguredDefaultLocale(_: string): string | undefined {
// This returns undefined because this implementation isn't used and is overwritten by the loader
// when loaded.
return undefined;
}
/**
* @skipMangle
*/
export function setPseudoTranslation(value: boolean) {
isPseudo = value;
}
/**
* Invoked in a built product at run-time
* @skipMangle
*/
export function create(key: string, data: IBundledStrings & IConsumerAPI): IConsumerAPI {
return {
localize: createScopedLocalize(data[key]),
localize2: createScopedLocalize2(data[key]),
getConfiguredDefaultLocale: data.getConfiguredDefaultLocale ?? ((_: string) => undefined)
};
}
/**
* Invoked by the loader at run-time
* @skipMangle
*/
export function load(name: string, req: AMDLoader.IRelativeRequire, load: AMDLoader.IPluginLoadCallback, config: AMDLoader.IConfigurationOptions): void {
const pluginConfig: INLSPluginConfig = config['vs/nls'] ?? {};
if (!name || name.length === 0) {
// TODO: We need to give back the mangled names here
return load({
localize: localize,
localize2: localize2,
getConfiguredDefaultLocale: () => pluginConfig.availableLanguages?.['*']
} as IConsumerAPI);
}
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: string[] | IBundledStrings) => {
if (Array.isArray(messages)) {
(messages as any as IConsumerAPI).localize = createScopedLocalize(messages);
(messages as any as IConsumerAPI).localize2 = createScopedLocalize2(messages);
} else {
(messages as any as IConsumerAPI).localize = createScopedLocalize(messages[name]);
(messages as any as IConsumerAPI).localize2 = createScopedLocalize2(messages[name]);
}
(messages as any as IConsumerAPI).getConfiguredDefaultLocale = () => pluginConfig.availableLanguages?.['*'];
load(messages);
};
if (typeof pluginConfig.loadBundle === 'function') {
(pluginConfig.loadBundle as BundleLoader)(name, language, (err: Error, 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 () => {
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.
pluginConfig.availableLanguages ??= {};
pluginConfig.availableLanguages['*'] = genericLanguage;
return messagesLoaded(messages);
} catch (err) {
console.error(err);
return req([name + '.nls'], messagesLoaded);
}
}
})();
export function localize2(data: ILocalizeInfo | string /* | number when built */, originalMessage: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString {
let message: string;
if (typeof data === 'number') {
message = lookupMessage(data, originalMessage);
} else {
req([name + suffix], messagesLoaded, (err: Error) => {
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);
});
message = originalMessage;
}
const value = _format(message, args);
return {
value,
original: originalMessage === message ? value : _format(originalMessage, args)
};
}
export interface INLSLanguagePackConfiguration {
/**
* The path to the translations config file that contains pointers to
* all message bundles for `main` and extensions.
*/
readonly translationsConfigFile: string;
/**
* The path to the file containing the translations for this language
* pack as flat string array.
*/
readonly messagesFile: string;
/**
* The path to the file that can be used to signal a corrupt language
* pack, for example when reading the `messagesFile` fails. This will
* instruct the application to re-create the cache on next startup.
*/
readonly corruptMarkerFile: string;
}
export interface INLSConfiguration {
/**
* Locale as defined in `argv.json` or `app.getLocale()`.
*/
readonly userLocale: string;
/**
* Locale as defined by the OS (e.g. `app.getPreferredSystemLanguages()`).
*/
readonly osLocale: string;
/**
* The actual language of the UI that ends up being used considering `userLocale`
* and `osLocale`.
*/
readonly resolvedLanguage: string;
/**
* Defined if a language pack is used that is not the
* default english language pack. This requires a language
* pack to be installed as extension.
*/
readonly languagePack?: INLSLanguagePackConfiguration;
/**
* The path to the file containing the default english messages
* as flat string array. The file is only present in built
* versions of the application.
*/
readonly defaultMessagesFile: string;
/**
* Below properties are deprecated and only there to continue support
* for `vscode-nls` module that depends on them.
* Refs https://github.com/microsoft/vscode-nls/blob/main/src/node/main.ts#L36-L46
*/
/** @deprecated */
readonly locale: string;
/** @deprecated */
readonly availableLanguages: Record<string, string>;
/** @deprecated */
readonly _languagePackSupport?: boolean;
/** @deprecated */
readonly _languagePackId?: string;
/** @deprecated */
readonly _translationsConfigFile?: string;
/** @deprecated */
readonly _cacheRoot?: string;
/** @deprecated */
readonly _resolvedLanguagePackCoreLocation?: string;
/** @deprecated */
readonly _corruptedFile?: string;
}
export interface ILanguagePack {
readonly hash: string;
readonly label: string | undefined;
readonly extensions: {
readonly extensionIdentifier: { readonly id: string; readonly uuid?: string };
readonly version: string;
}[];
readonly translations: Record<string, string | undefined>;
}
export type ILanguagePacks = Record<string, ILanguagePack | undefined>;

View File

@ -19,9 +19,6 @@ export const IEnvironmentMainService = refineServiceDecorator<IEnvironmentServic
*/
export interface IEnvironmentMainService extends INativeEnvironmentService {
// --- NLS cache path
readonly cachedLanguagesPath: string;
// --- backup paths
readonly backupHome: string;
@ -44,9 +41,6 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
private _snapEnv: Record<string, string> = {};
@memoize
get cachedLanguagesPath(): string { return join(this.userDataPath, 'clp'); }
@memoize
get backupHome(): string { return join(this.userDataPath, 'Backups'); }

View File

@ -81,7 +81,12 @@ export class IssueMainService implements IIssueMainService {
arch: arch(),
release: release(),
},
product
product,
nls: {
// VSCODE_GLOBALS: NLS
messages: globalThis._VSCODE_NLS_MESSAGES,
language: globalThis._VSCODE_NLS_LANGUAGE
}
});
this.issueReporterWindow.loadURL(

View File

@ -153,7 +153,12 @@ export class ProcessMainService implements IProcessMainService {
windowId: this.processExplorerWindow.id,
userEnv: this.userEnv,
data,
product
product,
nls: {
// VSCODE_GLOBALS: NLS
messages: globalThis._VSCODE_NLS_MESSAGES,
language: globalThis._VSCODE_NLS_LANGUAGE
}
});
this.processExplorerWindow.loadURL(

View File

@ -1444,6 +1444,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
workspace: options.workspace,
userEnv: { ...this.initialUserEnv, ...options.userEnv },
nls: {
// VSCODE_GLOBALS: NLS
messages: globalThis._VSCODE_NLS_MESSAGES,
language: globalThis._VSCODE_NLS_LANGUAGE
},
filesToOpenOrCreate: options.filesToOpen?.filesToOpenOrCreate,
filesToDiff: options.filesToOpen?.filesToDiff,
filesToMerge: options.filesToOpen?.filesToMerge,

View File

@ -43,7 +43,7 @@ export async function buildUserEnvironment(startParamsEnv: { [key: string]: stri
...{
VSCODE_AMD_ENTRYPOINT: 'vs/workbench/api/node/extensionHostProcess',
VSCODE_HANDLES_UNCAUGHT_ERRORS: 'true',
VSCODE_NLS_CONFIG: JSON.stringify(nlsConfig, undefined, 0)
VSCODE_NLS_CONFIG: JSON.stringify(nlsConfig)
},
...startParamsEnv
};

View File

@ -14,7 +14,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { getNLSConfiguration, InternalNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
import { getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
export class ExtensionsScannerService extends AbstractExtensionsScannerService implements IExtensionsScannerService {
@ -38,9 +38,9 @@ export class ExtensionsScannerService extends AbstractExtensionsScannerService i
protected async getTranslations(language: string): Promise<Translations> {
const config = await getNLSConfiguration(language, this.nativeEnvironmentService.userDataPath);
if (InternalNLSConfiguration.is(config)) {
if (config.languagePack) {
try {
const content = await this.fileService.readFile(URI.file(config._translationsConfigFile));
const content = await this.fileService.readFile(URI.file(config.languagePack.translationsConfigFile));
return JSON.parse(content.value.toString());
} catch (err) { /* Ignore error */ }
}

View File

@ -3,46 +3,37 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import { FileAccess } from 'vs/base/common/network';
import * as path from 'vs/base/common/path';
import * as lp from 'vs/base/node/languagePacks';
import { join } from 'vs/base/common/path';
import type { INLSConfiguration } from 'vs/nls';
import { resolveNLSConfiguration } from 'vs/base/node/nls';
import { Promises } from 'vs/base/node/pfs';
import product from 'vs/platform/product/common/product';
const metaData = path.join(FileAccess.asFileUri('').fsPath, 'nls.metadata.json');
const _cache: Map<string, Promise<lp.NLSConfiguration>> = new Map();
const nlsMetadataPath = join(FileAccess.asFileUri('').fsPath);
const defaultMessagesFile = join(nlsMetadataPath, 'nls.messages.json');
const nlsConfigurationCache = new Map<string, Promise<INLSConfiguration>>();
function exists(file: string) {
return new Promise(c => fs.exists(file, c));
}
export async function getNLSConfiguration(language: string, userDataPath: string): Promise<INLSConfiguration> {
if (!product.commit || !(await Promises.exists(defaultMessagesFile))) {
return {
userLocale: 'en',
osLocale: 'en',
resolvedLanguage: 'en',
defaultMessagesFile,
export function getNLSConfiguration(language: string, userDataPath: string): Promise<lp.NLSConfiguration> {
return exists(metaData).then((fileExists) => {
if (!fileExists || !product.commit) {
// console.log(`==> MetaData or commit unknown. Using default language.`);
// The OS Locale on the remote side really doesn't matter, so we return the default locale
return Promise.resolve({ locale: 'en', osLocale: 'en', availableLanguages: {} });
}
const key = `${language}||${userDataPath}`;
let result = _cache.get(key);
if (!result) {
// The OS Locale on the remote side really doesn't matter, so we pass in the same language
result = lp.getNLSConfiguration(product.commit, userDataPath, metaData, language, language).then(value => {
if (InternalNLSConfiguration.is(value)) {
value._languagePackSupport = true;
}
return value;
});
_cache.set(key, result);
}
return result;
});
}
export namespace InternalNLSConfiguration {
export function is(value: lp.NLSConfiguration): value is lp.InternalNLSConfiguration {
const candidate: lp.InternalNLSConfiguration = value as lp.InternalNLSConfiguration;
return candidate && typeof candidate._languagePackId === 'string';
// NLS: below 2 are a relic from old times only used by vscode-nls and deprecated
locale: 'en',
availableLanguages: {}
};
}
const cacheKey = `${language}||${userDataPath}`;
let result = nlsConfigurationCache.get(cacheKey);
if (!result) {
result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: product.commit, userDataPath, nlsMetadataPath });
nlsConfigurationCache.set(cacheKey, result);
}
return result;
}

View File

@ -338,12 +338,23 @@ export class WebClientServer {
callbackRoute: this._callbackRoute
};
const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl;
const cookies = cookie.parse(req.headers.cookie || '');
const locale = cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en';
let WORKBENCH_NLS_BASE_URL: string | undefined;
let WORKBENCH_NLS_URL: string;
if (!locale.startsWith('en')) {
WORKBENCH_NLS_BASE_URL = `https://www.vscode-unpkg.net/nls/`;
WORKBENCH_NLS_URL = `${WORKBENCH_NLS_BASE_URL}${this._productService.commit}/${this._productService.version}/${locale}/nls.messages.js`; // TODO@bpasero make it a product.json thing
} else {
WORKBENCH_NLS_URL = ''; // fallback will apply
}
const values: { [key: string]: string } = {
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '',
WORKBENCH_WEB_BASE_URL: this._staticRoute,
WORKBENCH_NLS_BASE_URL: nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : '',
WORKBENCH_NLS_URL,
WORKBENCH_NLS_FALLBACK_URL: `${this._staticRoute}/out/nls.messages.js`
};
if (useTestResolver) {
@ -364,13 +375,13 @@ export class WebClientServer {
return void res.end('Not found');
}
const webWorkerExtensionHostIframeScriptSHA = 'sha256-75NYUUvf+5++1WbfCZOV3PSWxBhONpaxwx+mkOFRv/Y=';
const webWorkerExtensionHostIframeScriptSHA = 'sha256-V28GQnL3aYxbwgpV3yW1oJ+VKKe/PBSzWntNyH8zVXA=';
const cspDirectives = [
'default-src \'self\';',
'img-src \'self\' https: data: blob:;',
'media-src \'self\';',
`script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
`script-src 'self' 'unsafe-eval' ${WORKBENCH_NLS_BASE_URL ?? ''} ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
'child-src \'self\';',
`frame-src 'self' https://*.vscode-cdn.net data:;`,
'worker-src \'self\' data: blob:;',

View File

@ -181,6 +181,23 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
err.stack = stack;
return rejectBarrier(ExtensionHostExitCode.UnexpectedError, err);
}
if (event.data.type === 'vscode.bootstrap.nls') {
const factoryModuleId = 'vs/base/worker/workerMain.js';
const baseUrl = require.toUrl(factoryModuleId).slice(0, -factoryModuleId.length);
iframe.contentWindow!.postMessage({
type: event.data.type,
data: {
baseUrl,
workerUrl: require.toUrl(factoryModuleId),
nls: {
// VSCODE_GLOBALS: NLS
messages: globalThis._VSCODE_NLS_MESSAGES,
language: globalThis._VSCODE_NLS_LANGUAGE
}
}
}, '*');
return;
}
const { data } = event.data;
if (barrier.isOpen() || !(data instanceof MessagePort)) {
console.warn('UNEXPECTED message', event);

View File

@ -4,7 +4,7 @@
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
child-src 'self' data: blob:;
script-src 'self' 'unsafe-eval' 'sha256-75NYUUvf+5++1WbfCZOV3PSWxBhONpaxwx+mkOFRv/Y=' https:;
script-src 'self' 'unsafe-eval' 'sha256-V28GQnL3aYxbwgpV3yW1oJ+VKKe/PBSzWntNyH8zVXA=' https:;
connect-src 'self' https: wss: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:*;"/>
</head>
<body>
@ -66,13 +66,44 @@
}
function start() {
// Before we can load the worker, we need to get the current set of NLS
// configuration into this iframe. We ask the parent window to send it
// together with the necessary information to load the worker via Blob.
const bootstrapNlsType = 'vscode.bootstrap.nls';
self.onmessage = (event) => {
if (event.origin !== parentOrigin || event.data.type !== bootstrapNlsType) {
return;
}
const { data } = event.data;
createWorker(data.baseUrl, data.workerUrl, data.nls.messages, data.nls.language);
};
window.parent.postMessage({
vscodeWebWorkerExtHostId,
type: bootstrapNlsType
}, '*');
}
function createWorker(baseUrl, workerUrl, nlsMessages, nlsLanguage) {
try {
let workerUrl = '../../../../base/worker/workerMain.js';
if (globalThis.crossOriginIsolated) {
workerUrl += '?vscode-coi=2'; // COEP
}
const worker = new Worker(workerUrl, { name });
const blob = new Blob([[
`/*extensionHostWorker*/`,
`globalThis.MonacoEnvironment = { baseUrl: '${baseUrl}' };`,
// VSCODE_GLOBALS: NLS
`globalThis._VSCODE_NLS_MESSAGES = ${JSON.stringify(nlsMessages)};`,
`globalThis._VSCODE_NLS_LANGUAGE = ${JSON.stringify(nlsLanguage)};`,
`importScripts('${workerUrl}');`,
`/*extensionHostWorker*/`
].join('')], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob), { name });
worker.postMessage('vs/workbench/api/worker/extensionHostWorker');
const nestedWorkers = new Map();

View File

@ -14,11 +14,69 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/
import { CancellationToken } from 'vs/base/common/cancellation';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILogService } from 'vs/platform/log/common/log';
import { getCookieValue } from 'vs/base/browser/dom';
const localeStorage = new class LocaleStorage {
private static readonly LOCAL_STORAGE_LOCALE_KEY = 'vscode.nls.locale';
private static readonly LOCAL_STORAGE_EXTENSION_ID_KEY = 'vscode.nls.languagePackExtensionId';
constructor() {
this.migrateCookie(); // TODO@bpasero remove me eventually
}
private migrateCookie(): void {
const localeCookieValue = getCookieValue(LocaleStorage.LOCAL_STORAGE_LOCALE_KEY);
const localeStorageValue = localStorage.getItem(LocaleStorage.LOCAL_STORAGE_LOCALE_KEY);
if (
(typeof localeCookieValue !== 'string' && typeof localeStorageValue !== 'string') ||
(localeCookieValue === localeStorageValue)
) {
return; // already matching
}
if (typeof localeStorageValue === 'string') {
this.doSetLocaleToCookie(localeStorageValue);
} else {
this.doClearLocaleToCookie();
}
}
setLocale(locale: string): void {
localStorage.setItem(LocaleStorage.LOCAL_STORAGE_LOCALE_KEY, locale);
this.doSetLocaleToCookie(locale);
}
private doSetLocaleToCookie(locale: string): void {
document.cookie = `${LocaleStorage.LOCAL_STORAGE_LOCALE_KEY}=${locale};path=/;max-age=3153600000`;
}
clearLocale(): void {
localStorage.removeItem(LocaleStorage.LOCAL_STORAGE_LOCALE_KEY);
this.doClearLocaleToCookie();
}
private doClearLocaleToCookie(): void {
document.cookie = `${LocaleStorage.LOCAL_STORAGE_LOCALE_KEY}=;path=/;max-age=0`;
}
setExtensionId(extensionId: string): void {
localStorage.setItem(LocaleStorage.LOCAL_STORAGE_EXTENSION_ID_KEY, extensionId);
}
getExtensionId(): string | null {
return localStorage.getItem(LocaleStorage.LOCAL_STORAGE_EXTENSION_ID_KEY);
}
clearExtensionId(): void {
localStorage.removeItem(LocaleStorage.LOCAL_STORAGE_EXTENSION_ID_KEY);
}
};
export class WebLocaleService implements ILocaleService {
declare readonly _serviceBrand: undefined;
static readonly _LOCAL_STORAGE_EXTENSION_ID_KEY = 'vscode.nls.languagePackExtensionId';
static readonly _LOCAL_STORAGE_LOCALE_KEY = 'vscode.nls.locale';
constructor(
@IDialogService private readonly dialogService: IDialogService,
@ -32,13 +90,13 @@ export class WebLocaleService implements ILocaleService {
return;
}
if (locale) {
localStorage.setItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY, locale);
localeStorage.setLocale(locale);
if (languagePackItem.extensionId) {
localStorage.setItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY, languagePackItem.extensionId);
localeStorage.setExtensionId(languagePackItem.extensionId);
}
} else {
localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY);
localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY);
localeStorage.clearLocale();
localeStorage.clearExtensionId();
}
const restartDialog = await this.dialogService.confirm({
@ -54,8 +112,8 @@ export class WebLocaleService implements ILocaleService {
}
async clearLocalePreference(): Promise<void> {
localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY);
localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY);
localeStorage.clearLocale();
localeStorage.clearExtensionId();
if (Language.value() === navigator.language.toLowerCase()) {
return;
@ -87,7 +145,7 @@ class WebActiveLanguagePackService implements IActiveLanguagePackService {
if (language === LANGUAGE_DEFAULT) {
return undefined;
}
const extensionId = localStorage.getItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY);
const extensionId = localeStorage.getExtensionId();
if (extensionId) {
return extensionId;
}
@ -102,7 +160,7 @@ class WebActiveLanguagePackService implements IActiveLanguagePackService {
// Only install extensions that are published by Microsoft and start with vscode-language-pack for extra certainty
const extensionToInstall = tagResult.firstPage.find(e => e.publisher === 'MS-CEINTL' && e.name.startsWith('vscode-language-pack'));
if (extensionToInstall) {
localStorage.setItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY, extensionToInstall.identifier.id);
localeStorage.setExtensionId(extensionToInstall.identifier.id);
return extensionToInstall.identifier.id;
}

View File

@ -71,6 +71,10 @@ const TestNativeWindowConfiguration: INativeWindowConfiguration = {
tmpDir: tmpDir.fsPath,
userDataDir: joinPath(homeDir, product.nameShort).fsPath,
profiles: { profile: NULL_PROFILE, all: [NULL_PROFILE], home: homeDir },
nls: {
messages: [],
language: 'en'
},
_: []
};

View File

@ -1,8 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// NOTE: THIS FILE WILL BE OVERWRITTEN DURING BUILD TIME, DO NOT EDIT
define([], {});

View File

@ -156,7 +156,6 @@ import 'vs/workbench/contrib/tags/browser/workspaceTagsService';
// Issues
import 'vs/workbench/contrib/issue/browser/issue.contribution';
// Splash
import 'vs/workbench/contrib/splash/browser/splash.contribution';

View File

@ -246,6 +246,18 @@ async function runTestsInBrowser(testModules, browserType) {
await page.goto(target.href);
if (args.build) {
const nlsMessages = await fs.promises.readFile(path.join(out, 'nls.messages.json'), 'utf8');
await page.evaluate(value => {
// when running from `out-build`, ensure to load the default
// messages file, because all `nls.localize` calls have their
// english values removed and replaced by an index.
// VSCODE_GLOBALS: NLS
// @ts-ignore
globalThis._VSCODE_NLS_MESSAGES = JSON.parse(value);
}, nlsMessages);
}
page.on('console', async msg => {
consoleLogFn(msg)(msg.text(), await Promise.all(msg.args().map(async arg => await arg.jsonValue())));
});

View File

@ -97,6 +97,16 @@ const _tests_glob = '**/test/**/*.test.js';
let loader;
let _out;
function initNls(opts) {
if (opts.build) {
// when running from `out-build`, ensure to load the default
// messages file, because all `nls.localize` calls have their
// english values removed and replaced by an index.
// VSCODE_GLOBALS: NLS
globalThis._VSCODE_NLS_MESSAGES = (require.__$__nodeRequire ?? require)(`../../../out-build/nls.messages.json`);
}
}
function initLoader(opts) {
const outdir = opts.build ? 'out-build' : 'out';
_out = path.join(__dirname, `../../../${outdir}`);
@ -438,6 +448,7 @@ function runTests(opts) {
}
ipcRenderer.on('run', (e, opts) => {
initNls(opts);
initLoader(opts);
runTests(opts).catch(err => {
if (typeof err !== 'string') {

View File

@ -84,6 +84,14 @@ function main() {
globalThis._VSCODE_PRODUCT_JSON = require(`${REPO_ROOT}/product.json`);
globalThis._VSCODE_PACKAGE_JSON = require(`${REPO_ROOT}/package.json`);
if (args.build) {
// when running from `out-build`, ensure to load the default
// messages file, because all `nls.localize` calls have their
// english values removed and replaced by an index.
// VSCODE_GLOBALS: NLS
globalThis._VSCODE_NLS_MESSAGES = require(`../../../${out}/nls.messages.json`);
}
// Test file operations that are common across platforms. Used for test infra, namely snapshot tests
Object.assign(globalThis, {
__analyzeSnapshotInTests: takeSnapshotAndCountClasses,