', 'interpolation_2': '{{ valueC }}', 'interpolation_3': '{{ valueD }}', 'interpolation_4': '{{ valueE + valueF }}', 'interpolation_5': '{{ valueG | uppercase }}'}}, {}, [])
return [
$i18n_0$,
[__AttributeMarker.Template__, "ngIf"]
@@ -72,4 +72,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵproperty("ngIf", !ctx.visible);
}
-}
\ No newline at end of file
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/plain_text_messages.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/plain_text_messages.js
index 58525804563..f72e20b4ed6 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/plain_text_messages.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/plain_text_messages.js
@@ -1,7 +1,7 @@
consts: function() {
- __i18nMsg__('My i18n block #1', [], {})
- __i18nMsg__('My i18n block #2', [], {})
- __i18nMsg__('My i18n block #3', [], {})
+ __i18nMsg__('My i18n block #1', [], {}, {})
+ __i18nMsg__('My i18n block #2', [], {}, {})
+ __i18nMsg__('My i18n block #3', [], {}, {})
return [
$i18n_0$,
$i18n_1$,
@@ -26,4 +26,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18n(9, 2);
$r3$.ɵɵelementEnd();
}
-}
\ No newline at end of file
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/self_closing.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/self_closing.js
index e7e404a0a2a..e42014d365f 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/self_closing.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/self_closing.js
@@ -20,7 +20,7 @@ function MyComponent_img_2_Template(rf, ctx) {
decls: 3,
vars: 2,
consts: function() {
- __i18nMsg__('App logo #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
+ __i18nMsg__('App logo #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ id }}'}}, {})
return [
["src", "logo.png"],
["src", "logo.png", __AttributeMarker.Template__, "ngIf"],
@@ -42,4 +42,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵproperty("ngIf", ctx.visible);
}
-}
\ No newline at end of file
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/bare_icus.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/bare_icus.js
index e619ab50687..8345bfb1e3d 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/bare_icus.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/bare_icus.js
@@ -12,8 +12,8 @@ function MyComponent_ng_template_0_Template(rf, ctx) {
decls: 3,
vars: 1,
consts: function() {
- __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
- __i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
+ __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
+ __i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
return [
$i18n_0$,
$i18n_1$
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/child_elements.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/child_elements.js
index 6d76814a546..ab8c79c2168 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/child_elements.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/child_elements.js
@@ -14,7 +14,7 @@ function MyComponent_ng_template_2_Template(rf, ctx) {
decls: 5,
vars: 3,
consts: function() {
- __i18nMsg__('{$startTagNgTemplate}Template content: {$interpolation}{$closeTagNgTemplate}{$startTagNgContainer}Container content: {$interpolation_1}{$closeTagNgContainer}', [['startTagNgTemplate', String.raw`\uFFFD*2:1\uFFFD`], ['closeTagNgTemplate', String.raw`\uFFFD/*2:1\uFFFD`], ['startTagNgContainer', String.raw`\uFFFD#3\uFFFD`], ['interpolation_1', String.raw`\uFFFD0\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#3\uFFFD`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`]], {})
+ __i18nMsg__('{$startTagNgTemplate}Template content: {$interpolation}{$closeTagNgTemplate}{$startTagNgContainer}Container content: {$interpolation_1}{$closeTagNgContainer}', [['startTagNgTemplate', String.raw`\uFFFD*2:1\uFFFD`], ['closeTagNgTemplate', String.raw`\uFFFD/*2:1\uFFFD`], ['startTagNgContainer', String.raw`\uFFFD#3\uFFFD`], ['interpolation_1', String.raw`\uFFFD0\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#3\uFFFD`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`]], {original_code: {'startTagNgTemplate': '
', 'closeTagNgTemplate': '', 'startTagNgContainer': '
', 'interpolation_1': '{{ valueB | uppercase }}', 'closeTagNgContainer': '', 'interpolation': '{{ valueA | uppercase }}'}}, {})
return [
$i18n_0$
];
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/duplicate_content.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/duplicate_content.js
index 0ad3c81e57c..8dbb61d3e99 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/duplicate_content.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/duplicate_content.js
@@ -1,4 +1,4 @@
// NOTE: 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.
-__i18nMsg__('Test', [], {})
-__i18nMsg__('Test', [], {})
+__i18nMsg__('Test', [], {}, {})
+__i18nMsg__('Test', [], {}, {})
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/icus.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/icus.js
index 7207b20e49a..4656f170501 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/icus.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/icus.js
@@ -12,8 +12,8 @@ function MyComponent_ng_template_2_Template(rf, ctx) {
decls: 3,
vars: 1,
consts: function() {
- __i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
- __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
+ __i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
+ __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
return [
$i18n_0$,
$i18n_1$
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/nested_templates.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/nested_templates.js
index dd692ebf423..39fedbedae4 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/nested_templates.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/nested_templates.js
@@ -40,7 +40,7 @@ function MyComponent_ng_template_2_Template(rf, ctx) {
decls: 3,
vars: 0,
consts: function() {
- __i18nMsgWithPostprocess__('{$startTagNgTemplate} Template A: {$interpolation} {$startTagNgTemplate} Template B: {$interpolation_1} {$startTagNgTemplate} Template C: {$interpolation_2} {$closeTagNgTemplate}{$closeTagNgTemplate}{$closeTagNgTemplate}', [['startTagNgTemplate', String.raw`[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]`], ['closeTagNgTemplate', String.raw`[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`], ['interpolation_1', String.raw`\uFFFD0:2\uFFFD`], ['interpolation_2', String.raw`\uFFFD0:3\uFFFD`]], {}, [])
+ __i18nMsgWithPostprocess__('{$startTagNgTemplate} Template A: {$interpolation} {$startTagNgTemplate} Template B: {$interpolation_1} {$startTagNgTemplate} Template C: {$interpolation_2} {$closeTagNgTemplate}{$closeTagNgTemplate}{$closeTagNgTemplate}', [['startTagNgTemplate', String.raw`[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]`], ['closeTagNgTemplate', String.raw`[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`], ['interpolation_1', String.raw`\uFFFD0:2\uFFFD`], ['interpolation_2', String.raw`\uFFFD0:3\uFFFD`]], {original_code: {'startTagNgTemplate': '
', 'closeTagNgTemplate': '', 'interpolation': '{{ valueA | uppercase }}', 'interpolation_1': '{{ valueB }}', 'interpolation_2': '{{ valueC }}'}}, {}, [])
return [
$i18n_0$
];
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/ng-container_with_non_text_content.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/ng-container_with_non_text_content.js
index de6725ad4df..b453a161a62 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/ng-container_with_non_text_content.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/ng-container_with_non_text_content.js
@@ -1,7 +1,7 @@
decls: 4,
vars: 0,
consts: function() {
- __i18nMsg__(' Hello {$startTagNgContainer}there {$startTagStrong}!{$closeTagStrong}{$closeTagNgContainer}', [['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], ['startTagStrong', String.raw`\uFFFD#3\uFFFD`], ['closeTagStrong', String.raw`\uFFFD/#3\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`]], {})
+ __i18nMsg__(' Hello {$startTagNgContainer}there {$startTagStrong}!{$closeTagStrong}{$closeTagNgContainer}', [['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], ['startTagStrong', String.raw`\uFFFD#3\uFFFD`], ['closeTagStrong', String.raw`\uFFFD/#3\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`]], {original_code: {'startTagNgContainer': '
', 'startTagStrong': '', 'closeTagStrong': '', 'closeTagNgContainer': ''}}, {})
return [
$i18n_0$
];
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/self_closing_ng-container.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/self_closing_ng-container.js
index af6da8ad8b4..da52248d93c 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/self_closing_ng-container.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/self_closing_ng-container.js
@@ -1,7 +1,7 @@
decls: 3,
vars: 0,
consts: function() {
- __i18nMsg__(' Hello {$startTagNgContainer}there{$closeTagNgContainer}', [['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`]], {})
+ __i18nMsg__(' Hello {$startTagNgContainer}there{$closeTagNgContainer}', [['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`]], {original_code: {'startTagNgContainer': '
', 'closeTagNgContainer': ''}}, {})
return [
$i18n_0$
];
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/self_closing_tags.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/self_closing_tags.js
index 4e714992656..55ac3113fc8 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/self_closing_tags.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/self_closing_tags.js
@@ -7,8 +7,8 @@ function MyComponent_ng_template_3_Template(rf, ctx) {
}
…
consts: function() {
- __i18nMsg__('{$tagImg} is my logo #1 ', [['tagImg', String.raw`\uFFFD#2\uFFFD\uFFFD/#2\uFFFD`]], {})
- __i18nMsg__('{$tagImg} is my logo #2 ', [['tagImg', String.raw`\uFFFD#1\uFFFD\uFFFD/#1\uFFFD`]], {})
+ __i18nMsg__('{$tagImg} is my logo #1 ', [['tagImg', String.raw`\uFFFD#2\uFFFD\uFFFD/#2\uFFFD`]], {original_code: {tagImg: '

'}}, {})
+ __i18nMsg__('{$tagImg} is my logo #2 ', [['tagImg', String.raw`\uFFFD#1\uFFFD\uFFFD/#1\uFFFD`]], {original_code: {tagImg: '

'}}, {})
return [
$i18n_0$,
["src", "logo.png", "title", "Logo"],
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/single_ng-container.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/single_ng-container.js
index f9c6f7f9240..cb1dd941408 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/single_ng-container.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/single_ng-container.js
@@ -1,7 +1,7 @@
decls: 3,
vars: 3,
consts: function() {
- __i18nMsg__('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
+ __i18nMsg__('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ valueA | uppercase }}'}}, {})
return [
$i18n_0$
];
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/single_ng-template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/single_ng-template.js
index e12d3eb8cee..ee9a24d9598 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/single_ng-template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/single_ng-template.js
@@ -12,7 +12,7 @@ function MyComponent_ng_template_0_Template(rf, ctx) {
}
…
decls: 1, vars: 0, consts: function() {
- __i18nMsg__('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
+ __i18nMsg__('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ valueA | uppercase }}'}}, {})
return [$i18n_0$];
},
template: function MyComponent_Template(rf, ctx) {
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/structural_directives.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/structural_directives.js
index 6659ace8c59..807a0cb2c78 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/structural_directives.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/ng-container_ng-template/structural_directives.js
@@ -21,8 +21,8 @@ function MyComponent_ng_container_1_Template(rf, ctx) {
decls: 2,
vars: 2,
consts: function() {
- __i18nMsg__('Content A', [], {})
- __i18nMsg__('Content B', [], {})
+ __i18nMsg__('Content A', [], {}, {})
+ __i18nMsg__('Content B', [], {}, {})
return [
[__AttributeMarker.Template__, "ngIf"],
$i18n_0$,
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/repeated_placeholder.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/repeated_placeholder.js
new file mode 100644
index 00000000000..425cae70b5e
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/repeated_placeholder.js
@@ -0,0 +1,3 @@
+consts: function () {
+ __i18nMsgWithPostprocess__('Hello, {$interpolation}! You are a very good {$interpolation}.', [['interpolation', String.raw`[\uFFFD0\uFFFD|\uFFFD1\uFFFD]`]], {original_code: {'interpolation': '{{ placeholder }}'}}, {}, [])
+ __i18nMsgWithPostprocess__('Hello, {$ph}! Hello again {$ph}.', [['ph', String.raw`[\uFFFD0\uFFFD|\uFFFD1\uFFFD]`]], {original_code: {'ph': '{{ placeholder // i18n(ph = "ph") }}'}}, {}, [])
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/repeated_placeholder.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/repeated_placeholder.ts
new file mode 100644
index 00000000000..693745322c0
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/repeated_placeholder.ts
@@ -0,0 +1,16 @@
+import {Component, NgModule} from '@angular/core';
+
+@Component({
+ selector: 'my-component',
+ template: `
+
Hello, {{ placeholder }}! You are a very good {{ placeholder }}.
+
Hello, {{ placeholder // i18n(ph = "ph") }}! Hello again {{ placeholder // i18n(ph = "ph") }}.
+ `,
+})
+export class MyComponent {
+ placeholder: any;
+}
+
+@NgModule({declarations: [MyComponent]})
+export class MyModule {
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/icu_only.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/icu_only.js
index cf8c5c9b733..2cc0c08f94b 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/icu_only.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/icu_only.js
@@ -1,7 +1,7 @@
decls: 2,
vars: 1,
consts: function() {
- __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
+ __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
return [
$i18n_0$
];
@@ -17,4 +17,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18nExp(ctx.age);
$r3$.ɵɵi18nApply(1);
}
-}
\ No newline at end of file
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/ng-container_ng-template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/ng-container_ng-template.js
index 6fbaa56d5af..3c49d7acdde 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/ng-container_ng-template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/ng-container_ng-template.js
@@ -5,8 +5,8 @@ function MyComponent_ng_template_0_Template(rf, ctx) {
}
…
consts: function() {
- __i18nMsg__('My i18n block #2', [], {})
- __i18nMsg__('My i18n block #1', [], {})
+ __i18nMsg__('My i18n block #2', [], {}, {})
+ __i18nMsg__('My i18n block #1', [], {}, {})
return [
$i18n_0$,
$i18n_1$
@@ -19,4 +19,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18n(2, 0);
$r3$.ɵɵelementContainerEnd();
}
-}
\ No newline at end of file
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/styles.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/styles.js
index 26ad3ad31af..5c375eafbcc 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/styles.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/styles.js
@@ -1,8 +1,8 @@
decls: 4,
vars: 0,
consts: function() {
- __i18nMsg__('Text #1', [] , {})
- __i18nMsg__('Text #2', [] , {})
+ __i18nMsg__('Text #1', [], {}, {})
+ __i18nMsg__('Text #2', [], {}, {})
return [
[__AttributeMarker.Classes__, "myClass"],
$i18n_0$,
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/text_only_content.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/text_only_content.js
index 5128c429edf..81dfb6b233b 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/text_only_content.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/self-closing_i18n_instructions/text_only_content.js
@@ -1,5 +1,5 @@
consts: function() {
- __i18nMsg__('My i18n block #1', [] , {})
+ __i18nMsg__('My i18n block #1', [], {}, {})
return [
$i18n_0$
];
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/whitespace_preserving_mode/preserve_inner_content.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/whitespace_preserving_mode/preserve_inner_content.js
index e089ff0eb75..7baa99d55d3 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/whitespace_preserving_mode/preserve_inner_content.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/whitespace_preserving_mode/preserve_inner_content.js
@@ -8,6 +8,11 @@ consts: function() {
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"
+ }, {
+ original_code: {
+ "startTagSpan": "
",
+ "closeTagSpan": ""
+ }
});
$I18N_0$ = $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$;
}
diff --git a/packages/compiler-cli/test/compliance/test_helpers/expected_file_macros.ts b/packages/compiler-cli/test/compliance/test_helpers/expected_file_macros.ts
index af2a6ed7714..c1e7161a21c 100644
--- a/packages/compiler-cli/test/compliance/test_helpers/expected_file_macros.ts
+++ b/packages/compiler-cli/test/compliance/test_helpers/expected_file_macros.ts
@@ -7,26 +7,32 @@
*/
import {AttributeMarker, SelectorFlags} from '@angular/compiler/src/core';
import {QueryFlags} from '@angular/compiler/src/render3/view/compiler';
-import {i18nIcuMsg, i18nMsg, i18nMsgWithPostprocess, Placeholder, resetMessageIndex} from './i18n_helpers';
+
+import {i18nIcuMsg, i18nMsg, i18nMsgWithPostprocess, Options, Placeholder, resetMessageIndex} from './i18n_helpers';
const EXPECTED_FILE_MACROS: [RegExp, (...args: string[]) => string][] = [
[
- // E.g. `__i18nMsg__('message string', [ ['placeholder', 'pair'] ], { meta: 'properties'})`
- macroFn(/__i18nMsg__/, stringParam(), arrayParam(), objectParam()),
- (_match, message, placeholders, meta) =>
- i18nMsg(message, parsePlaceholders(placeholders), parseMetaProperties(meta)),
+ // E.g. `__i18nMsg__('message string', [ ['placeholder', 'pair']
+ // ], {original_code: {'placeholder': '{{ foo }}'}}, {meta: 'properties'})`
+ macroFn(/__i18nMsg__/, stringParam(), arrayParam(), objectParam(), objectParam()),
+ (_match, message, placeholders, options, meta) => i18nMsg(
+ message, parsePlaceholders(placeholders), parseOptions(options), parseMetaProperties(meta)),
],
[
// E.g. `__i18nMsgWithPostprocess__('message', [ ['placeholder', 'pair'] ], { meta: 'props'})`
- macroFn(/__i18nMsgWithPostprocess__/, stringParam(), arrayParam(), objectParam(), arrayParam()),
- (_match, message, placeholders, meta, postProcessPlaceholders) => i18nMsgWithPostprocess(
- message, parsePlaceholders(placeholders), parseMetaProperties(meta),
- parsePlaceholders(postProcessPlaceholders)),
+ macroFn(
+ /__i18nMsgWithPostprocess__/, stringParam(), arrayParam(), objectParam(), objectParam(),
+ arrayParam()),
+ (_match, message, placeholders, options, meta, postProcessPlaceholders) =>
+ i18nMsgWithPostprocess(
+ message, parsePlaceholders(placeholders), parseOptions(options),
+ parseMetaProperties(meta), parsePlaceholders(postProcessPlaceholders)),
],
[
// E.g. `__i18nIcuMsg__('message string', [ ['placeholder', 'pair'] ])`
- macroFn(/__i18nIcuMsg__/, stringParam(), arrayParam()),
- (_match, message, placeholders) => i18nIcuMsg(message, parsePlaceholders(placeholders)),
+ macroFn(/__i18nIcuMsg__/, stringParam(), arrayParam(), objectParam()),
+ (_match, message, placeholders, options) =>
+ i18nIcuMsg(message, parsePlaceholders(placeholders), parseOptions(options)),
],
[
// E.g. `__AttributeMarker.Bindings__`
@@ -68,6 +74,38 @@ function parsePlaceholders(str: string): Placeholder[] {
return placeholders;
}
+function parseOptions(str: string): Options {
+ const inputObj = eval(`(${str})`) as unknown;
+ if (typeof inputObj !== 'object') {
+ throw new Error(`Expected an object of properties but got:\n\n${str}.`);
+ }
+ const obj = inputObj as Record
;
+
+ // Verify the object does not have any unexpected properties, as this is likely a sign that it was
+ // authored incorrectly.
+ const unexpectedKeys = Object.keys(obj).filter((key) => key !== 'original_code');
+ if (unexpectedKeys.length > 0) {
+ throw new Error(`Expected an i18n options object with \`original_code\`, but got ${
+ unexpectedKeys.join(', ')}`);
+ }
+
+ // Validate `original_code`.
+ const original = obj?.original_code;
+ if (typeof original !== 'undefined' && typeof original !== 'object') {
+ throw new Error(
+ `Expected an i18n options object with \`original_code\`, as a nested object, but got ${
+ JSON.stringify(obj, null, 4)}`);
+ }
+ for (const [key, value] of Object.entries(original ?? {})) {
+ if (typeof value !== 'string') {
+ throw new Error(`Expected an object whose values are strings, but property ${key} has type ${
+ typeof value}, when parsing:\n\n${str}`);
+ }
+ }
+
+ return obj;
+}
+
function parseMetaProperties(str: string): Record {
const obj = eval(`(${str})`);
if (typeof obj !== 'object') {
@@ -137,7 +175,14 @@ function arrayParam() {
return /(\[.*?\])/;
}
function objectParam() {
- return /(\{[^}]*\})/;
+ // Matches a JavaScript object literal with up to 6 levels of indentation. Regular expressions
+ // cannot use recursion so it is impossible to match an arbitrarily deep object. While it looks
+ // complicated it is really just the same pattern nested inside itself n times:
+ // (?:\{[^{}]*RECURSE_HERE\}[^{}]*)
+ //
+ // Each nested level uses (?:) for a non-matching group so the whole expression is the only match
+ // and avoids generating multiple macro arguments for each nested level.
+ return /(\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*\}[^{}]*)*\}[^{}]*)*\}[^{}]*)*\})/;
}
function macroFn(fnName: RegExp, ...args: RegExp[]): RegExp {
diff --git a/packages/compiler-cli/test/compliance/test_helpers/i18n_helpers.ts b/packages/compiler-cli/test/compliance/test_helpers/i18n_helpers.ts
index 09958f631fb..e7de55ed5cd 100644
--- a/packages/compiler-cli/test/compliance/test_helpers/i18n_helpers.ts
+++ b/packages/compiler-cli/test/compliance/test_helpers/i18n_helpers.ts
@@ -19,15 +19,18 @@ export function resetMessageIndex(): void {
/**
* Generate a string that represents expected i18n block content for a simple message.
*/
-export function i18nMsg(message: string, placeholders: Placeholder[], meta: Meta): string {
+export function i18nMsg(
+ message: string, placeholders: Placeholder[], options: Options, meta: Meta): string {
const varName = `$I18N_${msgIndex++}$`;
const closurePlaceholders = i18nPlaceholdersToString(placeholders);
+ const closureOptions = i18nOptionsToString(options);
const locMessageWithPlaceholders = i18nMsgInsertLocalizePlaceholders(message, placeholders);
return `
let ${varName};
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
${i18nMsgClosureMeta(meta)}
- const $MSG_EXTERNAL_${msgIndex}$ = goog.getMsg("${message}"${closurePlaceholders});
+ const $MSG_EXTERNAL_${msgIndex}$ = goog.getMsg("${message}"${closurePlaceholders}${
+ closureOptions});
${varName} = $MSG_EXTERNAL_${msgIndex}$;
}
else {
@@ -35,17 +38,22 @@ export function i18nMsg(message: string, placeholders: Placeholder[], meta: Meta
}`;
}
+/** Describes options bag passed to `goog.getMsg()`. */
+export interface Options {
+ original_code?: Record;
+}
+
/**
* Generate a string that represents expected i18n block content for a message that requires
* post-processing.
*/
export function i18nMsgWithPostprocess(
- message: string, placeholders: Placeholder[], meta: Meta,
+ message: string, placeholders: Placeholder[], options: Options, meta: Meta,
postprocessPlaceholders: Placeholder[]): string {
const varName = `$I18N_${msgIndex}$`;
const ppPlaceholders = i18nPlaceholdersToString(postprocessPlaceholders);
return String.raw`
- ${i18nMsg(message, placeholders, meta)}
+ ${i18nMsg(message, placeholders, options, meta)}
${varName} = $r3$.ɵɵi18nPostprocess($${varName}$${ppPlaceholders});
`;
}
@@ -53,8 +61,8 @@ export function i18nMsgWithPostprocess(
/**
* Generates a string that represents expected i18n block content for an ICU.
*/
-export function i18nIcuMsg(message: string, placeholders: Placeholder[]): string {
- return i18nMsgWithPostprocess(message, [], {}, placeholders);
+export function i18nIcuMsg(message: string, placeholders: Placeholder[], options: Options): string {
+ return i18nMsgWithPostprocess(message, [], options, {}, placeholders);
}
/**
@@ -86,6 +94,13 @@ function i18nPlaceholdersToString(placeholders: Placeholder[]): string {
return `, { ${result.join(',')} }`;
}
+/** Convert an object of `goog.getMsg()` options to the expected string. */
+function i18nOptionsToString({original_code: originals = {}}: Options = {}): string {
+ if (Object.keys(originals).length === 0) return '';
+ const result = Object.entries(originals).map(([key, value]) => `"${key}": ${quotedValue(value)}`);
+ return `, { original_code: {\n${result.join(',\n')}\n} }`;
+}
+
/**
* Transform a message in a Closure format to a $localize version.
*/
@@ -135,5 +150,5 @@ function i18nMsgLocalizeMeta(meta?: Meta): string {
* placeholder value. Such special cases should not be wrapped in quotes.
*/
function quotedValue(value: string): string {
- return value.startsWith('$') ? value : `"${value}"`;
+ return value.startsWith('$') ? value : `"${value.replace(/"/g, '\\"')}"`;
}
diff --git a/packages/compiler/src/render3/view/i18n/get_msg_utils.ts b/packages/compiler/src/render3/view/i18n/get_msg_utils.ts
index 61fc84b77b9..e87b7399b0d 100644
--- a/packages/compiler/src/render3/view/i18n/get_msg_utils.ts
+++ b/packages/compiler/src/render3/view/i18n/get_msg_utils.ts
@@ -11,18 +11,77 @@ import * as o from '../../../output/output_ast';
import {serializeIcuNode} from './icu_serializer';
import {i18nMetaToJSDoc} from './meta';
-import {formatI18nPlaceholderName} from './util';
+import {formatI18nPlaceholderName, formatI18nPlaceholderNamesInMap} from './util';
/** Closure uses `goog.getMsg(message)` to lookup translations */
const GOOG_GET_MSG = 'goog.getMsg';
+/**
+ * Generates a `goog.getMsg()` statement and reassignment. The template:
+ *
+ * ```html
+ * Sent from {{ sender }} to {{ receiver }}
+ * ```
+ *
+ * Generates:
+ *
+ * ```typescript
+ * const MSG_FOO = goog.getMsg(
+ * // Message template.
+ * 'Sent from {$interpolation} to {$startTagSpan}{$interpolation_1}{$closeTagSpan}.',
+ * // Placeholder values, set to magic strings which get replaced by the Angular runtime.
+ * {
+ * 'interpolation': '\uFFFD0\uFFFD',
+ * 'startTagSpan': '\uFFFD1\uFFFD',
+ * 'interpolation_1': '\uFFFD2\uFFFD',
+ * 'closeTagSpan': '\uFFFD3\uFFFD',
+ * },
+ * // Options bag.
+ * {
+ * // Maps each placeholder to the original Angular source code which generates it's value.
+ * original_code: {
+ * 'interpolation': '{{ sender }}',
+ * 'startTagSpan': '',
+ * 'interploation_1': '{{ receiver }}',
+ * 'closeTagSpan': '',
+ * },
+ * },
+ * );
+ * const I18N_0 = MSG_FOO;
+ * ```
+ */
export function createGoogleGetMsgStatements(
variable: o.ReadVarExpr, message: i18n.Message, closureVar: o.ReadVarExpr,
- params: {[name: string]: o.Expression}): o.Statement[] {
+ placeholderValues: {[name: string]: o.Expression}): o.Statement[] {
const messageString = serializeI18nMessageForGetMsg(message);
const args = [o.literal(messageString) as o.Expression];
- if (Object.keys(params).length) {
- args.push(mapLiteral(params, true));
+ if (Object.keys(placeholderValues).length) {
+ // Message template parameters containing the magic strings replaced by the Angular runtime with
+ // real data, e.g. `{'interpolation': '\uFFFD0\uFFFD'}`.
+ args.push(mapLiteral(
+ formatI18nPlaceholderNamesInMap(placeholderValues, true /* useCamelCase */),
+ true /* quoted */));
+
+ // Message options object, which contains original source code for placeholders (as they are
+ // present in a template, e.g.
+ // `{original_code: {'interpolation': '{{ name }}', 'startTagSpan': ''}}`.
+ args.push(mapLiteral({
+ original_code:
+ o.literalMap(Object.keys(placeholderValues)
+ .map((param) => ({
+ key: formatI18nPlaceholderName(param),
+ quoted: true,
+ value: message.placeholders[param] ?
+ // Get source span for typical placeholder if it exists.
+ o.literal(message.placeholders[param].sourceSpan.toString()) :
+ // Otherwise must be an ICU expression, get it's source span.
+ o.literal(
+ message.placeholderToMessage[param]
+ .nodes.map((node) => node.sourceSpan.toString())
+ .join(''),
+ ),
+ }))),
+ }));
}
// /**
diff --git a/packages/compiler/src/render3/view/i18n/util.ts b/packages/compiler/src/render3/view/i18n/util.ts
index 3c0360cffeb..ee807791333 100644
--- a/packages/compiler/src/render3/view/i18n/util.ts
+++ b/packages/compiler/src/render3/view/i18n/util.ts
@@ -123,7 +123,7 @@ export function assembleBoundTextPlaceholders(
* @param useCamelCase whether to camelCase the placeholder name when formatting.
* @returns A new map of formatted placeholder names to expressions.
*/
-export function i18nFormatPlaceholderNames(
+export function formatI18nPlaceholderNamesInMap(
params: {[name: string]: o.Expression} = {}, useCamelCase: boolean) {
const _params: {[key: string]: o.Expression} = {};
if (params && Object.keys(params).length) {
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index c14c7b7357d..9d385ead3c0 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -36,7 +36,7 @@ import {I18nContext} from './i18n/context';
import {createGoogleGetMsgStatements} from './i18n/get_msg_utils';
import {createLocalizeStatements} from './i18n/localize_utils';
import {I18nMetaVisitor} from './i18n/meta';
-import {assembleBoundTextPlaceholders, assembleI18nBoundString, declareI18nVariable, getTranslationConstPrefix, hasI18nMeta, I18N_ICU_MAPPING_PREFIX, i18nFormatPlaceholderNames, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, placeholdersToParams, TRANSLATION_VAR_PREFIX, wrapI18nPlaceholder} from './i18n/util';
+import {assembleBoundTextPlaceholders, assembleI18nBoundString, declareI18nVariable, formatI18nPlaceholderNamesInMap, getTranslationConstPrefix, hasI18nMeta, I18N_ICU_MAPPING_PREFIX, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, placeholdersToParams, TRANSLATION_VAR_PREFIX, wrapI18nPlaceholder} from './i18n/util';
import {StylingBuilder, StylingInstruction} from './styling_builder';
import {asLiteral, CONTEXT_NAME, getInstructionStatements, getInterpolationArgsLength, IMPLICIT_REFERENCE, Instruction, InstructionParams, invalid, invokeInstruction, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, RESTORED_VIEW_CONTEXT_NAME, trimTrailingNulls} from './util';
@@ -1035,7 +1035,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// - all ICU vars (such as `VAR_SELECT` or `VAR_PLURAL`) are replaced with correct values
const transformFn = (raw: o.ReadVarExpr) => {
const params = {...vars, ...placeholders};
- const formatted = i18nFormatPlaceholderNames(params, /* useCamelCase */ false);
+ const formatted = formatI18nPlaceholderNamesInMap(params, /* useCamelCase */ false);
return invokeInstruction(null, R3.i18nPostprocess, [raw, mapLiteral(formatted, true)]);
};
@@ -2269,11 +2269,9 @@ export function getTranslationDeclStmts(
declareI18nVariable(variable),
o.ifStmt(
createClosureModeGuard(),
- createGoogleGetMsgStatements(
- variable, message, closureVar,
- i18nFormatPlaceholderNames(params, /* useCamelCase */ true)),
+ createGoogleGetMsgStatements(variable, message, closureVar, params),
createLocalizeStatements(
- variable, message, i18nFormatPlaceholderNames(params, /* useCamelCase */ false))),
+ variable, message, formatI18nPlaceholderNamesInMap(params, /* useCamelCase */ false))),
];
if (transformFn) {