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 61539b698b..1186e84357 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
@@ -764,6 +764,37 @@ describe('i18n support in the view compiler', () => {
verify(input, output, {inputArgs: {interpolation: ['{%', '%}']}});
});
+ it('should support interpolations with complex expressions', () => {
+ const input = `
+
+ {{ valueA | async }}
+ {{ valueA?.a?.b }}
+
+ `;
+
+ const output = String.raw `
+ const $MSG_EXTERNAL_1482713963707913023$$APP_SPEC_TS_0$ = goog.getMsg(" {$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, $MSG_EXTERNAL_1482713963707913023$$APP_SPEC_TS_0$);
+ $r3$.ɵpipe(2, "async");
+ $r3$.ɵelementEnd();
+ }
+ if (rf & 2) {
+ $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 }}
@@ -800,7 +831,7 @@ describe('i18n support in the view compiler', () => {
if (rf & 2) {
$r3$.ɵi18nExp($r3$.ɵbind(ctx.one));
$r3$.ɵi18nApply(1);
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 0, ctx.two)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 3, ctx.two)));
$r3$.ɵi18nApply(3);
$r3$.ɵi18nExp($r3$.ɵbind(((ctx.three + ctx.four) + ctx.five)));
$r3$.ɵi18nApply(6);
@@ -868,7 +899,7 @@ describe('i18n support in the view compiler', () => {
if (rf & 2) {
$r3$.ɵi18nExp($r3$.ɵbind(ctx.one));
$r3$.ɵi18nApply(1);
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(5, 0, ctx.two)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(5, 3, ctx.two)));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.nestedInBlockTwo));
$r3$.ɵi18nApply(4);
}
@@ -941,7 +972,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵi18nApply(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueE));
$r3$.ɵi18nApply(8);
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(6, 0, ctx.valueD)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(6, 5, ctx.valueD)));
$r3$.ɵi18nApply(5);
}
}
@@ -988,7 +1019,7 @@ describe('i18n support in the view compiler', () => {
if (rf & 2) {
const $ctx_r0$ = $r3$.ɵnextContext();
$r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA));
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 0, $ctx_r0$.valueB)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 2, $ctx_r0$.valueB)));
$r3$.ɵi18nApply(2);
}
}
@@ -1118,7 +1149,7 @@ describe('i18n support in the view compiler', () => {
const $ctx_r0$ = $r3$.ɵnextContext();
$r3$.ɵelementProperty(4, "ngIf", $r3$.ɵbind($ctx_r0$.exists));
$r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA));
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(3, 0, $ctx_r0$.valueB)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(3, 3, $ctx_r0$.valueB)));
$r3$.ɵi18nApply(0);
}
}
@@ -1148,7 +1179,7 @@ describe('i18n support in the view compiler', () => {
if (rf & 2) {
const $ctx_r1$ = $r3$.ɵnextContext();
$r3$.ɵi18nExp($r3$.ɵbind(($ctx_r1$.valueE + $ctx_r1$.valueF)));
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(3, 0, $ctx_r1$.valueG)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(3, 2, $ctx_r1$.valueG)));
$r3$.ɵi18nApply(0);
}
}
@@ -1347,7 +1378,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementContainerEnd();
}
if (rf & 2) {
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 0, ctx.valueA)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 1, ctx.valueA)));
$r3$.ɵi18nApply(1);
}
}
@@ -1371,7 +1402,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵpipe(1, "uppercase");
} if (rf & 2) {
const $ctx_r0$ = $r3$.ɵnextContext();
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 0, $ctx_r0$.valueA)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 1, $ctx_r0$.valueA)));
$r3$.ɵi18nApply(0);
}
}
@@ -1412,7 +1443,7 @@ describe('i18n support in the view compiler', () => {
}
if (rf & 2) {
const $ctx_r0$ = $r3$.ɵnextContext();
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 0, $ctx_r0$.valueA)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 1, $ctx_r0$.valueA)));
$r3$.ɵi18nApply(0);
}
}
@@ -1431,7 +1462,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd();
}
if (rf & 2) {
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 0, ctx.valueB)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 1, ctx.valueB)));
$r3$.ɵi18nApply(1);
}
}
@@ -1540,7 +1571,7 @@ describe('i18n support in the view compiler', () => {
}
if (rf & 2) {
const $ctx_r0$ = $r3$.ɵnextContext();
- $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 0, $ctx_r0$.valueA)));
+ $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 1, $ctx_r0$.valueA)));
$r3$.ɵi18nApply(0);
}
}
diff --git a/packages/compiler/src/render3/view/i18n/context.ts b/packages/compiler/src/render3/view/i18n/context.ts
index 6be118d8d3..d2573d6f4c 100644
--- a/packages/compiler/src/render3/view/i18n/context.ts
+++ b/packages/compiler/src/render3/view/i18n/context.ts
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
+import {AST} from '../../../expression_parser/ast';
import * as i18n from '../../../i18n/i18n_ast';
import * as o from '../../../output/output_ast';
@@ -40,7 +41,7 @@ function setupRegistry() {
*/
export class I18nContext {
public readonly id: number;
- public bindings = new Set();
+ public bindings = new Set();
public placeholders = new Map();
public isEmitted: boolean = false;
@@ -75,7 +76,7 @@ export class I18nContext {
}
// public API to accumulate i18n-related content
- appendBinding(binding: o.Expression) { this.bindings.add(binding); }
+ appendBinding(binding: AST) { this.bindings.add(binding); }
appendIcu(name: string, ref: o.Expression) {
updatePlaceholderMap(this._registry.icus, name, ref);
}
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index e5048daf71..d7c26c8e03 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -329,12 +329,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
}
i18nAppendBindings(expressions: AST[]) {
- if (!this.i18n || !expressions.length) return;
- const implicit = o.variable(CONTEXT_NAME);
- expressions.forEach(expression => {
- const binding = this.convertExpressionBinding(implicit, expression);
- this.i18n !.appendBinding(binding);
- });
+ if (expressions.length > 0) {
+ expressions.forEach(expression => this.i18n !.appendBinding(expression));
+ }
}
i18nBindProps(props: {[key: string]: t.Text | t.BoundText}): {[key: string]: o.Expression} {
@@ -452,7 +449,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// setup accumulated bindings
const {index, bindings} = this.i18n;
if (bindings.size) {
- bindings.forEach(binding => this.updateInstruction(span, R3.i18nExp, [binding]));
+ bindings.forEach(binding => {
+ this.updateInstruction(
+ span, R3.i18nExp,
+ () => [this.convertPropertyBinding(o.variable(CONTEXT_NAME), binding)]);
+ });
this.updateInstruction(span, R3.i18nApply, [o.literal(index)]);
}
if (!selfClosing) {
diff --git a/packages/compiler/test/render3/view/i18n_spec.ts b/packages/compiler/test/render3/view/i18n_spec.ts
index 62588c98da..8acdd6fc41 100644
--- a/packages/compiler/test/render3/view/i18n_spec.ts
+++ b/packages/compiler/test/render3/view/i18n_spec.ts
@@ -6,6 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
+import {AST} from '../../../src/expression_parser/ast';
+import {Lexer} from '../../../src/expression_parser/lexer';
+import {Parser} from '../../../src/expression_parser/parser';
import * as i18n from '../../../src/i18n/i18n_ast';
import * as o from '../../../src/output/output_ast';
import * as t from '../../../src/render3/r3_ast';
@@ -15,6 +18,7 @@ import {I18nMeta, formatI18nPlaceholderName, parseI18nMeta} from '../../../src/r
import {parseR3 as parse} from './util';
+const expressionParser = new Parser(new Lexer());
const i18nOf = (element: t.Node & {i18n?: i18n.AST}) => element.i18n !;
describe('I18nContext', () => {
@@ -44,8 +48,8 @@ describe('I18nContext', () => {
// binding collection checks
expect(ctx.bindings.size).toBe(0);
- ctx.appendBinding(o.literal(1));
- ctx.appendBinding(o.literal(2));
+ ctx.appendBinding(expressionParser.parseInterpolation('{{ valueA }}', '') as AST);
+ ctx.appendBinding(expressionParser.parseInterpolation('{{ valueB }}', '') as AST);
expect(ctx.bindings.size).toBe(2);
});
@@ -72,7 +76,7 @@ describe('I18nContext', () => {
// set data for root ctx
ctx.appendBoundText(i18nOf(boundTextA));
- ctx.appendBinding(o.literal('valueA'));
+ ctx.appendBinding(expressionParser.parseInterpolation('{{ valueA }}', '') as AST);
ctx.appendElement(i18nOf(elementA), 0);
ctx.appendTemplate(i18nOf(templateA), 1);
ctx.appendElement(i18nOf(elementA), 0, true);
@@ -88,11 +92,11 @@ describe('I18nContext', () => {
// set data for child context
childCtx.appendElement(i18nOf(elementB), 0);
childCtx.appendBoundText(i18nOf(boundTextB));
- childCtx.appendBinding(o.literal('valueB'));
+ childCtx.appendBinding(expressionParser.parseInterpolation('{{ valueB }}', '') as AST);
childCtx.appendElement(i18nOf(elementC), 1);
childCtx.appendElement(i18nOf(elementC), 1, true);
childCtx.appendBoundText(i18nOf(boundTextC));
- childCtx.appendBinding(o.literal('valueC'));
+ childCtx.appendBinding(expressionParser.parseInterpolation('{{ valueC }}', '') as AST);
childCtx.appendElement(i18nOf(elementB), 0, true);
expect(childCtx.bindings.size).toBe(2);
diff --git a/packages/core/test/i18n_integration_spec.ts b/packages/core/test/i18n_integration_spec.ts
index 06f51e631d..7a9e297d39 100644
--- a/packages/core/test/i18n_integration_spec.ts
+++ b/packages/core/test/i18n_integration_spec.ts
@@ -23,6 +23,7 @@ class DirectiveWithTplRef {
class MyComp {
name = 'John';
items = ['1', '2', '3'];
+ obj = {a: {b: 'value'}};
visible = true;
age = 20;
count = 2;
@@ -42,6 +43,7 @@ const TRANSLATIONS: any = {
'Item {$interpolation}': 'Article {$interpolation}',
'\'Single quotes\' and "Double quotes"': '\'Guillemets simples\' et "Guillemets doubles"',
'My logo': 'Mon logo',
+ '{$interpolation} - {$interpolation_1}': '{$interpolation} - {$interpolation_1} (fr)',
'{$startTagSpan}My logo{$tagImg}{$closeTagSpan}':
'{$startTagSpan}Mon logo{$tagImg}{$closeTagSpan}',
'{$startTagNgTemplate} Hello {$closeTagNgTemplate}{$startTagNgContainer} Bye {$closeTagNgContainer}':
@@ -175,6 +177,13 @@ onlyInIvy('Ivy i18n logic').describe('i18n', function() {
expect(element).toHaveText('Bonjour John');
});
+ it('should support interpolations with complex expressions', () => {
+ const template = `{{ name | uppercase }} - {{ obj?.a?.b }}
`;
+ const fixture = getFixtureWithOverrides({template});
+ const element = fixture.nativeElement.firstChild;
+ expect(element).toHaveText('JOHN - value (fr)');
+ });
+
it('should properly escape quotes in content', () => {
const content = `'Single quotes' and "Double quotes"`;
const template = `${content}
`;