From f00d03356f14293131e7a6aea2cb7c570b3bb5e9 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Sat, 24 Aug 2019 14:02:30 -0700 Subject: [PATCH] fix(ivy): handle expressions in i18n attributes properly (#32309) Prior to this commit, complex expressions (that require additional statements to be generated) were handled incorrectly in case they were used in attributes annotated with i18n flags. The problem was caused by the fact that extra statements were not appended to the temporary vars block, so they were missing in generated code. This commit updated the logic to use the `convertPropertyBinding`, which contains the necessary code to append extra statements. The `convertExpressionBinding` function was removed as it duplicates the `convertPropertyBinding` one (for the most part) and is no longer used. PR Close #32309 --- .../compliance/r3_view_compiler_i18n_spec.ts | 59 ++++++++++++++++--- .../compiler/src/render3/view/template.ts | 9 +-- packages/core/test/acceptance/i18n_spec.ts | 46 ++++++++++++--- 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts index 0250295795..2261a57782 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts @@ -596,6 +596,46 @@ describe('i18n support in the template compiler', () => { verify(input, output); }); + it('should support complex expressions in interpolation', () => { + const input = ` +
+ `; + + const output = String.raw ` + const $_c0$ = [${AttributeMarker.I18n}, "title"]; + var $I18N_1$; + if (ngI18nClosureMode) { + const $MSG_EXTERNAL_3462388422673575127$$APP_SPEC_TS_2$ = goog.getMsg("{$interpolation} title", { + "interpolation": "\uFFFD0\uFFFD" + }); + $I18N_1$ = $MSG_EXTERNAL_3462388422673575127$$APP_SPEC_TS_2$; + } + else { + $I18N_1$ = $localize \`$` + + String.raw `{"\uFFFD0\uFFFD"}:interpolation: title\`; + } + const $_c3$ = ["title", $I18N_1$]; + … + consts: 2, + vars: 1, + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelementStart(0, "div", $_c0$); + $r3$.ɵɵi18nAttributes(1, $_c3$); + $r3$.ɵɵelementEnd(); + } + if (rf & 2) { + var $tmp_0_0$ = null; + const $currVal_0$ = ($tmp_0_0$ = ctx.valueA.getRawValue()) == null ? null : $tmp_0_0$.getTitle(); + $r3$.ɵɵi18nExp($currVal_0$); + $r3$.ɵɵi18nApply(1); + } + } + `; + + verify(input, output); + }); + it('should support interpolation', () => { const input = `
{
{{ valueA | async }} {{ valueA?.a?.b }} + {{ valueA.getRawValue()?.getTitle() }}
`; const output = String.raw ` var $I18N_0$; if (ngI18nClosureMode) { - const $MSG_EXTERNAL_1482713963707913023$$APP_SPEC_TS_0$ = goog.getMsg(" {$interpolation} {$interpolation_1} ", { + const $MSG_EXTERNAL_5146016486383316049$$APP_SPEC_TS_1$ = goog.getMsg(" {$interpolation} {$interpolation_1} {$interpolation_2} ", { "interpolation": "\uFFFD0\uFFFD", - "interpolation_1": "\uFFFD1\uFFFD" + "interpolation_1": "\uFFFD1\uFFFD", + "interpolation_2": "\uFFFD2\uFFFD" }); - $I18N_0$ = $MSG_EXTERNAL_1482713963707913023$$APP_SPEC_TS_0$; + $I18N_0$ = $MSG_EXTERNAL_5146016486383316049$$APP_SPEC_TS_1$; } else { $I18N_0$ = $localize \` $` + String.raw `{"\uFFFD0\uFFFD"}:interpolation: $` + - String.raw `{"\uFFFD1\uFFFD"}:interpolation_1: \`; + String.raw `{"\uFFFD1\uFFFD"}:interpolation_1: $` + + String.raw `{"\uFFFD2\uFFFD"}:interpolation_2: \`; } … template: function MyComponent_Template(rf, ctx) { @@ -1081,8 +1124,10 @@ describe('i18n support in the template compiler', () => { $r3$.ɵɵelementEnd(); } if (rf & 2) { + var $tmp_2_0$ = null; + const $currVal_2$ = ($tmp_2_0$ = ctx.valueA.getRawValue()) == null ? null : $tmp_2_0$.getTitle(); $r3$.ɵɵselect(1); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(2, 2, ctx.valueA))(ctx.valueA == null ? null : ctx.valueA.a == null ? null : ctx.valueA.a.b); + $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(2, 3, ctx.valueA))(ctx.valueA == null ? null : ctx.valueA.a == null ? null : ctx.valueA.a.b)($currVal_2$); $r3$.ɵɵi18nApply(1); } } @@ -2615,9 +2660,9 @@ describe('i18n support in the template compiler', () => { function MyComponent_div_2_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div", $_c2$); - i0.ɵɵtext(1, " "); + $r3$.ɵɵtext(1, " "); $r3$.ɵɵi18n(2, $I18N_3$); - i0.ɵɵtext(3, " "); + $r3$.ɵɵtext(3, " "); $r3$.ɵɵelementEnd(); } if (rf & 2) { diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index cf00bc8269..af1c151d6a 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -664,7 +664,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver hasBindings = true; bindings.push({ sourceSpan: element.sourceSpan, - value: () => this.convertExpressionBinding(expression) + value: () => this.convertPropertyBinding(expression) }); }); } @@ -1155,19 +1155,12 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this._bindingScope.getOrCreateSharedContextVar(0); } - private convertExpressionBinding(value: AST): o.Expression { - const convertedPropertyBinding = convertPropertyBinding( - this, this.getImplicitReceiverExpr(), value, this.bindingContext(), BindingForm.TrySimple); - return convertedPropertyBinding.currValExpr; - } - private convertPropertyBinding(value: AST): o.Expression { const convertedPropertyBinding = convertPropertyBinding( this, this.getImplicitReceiverExpr(), value, this.bindingContext(), BindingForm.TrySimple, () => error('Unexpected interpolation')); const valExpr = convertedPropertyBinding.currValExpr; this._tempVariables.push(...convertedPropertyBinding.stmts); - return valExpr; } diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index 13ff777e95..fccbdf8519 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -75,14 +75,27 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { }); it('should support interpolations with complex expressions', () => { - loadTranslations( - {'{$interpolation} - {$interpolation_1}': '{$interpolation} - {$interpolation_1} (fr)'}); - const fixture = - initWithTemplate(AppComp, `
{{ name | uppercase }} - {{ obj?.a?.b }}
`); - expect(fixture.nativeElement.innerHTML).toEqual(`
ANGULAR - (fr)
`); - fixture.componentRef.instance.obj = {a: {b: 'value'}}; + loadTranslations({ + ' {$interpolation} - {$interpolation_1} - {$interpolation_2} ': + ' {$interpolation} - {$interpolation_1} - {$interpolation_2} (fr) ' + }); + const fixture = initWithTemplate(AppComp, ` +
+ {{ name | uppercase }} - + {{ obj?.a?.b }} - + {{ obj?.getA()?.b }} +
+ `); + // the `obj` field is not yet defined, so 2nd and 3rd interpolations return empty strings + expect(fixture.nativeElement.innerHTML).toEqual(`
ANGULAR - - (fr)
`); + + fixture.componentRef.instance.obj = { + a: {b: 'value 1'}, + getA: () => ({b: 'value 2'}), + }; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual(`
ANGULAR - value (fr)
`); + expect(fixture.nativeElement.innerHTML) + .toEqual(`
ANGULAR - value 1 - value 2 (fr)
`); }); it('should support elements', () => { @@ -1057,6 +1070,25 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.debugElement.children[0].children[0].references.ref.test).toBe('Set'); expect(fixture.debugElement.children[1].children[0].references.ref.test).toBe('Set'); }); + + it('with complex expressions', () => { + loadTranslations({ + '{$interpolation} - {$interpolation_1} - {$interpolation_2}': + '{$interpolation} - {$interpolation_1} - {$interpolation_2} (fr)' + }); + const fixture = initWithTemplate(AppComp, ` +
+ `); + // the `obj` field is not yet defined, so 2nd and 3rd interpolations return empty strings + expect(fixture.nativeElement.firstChild.title).toEqual(`ANGULAR - - (fr)`); + + fixture.componentRef.instance.obj = { + a: {b: 'value 1'}, + getA: () => ({b: 'value 2'}), + }; + fixture.detectChanges(); + expect(fixture.nativeElement.firstChild.title).toEqual(`ANGULAR - value 1 - value 2 (fr)`); + }); }); it('should work with directives and host bindings', () => {