@@ -10,6 +10,7 @@ import type { BuildOptions } from 'esbuild';
1010import assert from 'node:assert' ;
1111import { createHash } from 'node:crypto' ;
1212import { readFile } from 'node:fs/promises' ;
13+ import { createRequire } from 'node:module' ;
1314import { extname , join , relative } from 'node:path' ;
1415import type { NormalizedApplicationBuildOptions } from '../../builders/application/options' ;
1516import { allowMangle } from '../../utils/environment-options' ;
@@ -114,8 +115,11 @@ export function createBrowserCodeBundleOptions(
114115 createVirtualModulePlugin ( {
115116 namespace,
116117 loadContent : async ( _ , build ) => {
118+ let hasLocalizePolyfill = false ;
117119 const polyfillPaths = await Promise . all (
118120 polyfills . map ( async ( path ) => {
121+ hasLocalizePolyfill ||= path . startsWith ( '@angular/localize' ) ;
122+
119123 if ( path . startsWith ( 'zone.js' ) || ! extname ( path ) ) {
120124 return path ;
121125 }
@@ -130,10 +134,29 @@ export function createBrowserCodeBundleOptions(
130134 } ) ,
131135 ) ;
132136
137+ if ( ! options . i18nOptions . shouldInline && ! hasLocalizePolyfill ) {
138+ // Cannot use `build.resolve` here since it does not allow overriding the external options
139+ // and the actual presence of the `@angular/localize` package needs to be checked here.
140+ const workspaceRequire = createRequire ( workspaceRoot + '/' ) ;
141+ try {
142+ workspaceRequire . resolve ( '@angular/localize' ) ;
143+ // The resolve call above will throw if not found
144+ polyfillPaths . push ( '@angular/localize/init' ) ;
145+ } catch { }
146+ }
147+
148+ // Generate module contents with an import statement per defined polyfill
149+ let contents = polyfillPaths
150+ . map ( ( file ) => `import '${ file . replace ( / \\ / g, '/' ) } ';` )
151+ . join ( '\n' ) ;
152+
153+ // If not inlining translations and source locale is defined, inject the locale specifier
154+ if ( ! options . i18nOptions . shouldInline && options . i18nOptions . hasDefinedSourceLocale ) {
155+ contents += `(globalThis.$localize ??= {}).locale = "${ options . i18nOptions . sourceLocale } ";\n` ;
156+ }
157+
133158 return {
134- contents : polyfillPaths
135- . map ( ( file ) => `import '${ file . replace ( / \\ / g, '/' ) } ';` )
136- . join ( '\n' ) ,
159+ contents,
137160 loader : 'js' ,
138161 resolveDir : workspaceRoot ,
139162 } ;
@@ -258,6 +281,27 @@ export function createServerCodeBundleOptions(
258281 contents . push ( `export { ɵresetCompiledComponents } from '@angular/core';` ) ;
259282 }
260283
284+ if ( ! options . i18nOptions . shouldInline ) {
285+ // Cannot use `build.resolve` here since it does not allow overriding the external options
286+ // and the actual presence of the `@angular/localize` package needs to be checked here.
287+ const workspaceRequire = createRequire ( workspaceRoot + '/' ) ;
288+ try {
289+ workspaceRequire . resolve ( '@angular/localize' ) ;
290+ // The resolve call above will throw if not found
291+ contents . push ( `import '@angular/localize/init';` ) ;
292+ } catch { }
293+ }
294+
295+ if ( options . i18nOptions . shouldInline ) {
296+ // When inlining, a placeholder is used to allow the post-processing step to inject the $localize locale identifier
297+ contents . push ( '(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";' ) ;
298+ } else if ( options . i18nOptions . hasDefinedSourceLocale ) {
299+ // If not inlining translations and source locale is defined, inject the locale specifier
300+ contents . push (
301+ `(globalThis.$localize ??= {}).locale = "${ options . i18nOptions . sourceLocale } ";` ,
302+ ) ;
303+ }
304+
261305 if ( prerenderOptions ?. discoverRoutes ) {
262306 // We do not import it directly so that node.js modules are resolved using the correct context.
263307 const routesExtractorCode = await readFile (
0 commit comments