Skip to content

Commit aa9e7c6

Browse files
clydinalan-agius4
authored andcommitted
fix(@angular-devkit/build-angular): normalize locale tags with Intl API when resolving in application builder
All specified locales in the i18n configuration for an application use the `application` or `browser-esbuild` builders will now be normalized using the `Intl` API. This ensures that the provided locale tags are both well-formed and correctly cased. This also more easily allowed an optimization for the default locale which is already embedded into the framework and will now no longer be injected by the build process if active. (cherry picked from commit f0c032d)
1 parent bce3fa6 commit aa9e7c6

File tree

1 file changed

+47
-17
lines changed

1 file changed

+47
-17
lines changed

packages/angular_devkit/build_angular/src/tools/esbuild/i18n-locale-plugin.ts

+47-17
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88

99
import type { Plugin } from 'esbuild';
1010

11+
/**
12+
* The internal namespace used by generated locale import statements and Angular locale data plugin.
13+
*/
14+
export const LOCALE_DATA_NAMESPACE = 'angular:locale/data';
15+
1116
/**
1217
* The base module location used to search for locale specific data.
1318
*/
@@ -35,15 +40,39 @@ export function createAngularLocaleDataPlugin(): Plugin {
3540

3641
build.onResolve({ filter: /^angular:locale\/data:/ }, async ({ path }) => {
3742
// Extract the locale from the path
38-
const originalLocale = path.split(':', 3)[2];
43+
const rawLocaleTag = path.split(':', 3)[2];
3944

40-
// Remove any private subtags since these will never match
41-
let partialLocale = originalLocale.replace(/-x(-[a-zA-Z0-9]{1,8})+$/, '');
45+
// Extract and normalize the base name of the raw locale tag
46+
let partialLocaleTag: string;
47+
try {
48+
const locale = new Intl.Locale(rawLocaleTag);
49+
partialLocaleTag = locale.baseName;
50+
} catch {
51+
return {
52+
path: rawLocaleTag,
53+
namespace: LOCALE_DATA_NAMESPACE,
54+
errors: [
55+
{
56+
text: `Invalid or unsupported locale provided in configuration: "${rawLocaleTag}"`,
57+
},
58+
],
59+
};
60+
}
4261

4362
let exact = true;
44-
while (partialLocale) {
45-
const potentialPath = `${LOCALE_DATA_BASE_MODULE}/${partialLocale}`;
63+
while (partialLocaleTag) {
64+
// Angular embeds the `en`/`en-US` locale into the framework and it does not need to be included again here.
65+
// The onLoad hook below for the locale data namespace has an `empty` loader that will prevent inclusion.
66+
// Angular does not contain exact locale data for `en-US` but `en` is equivalent.
67+
if (partialLocaleTag === 'en' || partialLocaleTag === 'en-US') {
68+
return {
69+
path: rawLocaleTag,
70+
namespace: LOCALE_DATA_NAMESPACE,
71+
};
72+
}
4673

74+
// Attempt to resolve the locale tag data within the Angular base module location
75+
const potentialPath = `${LOCALE_DATA_BASE_MODULE}/${partialLocaleTag}`;
4776
const result = await build.resolve(potentialPath, {
4877
kind: 'import-statement',
4978
resolveDir: build.initialOptions.absWorkingDir,
@@ -58,39 +87,40 @@ export function createAngularLocaleDataPlugin(): Plugin {
5887
...result.warnings,
5988
{
6089
location: null,
61-
text: `Locale data for '${originalLocale}' cannot be found. Using locale data for '${partialLocale}'.`,
90+
text: `Locale data for '${rawLocaleTag}' cannot be found. Using locale data for '${partialLocaleTag}'.`,
6291
},
6392
],
6493
};
6594
}
6695
}
6796

68-
// Remove the last subtag and try again with a less specific locale
69-
const parts = partialLocale.split('-');
70-
partialLocale = parts.slice(0, -1).join('-');
97+
// Remove the last subtag and try again with a less specific locale.
98+
// Usually the match is exact so the string splitting here is not done until actually needed after the exact
99+
// match fails to resolve.
100+
const parts = partialLocaleTag.split('-');
101+
partialLocaleTag = parts.slice(0, -1).join('-');
71102
exact = false;
72-
// The locales "en" and "en-US" are considered exact to retain existing behavior
73-
if (originalLocale === 'en-US' && partialLocale === 'en') {
74-
exact = true;
75-
}
76103
}
77104

78105
// Not found so issue a warning and use an empty loader. Framework built-in `en-US` data will be used.
79106
// This retains existing behavior as in the Webpack-based builder.
80107
return {
81-
path: originalLocale,
82-
namespace: 'angular:locale/data',
108+
path: rawLocaleTag,
109+
namespace: LOCALE_DATA_NAMESPACE,
83110
warnings: [
84111
{
85112
location: null,
86-
text: `Locale data for '${originalLocale}' cannot be found. No locale data will be included for this locale.`,
113+
text: `Locale data for '${rawLocaleTag}' cannot be found. No locale data will be included for this locale.`,
87114
},
88115
],
89116
};
90117
});
91118

92119
// Locales that cannot be found will be loaded as empty content with a warning from the resolve step
93-
build.onLoad({ filter: /./, namespace: 'angular:locale/data' }, () => ({ loader: 'empty' }));
120+
build.onLoad({ filter: /./, namespace: LOCALE_DATA_NAMESPACE }, () => ({
121+
contents: '',
122+
loader: 'empty',
123+
}));
94124
},
95125
};
96126
}

0 commit comments

Comments
 (0)