/** * @license * Copyright Google Inc. All Rights Reserved. * * 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 {AttributeMarker} from '@angular/compiler/src/core'; import {setup} from '@angular/compiler/test/aot/test_util'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../compiler/src/compiler'; import {decimalDigest} from '../../../compiler/src/i18n/digest'; import {extractMessages} from '../../../compiler/src/i18n/extractor_merger'; import {HtmlParser} from '../../../compiler/src/ml_parser/html_parser'; import {compile, expectEmit} from './mock_compile'; const angularFiles = setup({ compileAngular: false, compileFakeCore: true, compileAnimations: false, }); const htmlParser = new HtmlParser(); // TODO: update translation extraction RegExp to support i18nLocalize calls once #28689 lands. const EXTRACT_GENERATED_TRANSLATIONS_REGEXP = /const\s*(.*?)\s*=\s*goog\.getMsg\("(.*?)",?\s*(.*?)\)/g; const diff = (a: Set, b: Set): Set => new Set([...Array.from(a)].filter(x => !b.has(x))); const extract = (from: string, regex: any, transformFn: (match: any[], state?: Set) => any) => { const result = new Set(); let item; while ((item = regex.exec(from)) !== null) { result.add(transformFn(item, result)); } return result; }; // verify that we extracted all the necessary translations // and their ids match the ones extracted via 'ng xi18n' const verifyTranslationIds = (source: string, output: string, exceptions = {}, interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) => { const parseResult = htmlParser.parse(source, 'path:://to/template', {tokenizeExpansionForms: true}); const extractedIdToMsg = new Map(); const extractedIds = new Set(); const generatedIds = new Set(); const msgs = extractMessages(parseResult.rootNodes, interpolationConfig, [], {}); msgs.messages.forEach(msg => { const id = msg.id || decimalDigest(msg); extractedIds.add(id); extractedIdToMsg.set(id, msg); }); const regexp = /const\s*MSG_EXTERNAL_(.+?)\s*=\s*goog\.getMsg/g; const ids = extract(output, regexp, v => v[1]); ids.forEach(id => { generatedIds.add(id.split('$$')[0]); }); const delta = diff(extractedIds, generatedIds); if (delta.size) { // check if we have ids in exception list const outstanding = diff(delta, new Set(Object.keys(exceptions))); if (outstanding.size) { throw new Error(` Extracted and generated IDs don't match, delta: ${JSON.stringify(Array.from(delta))} `); } } return true; }; // verify that placeholders in translation string match // placeholders object defined as goog.getMsg function argument const verifyPlaceholdersIntegrity = (output: string) => { const extractTranslations = (from: string) => { return extract(from, EXTRACT_GENERATED_TRANSLATIONS_REGEXP, v => [v[2], v[3]]); }; const extractPlaceholdersFromBody = (body: string) => { const regex = /{\$(.*?)}/g; return extract(body, regex, v => v[1]); }; const extractPlaceholdersFromArgs = (args: string) => { const regex = /\s+"(.+?)":\s*".*?"/g; return extract(args, regex, v => v[1]); }; const translations = extractTranslations(output); translations.forEach((translation) => { const bodyPhs = extractPlaceholdersFromBody(translation[0]); const argsPhs = extractPlaceholdersFromArgs(translation[1]); if (bodyPhs.size !== argsPhs.size || diff(bodyPhs, argsPhs).size) { return false; } }); return true; }; const verifyUniqueConsts = (output: string) => { extract( output, EXTRACT_GENERATED_TRANSLATIONS_REGEXP, (current: string[], state: Set): string => { const key = current[1]; if (state.has(key)) { throw new Error(`Duplicate const ${key} found in generated output!`); } return key; }); return true; }; const getAppFilesWithTemplate = (template: string, args: any = {}) => ({ app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', ${args.preserveWhitespaces ? 'preserveWhitespaces: true,' : ''} ${args.interpolation ? 'interpolation: ' + JSON.stringify(args.interpolation) + ', ' : ''} template: \`${template}\` }) export class MyComponent {} @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }); const maybePrint = (output: string, verbose: boolean) => { if (!verbose) return; // tslint:disable-next-line console.log(` ========== Generated output: ========== ${output} ======================================= `); }; const verify = (input: string, output: string, extra: any = {}): void => { const files = getAppFilesWithTemplate(input, extra.inputArgs); const opts = (i18nUseExternalIds: boolean) => ({i18nUseExternalIds, ...(extra.compilerOptions || {})}); // invoke with file-based prefix translation names if (!extra.skipPathBasedCheck) { const result = compile(files, angularFiles, opts(false)); maybePrint(result.source, extra.verbose); expect(verifyPlaceholdersIntegrity(result.source)).toBe(true); expect(verifyUniqueConsts(result.source)).toBe(true); expectEmit(result.source, output, 'Incorrect template'); } // invoke with translation names based on external ids if (!extra.skipIdBasedCheck) { const result = compile(files, angularFiles, opts(true)); maybePrint(result.source, extra.verbose); const interpolationConfig = extra.inputArgs && extra.inputArgs.interpolation ? InterpolationConfig.fromArray(extra.inputArgs.interpolation) : undefined; expect(verifyTranslationIds(input, result.source, extra.exceptions, interpolationConfig)) .toBe(true); expect(verifyPlaceholdersIntegrity(result.source)).toBe(true); expect(verifyUniqueConsts(result.source)).toBe(true); expectEmit(result.source, output, 'Incorrect template'); } }; describe('i18n support in the view compiler', () => { describe('element attributes', () => { it('should add the meaning and description as JsDoc comments', () => { const input = `
Content A
Content B
Content C
Content D
Content E
Content F
Content G
`; const output = ` var $I18N_0$; if (ngI18nClosureMode) { /** * @desc descA * @meaning meaningA */ const $MSG_EXTERNAL_idA$$APP_SPEC_TS_1$ = goog.getMsg("Content A"); $I18N_0$ = $MSG_EXTERNAL_idA$$APP_SPEC_TS_1$; } else { $I18N_0$ = $r3$.Δi18nLocalize("Content A"); } const $_c2$ = ["title", "Title B"]; var $I18N_3$; if (ngI18nClosureMode) { /** * @desc descB * @meaning meaningB */ const $MSG_EXTERNAL_idB$$APP_SPEC_TS_4$ = goog.getMsg("Title B"); $I18N_3$ = $MSG_EXTERNAL_idB$$APP_SPEC_TS_4$; } else { $I18N_3$ = $r3$.Δi18nLocalize("Title B"); } const $_c5$ = ["title", $I18N_3$]; const $_c6$ = ["title", "Title C"]; var $I18N_7$; if (ngI18nClosureMode) { /** * @desc meaningC */ const $MSG_EXTERNAL_4978592519614169666$$APP_SPEC_TS_8$ = goog.getMsg("Title C"); $I18N_7$ = $MSG_EXTERNAL_4978592519614169666$$APP_SPEC_TS_8$; } else { $I18N_7$ = $r3$.Δi18nLocalize("Title C"); } const $_c9$ = ["title", $I18N_7$]; const $_c10$ = ["title", "Title D"]; var $I18N_11$; if (ngI18nClosureMode) { /** * @desc descD * @meaning meaningD */ const $MSG_EXTERNAL_5200291527729162531$$APP_SPEC_TS_12$ = goog.getMsg("Title D"); $I18N_11$ = $MSG_EXTERNAL_5200291527729162531$$APP_SPEC_TS_12$; } else { $I18N_11$ = $r3$.Δi18nLocalize("Title D"); } const $_c13$ = ["title", $I18N_11$]; const $_c14$ = ["title", "Title E"]; var $I18N_15$; if (ngI18nClosureMode) { /** * @desc meaningE */ const $MSG_EXTERNAL_idE$$APP_SPEC_TS_16$ = goog.getMsg("Title E"); $I18N_15$ = $MSG_EXTERNAL_idE$$APP_SPEC_TS_16$; } else { $I18N_15$ = $r3$.Δi18nLocalize("Title E"); } const $_c17$ = ["title", $I18N_15$]; const $_c18$ = ["title", "Title F"]; var $I18N_19$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_idF$$APP_SPEC_TS_20$ = goog.getMsg("Title F"); $I18N_19$ = $MSG_EXTERNAL_idF$$APP_SPEC_TS_20$; } else { $I18N_19$ = $r3$.Δi18nLocalize("Title F"); } const $_c21$ = ["title", $I18N_19$]; const $_c22$ = ["title", "Title G"]; var $I18N_23$; if (ngI18nClosureMode) { /** * @desc [BACKUP_MESSAGE_ID:idH]desc */ const $MSG_EXTERNAL_idG$$APP_SPEC_TS_24$ = goog.getMsg("Title G"); $I18N_23$ = $MSG_EXTERNAL_idG$$APP_SPEC_TS_24$; } else { $I18N_23$ = $r3$.Δi18nLocalize("Title G"); } const $_c25$ = ["title", $I18N_23$]; … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); $r3$.ΔelementStart(2, "div", $_c2$); $r3$.Δi18nAttributes(3, $_c5$); $r3$.Δtext(4, "Content B"); $r3$.ΔelementEnd(); $r3$.ΔelementStart(5, "div", $_c6$); $r3$.Δi18nAttributes(6, $_c9$); $r3$.Δtext(7, "Content C"); $r3$.ΔelementEnd(); $r3$.ΔelementStart(8, "div", $_c10$); $r3$.Δi18nAttributes(9, $_c13$); $r3$.Δtext(10, "Content D"); $r3$.ΔelementEnd(); $r3$.ΔelementStart(11, "div", $_c14$); $r3$.Δi18nAttributes(12, $_c17$); $r3$.Δtext(13, "Content E"); $r3$.ΔelementEnd(); $r3$.ΔelementStart(14, "div", $_c18$); $r3$.Δi18nAttributes(15, $_c21$); $r3$.Δtext(16, "Content F"); $r3$.ΔelementEnd(); $r3$.ΔelementStart(17, "div", $_c22$); $r3$.Δi18nAttributes(18, $_c25$); $r3$.Δtext(19, "Content G"); $r3$.ΔelementEnd(); } } `; verify(input, output); }); it('should not create translations for empty attributes', () => { const input = `
`; const output = ` const $_c0$ = ["id", "static", "title", ""]; … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δelement(0, "div", $_c0$); } } `; verify(input, output); }); it('should translate static attributes', () => { const input = `
`; const output = ` const $_c0$ = ["id", "static", "title", "introduction"]; var $I18N_1$; if (ngI18nClosureMode) { /** * @desc d * @meaning m */ const $MSG_EXTERNAL_8809028065680254561$$APP_SPEC_TS_1$ = goog.getMsg("introduction"); $I18N_1$ = $MSG_EXTERNAL_8809028065680254561$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("introduction"); } const $_c1$ = ["title", $I18N_1$]; … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div", $_c0$); $r3$.Δi18nAttributes(1, $_c1$); $r3$.ΔelementEnd(); } } `; verify(input, output); }); it('should support interpolation', () => { const input = `
`; const output = String.raw ` const $_c0$ = ["id", "dynamic-1", "aria-roledescription", "static text", ${AttributeMarker.Bindings}, "title", "aria-label"]; var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_5526535577705876535$$APP_SPEC_TS_1$ = goog.getMsg("static text"); $I18N_1$ = $MSG_EXTERNAL_5526535577705876535$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("static text"); } var $I18N_2$; if (ngI18nClosureMode) { /** * @desc d * @meaning m */ const $MSG_EXTERNAL_8977039798304050198$$APP_SPEC_TS_2$ = goog.getMsg("intro {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_2$ = $MSG_EXTERNAL_8977039798304050198$$APP_SPEC_TS_2$; } else { $I18N_2$ = $r3$.Δi18nLocalize("intro {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } var $I18N_3$; if (ngI18nClosureMode) { /** * @desc d1 * @meaning m1 */ const $MSG_EXTERNAL_7432761130955693041$$APP_SPEC_TS_3$ = goog.getMsg("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_3$ = $MSG_EXTERNAL_7432761130955693041$$APP_SPEC_TS_3$; } else { $I18N_3$ = $r3$.Δi18nLocalize("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } const $_c1$ = [ "aria-roledescription", $I18N_1$, "title", $I18N_2$, "aria-label", $I18N_3$ ]; const $_c2$ = ["id", "dynamic-2", ${AttributeMarker.Bindings}, "title", "aria-roledescription"]; var $I18N_6$; if (ngI18nClosureMode) { /** * @desc d2 * @meaning m2 */ const $MSG_EXTERNAL_7566208596013750546$$APP_SPEC_TS_6$ = goog.getMsg("{$interpolation} and {$interpolation_1} and again {$interpolation_2}", { "interpolation": "\uFFFD0\uFFFD", "interpolation_1": "\uFFFD1\uFFFD", "interpolation_2": "\uFFFD2\uFFFD" }); $I18N_6$ = $MSG_EXTERNAL_7566208596013750546$$APP_SPEC_TS_6$; } else { $I18N_6$ = $r3$.Δi18nLocalize("{$interpolation} and {$interpolation_1} and again {$interpolation_2}", { "interpolation": "\uFFFD0\uFFFD", "interpolation_1": "\uFFFD1\uFFFD", "interpolation_2": "\uFFFD2\uFFFD" }); } var $I18N_7$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_6639222533406278123$$APP_SPEC_TS_7$ = goog.getMsg("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_7$ = $MSG_EXTERNAL_6639222533406278123$$APP_SPEC_TS_7$; } else { $I18N_7$ = $r3$.Δi18nLocalize("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } const $_c3$ = [ "title", $I18N_6$, "aria-roledescription", $I18N_7$ ]; … consts: 5, vars: 8, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div", $_c0$); $r3$.Δpipe(1, "uppercase"); $r3$.Δi18nAttributes(2, $_c1$); $r3$.ΔelementEnd(); $r3$.ΔelementStart(3, "div", $_c2$); $r3$.Δi18nAttributes(4, $_c3$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(1, 0, ctx.valueA))); $r3$.Δi18nExp($r3$.Δbind(ctx.valueB)); $r3$.Δi18nApply(2); $r3$.Δselect(3); $r3$.Δi18nExp($r3$.Δbind(ctx.valueA)); $r3$.Δi18nExp($r3$.Δbind(ctx.valueB)); $r3$.Δi18nExp($r3$.Δbind((ctx.valueA + ctx.valueB))); $r3$.Δi18nExp($r3$.Δbind(ctx.valueC)); $r3$.Δi18nApply(4); } } `; verify(input, output); }); it('should support interpolation with custom interpolation config', () => { const input = `
`; const output = String.raw ` const $_c0$ = [${AttributeMarker.Bindings}, "title"]; var $I18N_1$; if (ngI18nClosureMode) { /** * @desc d * @meaning m */ const $MSG_EXTERNAL_8977039798304050198$ = goog.getMsg("intro {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_1$ = $MSG_EXTERNAL_8977039798304050198$; } else { $I18N_1$ = $r3$.Δi18nLocalize("intro {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } const $_c3$ = ["title", $I18N_1$]; … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div", $_c0$); $r3$.Δpipe(1, "uppercase"); $r3$.Δi18nAttributes(2, $_c3$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(1, 0, ctx.valueA))); $r3$.Δi18nApply(2); } } `; verify(input, output, {inputArgs: {interpolation: ['{%', '%}']}}); }); it('should correctly bind to context in nested template', () => { const input = `
`; const output = String.raw ` const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"]; const $_c1$ = [${AttributeMarker.Bindings}, "title"]; var $I18N_1$; if (ngI18nClosureMode) { /** * @desc d * @meaning m */ const $MSG_EXTERNAL_8538466649243975456$$APP_SPEC_TS__1$ = goog.getMsg("different scope {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_1$ = $MSG_EXTERNAL_8538466649243975456$$APP_SPEC_TS__1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("different scope {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } const $_c2$ = ["title", $I18N_1$]; function MyComponent_div_0_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.ΔelementStart(1, "div", $_c1$); $r3$.Δpipe(2, "uppercase"); $r3$.Δi18nAttributes(3, $_c2$); $r3$.ΔelementEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { const $outer_r1$ = ctx.$implicit; $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(2, 0, $outer_r1$))); $r3$.Δi18nApply(3); } } … consts: 1, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δtemplate(0, MyComponent_div_0_Template, 4, 3, "div", $_c0$); } if (rf & 2) { $r3$.Δselect(0); $r3$.Δproperty("ngForOf", ctx.items); } } `; verify(input, output); }); it('should support interpolation', () => { const input = `
`; const output = String.raw ` const $_c0$ = [ "id", "dynamic-1", "aria-roledescription", "static text", ${AttributeMarker.Bindings}, "title", "aria-label" ]; var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_5526535577705876535$$APP_SPEC_TS_1$ = goog.getMsg("static text"); $I18N_1$ = $MSG_EXTERNAL_5526535577705876535$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("static text"); } var $I18N_2$; if (ngI18nClosureMode) { /** * @desc d * @meaning m */ const $MSG_EXTERNAL_8977039798304050198$$APP_SPEC_TS_2$ = goog.getMsg("intro {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_2$ = $MSG_EXTERNAL_8977039798304050198$$APP_SPEC_TS_2$; } else { $I18N_2$ = $r3$.Δi18nLocalize("intro {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } var $I18N_3$; if (ngI18nClosureMode) { /** * @desc d1 * @meaning m1 */ const $MSG_EXTERNAL_7432761130955693041$$APP_SPEC_TS_3$ = goog.getMsg("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_3$ = $MSG_EXTERNAL_7432761130955693041$$APP_SPEC_TS_3$; } else { $I18N_3$ = $r3$.Δi18nLocalize("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } const $_c1$ = [ "aria-roledescription", $I18N_1$, "title", $I18N_2$, "aria-label", $I18N_3$ ]; const $_c2$ = ["id", "dynamic-2", ${AttributeMarker.Bindings}, "title", "aria-roledescription"]; var $I18N_6$; if (ngI18nClosureMode) { /** * @desc d2 * @meaning m2 */ const $MSG_EXTERNAL_7566208596013750546$$APP_SPEC_TS_6$ = goog.getMsg("{$interpolation} and {$interpolation_1} and again {$interpolation_2}", { "interpolation": "\uFFFD0\uFFFD", "interpolation_1": "\uFFFD1\uFFFD", "interpolation_2": "\uFFFD2\uFFFD" }); $I18N_6$ = $MSG_EXTERNAL_7566208596013750546$$APP_SPEC_TS_6$; } else { $I18N_6$ = $r3$.Δi18nLocalize("{$interpolation} and {$interpolation_1} and again {$interpolation_2}", { "interpolation": "\uFFFD0\uFFFD", "interpolation_1": "\uFFFD1\uFFFD", "interpolation_2": "\uFFFD2\uFFFD" }); } var $I18N_7$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_6639222533406278123$$APP_SPEC_TS_7$ = goog.getMsg("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_7$ = $MSG_EXTERNAL_6639222533406278123$$APP_SPEC_TS_7$; } else { $I18N_7$ = $r3$.Δi18nLocalize("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } const $_c3$ = [ "title", $I18N_6$, "aria-roledescription", $I18N_7$ ]; … consts: 5, vars: 8, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div", $_c0$); $r3$.Δpipe(1, "uppercase"); $r3$.Δi18nAttributes(2, $_c1$); $r3$.ΔelementEnd(); $r3$.ΔelementStart(3, "div", $_c2$); $r3$.Δi18nAttributes(4, $_c3$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(1, 0, ctx.valueA))); $r3$.Δi18nExp($r3$.Δbind(ctx.valueB)); $r3$.Δi18nApply(2); $r3$.Δselect(3); $r3$.Δi18nExp($r3$.Δbind(ctx.valueA)); $r3$.Δi18nExp($r3$.Δbind(ctx.valueB)); $r3$.Δi18nExp($r3$.Δbind((ctx.valueA + ctx.valueB))); $r3$.Δi18nExp($r3$.Δbind(ctx.valueC)); $r3$.Δi18nApply(4); } } `; verify(input, output); }); it('should correctly bind to context in nested template', () => { const input = `
`; const output = String.raw ` const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"]; const $_c1$ = [${AttributeMarker.Bindings}, "title"]; var $I18N_2$; if (ngI18nClosureMode) { /** * @desc d * @meaning m */ const $MSG_EXTERNAL_8538466649243975456$$APP_SPEC_TS__3$ = goog.getMsg("different scope {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_2$ = $MSG_EXTERNAL_8538466649243975456$$APP_SPEC_TS__3$; } else { $I18N_2$ = $r3$.Δi18nLocalize("different scope {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } const $_c4$ = ["title", $I18N_2$]; function MyComponent_div_0_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.ΔelementStart(1, "div", $_c1$); $r3$.Δpipe(2, "uppercase"); $r3$.Δi18nAttributes(3, $_c4$); $r3$.ΔelementEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { const $outer_r1$ = ctx.$implicit; $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(2, 0, $outer_r1$))); $r3$.Δi18nApply(3); } } … consts: 1, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δtemplate(0, MyComponent_div_0_Template, 4, 3, "div", $_c0$); } if (rf & 2) { $r3$.Δselect(0); $r3$.Δproperty("ngForOf", ctx.items); } } `; verify(input, output); }); it('should work correctly when placed on i18n root node', () => { const input = `
Some content
`; const output = String.raw ` const $_c0$ = ["title", "Element title"]; var $I18N_0$; if (ngI18nClosureMode) { /** * @desc d * @meaning m */ const $MSG_EXTERNAL_7727043314656808423$$APP_SPEC_TS_0$ = goog.getMsg("Element title"); $I18N_0$ = $MSG_EXTERNAL_7727043314656808423$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("Element title"); } const $_c1$ = ["title", $I18N_0$]; var $I18N_2$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4969674997806975147$$APP_SPEC_TS_2$ = goog.getMsg("Some content"); $I18N_2$ = $MSG_EXTERNAL_4969674997806975147$$APP_SPEC_TS_2$; } else { $I18N_2$ = $r3$.Δi18nLocalize("Some content"); } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div", $_c0$); $r3$.Δi18nAttributes(1, $_c1$); $r3$.Δi18n(2, $I18N_2$); $r3$.ΔelementEnd(); } } `; verify(input, output); }); it('should sanitize ids and generate proper var names', () => { const input = `
Some content
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_ID_WITH_INVALID_CHARS$$APP_SPEC_TS_1$ = goog.getMsg("Element title"); $I18N_0$ = $MSG_EXTERNAL_ID_WITH_INVALID_CHARS$$APP_SPEC_TS_1$; } else { $I18N_0$ = $r3$.Δi18nLocalize("Element title"); } const $_c1$ = ["title", $I18N_0$]; var $I18N_2$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_ID_WITH_INVALID_CHARS_2$$APP_SPEC_TS_4$ = goog.getMsg(" Some content "); $I18N_2$ = $MSG_EXTERNAL_ID_WITH_INVALID_CHARS_2$$APP_SPEC_TS_4$; } else { $I18N_2$ = $r3$.Δi18nLocalize(" Some content "); } … `; const exceptions = { 'ID.WITH.INVALID.CHARS': 'Verify const name generation only', 'ID.WITH.INVALID.CHARS.2': 'Verify const name generation only' }; verify(input, output, {exceptions, skipPathBasedCheck: true}); }); }); describe('nested nodes', () => { it('should not produce instructions for empty content', () => { const input = `
`; const output = String.raw ` template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δelement(0, "div"); $r3$.Δelement(1, "div"); $r3$.Δelement(2, "div"); } } `; const exceptions = { '6524085439495453930': 'No translation is produced for empty content (whitespaces)', '814405839137385666': 'No translation is produced for empty content (line breaks)' }; verify(input, output, {exceptions}); }); it('should properly escape quotes in content', () => { const input = `
Some text 'with single quotes', "with double quotes" and without quotes.
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4924931801512133405$$APP_SPEC_TS_0$ = goog.getMsg("Some text 'with single quotes', \"with double quotes\" and without quotes."); $I18N_0$ = $MSG_EXTERNAL_4924931801512133405$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("Some text 'with single quotes', \"with double quotes\" and without quotes."); } `; verify(input, output); }); it('should handle i18n attributes with plain-text content', () => { const input = `
My i18n block #1
My non-i18n block #1
My i18n block #2
My non-i18n block #2
My i18n block #3
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4890179241114413722$$APP_SPEC_TS_0$ = goog.getMsg("My i18n block #1"); $I18N_0$ = $MSG_EXTERNAL_4890179241114413722$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("My i18n block #1"); } var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2413150872298537152$$APP_SPEC_TS_1$ = goog.getMsg("My i18n block #2"); $I18N_1$ = $MSG_EXTERNAL_2413150872298537152$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("My i18n block #2"); } var $I18N_2$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_5023003143537152794$$APP_SPEC_TS_2$ = goog.getMsg("My i18n block #3"); $I18N_2$ = $MSG_EXTERNAL_5023003143537152794$$APP_SPEC_TS_2$; } else { $I18N_2$ = $r3$.Δi18nLocalize("My i18n block #3"); } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); $r3$.ΔelementStart(2, "div"); $r3$.Δtext(3, "My non-i18n block #1"); $r3$.ΔelementEnd(); $r3$.ΔelementStart(4, "div"); $r3$.Δi18n(5, $I18N_1$); $r3$.ΔelementEnd(); $r3$.ΔelementStart(6, "div"); $r3$.Δtext(7, "My non-i18n block #2"); $r3$.ΔelementEnd(); $r3$.ΔelementStart(8, "div"); $r3$.Δi18n(9, $I18N_2$); $r3$.ΔelementEnd(); } } `; verify(input, output); }); it('should support named interpolations', () => { const input = `
Some value: {{ valueA // i18n(ph="PH_A") }}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2817319788724342848$$APP_SPEC_TS_0$ = goog.getMsg("Some value: {$phA}", { "phA": "\uFFFD0\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_2817319788724342848$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("Some value: {$phA}", { "phA": "\uFFFD0\uFFFD" }); } … consts: 2, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.valueA)); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should support interpolation with custom interpolation config', () => { const input = `
{% valueA %}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_6749967533321674787$$APP_SPEC_TS_0$ = goog.getMsg("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_6749967533321674787$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.valueA)); $r3$.Δi18nApply(1); } } `; verify(input, output, {inputArgs: {interpolation: ['{%', '%}']}}); }); it('should support interpolations with complex expressions', () => { const input = `
{{ valueA | async }} {{ valueA?.a?.b }}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_1482713963707913023$$APP_SPEC_TS_0$ = goog.getMsg(" {$interpolation} {$interpolation_1} ", { "interpolation": "\uFFFD0\uFFFD", "interpolation_1": "\uFFFD1\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_1482713963707913023$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize(" {$interpolation} {$interpolation_1} ", { "interpolation": "\uFFFD0\uFFFD", "interpolation_1": "\uFFFD1\uFFFD" }); } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.Δpipe(2, "async"); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(2, 2, ctx.valueA))); $r3$.Δi18nExp($r3$.Δbind(((ctx.valueA == null) ? null : ((ctx.valueA.a == null) ? null : ctx.valueA.a.b)))); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should handle i18n attributes with bindings in content', () => { const input = `
My i18n block #{{ one }}
My i18n block #{{ two | uppercase }}
My i18n block #{{ three + four + five }}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_572579892698764378$$APP_SPEC_TS_0$ = goog.getMsg("My i18n block #{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_572579892698764378$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("My i18n block #{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_609623417156596326$$APP_SPEC_TS_1$ = goog.getMsg("My i18n block #{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_1$ = $MSG_EXTERNAL_609623417156596326$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("My i18n block #{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } var $I18N_2$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_3998119318957372120$$APP_SPEC_TS_2$ = goog.getMsg("My i18n block #{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_2$ = $MSG_EXTERNAL_3998119318957372120$$APP_SPEC_TS_2$; } else { $I18N_2$ = $r3$.Δi18nLocalize("My i18n block #{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } … consts: 7, vars: 5, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); $r3$.ΔelementStart(2, "div"); $r3$.Δi18n(3, $I18N_1$); $r3$.Δpipe(4, "uppercase"); $r3$.ΔelementEnd(); $r3$.ΔelementStart(5, "div"); $r3$.Δi18n(6, $I18N_2$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.one)); $r3$.Δi18nApply(1); $r3$.Δselect(3); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(4, 3, ctx.two))); $r3$.Δi18nApply(3); $r3$.Δselect(6); $r3$.Δi18nExp($r3$.Δbind(((ctx.three + ctx.four) + ctx.five))); $r3$.Δi18nApply(6); } } `; verify(input, output); }); it('should handle i18n attributes with bindings and nested elements in content', () => { const input = `
My i18n block #{{ one }} Plain text in nested element
My i18n block #{{ two | uppercase }}
More bindings in more nested element: {{ nestedInBlockTwo }}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7905233330103651696$$APP_SPEC_TS_0$ = goog.getMsg(" My i18n block #{$interpolation} {$startTagSpan}Plain text in nested element{$closeTagSpan}", { "interpolation": "\uFFFD0\uFFFD", "startTagSpan": "\uFFFD#2\uFFFD", "closeTagSpan": "\uFFFD/#2\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_7905233330103651696$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize(" My i18n block #{$interpolation} {$startTagSpan}Plain text in nested element{$closeTagSpan}", { "interpolation": "\uFFFD0\uFFFD", "startTagSpan": "\uFFFD#2\uFFFD", "closeTagSpan": "\uFFFD/#2\uFFFD" }); } var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_5788821996131681377$$APP_SPEC_TS_1$ = goog.getMsg(" My i18n block #{$interpolation} {$startTagDiv}{$startTagDiv}{$startTagSpan} More bindings in more nested element: {$interpolation_1} {$closeTagSpan}{$closeTagDiv}{$closeTagDiv}", { "interpolation": "\uFFFD0\uFFFD", "startTagDiv": "[\uFFFD#6\uFFFD|\uFFFD#7\uFFFD]", "startTagSpan": "\uFFFD#8\uFFFD", "interpolation_1": "\uFFFD1\uFFFD", "closeTagSpan": "\uFFFD/#8\uFFFD", "closeTagDiv": "[\uFFFD/#7\uFFFD|\uFFFD/#6\uFFFD]" }); $I18N_1$ = $MSG_EXTERNAL_5788821996131681377$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize(" My i18n block #{$interpolation} {$startTagDiv}{$startTagDiv}{$startTagSpan} More bindings in more nested element: {$interpolation_1} {$closeTagSpan}{$closeTagDiv}{$closeTagDiv}", { "interpolation": "\uFFFD0\uFFFD", "startTagDiv": "[\uFFFD#6\uFFFD|\uFFFD#7\uFFFD]", "startTagSpan": "\uFFFD#8\uFFFD", "interpolation_1": "\uFFFD1\uFFFD", "closeTagSpan": "\uFFFD/#8\uFFFD", "closeTagDiv": "[\uFFFD/#7\uFFFD|\uFFFD/#6\uFFFD]" }); } $I18N_1$ = $r3$.Δi18nPostprocess($I18N_1$); … consts: 9, vars: 5, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_0$); $r3$.Δelement(2, "span"); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); $r3$.ΔelementStart(3, "div"); $r3$.Δi18nStart(4, $I18N_1$); $r3$.Δpipe(5, "uppercase"); $r3$.ΔelementStart(6, "div"); $r3$.ΔelementStart(7, "div"); $r3$.Δelement(8, "span"); $r3$.ΔelementEnd(); $r3$.ΔelementEnd(); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.one)); $r3$.Δi18nApply(1); $r3$.Δselect(4); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(5, 3, ctx.two))); $r3$.Δi18nExp($r3$.Δbind(ctx.nestedInBlockTwo)); $r3$.Δi18nApply(4); } } `; verify(input, output); }); it('should handle i18n attributes with bindings in content and element attributes', () => { const input = `
My i18n block #1 with value: {{ valueA }} Plain text in nested element (block #1)
My i18n block #2 with value {{ valueD | uppercase }} Plain text in nested element (block #2)
`; const output = String.raw ` const $_c1$ = [${AttributeMarker.Bindings}, "title"]; var $I18N_2$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4782264005467235841$$APP_SPEC_TS_3$ = goog.getMsg("Span title {$interpolation} and {$interpolation_1}", { "interpolation": "\uFFFD0\uFFFD", "interpolation_1": "\uFFFD1\uFFFD" }); $I18N_2$ = $MSG_EXTERNAL_4782264005467235841$$APP_SPEC_TS_3$; } else { $I18N_2$ = $r3$.Δi18nLocalize("Span title {$interpolation} and {$interpolation_1}", { "interpolation": "\uFFFD0\uFFFD", "interpolation_1": "\uFFFD1\uFFFD" }); } const $_c4$ = ["title", $I18N_2$]; var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4446430594603971069$$APP_SPEC_TS_5$ = goog.getMsg(" My i18n block #1 with value: {$interpolation} {$startTagSpan} Plain text in nested element (block #1) {$closeTagSpan}", { "interpolation": "\uFFFD0\uFFFD", "startTagSpan": "\uFFFD#2\uFFFD", "closeTagSpan": "\uFFFD/#2\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_4446430594603971069$$APP_SPEC_TS_5$; } else { $I18N_0$ = $r3$.Δi18nLocalize(" My i18n block #1 with value: {$interpolation} {$startTagSpan} Plain text in nested element (block #1) {$closeTagSpan}", { "interpolation": "\uFFFD0\uFFFD", "startTagSpan": "\uFFFD#2\uFFFD", "closeTagSpan": "\uFFFD/#2\uFFFD" }); } var $I18N_7$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2719594642740200058$$APP_SPEC_TS_8$ = goog.getMsg("Span title {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_7$ = $MSG_EXTERNAL_2719594642740200058$$APP_SPEC_TS_8$; } else { $I18N_7$ = $r3$.Δi18nLocalize("Span title {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } const $_c9$ = ["title", $I18N_7$]; var $I18N_6$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2778714953278357902$$APP_SPEC_TS_10$ = goog.getMsg(" My i18n block #2 with value {$interpolation} {$startTagSpan} Plain text in nested element (block #2) {$closeTagSpan}", { "interpolation": "\uFFFD0\uFFFD", "startTagSpan": "\uFFFD#7\uFFFD", "closeTagSpan": "\uFFFD/#7\uFFFD" }); $I18N_6$ = $MSG_EXTERNAL_2778714953278357902$$APP_SPEC_TS_10$; } else { $I18N_6$ = $r3$.Δi18nLocalize(" My i18n block #2 with value {$interpolation} {$startTagSpan} Plain text in nested element (block #2) {$closeTagSpan}", { "interpolation": "\uFFFD0\uFFFD", "startTagSpan": "\uFFFD#7\uFFFD", "closeTagSpan": "\uFFFD/#7\uFFFD" }); } … consts: 9, vars: 7, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_0$); $r3$.ΔelementStart(2, "span", $_c1$); $r3$.Δi18nAttributes(3, $_c4$); $r3$.ΔelementEnd(); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); $r3$.ΔelementStart(4, "div"); $r3$.Δi18nStart(5, $I18N_6$); $r3$.Δpipe(6, "uppercase"); $r3$.ΔelementStart(7, "span", $_c1$); $r3$.Δi18nAttributes(8, $_c9$); $r3$.ΔelementEnd(); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(2); $r3$.Δi18nExp($r3$.Δbind(ctx.valueB)); $r3$.Δi18nExp($r3$.Δbind(ctx.valueC)); $r3$.Δi18nApply(3); $r3$.Δi18nExp($r3$.Δbind(ctx.valueA)); $r3$.Δi18nApply(1); $r3$.Δselect(7); $r3$.Δi18nExp($r3$.Δbind(ctx.valueE)); $r3$.Δi18nApply(8); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(6, 5, ctx.valueD))); $r3$.Δi18nApply(5); } } `; verify(input, output); }); it('should handle i18n attributes in nested templates', () => { const input = `
Some content
Some other content {{ valueA }}
More nested levels with bindings {{ valueB | uppercase }}
`; const output = String.raw ` const $_c0$ = [${AttributeMarker.Template}, "ngIf"]; var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7679414751795588050$$APP_SPEC_TS__1$ = goog.getMsg(" Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}", { "interpolation": "\uFFFD0\uFFFD", "startTagDiv": "\uFFFD#3\uFFFD", "interpolation_1": "\uFFFD1\uFFFD", "closeTagDiv": "\uFFFD/#3\uFFFD" }); $I18N_1$ = $MSG_EXTERNAL_7679414751795588050$$APP_SPEC_TS__1$; } else { $I18N_1$ = $r3$.Δi18nLocalize(" Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}", { "interpolation": "\uFFFD0\uFFFD", "startTagDiv": "\uFFFD#3\uFFFD", "interpolation_1": "\uFFFD1\uFFFD", "closeTagDiv": "\uFFFD/#3\uFFFD" }); } … function MyComponent_div_2_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.ΔelementStart(1, "div"); $r3$.Δi18nStart(2, $I18N_1$); $r3$.Δelement(3, "div"); $r3$.Δpipe(4, "uppercase"); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(2); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.valueA)); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(4, 2, $ctx_r0$.valueB))); $r3$.Δi18nApply(2); } } … consts: 3, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δtext(1, " Some content "); $r3$.Δtemplate(2, MyComponent_div_2_Template, 5, 4, "div", $_c0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(2); $r3$.Δproperty("ngIf", ctx.visible); } } `; verify(input, output); }); it('should ignore i18n attributes on self-closing tags', () => { const input = ` `; const output = String.raw ` const $_c0$ = ["src", "logo.png"]; const $_c1$ = ["src", "logo.png", ${AttributeMarker.Template}, "ngIf"]; const $_c2$ = ["src", "logo.png", ${AttributeMarker.Bindings}, "title", ${AttributeMarker.Template}, "ngIf"]; function MyComponent_img_1_Template(rf, ctx) { if (rf & 1) { $r3$.Δelement(0, "img", $_c0$); } } const $_c3$ = ["src", "logo.png", ${AttributeMarker.Bindings}, "title"]; var $I18N_2$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2367729185105559721$$APP_SPEC_TS__2$ = goog.getMsg("App logo #{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_2$ = $MSG_EXTERNAL_2367729185105559721$$APP_SPEC_TS__2$; } else { $I18N_2$ = $r3$.Δi18nLocalize("App logo #{$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } const $_c4$ = ["title", $I18N_2$]; function MyComponent_img_2_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "img", $_c3$); $r3$.Δi18nAttributes(1, $_c4$); $r3$.ΔelementEnd(); } if (rf & 2) { const $ctx_r1$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($ctx_r1$.id)); $r3$.Δi18nApply(1); } } … consts: 3, vars: 2, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δelement(0, "img", $_c0$); $r3$.Δtemplate(1, MyComponent_img_1_Template, 1, 0, "img", $_c1$); $r3$.Δtemplate(2, MyComponent_img_2_Template, 2, 1, "img", $_c2$); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δproperty("ngIf", ctx.visible); $r3$.Δselect(2); $r3$.Δproperty("ngIf", ctx.visible); } } `; verify(input, output); }); it('should handle i18n context in nested templates', () => { const input = `
Some content
Some other content {{ valueA }}
More nested levels with bindings {{ valueB | uppercase }}
Content inside sub-template {{ valueC }}
Bottom level element {{ valueD }}
Some other content {{ valueE + valueF }}
More nested levels with bindings {{ valueG | uppercase }}
`; const output = String.raw ` const $_c0$ = [${AttributeMarker.Template}, "ngIf"]; function MyComponent_div_2_div_4_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18nStart(0, $I18N_0$, 2); $r3$.ΔelementStart(1, "div"); $r3$.Δelement(2, "div"); $r3$.ΔelementEnd(); $r3$.Δi18nEnd(); } if (rf & 2) { const $ctx_r2$ = $r3$.ΔnextContext(2); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($ctx_r2$.valueC)); $r3$.Δi18nExp($r3$.Δbind($ctx_r2$.valueD)); $r3$.Δi18nApply(0); } } function MyComponent_div_2_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18nStart(0, $I18N_0$, 1); $r3$.ΔelementStart(1, "div"); $r3$.ΔelementStart(2, "div"); $r3$.Δpipe(3, "uppercase"); $r3$.Δtemplate(4, MyComponent_div_2_div_4_Template, 3, 2, "div", $_c1$); $r3$.ΔelementEnd(); $r3$.ΔelementEnd(); $r3$.Δi18nEnd(); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(4); $r3$.Δproperty("ngIf", $ctx_r0$.exists); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.valueA)); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(3, 3, $ctx_r0$.valueB))); $r3$.Δi18nApply(0); } } var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_1221890473527419724$$APP_SPEC_TS_0$ = goog.getMsg(" Some content {$startTagDiv_2} Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$startTagDiv_1} Content inside sub-template {$interpolation_2} {$startTagDiv} Bottom level element {$interpolation_3} {$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$startTagDiv_3} Some other content {$interpolation_4} {$startTagDiv} More nested levels with bindings {$interpolation_5} {$closeTagDiv}{$closeTagDiv}", { "startTagDiv_2": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD", "closeTagDiv": "[\uFFFD/#2:2\uFFFD|\uFFFD/#1:2\uFFFD\uFFFD/*4:2\uFFFD|\uFFFD/#2:1\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD|\uFFFD/#2:3\uFFFD|\uFFFD/#1:3\uFFFD\uFFFD/*3:3\uFFFD]", "startTagDiv_3": "\uFFFD*3:3\uFFFD\uFFFD#1:3\uFFFD", "interpolation": "\uFFFD0:1\uFFFD", "startTagDiv": "[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]", "interpolation_1": "\uFFFD1:1\uFFFD", "startTagDiv_1": "\uFFFD*4:2\uFFFD\uFFFD#1:2\uFFFD", "interpolation_2": "\uFFFD0:2\uFFFD", "interpolation_3": "\uFFFD1:2\uFFFD", "interpolation_4": "\uFFFD0:3\uFFFD", "interpolation_5": "\uFFFD1:3\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_1221890473527419724$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize(" Some content {$startTagDiv_2} Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$startTagDiv_1} Content inside sub-template {$interpolation_2} {$startTagDiv} Bottom level element {$interpolation_3} {$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$startTagDiv_3} Some other content {$interpolation_4} {$startTagDiv} More nested levels with bindings {$interpolation_5} {$closeTagDiv}{$closeTagDiv}", { "startTagDiv_2": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD", "closeTagDiv": "[\uFFFD/#2:2\uFFFD|\uFFFD/#1:2\uFFFD\uFFFD/*4:2\uFFFD|\uFFFD/#2:1\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD|\uFFFD/#2:3\uFFFD|\uFFFD/#1:3\uFFFD\uFFFD/*3:3\uFFFD]", "startTagDiv_3": "\uFFFD*3:3\uFFFD\uFFFD#1:3\uFFFD", "interpolation": "\uFFFD0:1\uFFFD", "startTagDiv": "[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]", "interpolation_1": "\uFFFD1:1\uFFFD", "startTagDiv_1": "\uFFFD*4:2\uFFFD\uFFFD#1:2\uFFFD", "interpolation_2": "\uFFFD0:2\uFFFD", "interpolation_3": "\uFFFD1:2\uFFFD", "interpolation_4": "\uFFFD0:3\uFFFD", "interpolation_5": "\uFFFD1:3\uFFFD" }); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$); function MyComponent_div_3_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18nStart(0, $I18N_0$, 3); $r3$.ΔelementStart(1, "div"); $r3$.Δelement(2, "div"); $r3$.Δpipe(3, "uppercase"); $r3$.ΔelementEnd(); $r3$.Δi18nEnd(); } if (rf & 2) { const $ctx_r1$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind(($ctx_r1$.valueE + $ctx_r1$.valueF))); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(3, 2, $ctx_r1$.valueG))); $r3$.Δi18nApply(0); } } … consts: 4, vars: 2, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_0$); $r3$.Δtemplate(2, MyComponent_div_2_Template, 5, 5, "div", $_c1$); $r3$.Δtemplate(3, MyComponent_div_3_Template, 4, 4, "div", $_c1$); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(2); $r3$.Δproperty("ngIf", ctx.visible); $r3$.Δselect(3); $r3$.Δproperty("ngIf", !ctx.visible); } } `; verify(input, output); }); it('should handle i18n attribute with directives', () => { const input = `
Some other content {{ valueA }}
`; const output = String.raw ` const $_c0$ = [${AttributeMarker.Template}, "ngIf"]; var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_119975189388320493$$APP_SPEC_TS__1$ = goog.getMsg("Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}", { "startTagSpan": "\uFFFD#2\uFFFD", "interpolation": "\uFFFD0\uFFFD", "closeTagSpan": "\uFFFD/#2\uFFFD" }); $I18N_1$ = $MSG_EXTERNAL_119975189388320493$$APP_SPEC_TS__1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}", { "startTagSpan": "\uFFFD#2\uFFFD", "interpolation": "\uFFFD0\uFFFD", "closeTagSpan": "\uFFFD/#2\uFFFD" }); } … function MyComponent_div_0_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_1$); $r3$.Δelement(2, "span"); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.valueA)); $r3$.Δi18nApply(1); } } … consts: 1, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δtemplate(0, MyComponent_div_0_Template, 3, 1, "div", $_c0$); } if (rf & 2) { $r3$.Δselect(0); $r3$.Δproperty("ngIf", ctx.visible); } } `; verify(input, output); }); it('should generate event listeners instructions before i18n ones', () => { const input = `
Hello
`; const output = String.raw ` const $_c0$ = [${AttributeMarker.Bindings}, "click"]; var $I18N_1$; if (ngI18nClosureMode) { const $MSG_APP_SPEC_TS_2$ = goog.getMsg("Hello"); $I18N_1$ = $MSG_APP_SPEC_TS_2$; } else { $I18N_1$ = $r3$.Δi18nLocalize("Hello"); } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div", $_c0$); $r3$.Δlistener("click", function MyComponent_Template_div_click_0_listener($event) { return ctx.onClick(); }); $r3$.Δi18n(1, $I18N_1$); $r3$.ΔelementEnd(); } } `; verify(input, output); }); }); describe('self-closing i18n instructions', () => { it('should be generated with text-only content', () => { const input = `
My i18n block #1
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4890179241114413722$$APP_SPEC_TS_0$ = goog.getMsg("My i18n block #1"); $I18N_0$ = $MSG_EXTERNAL_4890179241114413722$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("My i18n block #1"); } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } } `; verify(input, output); }); it('should be generated for ICU-only i18n blocks', () => { const input = `
{age, select, 10 {ten} 20 {twenty} other {other}}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); $I18N_0$ = $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); … consts: 2, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.age)); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should be generated within and blocks', () => { const input = ` My i18n block #1 My i18n block #2 `; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2413150872298537152$$APP_SPEC_TS_0$ = goog.getMsg("My i18n block #2"); $I18N_0$ = $MSG_EXTERNAL_2413150872298537152$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("My i18n block #2"); } var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4890179241114413722$$APP_SPEC_TS__1$ = goog.getMsg("My i18n block #1"); $I18N_1$ = $MSG_EXTERNAL_4890179241114413722$$APP_SPEC_TS__1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("My i18n block #1"); } function MyComponent_ng_template_0_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18n(0, $I18N_1$); } } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δtemplate(0, MyComponent_ng_template_0_Template, 1, 0, "ng-template"); $r3$.ΔelementContainerStart(1); $r3$.Δi18n(2, $I18N_0$); $r3$.ΔelementContainerEnd(); } } `; verify(input, output); }); it('should not be generated in case we have styling instructions', () => { const input = ` Text #1 Text #2 `; const output = String.raw ` const $_c0$ = [${AttributeMarker.Classes}, "myClass"]; var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_1$ = goog.getMsg("Text #1"); $I18N_1$ = $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("Text #1"); } const $_c2$ = [${AttributeMarker.Styles}, "padding", "10px"]; var $I18N_3$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_3$ = goog.getMsg("Text #2"); $I18N_3$ = $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_3$; } else { $I18N_3$ = $r3$.Δi18nLocalize("Text #2"); } … consts: 4, vars: 0, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "span", $_c0$); $r3$.Δi18n(1, $I18N_1$); $r3$.ΔelementEnd(); $r3$.ΔelementStart(2, "span", $_c1$); $r3$.Δi18n(3, $I18N_3$); $r3$.ΔelementEnd(); } } `; verify(input, output); }); }); describe('ng-container and ng-template', () => { it('should handle single translation message using ', () => { const input = ` Some content: {{ valueA | uppercase }} `; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_355394464191978948$$APP_SPEC_TS_0$ = goog.getMsg("Some content: {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_355394464191978948$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("Some content: {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } … consts: 3, vars: 3, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementContainerStart(0); $r3$.Δi18n(1, $I18N_0$); $r3$.Δpipe(2, "uppercase"); $r3$.ΔelementContainerEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(2, 1, ctx.valueA))); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should handle single translation message using ', () => { const input = ` Some content: {{ valueA | uppercase }} `; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_355394464191978948$$APP_SPEC_TS__0$ = goog.getMsg("Some content: {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_355394464191978948$$APP_SPEC_TS__0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("Some content: {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }); } function MyComponent_ng_template_0_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18n(0, $I18N_0$); $r3$.Δpipe(1, "uppercase"); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(1, 1, $ctx_r0$.valueA))); $r3$.Δi18nApply(0); } } … consts: 1, vars: 0, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δtemplate(0, MyComponent_ng_template_0_Template, 2, 3, "ng-template"); } } `; verify(input, output); }); it('should be able to act as child elements inside i18n block', () => { const input = `
Template content: {{ valueA | uppercase }} Container content: {{ valueB | uppercase }}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_702706566400598764$$APP_SPEC_TS_0$ = goog.getMsg("{$startTagNgTemplate}Template content: {$interpolation}{$closeTagNgTemplate}{$startTagNgContainer}Container content: {$interpolation_1}{$closeTagNgContainer}", { "startTagNgTemplate": "\uFFFD*2:1\uFFFD", "closeTagNgTemplate": "\uFFFD/*2:1\uFFFD", "startTagNgContainer": "\uFFFD#3\uFFFD", "interpolation_1": "\uFFFD0\uFFFD", "closeTagNgContainer": "\uFFFD/#3\uFFFD", "interpolation": "\uFFFD0:1\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_702706566400598764$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{$startTagNgTemplate}Template content: {$interpolation}{$closeTagNgTemplate}{$startTagNgContainer}Container content: {$interpolation_1}{$closeTagNgContainer}", { "startTagNgTemplate": "\uFFFD*2:1\uFFFD", "closeTagNgTemplate": "\uFFFD/*2:1\uFFFD", "startTagNgContainer": "\uFFFD#3\uFFFD", "interpolation_1": "\uFFFD0\uFFFD", "closeTagNgContainer": "\uFFFD/#3\uFFFD", "interpolation": "\uFFFD0:1\uFFFD" }); } function MyComponent_ng_template_2_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18n(0, $I18N_0$, 1); $r3$.Δpipe(1, "uppercase"); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(1, 1, $ctx_r0$.valueA))); $r3$.Δi18nApply(0); } } … consts: 5, vars: 3, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_0$); $r3$.Δtemplate(2, MyComponent_ng_template_2_Template, 2, 3, "ng-template"); $r3$.ΔelementContainerStart(3); $r3$.Δpipe(4, "uppercase"); $r3$.ΔelementContainerEnd(); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(4, 1, ctx.valueB))); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should handle ICUs outside of translatable sections', () => { const input = ` {gender, select, male {male} female {female} other {other}} {age, select, 10 {ten} 20 {twenty} other {other}} `; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); $I18N_0$ = $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS__1$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_1$ = $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS__1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male} female {female} other {other}}"); } $I18N_1$ = $r3$.Δi18nPostprocess($I18N_1$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); function MyComponent_ng_template_0_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18n(0, $I18N_1$); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.gender)); $r3$.Δi18nApply(0); } } … consts: 3, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δtemplate(0, MyComponent_ng_template_0_Template, 1, 1, "ng-template"); $r3$.ΔelementContainerStart(1); $r3$.Δi18n(2, $I18N_0$); $r3$.ΔelementContainerEnd(); } if (rf & 2) { $r3$.Δselect(2); $r3$.Δi18nExp($r3$.Δbind(ctx.age)); $r3$.Δi18nApply(2); } } `; verify(input, output); }); it('should correctly propagate i18n context through nested templates', () => { const input = `
Template A: {{ valueA | uppercase }} Template B: {{ valueB }} Template C: {{ valueC }}
`; const output = String.raw ` function MyComponent_ng_template_2_ng_template_2_ng_template_1_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18n(0, $I18N_0$, 3); } if (rf & 2) { const $ctx_r2$ = $r3$.ΔnextContext(3); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($ctx_r2$.valueC)); $r3$.Δi18nApply(0); } } function MyComponent_ng_template_2_ng_template_2_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18nStart(0, $I18N_0$, 2); $r3$.Δtemplate(1, MyComponent_ng_template_2_ng_template_2_ng_template_1_Template, 1, 1, "ng-template"); $r3$.Δi18nEnd(); } if (rf & 2) { const $ctx_r1$ = $r3$.ΔnextContext(2); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($ctx_r1$.valueB)); $r3$.Δi18nApply(0); } } var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2051477021417799640$$APP_SPEC_TS_0$ = goog.getMsg("{$startTagNgTemplate} Template A: {$interpolation} {$startTagNgTemplate} Template B: {$interpolation_1} {$startTagNgTemplate} Template C: {$interpolation_2} {$closeTagNgTemplate}{$closeTagNgTemplate}{$closeTagNgTemplate}", { "startTagNgTemplate": "[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]", "closeTagNgTemplate": "[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]", "interpolation": "\uFFFD0:1\uFFFD", "interpolation_1": "\uFFFD0:2\uFFFD", "interpolation_2": "\uFFFD0:3\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_2051477021417799640$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{$startTagNgTemplate} Template A: {$interpolation} {$startTagNgTemplate} Template B: {$interpolation_1} {$startTagNgTemplate} Template C: {$interpolation_2} {$closeTagNgTemplate}{$closeTagNgTemplate}{$closeTagNgTemplate}", { "startTagNgTemplate": "[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]", "closeTagNgTemplate": "[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]", "interpolation": "\uFFFD0:1\uFFFD", "interpolation_1": "\uFFFD0:2\uFFFD", "interpolation_2": "\uFFFD0:3\uFFFD" }); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$); function MyComponent_ng_template_2_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18nStart(0, $I18N_0$, 1); $r3$.Δpipe(1, "uppercase"); $r3$.Δtemplate(2, MyComponent_ng_template_2_ng_template_2_Template, 2, 1, "ng-template"); $r3$.Δi18nEnd(); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($r3$.ΔpipeBind1(1, 1, $ctx_r0$.valueA))); $r3$.Δi18nApply(0); } } … consts: 3, vars: 0, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_0$); $r3$.Δtemplate(2, MyComponent_ng_template_2_Template, 3, 3, "ng-template"); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } } `; verify(input, output); }); it('should work with ICUs', () => { const input = ` {gender, select, male {male} female {female} other {other}} {age, select, 10 {ten} 20 {twenty} other {other}} `; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_0$ = $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male} female {female} other {other}}"); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__1$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); $I18N_1$ = $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); } $I18N_1$ = $r3$.Δi18nPostprocess($I18N_1$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); function MyComponent_ng_template_2_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18n(0, $I18N_1$); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.age)); $r3$.Δi18nApply(0); } } … consts: 3, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementContainerStart(0); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementContainerEnd(); $r3$.Δtemplate(2, MyComponent_ng_template_2_Template, 1, 1, "ng-template"); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should handle self-closing tags as content', () => { const input = ` is my logo #1 is my logo #2 `; const output = String.raw ` const $_c0$ = ["src", "logo.png", "title", "Logo"]; var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4891196282781544695$$APP_SPEC_TS_0$ = goog.getMsg("{$tagImg} is my logo #1 ", { "tagImg": "\uFFFD#2\uFFFD\uFFFD/#2\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_4891196282781544695$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{$tagImg} is my logo #1 ", { "tagImg": "\uFFFD#2\uFFFD\uFFFD/#2\uFFFD" }); } var $I18N_2$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_461986953980355147$$APP_SPEC_TS__2$ = goog.getMsg("{$tagImg} is my logo #2 ", { "tagImg": "\uFFFD#1\uFFFD\uFFFD/#1\uFFFD" }); $I18N_2$ = $MSG_EXTERNAL_461986953980355147$$APP_SPEC_TS__2$; } else { $I18N_2$ = $r3$.Δi18nLocalize("{$tagImg} is my logo #2 ", { "tagImg": "\uFFFD#1\uFFFD\uFFFD/#1\uFFFD" }); } function MyComponent_ng_template_3_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18nStart(0, $I18N_2$); $r3$.Δelement(1, "img", $_c0$); $r3$.Δi18nEnd(); } } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementContainerStart(0); $r3$.Δi18nStart(1, $I18N_0$); $r3$.Δelement(2, "img", $_c0$); $r3$.Δi18nEnd(); $r3$.ΔelementContainerEnd(); $r3$.Δtemplate(3, MyComponent_ng_template_3_Template, 2, 0, "ng-template"); } } `; verify(input, output); }); it('should not emit duplicate i18n consts for nested s', () => { const input = ` Root content Nested content `; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_8537814667662432133$$APP_SPEC_TS__0$ = goog.getMsg(" Root content {$startTagNgContainer} Nested content {$closeTagNgContainer}", { "startTagNgContainer": "\uFFFD*1:1\uFFFD\uFFFD#1:1\uFFFD", "closeTagNgContainer": "\uFFFD/#1:1\uFFFD\uFFFD/*1:1\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_8537814667662432133$$APP_SPEC_TS__0$; } else { $I18N_0$ = $r3$.Δi18nLocalize(" Root content {$startTagNgContainer} Nested content {$closeTagNgContainer}", { "startTagNgContainer": "\uFFFD*1:1\uFFFD\uFFFD#1:1\uFFFD", "closeTagNgContainer": "\uFFFD/#1:1\uFFFD\uFFFD/*1:1\uFFFD" }); } … `; verify(input, output); }); it('should not emit duplicate i18n consts for elements with the same content', () => { const input = `
Test
Test
`; // TODO(FW-635): currently we generate unique consts for each i18n block even though it might // contain the same content. This should be optimized by translation statements caching, that // can be implemented in the future within FW-635. const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_6563391987554512024$$APP_SPEC_TS_0$ = goog.getMsg("Test"); $I18N_0$ = $MSG_EXTERNAL_6563391987554512024$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("Test"); } var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_6563391987554512024$$APP_SPEC_TS_1$ = goog.getMsg("Test"); $I18N_1$ = $MSG_EXTERNAL_6563391987554512024$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("Test"); } … `; verify(input, output); }); }); describe('whitespace preserving mode', () => { it('should keep inner content of i18n block as is', () => { const input = `
Some text Text inside span
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$ = goog.getMsg("\n Some text\n {$startTagSpan}Text inside span{$closeTagSpan}\n ", { "startTagSpan": "\uFFFD#3\uFFFD", "closeTagSpan": "\uFFFD/#3\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("\n Some text\n {$startTagSpan}Text inside span{$closeTagSpan}\n ", { "startTagSpan": "\uFFFD#3\uFFFD", "closeTagSpan": "\uFFFD/#3\uFFFD" }); } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δtext(0, "\n "); $r3$.ΔelementStart(1, "div"); $r3$.Δi18nStart(2, $I18N_0$); $r3$.Δelement(3, "span"); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); $r3$.Δtext(4, "\n "); } } `; verify(input, output, {inputArgs: {preserveWhitespaces: true}}); }); }); describe('icu logic', () => { it('should handle single icus', () => { const input = `
{gender, select, male {male} female {female} other {other}}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_0$ = $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male} female {female} other {other}}"); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); … consts: 2, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should properly escape quotes in content', () => { const input = `
{gender, select, single {'single quotes'} double {"double quotes"} other {other}}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4166854826696768832$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, single {'single quotes'} double {\"double quotes\"} other {other}}"); $I18N_0$ = $MSG_EXTERNAL_4166854826696768832$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, single {'single quotes'} double {\"double quotes\"} other {other}}"); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); `; verify(input, output); }); it('should support ICU-only templates', () => { const input = ` {age, select, 10 {ten} 20 {twenty} other {other}} `; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); $I18N_0$ = $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); … consts: 1, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18n(0, $I18N_0$); } if (rf & 2) { $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind(ctx.age)); $r3$.Δi18nApply(0); } } `; verify(input, output); }); it('should generate i18n instructions for icus generated outside of i18n blocks', () => { const input = `
{gender, select, male {male} female {female} other {other}}
{age, select, 10 {ten} 20 {twenty} other {other}}
You have {count, select, 0 {no emails} 1 {one email} other {{{count}} emails}}.
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_0$ = $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male} female {female} other {other}}"); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); const $_c0$ = ["title", "icu only", ${AttributeMarker.Template}, "ngIf"]; const $_c1$ = ["title", "icu and text", ${AttributeMarker.Template}, "ngIf"]; const $_c2$ = ["title", "icu only"]; var $I18N_3$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); $I18N_3$ = $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$; } else { $I18N_3$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); } $I18N_3$ = $r3$.Δi18nPostprocess($I18N_3$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); function MyComponent_div_2_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div", $_c2$); $r3$.Δi18n(1, $I18N_3$); $r3$.ΔelementEnd(); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.age)); $r3$.Δi18nApply(1); } } const $_c3$ = ["title", "icu and text"]; var $I18N_5$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_1922743304863699161$$APP_SPEC_TS__5$ = goog.getMsg("{VAR_SELECT, select, 0 {no emails} 1 {one email} other {{$interpolation} emails}}", { "interpolation": "\uFFFD1\uFFFD" }); $I18N_5$ = $MSG_EXTERNAL_1922743304863699161$$APP_SPEC_TS__5$; } else { $I18N_5$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 0 {no emails} 1 {one email} other {{$interpolation} emails}}", { "interpolation": "\uFFFD1\uFFFD" }); } $I18N_5$ = $r3$.Δi18nPostprocess($I18N_5$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); function MyComponent_div_3_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div", $_c3$); $r3$.Δtext(1, " You have "); $r3$.Δi18n(2, $I18N_5$); $r3$.Δtext(3, ". "); $r3$.ΔelementEnd(); } if (rf & 2) { const $ctx_r1$ = $r3$.ΔnextContext(); $r3$.Δselect(2); $r3$.Δi18nExp($r3$.Δbind($ctx_r1$.count)); $r3$.Δi18nExp($r3$.Δbind($ctx_r1$.count)); $r3$.Δi18nApply(2); } } … consts: 4, vars: 3, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); $r3$.Δtemplate(2, MyComponent_div_2_Template, 2, 1, "div", $_c0$); $r3$.Δtemplate(3, MyComponent_div_3_Template, 4, 2, "div", $_c1$); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nApply(1); $r3$.Δselect(2); $r3$.Δproperty("ngIf", ctx.visible); $r3$.Δselect(3); $r3$.Δproperty("ngIf", ctx.available); } } `; verify(input, output); }); it('should support interpolation with custom interpolation config', () => { const input = `
{age, select, 10 {ten} 20 {twenty} other {{% other %}}}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2949673783721159566$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {{$interpolation}}}", { "interpolation": "\uFFFD1\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_2949673783721159566$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {{$interpolation}}}", { "interpolation": "\uFFFD1\uFFFD" }); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.age)); $r3$.Δi18nExp($r3$.Δbind(ctx.other)); $r3$.Δi18nApply(1); } } `; verify(input, output, {inputArgs: {interpolation: ['{%', '%}']}}); }); it('should handle icus with html', () => { const input = `
{gender, select, male {male - male} female {female female} other {
other
}} Other content
Another content
`; const output = String.raw ` var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2417296354340576868$$APP_SPEC_TS_1$ = goog.getMsg("{VAR_SELECT, select, male {male - {$startBoldText}male{$closeBoldText}} female {female {$startBoldText}female{$closeBoldText}} other {{$startTagDiv}{$startItalicText}other{$closeItalicText}{$closeTagDiv}}}", { "startBoldText": "", "closeBoldText": "", "startItalicText": "", "closeItalicText": "", "startTagDiv": "
", "closeTagDiv": "
" }); $I18N_1$ = $MSG_EXTERNAL_2417296354340576868$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male - {$startBoldText}male{$closeBoldText}} female {female {$startBoldText}female{$closeBoldText}} other {{$startTagDiv}{$startItalicText}other{$closeItalicText}{$closeTagDiv}}}", { "startBoldText": "", "closeBoldText": "", "startItalicText": "", "closeItalicText": "", "startTagDiv": "
", "closeTagDiv": "
" }); } $I18N_1$ = $r3$.Δi18nPostprocess($I18N_1$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); const $_c2$ = [1, "other"]; var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", { "startBoldText": "\uFFFD#2\uFFFD", "closeBoldText": "\uFFFD/#2\uFFFD", "startTagDiv": "\uFFFD#3\uFFFD", "startItalicText": "\uFFFD#4\uFFFD", "closeItalicText": "\uFFFD/#4\uFFFD", "closeTagDiv": "\uFFFD/#3\uFFFD", "icu": I18N_APP_SPEC_TS_1 }); $I18N_0$ = $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{$icu}{$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", { "startBoldText": "\uFFFD#2\uFFFD", "closeBoldText": "\uFFFD/#2\uFFFD", "startTagDiv": "\uFFFD#3\uFFFD", "startItalicText": "\uFFFD#4\uFFFD", "closeItalicText": "\uFFFD/#4\uFFFD", "closeTagDiv": "\uFFFD/#3\uFFFD", "icu": I18N_APP_SPEC_TS_1 }); } … consts: 5, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_0$); $r3$.Δelement(2, "b"); $r3$.ΔelementStart(3, "div"); $r3$.Δstyling($_c2$); $r3$.Δelement(4, "i"); $r3$.ΔelementEnd(); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should handle icus with expressions', () => { const input = `
{gender, select, male {male of age: {{ ageA + ageB + ageC }}} female {female} other {other}}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_6879461626778511059$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, male {male of age: {$interpolation}} female {female} other {other}}", { "interpolation": "\uFFFD1\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_6879461626778511059$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male of age: {$interpolation}} female {female} other {other}}", { "interpolation": "\uFFFD1\uFFFD" }); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); … consts: 2, vars: 2, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nExp($r3$.Δbind(((ctx.ageA + ctx.ageB) + ctx.ageC))); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should handle multiple icus in one block', () => { const input = `
{gender, select, male {male} female {female} other {other}} {age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}
`; const output = String.raw ` var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_1$ = $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male} female {female} other {other}}"); } $I18N_1$ = $r3$.Δi18nPostprocess($I18N_1$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); var $I18N_2$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS_2$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}"); $I18N_2$ = $MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS_2$; } else { $I18N_2$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}"); } $I18N_2$ = $r3$.Δi18nPostprocess($I18N_2$, { "VAR_SELECT": "\uFFFD1\uFFFD" }); var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$icu_1}", { "icu": $I18N_1$, "icu_1": $I18N_2$ }); $I18N_0$ = $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{$icu}{$icu_1}", { "icu": $I18N_1$, "icu_1": $I18N_2$ }); } … consts: 2, vars: 2, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nExp($r3$.Δbind(ctx.age)); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should handle multiple icus that share same placeholder', () => { const input = `
{gender, select, male {male} female {female} other {other}}
{gender, select, male {male} female {female} other {other}}
{gender, select, male {male} female {female} other {other}}
`; const output = String.raw ` var $I18N_1$; if (ngI18nClosureMode) { const $MSG_APP_SPEC_TS_1$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_1$ = $MSG_APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male} female {female} other {other}}"); } $I18N_1$ = $r3$.Δi18nPostprocess($I18N_1$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); var $I18N_2$; if (ngI18nClosureMode) { const $MSG_APP_SPEC_TS_2$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_2$ = $MSG_APP_SPEC_TS_2$; } else { $I18N_2$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male} female {female} other {other}}"); } $I18N_2$ = $r3$.Δi18nPostprocess($I18N_2$, { "VAR_SELECT": "\uFFFD1\uFFFD" }); const $_c3$ = [${AttributeMarker.Template}, "ngIf"]; var $I18N_4$; if (ngI18nClosureMode) { const $MSG_APP_SPEC_TS__4$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_4$ = $MSG_APP_SPEC_TS__4$; } else { $I18N_4$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male} female {female} other {other}}"); } $I18N_4$ = $r3$.Δi18nPostprocess($I18N_4$, { "VAR_SELECT": "\uFFFD0:1\uFFFD" }); var $I18N_0$; if (ngI18nClosureMode) { const $MSG_APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagDiv}{$icu}{$closeTagDiv}{$startTagDiv_1}{$icu}{$closeTagDiv}", { "startTagDiv": "\uFFFD#2\uFFFD", "closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]", "startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD", "icu": "\uFFFDI18N_EXP_ICU\uFFFD" }); $I18N_0$ = $MSG_APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{$icu}{$startTagDiv}{$icu}{$closeTagDiv}{$startTagDiv_1}{$icu}{$closeTagDiv}", { "startTagDiv": "\uFFFD#2\uFFFD", "closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]", "startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD", "icu": "\uFFFDI18N_EXP_ICU\uFFFD" }); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "ICU": [$I18N_1$, $I18N_2$, $I18N_4$] }); function MyComponent_div_3_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18nStart(0, $I18N_0$, 1); $r3$.Δelement(1, "div"); $r3$.Δi18nEnd(); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.gender)); $r3$.Δi18nApply(0); } } … consts: 4, vars: 3, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_0$); $r3$.Δelement(2, "div"); $r3$.Δtemplate(3, MyComponent_div_3_Template, 2, 1, "div", $_c3$); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(3); $r3$.Δproperty("ngIf", ctx.visible); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nApply(1); } } `; // TODO(akushnir): this use-case is currently supported with // file-based prefix for translation const names. Translation statements // caching is required to support this use-case (FW-635) with id-based consts. verify(input, output, {skipIdBasedCheck: true}); }); it('should handle nested icus', () => { const input = `
{gender, select, male {male of age: {age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other} }
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}"); $I18N_0$ = $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}"); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD", "VAR_SELECT_1": "\uFFFD1\uFFFD" }); … consts: 2, vars: 2, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.age)); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nApply(1); } } `; const exceptions = { '3052001905251380936': 'Wrapper message generated by "ng xi18n" around ICU: " {$ICU} "' }; verify(input, output, {exceptions}); }); it('should handle icus in different contexts', () => { const input = `
{gender, select, male {male} female {female} other {other}} {age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}
`; const output = String.raw ` var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_1$ = $MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male} female {female} other {other}}"); } $I18N_1$ = $r3$.Δi18nPostprocess($I18N_1$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); const $_c2$ = [${AttributeMarker.Template}, "ngIf"]; var $I18N_3$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}"); $I18N_3$ = $MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$; } else { $I18N_3$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}"); } $I18N_3$ = $r3$.Δi18nPostprocess($I18N_3$, { "VAR_SELECT": "\uFFFD0:1\uFFFD" }); var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", { "startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD", "closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD", "icu": $I18N_1$, "icu_1": $I18N_3$ }); $I18N_0$ = $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", { "startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD", "closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD", "icu": $I18N_1$, "icu_1": $I18N_3$ }); } function MyComponent_span_2_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18nStart(0, $I18N_0$, 1); $r3$.Δelement(1, "span"); $r3$.Δi18nEnd(); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.age)); $r3$.Δi18nApply(0); } } … consts: 3, vars: 2, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_0$); $r3$.Δtemplate(2, MyComponent_span_2_Template, 2, 1, "span", $_c2$); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(2); $r3$.Δproperty("ngIf", ctx.ageVisible); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should handle icus with interpolations', () => { const input = `
{gender, select, male {male {{ weight }}} female {female {{ height }}} other {other}} {age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {{ otherAge }}}}
`; const output = String.raw ` var $I18N_1$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$ = goog.getMsg("{VAR_SELECT, select, male {male {$interpolation}} female {female {$interpolation_1}} other {other}}", { "interpolation": "\uFFFD1\uFFFD", "interpolation_1": "\uFFFD2\uFFFD" }); $I18N_1$ = $MSG_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$; } else { $I18N_1$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male {$interpolation}} female {female {$interpolation_1}} other {other}}", { "interpolation": "\uFFFD1\uFFFD", "interpolation_1": "\uFFFD2\uFFFD" }); } $I18N_1$ = $r3$.Δi18nPostprocess($I18N_1$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); const $_c0$ = [${AttributeMarker.Template}, "ngIf"]; var $I18N_3$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_2310343208266678305$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {$interpolation}}}", { "interpolation": "\uFFFD1:1\uFFFD" }); $I18N_3$ = $MSG_EXTERNAL_2310343208266678305$$APP_SPEC_TS__3$; } else { $I18N_3$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {$interpolation}}}", { "interpolation": "\uFFFD1:1\uFFFD" }); } $I18N_3$ = $r3$.Δi18nPostprocess($I18N_3$, { "VAR_SELECT": "\uFFFD0:1\uFFFD" }); var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", { "startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD", "closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD", "icu": $I18N_1$, "icu_1": $I18N_3$ }); $I18N_0$ = $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", { "startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD", "closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD", "icu": $I18N_1$, "icu_1": $I18N_3$ }); } function MyComponent_span_2_Template(rf, ctx) { if (rf & 1) { $r3$.Δi18nStart(0, $I18N_0$, 1); $r3$.Δelement(1, "span"); $r3$.Δi18nEnd(); } if (rf & 2) { const $ctx_r0$ = $r3$.ΔnextContext(); $r3$.Δselect(0); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.age)); $r3$.Δi18nExp($r3$.Δbind($ctx_r0$.otherAge)); $r3$.Δi18nApply(0); } } … consts: 3, vars: 4, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18nStart(1, $I18N_0$); $r3$.Δtemplate(2, MyComponent_span_2_Template, 2, 2, "span", $_c2$); $r3$.Δi18nEnd(); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(2); $r3$.Δproperty("ngIf", ctx.ageVisible); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nExp($r3$.Δbind(ctx.weight)); $r3$.Δi18nExp($r3$.Δbind(ctx.height)); $r3$.Δi18nApply(1); } } `; verify(input, output); }); it('should handle icus with named interpolations', () => { const input = `
{ gender, select, male {male {{ weight // i18n(ph="PH_A") }}} female {female {{ height // i18n(ph="PH_B") }}} other {other {{ age // i18n(ph="PH_C") }}} }
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { const $MSG_EXTERNAL_4853189513362404940$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, male {male {$phA}} female {female {$phB}} other {other {$phC}}}", { "phA": "\uFFFD1\uFFFD", "phB": "\uFFFD2\uFFFD", "phC": "\uFFFD3\uFFFD" }); $I18N_0$ = $MSG_EXTERNAL_4853189513362404940$$APP_SPEC_TS_0$; } else { $I18N_0$ = $r3$.Δi18nLocalize("{VAR_SELECT, select, male {male {$phA}} female {female {$phB}} other {other {$phC}}}", { "phA": "\uFFFD1\uFFFD", "phB": "\uFFFD2\uFFFD", "phC": "\uFFFD3\uFFFD" }); } $I18N_0$ = $r3$.Δi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" }); … consts: 2, vars: 4, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ΔelementStart(0, "div"); $r3$.Δi18n(1, $I18N_0$); $r3$.ΔelementEnd(); } if (rf & 2) { $r3$.Δselect(1); $r3$.Δi18nExp($r3$.Δbind(ctx.gender)); $r3$.Δi18nExp($r3$.Δbind(ctx.weight)); $r3$.Δi18nExp($r3$.Δbind(ctx.height)); $r3$.Δi18nExp($r3$.Δbind(ctx.age)); $r3$.Δi18nApply(1); } } `; verify(input, output); }); }); describe('errors', () => { it('should throw on nested i18n sections', () => { const files = getAppFilesWithTemplate(`
Some content
`); expect(() => compile(files, angularFiles)) .toThrowError( 'Could not mark an element as translatable inside of a translatable section'); }); }); });