i18n.js 48.5 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
exports.defaultLanguages = [
24 25
    { id: 'zh-tw', folderName: 'cht', translationId: 'zh-hant' },
    { id: 'zh-cn', folderName: 'chs', translationId: 'zh-hans' },
D
Dirk Baeumer 已提交
26 27 28 29 30 31 32 33 34 35 36 37 38 39
    { 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
    }
}
M
Matt Bierner 已提交
179
exports.XLF = XLF;
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
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' });
                }
200
            });
201
            resolve(files);
202
        });
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
    });
};
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.`));
221
                }
222 223 224 225 226 227 228 229 230 231 232 233
                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
                        }
234 235 236 237
                        let val = unit.target[0];
                        if (typeof val !== 'string') {
                            val = val._;
                        }
238 239 240 241
                        if (key && val) {
                            messages[key] = decodeEntities(val);
                        }
                        else {
242
                            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.`));
243 244 245
                        }
                    });
                    files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() });
246 247
                }
            });
248
            resolve(files);
249
        });
250 251 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 378 379
    let languageDirectory = path.join(__dirname, '..', '..', '..', '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.`);
    }
380 381
    let sortedLanguages = sortLanguages(languages);
    sortedLanguages.forEach((language) => {
382
        if (process.env['VSCODE_BUILD_VERBOSE']) {
383
            log(`Generating nls bundles for: ${language.id}`);
384
        }
D
Dirk Baeumer 已提交
385
        statistics[language.id] = 0;
386
        let localizedModules = Object.create(null);
387 388 389 390 391 392 393
        let languageFolderName = language.translationId || language.id;
        let i18nFile = path.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json');
        let allMessages;
        if (fs.existsSync(i18nFile)) {
            let content = stripComments(fs.readFileSync(i18nFile, 'utf8'));
            allMessages = JSON.parse(content);
        }
394 395
        modules.forEach((module) => {
            let order = keysSection[module];
396 397 398
            let moduleMessage;
            if (allMessages) {
                moduleMessage = allMessages.contents[module];
399
            }
400
            if (!moduleMessage) {
401
                if (process.env['VSCODE_BUILD_VERBOSE']) {
402
                    log(`No localized messages found for module ${module}. Using default messages.`);
403
                }
404 405
                moduleMessage = defaultMessages[module];
                statistics[language.id] = statistics[language.id] + Object.keys(moduleMessage).length;
406
            }
407 408 409
            let localizedMessages = [];
            order.forEach((keyInfo) => {
                let key = null;
D
Dirk Baeumer 已提交
410
                if (typeof keyInfo === 'string') {
411 412 413 414 415
                    key = keyInfo;
                }
                else {
                    key = keyInfo.key;
                }
416
                let message = moduleMessage[key];
417 418
                if (!message) {
                    if (process.env['VSCODE_BUILD_VERBOSE']) {
419
                        log(`No localized message found for key ${key} in module ${module}. Using default message.`);
420 421
                    }
                    message = defaultMessages[module][key];
D
Dirk Baeumer 已提交
422
                    statistics[language.id] = statistics[language.id] + 1;
423 424 425 426 427
                }
                localizedMessages.push(message);
            });
            localizedModules[module] = localizedMessages;
        });
428 429 430
        Object.keys(bundleSection).forEach((bundle) => {
            let modules = bundleSection[bundle];
            let contents = [
431
                fileHeader,
432
                `define("${bundle}.nls.${language.id}", {`
433
            ];
434 435 436
            modules.forEach((module, index) => {
                contents.push(`\t"${module}": [`);
                let messages = localizedModules[module];
437
                if (!messages) {
438
                    emitter.emit('error', `Didn't find messages for module ${module}.`);
439 440
                    return;
                }
441 442
                messages.forEach((message, index) => {
                    contents.push(`\t\t"${escapeCharacters(message)}${index < messages.length ? '",' : '"'}`);
443 444 445 446
                });
                contents.push(index < modules.length - 1 ? '\t],' : '\t]');
            });
            contents.push('});');
J
Joao Moreno 已提交
447
            emitter.queue(new File({ path: bundle + '.nls.' + language.id + '.js', contents: Buffer.from(contents.join('\n'), 'utf-8') }));
448 449
        });
    });
