fix(ivy): template compiler should render correct $localize placeholder names (#32509)

The `goog.getMsg()` function requires placeholder names to be camelCased.

This is not the case for `$localize`. Here placeholder names need
match what is serialized to translation files.

Specifically such placeholder names keep their casing but have all characters
that are not in `a-z`, `A-Z`, `0-9` and `_` converted to `_`.

PR Close #32509
This commit is contained in:
Pete Bacon Darwin 2019-09-06 12:26:48 +01:00 committed by Matias Niemelä
parent 9166baf709
commit ea6a2e9f25
7 changed files with 1344 additions and 1279 deletions

View File

@ -417,7 +417,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_2$ = $localize \`intro $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
var $I18N_3$;
if (ngI18nClosureMode) {
@ -432,7 +432,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_3$ = $localize \`$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
const $_c1$ = [
"aria-roledescription", $I18N_1$,
@ -453,9 +453,9 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_6$ = $localize \`$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: and $` +
String.raw `{"\uFFFD1\uFFFD"}:interpolation_1: and again $` +
String.raw `{"\uFFFD2\uFFFD"}:interpolation_2:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: and $` +
String.raw `{"\uFFFD1\uFFFD"}:INTERPOLATION_1: and again $` +
String.raw `{"\uFFFD2\uFFFD"}:INTERPOLATION_2:\`;
}
var $I18N_7$;
if (ngI18nClosureMode) {
@ -466,7 +466,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_7$ = $localize \`$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
const $_c3$ = [
"title", $I18N_6$,
@ -518,7 +518,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_1$ = $localize \`intro $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
const $_c3$ = ["title", $I18N_1$];
@ -561,7 +561,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_1$ = $localize \`different scope $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
const $_c2$ = ["title", $I18N_1$];
function MyComponent_div_0_Template(rf, ctx) {
@ -612,7 +612,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_1$ = $localize \`$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: title\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: title\`;
}
const $_c3$ = ["title", $I18N_1$];
@ -675,7 +675,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_2$ = $localize \`intro $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
var $I18N_3$;
if (ngI18nClosureMode) {
@ -690,7 +690,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_3$ = $localize \`$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
const $_c1$ = [
"aria-roledescription", $I18N_1$,
@ -711,9 +711,9 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_6$ = $localize \`$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: and $` +
String.raw `{"\uFFFD1\uFFFD"}:interpolation_1: and again $` +
String.raw `{"\uFFFD2\uFFFD"}:interpolation_2:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: and $` +
String.raw `{"\uFFFD1\uFFFD"}:INTERPOLATION_1: and again $` +
String.raw `{"\uFFFD2\uFFFD"}:INTERPOLATION_2:\`;
}
var $I18N_7$;
if (ngI18nClosureMode) {
@ -724,7 +724,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_7$ = $localize \`$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
const $_c3$ = [
"title", $I18N_6$,
@ -779,7 +779,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_2$ = $localize \`different scope $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
const $_c4$ = ["title", $I18N_2$];
function MyComponent_div_0_Template(rf, ctx) {
@ -1033,8 +1033,8 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` Named interpolation: $` +
String.raw `{"\uFFFD0\uFFFD"}:phA: Named interpolation with spaces: $` +
String.raw `{"\uFFFD1\uFFFD"}:phB: \`;
String.raw `{"\uFFFD0\uFFFD"}:PH_A: Named interpolation with spaces: $` +
String.raw `{"\uFFFD1\uFFFD"}:PH_B: \`;
}
consts: 2,
@ -1071,7 +1071,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \`$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
template: function MyComponent_Template(rf, ctx) {
@ -1111,9 +1111,9 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: $` +
String.raw `{"\uFFFD1\uFFFD"}:interpolation_1: $` +
String.raw `{"\uFFFD2\uFFFD"}:interpolation_2: \`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: $` +
String.raw `{"\uFFFD1\uFFFD"}:INTERPOLATION_1: $` +
String.raw `{"\uFFFD2\uFFFD"}:INTERPOLATION_2: \`;
}
template: function MyComponent_Template(rf, ctx) {
@ -1152,7 +1152,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \`My i18n block #$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
var $I18N_1$;
if (ngI18nClosureMode) {
@ -1163,7 +1163,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_1$ = $localize \`My i18n block #$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
var $I18N_2$;
if (ngI18nClosureMode) {
@ -1174,7 +1174,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_2$ = $localize \`My i18n block #$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
consts: 7,
@ -1239,9 +1239,9 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` My i18n block #$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: $` +
String.raw `{"\uFFFD#2\uFFFD"}:startTagSpan:Plain text in nested element$` +
String.raw `{"\uFFFD/#2\uFFFD"}:closeTagSpan:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: $` +
String.raw `{"\uFFFD#2\uFFFD"}:START_TAG_SPAN:Plain text in nested element$` +
String.raw `{"\uFFFD/#2\uFFFD"}:CLOSE_TAG_SPAN:\`;
}
var $I18N_1$;
if (ngI18nClosureMode) {
@ -1257,14 +1257,14 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_1$ = $localize \` My i18n block #$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: $` +
String.raw `{"[\uFFFD#6\uFFFD|\uFFFD#7\uFFFD]"}:startTagDiv:$` +
String.raw `{"[\uFFFD#6\uFFFD|\uFFFD#7\uFFFD]"}:startTagDiv:$` + String.raw
`{"\uFFFD#8\uFFFD"}:startTagSpan: More bindings in more nested element: $` +
String.raw `{"\uFFFD1\uFFFD"}:interpolation_1: $` +
String.raw `{"\uFFFD/#8\uFFFD"}:closeTagSpan:$` +
String.raw `{"[\uFFFD/#7\uFFFD|\uFFFD/#6\uFFFD]"}:closeTagDiv:$` +
String.raw `{"[\uFFFD/#7\uFFFD|\uFFFD/#6\uFFFD]"}:closeTagDiv:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: $` +
String.raw `{"[\uFFFD#6\uFFFD|\uFFFD#7\uFFFD]"}:START_TAG_DIV:$` +
String.raw `{"[\uFFFD#6\uFFFD|\uFFFD#7\uFFFD]"}:START_TAG_DIV:$` + String.raw
`{"\uFFFD#8\uFFFD"}:START_TAG_SPAN: More bindings in more nested element: $` +
String.raw `{"\uFFFD1\uFFFD"}:INTERPOLATION_1: $` +
String.raw `{"\uFFFD/#8\uFFFD"}:CLOSE_TAG_SPAN:$` +
String.raw `{"[\uFFFD/#7\uFFFD|\uFFFD/#6\uFFFD]"}:CLOSE_TAG_DIV:$` +
String.raw `{"[\uFFFD/#7\uFFFD|\uFFFD/#6\uFFFD]"}:CLOSE_TAG_DIV:\`;
}
$I18N_1$ = $r3$.ɵɵi18nPostprocess($I18N_1$);
@ -1330,8 +1330,8 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_2$ = $localize \`Span title $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: and $` +
String.raw `{"\uFFFD1\uFFFD"}:interpolation_1:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: and $` +
String.raw `{"\uFFFD1\uFFFD"}:INTERPOLATION_1:\`;
}
const $_c4$ = ["title", $I18N_2$];
var $I18N_0$;
@ -1345,9 +1345,9 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` My i18n block #1 with value: $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: $` + String.raw
`{"\uFFFD#2\uFFFD"}:startTagSpan: Plain text in nested element (block #1) $` +
String.raw `{"\uFFFD/#2\uFFFD"}:closeTagSpan:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: $` + String.raw
`{"\uFFFD#2\uFFFD"}:START_TAG_SPAN: Plain text in nested element (block #1) $` +
String.raw `{"\uFFFD/#2\uFFFD"}:CLOSE_TAG_SPAN:\`;
}
var $I18N_7$;
if (ngI18nClosureMode) {
@ -1358,7 +1358,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_7$ = $localize \`Span title $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
const $_c9$ = ["title", $I18N_7$];
var $I18N_6$;
@ -1372,9 +1372,9 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_6$ = $localize \` My i18n block #2 with value $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: $` + String.raw
`{"\uFFFD#7\uFFFD"}:startTagSpan: Plain text in nested element (block #2) $` +
String.raw `{"\uFFFD/#7\uFFFD"}:closeTagSpan:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: $` + String.raw
`{"\uFFFD#7\uFFFD"}:START_TAG_SPAN: Plain text in nested element (block #2) $` +
String.raw `{"\uFFFD/#7\uFFFD"}:CLOSE_TAG_SPAN:\`;
}
consts: 9,
@ -1444,10 +1444,10 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_1$ = $localize \` Some other content $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation: $` +
String.raw `{"\uFFFD#3\uFFFD"}:startTagDiv: More nested levels with bindings $` +
String.raw `{"\uFFFD1\uFFFD"}:interpolation_1: $` +
String.raw `{"\uFFFD/#3\uFFFD"}:closeTagDiv:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION: $` +
String.raw `{"\uFFFD#3\uFFFD"}:START_TAG_DIV: More nested levels with bindings $` +
String.raw `{"\uFFFD1\uFFFD"}:INTERPOLATION_1: $` +
String.raw `{"\uFFFD/#3\uFFFD"}:CLOSE_TAG_DIV:\`;
}
function MyComponent_div_2_Template(rf, ctx) {
@ -1514,7 +1514,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_2$ = $localize \`App logo #$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
const $_c4$ = ["title", $I18N_2$];
function MyComponent_img_2_Template(rf, ctx) {
@ -1630,29 +1630,29 @@ describe('i18n support in the template compiler', () => {
else {
$I18N_0$ = $localize \` Some content $` +
String.raw
`{"\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD"}:startTagDiv_2: Some other content $` +
String.raw `{"\uFFFD0:1\uFFFD"}:interpolation: $` + String.raw
`{"[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]"}:startTagDiv: More nested levels with bindings $` +
String.raw `{"\uFFFD1:1\uFFFD"}:interpolation_1: $` + String.raw
`{"\uFFFD*4:2\uFFFD\uFFFD#1:2\uFFFD"}:startTagDiv_1: Content inside sub-template $` +
String.raw `{"\uFFFD0:2\uFFFD"}:interpolation_2: $` + String.raw
`{"[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]"}:startTagDiv: Bottom level element $` +
String.raw `{"\uFFFD1:2\uFFFD"}:interpolation_3: $` + String.raw
`{"[\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]"}:closeTagDiv:$` +
`{"\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD"}:START_TAG_DIV_2: Some other content $` +
String.raw `{"\uFFFD0:1\uFFFD"}:INTERPOLATION: $` + String.raw
`{"[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]"}:START_TAG_DIV: More nested levels with bindings $` +
String.raw `{"\uFFFD1:1\uFFFD"}:INTERPOLATION_1: $` + String.raw
`{"\uFFFD*4:2\uFFFD\uFFFD#1:2\uFFFD"}:START_TAG_DIV_1: Content inside sub-template $` +
String.raw `{"\uFFFD0:2\uFFFD"}:INTERPOLATION_2: $` + String.raw
`{"[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]"}:START_TAG_DIV: Bottom level element $` +
String.raw `{"\uFFFD1:2\uFFFD"}:INTERPOLATION_3: $` + String.raw
`{"[\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]"}:CLOSE_TAG_DIV:$` +
String.raw
`{"[\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]"}: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]"}:CLOSE_TAG_DIV:$` +
String.raw
`{"[\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]"}: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]"}:CLOSE_TAG_DIV:$` +
String.raw
`{"[\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]"}: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]"}:CLOSE_TAG_DIV:$` +
String.raw
`{"\uFFFD*3:3\uFFFD\uFFFD#1:3\uFFFD"}:startTagDiv_3: Some other content $` +
String.raw `{"\uFFFD0:3\uFFFD"}:interpolation_4: $` + String.raw
`{"[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]"}:startTagDiv: More nested levels with bindings $` +
String.raw `{"\uFFFD1:3\uFFFD"}:interpolation_5: $` + String.raw
`{"[\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]"}:closeTagDiv:$` +
`{"\uFFFD*3:3\uFFFD\uFFFD#1:3\uFFFD"}:START_TAG_DIV_3: Some other content $` +
String.raw `{"\uFFFD0:3\uFFFD"}:INTERPOLATION_4: $` + String.raw
`{"[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]"}:START_TAG_DIV: More nested levels with bindings $` +
String.raw `{"\uFFFD1:3\uFFFD"}:INTERPOLATION_5: $` + String.raw
`{"[\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]"}:CLOSE_TAG_DIV:$` +
String.raw
`{"[\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]"}: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]"}:CLOSE_TAG_DIV:\`;
}
$I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$);
function MyComponent_div_3_Template(rf, ctx) {
@ -1712,9 +1712,9 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_1$ = $localize \`Some other content $` +
String.raw `{"\uFFFD#2\uFFFD"}:startTagSpan:$` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:$` +
String.raw `{"\uFFFD/#2\uFFFD"}:closeTagSpan:\`;
String.raw `{"\uFFFD#2\uFFFD"}:START_TAG_SPAN:$` +
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:$` +
String.raw `{"\uFFFD/#2\uFFFD"}:CLOSE_TAG_SPAN:\`;
}
function MyComponent_div_0_Template(rf, ctx) {
@ -1945,7 +1945,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \`Some content: $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
consts: 3,
@ -1983,7 +1983,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \`Some content: $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation:\`;
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
}
function MyComponent_ng_template_0_Template(rf, ctx) {
if (rf & 1) {
@ -2031,12 +2031,12 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \`$` +
String.raw `{"\uFFFD*2:1\uFFFD"}:startTagNgTemplate:Template content: $` +
String.raw `{"\uFFFD0:1\uFFFD"}:interpolation:$` +
String.raw `{"\uFFFD/*2:1\uFFFD"}:closeTagNgTemplate:$` +
String.raw `{"\uFFFD#3\uFFFD"}:startTagNgContainer:Container content: $` +
String.raw `{"\uFFFD0\uFFFD"}:interpolation_1:$` +
String.raw `{"\uFFFD/#3\uFFFD"}:closeTagNgContainer:\`;
String.raw `{"\uFFFD*2:1\uFFFD"}:START_TAG_NG_TEMPLATE:Template content: $` +
String.raw `{"\uFFFD0:1\uFFFD"}:INTERPOLATION:$` +
String.raw `{"\uFFFD/*2:1\uFFFD"}:CLOSE_TAG_NG_TEMPLATE:$` +
String.raw `{"\uFFFD#3\uFFFD"}:START_TAG_NG_CONTAINER:Container content: $` +
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION_1:$` +
String.raw `{"\uFFFD/#3\uFFFD"}:CLOSE_TAG_NG_CONTAINER:\`;
}
function MyComponent_ng_template_2_Template(rf, ctx) {
if (rf & 1) {
@ -2185,17 +2185,17 @@ describe('i18n support in the template compiler', () => {
else {
$I18N_0$ = $localize \`$` +
String.raw
`{"[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]"}:startTagNgTemplate: Template A: $` +
String.raw `{"\uFFFD0:1\uFFFD"}:interpolation: $` + String.raw
`{"[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]"}:startTagNgTemplate: Template B: $` +
String.raw `{"\uFFFD0:2\uFFFD"}:interpolation_1: $` + String.raw
`{"[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]"}:startTagNgTemplate: Template C: $` +
String.raw `{"\uFFFD0:3\uFFFD"}:interpolation_2: $` + String.raw
`{"[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]"}:closeTagNgTemplate:$` +
`{"[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]"}:START_TAG_NG_TEMPLATE: Template A: $` +
String.raw `{"\uFFFD0:1\uFFFD"}:INTERPOLATION: $` + String.raw
`{"[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]"}:START_TAG_NG_TEMPLATE: Template B: $` +
String.raw `{"\uFFFD0:2\uFFFD"}:INTERPOLATION_1: $` + String.raw
`{"[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]"}:START_TAG_NG_TEMPLATE: Template C: $` +
String.raw `{"\uFFFD0:3\uFFFD"}:INTERPOLATION_2: $` + String.raw
`{"[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]"}:CLOSE_TAG_NG_TEMPLATE:$` +
String.raw
`{"[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]"}:closeTagNgTemplate:$` +
`{"[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]"}:CLOSE_TAG_NG_TEMPLATE:$` +
String.raw
`{"[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]"}:closeTagNgTemplate:\`;
`{"[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]"}:CLOSE_TAG_NG_TEMPLATE:\`;
}
$I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$);
function MyComponent_ng_template_2_Template(rf, ctx) {
@ -2309,7 +2309,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \`$` +
String.raw `{"\uFFFD#2\uFFFD\uFFFD/#2\uFFFD"}:tagImg: is my logo #1 \`;
String.raw `{"\uFFFD#2\uFFFD\uFFFD/#2\uFFFD"}:TAG_IMG: is my logo #1 \`;
}
var $I18N_2$;
if (ngI18nClosureMode) {
@ -2320,7 +2320,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_2$ = $localize \`$` +
String.raw `{"\uFFFD#1\uFFFD\uFFFD/#1\uFFFD"}:tagImg: is my logo #2 \`;
String.raw `{"\uFFFD#1\uFFFD\uFFFD/#1\uFFFD"}:TAG_IMG: is my logo #2 \`;
}
function MyComponent_ng_template_3_Template(rf, ctx) {
if (rf & 1) {
@ -2367,8 +2367,8 @@ describe('i18n support in the template compiler', () => {
else {
$I18N_0$ = $localize \` Root content $` +
String.raw
`{"\uFFFD*1:1\uFFFD\uFFFD#1:1\uFFFD"}:startTagNgContainer: Nested content $` +
String.raw `{"\uFFFD/#1:1\uFFFD\uFFFD/*1:1\uFFFD"}:closeTagNgContainer:\`;
`{"\uFFFD*1:1\uFFFD\uFFFD#1:1\uFFFD"}:START_TAG_NG_CONTAINER: Nested content $` +
String.raw `{"\uFFFD/#1:1\uFFFD\uFFFD/*1:1\uFFFD"}:CLOSE_TAG_NG_CONTAINER:\`;
}
`;
@ -2423,8 +2423,8 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` Hello $` +
String.raw `{"\uFFFD#2\uFFFD"}:startTagNgContainer:there$` +
String.raw `{"\uFFFD/#2\uFFFD"}:closeTagNgContainer:\`;
String.raw `{"\uFFFD#2\uFFFD"}:START_TAG_NG_CONTAINER:there$` +
String.raw `{"\uFFFD/#2\uFFFD"}:CLOSE_TAG_NG_CONTAINER:\`;
}
consts: 3,
@ -2459,10 +2459,10 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` Hello $` +
String.raw `{"\uFFFD#2\uFFFD"}:startTagNgContainer:there $` +
String.raw `{"\uFFFD#3\uFFFD"}:startTagStrong:!$` +
String.raw `{"\uFFFD/#3\uFFFD"}:closeTagStrong:$` +
String.raw `{"\uFFFD/#2\uFFFD"}:closeTagNgContainer:\`;
String.raw `{"\uFFFD#2\uFFFD"}:START_TAG_NG_CONTAINER:there $` +
String.raw `{"\uFFFD#3\uFFFD"}:START_TAG_STRONG:!$` +
String.raw `{"\uFFFD/#3\uFFFD"}:CLOSE_TAG_STRONG:$` +
String.raw `{"\uFFFD/#2\uFFFD"}:CLOSE_TAG_NG_CONTAINER:\`;
}
consts: 4,
@ -2505,8 +2505,8 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \`\n Some text\n $` +
String.raw `{"\uFFFD#3\uFFFD"}:startTagSpan:Text inside span$` +
String.raw `{"\uFFFD/#3\uFFFD"}:closeTagSpan:\n \`;
String.raw `{"\uFFFD#3\uFFFD"}:START_TAG_SPAN:Text inside span$` +
String.raw `{"\uFFFD/#3\uFFFD"}:CLOSE_TAG_SPAN:\n \`;
}
template: function MyComponent_Template(rf, ctx) {
@ -2805,13 +2805,13 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` $` +
String.raw `{$I18N_1$}:icu: $` +
String.raw `{"\uFFFD#2\uFFFD"}:startBoldText:Other content$` +
String.raw `{"\uFFFD/#2\uFFFD"}:closeBoldText:$` +
String.raw `{"\uFFFD#3\uFFFD"}:startTagDiv:$` +
String.raw `{"\uFFFD#4\uFFFD"}:startItalicText:Another content$` +
String.raw `{"\uFFFD/#4\uFFFD"}:closeItalicText:$` +
String.raw `{"\uFFFD/#3\uFFFD"}:closeTagDiv:\`;
String.raw `{$I18N_1$}:ICU: $` +
String.raw `{"\uFFFD#2\uFFFD"}:START_BOLD_TEXT:Other content$` +
String.raw `{"\uFFFD/#2\uFFFD"}:CLOSE_BOLD_TEXT:$` +
String.raw `{"\uFFFD#3\uFFFD"}:START_TAG_DIV:$` +
String.raw `{"\uFFFD#4\uFFFD"}:START_ITALIC_TEXT:Another content$` +
String.raw `{"\uFFFD/#4\uFFFD"}:CLOSE_ITALIC_TEXT:$` +
String.raw `{"\uFFFD/#3\uFFFD"}:CLOSE_TAG_DIV:\`;
}
consts: 5,
@ -2917,7 +2917,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` $` +
String.raw `{$I18N_1$}:icu: $` + String.raw `{$I18N_2$}:icu_1: \`;
String.raw `{$I18N_1$}:ICU: $` + String.raw `{$I18N_2$}:ICU_1: \`;
}
consts: 2,
@ -2999,13 +2999,13 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` $` +
String.raw `{"\uFFFDI18N_EXP_ICU\uFFFD"}:icu: $` +
String.raw `{"\uFFFD#2\uFFFD"}:startTagDiv: $` +
String.raw `{"\uFFFDI18N_EXP_ICU\uFFFD"}:icu: $` + String.raw
`{"[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]"}:closeTagDiv:$` +
String.raw `{"\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD"}:startTagDiv_1: $` +
String.raw `{"\uFFFDI18N_EXP_ICU\uFFFD"}:icu: $` + String.raw
`{"[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]"}:closeTagDiv:\`;
String.raw `{"\uFFFDI18N_EXP_ICU\uFFFD"}:ICU: $` +
String.raw `{"\uFFFD#2\uFFFD"}:START_TAG_DIV: $` +
String.raw `{"\uFFFDI18N_EXP_ICU\uFFFD"}:ICU: $` + String.raw
`{"[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]"}:CLOSE_TAG_DIV:$` +
String.raw `{"\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD"}:START_TAG_DIV_1: $` +
String.raw `{"\uFFFDI18N_EXP_ICU\uFFFD"}:ICU: $` + String.raw
`{"[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]"}:CLOSE_TAG_DIV:\`;
}
$I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$, {
"ICU": [$I18N_1$, $I18N_2$, $I18N_4$]
@ -3080,7 +3080,7 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` $` +
String.raw `{$I18N_1$}:icu: \`;
String.raw `{$I18N_1$}:ICU: \`;
}
consts: 2,
vars: 2,
@ -3196,10 +3196,10 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` $` +
String.raw `{$I18N_1$}:icu: $` +
String.raw `{"\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD"}:startTagSpan: $` +
String.raw `{$I18N_3$}:icu_1: $` +
String.raw `{"\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD"}:closeTagSpan:\`;
String.raw `{$I18N_1$}:ICU: $` +
String.raw `{"\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD"}:START_TAG_SPAN: $` +
String.raw `{$I18N_3$}:ICU_1: $` +
String.raw `{"\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD"}:CLOSE_TAG_SPAN:\`;
}
function MyComponent_span_2_Template(rf, ctx) {
if (rf & 1) {
@ -3285,10 +3285,10 @@ describe('i18n support in the template compiler', () => {
}
else {
$I18N_0$ = $localize \` $` +
String.raw `{I18N_1}:icu: $` +
String.raw `{"\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD"}:startTagSpan: $` +
String.raw `{I18N_4}:icu_1: $` +
String.raw `{"\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD"}:closeTagSpan:\`;
String.raw `{I18N_1}:ICU: $` +
String.raw `{"\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD"}:START_TAG_SPAN: $` +
String.raw `{I18N_4}:ICU_1: $` +
String.raw `{"\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD"}:CLOSE_TAG_SPAN:\`;
}
function MyComponent_span_2_Template(rf, ctx) {
if (rf & 1) {

View File

@ -9,7 +9,6 @@ import * as i18n from '../../../i18n/i18n_ast';
import * as o from '../../../output/output_ast';
import {serializeIcuNode} from './icu_serializer';
import {i18nMetaToDocStmt, metaFromI18nMessage} from './meta';
import {formatI18nPlaceholderName} from './util';
export function createLocalizeStatements(
@ -37,7 +36,7 @@ class MessagePiece {
}
class LiteralPiece extends MessagePiece {}
class PlaceholderPiece extends MessagePiece {
constructor(name: string) { super(formatI18nPlaceholderName(name)); }
constructor(name: string) { super(formatI18nPlaceholderName(name, /* useCamelCase */ false)); }
}
/**

View File

@ -2021,13 +2021,15 @@ export function getTranslationDeclStmts(
message: i18n.Message, variable: o.ReadVarExpr, closureVar: o.ReadVarExpr,
params: {[name: string]: o.Expression} = {},
transformFn?: (raw: o.ReadVarExpr) => o.Expression): o.Statement[] {
const formattedParams = i18nFormatPlaceholderNames(params, /* useCamelCase */ true);
const statements: o.Statement[] = [
declareI18nVariable(variable),
o.ifStmt(
o.variable(NG_I18N_CLOSURE_MODE),
createGoogleGetMsgStatements(variable, message, closureVar, formattedParams),
createLocalizeStatements(variable, message, formattedParams)),
createGoogleGetMsgStatements(
variable, message, closureVar,
i18nFormatPlaceholderNames(params, /* useCamelCase */ true)),
createLocalizeStatements(
variable, message, i18nFormatPlaceholderNames(params, /* useCamelCase */ false))),
];
if (transformFn) {

View File

@ -232,6 +232,11 @@ describe('serializeI18nMessageForGetMsg', () => {
.toEqual('Some text {$interpolation} and {$interpolation_1}');
});
it('should serialize interpolation with named placeholder for `GetMsg()`', () => {
expect(serialize('{{ valueB + valueC // i18n(ph="PLACEHOLDER NAME") }}'))
.toEqual('{$placeholderName}');
});
it('should serialize content with HTML tags for `GetMsg()`', () => {
expect(serialize('A <span>B<div>C</div></span> D'))
.toEqual('A {$startTagSpan}B{$startTagDiv}C{$closeTagDiv}{$closeTagSpan} D');
@ -277,14 +282,14 @@ describe('serializeI18nMessageForLocalize', () => {
it('should serialize text with interpolation for `$localize()`', () => {
expect(serialize('Some text {{ valueA }} and {{ valueB + valueC }} done')).toEqual({
messageParts: ['Some text ', ' and ', ' done'],
placeHolders: ['interpolation', 'interpolation_1']
placeHolders: ['INTERPOLATION', 'INTERPOLATION_1']
});
});
it('should serialize text with interpolation at start for `$localize()`', () => {
expect(serialize('{{ valueA }} and {{ valueB + valueC }} done')).toEqual({
messageParts: ['', ' and ', ' done'],
placeHolders: ['interpolation', 'interpolation_1']
placeHolders: ['INTERPOLATION', 'INTERPOLATION_1']
});
});
@ -292,21 +297,27 @@ describe('serializeI18nMessageForLocalize', () => {
it('should serialize text with interpolation at end for `$localize()`', () => {
expect(serialize('Some text {{ valueA }} and {{ valueB + valueC }}')).toEqual({
messageParts: ['Some text ', ' and ', ''],
placeHolders: ['interpolation', 'interpolation_1']
placeHolders: ['INTERPOLATION', 'INTERPOLATION_1']
});
});
it('should serialize only interpolation for `$localize()`', () => {
expect(serialize('{{ valueB + valueC }}'))
.toEqual({messageParts: ['', ''], placeHolders: ['interpolation']});
.toEqual({messageParts: ['', ''], placeHolders: ['INTERPOLATION']});
});
it('should serialize interpolation with named placeholder for `$localize()`', () => {
expect(serialize('{{ valueB + valueC // i18n(ph="PLACEHOLDER NAME") }}'))
.toEqual({messageParts: ['', ''], placeHolders: ['PLACEHOLDER_NAME']});
});
it('should serialize content with HTML tags for `$localize()`', () => {
expect(serialize('A <span>B<div>C</div></span> D')).toEqual({
messageParts: ['A ', 'B', 'C', '', ' D'],
placeHolders: ['startTagSpan', 'startTagDiv', 'closeTagDiv', 'closeTagSpan']
placeHolders: ['START_TAG_SPAN', 'START_TAG_DIV', 'CLOSE_TAG_DIV', 'CLOSE_TAG_SPAN']
});
});
@ -346,7 +357,7 @@ describe('serializeI18nMessageForLocalize', () => {
'{gender, select, male {male} female {female} other {other}}<div>{gender, select, male {male} female {female} other {other}}</div>'))
.toEqual({
messageParts: ['', '', '', '', ''],
placeHolders: ['icu', 'startTagDiv', 'icu', 'closeTagDiv']
placeHolders: ['ICU', 'START_TAG_DIV', 'ICU', 'CLOSE_TAG_DIV']
});
});
});

View File

@ -17,9 +17,12 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
onlyInIvy('Ivy i18n logic')
.describe(
'runtime i18n', () => {
beforeEach(() => {
TestBed.configureTestingModule({declarations: [AppComp, DirectiveWithTplRef, UppercasePipe]});
TestBed.configureTestingModule(
{declarations: [AppComp, DirectiveWithTplRef, UppercasePipe]});
});
afterEach(() => { setDelayProjection(false); });
@ -31,7 +34,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should support interpolations', () => {
loadTranslations({'Hello {$interpolation}!': 'Bonjour {$interpolation}!'});
loadTranslations({'Hello {$INTERPOLATION}!': 'Bonjour {$INTERPOLATION}!'});
const fixture = initWithTemplate(AppComp, `<div i18n>Hello {{name}}!</div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div>Bonjour Angular!</div>`);
fixture.componentRef.instance.name = `John`;
@ -41,8 +44,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should support named interpolations', () => {
loadTranslations({
' Hello {$userName}! Emails: {$amountOfEmailsReceived} ':
' Bonjour {$userName}! Emails: {$amountOfEmailsReceived} '
' Hello {$USER_NAME}! Emails: {$AMOUNT_OF_EMAILS_RECEIVED} ':
' Bonjour {$USER_NAME}! Emails: {$AMOUNT_OF_EMAILS_RECEIVED} '
});
const fixture = initWithTemplate(AppComp, `
<div i18n>
@ -50,7 +53,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
Emails: {{ count // i18n(ph="amount of emails received") }}
</div>
`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div> Bonjour Angular! Emails: 0 </div>`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div> Bonjour Angular! Emails: 0 </div>`);
fixture.componentRef.instance.name = `John`;
fixture.componentRef.instance.count = 5;
fixture.detectChanges();
@ -58,7 +62,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should support interpolations with custom interpolation config', () => {
loadTranslations({'Hello {$interpolation}': 'Bonjour {$interpolation}'});
loadTranslations({'Hello {$INTERPOLATION}': 'Bonjour {$INTERPOLATION}'});
const interpolation = ['{%', '%}'] as[string, string];
TestBed.overrideComponent(AppComp, {set: {interpolation}});
const fixture = initWithTemplate(AppComp, `<div i18n>Hello {% name %}</div>`);
@ -69,15 +73,16 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should support &ngsp; in translatable sections', () => {
// note: the `` unicode symbol represents the `&ngsp;` in translations
loadTranslations({'text ||': 'texte ||'});
const fixture = initWithTemplate(AppCompWithWhitespaces, `<div i18n>text |&ngsp;|</div>`);
const fixture =
initWithTemplate(AppCompWithWhitespaces, `<div i18n>text |&ngsp;|</div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div>texte | |</div>`);
});
it('should support interpolations with complex expressions', () => {
loadTranslations({
' {$interpolation} - {$interpolation_1} - {$interpolation_2} ':
' {$interpolation} - {$interpolation_1} - {$interpolation_2} (fr) '
' {$INTERPOLATION} - {$INTERPOLATION_1} - {$INTERPOLATION_2} ':
' {$INTERPOLATION} - {$INTERPOLATION_1} - {$INTERPOLATION_2} (fr) '
});
const fixture = initWithTemplate(AppComp, `
<div i18n>
@ -86,7 +91,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
{{ obj?.getA()?.b }}
</div>
`);
// the `obj` field is not yet defined, so 2nd and 3rd interpolations return empty strings
// 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 = {
@ -100,8 +106,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should support elements', () => {
loadTranslations({
'Hello {$startTagSpan}world{$closeTagSpan} and {$startTagDiv}universe{$closeTagDiv}!':
'Bonjour {$startTagSpan}monde{$closeTagSpan} et {$startTagDiv}univers{$closeTagDiv}!'
'Hello {$START_TAG_SPAN}world{$CLOSE_TAG_SPAN} and {$START_TAG_DIV}universe{$CLOSE_TAG_DIV}!':
'Bonjour {$START_TAG_SPAN}monde{$CLOSE_TAG_SPAN} et {$START_TAG_DIV}univers{$CLOSE_TAG_DIV}!'
});
const fixture = initWithTemplate(
AppComp, `<div i18n>Hello <span>world</span> and <div>universe</div>!</div>`);
@ -111,19 +117,19 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should support removing elements', () => {
loadTranslations({
'Hello {$startBoldText}my{$closeBoldText}{$startTagSpan}world{$closeTagSpan}':
'Bonjour {$startTagSpan}monde{$closeTagSpan}'
'Hello {$START_BOLD_TEXT}my{$CLOSE_BOLD_TEXT}{$START_TAG_SPAN}world{$CLOSE_TAG_SPAN}':
'Bonjour {$START_TAG_SPAN}monde{$CLOSE_TAG_SPAN}'
});
const fixture =
initWithTemplate(AppComp, `<div i18n>Hello <b>my</b><span>world</span></div><div>!</div>`);
const fixture = initWithTemplate(
AppComp, `<div i18n>Hello <b>my</b><span>world</span></div><div>!</div>`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div>Bonjour <span>monde</span></div><div>!</div>`);
});
it('should support moving elements', () => {
loadTranslations({
'Hello {$startTagSpan}world{$closeTagSpan} and {$startTagDiv}universe{$closeTagDiv}!':
'Bonjour {$startTagDiv}univers{$closeTagDiv} et {$startTagSpan}monde{$closeTagSpan}!'
'Hello {$START_TAG_SPAN}world{$CLOSE_TAG_SPAN} and {$START_TAG_DIV}universe{$CLOSE_TAG_DIV}!':
'Bonjour {$START_TAG_DIV}univers{$CLOSE_TAG_DIV} et {$START_TAG_SPAN}monde{$CLOSE_TAG_SPAN}!'
});
const fixture = initWithTemplate(
AppComp, `<div i18n>Hello <span>world</span> and <div>universe</div>!</div>`);
@ -133,8 +139,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should support template directives', () => {
loadTranslations({
'Content: {$startTagDiv}before{$startTagSpan}middle{$closeTagSpan}after{$closeTagDiv}!':
'Contenu: {$startTagDiv}avant{$startTagSpan}milieu{$closeTagSpan}après{$closeTagDiv}!'
'Content: {$START_TAG_DIV}before{$START_TAG_SPAN}middle{$CLOSE_TAG_SPAN}after{$CLOSE_TAG_DIV}!':
'Contenu: {$START_TAG_DIV}avant{$START_TAG_SPAN}milieu{$CLOSE_TAG_SPAN}après{$CLOSE_TAG_DIV}!'
});
const fixture = initWithTemplate(
AppComp,
@ -153,11 +159,11 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should support multiple i18n blocks', () => {
loadTranslations({
'trad {$interpolation}': 'traduction {$interpolation}',
'start {$interpolation} middle {$interpolation_1} end':
'start {$interpolation_1} middle {$interpolation} end',
'{$startTagC}trad{$closeTagC}{$startTagD}{$closeTagD}{$startTagE}{$closeTagE}':
'{$startTagE}{$closeTagE}{$startTagC}traduction{$closeTagC}'
'trad {$INTERPOLATION}': 'traduction {$INTERPOLATION}',
'start {$INTERPOLATION} middle {$INTERPOLATION_1} end':
'start {$INTERPOLATION_1} middle {$INTERPOLATION} end',
'{$START_TAG_C}trad{$CLOSE_TAG_C}{$START_TAG_D}{$CLOSE_TAG_D}{$START_TAG_E}{$CLOSE_TAG_E}':
'{$START_TAG_E}{$CLOSE_TAG_E}{$START_TAG_C}traduction{$CLOSE_TAG_C}'
});
const fixture = initWithTemplate(AppComp, `
<div>
@ -187,7 +193,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
<div i18n>Section 3</div>
</div>`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div><div>Section un</div><div>Section deux</div><div>Section trois</div></div>`);
.toEqual(
`<div><div>Section un</div><div>Section deux</div><div>Section trois</div></div>`);
});
it('should support multiple sibling i18n blocks inside of a template directive', () => {
@ -211,7 +218,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should properly escape quotes in content', () => {
loadTranslations({
'\'Single quotes\' and "Double quotes"': '\'Guillemets simples\' et "Guillemets doubles"'
'\'Single quotes\' and "Double quotes"':
'\'Guillemets simples\' et "Guillemets doubles"'
});
const fixture =
initWithTemplate(AppComp, `<div i18n>'Single quotes' and "Double quotes"</div>`);
@ -221,7 +229,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should correctly bind to context in nested template', () => {
loadTranslations({'Item {$interpolation}': 'Article {$interpolation}'});
loadTranslations({'Item {$INTERPOLATION}': 'Article {$INTERPOLATION}'});
const fixture = initWithTemplate(AppComp, `
<div *ngFor='let id of items'>
<div i18n>Item {{ id }}</div>
@ -241,16 +249,19 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should handle i18n attribute with directives', () => {
loadTranslations({'Hello {$interpolation}': 'Bonjour {$interpolation}'});
const fixture = initWithTemplate(AppComp, `<div *ngIf="visible" i18n>Hello {{ name }}</div>`);
loadTranslations({'Hello {$INTERPOLATION}': 'Bonjour {$INTERPOLATION}'});
const fixture =
initWithTemplate(AppComp, `<div *ngIf="visible" i18n>Hello {{ name }}</div>`);
expect(fixture.nativeElement.firstChild).toHaveText('Bonjour Angular');
});
it('should work correctly with event listeners', () => {
loadTranslations({'Hello {$interpolation}': 'Bonjour {$interpolation}'});
loadTranslations({'Hello {$INTERPOLATION}': 'Bonjour {$INTERPOLATION}'});
@Component(
{selector: 'app-comp', template: `<div i18n (click)="onClick()">Hello {{ name }}</div>`})
@Component({
selector: 'app-comp',
template: `<div i18n (click)="onClick()">Hello {{ name }}</div>`
})
class ListenerComp {
name = `Angular`;
clicks = 0;
@ -280,9 +291,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should handle single translation message within ng-template', () => {
loadTranslations({'Hello {$interpolation}': 'Bonjour {$interpolation}'});
const fixture =
initWithTemplate(AppComp, `<ng-template i18n tplRef>Hello {{ name }}</ng-template>`);
loadTranslations({'Hello {$INTERPOLATION}': 'Bonjour {$INTERPOLATION}'});
const fixture = initWithTemplate(
AppComp, `<ng-template i18n tplRef>Hello {{ name }}</ng-template>`);
const element = fixture.nativeElement;
expect(element).toHaveText('Bonjour Angular');
@ -291,8 +302,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should be able to act as child elements inside i18n block (plain text content)', () => {
loadTranslations({
'{$startTagNgTemplate} Hello {$closeTagNgTemplate}{$startTagNgContainer} Bye {$closeTagNgContainer}':
'{$startTagNgTemplate} Bonjour {$closeTagNgTemplate}{$startTagNgContainer} Au revoir {$closeTagNgContainer}'
'{$START_TAG_NG_TEMPLATE} Hello {$CLOSE_TAG_NG_TEMPLATE}{$START_TAG_NG_CONTAINER} Bye {$CLOSE_TAG_NG_CONTAINER}':
'{$START_TAG_NG_TEMPLATE} Bonjour {$CLOSE_TAG_NG_TEMPLATE}{$START_TAG_NG_CONTAINER} Au revoir {$CLOSE_TAG_NG_CONTAINER}'
});
const fixture = initWithTemplate(AppComp, `
<div i18n>
@ -312,8 +323,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should be able to act as child elements inside i18n block (text + tags)', () => {
loadTranslations({
'{$startTagNgTemplate}{$startTagSpan}Hello{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan}Hello{$closeTagSpan}{$closeTagNgContainer}':
'{$startTagNgTemplate}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgContainer}'
'{$START_TAG_NG_TEMPLATE}{$START_TAG_SPAN}Hello{$CLOSE_TAG_SPAN}{$CLOSE_TAG_NG_TEMPLATE}{$START_TAG_NG_CONTAINER}{$START_TAG_SPAN}Hello{$CLOSE_TAG_SPAN}{$CLOSE_TAG_NG_CONTAINER}':
'{$START_TAG_NG_TEMPLATE}{$START_TAG_SPAN}Bonjour{$CLOSE_TAG_SPAN}{$CLOSE_TAG_NG_TEMPLATE}{$START_TAG_NG_CONTAINER}{$START_TAG_SPAN}Bonjour{$CLOSE_TAG_SPAN}{$CLOSE_TAG_NG_CONTAINER}'
});
const fixture = initWithTemplate(AppComp, `
<div i18n>
@ -334,15 +345,16 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should be able to act as child elements inside i18n block (text + pipes)', () => {
// Note: for some reason keeping this key inline causes clang to reformat the entire file
// Note: for some reason keeping this key inline causes clang to reformat the entire
// file
// in a very weird way. Keeping it separated like this seems to make it happy.
const key = '{$startTagNgTemplate}Hello {$interpolation}{$closeTagNgTemplate}' +
'{$startTagNgContainer}Bye {$interpolation}{$closeTagNgContainer}';
const key = '{$START_TAG_NG_TEMPLATE}Hello {$INTERPOLATION}{$CLOSE_TAG_NG_TEMPLATE}' +
'{$START_TAG_NG_CONTAINER}Bye {$INTERPOLATION}{$CLOSE_TAG_NG_CONTAINER}';
loadTranslations({
[key]:
'{$startTagNgTemplate}Hej {$interpolation}{$closeTagNgTemplate}{$startTagNgContainer}Vi ses {$interpolation}{$closeTagNgContainer}'
'{$START_TAG_NG_TEMPLATE}Hej {$INTERPOLATION}{$CLOSE_TAG_NG_TEMPLATE}{$START_TAG_NG_CONTAINER}Vi ses {$INTERPOLATION}{$CLOSE_TAG_NG_CONTAINER}'
});
const fixture = initWithTemplate(AppComp, `
<div i18n>
@ -352,14 +364,15 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
`);
const element = fixture.nativeElement.firstChild;
expect(element.textContent.replace(/\s+/g, ' ').trim()).toBe('Hej ANGULARVi ses ANGULAR');
expect(element.textContent.replace(/\s+/g, ' ').trim())
.toBe('Hej ANGULARVi ses ANGULAR');
});
it('should be able to handle deep nested levels with templates', () => {
loadTranslations({
'{$startTagSpan} Hello - 1 {$closeTagSpan}{$startTagSpan_1} Hello - 2 {$startTagSpan_1} Hello - 3 {$startTagSpan_1} Hello - 4 {$closeTagSpan}{$closeTagSpan}{$closeTagSpan}{$startTagSpan} Hello - 5 {$closeTagSpan}':
'{$startTagSpan} Bonjour - 1 {$closeTagSpan}{$startTagSpan_1} Bonjour - 2 {$startTagSpan_1} Bonjour - 3 {$startTagSpan_1} Bonjour - 4 {$closeTagSpan}{$closeTagSpan}{$closeTagSpan}{$startTagSpan} Bonjour - 5 {$closeTagSpan}'
'{$START_TAG_SPAN} Hello - 1 {$CLOSE_TAG_SPAN}{$START_TAG_SPAN_1} Hello - 2 {$START_TAG_SPAN_1} Hello - 3 {$START_TAG_SPAN_1} Hello - 4 {$CLOSE_TAG_SPAN}{$CLOSE_TAG_SPAN}{$CLOSE_TAG_SPAN}{$START_TAG_SPAN} Hello - 5 {$CLOSE_TAG_SPAN}':
'{$START_TAG_SPAN} Bonjour - 1 {$CLOSE_TAG_SPAN}{$START_TAG_SPAN_1} Bonjour - 2 {$START_TAG_SPAN_1} Bonjour - 3 {$START_TAG_SPAN_1} Bonjour - 4 {$CLOSE_TAG_SPAN}{$CLOSE_TAG_SPAN}{$CLOSE_TAG_SPAN}{$START_TAG_SPAN} Bonjour - 5 {$CLOSE_TAG_SPAN}'
});
const fixture = initWithTemplate(AppComp, `
<div i18n>
@ -391,8 +404,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should handle self-closing tags as content', () => {
loadTranslations({
'{$startTagSpan}My logo{$tagImg}{$closeTagSpan}':
'{$startTagSpan}Mon logo{$tagImg}{$closeTagSpan}'
'{$START_TAG_SPAN}My logo{$TAG_IMG}{$CLOSE_TAG_SPAN}':
'{$START_TAG_SPAN}Mon logo{$TAG_IMG}{$CLOSE_TAG_SPAN}'
});
const content = `My logo<img src="logo.png" title="Logo">`;
const fixture = initWithTemplate(AppComp, `
@ -412,7 +425,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
}
});
it('should correctly find context for an element inside i18n section in <ng-template>', () => {
it('should correctly find context for an element inside i18n section in <ng-template>',
() => {
@Directive({selector: '[myDir]'})
class Dir {
condition = true;
@ -477,7 +491,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
'{VAR_PLURAL, plural, =0 {no {START_BOLD_TEXT}emails{CLOSE_BOLD_TEXT}!} =1 {one {START_ITALIC_TEXT}email{CLOSE_ITALIC_TEXT}} other {{INTERPOLATION} {START_TAG_SPAN}emails{CLOSE_TAG_SPAN}}}':
'{VAR_PLURAL, plural, =0 {aucun {START_BOLD_TEXT}email{CLOSE_BOLD_TEXT}!} =1 {un {START_ITALIC_TEXT}email{CLOSE_ITALIC_TEXT}} other {{INTERPOLATION} {START_TAG_SPAN}emails{CLOSE_TAG_SPAN}}}',
'{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
'{VAR_SELECT, select, other {(name)}}':
'{VAR_SELECT, select, other {({$INTERPOLATION})}}'
});
const fixture = initWithTemplate(AppComp, `<div i18n>{count, plural,
=0 {no <b>emails</b>!}
@ -505,13 +520,13 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('with custom interpolation config', () => {
loadTranslations({
'{VAR_SELECT, select, 10 {ten} other {{$interpolation}}}':
'{VAR_SELECT, select, 10 {dix} other {{$interpolation}}}'
'{VAR_SELECT, select, 10 {ten} other {{$INTERPOLATION}}}':
'{VAR_SELECT, select, 10 {dix} other {{$INTERPOLATION}}}'
});
const interpolation = ['{%', '%}'] as[string, string];
TestBed.overrideComponent(AppComp, {set: {interpolation}});
const fixture =
initWithTemplate(AppComp, `<div i18n>{count, select, 10 {ten} other {{% name %}}}</div>`);
const fixture = initWithTemplate(
AppComp, `<div i18n>{count, select, 10 {ten} other {{% name %}}}</div>`);
expect(fixture.nativeElement).toHaveText(`Angular`);
});
@ -521,7 +536,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
'{VAR_PLURAL, plural, =0 {no {START_BOLD_TEXT}emails{CLOSE_BOLD_TEXT}!} =1 {one {START_ITALIC_TEXT}email{CLOSE_ITALIC_TEXT}} other {{INTERPOLATION} {START_TAG_SPAN}emails{CLOSE_TAG_SPAN}}}':
'{VAR_PLURAL, plural, =0 {aucun {START_BOLD_TEXT}email{CLOSE_BOLD_TEXT}!} =1 {un {START_ITALIC_TEXT}email{CLOSE_ITALIC_TEXT}} other {{INTERPOLATION} {START_TAG_SPAN}emails{CLOSE_TAG_SPAN}}}',
'{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
'{VAR_SELECT, select, other {(name)}}':
'{VAR_SELECT, select, other {({$INTERPOLATION})}}'
});
const fixture = initWithTemplate(AppComp, `<div i18n><span>{count, plural,
=0 {no <b>emails</b>!}
@ -551,9 +567,11 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('inside template directives', () => {
loadTranslations({
'{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
'{VAR_SELECT, select, other {(name)}}':
'{VAR_SELECT, select, other {({$INTERPOLATION})}}'
});
const fixture = initWithTemplate(AppComp, `<div i18n><span *ngIf="visible">{name, select,
const fixture =
initWithTemplate(AppComp, `<div i18n><span *ngIf="visible">{name, select,
other {({{name}})}
}</span></div>`);
expect(fixture.nativeElement.innerHTML)
@ -571,12 +589,14 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('inside ng-container', () => {
loadTranslations({
'{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
'{VAR_SELECT, select, other {(name)}}':
'{VAR_SELECT, select, other {({$INTERPOLATION})}}'
});
const fixture = initWithTemplate(AppComp, `<ng-container i18n>{name, select,
other {({{name}})}
}</ng-container>`);
expect(fixture.nativeElement.innerHTML).toEqual(`(Angular)<!--ICU 4--><!--ng-container-->`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`(Angular)<!--ICU 4--><!--ng-container-->`);
});
it('inside <ng-template>', () => {
@ -620,7 +640,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('nested with interpolations in "other" blocks', () => {
// Note: for some reason long string causing clang to reformat the entire file.
const key = '{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, ' +
const key =
'{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, ' +
'cat {cats} dog {dogs} other {animals}}!} other {other - {INTERPOLATION}}}';
const translation =
'{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, ' +
@ -651,7 +672,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should return the correct plural form for ICU expressions when using a specific locale',
() => {
registerLocaleData(localeRo);
TestBed.configureTestingModule({providers: [{provide: LOCALE_ID, useValue: 'ro'}]});
TestBed.configureTestingModule(
{providers: [{provide: LOCALE_ID, useValue: 'ro'}]});
// We could also use `TestBed.overrideProvider(LOCALE_ID, {useValue: 'ro'});`
const fixture = initWithTemplate(AppComp, `
{count, plural,
@ -716,7 +738,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('with empty values', () => {
const fixture = initWithTemplate(AppComp, `{count, select, 10 {} 20 {twenty} other {other}}`);
const fixture =
initWithTemplate(AppComp, `{count, select, 10 {} 20 {twenty} other {other}}`);
const element = fixture.nativeElement;
expect(element).toHaveText('other');
@ -800,7 +823,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
dir = this;
}
attachEmbeddedView() { this.viewContainerRef.createEmbeddedView(this.templateRef); }
attachEmbeddedView() {
this.viewContainerRef.createEmbeddedView(this.templateRef);
}
}
@Component({
@ -963,10 +988,11 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('interpolations', () => {
loadTranslations({'hello {$interpolation}': 'bonjour {$interpolation}'});
loadTranslations({'hello {$INTERPOLATION}': 'bonjour {$INTERPOLATION}'});
const fixture =
initWithTemplate(AppComp, `<div i18n i18n-title title="hello {{name}}"></div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div title="bonjour Angular"></div>`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div title="bonjour Angular"></div>`);
fixture.componentRef.instance.name = 'John';
fixture.detectChanges();
@ -974,14 +1000,15 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('with pipes', () => {
loadTranslations({'hello {$interpolation}': 'bonjour {$interpolation}'});
loadTranslations({'hello {$INTERPOLATION}': 'bonjour {$INTERPOLATION}'});
const fixture = initWithTemplate(
AppComp, `<div i18n i18n-title title="hello {{name | uppercase}}"></div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div title="bonjour ANGULAR"></div>`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div title="bonjour ANGULAR"></div>`);
});
it('multiple attributes', () => {
loadTranslations({'hello {$interpolation}': 'bonjour {$interpolation}'});
loadTranslations({'hello {$INTERPOLATION}': 'bonjour {$INTERPOLATION}'});
const fixture = initWithTemplate(
AppComp,
`<input i18n i18n-title title="hello {{name}}" i18n-placeholder placeholder="hello {{name}}">`);
@ -995,25 +1022,26 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('on removed elements', () => {
loadTranslations({'text': 'texte', '{$startTagSpan}content{$closeTagSpan}': 'contenu'});
const fixture =
initWithTemplate(AppComp, `<div i18n><span i18n-title title="text">content</span></div>`);
loadTranslations(
{'text': 'texte', '{$START_TAG_SPAN}content{$CLOSE_TAG_SPAN}': 'contenu'});
const fixture = initWithTemplate(
AppComp, `<div i18n><span i18n-title title="text">content</span></div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div>contenu</div>`);
});
it('with custom interpolation config', () => {
loadTranslations({'Hello {$interpolation}': 'Bonjour {$interpolation}'});
loadTranslations({'Hello {$INTERPOLATION}': 'Bonjour {$INTERPOLATION}'});
const interpolation = ['{%', '%}'] as[string, string];
TestBed.overrideComponent(AppComp, {set: {interpolation}});
const fixture =
initWithTemplate(AppComp, `<div i18n-title="m|d" title="Hello {% name %}"></div>`);
const fixture = initWithTemplate(
AppComp, `<div i18n-title="m|d" title="Hello {% name %}"></div>`);
const element = fixture.nativeElement.firstChild;
expect(element.title).toBe('Bonjour Angular');
});
it('in nested template', () => {
loadTranslations({'Item {$interpolation}': 'Article {$interpolation}'});
loadTranslations({'Item {$INTERPOLATION}': 'Article {$INTERPOLATION}'});
const fixture = initWithTemplate(AppComp, `
<div *ngFor='let item of [1,2,3]'>
<div i18n-title='m|d' title='Item {{ item }}'></div>
@ -1027,9 +1055,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should add i18n attributes on self-closing tags', () => {
loadTranslations({'Hello {$interpolation}': 'Bonjour {$interpolation}'});
const fixture =
initWithTemplate(AppComp, `<img src="logo.png" i18n-title title="Hello {{ name }}">`);
loadTranslations({'Hello {$INTERPOLATION}': 'Bonjour {$INTERPOLATION}'});
const fixture = initWithTemplate(
AppComp, `<img src="logo.png" i18n-title title="Hello {{ name }}">`);
const element = fixture.nativeElement.firstChild;
expect(element.title).toBe('Bonjour Angular');
@ -1073,13 +1101,14 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('with complex expressions', () => {
loadTranslations({
'{$interpolation} - {$interpolation_1} - {$interpolation_2}':
'{$interpolation} - {$interpolation_1} - {$interpolation_2} (fr)'
'{$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
// 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 = {
@ -1087,7 +1116,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
getA: () => ({b: 'value 2'}),
};
fixture.detectChanges();
expect(fixture.nativeElement.firstChild.title).toEqual(`ANGULAR - value 1 - value 2 (fr)`);
expect(fixture.nativeElement.firstChild.title)
.toEqual(`ANGULAR - value 1 - value 2 (fr)`);
});
});
@ -1121,11 +1151,11 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
TestBed.configureTestingModule({declarations: [ClsDir, MyApp]});
loadTranslations({
// Not that this translation switches the order of the expressions!
'start {$interpolation} middle {$interpolation_1} end':
'début {$interpolation_1} milieu {$interpolation} fin',
'start {$INTERPOLATION} middle {$INTERPOLATION_1} end':
'début {$INTERPOLATION_1} milieu {$INTERPOLATION} fin',
'{VAR_PLURAL, plural, =0 {no {START_BOLD_TEXT}emails{CLOSE_BOLD_TEXT}!} =1 {one {START_ITALIC_TEXT}email{CLOSE_ITALIC_TEXT}} other {{INTERPOLATION} emails}}':
'{VAR_PLURAL, plural, =0 {aucun {START_BOLD_TEXT}email{CLOSE_BOLD_TEXT}!} =1 {un {START_ITALIC_TEXT}email{CLOSE_ITALIC_TEXT}} other {{INTERPOLATION} emails}}',
' trad: {$icu} ': ' traduction: {$icu} '
' trad: {$ICU} ': ' traduction: {$ICU} '
});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
@ -1167,7 +1197,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
}
TestBed.configureTestingModule({declarations: [AppComp, MyComp]});
loadTranslations({'Hello {$interpolation}': 'Bonjour {$interpolation}', 'works': 'fonctionne'});
loadTranslations(
{'Hello {$INTERPOLATION}': 'Bonjour {$INTERPOLATION}', 'works': 'fonctionne'});
const fixture = initWithTemplate(
AppComp,
`<my-comp i18n i18n-title title="works" i18n-value="hi" value="Hello {{name}}"></my-comp>`);
@ -1182,8 +1213,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should support adding/moving/removing nodes', () => {
loadTranslations({
'{$startTagDiv2}{$closeTagDiv2}{$startTagDiv3}{$closeTagDiv3}{$startTagDiv4}{$closeTagDiv4}{$startTagDiv5}{$closeTagDiv5}{$startTagDiv6}{$closeTagDiv6}{$startTagDiv7}{$closeTagDiv7}{$startTagDiv8}{$closeTagDiv8}':
'{$startTagDiv2}{$closeTagDiv2}{$startTagDiv8}{$closeTagDiv8}{$startTagDiv4}{$closeTagDiv4}{$startTagDiv5}{$closeTagDiv5}Bonjour monde{$startTagDiv3}{$closeTagDiv3}{$startTagDiv7}{$closeTagDiv7}'
'{$START_TAG_DIV2}{$CLOSE_TAG_DIV2}{$START_TAG_DIV3}{$CLOSE_TAG_DIV3}{$START_TAG_DIV4}{$CLOSE_TAG_DIV4}{$START_TAG_DIV5}{$CLOSE_TAG_DIV5}{$START_TAG_DIV6}{$CLOSE_TAG_DIV6}{$START_TAG_DIV7}{$CLOSE_TAG_DIV7}{$START_TAG_DIV8}{$CLOSE_TAG_DIV8}':
'{$START_TAG_DIV2}{$CLOSE_TAG_DIV2}{$START_TAG_DIV8}{$CLOSE_TAG_DIV8}{$START_TAG_DIV4}{$CLOSE_TAG_DIV4}{$START_TAG_DIV5}{$CLOSE_TAG_DIV5}Bonjour monde{$START_TAG_DIV3}{$CLOSE_TAG_DIV3}{$START_TAG_DIV7}{$CLOSE_TAG_DIV7}'
});
const fixture = initWithTemplate(AppComp, `
<div i18n>
@ -1223,9 +1254,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
TestBed.configureTestingModule({declarations: [Parent, Child]});
loadTranslations({
'Child of {$interpolation}': 'Enfant de {$interpolation}',
'{$startTagChild}I am projected from {$startBoldText}{$interpolation}{$startTagRemoveMe_1}{$closeTagRemoveMe_1}{$closeBoldText}{$startTagRemoveMe_2}{$closeTagRemoveMe_2}{$closeTagChild}{$startTagRemoveMe_3}{$closeTagRemoveMe_3}':
'{$startTagChild}Je suis projeté depuis {$startBoldText}{$interpolation}{$closeBoldText}{$closeTagChild}'
'Child of {$INTERPOLATION}': 'Enfant de {$INTERPOLATION}',
'{$START_TAG_CHILD}I am projected from {$START_BOLD_TEXT}{$INTERPOLATION}{$START_TAG_REMOVE_ME_1}{$CLOSE_TAG_REMOVE_ME_1}{$CLOSE_BOLD_TEXT}{$START_TAG_REMOVE_ME_2}{$CLOSE_TAG_REMOVE_ME_2}{$CLOSE_TAG_CHILD}{$START_TAG_REMOVE_ME_3}{$CLOSE_TAG_REMOVE_ME_3}':
'{$START_TAG_CHILD}Je suis projeté depuis {$START_BOLD_TEXT}{$INTERPOLATION}{$CLOSE_BOLD_TEXT}{$CLOSE_TAG_CHILD}'
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
@ -1256,8 +1287,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
TestBed.configureTestingModule({declarations: [Parent, Child]});
loadTranslations({
'Child of {$interpolation}': 'Enfant de {$interpolation}',
'I am projected from {$interpolation}': 'Je suis projeté depuis {$interpolation}'
'Child of {$INTERPOLATION}': 'Enfant de {$INTERPOLATION}',
'I am projected from {$INTERPOLATION}': 'Je suis projeté depuis {$INTERPOLATION}'
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
@ -1281,12 +1312,15 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should re-project translations when multiple projections', () => {
@Component({selector: 'grand-child', template: '<div><ng-content></ng-content></div>'})
@Component(
{selector: 'grand-child', template: '<div><ng-content></ng-content></div>'})
class GrandChild {
}
@Component(
{selector: 'child', template: '<grand-child><ng-content></ng-content></grand-child>'})
@Component({
selector: 'child',
template: '<grand-child><ng-content></ng-content></grand-child>'
})
class Child {
}
@ -1298,22 +1332,26 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
TestBed.configureTestingModule({declarations: [Parent, Child, GrandChild]});
loadTranslations({
'{$startBoldText}Hello{$closeBoldText} World!':
'{$startBoldText}Bonjour{$closeBoldText} monde!'
'{$START_BOLD_TEXT}Hello{$CLOSE_BOLD_TEXT} World!':
'{$START_BOLD_TEXT}Bonjour{$CLOSE_BOLD_TEXT} monde!'
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual('<child><grand-child><div><b>Bonjour</b> monde!</div></grand-child></child>');
.toEqual(
'<child><grand-child><div><b>Bonjour</b> monde!</div></grand-child></child>');
});
it('should be able to remove projected placeholders', () => {
@Component({selector: 'grand-child', template: '<div><ng-content></ng-content></div>'})
@Component(
{selector: 'grand-child', template: '<div><ng-content></ng-content></div>'})
class GrandChild {
}
@Component(
{selector: 'child', template: '<grand-child><ng-content></ng-content></grand-child>'})
@Component({
selector: 'child',
template: '<grand-child><ng-content></ng-content></grand-child>'
})
class Child {
}
@ -1323,7 +1361,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
}
TestBed.configureTestingModule({declarations: [Parent, Child, GrandChild]});
loadTranslations({'{$startBoldText}Hello{$closeBoldText} World!': 'Bonjour monde!'});
loadTranslations(
{'{$START_BOLD_TEXT}Hello{$CLOSE_BOLD_TEXT} World!': 'Bonjour monde!'});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
@ -1350,8 +1389,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
TestBed.configureTestingModule({declarations: [Parent, Child]});
loadTranslations({
'{$startTagSpan}{$closeTagSpan}{$startTagSpan_1}{$closeTagSpan}':
'{$startTagSpan}Contenu{$closeTagSpan}'
'{$START_TAG_SPAN}{$CLOSE_TAG_SPAN}{$START_TAG_SPAN_1}{$CLOSE_TAG_SPAN}':
'{$START_TAG_SPAN}Contenu{$CLOSE_TAG_SPAN}'
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
@ -1374,8 +1413,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
TestBed.configureTestingModule({declarations: [Parent, Child]});
loadTranslations({
'Content projected from {$startTagNgContent}{$closeTagNgContent}':
'Contenu projeté depuis {$startTagNgContent}{$closeTagNgContent}'
'Content projected from {$START_TAG_NG_CONTENT}{$CLOSE_TAG_NG_CONTENT}':
'Contenu projeté depuis {$START_TAG_NG_CONTENT}{$CLOSE_TAG_NG_CONTENT}'
});
const fixture = TestBed.createComponent(Parent);
@ -1404,8 +1443,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
TestBed.configureTestingModule({declarations: [Parent, Child]});
loadTranslations({
'Content projected from {$startTagNgContent}{$closeTagNgContent}':
'{$startTagNgContent}{$closeTagNgContent} a projeté le contenu'
'Content projected from {$START_TAG_NG_CONTENT}{$CLOSE_TAG_NG_CONTENT}':
'{$START_TAG_NG_CONTENT}{$CLOSE_TAG_NG_CONTENT} a projeté le contenu'
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
@ -1414,21 +1453,26 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should project translated content in i18n blocks', () => {
@Component(
{selector: 'child', template: `<div i18n>Child content <ng-content></ng-content></div>`})
@Component({
selector: 'child',
template: `<div i18n>Child content <ng-content></ng-content></div>`
})
class Child {
}
@Component({selector: 'parent', template: `<child i18n>and projection from {{name}}</child>`})
@Component({
selector: 'parent',
template: `<child i18n>and projection from {{name}}</child>`
})
class Parent {
name: string = 'Parent';
}
TestBed.configureTestingModule({declarations: [Parent, Child]});
loadTranslations({
'Child content {$startTagNgContent}{$closeTagNgContent}':
'Contenu enfant {$startTagNgContent}{$closeTagNgContent}',
'and projection from {$interpolation}': 'et projection depuis {$interpolation}'
'Child content {$START_TAG_NG_CONTENT}{$CLOSE_TAG_NG_CONTENT}':
'Contenu enfant {$START_TAG_NG_CONTENT}{$CLOSE_TAG_NG_CONTENT}',
'and projection from {$INTERPOLATION}': 'et projection depuis {$INTERPOLATION}'
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
@ -1464,8 +1508,10 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should project ICUs in i18n blocks', () => {
@Component(
{selector: 'child', template: `<div i18n>Child content <ng-content></ng-content></div>`})
@Component({
selector: 'child',
template: `<div i18n>Child content <ng-content></ng-content></div>`
})
class Child {
}
@ -1480,9 +1526,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
TestBed.configureTestingModule({declarations: [Parent, Child]});
loadTranslations({
'Child content {$startTagNgContent}{$closeTagNgContent}':
'Contenu enfant {$startTagNgContent}{$closeTagNgContent}',
'and projection from {$icu}': 'et projection depuis {$icu}'
'Child content {$START_TAG_NG_CONTENT}{$CLOSE_TAG_NG_CONTENT}':
'Contenu enfant {$START_TAG_NG_CONTENT}{$CLOSE_TAG_NG_CONTENT}',
'and projection from {$ICU}': 'et projection depuis {$ICU}'
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
@ -1498,24 +1544,30 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it(`shouldn't project deleted projections in i18n blocks`, () => {
@Component(
{selector: 'child', template: `<div i18n>Child content <ng-content></ng-content></div>`})
@Component({
selector: 'child',
template: `<div i18n>Child content <ng-content></ng-content></div>`
})
class Child {
}
@Component({selector: 'parent', template: `<child i18n>and projection from {{name}}</child>`})
@Component({
selector: 'parent',
template: `<child i18n>and projection from {{name}}</child>`
})
class Parent {
name: string = 'Parent';
}
TestBed.configureTestingModule({declarations: [Parent, Child]});
loadTranslations({
'Child content {$startTagNgContent}{$closeTagNgContent}': 'Contenu enfant',
'and projection from {$interpolation}': 'et projection depuis {$interpolation}'
'Child content {$START_TAG_NG_CONTENT}{$CLOSE_TAG_NG_CONTENT}': 'Contenu enfant',
'and projection from {$INTERPOLATION}': 'et projection depuis {$INTERPOLATION}'
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual(`<child><div>Contenu enfant</div></child>`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`<child><div>Contenu enfant</div></child>`);
});
it('should display/destroy projected i18n content', () => {
@ -1599,8 +1651,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
TestBed.configureTestingModule({declarations: [TextDirective, DivQuery]});
loadTranslations({
'{$startTagNgTemplate}{$startTagDiv_1}{$startTagDiv}{$startTagSpan}Content{$closeTagSpan}{$closeTagDiv}{$closeTagDiv}{$closeTagNgTemplate}':
'{$startTagNgTemplate}Contenu{$closeTagNgTemplate}'
'{$START_TAG_NG_TEMPLATE}{$START_TAG_DIV_1}{$START_TAG_DIV}{$START_TAG_SPAN}Content{$CLOSE_TAG_SPAN}{$CLOSE_TAG_DIV}{$CLOSE_TAG_DIV}{$CLOSE_TAG_NG_TEMPLATE}':
'{$START_TAG_NG_TEMPLATE}Contenu{$CLOSE_TAG_NG_TEMPLATE}'
});
const fixture = initWithTemplate(AppComp, `
<div-query #q i18n>
@ -1647,7 +1699,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
data = false;
}
TestBed.configureTestingModule({declarations: [DialogDir, CloseBtn, ContentElementDialog]});
TestBed.configureTestingModule(
{declarations: [DialogDir, CloseBtn, ContentElementDialog]});
const fixture = TestBed.createComponent(ContentElementDialog);
fixture.detectChanges();
@ -1655,7 +1708,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
"ng-reflect-ng-if": "false"
}--></div><button ng-reflect-dialog-result="true" title="Close dialog">Button label</button>`);
});
});
});
function initWithTemplate(compType: Type<any>, template: string) {
TestBed.overrideComponent(compType, {set: {template}});

View File

@ -20,10 +20,10 @@ describe('ViewContainerRef', () => {
const TRANSLATIONS: any = {
'Bar': 'o',
'{$startTagBefore}{$closeTagBefore}{$startTagDiv}{$startTagInside}{$closeTagInside}{$closeTagDiv}{$startTagAfter}{$closeTagAfter}':
'F{$startTagDiv}{$closeTagDiv}o',
'{$startTagBefore}{$closeTagBefore}{$startTagDiv}{$startTagIn}{$closeTagIn}{$closeTagDiv}{$startTagAfter}{$closeTagAfter}':
'{$startTagDiv}{$closeTagDiv}{$startTagBefore}{$closeTagBefore}'
'{$START_TAG_BEFORE}{$CLOSE_TAG_BEFORE}{$START_TAG_DIV}{$START_TAG_INSIDE}{$CLOSE_TAG_INSIDE}{$CLOSE_TAG_DIV}{$START_TAG_AFTER}{$CLOSE_TAG_AFTER}':
'F{$START_TAG_DIV}{$CLOSE_TAG_DIV}o',
'{$START_TAG_BEFORE}{$CLOSE_TAG_BEFORE}{$START_TAG_DIV}{$START_TAG_IN}{$CLOSE_TAG_IN}{$CLOSE_TAG_DIV}{$START_TAG_AFTER}{$CLOSE_TAG_AFTER}':
'{$START_TAG_DIV}{$CLOSE_TAG_DIV}{$START_TAG_BEFORE}{$CLOSE_TAG_BEFORE}'
};
/**

View File

@ -10,12 +10,12 @@ import {loadTranslations} from '@angular/localize/run_time';
export const translations = {
'What needs to be done?': `Qu'y a-t-il à faire ?`,
'{$startHeadingLevel1}todos{$closeHeadingLevel1}{$tagInput}':
'{$startHeadingLevel1}liste de tâches{$closeHeadingLevel1}{$tagInput}',
'{$START_HEADING_LEVEL1}todos{$CLOSE_HEADING_LEVEL1}{$TAG_INPUT}':
'{$START_HEADING_LEVEL1}liste de tâches{$CLOSE_HEADING_LEVEL1}{$TAG_INPUT}',
'{VAR_PLURAL, plural, =1 {item left} other {items left}}':
'{VAR_PLURAL, plural, =1 {tâche restante} other {tâches restantes}}',
'{$startTagStrong}{$interpolation}{$closeTagStrong}{$icu}':
'{$startTagStrong}{$interpolation}{$closeTagStrong} {$icu}',
'{$START_TAG_STRONG}{$INTERPOLATION}{$CLOSE_TAG_STRONG}{$ICU}':
'{$START_TAG_STRONG}{$INTERPOLATION}{$CLOSE_TAG_STRONG} {$ICU}',
' Clear completed ': ' Effacer terminés ',
'Demonstrate Components': 'Démontrer les components',
'Demonstrate Structural Directives': 'Démontrer les directives structurelles',