/
githubmirror
/
angular-cli
Обзор
Документация
Войти
/
githubmirror
/
angular-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/angular/build/src/builders/application/i18n.ts
209 строк
7 KB
Charles Lyding
refactor(@angular/build): eliminate circular dependencies in bundler context
05 май 2026, 14:30
05 май 2026, 14:30
ca4b087
Код
Авторство
О чём код?
/** * @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 */ import { BuilderContext } from '@angular-devkit/architect'; import type { Metafile } from 'esbuild'; import { join } from 'node:path'; import { ExecutionResult, PrerenderedRoutesRecord, } from '../../tools/esbuild/bundler-execution-result'; import { BuildOutputFileType, InitialFileRecord } from '../../tools/esbuild/bundler-files'; import { I18nInliner } from '../../tools/esbuild/i18n-inliner'; import { maxWorkers } from '../../utils/environment-options'; import { loadTranslations } from '../../utils/i18n-options'; import { createTranslationLoader } from '../../utils/load-translations'; import { executePostBundleSteps } from './execute-post-bundle'; import { NormalizedApplicationBuildOptions, getLocaleBaseHref } from './options'; /** * Inlines all active locales as specified by the application build options into all * application JavaScript files created during the build. * @param metafile An esbuild metafile object. * @param options The normalized application builder options used to create the build. * @param executionResult The result of an executed build. * @param initialFiles A map containing initial file information for the executed build. */ export async function inlineI18n( metafile: Metafile, options: NormalizedApplicationBuildOptions, executionResult: ExecutionResult, initialFiles: Map<string, InitialFileRecord>, ): Promise<{ errors: string[]; warnings: string[]; prerenderedRoutes: PrerenderedRoutesRecord; }> { const { i18nOptions, optimizationOptions, baseHref, cacheOptions } = options; // Create the multi-threaded inliner with common options and the files generated from the build. const inliner = new I18nInliner( { missingTranslation: i18nOptions.missingTranslationBehavior ?? 'warning', outputFiles: executionResult.outputFiles, shouldOptimize: optimizationOptions.scripts, persistentCachePath: cacheOptions.enabled ? cacheOptions.path : undefined, }, maxWorkers, ); const inlineResult: { errors: string[]; warnings: string[]; prerenderedRoutes: PrerenderedRoutesRecord; } = { errors: [], warnings: [], prerenderedRoutes: {}, }; // For each active locale, use the inliner to process the output files of the build. const updatedOutputFiles = []; const updatedAssetFiles = []; // Root and SSR entry files are not modified. const unModifiedOutputFiles = executionResult.outputFiles.filter( ({ type }) => type === BuildOutputFileType.Root || type === BuildOutputFileType.ServerRoot, ); try { for (const locale of i18nOptions.inlineLocales) { // A locale specific set of files is returned from the inliner. const localeInlineResult = await inliner.inlineForLocale( locale, i18nOptions.locales[locale].translation, ); const localeOutputFiles = localeInlineResult.outputFiles; inlineResult.errors.push(...localeInlineResult.errors); inlineResult.warnings.push(...localeInlineResult.warnings); const { errors, warnings, additionalAssets, additionalOutputFiles, prerenderedRoutes: generatedRoutes, } = await executePostBundleSteps( metafile, { ...options, baseHref: getLocaleBaseHref(baseHref, i18nOptions, locale) ?? baseHref, }, [...unModifiedOutputFiles, ...localeOutputFiles], executionResult.assetFiles, initialFiles, locale, ); localeOutputFiles.push(...additionalOutputFiles); inlineResult.errors.push(...errors); inlineResult.warnings.push(...warnings); // Update directory with locale base or subPath const subPath = i18nOptions.locales[locale].subPath; if (i18nOptions.flatOutput !== true) { localeOutputFiles.forEach((file) => { file.path = join(subPath, file.path); }); for (const assetFile of [...executionResult.assetFiles, ...additionalAssets]) { updatedAssetFiles.push({ source: assetFile.source, destination: join(subPath, assetFile.destination), }); } } else { executionResult.assetFiles.push(...additionalAssets); } inlineResult.prerenderedRoutes = { ...inlineResult.prerenderedRoutes, ...generatedRoutes }; updatedOutputFiles.push(...localeOutputFiles); } // Update the result with all localized files. executionResult.outputFiles = [ // Root and SSR entry files are not modified. ...unModifiedOutputFiles, // Updated files for each locale. ...updatedOutputFiles, ]; // Assets are only changed if not using the flat output option if (!i18nOptions.flatOutput) { executionResult.assetFiles = updatedAssetFiles; } // Inline any template updates if present if (executionResult.templateUpdates?.size) { // The development server only allows a single locale but issue a warning if used programmatically (experimental) // with multiple locales and template HMR. if (i18nOptions.inlineLocales.size > 1) { inlineResult.warnings.push( `Component HMR updates can only be inlined with a single locale. The first locale will be used.`, ); } const firstLocale = [...i18nOptions.inlineLocales][0]; for (const [id, content] of executionResult.templateUpdates) { const templateUpdateResult = await inliner.inlineTemplateUpdate( firstLocale, i18nOptions.locales[firstLocale].translation, content, id, ); executionResult.templateUpdates.set(id, templateUpdateResult.code); inlineResult.errors.push(...templateUpdateResult.errors); inlineResult.warnings.push(...templateUpdateResult.warnings); } } return inlineResult; } finally { await inliner.close(); } } /** * Loads all active translations using the translation loaders from the `@angular/localize` package. * @param context The architect builder context for the current build. * @param i18n The normalized i18n options to use. */ export async function loadActiveTranslations( context: BuilderContext, i18n: NormalizedApplicationBuildOptions['i18nOptions'], ) { // Load locale data and translations (if present) let loader; for (const [locale, desc] of Object.entries(i18n.locales)) { if (!i18n.inlineLocales.has(locale) && locale !== i18n.sourceLocale) { continue; } if (!desc.files.length) { continue; } loader ??= await createTranslationLoader(); loadTranslations( locale, desc, context.workspaceRoot, loader, { warn(message) { context.logger.warn(message); }, error(message) { throw new Error(message); }, }, undefined, i18n.duplicateTranslationBehavior, ); } }