450 451 452
    Object.keys(statistics).forEach(key => {
        let value = statistics[key];
        log(`${key} has ${value} untranslated strings.`);
453
    });
454 455
    sortedLanguages.forEach(language => {
        let stats = statistics[language.id];
D
Dirk Baeumer 已提交
456
        if (Is.undef(stats)) {
457
            log(`\tNo translations found for language ${language.id}. Using default language instead.`);
458 459 460 461 462
        }
    });
}
function processNlsFiles(opts) {
    return event_stream_1.through(function (file) {
463
        let fileName = path.basename(file.path);
464
        if (fileName === 'nls.metadata.json') {
465
            let json = null;
466 467 468 469
            if (file.isBuffer()) {
                json = JSON.parse(file.contents.toString('utf8'));
            }
            else {
470
                this.emit('error', `Failed to read component file: ${file.relative}`);
D
Dirk Baeumer 已提交
471
                return;
472 473 474 475 476
            }
            if (BundledFormat.is(json)) {
                processCoreBundleFormat(opts.fileHeader, opts.languages, json, this);
            }
        }
D
Dirk Baeumer 已提交
477
        this.queue(file);
478 479 480
    });
}
exports.processNlsFiles = processNlsFiles;
481
const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup';
482
function getResource(sourceFile) {
483
    let resource;
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
    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 };
    }
B
Benjamin Pasero 已提交
499
    else if (/^vs\/workbench\/contrib/.test(sourceFile)) {
500 501 502 503 504 505 506 507 508 509
        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 };
    }
510
    throw new Error(`Could not identify the XLF bundle for ${sourceFile}`);
