"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 }); var path = require("path"); var fs = require("fs"); var event_stream_1 = require("event-stream"); var File = require("vinyl"); var Is = require("is"); var xml2js = require("xml2js"); var glob = require("glob"); var https = require("https"); var gulp = require("gulp"); var util = require('gulp-util'); var iconv = require('iconv-lite'); var NUMBER_OF_CONCURRENT_DOWNLOADS = 4; function log(message) { var rest = []; for (var _i = 1; _i < arguments.length; _i++) { rest[_i - 1] = arguments[_i]; } util.log.apply(util, [util.colors.green('[i18n]'), message].concat(rest)); } 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' } ]; exports.pseudoLanguage = { id: 'pseudo', folderName: 'pseudo', transifexId: 'pseudo' }; // non built-in extensions also that are transifex and need to be part of the language packs var externalExtensionsWithTranslations = [ "azure-account", "vscode-chrome-debug", "vscode-chrome-debug-core", "vscode-node-debug", "vscode-node-debug2" ]; var LocalizeInfo; (function (LocalizeInfo) { function is(value) { var candidate = value; return Is.defined(candidate) && Is.string(candidate.key) && (Is.undef(candidate.comment) || (Is.array(candidate.comment) && candidate.comment.every(function (element) { return Is.string(element); }))); } LocalizeInfo.is = is; })(LocalizeInfo || (LocalizeInfo = {})); var BundledFormat; (function (BundledFormat) { function is(value) { if (Is.undef(value)) { return false; } var candidate = value; var length = Object.keys(value).length; 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; } return Object.keys(value).every(function (key) { var element = value[key]; return Is.string(element) || (Is.object(element) && Is.defined(element.message) && Is.defined(element.comment)); }); } PackageJsonFormat.is = is; })(PackageJsonFormat || (PackageJsonFormat = {})); var ModuleJsonFormat; (function (ModuleJsonFormat) { function is(value) { var candidate = value; return Is.defined(candidate) && Is.array(candidate.messages) && candidate.messages.every(function (message) { return Is.string(message); }) && Is.array(candidate.keys) && candidate.keys.every(function (key) { return Is.string(key) || LocalizeInfo.is(key); }); } ModuleJsonFormat.is = is; })(ModuleJsonFormat || (ModuleJsonFormat = {})); var Line = /** @class */ (function () { function Line(indent) { if (indent === void 0) { indent = 0; } this.indent = indent; this.buffer = []; if (indent > 0) { this.buffer.push(new Array(indent + 1).join(' ')); } } Line.prototype.append = function (value) { this.buffer.push(value); return this; }; Line.prototype.toString = function () { return this.buffer.join(''); }; return Line; }()); exports.Line = Line; var TextModel = /** @class */ (function () { function TextModel(contents) { this._lines = contents.split(/\r\n|\r|\n/); } Object.defineProperty(TextModel.prototype, "lines", { get: function () { return this._lines; }, enumerable: true, configurable: true }); return TextModel; }()); var XLF = /** @class */ (function () { function XLF(project) { this.project = project; this.buffer = []; this.files = Object.create(null); } XLF.prototype.toString = function () { this.appendHeader(); for (var file in this.files) { this.appendNewLine("", 2); for (var _i = 0, _a = this.files[file]; _i < _a.length; _i++) { var item = _a[_i]; this.addStringItem(item); } this.appendNewLine('', 2); } this.appendFooter(); return this.buffer.join('\r\n'); }; XLF.prototype.addFile = function (original, keys, messages) { if (keys.length !== messages.length) { throw new Error("Unmatching keys(" + keys.length + ") and messages(" + messages.length + ")."); } this.files[original] = []; var existingKeys = new Set(); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var realKey = void 0; var comment = void 0; if (Is.string(key)) { realKey = key; comment = undefined; } else if (LocalizeInfo.is(key)) { realKey = key.key; if (key.comment && key.comment.length > 0) { comment = key.comment.map(function (comment) { return encodeEntities(comment); }).join('\r\n'); } } if (!realKey || existingKeys.has(realKey)) { continue; } existingKeys.add(realKey); var message = encodeEntities(messages[i]); this.files[original].push({ id: realKey, message: message, comment: comment }); } }; XLF.prototype.addStringItem = function (item) { if (!item.id || !item.message) { throw new Error("No item ID or value specified: " + JSON.stringify(item)); } this.appendNewLine("", 4); this.appendNewLine("" + item.message + "", 6); if (item.comment) { this.appendNewLine("" + item.comment + "", 6); } this.appendNewLine('', 4); }; XLF.prototype.appendHeader = function () { this.appendNewLine('', 0); this.appendNewLine('', 0); }; XLF.prototype.appendFooter = function () { this.appendNewLine('', 0); }; XLF.prototype.appendNewLine = function (content, indent) { var line = new Line(indent); line.append(content); this.buffer.push(line.toString()); }; XLF.parse = function (xlfString) { return new Promise(function (resolve, reject) { var parser = new xml2js.Parser(); var files = []; parser.parseString(xlfString, function (err, result) { if (err) { reject(new Error("XLF parsing error: Failed to parse XLIFF string. " + err)); } var 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(function (file) { var 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.")); } var 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.")); } var messages = {}; var transUnits = file.body[0]['trans-unit']; transUnits.forEach(function (unit) { var key = unit.$.id; if (!unit.target) { return; // No translation available } var val = unit.target.toString(); if (key && val) { messages[key] = decodeEntities(val); } else { reject(new Error("XLF parsing error: XLIFF file does not contain full localization data. ID or target translation for one of the trans-unit nodes is not present.")); } }); files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() }); }); resolve(files); }); }); }; return XLF; }()); exports.XLF = XLF; var Limiter = /** @class */ (function () { function Limiter(maxDegreeOfParalellism) { this.maxDegreeOfParalellism = maxDegreeOfParalellism; this.outstandingPromises = []; this.runningPromises = 0; } Limiter.prototype.queue = function (factory) { var _this = this; return new Promise(function (c, e) { _this.outstandingPromises.push({ factory: factory, c: c, e: e }); _this.consume(); }); }; Limiter.prototype.consume = function () { var _this = this; while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) { var iLimitedTask = this.outstandingPromises.shift(); this.runningPromises++; var promise = iLimitedTask.factory(); promise.then(iLimitedTask.c).catch(iLimitedTask.e); promise.then(function () { return _this.consumed(); }).catch(function () { return _this.consumed(); }); } }; Limiter.prototype.consumed = function () { this.runningPromises--; this.consume(); }; return Limiter; }()); exports.Limiter = Limiter; function sortLanguages(languages) { return languages.sort(function (a, b) { return a.id < b.id ? -1 : (a.id > b.id ? 1 : 0); }); } function stripComments(content) { /** * First capturing group matches double quoted string * Second matches single quotes string * Third matches block comments * Fourth matches line comments */ var regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; var result = content.replace(regexp, function (match, m1, m2, m3, m4) { // 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. var length_1 = m4.length; if (length_1 > 2 && m4[length_1 - 1] === '\n') { return m4[length_1 - 2] === '\r' ? '\r\n' : '\n'; } else { return ''; } } else { // We match a string return match; } }); return result; } function escapeCharacters(value) { var result = []; for (var i = 0; i < value.length; i++) { var ch = value.charAt(i); switch (ch) { case '\'': result.push('\\\''); break; case '"': result.push('\\"'); break; case '\\': result.push('\\\\'); break; case '\n': result.push('\\n'); break; case '\r': result.push('\\r'); break; case '\t': result.push('\\t'); break; case '\b': result.push('\\b'); break; case '\f': result.push('\\f'); break; default: result.push(ch); } } return result.join(''); } function processCoreBundleFormat(fileHeader, languages, json, emitter) { var keysSection = json.keys; var messageSection = json.messages; var bundleSection = json.bundles; var statistics = Object.create(null); var total = 0; var defaultMessages = Object.create(null); var modules = Object.keys(keysSection); modules.forEach(function (module) { var keys = keysSection[module]; var messages = messageSection[module]; if (!messages || keys.length !== messages.length) { emitter.emit('error', "Message for module " + module + " corrupted. Mismatch in number of keys and messages."); return; } var messageMap = Object.create(null); defaultMessages[module] = messageMap; keys.map(function (key, i) { total++; if (typeof key === 'string') { messageMap[key] = messages[i]; } else { messageMap[key.key] = messages[i]; } }); }); var languageDirectory = path.join(__dirname, '..', '..', 'i18n'); var sortedLanguages = sortLanguages(languages); sortedLanguages.forEach(function (language) { if (process.env['VSCODE_BUILD_VERBOSE']) { log("Generating nls bundles for: " + language.id); } statistics[language.id] = 0; var localizedModules = Object.create(null); var languageFolderName = language.folderName || language.id; var cwd = path.join(languageDirectory, languageFolderName, 'src'); modules.forEach(function (module) { var order = keysSection[module]; var i18nFile = path.join(cwd, module) + '.i18n.json'; var messages = null; if (fs.existsSync(i18nFile)) { var content = stripComments(fs.readFileSync(i18nFile, 'utf8')); messages = JSON.parse(content); } else { if (process.env['VSCODE_BUILD_VERBOSE']) { log("No localized messages found for module " + module + ". Using default messages."); } messages = defaultMessages[module]; statistics[language.id] = statistics[language.id] + Object.keys(messages).length; } var localizedMessages = []; order.forEach(function (keyInfo) { var key = null; if (typeof keyInfo === 'string') { key = keyInfo; } else { key = keyInfo.key; } var message = messages[key]; if (!message) { if (process.env['VSCODE_BUILD_VERBOSE']) { log("No localized message found for key " + key + " in module " + module + ". Using default message."); } message = defaultMessages[module][key]; statistics[language.id] = statistics[language.id] + 1; } localizedMessages.push(message); }); localizedModules[module] = localizedMessages; }); Object.keys(bundleSection).forEach(function (bundle) { var modules = bundleSection[bundle]; var contents = [ fileHeader, "define(\"" + bundle + ".nls." + language.id + "\", {" ]; modules.forEach(function (module, index) { contents.push("\t\"" + module + "\": ["); var messages = localizedModules[module]; if (!messages) { emitter.emit('error', "Didn't find messages for module " + module + "."); return; } messages.forEach(function (message, index) { contents.push("\t\t\"" + escapeCharacters(message) + (index < messages.length ? '",' : '"')); }); contents.push(index < modules.length - 1 ? '\t],' : '\t]'); }); contents.push('});'); emitter.queue(new File({ path: bundle + '.nls.' + language.id + '.js', contents: new Buffer(contents.join('\n'), 'utf-8') })); }); }); Object.keys(statistics).forEach(function (key) { var value = statistics[key]; log(key + " has " + value + " untranslated strings."); }); sortedLanguages.forEach(function (language) { var stats = statistics[language.id]; if (Is.undef(stats)) { log("\tNo translations found for language " + language.id + ". Using default language instead."); } }); } function processNlsFiles(opts) { return event_stream_1.through(function (file) { var fileName = path.basename(file.path); if (fileName === 'nls.metadata.json') { var json = null; if (file.isBuffer()) { json = JSON.parse(file.contents.toString('utf8')); } else { this.emit('error', "Failed to read component file: " + file.relative); return; } if (BundledFormat.is(json)) { processCoreBundleFormat(opts.fileHeader, opts.languages, json, this); } } this.queue(file); }); } exports.processNlsFiles = processNlsFiles; var editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup'; function getResource(sourceFile) { var resource; 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 }; } throw new Error("Could not identify the XLF bundle for " + sourceFile); } exports.getResource = getResource; function createXlfFilesForCoreBundle() { return event_stream_1.through(function (file) { var basename = path.basename(file.path); if (basename === 'nls.metadata.json') { if (file.isBuffer()) { var xlfs = Object.create(null); var json = JSON.parse(file.contents.toString('utf8')); for (var coreModule in json.keys) { var projectResource = getResource(coreModule); var resource = projectResource.name; var project = projectResource.project; var keys = json.keys[coreModule]; var messages = json.messages[coreModule]; if (keys.length !== messages.length) { this.emit('error', "There is a mismatch between keys and messages in " + file.relative + " for module " + coreModule); return; } else { var xlf = xlfs[resource]; if (!xlf) { xlf = new XLF(project); xlfs[resource] = xlf; } xlf.addFile("src/" + coreModule, keys, messages); } } for (var resource in xlfs) { var xlf = xlfs[resource]; var filePath = xlf.project + "/" + resource.replace(/\//g, '_') + ".xlf"; var xlfFile = new File({ path: filePath, contents: new Buffer(xlf.toString(), 'utf8') }); this.queue(xlfFile); } } else { this.emit('error', new Error("File " + file.relative + " is not using a buffer content")); return; } } else { this.emit('error', new Error("File " + file.relative + " is not a core meta data file.")); return; } }); } exports.createXlfFilesForCoreBundle = createXlfFilesForCoreBundle; function createXlfFilesForExtensions() { var counter = 0; var folderStreamEnded = false; var folderStreamEndEmitted = false; return event_stream_1.through(function (extensionFolder) { var folderStream = this; var stat = fs.statSync(extensionFolder.path); if (!stat.isDirectory()) { return; } var extensionName = path.basename(extensionFolder.path); if (extensionName === 'node_modules') { return; } counter++; var _xlf; function getXlf() { if (!_xlf) { _xlf = new XLF(extensionsProject); } return _xlf; } gulp.src(["./extensions/" + extensionName + "/package.nls.json", "./extensions/" + extensionName + "/**/nls.metadata.json"]).pipe(event_stream_1.through(function (file) { if (file.isBuffer()) { var buffer = file.contents; var basename = path.basename(file.path); if (basename === 'package.nls.json') { var json_1 = JSON.parse(buffer.toString('utf8')); var keys = Object.keys(json_1); var messages = keys.map(function (key) { var value = json_1[key]; if (Is.string(value)) { return value; } else if (value) { return value.message; } else { return "Unknown message for key: " + key; } }); getXlf().addFile("extensions/" + extensionName + "/package", keys, messages); } else if (basename === 'nls.metadata.json') { var json = JSON.parse(buffer.toString('utf8')); var relPath = path.relative("./extensions/" + extensionName, path.dirname(file.path)); for (var file_1 in json) { var fileContent = json[file_1]; getXlf().addFile("extensions/" + extensionName + "/" + relPath + "/" + file_1, fileContent.keys, fileContent.messages); } } else { this.emit('error', new Error(file.path + " is not a valid extension nls file")); return; } } }, function () { if (_xlf) { var xlfFile = new File({ path: path.join(extensionsProject, extensionName + '.xlf'), contents: new Buffer(_xlf.toString(), 'utf8') }); folderStream.queue(xlfFile); } this.queue(null); counter--; if (counter === 0 && folderStreamEnded && !folderStreamEndEmitted) { folderStreamEndEmitted = true; folderStream.queue(null); } })); }, function () { folderStreamEnded = true; if (counter === 0) { folderStreamEndEmitted = true; this.queue(null); } }); } exports.createXlfFilesForExtensions = createXlfFilesForExtensions; function createXlfFilesForIsl() { return event_stream_1.through(function (file) { var projectName, resourceFile; if (path.basename(file.path) === 'Default.isl') { projectName = setupProject; resourceFile = 'setup_default.xlf'; } else { projectName = workbenchProject; resourceFile = 'setup_messages.xlf'; } var xlf = new XLF(projectName), keys = [], messages = []; var model = new TextModel(file.contents.toString()); var inMessageSection = false; model.lines.forEach(function (line) { if (line.length === 0) { return; } var firstChar = line.charAt(0); switch (firstChar) { case ';': // Comment line; return; case '[': inMessageSection = '[Messages]' === line || '[CustomMessages]' === line; return; } if (!inMessageSection) { return; } var sections = line.split('='); if (sections.length !== 2) { throw new Error("Badly formatted message found: " + line); } else { var key = sections[0]; var value = sections[1]; if (key.length > 0 && value.length > 0) { keys.push(key); messages.push(value); } } }); var originalPath = file.path.substring(file.cwd.length + 1, file.path.split('.')[0].length).replace(/\\/g, '/'); xlf.addFile(originalPath, keys, messages); // Emit only upon all ISL files combined into single XLF instance var newFilePath = path.join(projectName, resourceFile); var xlfFile = new File({ path: newFilePath, contents: new Buffer(xlf.toString(), 'utf-8') }); this.queue(xlfFile); }); } exports.createXlfFilesForIsl = createXlfFilesForIsl; function pushXlfFiles(apiHostname, username, password) { var tryGetPromises = []; var updateCreatePromises = []; return event_stream_1.through(function (file) { var project = path.dirname(file.relative); var fileName = path.basename(file.path); var slug = fileName.substr(0, fileName.length - '.xlf'.length); var credentials = username + ":" + password; // Check if resource already exists, if not, then create it. var promise = tryGetResource(project, slug, apiHostname, credentials); tryGetPromises.push(promise); promise.then(function (exists) { if (exists) { promise = updateResource(project, slug, file, apiHostname, credentials); } else { promise = createResource(project, slug, file, apiHostname, credentials); } updateCreatePromises.push(promise); }); }, function () { var _this = this; // End the pipe only after all the communication with Transifex API happened Promise.all(tryGetPromises).then(function () { Promise.all(updateCreatePromises).then(function () { _this.queue(null); }).catch(function (reason) { throw new Error(reason); }); }).catch(function (reason) { throw new Error(reason); }); }); } exports.pushXlfFiles = pushXlfFiles; function getAllResources(project, apiHostname, username, password) { return new Promise(function (resolve, reject) { var credentials = username + ":" + password; var options = { hostname: apiHostname, path: "/api/2/project/" + project + "/resources", auth: credentials, method: 'GET' }; var request = https.request(options, function (res) { var buffer = []; res.on('data', function (chunk) { return buffer.push(chunk); }); res.on('end', function () { if (res.statusCode === 200) { var json = JSON.parse(Buffer.concat(buffer).toString()); if (Array.isArray(json)) { resolve(json.map(function (o) { return o.slug; })); return; } reject("Unexpected data format. Response code: " + res.statusCode + "."); } else { reject("No resources in " + project + " returned no data. Response code: " + res.statusCode + "."); } }); }); request.on('error', function (err) { reject("Failed to query resources in " + project + " with the following error: " + err + ". " + options.path); }); request.end(); }); } function findObsoleteResources(apiHostname, username, password) { var resourcesByProject = Object.create(null); resourcesByProject[extensionsProject] = [].concat(externalExtensionsWithTranslations); // clone return event_stream_1.through(function (file) { var project = path.dirname(file.relative); var fileName = path.basename(file.path); var slug = fileName.substr(0, fileName.length - '.xlf'.length); var slugs = resourcesByProject[project]; if (!slugs) { resourcesByProject[project] = slugs = []; } slugs.push(slug); this.push(file); }, function () { var _this = this; var promises = []; var _loop_1 = function (project) { promises.push(getAllResources(project, apiHostname, username, password).then(function (resources) { var expectedResources = resourcesByProject[project]; var unusedResources = resources.filter(function (resource) { return resource && expectedResources.indexOf(resource) === -1; }); if (unusedResources.length) { console.log("[transifex] Obsolete resources in project '" + project + "': " + unusedResources.join(', ')); } })); }; for (var project in resourcesByProject) { _loop_1(project); } return Promise.all(promises).then(function (_) { _this.push(null); }).catch(function (reason) { throw new Error(reason); }); }); } exports.findObsoleteResources = findObsoleteResources; function tryGetResource(project, slug, apiHostname, credentials) { return new Promise(function (resolve, reject) { var options = { hostname: apiHostname, path: "/api/2/project/" + project + "/resource/" + slug + "/?details", auth: credentials, method: 'GET' }; var request = https.request(options, function (response) { if (response.statusCode === 404) { resolve(false); } else if (response.statusCode === 200) { resolve(true); } else { reject("Failed to query resource " + project + "/" + slug + ". Response: " + response.statusCode + " " + response.statusMessage); } }); request.on('error', function (err) { reject("Failed to get " + project + "/" + slug + " on Transifex: " + err); }); request.end(); }); } function createResource(project, slug, xlfFile, apiHostname, credentials) { return new Promise(function (resolve, reject) { var data = JSON.stringify({ 'content': xlfFile.contents.toString(), 'name': slug, 'slug': slug, 'i18n_type': 'XLIFF' }); var options = { hostname: apiHostname, path: "/api/2/project/" + project + "/resources", headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }, auth: credentials, method: 'POST' }; var request = https.request(options, function (res) { if (res.statusCode === 201) { log("Resource " + project + "/" + slug + " successfully created on Transifex."); } else { reject("Something went wrong in the request creating " + slug + " in " + project + ". " + res.statusCode); } }); request.on('error', function (err) { reject("Failed to create " + project + "/" + slug + " on Transifex: " + err); }); 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) { return new Promise(function (resolve, reject) { var data = JSON.stringify({ content: xlfFile.contents.toString() }); var options = { hostname: apiHostname, path: "/api/2/project/" + project + "/resource/" + slug + "/content", headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }, auth: credentials, method: 'PUT' }; var request = https.request(options, function (res) { if (res.statusCode === 200) { res.setEncoding('utf8'); var responseBuffer_1 = ''; res.on('data', function (chunk) { responseBuffer_1 += chunk; }); res.on('end', function () { var response = JSON.parse(responseBuffer_1); log("Resource " + project + "/" + slug + " successfully updated on Transifex. Strings added: " + response.strings_added + ", updated: " + response.strings_added + ", deleted: " + response.strings_added); resolve(); }); } else { reject("Something went wrong in the request updating " + slug + " in " + project + ". " + res.statusCode); } }); request.on('error', function (err) { reject("Failed to update " + project + "/" + slug + " on Transifex: " + err); }); request.write(data); request.end(); }); } // cache resources var _coreAndExtensionResources; function pullCoreAndExtensionsXlfFiles(apiHostname, username, password, language) { if (!_coreAndExtensionResources) { _coreAndExtensionResources = []; // editor and workbench var json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8')); _coreAndExtensionResources.push.apply(_coreAndExtensionResources, json.editor); _coreAndExtensionResources.push.apply(_coreAndExtensionResources, json.workbench); // extensions var extensionsToLocalize_1 = Object.create(null); glob.sync('./extensions/**/*.nls.json').forEach(function (extension) { return extensionsToLocalize_1[extension.split('/')[2]] = true; }); glob.sync('./extensions/*/node_modules/vscode-nls').forEach(function (extension) { return extensionsToLocalize_1[extension.split('/')[2]] = true; }); for (var _i = 0, externalExtensionsWithTranslations_1 = externalExtensionsWithTranslations; _i < externalExtensionsWithTranslations_1.length; _i++) { var extension = externalExtensionsWithTranslations_1[_i]; extensionsToLocalize_1[extension] = true; } Object.keys(extensionsToLocalize_1).forEach(function (extension) { _coreAndExtensionResources.push({ name: extension, project: extensionsProject }); }); } return pullXlfFiles(apiHostname, username, password, language, _coreAndExtensionResources); } exports.pullCoreAndExtensionsXlfFiles = pullCoreAndExtensionsXlfFiles; function pullSetupXlfFiles(apiHostname, username, password, language, includeDefault) { var setupResources = [{ name: 'setup_messages', project: workbenchProject }]; if (includeDefault) { setupResources.push({ name: 'setup_default', project: setupProject }); } return pullXlfFiles(apiHostname, username, password, language, setupResources); } exports.pullSetupXlfFiles = pullSetupXlfFiles; function pullXlfFiles(apiHostname, username, password, language, resources) { var credentials = username + ":" + password; var expectedTranslationsCount = resources.length; var translationsRetrieved = 0, called = false; return event_stream_1.readable(function (count, callback) { // Mark end of stream when all resources were retrieved if (translationsRetrieved === expectedTranslationsCount) { return this.emit('end'); } if (!called) { called = true; var stream_1 = this; resources.map(function (resource) { retrieveResource(language, resource, apiHostname, credentials).then(function (file) { if (file) { stream_1.emit('data', file); } translationsRetrieved++; }).catch(function (error) { throw new Error(error); }); }); } callback(); }); } var limiter = new Limiter(NUMBER_OF_CONCURRENT_DOWNLOADS); function retrieveResource(language, resource, apiHostname, credentials) { return limiter.queue(function () { return new Promise(function (resolve, reject) { var slug = resource.name.replace(/\//g, '_'); var project = resource.project; var transifexLanguageId = language.transifexId || language.id; var options = { hostname: apiHostname, path: "/api/2/project/" + project + "/resource/" + slug + "/translation/" + transifexLanguageId + "?file&mode=onlyreviewed", auth: credentials, port: 443, method: 'GET' }; console.log('Fetching ' + options.path); var request = https.request(options, function (res) { var xlfBuffer = []; res.on('data', function (chunk) { return xlfBuffer.push(chunk); }); res.on('end', function () { if (res.statusCode === 200) { resolve(new File({ contents: Buffer.concat(xlfBuffer), path: project + "/" + slug + ".xlf" })); } else if (res.statusCode === 404) { console.log(slug + " in " + project + " returned no data."); resolve(null); } else { reject(slug + " in " + project + " returned no data. Response code: " + res.statusCode + "."); } }); }); request.on('error', function (err) { reject("Failed to query resource " + slug + " with the following error: " + err + ". " + options.path); }); request.end(); }); }); } function prepareI18nFiles() { var parsePromises = []; return event_stream_1.through(function (xlf) { var stream = this; var parsePromise = XLF.parse(xlf.contents.toString()); parsePromises.push(parsePromise); parsePromise.then(function (resolvedFiles) { resolvedFiles.forEach(function (file) { var translatedFile = createI18nFile(file.originalFilePath, file.messages); stream.queue(translatedFile); }); }); }, function () { var _this = this; Promise.all(parsePromises) .then(function () { _this.queue(null); }) .catch(function (reason) { throw new Error(reason); }); }); } exports.prepareI18nFiles = prepareI18nFiles; function createI18nFile(originalFilePath, messages) { var result = Object.create(null); 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.' ]; for (var _i = 0, _a = Object.keys(messages); _i < _a.length; _i++) { var key = _a[_i]; result[key] = messages[key]; } var content = JSON.stringify(result, null, '\t').replace(/\r\n/g, '\n'); return new File({ path: path.join(originalFilePath + '.i18n.json'), contents: new Buffer(content, 'utf8') }); } var i18nPackVersion = "1.0.0"; function pullI18nPackFiles(apiHostname, username, password, language) { return pullCoreAndExtensionsXlfFiles(apiHostname, username, password, language).pipe(prepareI18nPackFiles()); } exports.pullI18nPackFiles = pullI18nPackFiles; function prepareI18nPackFiles() { var parsePromises = []; var mainPack = { version: i18nPackVersion, contents: {} }; var extensionsPacks = {}; return event_stream_1.through(function (xlf) { var stream = this; var parsePromise = XLF.parse(xlf.contents.toString()); parsePromises.push(parsePromise); parsePromise.then(function (resolvedFiles) { resolvedFiles.forEach(function (file) { var path = file.originalFilePath; var firstSlash = path.indexOf('/'); var firstSegment = path.substr(0, firstSlash); if (firstSegment === 'src') { mainPack.contents[path.substr(firstSlash + 1)] = file.messages; } else if (firstSegment === 'extensions') { var secondSlash = path.indexOf('/', firstSlash + 1); var secondSegment = path.substring(firstSlash + 1, secondSlash); if (secondSegment) { var extPack = extensionsPacks[secondSegment]; if (!extPack) { extPack = extensionsPacks[secondSegment] = { version: i18nPackVersion, contents: {} }; } extPack.contents[path.substr(secondSlash + 1)] = file.messages; } else { console.log('Unknown second segment ' + path); } } else { console.log('Unknown first segment ' + path); } }); }); }, function () { var _this = this; Promise.all(parsePromises) .then(function () { var translatedMainFile = createI18nFile('./main', mainPack); _this.queue(translatedMainFile); for (var extension in extensionsPacks) { var translatedExtFile = createI18nFile("./extensions/" + extension, extensionsPacks[extension]); _this.queue(translatedExtFile); } _this.queue(null); }) .catch(function (reason) { throw new Error(reason); }); }); } exports.prepareI18nPackFiles = prepareI18nPackFiles; function prepareIslFiles(language, innoSetupConfig) { var parsePromises = []; return event_stream_1.through(function (xlf) { var stream = this; var parsePromise = XLF.parse(xlf.contents.toString()); parsePromises.push(parsePromise); parsePromise.then(function (resolvedFiles) { resolvedFiles.forEach(function (file) { if (path.basename(file.originalFilePath) === 'Default' && !innoSetupConfig.defaultInfo) { return; } var translatedFile = createIslFile(file.originalFilePath, file.messages, language, innoSetupConfig); stream.queue(translatedFile); }); }); }, function () { var _this = this; Promise.all(parsePromises) .then(function () { _this.queue(null); }) .catch(function (reason) { throw new Error(reason); }); }); } exports.prepareIslFiles = prepareIslFiles; function createIslFile(originalFilePath, messages, language, innoSetup) { var content = []; var originalContent; if (path.basename(originalFilePath) === 'Default') { originalContent = new TextModel(fs.readFileSync(originalFilePath + '.isl', 'utf8')); } else { originalContent = new TextModel(fs.readFileSync(originalFilePath + '.en.isl', 'utf8')); } originalContent.lines.forEach(function (line) { if (line.length > 0) { var firstChar = line.charAt(0); if (firstChar === '[' || firstChar === ';') { if (line === '; *** Inno Setup version 5.5.3+ English messages ***') { content.push("; *** Inno Setup version 5.5.3+ " + innoSetup.defaultInfo.name + " messages ***"); } else { content.push(line); } } else { var sections = line.split('='); var key = sections[0]; var translated = line; if (key) { if (key === 'LanguageName') { translated = key + "=" + innoSetup.defaultInfo.name; } else if (key === 'LanguageID') { translated = key + "=" + innoSetup.defaultInfo.id; } else if (key === 'LanguageCodePage') { translated = key + "=" + innoSetup.codePage.substr(2); } else { var translatedMessage = messages[key]; if (translatedMessage) { translated = key + "=" + translatedMessage; } } } content.push(translated); } } }); var basename = path.basename(originalFilePath); var filePath = basename + "." + language.id + ".isl"; return new File({ path: filePath, contents: iconv.encode(new Buffer(content.join('\r\n'), 'utf8'), innoSetup.codePage) }); } function encodeEntities(value) { var result = []; for (var i = 0; i < value.length; i++) { var ch = value[i]; switch (ch) { case '<': result.push('<'); break; case '>': result.push('>'); break; case '&': result.push('&'); break; default: result.push(ch); } } return result.join(''); } function decodeEntities(value) { return value.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); }