Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/lib/i18n.js
3520 views
1
"use strict";
2
var __importDefault = (this && this.__importDefault) || function (mod) {
3
return (mod && mod.__esModule) ? mod : { "default": mod };
4
};
5
Object.defineProperty(exports, "__esModule", { value: true });
6
exports.EXTERNAL_EXTENSIONS = exports.XLF = exports.Line = exports.extraLanguages = exports.defaultLanguages = void 0;
7
exports.processNlsFiles = processNlsFiles;
8
exports.getResource = getResource;
9
exports.createXlfFilesForCoreBundle = createXlfFilesForCoreBundle;
10
exports.createXlfFilesForExtensions = createXlfFilesForExtensions;
11
exports.createXlfFilesForIsl = createXlfFilesForIsl;
12
exports.prepareI18nPackFiles = prepareI18nPackFiles;
13
exports.prepareIslFiles = prepareIslFiles;
14
/*---------------------------------------------------------------------------------------------
15
* Copyright (c) Microsoft Corporation. All rights reserved.
16
* Licensed under the MIT License. See License.txt in the project root for license information.
17
*--------------------------------------------------------------------------------------------*/
18
const path_1 = __importDefault(require("path"));
19
const fs_1 = __importDefault(require("fs"));
20
const event_stream_1 = require("event-stream");
21
const gulp_merge_json_1 = __importDefault(require("gulp-merge-json"));
22
const vinyl_1 = __importDefault(require("vinyl"));
23
const xml2js_1 = __importDefault(require("xml2js"));
24
const gulp_1 = __importDefault(require("gulp"));
25
const fancy_log_1 = __importDefault(require("fancy-log"));
26
const ansi_colors_1 = __importDefault(require("ansi-colors"));
27
const iconv_lite_umd_1 = __importDefault(require("@vscode/iconv-lite-umd"));
28
const l10n_dev_1 = require("@vscode/l10n-dev");
29
const REPO_ROOT_PATH = path_1.default.join(__dirname, '../..');
30
function log(message, ...rest) {
31
(0, fancy_log_1.default)(ansi_colors_1.default.green('[i18n]'), message, ...rest);
32
}
33
exports.defaultLanguages = [
34
{ id: 'zh-tw', folderName: 'cht', translationId: 'zh-hant' },
35
{ id: 'zh-cn', folderName: 'chs', translationId: 'zh-hans' },
36
{ id: 'ja', folderName: 'jpn' },
37
{ id: 'ko', folderName: 'kor' },
38
{ id: 'de', folderName: 'deu' },
39
{ id: 'fr', folderName: 'fra' },
40
{ id: 'es', folderName: 'esn' },
41
{ id: 'ru', folderName: 'rus' },
42
{ id: 'it', folderName: 'ita' }
43
];
44
// languages requested by the community to non-stable builds
45
exports.extraLanguages = [
46
{ id: 'pt-br', folderName: 'ptb' },
47
{ id: 'hu', folderName: 'hun' },
48
{ id: 'tr', folderName: 'trk' }
49
];
50
var LocalizeInfo;
51
(function (LocalizeInfo) {
52
function is(value) {
53
const candidate = value;
54
return candidate && typeof candidate.key === 'string' && (candidate.comment === undefined || (Array.isArray(candidate.comment) && candidate.comment.every(element => typeof element === 'string')));
55
}
56
LocalizeInfo.is = is;
57
})(LocalizeInfo || (LocalizeInfo = {}));
58
var BundledFormat;
59
(function (BundledFormat) {
60
function is(value) {
61
if (value === undefined) {
62
return false;
63
}
64
const candidate = value;
65
const length = Object.keys(value).length;
66
return length === 3 && !!candidate.keys && !!candidate.messages && !!candidate.bundles;
67
}
68
BundledFormat.is = is;
69
})(BundledFormat || (BundledFormat = {}));
70
var NLSKeysFormat;
71
(function (NLSKeysFormat) {
72
function is(value) {
73
if (value === undefined) {
74
return false;
75
}
76
const candidate = value;
77
return Array.isArray(candidate) && Array.isArray(candidate[1]);
78
}
79
NLSKeysFormat.is = is;
80
})(NLSKeysFormat || (NLSKeysFormat = {}));
81
class Line {
82
buffer = [];
83
constructor(indent = 0) {
84
if (indent > 0) {
85
this.buffer.push(new Array(indent + 1).join(' '));
86
}
87
}
88
append(value) {
89
this.buffer.push(value);
90
return this;
91
}
92
toString() {
93
return this.buffer.join('');
94
}
95
}
96
exports.Line = Line;
97
class TextModel {
98
_lines;
99
constructor(contents) {
100
this._lines = contents.split(/\r\n|\r|\n/);
101
}
102
get lines() {
103
return this._lines;
104
}
105
}
106
class XLF {
107
project;
108
buffer;
109
files;
110
numberOfMessages;
111
constructor(project) {
112
this.project = project;
113
this.buffer = [];
114
this.files = Object.create(null);
115
this.numberOfMessages = 0;
116
}
117
toString() {
118
this.appendHeader();
119
const files = Object.keys(this.files).sort();
120
for (const file of files) {
121
this.appendNewLine(`<file original="${file}" source-language="en" datatype="plaintext"><body>`, 2);
122
const items = this.files[file].sort((a, b) => {
123
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
124
});
125
for (const item of items) {
126
this.addStringItem(file, item);
127
}
128
this.appendNewLine('</body></file>');
129
}
130
this.appendFooter();
131
return this.buffer.join('\r\n');
132
}
133
addFile(original, keys, messages) {
134
if (keys.length === 0) {
135
console.log('No keys in ' + original);
136
return;
137
}
138
if (keys.length !== messages.length) {
139
throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`);
140
}
141
this.numberOfMessages += keys.length;
142
this.files[original] = [];
143
const existingKeys = new Set();
144
for (let i = 0; i < keys.length; i++) {
145
const key = keys[i];
146
let realKey;
147
let comment;
148
if (typeof key === 'string') {
149
realKey = key;
150
comment = undefined;
151
}
152
else if (LocalizeInfo.is(key)) {
153
realKey = key.key;
154
if (key.comment && key.comment.length > 0) {
155
comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n');
156
}
157
}
158
if (!realKey || existingKeys.has(realKey)) {
159
continue;
160
}
161
existingKeys.add(realKey);
162
const message = encodeEntities(messages[i]);
163
this.files[original].push({ id: realKey, message: message, comment: comment });
164
}
165
}
166
addStringItem(file, item) {
167
if (!item.id || item.message === undefined || item.message === null) {
168
throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`);
169
}
170
if (item.message.length === 0) {
171
log(`Item with id ${item.id} in file ${file} has an empty message.`);
172
}
173
this.appendNewLine(`<trans-unit id="${item.id}">`, 4);
174
this.appendNewLine(`<source xml:lang="en">${item.message}</source>`, 6);
175
if (item.comment) {
176
this.appendNewLine(`<note>${item.comment}</note>`, 6);
177
}
178
this.appendNewLine('</trans-unit>', 4);
179
}
180
appendHeader() {
181
this.appendNewLine('<?xml version="1.0" encoding="utf-8"?>', 0);
182
this.appendNewLine('<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">', 0);
183
}
184
appendFooter() {
185
this.appendNewLine('</xliff>', 0);
186
}
187
appendNewLine(content, indent) {
188
const line = new Line(indent);
189
line.append(content);
190
this.buffer.push(line.toString());
191
}
192
static parse = function (xlfString) {
193
return new Promise((resolve, reject) => {
194
const parser = new xml2js_1.default.Parser();
195
const files = [];
196
parser.parseString(xlfString, function (err, result) {
197
if (err) {
198
reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`));
199
}
200
const fileNodes = result['xliff']['file'];
201
if (!fileNodes) {
202
reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`));
203
}
204
fileNodes.forEach((file) => {
205
const name = file.$.original;
206
if (!name) {
207
reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`));
208
}
209
const language = file.$['target-language'];
210
if (!language) {
211
reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`));
212
}
213
const messages = {};
214
const transUnits = file.body[0]['trans-unit'];
215
if (transUnits) {
216
transUnits.forEach((unit) => {
217
const key = unit.$.id;
218
if (!unit.target) {
219
return; // No translation available
220
}
221
let val = unit.target[0];
222
if (typeof val !== 'string') {
223
// We allow empty source values so support them for translations as well.
224
val = val._ ? val._ : '';
225
}
226
if (!key) {
227
reject(new Error(`XLF parsing error: trans-unit ${JSON.stringify(unit, undefined, 0)} defined in file ${name} is missing the ID attribute.`));
228
return;
229
}
230
messages[key] = decodeEntities(val);
231
});
232
files.push({ messages, name, language: language.toLowerCase() });
233
}
234
});
235
resolve(files);
236
});
237
});
238
};
239
}
240
exports.XLF = XLF;
241
function sortLanguages(languages) {
242
return languages.sort((a, b) => {
243
return a.id < b.id ? -1 : (a.id > b.id ? 1 : 0);
244
});
245
}
246
function stripComments(content) {
247
// Copied from stripComments.js
248
//
249
// First group matches a double quoted string
250
// Second group matches a single quoted string
251
// Third group matches a multi line comment
252
// Forth group matches a single line comment
253
// Fifth group matches a trailing comma
254
const regexp = /("[^"\\]*(?:\\.[^"\\]*)*")|('[^'\\]*(?:\\.[^'\\]*)*')|(\/\*[^\/\*]*(?:(?:\*|\/)[^\/\*]*)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))|(,\s*[}\]])/g;
255
const result = content.replace(regexp, (match, _m1, _m2, m3, m4, m5) => {
256
// Only one of m1, m2, m3, m4, m5 matches
257
if (m3) {
258
// A block comment. Replace with nothing
259
return '';
260
}
261
else if (m4) {
262
// Since m4 is a single line comment is is at least of length 2 (e.g. //)
263
// If it ends in \r?\n then keep it.
264
const length = m4.length;
265
if (m4[length - 1] === '\n') {
266
return m4[length - 2] === '\r' ? '\r\n' : '\n';
267
}
268
else {
269
return '';
270
}
271
}
272
else if (m5) {
273
// Remove the trailing comma
274
return match.substring(1);
275
}
276
else {
277
// We match a string
278
return match;
279
}
280
});
281
return result;
282
}
283
function processCoreBundleFormat(base, fileHeader, languages, json, emitter) {
284
const languageDirectory = path_1.default.join(REPO_ROOT_PATH, '..', 'vscode-loc', 'i18n');
285
if (!fs_1.default.existsSync(languageDirectory)) {
286
log(`No VS Code localization repository found. Looking at ${languageDirectory}`);
287
log(`To bundle translations please check out the vscode-loc repository as a sibling of the vscode repository.`);
288
}
289
const sortedLanguages = sortLanguages(languages);
290
sortedLanguages.forEach((language) => {
291
if (process.env['VSCODE_BUILD_VERBOSE']) {
292
log(`Generating nls bundles for: ${language.id}`);
293
}
294
const languageFolderName = language.translationId || language.id;
295
const i18nFile = path_1.default.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json');
296
let allMessages;
297
if (fs_1.default.existsSync(i18nFile)) {
298
const content = stripComments(fs_1.default.readFileSync(i18nFile, 'utf8'));
299
allMessages = JSON.parse(content);
300
}
301
let nlsIndex = 0;
302
const nlsResult = [];
303
for (const [moduleId, nlsKeys] of json) {
304
const moduleTranslations = allMessages?.contents[moduleId];
305
for (const nlsKey of nlsKeys) {
306
nlsResult.push(moduleTranslations?.[nlsKey]); // pushing `undefined` is fine, as we keep english strings as fallback for monaco editor in the build
307
nlsIndex++;
308
}
309
}
310
emitter.queue(new vinyl_1.default({
311
contents: Buffer.from(`${fileHeader}
312
globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(nlsResult)};
313
globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(language.id)};`),
314
base,
315
path: `${base}/nls.messages.${language.id}.js`
316
}));
317
});
318
}
319
function processNlsFiles(opts) {
320
return (0, event_stream_1.through)(function (file) {
321
const fileName = path_1.default.basename(file.path);
322
if (fileName === 'nls.keys.json') {
323
try {
324
const contents = file.contents.toString('utf8');
325
const json = JSON.parse(contents);
326
if (NLSKeysFormat.is(json)) {
327
processCoreBundleFormat(file.base, opts.fileHeader, opts.languages, json, this);
328
}
329
}
330
catch (error) {
331
this.emit('error', `Failed to read component file: ${error}`);
332
}
333
}
334
this.queue(file);
335
});
336
}
337
const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup', serverProject = 'vscode-server';
338
function getResource(sourceFile) {
339
let resource;
340
if (/^vs\/platform/.test(sourceFile)) {
341
return { name: 'vs/platform', project: editorProject };
342
}
343
else if (/^vs\/editor\/contrib/.test(sourceFile)) {
344
return { name: 'vs/editor/contrib', project: editorProject };
345
}
346
else if (/^vs\/editor/.test(sourceFile)) {
347
return { name: 'vs/editor', project: editorProject };
348
}
349
else if (/^vs\/base/.test(sourceFile)) {
350
return { name: 'vs/base', project: editorProject };
351
}
352
else if (/^vs\/code/.test(sourceFile)) {
353
return { name: 'vs/code', project: workbenchProject };
354
}
355
else if (/^vs\/server/.test(sourceFile)) {
356
return { name: 'vs/server', project: serverProject };
357
}
358
else if (/^vs\/workbench\/contrib/.test(sourceFile)) {
359
resource = sourceFile.split('/', 4).join('/');
360
return { name: resource, project: workbenchProject };
361
}
362
else if (/^vs\/workbench\/services/.test(sourceFile)) {
363
resource = sourceFile.split('/', 4).join('/');
364
return { name: resource, project: workbenchProject };
365
}
366
else if (/^vs\/workbench/.test(sourceFile)) {
367
return { name: 'vs/workbench', project: workbenchProject };
368
}
369
throw new Error(`Could not identify the XLF bundle for ${sourceFile}`);
370
}
371
function createXlfFilesForCoreBundle() {
372
return (0, event_stream_1.through)(function (file) {
373
const basename = path_1.default.basename(file.path);
374
if (basename === 'nls.metadata.json') {
375
if (file.isBuffer()) {
376
const xlfs = Object.create(null);
377
const json = JSON.parse(file.contents.toString('utf8'));
378
for (const coreModule in json.keys) {
379
const projectResource = getResource(coreModule);
380
const resource = projectResource.name;
381
const project = projectResource.project;
382
const keys = json.keys[coreModule];
383
const messages = json.messages[coreModule];
384
if (keys.length !== messages.length) {
385
this.emit('error', `There is a mismatch between keys and messages in ${file.relative} for module ${coreModule}`);
386
return;
387
}
388
else {
389
let xlf = xlfs[resource];
390
if (!xlf) {
391
xlf = new XLF(project);
392
xlfs[resource] = xlf;
393
}
394
xlf.addFile(`src/${coreModule}`, keys, messages);
395
}
396
}
397
for (const resource in xlfs) {
398
const xlf = xlfs[resource];
399
const filePath = `${xlf.project}/${resource.replace(/\//g, '_')}.xlf`;
400
const xlfFile = new vinyl_1.default({
401
path: filePath,
402
contents: Buffer.from(xlf.toString(), 'utf8')
403
});
404
this.queue(xlfFile);
405
}
406
}
407
else {
408
this.emit('error', new Error(`File ${file.relative} is not using a buffer content`));
409
return;
410
}
411
}
412
else {
413
this.emit('error', new Error(`File ${file.relative} is not a core meta data file.`));
414
return;
415
}
416
});
417
}
418
function createL10nBundleForExtension(extensionFolderName, prefixWithBuildFolder) {
419
const prefix = prefixWithBuildFolder ? '.build/' : '';
420
return gulp_1.default
421
.src([
422
// For source code of extensions
423
`${prefix}extensions/${extensionFolderName}/{src,client,server}/**/*.{ts,tsx}`,
424
// // For any dependencies pulled in (think vscode-css-languageservice or @vscode/emmet-helper)
425
`${prefix}extensions/${extensionFolderName}/**/node_modules/{@vscode,vscode-*}/**/*.{js,jsx}`,
426
// // For any dependencies pulled in that bundle @vscode/l10n. They needed to export the bundle
427
`${prefix}extensions/${extensionFolderName}/**/bundle.l10n.json`,
428
])
429
.pipe((0, event_stream_1.map)(function (data, callback) {
430
const file = data;
431
if (!file.isBuffer()) {
432
// Not a buffer so we drop it
433
callback();
434
return;
435
}
436
const extension = path_1.default.extname(file.relative);
437
if (extension !== '.json') {
438
const contents = file.contents.toString('utf8');
439
(0, l10n_dev_1.getL10nJson)([{ contents, extension }])
440
.then((json) => {
441
callback(undefined, new vinyl_1.default({
442
path: `extensions/${extensionFolderName}/bundle.l10n.json`,
443
contents: Buffer.from(JSON.stringify(json), 'utf8')
444
}));
445
})
446
.catch((err) => {
447
callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`));
448
});
449
// signal pause?
450
return false;
451
}
452
// for bundle.l10n.jsons
453
let bundleJson;
454
try {
455
bundleJson = JSON.parse(file.contents.toString('utf8'));
456
}
457
catch (err) {
458
callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`));
459
return;
460
}
461
// some validation of the bundle.l10n.json format
462
for (const key in bundleJson) {
463
if (typeof bundleJson[key] !== 'string' &&
464
(typeof bundleJson[key].message !== 'string' || !Array.isArray(bundleJson[key].comment))) {
465
callback(new Error(`Invalid bundle.l10n.json file. The value for key ${key} is not in the expected format.`));
466
return;
467
}
468
}
469
callback(undefined, file);
470
}))
471
.pipe((0, gulp_merge_json_1.default)({
472
fileName: `extensions/${extensionFolderName}/bundle.l10n.json`,
473
jsonSpace: '',
474
concatArrays: true
475
}));
476
}
477
exports.EXTERNAL_EXTENSIONS = [
478
'ms-vscode.js-debug',
479
'ms-vscode.js-debug-companion',
480
'ms-vscode.vscode-js-profile-table',
481
];
482
function createXlfFilesForExtensions() {
483
let counter = 0;
484
let folderStreamEnded = false;
485
let folderStreamEndEmitted = false;
486
return (0, event_stream_1.through)(function (extensionFolder) {
487
const folderStream = this;
488
const stat = fs_1.default.statSync(extensionFolder.path);
489
if (!stat.isDirectory()) {
490
return;
491
}
492
const extensionFolderName = path_1.default.basename(extensionFolder.path);
493
if (extensionFolderName === 'node_modules') {
494
return;
495
}
496
// Get extension id and use that as the id
497
const manifest = fs_1.default.readFileSync(path_1.default.join(extensionFolder.path, 'package.json'), 'utf-8');
498
const manifestJson = JSON.parse(manifest);
499
const extensionId = manifestJson.publisher + '.' + manifestJson.name;
500
counter++;
501
let _l10nMap;
502
function getL10nMap() {
503
if (!_l10nMap) {
504
_l10nMap = new Map();
505
}
506
return _l10nMap;
507
}
508
(0, event_stream_1.merge)(gulp_1.default.src([`.build/extensions/${extensionFolderName}/package.nls.json`, `.build/extensions/${extensionFolderName}/**/nls.metadata.json`], { allowEmpty: true }), createL10nBundleForExtension(extensionFolderName, exports.EXTERNAL_EXTENSIONS.includes(extensionId))).pipe((0, event_stream_1.through)(function (file) {
509
if (file.isBuffer()) {
510
const buffer = file.contents;
511
const basename = path_1.default.basename(file.path);
512
if (basename === 'package.nls.json') {
513
const json = JSON.parse(buffer.toString('utf8'));
514
getL10nMap().set(`extensions/${extensionId}/package`, json);
515
}
516
else if (basename === 'nls.metadata.json') {
517
const json = JSON.parse(buffer.toString('utf8'));
518
const relPath = path_1.default.relative(`.build/extensions/${extensionFolderName}`, path_1.default.dirname(file.path));
519
for (const file in json) {
520
const fileContent = json[file];
521
const info = Object.create(null);
522
for (let i = 0; i < fileContent.messages.length; i++) {
523
const message = fileContent.messages[i];
524
const { key, comment } = LocalizeInfo.is(fileContent.keys[i])
525
? fileContent.keys[i]
526
: { key: fileContent.keys[i], comment: undefined };
527
info[key] = comment ? { message, comment } : message;
528
}
529
getL10nMap().set(`extensions/${extensionId}/${relPath}/${file}`, info);
530
}
531
}
532
else if (basename === 'bundle.l10n.json') {
533
const json = JSON.parse(buffer.toString('utf8'));
534
getL10nMap().set(`extensions/${extensionId}/bundle`, json);
535
}
536
else {
537
this.emit('error', new Error(`${file.path} is not a valid extension nls file`));
538
return;
539
}
540
}
541
}, function () {
542
if (_l10nMap?.size > 0) {
543
const xlfFile = new vinyl_1.default({
544
path: path_1.default.join(extensionsProject, extensionId + '.xlf'),
545
contents: Buffer.from((0, l10n_dev_1.getL10nXlf)(_l10nMap), 'utf8')
546
});
547
folderStream.queue(xlfFile);
548
}
549
this.queue(null);
550
counter--;
551
if (counter === 0 && folderStreamEnded && !folderStreamEndEmitted) {
552
folderStreamEndEmitted = true;
553
folderStream.queue(null);
554
}
555
}));
556
}, function () {
557
folderStreamEnded = true;
558
if (counter === 0) {
559
folderStreamEndEmitted = true;
560
this.queue(null);
561
}
562
});
563
}
564
function createXlfFilesForIsl() {
565
return (0, event_stream_1.through)(function (file) {
566
let projectName, resourceFile;
567
if (path_1.default.basename(file.path) === 'messages.en.isl') {
568
projectName = setupProject;
569
resourceFile = 'messages.xlf';
570
}
571
else {
572
throw new Error(`Unknown input file ${file.path}`);
573
}
574
const xlf = new XLF(projectName), keys = [], messages = [];
575
const model = new TextModel(file.contents.toString());
576
let inMessageSection = false;
577
model.lines.forEach(line => {
578
if (line.length === 0) {
579
return;
580
}
581
const firstChar = line.charAt(0);
582
switch (firstChar) {
583
case ';':
584
// Comment line;
585
return;
586
case '[':
587
inMessageSection = '[Messages]' === line || '[CustomMessages]' === line;
588
return;
589
}
590
if (!inMessageSection) {
591
return;
592
}
593
const sections = line.split('=');
594
if (sections.length !== 2) {
595
throw new Error(`Badly formatted message found: ${line}`);
596
}
597
else {
598
const key = sections[0];
599
const value = sections[1];
600
if (key.length > 0 && value.length > 0) {
601
keys.push(key);
602
messages.push(value);
603
}
604
}
605
});
606
const originalPath = file.path.substring(file.cwd.length + 1, file.path.split('.')[0].length).replace(/\\/g, '/');
607
xlf.addFile(originalPath, keys, messages);
608
// Emit only upon all ISL files combined into single XLF instance
609
const newFilePath = path_1.default.join(projectName, resourceFile);
610
const xlfFile = new vinyl_1.default({ path: newFilePath, contents: Buffer.from(xlf.toString(), 'utf-8') });
611
this.queue(xlfFile);
612
});
613
}
614
function createI18nFile(name, messages) {
615
const result = Object.create(null);
616
result[''] = [
617
'--------------------------------------------------------------------------------------------',
618
'Copyright (c) Microsoft Corporation. All rights reserved.',
619
'Licensed under the MIT License. See License.txt in the project root for license information.',
620
'--------------------------------------------------------------------------------------------',
621
'Do not edit this file. It is machine generated.'
622
];
623
for (const key of Object.keys(messages)) {
624
result[key] = messages[key];
625
}
626
let content = JSON.stringify(result, null, '\t');
627
if (process.platform === 'win32') {
628
content = content.replace(/\n/g, '\r\n');
629
}
630
return new vinyl_1.default({
631
path: path_1.default.join(name + '.i18n.json'),
632
contents: Buffer.from(content, 'utf8')
633
});
634
}
635
const i18nPackVersion = '1.0.0';
636
function getRecordFromL10nJsonFormat(l10nJsonFormat) {
637
const record = {};
638
for (const key of Object.keys(l10nJsonFormat).sort()) {
639
const value = l10nJsonFormat[key];
640
record[key] = typeof value === 'string' ? value : value.message;
641
}
642
return record;
643
}
644
function prepareI18nPackFiles(resultingTranslationPaths) {
645
const parsePromises = [];
646
const mainPack = { version: i18nPackVersion, contents: {} };
647
const extensionsPacks = {};
648
const errors = [];
649
return (0, event_stream_1.through)(function (xlf) {
650
let project = path_1.default.basename(path_1.default.dirname(path_1.default.dirname(xlf.relative)));
651
// strip `-new` since vscode-extensions-loc uses the `-new` suffix to indicate that it's from the new loc pipeline
652
const resource = path_1.default.basename(path_1.default.basename(xlf.relative, '.xlf'), '-new');
653
if (exports.EXTERNAL_EXTENSIONS.find(e => e === resource)) {
654
project = extensionsProject;
655
}
656
const contents = xlf.contents.toString();
657
log(`Found ${project}: ${resource}`);
658
const parsePromise = (0, l10n_dev_1.getL10nFilesFromXlf)(contents);
659
parsePromises.push(parsePromise);
660
parsePromise.then(resolvedFiles => {
661
resolvedFiles.forEach(file => {
662
const path = file.name;
663
const firstSlash = path.indexOf('/');
664
if (project === extensionsProject) {
665
// resource will be the extension id
666
let extPack = extensionsPacks[resource];
667
if (!extPack) {
668
extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} };
669
}
670
// remove 'extensions/extensionId/' segment
671
const secondSlash = path.indexOf('/', firstSlash + 1);
672
extPack.contents[path.substring(secondSlash + 1)] = getRecordFromL10nJsonFormat(file.messages);
673
}
674
else {
675
mainPack.contents[path.substring(firstSlash + 1)] = getRecordFromL10nJsonFormat(file.messages);
676
}
677
});
678
}).catch(reason => {
679
errors.push(reason);
680
});
681
}, function () {
682
Promise.all(parsePromises)
683
.then(() => {
684
if (errors.length > 0) {
685
throw errors;
686
}
687
const translatedMainFile = createI18nFile('./main', mainPack);
688
resultingTranslationPaths.push({ id: 'vscode', resourceName: 'main.i18n.json' });
689
this.queue(translatedMainFile);
690
for (const extensionId in extensionsPacks) {
691
const translatedExtFile = createI18nFile(`extensions/${extensionId}`, extensionsPacks[extensionId]);
692
this.queue(translatedExtFile);
693
resultingTranslationPaths.push({ id: extensionId, resourceName: `extensions/${extensionId}.i18n.json` });
694
}
695
this.queue(null);
696
})
697
.catch((reason) => {
698
this.emit('error', reason);
699
});
700
});
701
}
702
function prepareIslFiles(language, innoSetupConfig) {
703
const parsePromises = [];
704
return (0, event_stream_1.through)(function (xlf) {
705
const stream = this;
706
const parsePromise = XLF.parse(xlf.contents.toString());
707
parsePromises.push(parsePromise);
708
parsePromise.then(resolvedFiles => {
709
resolvedFiles.forEach(file => {
710
const translatedFile = createIslFile(file.name, file.messages, language, innoSetupConfig);
711
stream.queue(translatedFile);
712
});
713
}).catch(reason => {
714
this.emit('error', reason);
715
});
716
}, function () {
717
Promise.all(parsePromises)
718
.then(() => { this.queue(null); })
719
.catch(reason => {
720
this.emit('error', reason);
721
});
722
});
723
}
724
function createIslFile(name, messages, language, innoSetup) {
725
const content = [];
726
let originalContent;
727
if (path_1.default.basename(name) === 'Default') {
728
originalContent = new TextModel(fs_1.default.readFileSync(name + '.isl', 'utf8'));
729
}
730
else {
731
originalContent = new TextModel(fs_1.default.readFileSync(name + '.en.isl', 'utf8'));
732
}
733
originalContent.lines.forEach(line => {
734
if (line.length > 0) {
735
const firstChar = line.charAt(0);
736
if (firstChar === '[' || firstChar === ';') {
737
content.push(line);
738
}
739
else {
740
const sections = line.split('=');
741
const key = sections[0];
742
let translated = line;
743
if (key) {
744
const translatedMessage = messages[key];
745
if (translatedMessage) {
746
translated = `${key}=${translatedMessage}`;
747
}
748
}
749
content.push(translated);
750
}
751
}
752
});
753
const basename = path_1.default.basename(name);
754
const filePath = `${basename}.${language.id}.isl`;
755
const encoded = iconv_lite_umd_1.default.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage);
756
return new vinyl_1.default({
757
path: filePath,
758
contents: Buffer.from(encoded),
759
});
760
}
761
function encodeEntities(value) {
762
const result = [];
763
for (let i = 0; i < value.length; i++) {
764
const ch = value[i];
765
switch (ch) {
766
case '<':
767
result.push('&lt;');
768
break;
769
case '>':
770
result.push('&gt;');
771
break;
772
case '&':
773
result.push('&amp;');
774
break;
775
default:
776
result.push(ch);
777
}
778
}
779
return result.join('');
780
}
781
function decodeEntities(value) {
782
return value.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
783
}
784
//# sourceMappingURL=i18n.js.map
785