@angular/build
Version:
Official build system for Angular
208 lines (207 loc) • 9.54 kB
JavaScript
;
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createI18nOptions = createI18nOptions;
exports.loadTranslations = loadTranslations;
const node_path_1 = __importDefault(require("node:path"));
function normalizeTranslationFileOption(option, locale, expectObjectInError) {
if (typeof option === 'string') {
return [option];
}
if (Array.isArray(option) && option.every((element) => typeof element === 'string')) {
return option;
}
let errorMessage = `Project i18n locales translation field value for '${locale}' is malformed. `;
if (expectObjectInError) {
errorMessage += 'Expected a string, array of strings, or object.';
}
else {
errorMessage += 'Expected a string or array of strings.';
}
throw new Error(errorMessage);
}
function ensureObject(value, name) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`Project field '${name}' is malformed. Expected an object.`);
}
}
function ensureString(value, name) {
if (typeof value !== 'string') {
throw new Error(`Project field '${name}' is malformed. Expected a string.`);
}
}
function ensureValidSubPath(value, name) {
ensureString(value, name);
if (!/^[\w-]*$/.test(value)) {
throw new Error(`Project field '${name}' is invalid. It can only contain letters, numbers, hyphens, and underscores.`);
}
}
function createI18nOptions(projectMetadata, inline, logger, ssrEnabled) {
const { i18n: metadata = {} } = projectMetadata;
ensureObject(metadata, 'i18n');
const i18n = {
inlineLocales: new Set(),
// en-US is the default locale added to Angular applications (https://angular.dev/guide/i18n/format-data-locale)
sourceLocale: 'en-US',
locales: {},
get shouldInline() {
return this.inlineLocales.size > 0;
},
};
let rawSourceLocale;
let rawSourceLocaleBaseHref;
let rawsubPath;
if (typeof metadata.sourceLocale === 'string') {
rawSourceLocale = metadata.sourceLocale;
}
else if (metadata.sourceLocale !== undefined) {
ensureObject(metadata.sourceLocale, 'i18n.sourceLocale');
if (metadata.sourceLocale.code !== undefined) {
ensureString(metadata.sourceLocale.code, 'i18n.sourceLocale.code');
rawSourceLocale = metadata.sourceLocale.code;
}
if (metadata.sourceLocale.baseHref !== undefined) {
ensureString(metadata.sourceLocale.baseHref, 'i18n.sourceLocale.baseHref');
if (ssrEnabled) {
logger?.warn(`'baseHref' in 'i18n.sourceLocale' may lead to undefined behavior when used with SSR. ` +
`Consider using 'subPath' instead.\n\n` +
`Note: 'subPath' specifies the URL segment for the locale, serving as both the HTML base HREF ` +
`and the output directory name.\nBy default, if not explicitly set, 'subPath' defaults to the locale code.`);
}
rawSourceLocaleBaseHref = metadata.sourceLocale.baseHref;
}
if (metadata.sourceLocale.subPath !== undefined) {
ensureValidSubPath(metadata.sourceLocale.subPath, 'i18n.sourceLocale.subPath');
rawsubPath = metadata.sourceLocale.subPath;
}
if (rawsubPath !== undefined && rawSourceLocaleBaseHref !== undefined) {
throw new Error(`'i18n.sourceLocale.subPath' and 'i18n.sourceLocale.baseHref' cannot be used together.`);
}
}
if (rawSourceLocale !== undefined) {
i18n.sourceLocale = rawSourceLocale;
i18n.hasDefinedSourceLocale = true;
}
i18n.locales[i18n.sourceLocale] = {
files: [],
baseHref: rawSourceLocaleBaseHref,
subPath: rawsubPath ?? i18n.sourceLocale,
};
if (metadata.locales !== undefined) {
ensureObject(metadata.locales, 'i18n locales');
for (const [locale, options] of Object.entries(metadata.locales)) {
let translationFiles;
let baseHref;
let subPath;
if (options && typeof options === 'object' && 'translation' in options) {
translationFiles = normalizeTranslationFileOption(options.translation, locale, false);
if ('baseHref' in options) {
ensureString(options.baseHref, `i18n.locales.${locale}.baseHref`);
if (ssrEnabled) {
logger?.warn(`'baseHref' in 'i18n.locales.${locale}' may lead to undefined behavior when used with SSR. ` +
`Consider using 'subPath' instead.\n\n` +
`Note: 'subPath' specifies the URL segment for the locale, serving as both the HTML base HREF ` +
`and the output directory name.\nBy default, if not explicitly set, 'subPath' defaults to the locale code.`);
}
baseHref = options.baseHref;
}
if ('subPath' in options) {
ensureValidSubPath(options.subPath, `i18n.locales.${locale}.subPath`);
subPath = options.subPath;
}
if (subPath !== undefined && baseHref !== undefined) {
throw new Error(`'i18n.locales.${locale}.subPath' and 'i18n.locales.${locale}.baseHref' cannot be used together.`);
}
}
else {
translationFiles = normalizeTranslationFileOption(options, locale, true);
}
if (locale === i18n.sourceLocale) {
throw new Error(`An i18n locale ('${locale}') cannot both be a source locale and provide a translation.`);
}
i18n.locales[locale] = {
files: translationFiles.map((file) => ({ path: file })),
baseHref,
subPath: subPath ?? locale,
};
}
}
if (inline === true) {
i18n.inlineLocales.add(i18n.sourceLocale);
Object.keys(i18n.locales).forEach((locale) => i18n.inlineLocales.add(locale));
}
else if (inline) {
for (const locale of inline) {
if (!i18n.locales[locale] && i18n.sourceLocale !== locale) {
throw new Error(`Requested locale '${locale}' is not defined for the project.`);
}
i18n.inlineLocales.add(locale);
}
}
// Check that subPaths are unique only the locales that we are inlining.
const localesData = Object.entries(i18n.locales).filter(([locale]) => i18n.inlineLocales.has(locale));
for (let i = 0; i < localesData.length; i++) {
const [localeA, { subPath: subPathA }] = localesData[i];
for (let j = i + 1; j < localesData.length; j++) {
const [localeB, { subPath: subPathB }] = localesData[j];
if (subPathA === subPathB) {
throw new Error(`Invalid i18n configuration: Locales '${localeA}' and '${localeB}' cannot have the same subPath: '${subPathB}'.`);
}
}
}
return i18n;
}
function loadTranslations(locale, desc, workspaceRoot, loader, logger, usedFormats, duplicateTranslation) {
let translations = undefined;
for (const file of desc.files) {
const loadResult = loader(node_path_1.default.join(workspaceRoot, file.path));
for (const diagnostics of loadResult.diagnostics.messages) {
if (diagnostics.type === 'error') {
logger.error(`Error parsing translation file '${file.path}': ${diagnostics.message}`);
}
else {
logger.warn(`WARNING [${file.path}]: ${diagnostics.message}`);
}
}
if (loadResult.locale !== undefined && loadResult.locale !== locale) {
logger.warn(`WARNING [${file.path}]: File target locale ('${loadResult.locale}') does not match configured locale ('${locale}')`);
}
usedFormats?.add(loadResult.format);
file.format = loadResult.format;
file.integrity = loadResult.integrity;
if (translations) {
// Merge translations
for (const [id, message] of Object.entries(loadResult.translations)) {
if (translations[id] !== undefined) {
const duplicateTranslationMessage = `[${file.path}]: Duplicate translations for message '${id}' when merging.`;
switch (duplicateTranslation) {
case 'ignore':
break;
case 'error':
logger.error(`ERROR ${duplicateTranslationMessage}`);
break;
case 'warning':
default:
logger.warn(`WARNING ${duplicateTranslationMessage}`);
break;
}
}
translations[id] = message;
}
}
else {
// First or only translation file
translations = loadResult.translations;
}
}
desc.translation = translations;
}