i18n.js 48.0 KB
Newer Older
1 2 3 4 5 6
"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
7 8 9 10 11 12 13 14 15
const path = require("path");
const fs = require("fs");
const event_stream_1 = require("event-stream");
const File = require("vinyl");
const Is = require("is");
const xml2js = require("xml2js");
const glob = require("glob");
const https = require("https");
const gulp = require("gulp");
A
Alex Dima 已提交
16 17
const fancyLog = require("fancy-log");
const ansiColors = require("ansi-colors");
18 19 20
const iconv = require("iconv-lite");
const NUMBER_OF_CONCURRENT_DOWNLOADS = 4;
function log(message, ...rest) {
A
Alex Dima 已提交
21
    fancyLog(ansiColors.green('[i18n]'), message, ...rest);
22
}
D
Dirk Baeumer 已提交
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
exports.defaultLanguages = [
    { id: 'zh-tw', folderName: 'cht', transifexId: 'zh-hant' },
    { id: 'zh-cn', folderName: 'chs', transifexId: 'zh-hans' },
    { id: 'ja', folderName: 'jpn' },
    { id: 'ko', folderName: 'kor' },
    { id: 'de', folderName: 'deu' },
    { id: 'fr', folderName: 'fra' },
    { id: 'es', folderName: 'esn' },
    { id: 'ru', folderName: 'rus' },
    { id: 'it', folderName: 'ita' }
];
// languages requested by the community to non-stable builds
exports.extraLanguages = [
    { id: 'pt-br', folderName: 'ptb' },
    { id: 'hu', folderName: 'hun' },
    { id: 'tr', folderName: 'trk' }
];
40
// non built-in extensions also that are transifex and need to be part of the language packs
41
exports.externalExtensionsWithTranslations = {
42 43 44 45
    'vscode-chrome-debug': 'msjsdiag.debugger-for-chrome',
    'vscode-node-debug': 'ms-vscode.node-debug',
    'vscode-node-debug2': 'ms-vscode.node-debug2'
};
46 47 48
var LocalizeInfo;
(function (LocalizeInfo) {
    function is(value) {
49 50
        let candidate = value;
        return Is.defined(candidate) && Is.string(candidate.key) && (Is.undef(candidate.comment) || (Is.array(candidate.comment) && candidate.comment.every(element => Is.string(element))));
51 52 53 54 55 56 57 58 59
    }
    LocalizeInfo.is = is;
})(LocalizeInfo || (LocalizeInfo = {}));
var BundledFormat;
(function (BundledFormat) {
    function is(value) {
        if (Is.undef(value)) {
            return false;
        }
60 61
        let candidate = value;
        let length = Object.keys(value).length;
62 63 64 65 66 67 68 69 70 71
        return length === 3 && Is.defined(candidate.keys) && Is.defined(candidate.messages) && Is.defined(candidate.bundles);
    }
    BundledFormat.is = is;
})(BundledFormat || (BundledFormat = {}));
var PackageJsonFormat;
(function (PackageJsonFormat) {
    function is(value) {
        if (Is.undef(value) || !Is.object(value)) {
            return false;
        }
72 73
        return Object.keys(value).every(key => {
            let element = value[key];
74 75 76 77 78
            return Is.string(element) || (Is.object(element) && Is.defined(element.message) && Is.defined(element.comment));
        });
    }
    PackageJsonFormat.is = is;
})(PackageJsonFormat || (PackageJsonFormat = {}));
79 80
class Line {
    constructor(indent = 0) {
81 82 83 84 85
        this.buffer = [];
        if (indent > 0) {
            this.buffer.push(new Array(indent + 1).join(' '));
        }
    }
86
    append(value) {
87 88
        this.buffer.push(value);
        return this;
89 90
    }
    toString() {
91
        return this.buffer.join('');
92 93
    }
}
94
exports.Line = Line;
95 96
class TextModel {
    constructor(contents) {
97 98
        this._lines = contents.split(/\r\n|\r|\n/);
    }
99 100 101 102 103 104
    get lines() {
        return this._lines;
    }
}
class XLF {
    constructor(project) {
105 106 107
        this.project = project;
        this.buffer = [];
        this.files = Object.create(null);
108
        this.numberOfMessages = 0;
109
    }
110
    toString() {
111
        this.appendHeader();
112 113 114
        for (let file in this.files) {
            this.appendNewLine(`<file original="${file}" source-language="en" datatype="plaintext"><body>`, 2);
            for (let item of this.files[file]) {
115 116 117 118 119 120
                this.addStringItem(item);
            }
            this.appendNewLine('</body></file>', 2);
        }
        this.appendFooter();
        return this.buffer.join('\r\n');
121 122
    }
    addFile(original, keys, messages) {
123 124 125 126
        if (keys.length === 0) {
            console.log('No keys in ' + original);
            return;
        }
D
Dirk Baeumer 已提交
127
        if (keys.length !== messages.length) {
128
            throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`);
D
Dirk Baeumer 已提交
129
        }
130
        this.numberOfMessages += keys.length;
131
        this.files[original] = [];
132 133 134 135 136
        let existingKeys = new Set();
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i];
            let realKey;
            let comment;
137
            if (Is.string(key)) {
D
Dirk Baeumer 已提交
138 139
                realKey = key;
                comment = undefined;
140
            }
D
Dirk Baeumer 已提交
141 142 143
            else if (LocalizeInfo.is(key)) {
                realKey = key.key;
                if (key.comment && key.comment.length > 0) {
144
                    comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n');
145 146
                }
            }
D
Dirk Baeumer 已提交
147 148 149 150
            if (!realKey || existingKeys.has(realKey)) {
                continue;
            }
            existingKeys.add(realKey);
151
            let message = encodeEntities(messages[i]);
D
Dirk Baeumer 已提交
152
            this.files[original].push({ id: realKey, message: message, comment: comment });
153
        }
154 155
    }
    addStringItem(item) {
156
        if (!item.id || !item.message) {
157
            throw new Error(`No item ID or value specified: ${JSON.stringify(item)}`);
158
        }
159 160
        this.appendNewLine(`<trans-unit id="${item.id}">`, 4);
        this.appendNewLine(`<source xml:lang="en">${item.message}</source>`, 6);
161
        if (item.comment) {
162
            this.appendNewLine(`<note>${item.comment}</note>`, 6);
163 164
        }
        this.appendNewLine('</trans-unit>', 4);
165 166
    }
    appendHeader() {
167 168
        this.appendNewLine('<?xml version="1.0" encoding="utf-8"?>', 0);
        this.appendNewLine('<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">', 0);
169 170
    }
    appendFooter() {
171
        this.appendNewLine('</xliff>', 0);
172 173 174
    }
    appendNewLine(content, indent) {
        let line = new Line(indent);
175 176
        line.append(content);
        this.buffer.push(line.toString());
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
    }
}
XLF.parsePseudo = function (xlfString) {
    return new Promise((resolve) => {
        let parser = new xml2js.Parser();
        let files = [];
        parser.parseString(xlfString, function (_err, result) {
            const fileNodes = result['xliff']['file'];
            fileNodes.forEach(file => {
                const originalFilePath = file.$.original;
                const messages = {};
                const transUnits = file.body[0]['trans-unit'];
                if (transUnits) {
                    transUnits.forEach((unit) => {
                        const key = unit.$.id;
                        const val = pseudify(unit.source[0]['_'].toString());
                        if (key && val) {
                            messages[key] = decodeEntities(val);
                        }
                    });
                    files.push({ messages: messages, originalFilePath: originalFilePath, language: 'ps' });
                }
199
            });
200
            resolve(files);
201
        });
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
    });
};
XLF.parse = function (xlfString) {
    return new Promise((resolve, reject) => {
        let parser = new xml2js.Parser();
        let files = [];
        parser.parseString(xlfString, function (err, result) {
            if (err) {
                reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`));
            }
            const fileNodes = result['xliff']['file'];
            if (!fileNodes) {
                reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`));
            }
            fileNodes.forEach((file) => {
                const originalFilePath = file.$.original;
                if (!originalFilePath) {
                    reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`));
