Skip to content

Commit 750baf9

Browse files
alan-agius4vikerman
authored andcommitted
feat(@schematics/angular): add migration to add new i18n options for Ivy
This migration will update current projects by adding the `i18n` project level option and add `localize` option in the server and browser builder configurations when both `i18nLocale` and `i18nFile` are defined.
1 parent 7045a78 commit 750baf9

File tree

3 files changed

+176
-20
lines changed

3 files changed

+176
-20
lines changed

packages/schematics/angular/migrations/update-9/update-workspace-config.ts

+73-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
removePropertyInAstObject,
1616
} from '../../utility/json-utils';
1717
import { Builders } from '../../utility/workspace-models';
18-
import { getAllOptions, getTargets, getWorkspace, isIvyEnabled } from './utils';
18+
import { getAllOptions, getProjectTarget, getTargets, getWorkspace, isIvyEnabled } from './utils';
1919

2020
export const ANY_COMPONENT_STYLE_BUDGET = {
2121
type: 'anyComponentStyle',
@@ -33,6 +33,7 @@ export function updateWorkspaceConfig(): Rule {
3333
updateStyleOrScriptOption('scripts', recorder, target);
3434
addAnyComponentStyleBudget(recorder, target);
3535
updateAotOption(tree, recorder, target);
36+
addBuilderI18NOptions(recorder, target);
3637
}
3738

3839
for (const { target } of getTargets(workspace, 'test', Builders.Karma)) {
@@ -42,6 +43,11 @@ export function updateWorkspaceConfig(): Rule {
4243

4344
for (const { target } of getTargets(workspace, 'server', Builders.Server)) {
4445
updateOptimizationOption(recorder, target);
46+
addBuilderI18NOptions(recorder, target);
47+
}
48+
49+
for (const { target, project } of getTargets(workspace, 'extract-i18n', Builders.ExtractI18n)) {
50+
addProjectI18NOptions(recorder, target, project);
4551
}
4652

4753
tree.commitUpdate(recorder);
@@ -50,6 +56,72 @@ export function updateWorkspaceConfig(): Rule {
5056
};
5157
}
5258

59+
function addProjectI18NOptions(recorder: UpdateRecorder, builderConfig: JsonAstObject, projectConfig: JsonAstObject) {
60+
const browserConfig = getProjectTarget(projectConfig, 'build', Builders.Browser);
61+
if (!browserConfig || browserConfig.kind !== 'object') {
62+
return;
63+
}
64+
65+
// browser builder options
66+
let locales: Record<string, string> | undefined;
67+
const options = getAllOptions(browserConfig);
68+
for (const option of options) {
69+
const localeId = findPropertyInAstObject(option, 'i18nLocale');
70+
if (!localeId || localeId.kind !== 'string') {
71+
continue;
72+
}
73+
74+
const localeFile = findPropertyInAstObject(option, 'i18nFile');
75+
if (!localeFile || localeFile.kind !== 'string') {
76+
continue;
77+
}
78+
79+
const localIdValue = localeId.value;
80+
const localeFileValue = localeFile.value;
81+
82+
if (!locales) {
83+
locales = {
84+
[localIdValue]: localeFileValue,
85+
};
86+
} else {
87+
locales[localIdValue] = localeFileValue;
88+
}
89+
}
90+
91+
if (locales) {
92+
// Get sourceLocale from extract-i18n builder
93+
const i18nOptions = getAllOptions(builderConfig);
94+
const sourceLocale = i18nOptions
95+
.map(o => {
96+
const sourceLocale = findPropertyInAstObject(o, 'i18nLocale');
97+
98+
return sourceLocale && sourceLocale.value;
99+
})
100+
.find(x => !!x);
101+
102+
// Add i18n project configuration
103+
insertPropertyInAstObjectInOrder(recorder, projectConfig, 'i18n', {
104+
locales,
105+
// tslint:disable-next-line: no-any
106+
sourceLocale: sourceLocale as any,
107+
}, 6);
108+
}
109+
}
110+
111+
function addBuilderI18NOptions(recorder: UpdateRecorder, builderConfig: JsonAstObject) {
112+
const options = getAllOptions(builderConfig);
113+
114+
for (const option of options) {
115+
const localeId = findPropertyInAstObject(option, 'i18nLocale');
116+
if (!localeId || localeId.kind !== 'string') {
117+
continue;
118+
}
119+
120+
// add new localize option
121+
insertPropertyInAstObjectInOrder(recorder, option, 'localize', [localeId.value], 12);
122+
}
123+
}
124+
53125
function updateAotOption(tree: Tree, recorder: UpdateRecorder, builderConfig: JsonAstObject) {
54126
const options = findPropertyInAstObject(builderConfig, 'options');
55127
if (!options || options.kind !== 'object') {

packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts

+71
Original file line numberDiff line numberDiff line change
@@ -296,5 +296,76 @@ describe('Migration to version 9', () => {
296296
expect(config.production.optimization).toBe(true);
297297
});
298298
});
299+
300+
describe('i18n configuration', () => {
301+
function getI18NConfig(localId: string): object {
302+
return {
303+
outputPath: `dist/my-project-${localId}/`,
304+
i18nFile: `src/locale/messages.${localId}.xlf`,
305+
i18nFormat: 'xlf',
306+
i18nLocale: localId,
307+
};
308+
}
309+
310+
describe('when i18n builder options are set', () => {
311+
it(`should add 'localize' option in configuration`, async () => {
312+
let config = getWorkspaceTargets(tree);
313+
config.build.options.aot = false;
314+
config.build.options = getI18NConfig('fr');
315+
config.build.configurations.de = getI18NConfig('de');
316+
updateWorkspaceTargets(tree, config);
317+
318+
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
319+
config = getWorkspaceTargets(tree2).build;
320+
expect(config.options.localize).toEqual(['fr']);
321+
expect(config.configurations.de.localize).toEqual(['de']);
322+
});
323+
324+
it(`should add i18n 'sourceLocale' project config when 'extract-i18n' 'i18nLocale' is defined`, async () => {
325+
const config = getWorkspaceTargets(tree);
326+
config.build.options.aot = false;
327+
config.build.options = getI18NConfig('fr');
328+
config['extract-i18n'].options.i18nLocale = 'en-GB';
329+
config.build.configurations.de = getI18NConfig('de');
330+
updateWorkspaceTargets(tree, config);
331+
332+
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
333+
const projectConfig = JSON.parse(tree2.readContent(workspacePath)).projects['migration-test'];
334+
expect(projectConfig.i18n.sourceLocale).toBe('en-GB');
335+
expect(projectConfig.i18n.locales).toBeDefined();
336+
});
337+
338+
it(`should add i18n 'locales' project config`, async () => {
339+
const config = getWorkspaceTargets(tree);
340+
config.build.options.aot = false;
341+
config.build.options = getI18NConfig('fr');
342+
config.build.configurations.de = getI18NConfig('de');
343+
updateWorkspaceTargets(tree, config);
344+
345+
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
346+
const projectConfig = JSON.parse(tree2.readContent(workspacePath)).projects['migration-test'];
347+
expect(projectConfig.i18n.sourceLocale).toBeUndefined();
348+
expect(projectConfig.i18n.locales).toEqual({
349+
de: 'src/locale/messages.de.xlf',
350+
fr: 'src/locale/messages.fr.xlf',
351+
});
352+
});
353+
});
354+
355+
describe('when i18n builder options are not set', () => {
356+
it(`should not add 'localize' option`, async () => {
357+
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
358+
const config = getWorkspaceTargets(tree2).build;
359+
expect(config.options.localize).toBeUndefined();
360+
expect(config.configurations.production.localize).toBeUndefined();
361+
});
362+
363+
it('should not add i18n project config', async () => {
364+
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
365+
const projectConfig = JSON.parse(tree2.readContent(workspacePath)).projects['migration-test'];
366+
expect(projectConfig.i18n).toBeUndefined();
367+
});
368+
});
369+
});
299370
});
300371
});