511 512
}
exports.getResource = getResource;
D
Dirk Baeumer 已提交
513 514
function createXlfFilesForCoreBundle() {
    return event_stream_1.through(function (file) {
515
        const basename = path.basename(file.path);
D
Dirk Baeumer 已提交
516 517
        if (basename === 'nls.metadata.json') {
            if (file.isBuffer()) {
518 519 520 521 522 523 524 525
                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 已提交
526
                    if (keys.length !== messages.length) {
527
                        this.emit('error', `There is a mismatch between keys and messages in ${file.relative} for module ${coreModule}`);
D
Dirk Baeumer 已提交
528 529 530
                        return;
                    }
                    else {
531
                        let xlf = xlfs[resource];
D
Dirk Baeumer 已提交
532 533 534 535
                        if (!xlf) {
                            xlf = new XLF(project);
                            xlfs[resource] = xlf;
                        }
536
                        xlf.addFile(`src/${coreModule}`, keys, messages);
D
Dirk Baeumer 已提交
537 538
                    }
                }
539 540 541 542
                for (let resource in xlfs) {
                    const xlf = xlfs[resource];
                    const filePath = `${xlf.project}/${resource.replace(/\//g, '_')}.xlf`;
                    const xlfFile = new File({
D
Dirk Baeumer 已提交
543
                        path: filePath,
J
Joao Moreno 已提交
544
                        contents: Buffer.from(xlf.toString(), 'utf8')
D
Dirk Baeumer 已提交
545 546 547 548 549
                    });
                    this.queue(xlfFile);
                }
            }
            else {
550
                this.emit('error', new Error(`File ${file.relative} is not using a buffer content`));
D
Dirk Baeumer 已提交
551 552
                return;
            }
553
        }
D
Dirk Baeumer 已提交
554
        else {
555
            this.emit('error', new Error(`File ${file.relative} is not a core meta data file.`));
556 557
            return;
        }
D
Dirk Baeumer 已提交
558 559 560 561
    });
}
exports.createXlfFilesForCoreBundle = createXlfFilesForCoreBundle;
function createXlfFilesForExtensions() {
562 563 564
    let counter = 0;
    let folderStreamEnded = false;
    let folderStreamEndEmitted = false;
D
Dirk Baeumer 已提交
565
    return event_stream_1.through(function (extensionFolder) {
566 567
        const folderStream = this;
        const stat = fs.statSync(extensionFolder.path);
D
Dirk Baeumer 已提交
568 569
        if (!stat.isDirectory()) {
            return;
570
        }
571
        let extensionName = path.basename(extensionFolder.path);
D
Dirk Baeumer 已提交
572
        if (extensionName === 'node_modules') {
573 574
            return;
        }
D
Dirk Baeumer 已提交
575
        counter++;
576
        let _xlf;
D
Dirk Baeumer 已提交
577 578 579 580 581
        function getXlf() {
            if (!_xlf) {
                _xlf = new XLF(extensionsProject);
            }
            return _xlf;
582
        }
583
        gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`], { allowEmpty: true }).pipe(event_stream_1.through(function (file) {
D
Dirk Baeumer 已提交
584
            if (file.isBuffer()) {
585 586
                const buffer = file.contents;
                const basename = path.basename(file.path);
D
Dirk Baeumer 已提交
587
                if (basename === 'package.nls.json') {
588 589 590 591
                    const json = JSON.parse(buffer.toString('utf8'));
                    const keys = Object.keys(json);
                    const messages = keys.map((key) => {
                        const value = json[key];
D
Dirk Baeumer 已提交
592 593 594 595 596 597 598
                        if (Is.string(value)) {
                            return value;
                        }
                        else if (value) {
                            return value.message;
                        }
                        else {
599
                            return `Unknown message for key: ${key}`;
D
Dirk Baeumer 已提交
600 601
                        }
                    });
602
                    getXlf().addFile(`extensions/${extensionName}/package`, keys, messages);
D
Dirk Baeumer 已提交
603 604
                }
                else if (basename === 'nls.metadata.json') {
605 606 607 608 609
                    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 已提交
610 611 612
                    }
                }
                else {
613
                    this.emit('error', new Error(`${file.path} is not a valid extension nls file`));
D
Dirk Baeumer 已提交
614 615 616 617 618
                    return;
                }
            }
        }, function () {
            if (_xlf) {
619
                let xlfFile = new File({
D
Dirk Baeumer 已提交
620
                    path: path.join(extensionsProject, extensionName + '.xlf'),
J
Joao Moreno 已提交
621
                    contents: Buffer.from(_xlf.toString(), 'utf8')
D
Dirk Baeumer 已提交
622 623 624 625 626 627 628 629
                });
                folderStream.queue(xlfFile);
            }
            this.queue(null);
            counter--;
            if (counter === 0 && folderStreamEnded && !folderStreamEndEmitted) {
                folderStreamEndEmitted = true;
                folderStream.queue(null);
630
            }
D
Dirk Baeumer 已提交
631 632 633 634 635 636
        }));
    }, function () {
        folderStreamEnded = true;
        if (counter === 0) {
            folderStreamEndEmitted = true;
            this.queue(null);
637 638 639
        }
    });
}
D
Dirk Baeumer 已提交
640 641 642
exports.createXlfFilesForExtensions = createXlfFilesForExtensions;
function createXlfFilesForIsl() {
    return event_stream_1.through(function (file) {
643
        let projectName, resourceFile;
D
Dirk Baeumer 已提交
644 645 646 647 648 649 650 651
        if (path.basename(file.path) === 'Default.isl') {
            projectName = setupProject;
            resourceFile = 'setup_default.xlf';
        }
        else {
            projectName = workbenchProject;
            resourceFile = 'setup_messages.xlf';
        }
652 653 654 655
        let xlf = new XLF(projectName), keys = [], messages = [];
        let model = new TextModel(file.contents.toString());
        let inMessageSection = false;
        model.lines.forEach(line => {
D
Dirk Baeumer 已提交
656 657 658
            if (line.length === 0) {
                return;
            }
659
            let firstChar = line.charAt(0);
D
Dirk Baeumer 已提交
660 661 662 663 664 665 666 667 668 669 670
            switch (firstChar) {
                case ';':
                    // Comment line;
                    return;
                case '[':
                    inMessageSection = '[Messages]' === line || '[CustomMessages]' === line;
                    return;
            }
            if (!inMessageSection) {
                return;
            }
671
            let sections = line.split('=');
D
Dirk Baeumer 已提交
672
            if (sections.length !== 2) {
673
                throw new Error(`Badly formatted message found: ${line}`);
D
Dirk Baeumer 已提交
674 675
            }
            else {
676 677
                let key = sections[0];
                let value = sections[1];
D
Dirk Baeumer 已提交
678 679 680 681 682 683
                if (key.length > 0 && value.length > 0) {
                    keys.push(key);
                    messages.push(value);
                }
            }
        });
684
        const originalPath = file.path.substring(file.cwd.length + 1, file.path.split('.')[0].length).replace(/\\/g, '/');
D
Dirk Baeumer 已提交
685 686
        xlf.addFile(originalPath, keys, messages);
        // Emit only upon all ISL files combined into single XLF instance
687 688
        const newFilePath = path.join(projectName, resourceFile);
        const xlfFile = new File({ path: newFilePath, contents: Buffer.from(xlf.toString(), 'utf-8') });
D
Dirk Baeumer 已提交
689 690 691 692
        this.queue(xlfFile);
    });
}
exports.createXlfFilesForIsl = createXlfFilesForIsl;
693
function pushXlfFiles(apiHostname, username, password) {
694 695
    let tryGetPromises = [];
    let updateCreatePromises = [];
696
    return event_stream_1.through(function (file) {
697 698 699 700
        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}`;
701
        // Check if resource already exists, if not, then create it.
702
        let promise = tryGetResource(project, slug, apiHostname, credentials);
703
        tryGetPromises.push(promise);
704
        promise.then(exists => {
705 706 707 708 709 710 711 712 713 714
            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
715 716 717 718 719
        Promise.all(tryGetPromises).then(() => {
            Promise.all(updateCreatePromises).then(() => {
                this.queue(null);
            }).catch((reason) => { throw new Error(reason); });
        }).catch((reason) => { throw new Error(reason); });
720 721 722
    });
}
exports.pushXlfFiles = pushXlfFiles;
723
function getAllResources(project, apiHostname, username, password) {
724 725 726
    return new Promise((resolve, reject) => {
        const credentials = `${username}:${password}`;
        const options = {
727
            hostname: apiHostname,
728
            path: `/api/2/project/${project}/resources`,
729 730 731
            auth: credentials,
            method: 'GET'
        };
732 733 734 735
        const request = https.request(options, (res) => {
            let buffer = [];
            res.on('data', (chunk) => buffer.push(chunk));
            res.on('end', () => {
736
                if (res.statusCode === 200) {
737
                    let json = JSON.parse(Buffer.concat(buffer).toString());
738
                    if (Array.isArray(json)) {
739
                        resolve(json.map(o => o.slug));
740 741
                        return;
                    }
742
                    reject(`Unexpected data format. Response code: ${res.statusCode}.`);
743 744
                }
                else {
745
                    reject(`No resources in ${project} returned no data. Response code: ${res.statusCode}.`);
746 747 748
                }
            });
        });
749 750
        request.on('error', (err) => {
            reject(`Failed to query resources in ${project} with the following error: ${err}. ${options.path}`);
751 752 753 754 755
        });
        request.end();
    });
}
function findObsoleteResources(apiHostname, username, password) {
756
    let resourcesByProject = Object.create(null);
757
    resourcesByProject[extensionsProject] = [].concat(exports.externalExtensionsWithTranslations); // clone
758
    return event_stream_1.through(function (file) {
759 760 761 762
        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];
763 764 765 766 767 768
        if (!slugs) {
            resourcesByProject[project] = slugs = [];
        }
        slugs.push(slug);
        this.push(file);
    }, function () {
769 770 771 772 773
        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]) {
774 775 776 777 778 779
                if (resource !== 'setup_messages') {
                    extractedResources.push(project + '/' + resource);
                }
            }
        }
        if (i18Resources.length !== extractedResources.length) {
780 781
            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)})`);
782
        }
783 784 785 786 787
        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);
788
                if (unusedResources.length) {
789
                    console.log(`[transifex] Obsolete resources in project '${project}': ${unusedResources.join(', ')}`);
790 791 792
                }
            }));
        }
793 794 795
        return Promise.all(promises).then(_ => {
            this.push(null);
        }).catch((reason) => { throw new Error(reason); });
796 797 798
    });
}
exports.findObsoleteResources = findObsoleteResources;
799
function tryGetResource(project, slug, apiHostname, credentials) {
800 801
    return new Promise((resolve, reject) => {
        const options = {
802
            hostname: apiHostname,
803
            path: `/api/2/project/${project}/resource/${slug}/?details`,
804 805 806
            auth: credentials,
            method: 'GET'
        };
807
        const request = https.request(options, (response) => {
808 809 810 811 812 813 814
            if (response.statusCode === 404) {
                resolve(false);
            }
            else if (response.statusCode === 200) {
                resolve(true);
            }
            else {
815
                reject(`Failed to query resource ${project}/${slug}. Response: ${response.statusCode} ${response.statusMessage}`);
816 817
            }
        });
818 819
        request.on('error', (err) => {
            reject(`Failed to get ${project}/${slug} on Transifex: ${err}`);
820 821 822 823 824
        });
        request.end();
    });
}
function createResource(project, slug, xlfFile, apiHostname, credentials) {
825 826
    return new Promise((_resolve, reject) => {
        const data = JSON.stringify({
827 828 829 830 831
            'content': xlfFile.contents.toString(),
            'name': slug,
            'slug': slug,
            'i18n_type': 'XLIFF'
        });
832
        const options = {
833
            hostname: apiHostname,
834
            path: `/api/2/project/${project}/resources`,
835 836 837 838 839 840 841
            headers: {
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(data)
            },
            auth: credentials,
            method: 'POST'
        };
842
        let request = https.request(options, (res) => {
843
            if (res.statusCode === 201) {
844
                log(`Resource ${project}/${slug} successfully created on Transifex.`);
845 846
            }
            else {
847
                reject(`Something went wrong in the request creating ${slug} in ${project}. ${res.statusCode}`);
848 849
            }
        });
850 851
        request.on('error', (err) => {
            reject(`Failed to create ${project}/${slug} on Transifex: ${err}`);
852 853 854 855 856 857 858 859 860 861
        });
        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) {
862 863 864
    return new Promise((resolve, reject) => {
        const data = JSON.stringify({ content: xlfFile.contents.toString() });
        const options = {
865
            hostname: apiHostname,
866
            path: `/api/2/project/${project}/resource/${slug}/content`,
867 868 869 870 871 872 873
            headers: {
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(data)
            },
            auth: credentials,
            method: 'PUT'
        };
874
        let request = https.request(options, (res) => {
875 876
            if (res.statusCode === 200) {
                res.setEncoding('utf8');
877
                let responseBuffer = '';
878
                res.on('data', function (chunk) {
879
                    responseBuffer += chunk;
880
                });
881 882 883
                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}`);