220
                }
221 222 223 224 225 226 227 228 229 230 231 232
                let language = file.$['target-language'];
                if (!language) {
                    reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`));
                }
                const messages = {};
                const transUnits = file.body[0]['trans-unit'];
                if (transUnits) {
                    transUnits.forEach((unit) => {
                        const key = unit.$.id;
                        if (!unit.target) {
                            return; // No translation available
                        }
233 234 235 236
                        let val = unit.target[0];
                        if (typeof val !== 'string') {
                            val = val._;
                        }
237 238 239 240
                        if (key && val) {
                            messages[key] = decodeEntities(val);
                        }
                        else {
241
                            reject(new Error(`XLF parsing error: XLIFF file ${originalFilePath} does not contain full localization data. ID or target translation for one of the trans-unit nodes is not present.`));
242 243 244
                        }
                    });
                    files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() });
245 246
                }
            });
247
            resolve(files);
248
        });
249 250
    });
};
251
exports.XLF = XLF;
252 253
class Limiter {
    constructor(maxDegreeOfParalellism) {
254 255 256 257
        this.maxDegreeOfParalellism = maxDegreeOfParalellism;
        this.outstandingPromises = [];
        this.runningPromises = 0;
    }
258 259 260 261
    queue(factory) {
        return new Promise((c, e) => {
            this.outstandingPromises.push({ factory, c, e });
            this.consume();
262
        });
263 264
    }
    consume() {
265
        while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
266
            const iLimitedTask = this.outstandingPromises.shift();
267
            this.runningPromises++;
268
            const promise = iLimitedTask.factory();
269
            promise.then(iLimitedTask.c).catch(iLimitedTask.e);
270
            promise.then(() => this.consumed()).catch(() => this.consumed());
271
        }
272 273
    }
    consumed() {
274 275
        this.runningPromises--;
        this.consume();
276 277
    }
}
278
exports.Limiter = Limiter;
D
Dirk Baeumer 已提交
279
function sortLanguages(languages) {
280
    return languages.sort((a, b) => {
D
Dirk Baeumer 已提交
281
        return a.id < b.id ? -1 : (a.id > b.id ? 1 : 0);
282 283 284 285 286 287 288 289 290
    });
}
function stripComments(content) {
    /**
    * First capturing group matches double quoted string
    * Second matches single quotes string
    * Third matches block comments
    * Fourth matches line comments
    */
291 292
    const regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
    let result = content.replace(regexp, (match, _m1, _m2, m3, m4) => {
293 294 295 296 297 298 299
        // Only one of m1, m2, m3, m4 matches
        if (m3) {
            // A block comment. Replace with nothing
            return '';
        }
        else if (m4) {
            // A line comment. If it ends in \r?\n then keep it.
300 301 302
            let length = m4.length;
            if (length > 2 && m4[length - 1] === '\n') {
                return m4[length - 2] === '\r' ? '\r\n' : '\n';
303 304 305 306 307 308 309 310 311 312 313 314 315
            }
            else {
                return '';
            }
        }
        else {
            // We match a string
            return match;
        }
    });
    return result;
}
function escapeCharacters(value) {
316 317 318
    const result = [];
    for (let i = 0; i < value.length; i++) {
        const ch = value.charAt(i);
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
        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) {
351 352 353 354 355 356 357 358 359
    let keysSection = json.keys;
    let messageSection = json.messages;
    let bundleSection = json.bundles;
    let statistics = Object.create(null);
    let defaultMessages = Object.create(null);
    let modules = Object.keys(keysSection);
    modules.forEach((module) => {
        let keys = keysSection[module];
        let messages = messageSection[module];
360
        if (!messages || keys.length !== messages.length) {
361
            emitter.emit('error', `Message for module ${module} corrupted. Mismatch in number of keys and messages.`);
362 363
            return;
        }
364
        let messageMap = Object.create(null);
365
        defaultMessages[module] = messageMap;
366
        keys.map((key, i) => {
D
Dirk Baeumer 已提交
367
            if (typeof key === 'string') {
368 369 370 371 372 373 374
                messageMap[key] = messages[i];
            }
            else {
                messageMap[key.key] = messages[i];
            }
        });
    });
375 376 377
    let languageDirectory = path.join(__dirname, '..', '..', 'i18n');
    let sortedLanguages = sortLanguages(languages);
    sortedLanguages.forEach((language) => {
378
        if (process.env['VSCODE_BUILD_VERBOSE']) {
379
            log(`Generating nls bundles for: ${language.id}`);
380
        }
D
Dirk Baeumer 已提交
381
        statistics[language.id] = 0;
382 383 384 385 386 387 388
        let localizedModules = Object.create(null);
        let languageFolderName = language.folderName || language.id;
        let cwd = path.join(languageDirectory, languageFolderName, 'src');
        modules.forEach((module) => {
            let order = keysSection[module];
            let i18nFile = path.join(cwd, module) + '.i18n.json';
            let messages = null;
389
            if (fs.existsSync(i18nFile)) {
390
                let content = stripComments(fs.readFileSync(i18nFile, 'utf8'));
391 392 393 394
                messages = JSON.parse(content);
            }
            else {
                if (process.env['VSCODE_BUILD_VERBOSE']) {
395
                    log(`No localized messages found for module ${module}. Using default messages.`);
396 397
                }
                messages = defaultMessages[module];
D
Dirk Baeumer 已提交
398
                statistics[language.id] = statistics[language.id] + Object.keys(messages).length;
399
            }
400 401 402
            let localizedMessages = [];
            order.forEach((keyInfo) => {
                let key = null;
D
Dirk Baeumer 已提交
403
                if (typeof keyInfo === 'string') {
404 405 406 407 408
                    key = keyInfo;
                }
                else {
                    key = keyInfo.key;
                }
409
                let message = messages[key];
410 411
                if (!message) {
                    if (process.env['VSCODE_BUILD_VERBOSE']) {
412
                        log(`No localized message found for key ${key} in module ${module}. Using default message.`);
413 414
                    }
                    message = defaultMessages[module][key];
D
Dirk Baeumer 已提交
415
                    statistics[language.id] = statistics[language.id] + 1;
416 417 418 419 420
                }
                localizedMessages.push(message);
            });
            localizedModules[module] = localizedMessages;
        });
421 422 423
        Object.keys(bundleSection).forEach((bundle) => {
            let modules = bundleSection[bundle];
            let contents = [
424
                fileHeader,
425
                `define("${bundle}.nls.${language.id}", {`
426
            ];
427 428 429
            modules.forEach((module, index) => {
                contents.push(`\t"${module}": [`);
                let messages = localizedModules[module];
430
                if (!messages) {
431
                    emitter.emit('error', `Didn't find messages for module ${module}.`);
432 433
                    return;
                }
434 435
                messages.forEach((message, index) => {
                    contents.push(`\t\t"${escapeCharacters(message)}${index < messages.length ? '",' : '"'}`);
436 437 438 439
                });
                contents.push(index < modules.length - 1 ? '\t],' : '\t]');
            });
            contents.push('});');
J
Joao Moreno 已提交
440
            emitter.queue(new File({ path: bundle + '.nls.' + language.id + '.js', contents: Buffer.from(contents.join('\n'), 'utf-8') }));
441 442
        });
    });
