UNPKG

@angular/localize

Version:

Angular - library for localizing messages

332 lines (320 loc) 15.2 kB
#!/usr/bin/env node import {createRequire as __cjsCompatRequire} from 'module'; const require = __cjsCompatRequire(import.meta.url); import { ArbTranslationParser, SimpleJsonTranslationParser, Xliff1TranslationParser, Xliff2TranslationParser, XtbTranslationParser, makeEs2015TranslatePlugin, makeEs5TranslatePlugin, makeLocalePlugin } from "../../chunk-6WAVAPO6.js"; import { Diagnostics } from "../../chunk-ARKHNZ5Y.js"; // bazel-out/k8-fastbuild/bin/packages/localize/tools/src/translate/cli.js import { NodeJSFileSystem, setFileSystem } from "@angular/compiler-cli/private/localize"; import { globSync } from "tinyglobby"; import yargs from "yargs"; // bazel-out/k8-fastbuild/bin/packages/localize/tools/src/translate/output_path.js function getOutputPathFn(fs2, outputFolder) { const [pre, post] = outputFolder.split("{{LOCALE}}"); return post === void 0 ? (_locale, relativePath) => fs2.join(pre, relativePath) : (locale, relativePath) => fs2.join(pre + locale + post, relativePath); } // bazel-out/k8-fastbuild/bin/packages/localize/tools/src/translate/index.js import { getFileSystem, relativeFrom } from "@angular/compiler-cli/private/localize"; // bazel-out/k8-fastbuild/bin/packages/localize/tools/src/translate/asset_files/asset_translation_handler.js import { absoluteFrom } from "@angular/compiler-cli/private/localize"; var AssetTranslationHandler = class { fs; constructor(fs2) { this.fs = fs2; } canTranslate(_relativeFilePath, _contents) { return true; } translate(diagnostics2, _sourceRoot, relativeFilePath, contents, outputPathFn2, translations, sourceLocale2) { for (const translation of translations) { this.writeAssetFile(diagnostics2, outputPathFn2, translation.locale, relativeFilePath, contents); } if (sourceLocale2 !== void 0) { this.writeAssetFile(diagnostics2, outputPathFn2, sourceLocale2, relativeFilePath, contents); } } writeAssetFile(diagnostics2, outputPathFn2, locale, relativeFilePath, contents) { try { const outputPath = absoluteFrom(outputPathFn2(locale, relativeFilePath)); this.fs.ensureDir(this.fs.dirname(outputPath)); this.fs.writeFile(outputPath, contents); } catch (e) { diagnostics2.error(e.message); } } }; // bazel-out/k8-fastbuild/bin/packages/localize/tools/src/translate/source_files/source_file_translation_handler.js import { absoluteFrom as absoluteFrom2 } from "@angular/compiler-cli/private/localize"; import babel from "@babel/core"; var SourceFileTranslationHandler = class { fs; translationOptions; sourceLocaleOptions; constructor(fs2, translationOptions = {}) { this.fs = fs2; this.translationOptions = translationOptions; this.sourceLocaleOptions = { ...this.translationOptions, missingTranslation: "ignore" }; } canTranslate(relativeFilePath, _contents) { return this.fs.extname(relativeFilePath) === ".js"; } translate(diagnostics2, sourceRoot, relativeFilePath, contents, outputPathFn2, translations, sourceLocale2) { const sourceCode = Buffer.from(contents).toString("utf8"); if (!sourceCode.includes("$localize")) { for (const translation of translations) { this.writeSourceFile(diagnostics2, outputPathFn2, translation.locale, relativeFilePath, contents); } if (sourceLocale2 !== void 0) { this.writeSourceFile(diagnostics2, outputPathFn2, sourceLocale2, relativeFilePath, contents); } } else { const ast = babel.parseSync(sourceCode, { sourceRoot, filename: relativeFilePath }); if (!ast) { diagnostics2.error(`Unable to parse source file: ${this.fs.join(sourceRoot, relativeFilePath)}`); return; } for (const translationBundle of translations) { this.translateFile(diagnostics2, ast, translationBundle, sourceRoot, relativeFilePath, outputPathFn2, this.translationOptions); } if (sourceLocale2 !== void 0) { this.translateFile(diagnostics2, ast, { locale: sourceLocale2, translations: {} }, sourceRoot, relativeFilePath, outputPathFn2, this.sourceLocaleOptions); } } } translateFile(diagnostics2, ast, translationBundle, sourceRoot, filename, outputPathFn2, options2) { const translated = babel.transformFromAstSync(ast, void 0, { compact: true, generatorOpts: { minified: true }, plugins: [ makeLocalePlugin(translationBundle.locale), makeEs2015TranslatePlugin(diagnostics2, translationBundle.translations, options2, this.fs), makeEs5TranslatePlugin(diagnostics2, translationBundle.translations, options2, this.fs) ], cwd: sourceRoot, filename }); if (translated && translated.code) { this.writeSourceFile(diagnostics2, outputPathFn2, translationBundle.locale, filename, translated.code); const outputPath = absoluteFrom2(outputPathFn2(translationBundle.locale, filename)); this.fs.ensureDir(this.fs.dirname(outputPath)); this.fs.writeFile(outputPath, translated.code); } else { diagnostics2.error(`Unable to translate source file: ${this.fs.join(sourceRoot, filename)}`); return; } } writeSourceFile(diagnostics2, outputPathFn2, locale, relativeFilePath, contents) { try { const outputPath = absoluteFrom2(outputPathFn2(locale, relativeFilePath)); this.fs.ensureDir(this.fs.dirname(outputPath)); this.fs.writeFile(outputPath, contents); } catch (e) { diagnostics2.error(e.message); } } }; // bazel-out/k8-fastbuild/bin/packages/localize/tools/src/translate/translation_files/translation_loader.js var TranslationLoader = class { fs; translationParsers; duplicateTranslation; diagnostics; constructor(fs2, translationParsers, duplicateTranslation2, diagnostics2) { this.fs = fs2; this.translationParsers = translationParsers; this.duplicateTranslation = duplicateTranslation2; this.diagnostics = diagnostics2; } loadBundles(translationFilePaths2, translationFileLocales2) { return translationFilePaths2.map((filePaths, index) => { const providedLocale = translationFileLocales2[index]; return this.mergeBundles(filePaths, providedLocale); }); } loadBundle(filePath, providedLocale) { const fileContents = this.fs.readFile(filePath); const unusedParsers = /* @__PURE__ */ new Map(); for (const translationParser of this.translationParsers) { const result = translationParser.analyze(filePath, fileContents); if (!result.canParse) { unusedParsers.set(translationParser, result); continue; } const { locale: parsedLocale, translations, diagnostics: diagnostics2 } = translationParser.parse(filePath, fileContents, result.hint); if (diagnostics2.hasErrors) { throw new Error(diagnostics2.formatDiagnostics(`The translation file "${filePath}" could not be parsed.`)); } const locale = providedLocale || parsedLocale; if (locale === void 0) { throw new Error(`The translation file "${filePath}" does not contain a target locale and no explicit locale was provided for this file.`); } if (parsedLocale !== void 0 && providedLocale !== void 0 && parsedLocale !== providedLocale) { diagnostics2.warn(`The provided locale "${providedLocale}" does not match the target locale "${parsedLocale}" found in the translation file "${filePath}".`); } if (this.diagnostics) { this.diagnostics.merge(diagnostics2); } return { locale, translations, diagnostics: diagnostics2 }; } const diagnosticsMessages = []; for (const [parser, result] of unusedParsers.entries()) { diagnosticsMessages.push(result.diagnostics.formatDiagnostics(` ${parser.constructor.name} cannot parse translation file.`)); } throw new Error(`There is no "TranslationParser" that can parse this translation file: ${filePath}.` + diagnosticsMessages.join("\n")); } mergeBundles(filePaths, providedLocale) { const bundles = filePaths.map((filePath) => this.loadBundle(filePath, providedLocale)); const bundle = bundles[0]; for (let i = 1; i < bundles.length; i++) { const nextBundle = bundles[i]; if (nextBundle.locale !== bundle.locale) { if (this.diagnostics) { const previousFiles = filePaths.slice(0, i).map((f) => `"${f}"`).join(", "); this.diagnostics.warn(`When merging multiple translation files, the target locale "${nextBundle.locale}" found in "${filePaths[i]}" does not match the target locale "${bundle.locale}" found in earlier files [${previousFiles}].`); } } Object.keys(nextBundle.translations).forEach((messageId) => { if (bundle.translations[messageId] !== void 0) { this.diagnostics?.add(this.duplicateTranslation, `Duplicate translations for message "${messageId}" when merging "${filePaths[i]}".`); } else { bundle.translations[messageId] = nextBundle.translations[messageId]; } }); } return bundle; } }; // bazel-out/k8-fastbuild/bin/packages/localize/tools/src/translate/translator.js var Translator = class { fs; resourceHandlers; diagnostics; constructor(fs2, resourceHandlers, diagnostics2) { this.fs = fs2; this.resourceHandlers = resourceHandlers; this.diagnostics = diagnostics2; } translateFiles(inputPaths, rootPath, outputPathFn2, translations, sourceLocale2) { inputPaths.forEach((inputPath) => { const absInputPath = this.fs.resolve(rootPath, inputPath); const contents = this.fs.readFileBuffer(absInputPath); const relativePath = this.fs.relative(rootPath, absInputPath); for (const resourceHandler of this.resourceHandlers) { if (resourceHandler.canTranslate(relativePath, contents)) { return resourceHandler.translate(this.diagnostics, rootPath, relativePath, contents, outputPathFn2, translations, sourceLocale2); } } this.diagnostics.error(`Unable to handle resource file: ${inputPath}`); }); } }; // bazel-out/k8-fastbuild/bin/packages/localize/tools/src/translate/index.js function translateFiles({ sourceRootPath: sourceRootPath2, sourceFilePaths: sourceFilePaths2, translationFilePaths: translationFilePaths2, translationFileLocales: translationFileLocales2, outputPathFn: outputPathFn2, diagnostics: diagnostics2, missingTranslation: missingTranslation2, duplicateTranslation: duplicateTranslation2, sourceLocale: sourceLocale2 }) { const fs2 = getFileSystem(); const translationLoader = new TranslationLoader(fs2, [ new Xliff2TranslationParser(), new Xliff1TranslationParser(), new XtbTranslationParser(), new SimpleJsonTranslationParser(), new ArbTranslationParser() ], duplicateTranslation2, diagnostics2); const resourceProcessor = new Translator(fs2, [new SourceFileTranslationHandler(fs2, { missingTranslation: missingTranslation2 }), new AssetTranslationHandler(fs2)], diagnostics2); const translationFilePathsArrays = translationFilePaths2.map((filePaths) => Array.isArray(filePaths) ? filePaths.map((p) => fs2.resolve(p)) : [fs2.resolve(filePaths)]); const translations = translationLoader.loadBundles(translationFilePathsArrays, translationFileLocales2); sourceRootPath2 = fs2.resolve(sourceRootPath2); resourceProcessor.translateFiles(sourceFilePaths2.map(relativeFrom), fs2.resolve(sourceRootPath2), outputPathFn2, translations, sourceLocale2); } // bazel-out/k8-fastbuild/bin/packages/localize/tools/src/translate/cli.js process.title = "Angular Localization Message Translator (localize-translate)"; var args = process.argv.slice(2); var options = yargs(args).option("r", { alias: "root", required: true, describe: "The root path of the files to translate, either absolute or relative to the current working directory. E.g. `dist/en`.", type: "string" }).option("s", { alias: "source", required: true, describe: "A glob pattern indicating what files to translate, relative to the `root` path. E.g. `bundles/**/*`.", type: "string" }).option("l", { alias: "source-locale", describe: "The source locale of the application. If this is provided then a copy of the application will be created with no translation but just the `$localize` calls stripped out.", type: "string" }).option("t", { alias: "translations", required: true, array: true, describe: 'A list of paths to the translation files to load, either absolute or relative to the current working directory.\nE.g. `-t src/locale/messages.en.xlf src/locale/messages.fr.xlf src/locale/messages.de.xlf`.\nIf you want to merge multiple translation files for each locale, then provide the list of files in an array.\nNote that the arrays must be in double quotes if you include any whitespace within the array.\nE.g. `-t "[src/locale/messages.en.xlf, src/locale/messages-2.en.xlf]" [src/locale/messages.fr.xlf,src/locale/messages-2.fr.xlf]`', type: "string" }).option("target-locales", { array: true, describe: 'A list of target locales for the translation files, which will override any target locale parsed from the translation file.\nE.g. "-t en fr de".', type: "string" }).option("o", { alias: "outputPath", required: true, describe: "A output path pattern to where the translated files will be written.\nThe path must be either absolute or relative to the current working directory.\nThe marker `{{LOCALE}}` will be replaced with the target locale. E.g. `dist/{{LOCALE}}`.", type: "string" }).option("m", { alias: "missingTranslation", describe: "How to handle missing translations.", choices: ["error", "warning", "ignore"], default: "warning", type: "string" }).option("d", { alias: "duplicateTranslation", describe: "How to handle duplicate translations.", choices: ["error", "warning", "ignore"], default: "warning", type: "string" }).strict().help().parseSync(); var fs = new NodeJSFileSystem(); setFileSystem(fs); var sourceRootPath = options.r; var sourceFilePaths = globSync(options.s, { cwd: sourceRootPath, onlyFiles: true }); var translationFilePaths = convertArraysFromArgs(options.t); var outputPathFn = getOutputPathFn(fs, fs.resolve(options.o)); var diagnostics = new Diagnostics(); var missingTranslation = options.m; var duplicateTranslation = options.d; var sourceLocale = options.l; var translationFileLocales = options["target-locales"] || []; translateFiles({ sourceRootPath, sourceFilePaths, translationFilePaths, translationFileLocales, outputPathFn, diagnostics, missingTranslation, duplicateTranslation, sourceLocale }); diagnostics.messages.forEach((m) => console.warn(`${m.type}: ${m.message}`)); process.exit(diagnostics.hasErrors ? 1 : 0); function convertArraysFromArgs(args2) { return args2.map((arg) => arg.startsWith("[") && arg.endsWith("]") ? arg.slice(1, -1).split(",").map((arg2) => arg2.trim()) : arg); } /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ //# sourceMappingURL=cli.js.map