From b8b3351dc9eabf90b8595376fc78174e8062dc84 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Mon, 18 Nov 2019 20:45:43 +0100 Subject: [PATCH] fix(@schematics/angular): migrate project to use new i18n options (cherry picked from commit 8b23e649723be219e969f84c80b92b20343684d9) --- .../update-9/update-workspace-config.ts | 109 +++++++++++++++--- .../update-9/update-workspace-config_spec.ts | 58 +++++++++- 2 files changed, 152 insertions(+), 15 deletions(-) diff --git a/packages/schematics/angular/migrations/update-9/update-workspace-config.ts b/packages/schematics/angular/migrations/update-9/update-workspace-config.ts index d81aaa316030..a2986c90535e 100644 --- a/packages/schematics/angular/migrations/update-9/update-workspace-config.ts +++ b/packages/schematics/angular/migrations/update-9/update-workspace-config.ts @@ -8,12 +8,14 @@ import { JsonAstObject } from '@angular-devkit/core'; import { Rule, Tree, UpdateRecorder } from '@angular-devkit/schematics'; import { getWorkspacePath } from '../../utility/config'; +import { NodeDependencyType, addPackageJsonDependency, getPackageJsonDependency } from '../../utility/dependencies'; import { appendValueInAstArray, findPropertyInAstObject, insertPropertyInAstObjectInOrder, removePropertyInAstObject, } from '../../utility/json-utils'; +import { latestVersions } from '../../utility/latest-versions'; import { Builders } from '../../utility/workspace-models'; import { getAllOptions, getProjectTarget, getTargets, getWorkspace, isIvyEnabled } from './utils'; @@ -28,29 +30,41 @@ export function updateWorkspaceConfig(): Rule { const workspace = getWorkspace(tree); const recorder = tree.beginUpdate(workspacePath); - for (const { target } of getTargets(workspace, 'build', Builders.Browser)) { + for (const { target, project } of getTargets(workspace, 'build', Builders.Browser)) { updateStyleOrScriptOption('styles', recorder, target); updateStyleOrScriptOption('scripts', recorder, target); addAnyComponentStyleBudget(recorder, target); updateAotOption(tree, recorder, target); + addBuilderI18NOptions(recorder, target, project); } - for (const { target } of getTargets(workspace, 'test', Builders.Karma)) { + for (const { target, project } of getTargets(workspace, 'test', Builders.Karma)) { updateStyleOrScriptOption('styles', recorder, target); updateStyleOrScriptOption('scripts', recorder, target); + addBuilderI18NOptions(recorder, target, project); } for (const { target } of getTargets(workspace, 'server', Builders.Server)) { updateOptimizationOption(recorder, target); } + for (const { target, project } of getTargets(workspace, 'extract-i18n', Builders.ExtractI18n)) { + addProjectI18NOptions(recorder, tree, target, project); + removeExtracti18nDeprecatedOptions(recorder, target); + } + tree.commitUpdate(recorder); return tree; }; } -function addProjectI18NOptions(recorder: UpdateRecorder, builderConfig: JsonAstObject, projectConfig: JsonAstObject) { +function addProjectI18NOptions( + recorder: UpdateRecorder, + tree: Tree, + builderConfig: JsonAstObject, + projectConfig: JsonAstObject, +) { const browserConfig = getProjectTarget(projectConfig, 'build', Builders.Browser); if (!browserConfig || browserConfig.kind !== 'object') { return; @@ -86,12 +100,12 @@ function addProjectI18NOptions(recorder: UpdateRecorder, builderConfig: JsonAstO // Get sourceLocale from extract-i18n builder const i18nOptions = getAllOptions(builderConfig); const sourceLocale = i18nOptions - .map(o => { - const sourceLocale = findPropertyInAstObject(o, 'i18nLocale'); + .map(o => { + const sourceLocale = findPropertyInAstObject(o, 'i18nLocale'); - return sourceLocale && sourceLocale.value; - }) - .find(x => !!x); + return sourceLocale && sourceLocale.value; + }) + .find(x => !!x); // Add i18n project configuration insertPropertyInAstObjectInOrder(recorder, projectConfig, 'i18n', { @@ -99,20 +113,88 @@ function addProjectI18NOptions(recorder: UpdateRecorder, builderConfig: JsonAstO // tslint:disable-next-line: no-any sourceLocale: sourceLocale as any, }, 6); + + // Add @angular/localize if not already a dependency + if (!getPackageJsonDependency(tree, '@angular/localize')) { + addPackageJsonDependency(tree, { + name: '@angular/localize', + version: latestVersions.Angular, + type: NodeDependencyType.Default, + }); + } } } -function addBuilderI18NOptions(recorder: UpdateRecorder, builderConfig: JsonAstObject) { +function addBuilderI18NOptions(recorder: UpdateRecorder, builderConfig: JsonAstObject, projectConfig: JsonAstObject) { const options = getAllOptions(builderConfig); + let hasi18n = false; for (const option of options) { const localeId = findPropertyInAstObject(option, 'i18nLocale'); - if (!localeId || localeId.kind !== 'string') { - continue; + if (localeId && localeId.kind === 'string') { + // add new localize option + insertPropertyInAstObjectInOrder(recorder, option, 'localize', [localeId.value], 12); + removePropertyInAstObject(recorder, option, 'i18nLocale'); + } + + const i18nFile = findPropertyInAstObject(option, 'i18nFile'); + if (i18nFile) { + removePropertyInAstObject(recorder, option, 'i18nFile'); + } + + const i18nFormat = findPropertyInAstObject(option, 'i18nFormat'); + if (i18nFormat) { + removePropertyInAstObject(recorder, option, 'i18nFormat'); } - // add new localize option - insertPropertyInAstObjectInOrder(recorder, option, 'localize', [localeId.value], 12); + hasi18n = !!(hasi18n || i18nFormat || i18nFile || localeId); + } + + if (hasi18n) { + const options = findPropertyInAstObject(builderConfig, 'options'); + if (!options || options.kind !== 'object') { + return; + } + + // Don't add localize option of it's already present in the main options + if (findPropertyInAstObject(options, 'i18nLocale') || findPropertyInAstObject(options, 'localize')) { + return; + } + + // Get sourceLocale from extract-i18n builder + const extractI18nConfig = getProjectTarget(projectConfig, 'extract-i18n', Builders.ExtractI18n); + let sourceLocale: string | undefined; + + if (extractI18nConfig && extractI18nConfig.kind === 'object') { + const i18nOptions = getAllOptions(extractI18nConfig); + sourceLocale = i18nOptions + .map(o => { + const sourceLocale = findPropertyInAstObject(o, 'i18nLocale'); + + return sourceLocale && sourceLocale.value; + }) + .find(x => !!x) as string; + } + + insertPropertyInAstObjectInOrder(recorder, options, 'localize', [sourceLocale || 'en-US'], 12); + } +} + +function removeExtracti18nDeprecatedOptions(recorder: UpdateRecorder, builderConfig: JsonAstObject) { + const options = getAllOptions(builderConfig); + + for (const option of options) { + // deprecated options + removePropertyInAstObject(recorder, option, 'i18nLocale'); + const i18nFormat = option.properties.find(({ key }) => key.value === 'i18nFormat'); + + if (i18nFormat) { + // i18nFormat has been changed to format + const key = i18nFormat.key; + const offset = key.start.offset + 1; + recorder.remove(offset, key.value.length); + recorder.insertLeft(offset, 'format'); + } } } @@ -122,7 +204,6 @@ function updateAotOption(tree: Tree, recorder: UpdateRecorder, builderConfig: Js return; } - const tsConfig = findPropertyInAstObject(options, 'tsConfig'); // Do not add aot option if the users already opted out from Ivy. if (tsConfig && tsConfig.kind === 'string' && !isIvyEnabled(tree, tsConfig.value)) { diff --git a/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts b/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts index 9a3b656e9bbb..c96a2b800fd3 100644 --- a/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts +++ b/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts @@ -8,6 +8,7 @@ import { EmptyTree } from '@angular-devkit/schematics'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; +import { latestVersions } from '../../utility/latest-versions'; import { WorkspaceTargets } from '../../utility/workspace-models'; import { ANY_COMPONENT_STYLE_BUDGET } from './update-workspace-config'; @@ -297,7 +298,7 @@ describe('Migration to version 9', () => { }); }); - xdescribe('i18n configuration', () => { + describe('i18n configuration', () => { function getI18NConfig(localId: string): object { return { outputPath: `dist/my-project-${localId}/`, @@ -308,6 +309,17 @@ describe('Migration to version 9', () => { } describe('when i18n builder options are set', () => { + it(`should add '@angular/localize' as a dependency`, async () => { + const config = getWorkspaceTargets(tree); + config.build.options = getI18NConfig('fr'); + config.build.configurations.de = getI18NConfig('de'); + updateWorkspaceTargets(tree, config); + + const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise(); + const { dependencies } = JSON.parse(tree2.readContent('/package.json')); + expect(dependencies['@angular/localize']).toBe(latestVersions.Angular); + }); + it(`should add 'localize' option in configuration`, async () => { let config = getWorkspaceTargets(tree); config.build.options.aot = false; @@ -321,6 +333,50 @@ describe('Migration to version 9', () => { expect(config.configurations.de.localize).toEqual(['de']); }); + it(`should add 'localize' option in main options`, async () => { + let config = getWorkspaceTargets(tree); + config.build.options.aot = false; + config.build.configurations.de = getI18NConfig('de'); + config['extract-i18n'].options.i18nFormat = 'xmb'; + config['extract-i18n'].options.i18nLocale = 'en-GB'; + updateWorkspaceTargets(tree, config); + + const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise(); + config = getWorkspaceTargets(tree2).build; + expect(config.options.localize).toEqual(['en-GB']); + expect(config.configurations.de.localize).toEqual(['de']); + }); + + it('should remove deprecated i18n options', async () => { + let config = getWorkspaceTargets(tree); + config.build.options.aot = false; + config.build.options = getI18NConfig('fr'); + config.build.configurations.de = getI18NConfig('de'); + updateWorkspaceTargets(tree, config); + + const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise(); + config = getWorkspaceTargets(tree2).build; + expect(config.options.i18nFormat).toBeUndefined(); + expect(config.options.i18nFile).toBeUndefined(); + expect(config.options.i18nLocale).toBeUndefined(); + expect(config.configurations.de.i18nFormat).toBeUndefined(); + expect(config.configurations.de.i18nFile).toBeUndefined(); + expect(config.configurations.de.i18nLocale).toBeUndefined(); + }); + + it('should remove deprecated extract-i18n options', async () => { + let config = getWorkspaceTargets(tree); + config['extract-i18n'].options.i18nFormat = 'xmb'; + config['extract-i18n'].options.i18nLocale = 'en-GB'; + updateWorkspaceTargets(tree, config); + + const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise(); + config = getWorkspaceTargets(tree2)['extract-i18n']; + expect(config.options.i18nFormat).toBeUndefined(); + expect(config.options.i18nLocale).toBeUndefined(); + expect(config.options.format).toBe('xmb'); + }); + it(`should add i18n 'sourceLocale' project config when 'extract-i18n' 'i18nLocale' is defined`, async () => { const config = getWorkspaceTargets(tree); config.build.options.aot = false;