884 885 886 887
                    resolve();
                });
            }
            else {
888
                reject(`Something went wrong in the request updating ${slug} in ${project}. ${res.statusCode}`);
889 890
            }
        });
891 892
        request.on('error', (err) => {
            reject(`Failed to update ${project}/${slug} on Transifex: ${err}`);
893 894 895 896 897
        });
        request.write(data);
        request.end();
    });
}
D
Dirk Baeumer 已提交
898
// cache resources
899
let _coreAndExtensionResources;
900
function pullCoreAndExtensionsXlfFiles(apiHostname, username, password, language, externalExtensions) {
901 902
    if (!_coreAndExtensionResources) {
        _coreAndExtensionResources = [];
D
Dirk Baeumer 已提交
903
        // editor and workbench
904 905 906
        const json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8'));
        _coreAndExtensionResources.push(...json.editor);
        _coreAndExtensionResources.push(...json.workbench);
D
Dirk Baeumer 已提交
907
        // extensions
908 909 910 911
        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 => {
912
            _coreAndExtensionResources.push({ name: extension, project: extensionsProject });
913
        });
914
        if (externalExtensions) {
915
            for (let resourceName in externalExtensions) {
916 917 918
                _coreAndExtensionResources.push({ name: resourceName, project: extensionsProject });
            }
        }
919
    }
