"use strict"; /** * @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; }