packages/schematics/angular/migrations/update-9/utils.ts

+32-19
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,36 @@ import { getWorkspacePath } from '../../utility/config';
1212
import { findPropertyInAstObject } from '../../utility/json-utils';
1313
import { Builders, WorkspaceTargets } from '../../utility/workspace-models';
1414

15-
/** Get all workspace targets which builder and target names matches the provided. */
15+
/** Get a project target which builder and target names matches the provided. */
16+
export function getProjectTarget(
17+
project: JsonAstObject,
18+
targetName: Exclude<keyof WorkspaceTargets, number>,
19+
builderName: Builders,
20+
): JsonAstObject | undefined {
21+
const projectRoot = findPropertyInAstObject(project, 'root');
22+
if (!projectRoot || projectRoot.kind !== 'string') {
23+
return undefined;
24+
}
25+
26+
const architect = findPropertyInAstObject(project, 'architect');
27+
if (!architect || architect.kind !== 'object') {
28+
return undefined;
29+
}
30+
31+
const target = findPropertyInAstObject(architect, targetName);
32+
if (!target || target.kind !== 'object') {
33+
return undefined;
34+
}
35+
36+
const builder = findPropertyInAstObject(target, 'builder');
37+
// Projects who's build builder is @angular-devkit/build-ng-packagr
38+
if (builder && builder.kind === 'string' && builder.value === builderName) {
39+
return target;
40+
}
41+
42+
return undefined;
43+
}
44+
1645
export function getTargets(
1746
workspace: JsonAstObject,
1847
targetName: Exclude<keyof WorkspaceTargets, number>,
@@ -30,24 +59,8 @@ export function getTargets(
3059
continue;
3160
}
3261

33-
const projectRoot = findPropertyInAstObject(projectConfig, 'root');
34-
if (!projectRoot || projectRoot.kind !== 'string') {
35-
continue;
36-
}
37-
38-
const architect = findPropertyInAstObject(projectConfig, 'architect');
39-
if (!architect || architect.kind !== 'object') {
40-
continue;
41-
}
42-
43-
const target = findPropertyInAstObject(architect, targetName);
44-
if (!target || target.kind !== 'object') {
45-
continue;
46-
}
47-
48-
const builder = findPropertyInAstObject(target, 'builder');
49-
// Projects who's build builder is @angular-devkit/build-ng-packagr
50-
if (builder && builder.kind === 'string' && builder.value === builderName) {
62+
const target = getProjectTarget(projectConfig, targetName, builderName);
63+
if (target) {
5164
targets.push({ target, project: projectConfig });
5265
}
5366
}

0 commit comments

Comments
 (0)