920
    return pullXlfFiles(apiHostname, username, password, language, _coreAndExtensionResources);
921
}
922
exports.pullCoreAndExtensionsXlfFiles = pullCoreAndExtensionsXlfFiles;
D
Dirk Baeumer 已提交
923
function pullSetupXlfFiles(apiHostname, username, password, language, includeDefault) {
924
    let setupResources = [{ name: 'setup_messages', project: workbenchProject }];
D
Dirk Baeumer 已提交
925
    if (includeDefault) {
926
        setupResources.push({ name: 'setup_default', project: setupProject });
927
    }
D
Dirk Baeumer 已提交
928 929 930 931
    return pullXlfFiles(apiHostname, username, password, language, setupResources);
}
exports.pullSetupXlfFiles = pullSetupXlfFiles;
function pullXlfFiles(apiHostname, username, password, language, resources) {
932 933 934
    const credentials = `${username}:${password}`;
    let expectedTranslationsCount = resources.length;
    let translationsRetrieved = 0, called = false;
M
Matt Bierner 已提交
935
    return event_stream_1.readable(function (_count, callback) {
936 937 938 939 940 941
        // Mark end of stream when all resources were retrieved
        if (translationsRetrieved === expectedTranslationsCount) {
            return this.emit('end');
        }
        if (!called) {
            called = true;
942
            const stream = this;
D
Dirk Baeumer 已提交
943
            resources.map(function (resource) {
944
                retrieveResource(language, resource, apiHostname, credentials).then((file) => {
D
Dirk Baeumer 已提交
945
                    if (file) {
946
                        stream.emit('data', file);
D
Dirk Baeumer 已提交
947 948
                    }
                    translationsRetrieved++;
949
                }).catch(error => { throw new Error(error); });
950 951 952 953 954
            });
        }
        callback();
    });
}
955
const limiter = new Limiter(NUMBER_OF_CONCURRENT_DOWNLOADS);
956
function retrieveResource(language, resource, apiHostname, credentials) {
957 958 959
    return limiter.queue(() => new Promise((resolve, reject) => {
        const slug = resource.name.replace(/\//g, '_');
        const project = resource.project;
960
        let transifexLanguageId = language.id === 'ps' ? 'en' : language.translationId || language.id;
961
        const options = {
962
            hostname: apiHostname,
963
            path: `/api/2/project/${project}/resource/${slug}/translation/${transifexLanguageId}?file&mode=onlyreviewed`,
964
            auth: credentials,
965
            port: 443,
966 967
            method: 'GET'
        };
968
        console.log('[transifex] Fetching ' + options.path);
969 970 971 972
        let request = https.request(options, (res) => {
            let xlfBuffer = [];
            res.on('data', (chunk) => xlfBuffer.push(chunk));
            res.on('end', () => {
973
                if (res.statusCode === 200) {
974
                    resolve(new File({ contents: Buffer.concat(xlfBuffer), path: `${project}/${slug}.xlf` }));
D
Dirk Baeumer 已提交
975 976
                }
                else if (res.statusCode === 404) {
977
                    console.log(`[transifex] ${slug} in ${project} returned no data.`);
D
Dirk Baeumer 已提交
978 979 980
                    resolve(null);
                }
                else {
981
                    reject(`${slug} in ${project} returned no data. Response code: ${res.statusCode}.`);
982 983 984
                }
            });
        });
985 986
        request.on('error', (err) => {
            reject(`Failed to query resource ${slug} with the following error: ${err}. ${options.path}`);
987 988
        });
        request.end();
989
    }));
990
}
D
Dirk Baeumer 已提交
991
function prepareI18nFiles() {
992
    let parsePromises = [];
993
    return event_stream_1.through(function (xlf) {
994 995
        let stream = this;
        let parsePromise = XLF.parse(xlf.contents.toString());
996
        parsePromises.push(parsePromise);
997 998 999
        parsePromise.then(resolvedFiles => {
            resolvedFiles.forEach(file => {
                let translatedFile = createI18nFile(file.originalFilePath, file.messages);
D
Dirk Baeumer 已提交
1000 1001 1002 1003 1004
                stream.queue(translatedFile);
            });
        });
    }, function () {
        Promise.all(parsePromises)
1005 1006
            .then(() => { this.queue(null); })
            .catch(reason => { throw new Error(reason); });
D
Dirk Baeumer 已提交
1007 1008 1009 1010
    });
}
exports.prepareI18nFiles = prepareI18nFiles;
function createI18nFile(originalFilePath, messages) {
1011
    let result = Object.create(null);
D
Dirk Baeumer 已提交
1012 1013 1014 1015 1016 1017 1018
    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.'
    ];
1019
    for (let key of Object.keys(messages)) {
D
Dirk Baeumer 已提交
1020 1021
        result[key] = messages[key];
    }
1022
    let content = JSON.stringify(result, null, '\t');
D
Dirk Baeumer 已提交
1023
    if (process.platform === 'win32') {
D
Dirk Baeumer 已提交
1024
        content = content.replace(/\n/g, '\r\n');
D
Dirk Baeumer 已提交
1025
    }
D
Dirk Baeumer 已提交
1026 1027
    return new File({
        path: path.join(originalFilePath + '.i18n.json'),
J
Joao Moreno 已提交
1028
        contents: Buffer.from(content, 'utf8')
D
Dirk Baeumer 已提交
1029 1030
    });
}
1031
const i18nPackVersion = "1.0.0";
1032
function pullI18nPackFiles(apiHostname, username, password, language, resultingTranslationPaths) {
1033 1034
    return pullCoreAndExtensionsXlfFiles(apiHostname, username, password, language, exports.externalExtensionsWithTranslations)
        .pipe(prepareI18nPackFiles(exports.externalExtensionsWithTranslations, resultingTranslationPaths, language.id === 'ps'));
D
Dirk Baeumer 已提交
1035 1036
}
exports.pullI18nPackFiles = pullI18nPackFiles;
1037 1038 1039 1040
function prepareI18nPackFiles(externalExtensions, resultingTranslationPaths, pseudo = false) {
    let parsePromises = [];
    let mainPack = { version: i18nPackVersion, contents: {} };
    let extensionsPacks = {};
1041
    let errors = [];
D
Dirk Baeumer 已提交
1042
    return event_stream_1.through(function (xlf) {
1043
        let project = path.basename(path.dirname(xlf.relative));
1044
        let resource = path.basename(xlf.relative, '.xlf');
1045 1046
        let contents = xlf.contents.toString();
        let parsePromise = pseudo ? XLF.parsePseudo(contents) : XLF.parse(contents);
D
Dirk Baeumer 已提交
1047
        parsePromises.push(parsePromise);
1048 1049 1050 1051
        parsePromise.then(resolvedFiles => {
            resolvedFiles.forEach(file => {
                const path = file.originalFilePath;
                const firstSlash = path.indexOf('/');
1052
                if (project === extensionsProject) {
1053
                    let extPack = extensionsPacks[resource];
1054 1055
                    if (!extPack) {
                        extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} };
1056
                    }
1057
                    const externalId = externalExtensions[resource];
J
Joao Moreno 已提交
1058
                    if (!externalId) { // internal extension: remove 'extensions/extensionId/' segnent
1059
                        const secondSlash = path.indexOf('/', firstSlash + 1);
1060 1061 1062 1063 1064
                        extPack.contents[path.substr(secondSlash + 1)] = file.messages;
                    }
                    else {
                        extPack.contents[path] = file.messages;
                    }
1065 1066
                }
                else {
1067
                    mainPack.contents[path.substr(firstSlash + 1)] = file.messages;
1068 1069
                }
            });
1070 1071
        }).catch(reason => {
            errors.push(reason);
1072 1073 1074
        });
    }, function () {
        Promise.all(parsePromises)
1075
            .then(() => {
1076 1077 1078
            if (errors.length > 0) {
                throw errors;
            }
1079
            const translatedMainFile = createI18nFile('./main', mainPack);
1080
            resultingTranslationPaths.push({ id: 'vscode', resourceName: 'main.i18n.json' });
1081 1082 1083 1084 1085
            this.queue(translatedMainFile);
            for (let extension in extensionsPacks) {
                const translatedExtFile = createI18nFile(`./extensions/${extension}`, extensionsPacks[extension]);
                this.queue(translatedExtFile);
                const externalExtensionId = externalExtensions[extension];
1086
                if (externalExtensionId) {
1087
                    resultingTranslationPaths.push({ id: externalExtensionId, resourceName: `extensions/${extension}.i18n.json` });
1088 1089
                }
                else {
1090
                    resultingTranslationPaths.push({ id: `vscode.${extension}`, resourceName: `extensions/${extension}.i18n.json` });
1091
                }
D
Dirk Baeumer 已提交
1092
            }
1093
            this.queue(null);
D
Dirk Baeumer 已提交
1094
        })
1095 1096 1097
            .catch((reason) => {
            this.emit('error', reason);
        });
1098 1099
    });
}
D
Dirk Baeumer 已提交
1100 1101
exports.prepareI18nPackFiles = prepareI18nPackFiles;
function prepareIslFiles(language, innoSetupConfig) {
1102
    let parsePromises = [];
D
Dirk Baeumer 已提交
1103
    return event_stream_1.through(function (xlf) {
1104 1105
        let stream = this;
        let parsePromise = XLF.parse(xlf.contents.toString());
D
Dirk Baeumer 已提交
1106
        parsePromises.push(parsePromise);
1107 1108
        parsePromise.then(resolvedFiles => {
            resolvedFiles.forEach(file => {
D
Dirk Baeumer 已提交
1109 1110 1111
                if (path.basename(file.originalFilePath) === 'Default' && !innoSetupConfig.defaultInfo) {
                    return;
                }
1112
                let translatedFile = createIslFile(file.originalFilePath, file.messages, language, innoSetupConfig);
D
Dirk Baeumer 已提交
1113 1114
                stream.queue(translatedFile);
            });
1115 1116
        }).catch(reason => {
            this.emit('error', reason);
D
Dirk Baeumer 已提交
1117 1118 1119
        });
    }, function () {
        Promise.all(parsePromises)
1120
            .then(() => { this.queue(null); })
1121 1122 1123
            .catch(reason => {
            this.emit('error', reason);
        });
1124 1125
    });
}
D
Dirk Baeumer 已提交
1126 1127
exports.prepareIslFiles = prepareIslFiles;
function createIslFile(originalFilePath, messages, language, innoSetup) {
1128 1129
    let content = [];
    let originalContent;
1130 1131 1132 1133 1134 1135
    if (path.basename(originalFilePath) === 'Default') {
        originalContent = new TextModel(fs.readFileSync(originalFilePath + '.isl', 'utf8'));
    }
    else {
        originalContent = new TextModel(fs.readFileSync(originalFilePath + '.en.isl', 'utf8'));
    }
1136
    originalContent.lines.forEach(line => {
1137
        if (line.length > 0) {
1138
            let firstChar = line.charAt(0);
1139 1140
            if (firstChar === '[' || firstChar === ';') {
                if (line === '; *** Inno Setup version 5.5.3+ English messages ***') {
1141
                    content.push(`; *** Inno Setup version 5.5.3+ ${innoSetup.defaultInfo.name} messages ***`);
1142 1143 1144 1145 1146 1147
                }
                else {
                    content.push(line);
                }
            }
            else {
1148 1149 1150
                let sections = line.split('=');
                let key = sections[0];
                let translated = line;
1151 1152
                if (key) {
                    if (key === 'LanguageName') {
1153
                        translated = `${key}=${innoSetup.defaultInfo.name}`;
1154 1155
                    }
                    else if (key === 'LanguageID') {
1156
                        translated = `${key}=${innoSetup.defaultInfo.id}`;
1157 1158
                    }
                    else if (key === 'LanguageCodePage') {
1159
                        translated = `${key}=${innoSetup.codePage.substr(2)}`;
1160 1161
                    }
                    else {
1162
                        let translatedMessage = messages[key];
1163
                        if (translatedMessage) {
1164
                            translated = `${key}=${translatedMessage}`;
1165 1166 1167 1168 1169 1170 1171
                        }
                    }
                }
                content.push(translated);
            }
        }
    });
1172 1173
    const basename = path.basename(originalFilePath);
    const filePath = `${basename}.${language.id}.isl`;
1174 1175
    return new File({
        path: filePath,
1176
        contents: iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage)
1177 1178 1179
    });
}
function encodeEntities(value) {
1180 1181 1182
    let result = [];
    for (let i = 0; i < value.length; i++) {
        let ch = value[i];
1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
        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, '&');
}
1202 1203 1204
function pseudify(message) {
    return '\uFF3B' + message.replace(/[aouei]/g, '$&$&') + '\uFF3D';
}