From 406d79280e5edee5d24aa11aebe64d56894a6a55 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 27 Nov 2019 20:44:27 -0500 Subject: [PATCH] fix(@angular-devkit/build-angular): ensure HTML lang attribute is set when localizing --- .../build_angular/src/browser/index.ts | 21 +++++++++++-------- .../build_angular/src/server/index.ts | 4 ++-- .../build_angular/src/utils/output-paths.ts | 18 +++++++++------- .../e2e/tests/i18n/ivy-localize-dl-xliff2.ts | 3 +++ .../e2e/tests/i18n/ivy-localize-es2015.ts | 3 +++ .../e2e/tests/i18n/ivy-localize-es5.ts | 3 +++ tests/legacy-cli/e2e/tests/i18n/legacy.ts | 5 ++++- .../e2e/tests/i18n/ve-localize-es2015.ts | 3 +++ 8 files changed, 41 insertions(+), 19 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index 5f3efe305c25..1c4bcb80fb84 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -230,7 +230,7 @@ export function buildWebpackBrowser( const host = new NodeJsSyncHost(); const root = normalize(context.workspaceRoot); const baseOutputPath = path.resolve(context.workspaceRoot, options.outputPath); - let outputPaths: undefined | string[]; + let outputPaths: undefined | Map; // Check Angular version. assertCompatibleAngularVersion(context.workspaceRoot, context.logger); @@ -304,7 +304,7 @@ export function buildWebpackBrowser( emittedFiles, i18n, baseOutputPath, - outputPaths, + Array.from(outputPaths.values()), scriptsEntryPointName, // tslint:disable-next-line: no-non-null-assertion webpackStats.outputPath!, @@ -529,7 +529,7 @@ export function buildWebpackBrowser( ), }, ], - outputPaths, + Array.from(outputPaths.values()), '', ); } catch (err) { @@ -561,7 +561,7 @@ export function buildWebpackBrowser( normalize(projectRoot), projectSourceRoot === undefined ? undefined : normalize(projectSourceRoot), ), - outputPaths, + Array.from(outputPaths.values()), context.workspaceRoot, ); } catch (err) { @@ -646,7 +646,7 @@ export function buildWebpackBrowser( emittedFiles, i18n, baseOutputPath, - outputPaths, + Array.from(outputPaths.values()), scriptsEntryPointName, // tslint:disable-next-line: no-non-null-assertion webpackStats.outputPath!, @@ -660,7 +660,7 @@ export function buildWebpackBrowser( } if (options.index) { - for (const outputPath of outputPaths) { + for (const [locale, outputPath] of outputPaths.entries()) { try { await generateIndex( outputPath, @@ -670,6 +670,8 @@ export function buildWebpackBrowser( noModuleFiles, moduleFiles, transforms.indexHtml, + // i18nLocale is used when Ivy is disabled + locale || options.i18nLocale, ); } catch (err) { return { success: false, error: mapErrorToMessage(err) }; @@ -678,7 +680,7 @@ export function buildWebpackBrowser( } if (!options.watch && options.serviceWorker) { - for (const outputPath of outputPaths) { + for (const outputPath of outputPaths.values()) { try { await augmentAppWithServiceWorker( host, @@ -703,7 +705,7 @@ export function buildWebpackBrowser( ...event, baseOutputPath, outputPath: baseOutputPath, - outputPaths: outputPaths || [baseOutputPath], + outputPaths: outputPaths && Array.from(outputPaths.values()) || [baseOutputPath], } as BrowserBuilderOutput), ), ); @@ -719,6 +721,7 @@ function generateIndex( noModuleFiles: EmittedFiles[] | undefined, moduleFiles: EmittedFiles[] | undefined, transformer?: IndexHtmlTransform, + locale?: string, ): Promise { const host = new NodeJsSyncHost(); @@ -736,7 +739,7 @@ function generateIndex( styles: options.styles, postTransform: transformer, crossOrigin: options.crossOrigin, - lang: options.i18nLocale, + lang: locale, }).toPromise(); } diff --git a/packages/angular_devkit/build_angular/src/server/index.ts b/packages/angular_devkit/build_angular/src/server/index.ts index fe25cd7a181c..5cf2fc13b287 100644 --- a/packages/angular_devkit/build_angular/src/server/index.ts +++ b/packages/angular_devkit/build_angular/src/server/index.ts @@ -57,7 +57,7 @@ export function execute( const tsConfig = readTsconfig(options.tsConfig, root); const target = tsConfig.options.target || ScriptTarget.ES5; const baseOutputPath = path.resolve(root, options.outputPath); - let outputPaths: undefined | string[]; + let outputPaths: undefined | Map; return from(initialize(options, context, transforms.webpackConfiguration)).pipe( concatMap(({ config, i18n }) => { @@ -81,7 +81,7 @@ export function execute( emittedFiles, i18n, baseOutputPath, - outputPaths, + Array.from(outputPaths.values()), [], // tslint:disable-next-line: no-non-null-assertion webpackStats.outputPath!, 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 e1778ec82831..45f0729953cc 100644 --- a/packages/angular_devkit/build_angular/src/utils/output-paths.ts +++ b/packages/angular_devkit/build_angular/src/utils/output-paths.ts @@ -9,17 +9,21 @@ 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)) - : [i18n.veCompatLocale ? join(baseOutputPath, i18n.veCompatLocale) : baseOutputPath]; +export function ensureOutputPaths(baseOutputPath: string, i18n: I18nOptions): Map { + const outputPaths: [string, string][] = + i18n.shouldInline + ? [...i18n.inlineLocales].map(l => [l, i18n.flatOutput ? baseOutputPath : join(baseOutputPath, l)]) + : [ + i18n.veCompatLocale + ? [i18n.veCompatLocale, join(baseOutputPath, i18n.veCompatLocale)] + : ['', baseOutputPath], + ]; - for (const outputPath of outputPaths) { + for (const [, outputPath] of outputPaths) { if (!existsSync(outputPath)) { mkdirSync(outputPath, { recursive: true }); } } - return outputPaths; + return new Map(outputPaths); } diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts index 90af8d54efe3..cc45fdc6202b 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts @@ -31,6 +31,9 @@ export async function executeTest() { await expectFileToMatch(`${outputPath}/vendor-es5.js`, lang); await expectFileToMatch(`${outputPath}/vendor-es2015.js`, lang); + // Verify the HTML lang attribute is present + await expectFileToMatch(`${outputPath}/index.html`, `lang="${lang}"`); + // Verify the locale data is registered using the global files await expectFileToMatch(`${outputPath}/vendor-es5.js`, '.ng.common.locales'); await expectFileToMatch(`${outputPath}/vendor-es2015.js`, '.ng.common.locales'); diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts index f5fb2a8cc30a..4e3901f4cd13 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts @@ -22,6 +22,9 @@ export default async function() { await expectFileNotToExist(`${outputPath}/main-es5.js`); await expectFileToMatch(`${outputPath}/main.js`, lang); + // Verify the HTML lang attribute is present + await expectFileToMatch(`${outputPath}/index.html`, `lang="${lang}"`); + // Execute Application E2E tests with dev server await ng('e2e', `--configuration=${lang}`, '--port=0'); diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts index 99c8a408357d..eee1e7251827 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts @@ -22,6 +22,9 @@ export default async function() { await expectFileNotToExist(`${outputPath}/main-es2015.js`); await expectFileToMatch(`${outputPath}/main.js`, lang); + // Verify the HTML lang attribute is present + await expectFileToMatch(`${outputPath}/index.html`, `lang="${lang}"`); + // Execute Application E2E tests with dev server await ng('e2e', `--configuration=${lang}`, '--port=0'); diff --git a/tests/legacy-cli/e2e/tests/i18n/legacy.ts b/tests/legacy-cli/e2e/tests/i18n/legacy.ts index adfb96994eaf..ff02cbba1b0c 100644 --- a/tests/legacy-cli/e2e/tests/i18n/legacy.ts +++ b/tests/legacy-cli/e2e/tests/i18n/legacy.ts @@ -181,7 +181,7 @@ export async function setupI18nConfig(useLocalize = true, format: keyof typeof f for (const { lang, outputPath } of langTranslations) { if (!useLocalize) { if (lang == sourceLocale) { - buildConfigs[lang] = { outputPath }; + buildConfigs[lang] = { outputPath, i18nLocale: lang }; } else { buildConfigs[lang] = { outputPath, @@ -262,6 +262,9 @@ export default async function () { await expectFileToMatch(`${outputPath}/main-es5.js`, translation.helloPartial); await expectFileToMatch(`${outputPath}/main-es2015.js`, translation.helloPartial); + // Verify the HTML lang attribute is present + await expectFileToMatch(`${outputPath}/index.html`, `lang="${lang}"`); + // Execute Application E2E tests with dev server await ng('e2e', `--configuration=${lang}`, '--port=0'); diff --git a/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts b/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts index fa90c46997ba..c02c411454b9 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts @@ -37,6 +37,9 @@ export default async function() { await expectFileToMatch(`${outputPath}/main.js`, translation.helloPartial); await expectToFail(() => expectFileToMatch(`${outputPath}/main.js`, '$localize`')); + // Verify the HTML lang attribute is present + await expectFileToMatch(`${outputPath}/index.html`, `lang="${lang}"`); + // Execute Application E2E tests with dev server await ng('e2e', `--configuration=${lang}`, '--port=0');