UNPKG

vscode-nls-dev

Version:

Development time npm module to generate strings bundles from Javascript files

565 lines 22.8 kB
/* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.prepareJsonFiles = exports.createXlfFiles = exports.XLF = exports.Line = exports.createKeyValuePairFile = exports.debug = exports.bundleLanguageFiles = exports.createAdditionalLanguageFiles = exports.bundleMetaDataFiles = exports.createMetaDataFiles = exports.rewriteLocalizeCalls = void 0; const event_stream_1 = require("event-stream"); const Is = require("is"); const path = require("path"); const xml2js = require("xml2js"); const lib_1 = require("./lib"); const File = require("vinyl"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); function log(message, ...rest) { fancyLog(ansiColors.cyan('[i18n]'), message, ...rest); } const NLS_JSON = '.nls.json'; const NLS_METADATA_JSON = '.nls.metadata.json'; const I18N_JSON = '.i18n.json'; function rewriteLocalizeCalls() { return (0, event_stream_1.through)(function (file) { if (!file.isBuffer()) { this.emit('error', `Failed to read file: ${file.relative}`); return; } const buffer = file.contents; const content = buffer.toString('utf8'); const sourceMap = file.sourceMap; const result = (0, lib_1.processFile)(content, undefined, undefined, sourceMap); let messagesFile; let metaDataFile; if (result.errors && result.errors.length > 0) { result.errors.forEach(error => console.error(`${file.relative}${error}`)); this.emit('error', `Failed to rewrite file: ${file.path}`); return; } else { if (result.contents) { file.contents = new Buffer(result.contents, 'utf8'); } if (result.sourceMap) { file.sourceMap = JSON.parse(result.sourceMap); } if (result.bundle) { let ext = path.extname(file.path); let filePath = file.path.substr(0, file.path.length - ext.length); messagesFile = new File({ base: file.base, path: filePath + NLS_JSON, contents: new Buffer(JSON.stringify(result.bundle.messages, null, '\t'), 'utf8') }); let metaDataContent = Object.assign({}, result.bundle, { filePath: (0, lib_1.removePathPrefix)(filePath, file.base) }); metaDataFile = new File({ base: file.base, path: filePath + NLS_METADATA_JSON, contents: new Buffer(JSON.stringify(metaDataContent, null, '\t'), 'utf8') }); } } this.queue(file); if (messagesFile) { this.queue(messagesFile); } if (metaDataFile) { this.queue(metaDataFile); } }); } exports.rewriteLocalizeCalls = rewriteLocalizeCalls; function createMetaDataFiles() { return (0, event_stream_1.through)(function (file) { if (!file.isBuffer()) { this.emit('error', `Failed to read file: ${file.relative}`); return; } let result = (0, lib_1.processFile)(file.contents.toString('utf8')); if (result.errors && result.errors.length > 0) { result.errors.forEach(error => console.error(`${file.relative}${error}`)); this.emit('error', `Failed to rewrite file: ${file.path}`); return; } // emit the input file as-is this.queue(file); // emit nls meta data if available if (result.bundle) { let ext = path.extname(file.path); let filePath = file.path.substr(0, file.path.length - ext.length); this.queue(new File({ base: file.base, path: filePath + NLS_JSON, contents: new Buffer(JSON.stringify(result.bundle.messages, null, '\t'), 'utf8') })); let metaDataContent = Object.assign({}, result.bundle, { filePath: (0, lib_1.removePathPrefix)(filePath, file.base) }); this.queue(new File({ base: file.base, path: filePath + NLS_METADATA_JSON, contents: new Buffer(JSON.stringify(metaDataContent, null, '\t'), 'utf8') })); } }); } exports.createMetaDataFiles = createMetaDataFiles; function bundleMetaDataFiles(id, outDir) { let base = undefined; const bundler = new lib_1.MetaDataBundler(id, outDir); return (0, event_stream_1.through)(function (file) { const basename = path.basename(file.relative); if (basename.length < NLS_METADATA_JSON.length || NLS_METADATA_JSON !== basename.substr(basename.length - NLS_METADATA_JSON.length)) { this.queue(file); return; } if (file.isBuffer()) { if (!base) { base = file.base; } } else { this.emit('error', `Failed to bundle file: ${file.relative}`); return; } if (!base) { base = file.base; } const buffer = file.contents; const json = JSON.parse(buffer.toString('utf8')); bundler.add(json); }, function () { if (base) { const [header, content] = bundler.bundle(); this.queue(new File({ base: base, path: path.join(base, 'nls.metadata.header.json'), contents: new Buffer(JSON.stringify(header), 'utf8') })); this.queue(new File({ base: base, path: path.join(base, 'nls.metadata.json'), contents: new Buffer(JSON.stringify(content), 'utf8') })); } this.queue(null); }); } exports.bundleMetaDataFiles = bundleMetaDataFiles; function createAdditionalLanguageFiles(languages, i18nBaseDir, baseDir, logProblems = true) { return (0, event_stream_1.through)(function (file) { // Queue the original file again. this.queue(file); const basename = path.basename(file.relative); const isPackageFile = basename === 'package.nls.json'; const isAffected = isPackageFile || basename.match(/nls.metadata.json$/) !== null; if (!isAffected) { return; } const filename = isPackageFile ? file.relative.substr(0, file.relative.length - '.nls.json'.length) : file.relative.substr(0, file.relative.length - NLS_METADATA_JSON.length); let json; if (file.isBuffer()) { const buffer = file.contents; json = JSON.parse(buffer.toString('utf8')); const resolvedBundle = (0, lib_1.resolveMessageBundle)(json); languages.forEach((language) => { const folderName = language.folderName || language.id; const result = (0, lib_1.createLocalizedMessages)(filename, resolvedBundle, folderName, i18nBaseDir, baseDir); if (result.problems && result.problems.length > 0 && logProblems) { result.problems.forEach(problem => log(problem)); } if (result.messages) { this.queue(new File({ base: file.base, path: path.join(file.base, filename) + '.nls.' + language.id + '.json', contents: new Buffer(JSON.stringify(result.messages, null, '\t').replace(/\r\n/g, '\n'), 'utf8') })); } }); } else { this.emit('error', `Failed to read component file: ${file.relative}`); return; } }); } exports.createAdditionalLanguageFiles = createAdditionalLanguageFiles; function bundleLanguageFiles() { const bundles = Object.create(null); function getModuleKey(relativeFile) { return relativeFile.match(/(.*)\.nls\.(?:.*\.)?json/)[1].replace(/\\/g, '/'); } return (0, event_stream_1.through)(function (file) { const basename = path.basename(file.path); const matches = basename.match(/.nls\.(?:(.*)\.)?json/); if (!matches || !file.isBuffer()) { // Not an nls file. this.queue(file); return; } const language = matches[1] ? matches[1] : 'en'; let bundle = bundles[language]; if (!bundle) { bundle = { base: file.base, content: Object.create(null) }; bundles[language] = bundle; } bundle.content[getModuleKey(file.relative)] = JSON.parse(file.contents.toString('utf8')); }, function () { for (const language in bundles) { const bundle = bundles[language]; const languageId = language === 'en' ? '' : `${language}.`; const file = new File({ base: bundle.base, path: path.join(bundle.base, `nls.bundle.${languageId}json`), contents: new Buffer(JSON.stringify(bundle.content), 'utf8') }); this.queue(file); } this.queue(null); }); } exports.bundleLanguageFiles = bundleLanguageFiles; function debug(prefix = '') { return (0, event_stream_1.through)(function (file) { console.log(`${prefix}In pipe ${file.path}`); this.queue(file); }); } exports.debug = debug; /** * A stream the creates additional key/value pair files for structured nls files. * * @param commentSeparator - if provided comments will be joined into one string using * the commentSeparator value. If omitted comments will be includes as a string array. */ function createKeyValuePairFile(commentSeparator = undefined) { return (0, event_stream_1.through)(function (file) { const basename = path.basename(file.relative); if (basename.length < NLS_METADATA_JSON.length || NLS_METADATA_JSON !== basename.substr(basename.length - NLS_METADATA_JSON.length)) { this.queue(file); return; } let kvpFile; const filename = file.relative.substr(0, file.relative.length - NLS_METADATA_JSON.length); if (file.isBuffer()) { const buffer = file.contents; const json = JSON.parse(buffer.toString('utf8')); if (lib_1.JavaScriptMessageBundle.is(json)) { const resolvedBundle = json; if (resolvedBundle.messages.length !== resolvedBundle.keys.length) { this.queue(file); return; } const kvpObject = (0, lib_1.bundle2keyValuePair)(resolvedBundle, commentSeparator); kvpFile = new File({ base: file.base, path: path.join(file.base, filename) + I18N_JSON, contents: new Buffer(JSON.stringify(kvpObject, null, '\t'), 'utf8') }); } else { this.emit('error', `Not a valid JavaScript message bundle: ${file.relative}`); return; } } else { this.emit('error', `Failed to read JavaScript message bundle file: ${file.relative}`); return; } this.queue(file); if (kvpFile) { this.queue(kvpFile); } }); } exports.createKeyValuePairFile = createKeyValuePairFile; var PackageJsonFormat; (function (PackageJsonFormat) { function is(value) { if (Is.undef(value) || !Is.object(value)) { return false; } return Object.keys(value).every(key => { let element = value[key]; return Is.string(element) || (Is.object(element) && Is.defined(element.message) && Is.defined(element.comment)); }); } PackageJsonFormat.is = is; })(PackageJsonFormat || (PackageJsonFormat = {})); var MessageInfo; (function (MessageInfo) { function message(value) { return typeof value === 'string' ? value : value.message; } MessageInfo.message = message; function comment(value) { return typeof value === 'string' ? undefined : value.comment; } MessageInfo.comment = comment; })(MessageInfo || (MessageInfo = {})); class Line { constructor(indent = 0) { this.buffer = []; if (indent > 0) { this.buffer.push(new Array(indent + 1).join(' ')); } } append(value) { this.buffer.push(value); return this; } toString() { return this.buffer.join(''); } } exports.Line = Line; class XLF { constructor(project) { this.project = project; this.buffer = []; this.files = Object.create(null); } toString() { this.appendHeader(); for (const file in this.files) { this.appendNewLine(`<file original="${file}" source-language="en" datatype="plaintext"><body>`, 2); for (const item of this.files[file]) { this.addStringItem(item); } this.appendNewLine('</body></file>', 2); } this.appendFooter(); return this.buffer.join('\r\n'); } addFile(original, keys, messages) { var _a; if (keys.length === 0) { return; } if (keys.length !== messages.length) { throw new Error(`Un-matching keys(${keys.length}) and messages(${messages.length}).`); } this.files[original] = []; const existingKeys = new Set(); for (let i = 0; i < keys.length; i++) { const keyInfo = keys[i]; const key = lib_1.KeyInfo.key(keyInfo); if (existingKeys.has(key)) { continue; } existingKeys.add(key); const messageInfo = messages[i]; const message = encodeEntities(MessageInfo.message(messageInfo)); const comment = function (comments) { if (comments === undefined) { return undefined; } return comments.map(comment => encodeEntities(comment)).join(`\r\n`); }((_a = lib_1.KeyInfo.comment(keyInfo)) !== null && _a !== void 0 ? _a : MessageInfo.comment(messageInfo)); this.files[original].push(comment !== undefined ? { id: key, message: message, comment: comment } : { id: key, message: message }); } } addStringItem(item) { if (!item.id || !item.message) { throw new Error('No item ID or value specified.'); } this.appendNewLine(`<trans-unit id="${item.id}">`, 4); this.appendNewLine(`<source xml:lang="en">${item.message}</source>`, 6); if (item.comment) { this.appendNewLine(`<note>${item.comment}</note>`, 6); } this.appendNewLine('</trans-unit>', 4); } appendHeader() { 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); } appendFooter() { this.appendNewLine('</xliff>', 0); } appendNewLine(content, indent) { const line = new Line(indent); line.append(content); this.buffer.push(line.toString()); } static parse(xlfString) { const getValue = function (target) { if (typeof target === 'string') { return target; } if (typeof target._ === 'string') { return target._; } if (Array.isArray(target) && target.length === 1) { const item = target[0]; if (typeof item === 'string') { return item; } if (typeof item._ === 'string') { return item._; } return target[0]._; } return undefined; }; return new Promise((resolve, reject) => { const parser = new xml2js.Parser(); const files = []; parser.parseString(xlfString, function (err, result) { if (err) { reject(new Error(`Failed to parse XLIFF string. ${err}`)); } const fileNodes = result['xliff']['file']; if (!fileNodes) { reject(new 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('XLIFF file node does not contain original attribute to determine the original location of the resource file.')); } const language = file.$['target-language'].toLowerCase(); if (!language) { reject(new 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 } const val = getValue(unit.target); if (key && val) { messages[key] = decodeEntities(val); } else { reject(new 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 }); } }); resolve(files); }); }); } } exports.XLF = XLF; function createXlfFiles(projectName, extensionName) { let _xlf; let header; let data; function getXlf() { if (!_xlf) { _xlf = new XLF(projectName); } return _xlf; } return (0, event_stream_1.through)(function (file) { if (!file.isBuffer()) { this.emit('error', `File ${file.path} is not a buffer`); return; } const buffer = file.contents; const basename = path.basename(file.path); if (basename === 'package.nls.json') { const json = JSON.parse(buffer.toString('utf8')); const keys = Object.keys(json); const messages = keys.map((key) => { const value = json[key]; return value === undefined ? `Unknown message for key: ${key}` : value; }); getXlf().addFile('package', keys, messages); } else if (basename === 'nls.metadata.json') { data = JSON.parse(buffer.toString('utf8')); } else if (basename === 'nls.metadata.header.json') { header = JSON.parse(buffer.toString('utf8')); } else { this.emit('error', new Error(`${file.path} is not a valid nls or meta data file`)); return; } }, function () { if (header && data) { const outDir = header.outDir; for (const module in data) { const fileContent = data[module]; // in the XLF files we only use forward slashes. getXlf().addFile(`${outDir}/${module.replace(/\\/g, '/')}`, fileContent.keys, fileContent.messages); } } if (_xlf) { const xlfFile = new File({ path: path.join(projectName, extensionName + '.xlf'), contents: new Buffer(_xlf.toString(), 'utf8') }); this.queue(xlfFile); } this.queue(null); }); } exports.createXlfFiles = createXlfFiles; function prepareJsonFiles() { let parsePromises = []; return (0, event_stream_1.through)(function (xlf) { let stream = this; let parsePromise = XLF.parse(xlf.contents.toString()); parsePromises.push(parsePromise); parsePromise.then(function (resolvedFiles) { resolvedFiles.forEach(file => { let messages = file.messages, translatedFile; translatedFile = createI18nFile(file.originalFilePath, messages); stream.queue(translatedFile); }); }); }, function () { Promise.all(parsePromises) .then(() => { this.queue(null); }) .catch(reason => { throw new Error(reason); }); }); } exports.prepareJsonFiles = prepareJsonFiles; function createI18nFile(originalFilePath, messages) { let content = [ '/*---------------------------------------------------------------------------------------------', ' * 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.' ].join('\n') + '\n' + JSON.stringify(messages, null, '\t').replace(/\r\n/g, '\n'); return new File({ path: path.join(originalFilePath + '.i18n.json'), contents: new Buffer(content, 'utf8') }); } function encodeEntities(value) { var result = []; for (var i = 0; i < value.length; i++) { var ch = value[i]; 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, '&'); } //# sourceMappingURL=main.js.map