"use strict"; /** * @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 */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.inlineLocales = inlineLocales; const remapping_1 = __importDefault(require("@ampproject/remapping")); const core_1 = require("@babel/core"); const fs = __importStar(require("node:fs/promises")); const path = __importStar(require("node:path")); const node_worker_threads_1 = require("node:worker_threads"); const environment_options_1 = require("./environment-options"); const error_1 = require("./error"); const load_esm_1 = require("./load-esm"); // Lazy loaded webpack-sources object // Webpack is only imported if needed during the processing let webpackSources; const { i18n } = (node_worker_threads_1.workerData || {}); /** * Internal flag to enable the direct usage of the `@angular/localize` translation plugins. * Their usage is currently several times slower than the string manipulation method. * Future work to optimize the plugins should enable plugin usage as the default. */ const USE_LOCALIZE_PLUGINS = false; /** * Cached instance of the `@angular/localize/tools` module. * This is used to remove the need to repeatedly import the module per file translation. */ let localizeToolsModule; /** * Attempts to load the `@angular/localize/tools` module containing the functionality to * perform the file translations. * This module must be dynamically loaded as it is an ESM module and this file is CommonJS. */ async function loadLocalizeTools() { if (localizeToolsModule !== undefined) { return localizeToolsModule; } // Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround. // Once TypeScript provides support for keeping the dynamic import this workaround can be // changed to a direct dynamic import. return (0, load_esm_1.loadEsmModule)('@angular/localize/tools'); } async function createI18nPlugins(locale, translation, missingTranslation, shouldInline, localeDataContent) { const { Diagnostics, makeEs2015TranslatePlugin, makeLocalePlugin } = await loadLocalizeTools(); const plugins = []; const diagnostics = new Diagnostics(); if (shouldInline) { plugins.push( // eslint-disable-next-line @typescript-eslint/no-explicit-any makeEs2015TranslatePlugin(diagnostics, (translation || {}), { missingTranslation: translation === undefined ? 'ignore' : missingTranslation, })); } plugins.push(makeLocalePlugin(locale)); if (localeDataContent) { plugins.push({ visitor: { Program(path) { path.unshiftContainer('body', core_1.template.ast(localeDataContent)); }, }, }); } return { diagnostics, plugins }; } const localizeName = '$localize'; async function inlineLocales(options) { if (!i18n || i18n.inlineLocales.size === 0) { return { file: options.filename, diagnostics: [], count: 0 }; } if (i18n.flatOutput && i18n.inlineLocales.size > 1) { throw new Error('Flat output is only supported when inlining one locale.'); } const hasLocalizeName = options.code.includes(localizeName); if (!hasLocalizeName && !options.setLocale) { return inlineCopyOnly(options); } await loadLocalizeTools(); let ast; try { ast = (0, core_1.parseSync)(options.code, { babelrc: false, configFile: false, sourceType: 'unambiguous', filename: options.filename, }); } catch (error) { (0, error_1.assertIsError)(error); // Make the error more readable. // Same errors will contain the full content of the file as the error message // Which makes it hard to find the actual error message. const index = error.message.indexOf(')\n'); const msg = index !== -1 ? error.message.slice(0, index + 1) : error.message; throw new Error(`${msg}\nAn error occurred inlining file "${options.filename}"`); } if (!ast) { throw new Error(`Unknown error occurred inlining file "${options.filename}"`); } if (!USE_LOCALIZE_PLUGINS) { return inlineLocalesDirect(ast, options); } const diagnostics = []; for (const locale of i18n.inlineLocales) { const isSourceLocale = locale === i18n.sourceLocale; // eslint-disable-next-line @typescript-eslint/no-explicit-any const translations = isSourceLocale ? {} : i18n.locales[locale].translation || {}; let localeDataContent; if (options.setLocale) { // If locale data is provided, load it and prepend to file const localeDataPath = i18n.locales[locale]?.dataPath; if (localeDataPath) { localeDataContent = await loadLocaleData(localeDataPath, true); } } const { diagnostics: localeDiagnostics, plugins } = await createI18nPlugins(locale, translations, isSourceLocale ? 'ignore' : options.missingTranslation || 'warning', true, localeDataContent); const transformResult = (0, core_1.transformFromAstSync)(ast, options.code, { filename: options.filename, // using false ensures that babel will NOT search and process sourcemap comments (large memory usage) // The types do not include the false option even though it is valid // eslint-disable-next-line @typescript-eslint/no-explicit-any inputSourceMap: false, babelrc: false, configFile: false, plugins, compact: !environment_options_1.shouldBeautify, sourceMaps: !!options.map, }); diagnostics.push(...localeDiagnostics.messages); if (!transformResult || !transformResult.code) { throw new Error(`Unknown error occurred processing bundle for "${options.filename}".`); } const outputPath = path.join(options.outputPath, i18n.flatOutput ? '' : locale, options.filename); await fs.writeFile(outputPath, transformResult.code); if (options.map && transformResult.map) { const outputMap = (0, remapping_1.default)([transformResult.map, options.map], () => null); await fs.writeFile(outputPath + '.map', JSON.stringify(outputMap)); } } return { file: options.filename, diagnostics }; } async function inlineLocalesDirect(ast, options) { if (!i18n || i18n.inlineLocales.size === 0) { return { file: options.filename, diagnostics: [], count: 0 }; } const { default: generate } = await Promise.resolve().then(() => __importStar(require('@babel/generator'))); const localizeDiag = await loadLocalizeTools(); const diagnostics = new localizeDiag.Diagnostics(); const positions = findLocalizePositions(ast, options, localizeDiag); if (positions.length === 0 && !options.setLocale) { return inlineCopyOnly(options); } const inputMap = !!options.map && JSON.parse(options.map); // Cleanup source root otherwise it will be added to each source entry const mapSourceRoot = inputMap && inputMap.sourceRoot; if (inputMap) { delete inputMap.sourceRoot; } // Load Webpack only when needed if (webpackSources === undefined) { webpackSources = (await Promise.resolve().then(() => __importStar(require('webpack')))).sources; } const { ConcatSource, OriginalSource, ReplaceSource, SourceMapSource } = webpackSources; for (const locale of i18n.inlineLocales) { const content = new ReplaceSource(inputMap ? new SourceMapSource(options.code, options.filename, inputMap) : new OriginalSource(options.code, options.filename)); const isSourceLocale = locale === i18n.sourceLocale; // eslint-disable-next-line @typescript-eslint/no-explicit-any const translations = isSourceLocale ? {} : i18n.locales[locale].translation || {}; for (const position of positions) { const translated = localizeDiag.translate(diagnostics, translations, position.messageParts, position.expressions, isSourceLocale ? 'ignore' : options.missingTranslation || 'warning'); const expression = localizeDiag.buildLocalizeReplacement(translated[0], translated[1]); const { code } = generate(expression); content.replace(position.start, position.end - 1, code); } let outputSource = content; if (options.setLocale) { const setLocaleText = `globalThis.$localize=Object.assign(globalThis.$localize || {},{locale:"${locale}"});\n`; // If locale data is provided, load it and prepend to file let localeDataSource; const localeDataPath = i18n.locales[locale] && i18n.locales[locale].dataPath; if (localeDataPath) { const localeDataContent = await loadLocaleData(localeDataPath, true); localeDataSource = new OriginalSource(localeDataContent, path.basename(localeDataPath)); } outputSource = localeDataSource ? // The semicolon ensures that there is no syntax error between statements new ConcatSource(setLocaleText, localeDataSource, ';\n', content) : new ConcatSource(setLocaleText, content); } const { source: outputCode, map: outputMap } = outputSource.sourceAndMap(); const outputPath = path.join(options.outputPath, i18n.flatOutput ? '' : locale, options.filename); await fs.writeFile(outputPath, outputCode); if (inputMap && outputMap) { outputMap.file = options.filename; if (mapSourceRoot) { outputMap.sourceRoot = mapSourceRoot; } await fs.writeFile(outputPath + '.map', JSON.stringify(outputMap)); } } return { file: options.filename, diagnostics: diagnostics.messages, count: positions.length }; } async function inlineCopyOnly(options) { if (!i18n) { throw new Error('i18n options are missing'); } for (const locale of i18n.inlineLocales) { const outputPath = path.join(options.outputPath, i18n.flatOutput ? '' : locale, options.filename); await fs.writeFile(outputPath, options.code); if (options.map) { await fs.writeFile(outputPath + '.map', options.map); } } return { file: options.filename, diagnostics: [], count: 0 }; } function findLocalizePositions(ast, options, utils) { const positions = []; // Workaround to ensure a path hub is present for traversal const { File } = require('@babel/core'); const file = new File({}, { code: options.code, ast }); (0, core_1.traverse)(file.ast, { TaggedTemplateExpression(path) { if (core_1.types.isIdentifier(path.node.tag) && path.node.tag.name === localizeName) { const [messageParts, expressions] = unwrapTemplateLiteral(path, utils); positions.push({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion start: path.node.start, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion end: path.node.end, messageParts, expressions, }); } }, }); return positions; } function unwrapTemplateLiteral(path, utils) { const [messageParts] = utils.unwrapMessagePartsFromTemplateLiteral(path.get('quasi').get('quasis')); const [expressions] = utils.unwrapExpressionsFromTemplateLiteral(path.get('quasi')); return [messageParts, expressions]; } async function loadLocaleData(path, optimize) { // The path is validated during option processing before the build starts const content = await fs.readFile(path, 'utf8'); // Downlevel and optimize the data const transformResult = await (0, core_1.transformAsync)(content, { filename: path, // The types do not include the false option even though it is valid // eslint-disable-next-line @typescript-eslint/no-explicit-any inputSourceMap: false, babelrc: false, configFile: false, presets: [ [ require.resolve('@babel/preset-env'), { bugfixes: true, targets: { esmodules: true }, }, ], ], minified: environment_options_1.allowMinify && optimize, compact: !environment_options_1.shouldBeautify && optimize, comments: !optimize, }); if (!transformResult || !transformResult.code) { throw new Error(`Unknown error occurred processing bundle for "${path}".`); } return transformResult.code; }