@@ -10,6 +10,7 @@ import type { BuildOptions } from 'esbuild';
10
10
import assert from 'node:assert' ;
11
11
import { createHash } from 'node:crypto' ;
12
12
import { readFile } from 'node:fs/promises' ;
13
+ import { createRequire } from 'node:module' ;
13
14
import { extname , join , relative } from 'node:path' ;
14
15
import type { NormalizedApplicationBuildOptions } from '../../builders/application/options' ;
15
16
import { allowMangle } from '../../utils/environment-options' ;
@@ -114,8 +115,11 @@ export function createBrowserCodeBundleOptions(
114
115
createVirtualModulePlugin ( {
115
116
namespace,
116
117
loadContent : async ( _ , build ) => {
118
+ let hasLocalizePolyfill = false ;
117
119
const polyfillPaths = await Promise . all (
118
120
polyfills . map ( async ( path ) => {
121
+ hasLocalizePolyfill ||= path . startsWith ( '@angular/localize' ) ;
122
+
119
123
if ( path . startsWith ( 'zone.js' ) || ! extname ( path ) ) {
120
124
return path ;
121
125
}
@@ -130,10 +134,29 @@ export function createBrowserCodeBundleOptions(
130
134
} ) ,
131
135
) ;
132
136
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
+
133
158
return {
134
- contents : polyfillPaths
135
- . map ( ( file ) => `import '${ file . replace ( / \\ / g, '/' ) } ';` )
136
- . join ( '\n' ) ,
159
+ contents,
137
160
loader : 'js' ,
138
161
resolveDir : workspaceRoot ,
139
162
} ;
@@ -258,6 +281,27 @@ export function createServerCodeBundleOptions(
258
281
contents . push ( `export { ɵresetCompiledComponents } from '@angular/core';` ) ;
259
282
}
260
283
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
+
261
305
if ( prerenderOptions ?. discoverRoutes ) {
262
306
// We do not import it directly so that node.js modules are resolved using the correct context.
263
307
const routesExtractorCode = await readFile (
0 commit comments