443 444 445
    Object.keys(statistics).forEach(key => {
        let value = statistics[key];
        log(`${key} has ${value} untranslated strings.`);
446
    });
447 448
    sortedLanguages.forEach(language => {
        let stats = statistics[language.id];
D
Dirk Baeumer 已提交
449
        if (Is.undef(stats)) {
450
            log(`\tNo translations found for language ${language.id}. Using default language instead.`);
451 452 453 454 455
        }
    });
}
function processNlsFiles(opts) {
    return event_stream_1.through(function (file) {
456
        let fileName = path.basename(file.path);
457
        if (fileName === 'nls.metadata.json') {
458
            let json = null;
459 460 461 462
            if (file.isBuffer()) {
                json = JSON.parse(file.contents.toString('utf8'));
            }
            else {
463
                this.emit('error', `Failed to read component file: ${file.relative}`);
D
Dirk Baeumer 已提交
464
                return;
465 466 467 468 469
            }
            if (BundledFormat.is(json)) {
                processCoreBundleFormat(opts.fileHeader, opts.languages, json, this);
            }
        }
D
Dirk Baeumer 已提交
470
        this.queue(file);
471 472 473
    });
}
exports.processNlsFiles = processNlsFiles;
474
const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup';
475
function getResource(sourceFile) {
476
    let resource;
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
    if (/^vs\/platform/.test(sourceFile)) {
        return { name: 'vs/platform', project: editorProject };
    }
    else if (/^vs\/editor\/contrib/.test(sourceFile)) {
        return { name: 'vs/editor/contrib', project: editorProject };
    }
    else if (/^vs\/editor/.test(sourceFile)) {
        return { name: 'vs/editor', project: editorProject };
    }
    else if (/^vs\/base/.test(sourceFile)) {
        return { name: 'vs/base', project: editorProject };
    }
    else if (/^vs\/code/.test(sourceFile)) {
        return { name: 'vs/code', project: workbenchProject };
    }
    else if (/^vs\/workbench\/parts/.test(sourceFile)) {
        resource = sourceFile.split('/', 4).join('/');
        return { name: resource, project: workbenchProject };
    }
    else if (/^vs\/workbench\/services/.test(sourceFile)) {
        resource = sourceFile.split('/', 4).join('/');
        return { name: resource, project: workbenchProject };
    }
    else if (/^vs\/workbench/.test(sourceFile)) {
        return { name: 'vs/workbench', project: workbenchProject };
    }
503
    throw new Error(`Could not identify the XLF bundle for ${sourceFile}`);
504 505
}
exports.getResource = getResource;
D
Dirk Baeumer 已提交
506 507
function createXlfFilesForCoreBundle() {
    return event_stream_1.through(function (file) {
508
        const basename = path.basename(file.path);
D
Dirk Baeumer 已提交
509 510
        if (basename === 'nls.metadata.json') {
            if (file.isBuffer()) {
511 512 513 514 515 516 517 518
                const xlfs = Object.create(null);
                const json = JSON.parse(file.contents.toString('utf8'));
                for (let coreModule in json.keys) {
                    const projectResource = getResource(coreModule);
                    const resource = projectResource.name;
                    const project = projectResource.project;
                    const keys = json.keys[coreModule];
                    const messages = json.messages[coreModule];
D
Dirk Baeumer 已提交
519
                    if (keys.length !== messages.length) {
520
                        this.emit('error', `There is a mismatch between keys and messages in ${file.relative} for module ${coreModule}`);
D
Dirk Baeumer 已提交
521 522 523
                        return;
                    }
                    else {
524
                        let xlf = xlfs[resource];
D
Dirk Baeumer 已提交
525 526 527 528
                        if (!xlf) {
                            xlf = new XLF(project);
                            xlfs[resource] = xlf;
                        }
529
                        xlf.addFile(`src/${coreModule}`, keys, messages);
D
Dirk Baeumer 已提交
530 531
                    }
                }
532 533 534 535
                for (let resource in xlfs) {
                    const xlf = xlfs[resource];
                    const filePath = `${xlf.project}/${resource.replace(/\//g, '_')}.xlf`;
                    const xlfFile = new File({
D
Dirk Baeumer 已提交
536
                        path: filePath,
J
Joao Moreno 已提交
537
                        contents: Buffer.from(xlf.toString(), 'utf8')
D
Dirk Baeumer 已提交
538 539 540 541 542
                    });
                    this.queue(xlfFile);
                }
            }
            else {
543
                this.emit('error', new Error(`File ${file.relative} is not using a buffer content`));
D
Dirk Baeumer 已提交
544 545
                return;
            }
546
        }
D
Dirk Baeumer 已提交
547
        else {
548
            this.emit('error', new Error(`File ${file.relative} is not a core meta data file.`));
549 550
            return;
        }
D
Dirk Baeumer 已提交
551 552 553 554
    });
}
exports.createXlfFilesForCoreBundle = createXlfFilesForCoreBundle;
function createXlfFilesForExtensions() {
555 556 557
    let counter = 0;
    let folderStreamEnded = false;
    let folderStreamEndEmitted = false;
D
Dirk Baeumer 已提交
558
    return event_stream_1.through(function (extensionFolder) {
559 560
        const folderStream = this;
        const stat = fs.statSync(extensionFolder.path);
D
Dirk Baeumer 已提交
561 562
        if (!stat.isDirectory()) {
            return;
563
        }
564
        let extensionName = path.basename(extensionFolder.path);
D
Dirk Baeumer 已提交
565
        if (extensionName === 'node_modules') {
566 567
            return;
        }
D
Dirk Baeumer 已提交
568
        counter++;
569
        let _xlf;
D
Dirk Baeumer 已提交
570 571 572 573 574
        function getXlf() {
            if (!_xlf) {
                _xlf = new XLF(extensionsProject);
            }
            return _xlf;
575
        }
576
        gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`]).pipe(event_stream_1.through(function (file) {
D
Dirk Baeumer 已提交
577
            if (file.isBuffer()) {
578 579
                const buffer = file.contents;
                const basename = path.basename(file.path);
D
Dirk Baeumer 已提交
580
                if (basename === 'package.nls.json') {
581 582 583 584
                    const json = JSON.parse(buffer.toString('utf8'));
                    const keys = Object.keys(json);
                    const messages = keys.map((key) => {
                        const value = json[key];
D
Dirk Baeumer 已提交
585 586 587 588 589 590 591
                        if (Is.string(value)) {
                            return value;
                        }
                        else if (value) {
                            return value.message;
                        }
                        else {
592
                            return `Unknown message for key: ${key}`;
D
Dirk Baeumer 已提交
593 594
                        }
                    });
595
                    getXlf().addFile(`extensions/${extensionName}/package`, keys, messages);
D
Dirk Baeumer 已提交
596 597
                }
                else if (basename === 'nls.metadata.json') {
598 599 600 601 602
                    const json = JSON.parse(buffer.toString('utf8'));
                    const relPath = path.relative(`./extensions/${extensionName}`, path.dirname(file.path));
                    for (let file in json) {
                        const fileContent = json[file];
                        getXlf().addFile(`extensions/${extensionName}/${relPath}/${file}`, fileContent.keys, fileContent.messages);
D
Dirk Baeumer 已提交
603 604 605
                    }
                }
                else {
606
                    this.emit('error', new Error(`${file.path} is not a valid extension nls file`));
D
Dirk Baeumer 已提交
607 608 609 610 611
                    return;
                }
            }
        }, function () {
            if (_xlf) {
612
                let xlfFile = new File({
D
Dirk Baeumer 已提交
613
                    path: path.join(extensionsProject, extensionName + '.xlf'),
J
Joao Moreno 已提交
614
                    contents: Buffer.from(_xlf.toString(), 'utf8')
D
Dirk Baeumer 已提交
615 616 617 618 619 620 621 622
                });
                folderStream.queue(xlfFile);
            }
            this.queue(null);
            counter--;
            if (counter === 0 && folderStreamEnded && !folderStreamEndEmitted) {
                folderStreamEndEmitted = true;
                folderStream.queue(null);
623
            }
D
Dirk Baeumer 已提交
624 625 626 627 628 629
        }));
    }, function () {
        folderStreamEnded = true;
        if (counter === 0) {
            folderStreamEndEmitted = true;
            this.queue(null);
630 631 632
        }
    });
}
D
Dirk Baeumer 已提交
633 634 635
exports.createXlfFilesForExtensions = createXlfFilesForExtensions;
function createXlfFilesForIsl() {
    return event_stream_1.through(function (file) {
636
        let projectName, resourceFile;
D
Dirk Baeumer 已提交
637 638 639 640 641 642 643 644
        if (path.basename(file.path) === 'Default.isl') {
            projectName = setupProject;
            resourceFile = 'setup_default.xlf';
        }
        else {
            projectName = workbenchProject;
            resourceFile = 'setup_messages.xlf';
        }
645 646 647 648
        let xlf = new XLF(projectName), keys = [], messages = [];
        let model = new TextModel(file.contents.toString());
        let inMessageSection = false;
        model.lines.forEach(line => {
D
Dirk Baeumer 已提交
649 650 651
            if (line.length === 0) {
                return;
            }
652
            let firstChar = line.charAt(0);
D
Dirk Baeumer 已提交
653 654 655 656 657 658 659 660 661 662 663
            switch (firstChar) {
                case ';':
                    // Comment line;
                    return;
                case '[':
                    inMessageSection = '[Messages]' === line || '[CustomMessages]' === line;
                    return;
            }
            if (!inMessageSection) {
                return;
            }
664
            let sections = line.split('=');
D
Dirk Baeumer 已提交
665
            if (sections.length !== 2) {
666
                throw new Error(`Badly formatted message found: ${line}`);
D
Dirk Baeumer 已提交
667 668
            }
            else {
669 670
                let key = sections[0];
                let value = sections[1];
D
Dirk Baeumer 已提交
671 672 673 674 675 676
                if (key.length > 0 && value.length > 0) {
                    keys.push(key);
                    messages.push(value);
                }
            }
        });
677
        const originalPath = file.path.substring(file.cwd.length + 1, file.path.split('.')[0].length).replace(/\\/g, '/');
D
Dirk Baeumer 已提交
678 679
        xlf.addFile(originalPath, keys, messages);
        // Emit only upon all ISL files combined into single XLF instance
680 681
        const newFilePath = path.join(projectName, resourceFile);
        const xlfFile = new File({ path: newFilePath, contents: Buffer.from(xlf.toString(), 'utf-8') });
D
Dirk Baeumer 已提交
682 683 684 685
        this.queue(xlfFile);
    });
}
exports.createXlfFilesForIsl = createXlfFilesForIsl;
686
function pushXlfFiles(apiHostname, username, password) {
687 688
    let tryGetPromises = [];
    let updateCreatePromises = [];
689
    return event_stream_1.through(function (file) {
690 691 692 693
        const project = path.dirname(file.relative);
        const fileName = path.basename(file.path);
        const slug = fileName.substr(0, fileName.length - '.xlf'.length);
        const credentials = `${username}:${password}`;
694
        // Check if resource already exists, if not, then create it.
695
        let promise = tryGetResource(project, slug, apiHostname, credentials);
696
        tryGetPromises.push(promise);
697
        promise.then(exists => {
698 699 700 701 702 703 704 705 706 707
            if (exists) {
                promise = updateResource(project, slug, file, apiHostname, credentials);
            }
            else {
                promise = createResource(project, slug, file, apiHostname, credentials);
            }
            updateCreatePromises.push(promise);
        });
    }, function () {
        // End the pipe only after all the communication with Transifex API happened
708 709 710 711 712
        Promise.all(tryGetPromises).then(() => {
            Promise.all(updateCreatePromises).then(() => {
                this.queue(null);
            }).catch((reason) => { throw new Error(reason); });
        }).catch((reason) => { throw new Error(reason); });
713 714 715
    });
}
exports.pushXlfFiles = pushXlfFiles;
716
function getAllResources(project, apiHostname, username, password) {
717 718 719
    return new Promise((resolve, reject) => {
        const credentials = `${username}:${password}`;
        const options = {
720
            hostname: apiHostname,
721
            path: `/api/2/project/${project}/resources`,
722 723 724
            auth: credentials,
            method: 'GET'
        };
725 726 727 728
        const request = https.request(options, (res) => {
            let buffer = [];
            res.on('data', (chunk) => buffer.push(chunk));
            res.on('end', () => {
729
                if (res.statusCode === 200) {
730
                    let json = JSON.parse(Buffer.concat(buffer).toString());
731
                    if (Array.isArray(json)) {
732
                        resolve(json.map(o => o.slug));
733 734
                        return;
                    }
735
                    reject(`Unexpected data format. Response code: ${res.statusCode}.`);
736 737
                }
                else {
738
                    reject(`No resources in ${project} returned no data. Response code: ${res.statusCode}.`);
739 740 741
                }
            });
        });
742 743
        request.on('error', (err) => {
            reject(`Failed to query resources in ${project} with the following error: ${err}. ${options.path}`);
744 745 746 747 748
        });
        request.end();
    });
}
function findObsoleteResources(apiHostname, username, password) {
749
    let resourcesByProject = Object.create(null);
750
    resourcesByProject[extensionsProject] = [].concat(exports.externalExtensionsWithTranslations); // clone
751
    return event_stream_1.through(function (file) {
752 753 754 755
        const project = path.dirname(file.relative);
        const fileName = path.basename(file.path);
        const slug = fileName.substr(0, fileName.length - '.xlf'.length);
        let slugs = resourcesByProject[project];
756 757 758 759 760 761
        if (!slugs) {
            resourcesByProject[project] = slugs = [];
        }
        slugs.push(slug);
        this.push(file);
    }, function () {
762 763 764 765 766
        const json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8'));
        let i18Resources = [...json.editor, ...json.workbench].map((r) => r.project + '/' + r.name.replace(/\//g, '_'));
        let extractedResources = [];
        for (let project of [workbenchProject, editorProject]) {
            for (let resource of resourcesByProject[project]) {
767 768 769 770 771 772
                if (resource !== 'setup_messages') {
                    extractedResources.push(project + '/' + resource);
                }
            }
        }
        if (i18Resources.length !== extractedResources.length) {
773 774
            console.log(`[i18n] Obsolete resources in file 'build/lib/i18n.resources.json': JSON.stringify(${i18Resources.filter(p => extractedResources.indexOf(p) === -1)})`);
            console.log(`[i18n] Missing resources in file 'build/lib/i18n.resources.json': JSON.stringify(${extractedResources.filter(p => i18Resources.indexOf(p) === -1)})`);
775
        }
776 777 778 779 780
        let promises = [];
        for (let project in resourcesByProject) {
            promises.push(getAllResources(project, apiHostname, username, password).then(resources => {
                let expectedResources = resourcesByProject[project];
                let unusedResources = resources.filter(resource => resource && expectedResources.indexOf(resource) === -1);
781
                if (unusedResources.length) {
782
                    console.log(`[transifex] Obsolete resources in project '${project}': ${unusedResources.join(', ')}`);
783 784 785
                }
            }));
        }
786 787 788
        return Promise.all(promises).then(_ => {
            this.push(null);
        }).catch((reason) => { throw new Error(reason); });
789 790 791
    });
}
exports.findObsoleteResources = findObsoleteResources;
792
function tryGetResource(project, slug, apiHostname, credentials) {
793 794
    return new Promise((resolve, reject) => {
        const options = {
795
            hostname: apiHostname,
796
            path: `/api/2/project/${project}/resource/${slug}/?details`,
797 798 799
            auth: credentials,
            method: 'GET'
        };
800
        const request = https.request(options, (response) => {
801 802 803 804 805 806 807
            if (response.statusCode === 404) {
                resolve(false);
            }
            else if (response.statusCode === 200) {
                resolve(true);
            }
            else {
808
                reject(`Failed to query resource ${project}/${slug}. Response: ${response.statusCode} ${response.statusMessage}`);
809 810
            }
        });
811 812
        request.on('error', (err) => {
            reject(`Failed to get ${project}/${slug} on Transifex: ${err}`);
813 814 815 816 817
        });
        request.end();
    });
}
function createResource(project, slug, xlfFile, apiHostname, credentials) {
818 819
    return new Promise((_resolve, reject) => {
        const data = JSON.stringify({
820 821 822 823 824
            'content': xlfFile.contents.toString(),
            'name': slug,
            'slug': slug,
            'i18n_type': 'XLIFF'
        });
825
        const options = {
826
            hostname: apiHostname,
827
            path: `/api/2/project/${project}/resources`,
828 829 830 831 832 833 834
            headers: {
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(data)
            },
            auth: credentials,
            method: 'POST'
        };
835
        let request = https.request(options, (res) => {
836
            if (res.statusCode === 201) {
837
                log(`Resource ${project}/${slug} successfully created on Transifex.`);
838 839
            }
            else {
840
                reject(`Something went wrong in the request creating ${slug} in ${project}. ${res.statusCode}`);
841 842
            }
        });
843 844
        request.on('error', (err) => {
            reject(`Failed to create ${project}/${slug} on Transifex: ${err}`);
845 846 847 848 849 850 851 852 853 854
        });
        request.write(data);
        request.end();
    });
}
/**
 * The following link provides information about how Transifex handles updates of a resource file:
 * https://dev.befoolish.co/tx-docs/public/projects/updating-content#what-happens-when-you-update-files
 */
function updateResource(project, slug, xlfFile, apiHostname, credentials) {
855 856 857
    return new Promise((resolve, reject) => {
        const data = JSON.stringify({ content: xlfFile.contents.toString() });
        const options = {
858
            hostname: apiHostname,
859
            path: `/api/2/project/${project}/resource/${slug}/content`,
860 861 862 863 864 865 866
            headers: {
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(data)
            },
            auth: credentials,
            method: 'PUT'
        };
867
        let request = https.request(options, (res) => {
868 869
            if (res.statusCode === 200) {
                res.setEncoding('utf8');
870
                let responseBuffer = '';
871
                res.on('data', function (chunk) {
872
                    responseBuffer += chunk;
873
                });
874 875 876
                res.on('end', () => {
                    const response = JSON.parse(responseBuffer);
                    log(`Resource ${project}/${slug} successfully updated on Transifex. Strings added: ${response.strings_added}, updated: ${response.strings_added}, deleted: ${response.strings_added}`);
877 878 879 880
                    resolve();
                });
            }
            else {
881
                reject(`Something went wrong in the request updating ${slug} in ${project}. ${res.statusCode}`);
882 883
            }
        });
884 885
        request.on('error', (err) => {
            reject(`Failed to update ${project}/${slug} on Transifex: ${err}`);
886 887 888 889 890
        });
        request.write(data);
        request.end();
    });
}
D
Dirk Baeumer 已提交
891
// cache resources
892
let _coreAndExtensionResources;
893
function pullCoreAndExtensionsXlfFiles(apiHostname, username, password, language, externalExtensions) {
894 895
    if (!_coreAndExtensionResources) {
        _coreAndExtensionResources = [];
D
Dirk Baeumer 已提交
896
        // editor and workbench
897 898 899
        const json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8'));
        _coreAndExtensionResources.push(...json.editor);
        _coreAndExtensionResources.push(...json.workbench);
D
Dirk Baeumer 已提交
900
        // extensions
901 902 903 904
        let extensionsToLocalize = Object.create(null);
        glob.sync('./extensions/**/*.nls.json').forEach(extension => extensionsToLocalize[extension.split('/')[2]] = true);
        glob.sync('./extensions/*/node_modules/vscode-nls').forEach(extension => extensionsToLocalize[extension.split('/')[2]] = true);
        Object.keys(extensionsToLocalize).forEach(extension => {
905
            _coreAndExtensionResources.push({ name: extension, project: extensionsProject });
906
        });
907
        if (externalExtensions) {
908
            for (let resourceName in externalExtensions) {
909 910 911
                _coreAndExtensionResources.push({ name: resourceName, project: extensionsProject });
            }
        }
912
    }
913
    return pullXlfFiles(apiHostname, username, password, language, _coreAndExtensionResources);
914
}
915
exports.pullCoreAndExtensionsXlfFiles = pullCoreAndExtensionsXlfFiles;
D
Dirk Baeumer 已提交
916
function pullSetupXlfFiles(apiHostname, username, password, language, includeDefault) {
917
    let setupResources = [{ name: 'setup_messages', project: workbenchProject }];
D
Dirk Baeumer 已提交
918
    if (includeDefault) {
919
        setupResources.push({ name: 'setup_default', project: setupProject });
920
    }
D
Dirk Baeumer 已提交
921 922 923 924
    return pullXlfFiles(apiHostname, username, password, language, setupResources);
}
exports.pullSetupXlfFiles = pullSetupXlfFiles;
function pullXlfFiles(apiHostname, username, password, language, resources) {
925 926 927
    const credentials = `${username}:${password}`;
    let expectedTranslationsCount = resources.length;
    let translationsRetrieved = 0, called = false;
M
Matt Bierner 已提交
928
    return event_stream_1.readable(function (_count, callback) {
929 930 931 932 933 934
        // Mark end of stream when all resources were retrieved
        if (translationsRetrieved === expectedTranslationsCount) {
            return this.emit('end');
        }
        if (!called) {
            called = true;
935
            const stream = this;
D
Dirk Baeumer 已提交
936
            resources.map(function (resource) {
937
                retrieveResource(language, resource, apiHostname, credentials).then((file) => {
D
Dirk Baeumer 已提交
938
                    if (file) {
939
                        stream.emit('data', file);
D
Dirk Baeumer 已提交
940 941
                    }
                    translationsRetrieved++;
942
                }).catch(error => { throw new Error(error); });
943 944 945 946 947
            });
        }
        callback();
    });
}
948
const limiter = new Limiter(NUMBER_OF_CONCURRENT_DOWNLOADS);
949
function retrieveResource(language, resource, apiHostname, credentials) {
950 951 952 953 954
    return limiter.queue(() => new Promise((resolve, reject) => {
        const slug = resource.name.replace(/\//g, '_');
        const project = resource.project;
        let transifexLanguageId = language.id === 'ps' ? 'en' : language.transifexId || language.id;
        const options = {
955
            hostname: apiHostname,
956
            path: `/api/2/project/${project}/resource/${slug}/translation/${transifexLanguageId}?file&mode=onlyreviewed`,
957
            auth: credentials,
958
            port: 443,
959 960
            method: 'GET'
        };
961
        console.log('[transifex] Fetching ' + options.path);
962 963 964 965
        let request = https.request(options, (res) => {
            let xlfBuffer = [];
            res.on('data', (chunk) => xlfBuffer.push(chunk));
            res.on('end', () => {
966
                if (res.statusCode === 200) {
967
                    resolve(new File({ contents: Buffer.concat(xlfBuffer), path: `${project}/${slug}.xlf` }));
D
Dirk Baeumer 已提交
968 969
                }
                else if (res.statusCode === 404) {
970
                    console.log(`[transifex] ${slug} in ${project} returned no data.`);
D
Dirk Baeumer 已提交
971 972 973
                    resolve(null);
                }
                else {
974
                    reject(`${slug} in ${project} returned no data. Response code: ${res.statusCode}.`);
975 976 977
                }
            });
        });
978 979
        request.on('error', (err) => {
            reject(`Failed to query resource ${slug} with the following error: ${err}. ${options.path}`);
980 981
        });
        request.end();
982
    }));
983
}
D
Dirk Baeumer 已提交
984
function prepareI18nFiles() {
985
    let parsePromises = [];
986
    return event_stream_1.through(function (xlf) {
987 988
        let stream = this;
        let parsePromise = XLF.parse(xlf.contents.toString());
989
        parsePromises.push(parsePromise);
990 991 992
        parsePromise.then(resolvedFiles => {
            resolvedFiles.forEach(file => {
                let translatedFile = createI18nFile(file.originalFilePath, file.messages);
D
Dirk Baeumer 已提交
993 994 995 996 997
                stream.queue(translatedFile);
            });
        });
    }, function () {
        Promise.all(parsePromises)
998 999
            .then(() => { this.queue(null); })
            .catch(reason => { throw new Error(reason); });
D
Dirk Baeumer 已提交
1000 1001 1002 1003
    });
}
exports.prepareI18nFiles = prepareI18nFiles;
function createI18nFile(originalFilePath, messages) {
1004
    let result = Object.create(null);
D
Dirk Baeumer 已提交
1005 1006 1007 1008 1009 1010 1011
    result[''] = [
        '--------------------------------------------------------------------------------------------',
        'Copyright (c) Microsoft Corporation. All rights reserved.',
        'Licensed under the MIT License. See License.txt in the project root for license information.',
        '--------------------------------------------------------------------------------------------',
        'Do not edit this file. It is machine generated.'
    ];
1012
    for (let key of Object.keys(messages)) {
D
Dirk Baeumer 已提交
1013 1014
        result[key] = messages[key];
    }
1015
    let content = JSON.stringify(result, null, '\t');
D
Dirk Baeumer 已提交
1016
    if (process.platform === 'win32') {
D
Dirk Baeumer 已提交
1017
        content = content.replace(/\n/g, '\r\n');
D
Dirk Baeumer 已提交
1018
    }
D
Dirk Baeumer 已提交
1019 1020
    return new File({
        path: path.join(originalFilePath + '.i18n.json'),
J
Joao Moreno 已提交
1021
        contents: Buffer.from(content, 'utf8')
D
Dirk Baeumer 已提交
1022 1023
    });
}
1024
const i18nPackVersion = "1.0.0";
1025
function pullI18nPackFiles(apiHostname, username, password, language, resultingTranslationPaths) {
1026 1027
    return pullCoreAndExtensionsXlfFiles(apiHostname, username, password, language, exports.externalExtensionsWithTranslations)
        .pipe(prepareI18nPackFiles(exports.externalExtensionsWithTranslations, resultingTranslationPaths, language.id === 'ps'));
D
Dirk Baeumer 已提交
1028 1029
}
exports.pullI18nPackFiles = pullI18nPackFiles;
1030 1031 1032 1033
function prepareI18nPackFiles(externalExtensions, resultingTranslationPaths, pseudo = false) {
    let parsePromises = [];
    let mainPack = { version: i18nPackVersion, contents: {} };
    let extensionsPacks = {};
1034
    let errors = [];
D
Dirk Baeumer 已提交
1035
    return event_stream_1.through(function (xlf) {
1036 1037
        let project = path.dirname(xlf.relative);
        let resource = path.basename(xlf.relative, '.xlf');
1038 1039
        let contents = xlf.contents.toString();
        let parsePromise = pseudo ? XLF.parsePseudo(contents) : XLF.parse(contents);
D
Dirk Baeumer 已提交
1040
        parsePromises.push(parsePromise);
1041 1042 1043 1044
        parsePromise.then(resolvedFiles => {
            resolvedFiles.forEach(file => {
                const path = file.originalFilePath;
                const firstSlash = path.indexOf('/');
1045
                if (project === extensionsProject) {
1046
                    let extPack = extensionsPacks[resource];
1047 1048
                    if (!extPack) {
                        extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} };
1049
                    }
1050
                    const externalId = externalExtensions[resource];
J
Joao Moreno 已提交
1051
                    if (!externalId) { // internal extension: remove 'extensions/extensionId/' segnent
1052
                        const secondSlash = path.indexOf('/', firstSlash + 1);
1053 1054 1055 1056 1057
                        extPack.contents[path.substr(secondSlash + 1)] = file.messages;
                    }
                    else {
                        extPack.contents[path] = file.messages;
                    }
1058 1059
                }
                else {
1060
                    mainPack.contents[path.substr(firstSlash + 1)] = file.messages;
1061 1062
                }
            });
1063 1064
        }).catch(reason => {
            errors.push(reason);
1065 1066 1067
        });
    }, function () {
        Promise.all(parsePromises)
1068
            .then(() => {
1069 1070 1071
            if (errors.length > 0) {
                throw errors;
            }
1072
            const translatedMainFile = createI18nFile('./main', mainPack);
1073
            resultingTranslationPaths.push({ id: 'vscode', resourceName: 'main.i18n.json' });
1074 1075 1076 1077 1078
            this.queue(translatedMainFile);
            for (let extension in extensionsPacks) {
                const translatedExtFile = createI18nFile(`./extensions/${extension}`, extensionsPacks[extension]);
                this.queue(translatedExtFile);
                const externalExtensionId = externalExtensions[extension];
1079
                if (externalExtensionId) {
1080
                    resultingTranslationPaths.push({ id: externalExtensionId, resourceName: `extensions/${extension}.i18n.json` });
1081 1082
                }
                else {
1083
                    resultingTranslationPaths.push({ id: `vscode.${extension}`, resourceName: `extensions/${extension}.i18n.json` });
1084
                }
D
Dirk Baeumer 已提交
1085
            }
1086
            this.queue(null);
D
Dirk Baeumer 已提交
1087
        })
1088 1089 1090
            .catch((reason) => {
            this.emit('error', reason);
        });
1091 1092
    });
}
D
Dirk Baeumer 已提交
1093 1094
exports.prepareI18nPackFiles = prepareI18nPackFiles;
function prepareIslFiles(language, innoSetupConfig) {
1095
    let parsePromises = [];
D
Dirk Baeumer 已提交
1096
    return event_stream_1.through(function (xlf) {
1097 1098
        let stream = this;
        let parsePromise = XLF.parse(xlf.contents.toString());
D
Dirk Baeumer 已提交
1099
        parsePromises.push(parsePromise);
1100 1101
        parsePromise.then(resolvedFiles => {
            resolvedFiles.forEach(file => {
D
Dirk Baeumer 已提交
1102 1103 1104
                if (path.basename(file.originalFilePath) === 'Default' && !innoSetupConfig.defaultInfo) {
                    return;
                }
1105
                let translatedFile = createIslFile(file.originalFilePath, file.messages, language, innoSetupConfig);
D
Dirk Baeumer 已提交
1106 1107
                stream.queue(translatedFile);
            });
1108 1109
        }).catch(reason => {
            this.emit('error', reason);
D
Dirk Baeumer 已提交
1110 1111 1112
        });
    }, function () {
        Promise.all(parsePromises)
1113
            .then(() => { this.queue(null); })
1114 1115 1116
            .catch(reason => {
            this.emit('error', reason);
        });
1117 1118
    });
}
D
Dirk Baeumer 已提交
1119 1120
exports.prepareIslFiles = prepareIslFiles;
function createIslFile(originalFilePath, messages, language, innoSetup) {
1121 1122
    let content = [];
    let originalContent;
1123 1124 1125 1126 1127 1128
    if (path.basename(originalFilePath) === 'Default') {
        originalContent = new TextModel(fs.readFileSync(originalFilePath + '.isl', 'utf8'));
    }
    else {
        originalContent = new TextModel(fs.readFileSync(originalFilePath + '.en.isl', 'utf8'));
    }
1129
    originalContent.lines.forEach(line => {
1130
        if (line.length > 0) {
1131
            let firstChar = line.charAt(0);
1132 1133
            if (firstChar === '[' || firstChar === ';') {
                if (line === '; *** Inno Setup version 5.5.3+ English messages ***') {
1134
                    content.push(`; *** Inno Setup version 5.5.3+ ${innoSetup.defaultInfo.name} messages ***`);
1135 1136 1137 1138 1139 1140
                }
                else {
                    content.push(line);
                }
            }
            else {
1141 1142 1143
                let sections = line.split('=');
                let key = sections[0];
                let translated = line;
1144 1145
                if (key) {
                    if (key === 'LanguageName') {
1146
                        translated = `${key}=${innoSetup.defaultInfo.name}`;
1147 1148
                    }
                    else if (key === 'LanguageID') {
1149
                        translated = `${key}=${innoSetup.defaultInfo.id}`;
1150 1151
                    }
                    else if (key === 'LanguageCodePage') {
1152
                        translated = `${key}=${innoSetup.codePage.substr(2)}`;
1153 1154
                    }
                    else {
1155
                        let translatedMessage = messages[key];
1156
                        if (translatedMessage) {
1157
                            translated = `${key}=${translatedMessage}`;
1158 1159 1160 1161 1162 1163 1164
                        }
                    }
                }
                content.push(translated);
            }
        }
    });
1165 1166
    const basename = path.basename(originalFilePath);
    const filePath = `${basename}.${language.id}.isl`;
1167 1168
    return new File({
        path: filePath,
1169
        contents: iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage)
1170 1171 1172
    });
}
function encodeEntities(value) {
1173 1174 1175
    let result = [];
    for (let i = 0; i < value.length; i++) {
        let ch = value[i];
1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
        switch (ch) {
            case '<':
                result.push('&lt;');
                break;
            case '>':
                result.push('&gt;');
                break;
            case '&':
                result.push('&amp;');
                break;
            default:
                result.push(ch);
        }
    }
    return result.join('');
}
function decodeEntities(value) {
    return value.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
}
1195 1196 1197
function pseudify(message) {
    return '\uFF3B' + message.replace(/[aouei]/g, '$&$&') + '\uFF3D';
}