88
99import 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 : / ^ a n g u l a r : l o c a l e \/ d a t a : / } , 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 - z A - Z 0 - 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