[6a3a178] | 1 | "use strict";
|
---|
| 2 | /**
|
---|
| 3 | * @license
|
---|
| 4 | * Copyright Google LLC All Rights Reserved.
|
---|
| 5 | *
|
---|
| 6 | * Use of this source code is governed by an MIT-style license that can be
|
---|
| 7 | * found in the LICENSE file at https://angular.io/license
|
---|
| 8 | */
|
---|
| 9 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
---|
| 10 | if (k2 === undefined) k2 = k;
|
---|
| 11 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
---|
| 12 | }) : (function(o, m, k, k2) {
|
---|
| 13 | if (k2 === undefined) k2 = k;
|
---|
| 14 | o[k2] = m[k];
|
---|
| 15 | }));
|
---|
| 16 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
---|
| 17 | Object.defineProperty(o, "default", { enumerable: true, value: v });
|
---|
| 18 | }) : function(o, v) {
|
---|
| 19 | o["default"] = v;
|
---|
| 20 | });
|
---|
| 21 | var __importStar = (this && this.__importStar) || function (mod) {
|
---|
| 22 | if (mod && mod.__esModule) return mod;
|
---|
| 23 | var result = {};
|
---|
| 24 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
---|
| 25 | __setModuleDefault(result, mod);
|
---|
| 26 | return result;
|
---|
| 27 | };
|
---|
| 28 | Object.defineProperty(exports, "__esModule", { value: true });
|
---|
| 29 | exports.configureI18nBuild = exports.createI18nOptions = void 0;
|
---|
| 30 | const core_1 = require("@angular-devkit/core");
|
---|
| 31 | const fs = __importStar(require("fs"));
|
---|
| 32 | const os = __importStar(require("os"));
|
---|
| 33 | const path = __importStar(require("path"));
|
---|
| 34 | const read_tsconfig_1 = require("../utils/read-tsconfig");
|
---|
| 35 | const load_translations_1 = require("./load-translations");
|
---|
| 36 | function normalizeTranslationFileOption(option, locale, expectObjectInError) {
|
---|
| 37 | if (typeof option === 'string') {
|
---|
| 38 | return [option];
|
---|
| 39 | }
|
---|
| 40 | if (Array.isArray(option) && option.every((element) => typeof element === 'string')) {
|
---|
| 41 | return option;
|
---|
| 42 | }
|
---|
| 43 | let errorMessage = `Project i18n locales translation field value for '${locale}' is malformed. `;
|
---|
| 44 | if (expectObjectInError) {
|
---|
| 45 | errorMessage += 'Expected a string, array of strings, or object.';
|
---|
| 46 | }
|
---|
| 47 | else {
|
---|
| 48 | errorMessage += 'Expected a string or array of strings.';
|
---|
| 49 | }
|
---|
| 50 | throw new Error(errorMessage);
|
---|
| 51 | }
|
---|
| 52 | function createI18nOptions(metadata, inline) {
|
---|
| 53 | if (metadata.i18n !== undefined && !core_1.json.isJsonObject(metadata.i18n)) {
|
---|
| 54 | throw new Error('Project i18n field is malformed. Expected an object.');
|
---|
| 55 | }
|
---|
| 56 | metadata = metadata.i18n || {};
|
---|
| 57 | const i18n = {
|
---|
| 58 | inlineLocales: new Set(),
|
---|
| 59 | // en-US is the default locale added to Angular applications (https://angular.io/guide/i18n#i18n-pipes)
|
---|
| 60 | sourceLocale: 'en-US',
|
---|
| 61 | locales: {},
|
---|
| 62 | get shouldInline() {
|
---|
| 63 | return this.inlineLocales.size > 0;
|
---|
| 64 | },
|
---|
| 65 | };
|
---|
| 66 | let rawSourceLocale;
|
---|
| 67 | let rawSourceLocaleBaseHref;
|
---|
| 68 | if (core_1.json.isJsonObject(metadata.sourceLocale)) {
|
---|
| 69 | rawSourceLocale = metadata.sourceLocale.code;
|
---|
| 70 | if (metadata.sourceLocale.baseHref !== undefined &&
|
---|
| 71 | typeof metadata.sourceLocale.baseHref !== 'string') {
|
---|
| 72 | throw new Error('Project i18n sourceLocale baseHref field is malformed. Expected a string.');
|
---|
| 73 | }
|
---|
| 74 | rawSourceLocaleBaseHref = metadata.sourceLocale.baseHref;
|
---|
| 75 | }
|
---|
| 76 | else {
|
---|
| 77 | rawSourceLocale = metadata.sourceLocale;
|
---|
| 78 | }
|
---|
| 79 | if (rawSourceLocale !== undefined) {
|
---|
| 80 | if (typeof rawSourceLocale !== 'string') {
|
---|
| 81 | throw new Error('Project i18n sourceLocale field is malformed. Expected a string.');
|
---|
| 82 | }
|
---|
| 83 | i18n.sourceLocale = rawSourceLocale;
|
---|
| 84 | i18n.hasDefinedSourceLocale = true;
|
---|
| 85 | }
|
---|
| 86 | i18n.locales[i18n.sourceLocale] = {
|
---|
| 87 | files: [],
|
---|
| 88 | baseHref: rawSourceLocaleBaseHref,
|
---|
| 89 | };
|
---|
| 90 | if (metadata.locales !== undefined && !core_1.json.isJsonObject(metadata.locales)) {
|
---|
| 91 | throw new Error('Project i18n locales field is malformed. Expected an object.');
|
---|
| 92 | }
|
---|
| 93 | else if (metadata.locales) {
|
---|
| 94 | for (const [locale, options] of Object.entries(metadata.locales)) {
|
---|
| 95 | let translationFiles;
|
---|
| 96 | let baseHref;
|
---|
| 97 | if (core_1.json.isJsonObject(options)) {
|
---|
| 98 | translationFiles = normalizeTranslationFileOption(options.translation, locale, false);
|
---|
| 99 | if (typeof options.baseHref === 'string') {
|
---|
| 100 | baseHref = options.baseHref;
|
---|
| 101 | }
|
---|
| 102 | }
|
---|
| 103 | else {
|
---|
| 104 | translationFiles = normalizeTranslationFileOption(options, locale, true);
|
---|
| 105 | }
|
---|
| 106 | if (locale === i18n.sourceLocale) {
|
---|
| 107 | throw new Error(`An i18n locale ('${locale}') cannot both be a source locale and provide a translation.`);
|
---|
| 108 | }
|
---|
| 109 | i18n.locales[locale] = {
|
---|
| 110 | files: translationFiles.map((file) => ({ path: file })),
|
---|
| 111 | baseHref,
|
---|
| 112 | };
|
---|
| 113 | }
|
---|
| 114 | }
|
---|
| 115 | if (inline === true) {
|
---|
| 116 | i18n.inlineLocales.add(i18n.sourceLocale);
|
---|
| 117 | Object.keys(i18n.locales).forEach((locale) => i18n.inlineLocales.add(locale));
|
---|
| 118 | }
|
---|
| 119 | else if (inline) {
|
---|
| 120 | for (const locale of inline) {
|
---|
| 121 | if (!i18n.locales[locale] && i18n.sourceLocale !== locale) {
|
---|
| 122 | throw new Error(`Requested locale '${locale}' is not defined for the project.`);
|
---|
| 123 | }
|
---|
| 124 | i18n.inlineLocales.add(locale);
|
---|
| 125 | }
|
---|
| 126 | }
|
---|
| 127 | return i18n;
|
---|
| 128 | }
|
---|
| 129 | exports.createI18nOptions = createI18nOptions;
|
---|
| 130 | async function configureI18nBuild(context, options) {
|
---|
| 131 | if (!context.target) {
|
---|
| 132 | throw new Error('The builder requires a target.');
|
---|
| 133 | }
|
---|
| 134 | const buildOptions = { ...options };
|
---|
| 135 | const tsConfig = read_tsconfig_1.readTsconfig(buildOptions.tsConfig, context.workspaceRoot);
|
---|
| 136 | const metadata = await context.getProjectMetadata(context.target);
|
---|
| 137 | const i18n = createI18nOptions(metadata, buildOptions.localize);
|
---|
| 138 | // No additional processing needed if no inlining requested and no source locale defined.
|
---|
| 139 | if (!i18n.shouldInline && !i18n.hasDefinedSourceLocale) {
|
---|
| 140 | return { buildOptions, i18n };
|
---|
| 141 | }
|
---|
| 142 | const projectRoot = path.join(context.workspaceRoot, metadata.root || '');
|
---|
| 143 | const localeDataBasePath = findLocaleDataBasePath(projectRoot);
|
---|
| 144 | if (!localeDataBasePath) {
|
---|
| 145 | throw new Error(`Unable to find locale data within '@angular/common'. Please ensure '@angular/common' is installed.`);
|
---|
| 146 | }
|
---|
| 147 | // Load locale data and translations (if present)
|
---|
| 148 | let loader;
|
---|
| 149 | const usedFormats = new Set();
|
---|
| 150 | for (const [locale, desc] of Object.entries(i18n.locales)) {
|
---|
| 151 | if (!i18n.inlineLocales.has(locale) && locale !== i18n.sourceLocale) {
|
---|
| 152 | continue;
|
---|
| 153 | }
|
---|
| 154 | let localeDataPath = findLocaleDataPath(locale, localeDataBasePath);
|
---|
| 155 | if (!localeDataPath) {
|
---|
| 156 | const [first] = locale.split('-');
|
---|
| 157 | if (first) {
|
---|
| 158 | localeDataPath = findLocaleDataPath(first.toLowerCase(), localeDataBasePath);
|
---|
| 159 | if (localeDataPath) {
|
---|
| 160 | context.logger.warn(`Locale data for '${locale}' cannot be found. Using locale data for '${first}'.`);
|
---|
| 161 | }
|
---|
| 162 | }
|
---|
| 163 | }
|
---|
| 164 | if (!localeDataPath) {
|
---|
| 165 | context.logger.warn(`Locale data for '${locale}' cannot be found. No locale data will be included for this locale.`);
|
---|
| 166 | }
|
---|
| 167 | else {
|
---|
| 168 | desc.dataPath = localeDataPath;
|
---|
| 169 | }
|
---|
| 170 | if (!desc.files.length) {
|
---|
| 171 | continue;
|
---|
| 172 | }
|
---|
| 173 | if (!loader) {
|
---|
| 174 | loader = await load_translations_1.createTranslationLoader();
|
---|
| 175 | }
|
---|
| 176 | for (const file of desc.files) {
|
---|
| 177 | const loadResult = loader(path.join(context.workspaceRoot, file.path));
|
---|
| 178 | for (const diagnostics of loadResult.diagnostics.messages) {
|
---|
| 179 | if (diagnostics.type === 'error') {
|
---|
| 180 | throw new Error(`Error parsing translation file '${file.path}': ${diagnostics.message}`);
|
---|
| 181 | }
|
---|
| 182 | else {
|
---|
| 183 | context.logger.warn(`WARNING [${file.path}]: ${diagnostics.message}`);
|
---|
| 184 | }
|
---|
| 185 | }
|
---|
| 186 | if (loadResult.locale !== undefined && loadResult.locale !== locale) {
|
---|
| 187 | context.logger.warn(`WARNING [${file.path}]: File target locale ('${loadResult.locale}') does not match configured locale ('${locale}')`);
|
---|
| 188 | }
|
---|
| 189 | usedFormats.add(loadResult.format);
|
---|
| 190 | if (usedFormats.size > 1 && tsConfig.options.enableI18nLegacyMessageIdFormat !== false) {
|
---|
| 191 | // This limitation is only for legacy message id support (defaults to true as of 9.0)
|
---|
| 192 | throw new Error('Localization currently only supports using one type of translation file format for the entire application.');
|
---|
| 193 | }
|
---|
| 194 | file.format = loadResult.format;
|
---|
| 195 | file.integrity = loadResult.integrity;
|
---|
| 196 | if (desc.translation) {
|
---|
| 197 | // Merge translations
|
---|
| 198 | for (const [id, message] of Object.entries(loadResult.translations)) {
|
---|
| 199 | if (desc.translation[id] !== undefined) {
|
---|
| 200 | context.logger.warn(`WARNING [${file.path}]: Duplicate translations for message '${id}' when merging`);
|
---|
| 201 | }
|
---|
| 202 | desc.translation[id] = message;
|
---|
| 203 | }
|
---|
| 204 | }
|
---|
| 205 | else {
|
---|
| 206 | // First or only translation file
|
---|
| 207 | desc.translation = loadResult.translations;
|
---|
| 208 | }
|
---|
| 209 | }
|
---|
| 210 | }
|
---|
| 211 | // If inlining store the output in a temporary location to facilitate post-processing
|
---|
| 212 | if (i18n.shouldInline) {
|
---|
| 213 | const tempPath = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'angular-cli-i18n-'));
|
---|
| 214 | buildOptions.outputPath = tempPath;
|
---|
| 215 | // Remove temporary directory used for i18n processing
|
---|
| 216 | process.on('exit', () => {
|
---|
| 217 | try {
|
---|
| 218 | fs.rmdirSync(tempPath, { recursive: true, maxRetries: 3 });
|
---|
| 219 | }
|
---|
| 220 | catch { }
|
---|
| 221 | });
|
---|
| 222 | }
|
---|
| 223 | return { buildOptions, i18n };
|
---|
| 224 | }
|
---|
| 225 | exports.configureI18nBuild = configureI18nBuild;
|
---|
| 226 | function findLocaleDataBasePath(projectRoot) {
|
---|
| 227 | try {
|
---|
| 228 | const commonPath = path.dirname(require.resolve('@angular/common/package.json', { paths: [projectRoot] }));
|
---|
| 229 | const localesPath = path.join(commonPath, 'locales/global');
|
---|
| 230 | if (!fs.existsSync(localesPath)) {
|
---|
| 231 | return null;
|
---|
| 232 | }
|
---|
| 233 | return localesPath;
|
---|
| 234 | }
|
---|
| 235 | catch {
|
---|
| 236 | return null;
|
---|
| 237 | }
|
---|
| 238 | }
|
---|
| 239 | function findLocaleDataPath(locale, basePath) {
|
---|
| 240 | // Remove private use subtags
|
---|
| 241 | const scrubbedLocale = locale.replace(/-x(-[a-zA-Z0-9]{1,8})+$/, '');
|
---|
| 242 | const localeDataPath = path.join(basePath, scrubbedLocale + '.js');
|
---|
| 243 | if (!fs.existsSync(localeDataPath)) {
|
---|
| 244 | if (scrubbedLocale === 'en-US') {
|
---|
| 245 | // fallback to known existing en-US locale data as of 9.0
|
---|
| 246 | return findLocaleDataPath('en-US-POSIX', basePath);
|
---|
| 247 | }
|
---|
| 248 | return null;
|
---|
| 249 | }
|
---|
| 250 | return localeDataPath;
|
---|
| 251 | }
|
---|