Skip to content

Commit

Permalink
fix(ivy): handle expressions in i18n attributes properly (angular#32309)
Browse files Browse the repository at this point in the history
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 angular#32309
  • Loading branch information
AndrewKushnir authored and matsko committed Sep 5, 2019
1 parent 36d613d commit f00d033
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,46 @@ describe('i18n support in the template compiler', () => {
verify(input, output);
});

it('should support complex expressions in interpolation', () => {
const input = `
<div i18n-title title="{{valueA.getRawValue()?.getTitle()}} title"></div>
`;

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 = `
<div id="dynamic-1"
Expand Down Expand Up @@ -1055,22 +1095,25 @@ describe('i18n support in the template compiler', () => {
<div i18n>
{{ valueA | async }}
{{ valueA?.a?.b }}
{{ valueA.getRawValue()?.getTitle() }}
</div>
`;

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) {
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 1 addition & 8 deletions packages/compiler/src/render3/view/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
hasBindings = true;
bindings.push({
sourceSpan: element.sourceSpan,
value: () => this.convertExpressionBinding(expression)
value: () => this.convertPropertyBinding(expression)
});
});
}
Expand Down Expand Up @@ -1155,19 +1155,12 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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;
}

Expand Down
46 changes: 39 additions & 7 deletions packages/core/test/acceptance/i18n_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, `<div i18n>{{ name | uppercase }} - {{ obj?.a?.b }}</div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div>ANGULAR - (fr)</div>`);
fixture.componentRef.instance.obj = {a: {b: 'value'}};
loadTranslations({
' {$interpolation} - {$interpolation_1} - {$interpolation_2} ':
' {$interpolation} - {$interpolation_1} - {$interpolation_2} (fr) '
});
const fixture = initWithTemplate(AppComp, `
<div i18n>
{{ name | uppercase }} -
{{ obj?.a?.b }} -
{{ obj?.getA()?.b }}
</div>
`);
// the `obj` field is not yet defined, so 2nd and 3rd interpolations return empty strings
expect(fixture.nativeElement.innerHTML).toEqual(`<div> ANGULAR - - (fr) </div>`);

fixture.componentRef.instance.obj = {
a: {b: 'value 1'},
getA: () => ({b: 'value 2'}),
};
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual(`<div>ANGULAR - value (fr)</div>`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div> ANGULAR - value 1 - value 2 (fr) </div>`);
});

it('should support elements', () => {
Expand Down Expand Up @@ -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, `
<div i18n-title title="{{ name | uppercase }} - {{ obj?.a?.b }} - {{ obj?.getA()?.b }}"></div>
`);
// 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', () => {
Expand Down

0 comments on commit f00d033

Please sign in to comment.