diff --git a/packages/angular_devkit/build_angular/src/utils/i18n-options.ts b/packages/angular_devkit/build_angular/src/utils/i18n-options.ts index fa74888ea387..06d5d551a536 100644 --- a/packages/angular_devkit/build_angular/src/utils/i18n-options.ts +++ b/packages/angular_devkit/build_angular/src/utils/i18n-options.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import { BuilderContext } from '@angular-devkit/architect'; -import { json, virtualFs } from '@angular-devkit/core'; +import { json } from '@angular-devkit/core'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; @@ -25,6 +25,7 @@ export interface I18nOptions { >; flatOutput?: boolean; readonly shouldInline: boolean; + veCompatLocale?: string; } export function createI18nOptions( @@ -116,9 +117,32 @@ export async function configureI18nBuild 1) + ) { + throw new Error( + `Localization with multiple locales in one build is not supported with View Engine.`, + ); + } + + for (const deprecatedOption of ['i18nLocale', 'i18nFormat', 'i18nFile']) { + // tslint:disable-next-line: no-any + if (typeof (buildOptions as any)[deprecatedOption] !== 'undefined') { + context.logger.warn( + `Option 'localize' and deprecated '${deprecatedOption}' found. Using 'localize'.`, + ); + } + } - context.logger.warn(`Option 'localize' is not supported with View Engine.`); + if ( + buildOptions.localize === false || + (Array.isArray(buildOptions.localize) && buildOptions.localize.length === 0) + ) { + buildOptions.i18nFile = undefined; + buildOptions.i18nLocale = undefined; + buildOptions.i18nFormat = undefined; + } } // Clear deprecated options when using Ivy to prevent unintended behavior @@ -180,6 +204,21 @@ export async function configureI18nBuild 0) { buildOptions.i18nFormat = [...usedFormats][0]; } + + // Provide support for using the Ivy i18n options with VE + if (!usingIvy) { + i18n.veCompatLocale = buildOptions.i18nLocale = [...i18n.inlineLocales][0]; + + if (buildOptions.i18nLocale !== i18n.sourceLocale) { + buildOptions.i18nFile = i18n.locales[buildOptions.i18nLocale].file; + } + + // Clear inline locales to prevent any new i18n related processing + i18n.inlineLocales.clear(); + + // Update the output path to include the locale to mimic Ivy localize behavior + buildOptions.outputPath = path.join(buildOptions.outputPath, buildOptions.i18nLocale); + } } // If inlining store the output in a temporary location to facilitate post-processing diff --git a/packages/angular_devkit/build_angular/src/utils/output-paths.ts b/packages/angular_devkit/build_angular/src/utils/output-paths.ts index 24f8563e664e..e1778ec82831 100644 --- a/packages/angular_devkit/build_angular/src/utils/output-paths.ts +++ b/packages/angular_devkit/build_angular/src/utils/output-paths.ts @@ -5,21 +5,21 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - import { existsSync, mkdirSync } from 'fs'; import { join } from 'path'; import { I18nOptions } from './i18n-options'; export function ensureOutputPaths(baseOutputPath: string, i18n: I18nOptions): string[] { - const outputPaths = i18n.shouldInline && !i18n.flatOutput - ? [...i18n.inlineLocales].map(l => join(baseOutputPath, l)) - : [baseOutputPath]; + const outputPaths = + i18n.shouldInline && !i18n.flatOutput + ? [...i18n.inlineLocales].map(l => join(baseOutputPath, l)) + : [i18n.veCompatLocale ? join(baseOutputPath, i18n.veCompatLocale) : baseOutputPath]; - for (const outputPath of outputPaths) { - if (!existsSync(outputPath)) { - mkdirSync(outputPath, { recursive: true }); - } + for (const outputPath of outputPaths) { + if (!existsSync(outputPath)) { + mkdirSync(outputPath, { recursive: true }); } + } - return outputPaths; + return outputPaths; } diff --git a/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts b/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts new file mode 100644 index 000000000000..fa90c46997ba --- /dev/null +++ b/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts @@ -0,0 +1,67 @@ +import { getGlobalVariable } from '../../utils/env'; +import { expectFileToMatch, writeFile } from '../../utils/fs'; +import { execAndWaitForOutputToMatch, ng, npm } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; +import { expectToFail } from '../../utils/utils'; +import { readNgVersion } from '../../utils/version'; +import { baseDir, externalServer, langTranslations, setupI18nConfig } from './legacy'; + +export default async function() { + if (!getGlobalVariable('argv')['ve']) { + return; + } + + // Setup i18n tests and config. + await setupI18nConfig(); + + // Using localize-based options requires the @angular/locale package + let localizeVersion = '@angular/localize@' + readNgVersion(); + if (getGlobalVariable('argv')['ng-snapshots']) { + localizeVersion = require('../../ng-snapshot/package.json').dependencies['@angular/localize']; + } + await npm('install', `${localizeVersion}`); + + // Ensure a ES2015 build is used. + await writeFile('browserslist', 'Chrome 65'); + await updateJsonFile('tsconfig.json', config => { + config.compilerOptions.target = 'es2015'; + config.angularCompilerOptions.disableTypeScriptVersionCheck = true; + }); + + // Attempts to build multiple locales with VE should fail + await expectToFail(() => ng('build')); + + for (const { lang, outputPath, translation } of langTranslations) { + await ng('build', `--configuration=${lang}`); + + await expectFileToMatch(`${outputPath}/main.js`, translation.helloPartial); + await expectToFail(() => expectFileToMatch(`${outputPath}/main.js`, '$localize`')); + + // Execute Application E2E tests with dev server + await ng('e2e', `--configuration=${lang}`, '--port=0'); + + // Execute Application E2E tests for a production build without dev server + const server = externalServer(outputPath); + try { + await ng('e2e', `--configuration=${lang}`, '--devServerTarget='); + } finally { + server.close(); + } + } + + await execAndWaitForOutputToMatch( + 'ng', + ['build', '--configuration=fr', '--i18n-locale=en-US'], + /Option 'localize' and deprecated 'i18nLocale' found. Using 'localize'./, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['build', '--configuration=fr', '--i18n-format=xmb'], + /Option 'localize' and deprecated 'i18nFormat' found. Using 'localize'./, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['build', '--configuration=fr', '--i18n-file=error.json'], + /Option 'localize' and deprecated 'i18nFile' found. Using 'localize'./, + ); +}