Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): normalize locale tags with Intl A…
Browse files Browse the repository at this point in the history
…PI 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.
  • Loading branch information
clydin authored and alan-agius4 committed Dec 4, 2023
1 parent aca0be0 commit f0c032d
Showing 1 changed file with 47 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

import type { Plugin } from 'esbuild';

/**
* The internal namespace used by generated locale import statements and Angular locale data plugin.
*/
export const LOCALE_DATA_NAMESPACE = 'angular:locale/data';

/**
* The base module location used to search for locale specific data.
*/
Expand Down Expand Up @@ -35,15 +40,39 @@ export function createAngularLocaleDataPlugin(): Plugin {

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

// Remove any private subtags since these will never match
let partialLocale = originalLocale.replace(/-x(-[a-zA-Z0-9]{1,8})+$/, '');
// Extract and normalize the base name of the raw locale tag
let partialLocaleTag: string;
try {
const locale = new Intl.Locale(rawLocaleTag);
partialLocaleTag = locale.baseName;
} catch {
return {
path: rawLocaleTag,
namespace: LOCALE_DATA_NAMESPACE,
errors: [
{
text: `Invalid or unsupported locale provided in configuration: "${rawLocaleTag}"`,
},
],
};
}

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

// Attempt to resolve the locale tag data within the Angular base module location
const potentialPath = `${LOCALE_DATA_BASE_MODULE}/${partialLocaleTag}`;
const result = await build.resolve(potentialPath, {
kind: 'import-statement',
resolveDir: build.initialOptions.absWorkingDir,
Expand All @@ -58,39 +87,40 @@ export function createAngularLocaleDataPlugin(): Plugin {
...result.warnings,
{
location: null,
text: `Locale data for '${originalLocale}' cannot be found. Using locale data for '${partialLocale}'.`,
text: `Locale data for '${rawLocaleTag}' cannot be found. Using locale data for '${partialLocaleTag}'.`,
},
],
};
}
}

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

// Not found so issue a warning and use an empty loader. Framework built-in `en-US` data will be used.
// This retains existing behavior as in the Webpack-based builder.
return {
path: originalLocale,
namespace: 'angular:locale/data',
path: rawLocaleTag,
namespace: LOCALE_DATA_NAMESPACE,
warnings: [
{
location: null,
text: `Locale data for '${originalLocale}' cannot be found. No locale data will be included for this locale.`,
text: `Locale data for '${rawLocaleTag}' cannot be found. No locale data will be included for this locale.`,
},
],
};
});

// Locales that cannot be found will be loaded as empty content with a warning from the resolve step
build.onLoad({ filter: /./, namespace: 'angular:locale/data' }, () => ({ loader: 'empty' }));
build.onLoad({ filter: /./, namespace: LOCALE_DATA_NAMESPACE }, () => ({
contents: '',
loader: 'empty',
}));
},
};
}

0 comments on commit f0c032d

Please sign in to comment.