Skip to content

Commit 52f5956

Browse files
clydinalan-agius4
authored andcommitted
fix(@angular-devkit/build-angular): ensure i18n locale data is included in SSR application builds
When using the application builder to create server builds for SSR and/or prerendering with localization enabled, the locale data for each enabled locale is now properly included in the server output files. While browser builds contain this data in the polyfills output file, the server builds do not contain a polyfill output file, as a result, the locale data is added to the main server code instead. This is also the case for other polyfill-like code including zone.js. (cherry picked from commit a136f2e)
1 parent 20a0c2a commit 52f5956

File tree

2 files changed

+53
-66
lines changed

2 files changed

+53
-66
lines changed

packages/angular_devkit/build_angular/src/tools/esbuild/application-code-bundle.ts

+17
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,23 @@ export function createServerCodeBundleOptions(
308308

309309
polyfills.push(`import '@angular/platform-server/init';`);
310310

311+
// Add Angular's global locale data if i18n options are present.
312+
let needLocaleDataPlugin = false;
313+
if (options.i18nOptions.shouldInline) {
314+
// Add locale data for all active locales
315+
for (const locale of options.i18nOptions.inlineLocales) {
316+
polyfills.unshift(`import 'angular:locale/data:${locale}';`);
317+
}
318+
needLocaleDataPlugin = true;
319+
} else if (options.i18nOptions.hasDefinedSourceLocale) {
320+
// When not inlining and a source local is present, use the source locale data directly
321+
polyfills.unshift(`import 'angular:locale/data:${options.i18nOptions.sourceLocale}';`);
322+
needLocaleDataPlugin = true;
323+
}
324+
if (needLocaleDataPlugin) {
325+
buildOptions.plugins.push(createAngularLocaleDataPlugin());
326+
}
327+
311328
buildOptions.plugins.push(
312329
createVirtualModulePlugin({
313330
namespace: mainServerNamespace,
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { readFileSync, readdirSync } from 'node:fs';
22
import { getGlobalVariable } from '../../utils/env';
3-
import { copyFile, expectFileToMatch, replaceInFile, writeFile } from '../../utils/fs';
43
import { installWorkspacePackages, uninstallPackage } from '../../utils/packages';
54
import { ng } from '../../utils/process';
65
import { updateJsonFile, useSha } from '../../utils/project';
7-
import { readNgVersion } from '../../utils/version';
8-
9-
const snapshots = require('../../ng-snapshot/package.json');
6+
import { langTranslations, setupI18nConfig } from './setup';
7+
import { expectFileToMatch } from '../../utils/fs';
108

119
export default async function () {
1210
const useWebpackBuilder = !getGlobalVariable('argv')['esbuild'];
@@ -15,12 +13,28 @@ export default async function () {
1513
return;
1614
}
1715

18-
const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots'];
19-
await updateJsonFile('package.json', (packageJson) => {
20-
const dependencies = packageJson['dependencies'];
21-
dependencies['@angular/localize'] = isSnapshotBuild
22-
? snapshots.dependencies['@angular/localize']
23-
: readNgVersion();
16+
// Setup i18n tests and config.
17+
await setupI18nConfig();
18+
19+
// Update angular.json
20+
await updateJsonFile('angular.json', (workspaceJson) => {
21+
const appProject = workspaceJson.projects['test-project'];
22+
// tslint:disable-next-line: no-any
23+
const i18n: Record<string, any> = appProject.i18n;
24+
25+
i18n.sourceLocale = {
26+
baseHref: '',
27+
};
28+
29+
i18n.locales['fr'] = {
30+
translation: i18n.locales['fr'],
31+
baseHref: '',
32+
};
33+
34+
i18n.locales['de'] = {
35+
translation: i18n.locales['de'],
36+
baseHref: '',
37+
};
2438
});
2539

2640
// forcibly remove in case another test doesn't clean itself up
@@ -31,65 +45,12 @@ export default async function () {
3145
await useSha();
3246
await installWorkspacePackages();
3347

34-
// Set configurations for each locale.
35-
const langTranslations = [
36-
{ lang: 'en-US', translation: 'Hello i18n!' },
37-
{ lang: 'fr', translation: 'Bonjour i18n!' },
38-
];
39-
40-
await updateJsonFile('angular.json', (workspaceJson) => {
41-
const appProject = workspaceJson.projects['test-project'];
42-
const appArchitect = appProject.architect || appProject.targets;
43-
const buildOptions = appArchitect['build'].options;
44-
45-
// Enable localization for all locales
46-
buildOptions.localize = true;
47-
48-
// Add locale definitions to the project
49-
const i18n: Record<string, any> = (appProject.i18n = { locales: {} });
50-
for (const { lang } of langTranslations) {
51-
if (lang == 'en-US') {
52-
i18n.sourceLocale = lang;
53-
} else {
54-
i18n.locales[lang] = `src/locale/messages.${lang}.xlf`;
55-
}
56-
}
57-
});
58-
59-
// Add a translatable element
60-
// Extraction of i18n only works on browser targets.
61-
// Let's add the same translation that there is in the app-shell
62-
await writeFile(
63-
'src/app/app.component.html',
64-
'<h1 i18n="An introduction header for this sample">Hello i18n!</h1>',
65-
);
66-
67-
// Extract the translation messages and copy them for each language.
68-
await ng('extract-i18n', '--output-path=src/locale');
69-
await expectFileToMatch('src/locale/messages.xlf', `source-language="en-US"`);
70-
await expectFileToMatch('src/locale/messages.xlf', `An introduction header for this sample`);
71-
72-
for (const { lang, translation } of langTranslations) {
73-
if (lang != 'en-US') {
74-
await copyFile('src/locale/messages.xlf', `src/locale/messages.${lang}.xlf`);
75-
await replaceInFile(
76-
`src/locale/messages.${lang}.xlf`,
77-
'source-language="en-US"',
78-
`source-language="en-US" target-language="${lang}"`,
79-
);
80-
await replaceInFile(
81-
`src/locale/messages.${lang}.xlf`,
82-
'<source>Hello i18n!</source>',
83-
`<source>Hello i18n!</source>\n<target>${translation}</target>`,
84-
);
85-
}
86-
}
87-
8848
// Build each locale and verify the output.
8949
await ng('build', '--output-hashing=none');
9050

9151
for (const { lang, translation } of langTranslations) {
9252
let foundTranslation = false;
53+
let foundLocaleData = false;
9354

9455
// The translation may be in any of the lazy-loaded generated chunks
9556
for (const entry of readdirSync(`dist/test-project/server/${lang}/`)) {
@@ -98,14 +59,23 @@ export default async function () {
9859
}
9960

10061
const contents = readFileSync(`dist/test-project/server/${lang}/${entry}`, 'utf-8');
101-
foundTranslation ||= contents.includes(translation);
102-
if (foundTranslation) {
62+
63+
// Check for translated content
64+
foundTranslation ||= contents.includes(translation.helloPartial);
65+
// Check for the locale data month name to be present
66+
foundLocaleData ||= contents.includes(translation.date);
67+
68+
if (foundTranslation && foundLocaleData) {
10369
break;
10470
}
10571
}
10672

10773
if (!foundTranslation) {
10874
throw new Error(`Translation not found in 'dist/test-project/server/${lang}/'`);
10975
}
76+
77+
if (!foundLocaleData) {
78+
throw new Error(`Locale data not found in 'dist/test-project/server/${lang}/'`);
79+
}
11080
}
11181
}

0 commit comments

Comments
 (0)