diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts
index 453498d1f4..3812841552 100644
--- a/packages/core/src/render3/i18n.ts
+++ b/packages/core/src/render3/i18n.ts
@@ -395,21 +395,19 @@ function i18nStartFirstPass(
}
} else {
// Even indexes are text (including bindings & ICU expressions)
- const parts = value.split(ICU_REGEXP);
+ const parts = extractParts(value);
for (let j = 0; j < parts.length; j++) {
- value = parts[j];
-
if (j & 1) {
// Odd indexes are ICU expressions
// Create the comment node that will anchor the ICU expression
allocExpando(viewData);
const icuNodeIndex = tView.blueprint.length - 1 - HEADER_OFFSET;
createOpCodes.push(
- COMMENT_MARKER, ngDevMode ? `ICU ${icuNodeIndex}` : '',
+ COMMENT_MARKER, ngDevMode ? `ICU ${icuNodeIndex}` : '', icuNodeIndex,
parentIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild);
// Update codes for the ICU expression
- const icuExpression = parseICUBlock(value.substr(1, value.length - 2));
+ const icuExpression = parts[j] as IcuExpression;
const mask = getBindingMask(icuExpression);
icuStart(icuExpressions, icuExpression, icuNodeIndex, icuNodeIndex);
// Since this is recursive, the last TIcu that was pushed is the one we want
@@ -422,19 +420,21 @@ function i18nStartFirstPass(
mask, // mask of all the bindings of this ICU expression
2, // skip 2 opCodes if not changed
icuNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate, tIcuIndex);
- } else if (value !== '') {
+ } else if (parts[j] !== '') {
+ const text = parts[j] as string;
// Even indexes are text (including bindings)
- const hasBinding = value.match(BINDING_REGEXP);
+ const hasBinding = text.match(BINDING_REGEXP);
// Create text nodes
allocExpando(viewData);
+ const textNodeIndex = tView.blueprint.length - 1 - HEADER_OFFSET;
createOpCodes.push(
// If there is a binding, the value will be set during update
- hasBinding ? '' : value,
+ hasBinding ? '' : text, textNodeIndex,
parentIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild);
if (hasBinding) {
addAllToArray(
- generateBindingUpdateOpCodes(value, tView.blueprint.length - 1 - HEADER_OFFSET),
+ generateBindingUpdateOpCodes(text, tView.blueprint.length - 1 - HEADER_OFFSET),
updateOpCodes);
}
}
@@ -580,8 +580,7 @@ function i18nEndFirstPass(tView: TView) {
// The last placeholder that was added before `i18nEnd`
const previousOrParentTNode = getPreviousOrParentTNode();
- const visitedPlaceholders =
- readCreateOpCodes(rootIndex, tI18n.create, tI18n.expandoStartIndex, viewData);
+ const visitedPlaceholders = readCreateOpCodes(rootIndex, tI18n.create, tI18n.icus, viewData);
// Remove deleted placeholders
// The last placeholder that was added before `i18nEnd` is `previousOrParentTNode`
@@ -593,7 +592,7 @@ function i18nEndFirstPass(tView: TView) {
}
function readCreateOpCodes(
- index: number, createOpCodes: I18nMutateOpCodes, expandoStartIndex: number,
+ index: number, createOpCodes: I18nMutateOpCodes, icus: TIcu[] | null,
viewData: LView): number[] {
const renderer = getLView()[RENDERER];
let currentTNode: TNode|null = null;
@@ -603,10 +602,10 @@ function readCreateOpCodes(
const opCode = createOpCodes[i];
if (typeof opCode == 'string') {
const textRNode = createTextNode(opCode, renderer);
+ const textNodeIndex = createOpCodes[++i] as number;
ngDevMode && ngDevMode.rendererCreateTextNode++;
previousTNode = currentTNode;
- currentTNode =
- createNodeAtIndex(expandoStartIndex++, TNodeType.Element, textRNode, null, null);
+ currentTNode = createNodeAtIndex(textNodeIndex, TNodeType.Element, textRNode, null, null);
setIsParent(false);
} else if (typeof opCode == 'number') {
switch (opCode & I18nMutateOpCode.MASK_OPCODE) {
@@ -658,14 +657,15 @@ function readCreateOpCodes(
switch (opCode) {
case COMMENT_MARKER:
const commentValue = createOpCodes[++i] as string;
+ const commentNodeIndex = createOpCodes[++i] as number;
ngDevMode && assertEqual(
typeof commentValue, 'string',
`Expected "${commentValue}" to be a comment node value`);
const commentRNode = renderer.createComment(commentValue);
ngDevMode && ngDevMode.rendererCreateComment++;
previousTNode = currentTNode;
- currentTNode = createNodeAtIndex(
- expandoStartIndex++, TNodeType.IcuContainer, commentRNode, null, null);
+ currentTNode =
+ createNodeAtIndex(commentNodeIndex, TNodeType.IcuContainer, commentRNode, null, null);
attachPatchData(commentRNode, viewData);
(currentTNode as TIcuContainerNode).activeCaseIndex = null;
// We will add the case nodes later, during the update phase
@@ -673,6 +673,7 @@ function readCreateOpCodes(
break;
case ELEMENT_MARKER:
const tagNameValue = createOpCodes[++i] as string;
+ const elementNodeIndex = createOpCodes[++i] as number;
ngDevMode && assertEqual(
typeof tagNameValue, 'string',
`Expected "${tagNameValue}" to be an element node tag name`);
@@ -680,7 +681,7 @@ function readCreateOpCodes(
ngDevMode && ngDevMode.rendererCreateElement++;
previousTNode = currentTNode;
currentTNode = createNodeAtIndex(
- expandoStartIndex++, TNodeType.Element, elementRNode, tagNameValue, null);
+ elementNodeIndex, TNodeType.Element, elementRNode, tagNameValue, null);
break;
default:
throw new Error(`Unable to determine the type of mutate operation for "${opCode}"`);
@@ -759,7 +760,7 @@ function readUpdateOpCodes(
icuTNode.activeCaseIndex = caseIndex !== -1 ? caseIndex : null;
// Add the nodes for the new case
- readCreateOpCodes(-1, tIcu.create[caseIndex], tIcu.expandoStartIndex, viewData);
+ readCreateOpCodes(-1, tIcu.create[caseIndex], icus, viewData);
caseCreated = true;
break;
case I18nUpdateOpCode.IcuUpdate:
@@ -1400,7 +1401,7 @@ function parseNodes(
icuCase.vars--;
} else {
icuCase.create.push(
- ELEMENT_MARKER, tagName,
+ ELEMENT_MARKER, tagName, newIndex,
parentIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild);
const elAttrs = element.attributes;
for (let i = 0; i < elAttrs.length; i++) {
@@ -1446,7 +1447,7 @@ function parseNodes(
const value = currentNode.textContent || '';
const hasBinding = value.match(BINDING_REGEXP);
icuCase.create.push(
- hasBinding ? '' : value,
+ hasBinding ? '' : value, newIndex,
parentIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild);
icuCase.remove.push(newIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove);
if (hasBinding) {
@@ -1461,7 +1462,7 @@ function parseNodes(
const newLocal = ngDevMode ? `nested ICU ${nestedIcuIndex}` : '';
// Create the comment node that will anchor the ICU expression
icuCase.create.push(
- COMMENT_MARKER, newLocal,
+ COMMENT_MARKER, newLocal, newIndex,
parentIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild);
const nestedIcu = nestedIcus[nestedIcuIndex];
nestedIcusToCreate.push([nestedIcu, newIndex]);
diff --git a/packages/core/test/i18n_integration_spec.ts b/packages/core/test/i18n_integration_spec.ts
index a0babb5886..e4d2f3bbf0 100644
--- a/packages/core/test/i18n_integration_spec.ts
+++ b/packages/core/test/i18n_integration_spec.ts
@@ -50,6 +50,8 @@ const TRANSLATIONS: any = {
'{$startTagNgTemplate}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan_1}Bonjour{$closeTagSpan}{$closeTagNgContainer}',
'{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}':
'{VAR_SELECT, select, 10 {dix} 20 {vingt} other {autres}}',
+ '{VAR_SELECT, select, 1 {one} 2 {two} other {more than two}}':
+ '{VAR_SELECT, select, 1 {un} 2 {deux} other {plus que deux}}',
'{VAR_SELECT, select, 10 {10 - {$startBoldText}ten{$closeBoldText}} 20 {20 - {$startItalicText}twenty{$closeItalicText}} other {{$startTagDiv}{$startUnderlinedText}other{$closeUnderlinedText}{$closeTagDiv}}}':
'{VAR_SELECT, select, 10 {10 - {$startBoldText}dix{$closeBoldText}} 20 {20 - {$startItalicText}vingt{$closeItalicText}} other {{$startTagDiv}{$startUnderlinedText}autres{$closeUnderlinedText}{$closeTagDiv}}}',
'{VAR_SELECT_2, select, 10 {ten - {VAR_SELECT, select, 1 {one} 2 {two} other {more than two}}} 20 {twenty - {VAR_SELECT_1, select, 1 {one} 2 {two} other {more than two}}} other {other}}':
@@ -384,23 +386,21 @@ onlyInIvy('Ivy i18n logic').describe('i18n', function() {
expect(italicTags[0].innerHTML).toBe('vingt');
});
- fixmeIvy('FW-905: Multiple ICUs in one i18n block are not processed')
- .it('should handle multiple ICUs in one block', () => {
- const template = `
+ it('should handle multiple ICUs in one block', () => {
+ const template = `
{age, select, 10 {ten} 20 {twenty} other {other}} -
{count, select, 1 {one} 2 {two} other {more than two}}
`;
- const fixture = getFixtureWithOverrides({template});
+ const fixture = getFixtureWithOverrides({template});
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('vingt - deux');
- });
+ const element = fixture.nativeElement.firstChild;
+ expect(element).toHaveText('vingt - deux');
+ });
- fixmeIvy('FW-906: Multiple ICUs wrapped in HTML tags in one i18n block throw an error')
- .it('should handle multiple ICUs in one i18n block wrapped in HTML elements', () => {
- const template = `
+ it('should handle multiple ICUs in one i18n block wrapped in HTML elements', () => {
+ const template = `
{age, select, 10 {ten} 20 {twenty} other {other}}
@@ -410,14 +410,14 @@ onlyInIvy('Ivy i18n logic').describe('i18n', function() {
`;
- const fixture = getFixtureWithOverrides({template});
+ const fixture = getFixtureWithOverrides({template});
- const element = fixture.nativeElement.firstChild;
- const spans = element.getElementsByTagName('span');
- expect(spans.length).toBe(2);
- expect(spans[0].innerHTML).toBe('vingt');
- expect(spans[1].innerHTML).toBe('deux');
- });
+ const element = fixture.nativeElement.firstChild;
+ const spans = element.getElementsByTagName('span');
+ expect(spans.length).toBe(2);
+ expect(spans[0]).toHaveText('vingt');
+ expect(spans[1]).toHaveText('deux');
+ });
it('should handle ICUs inside a template in i18n block', () => {
const template = `
diff --git a/packages/core/test/render3/i18n_spec.ts b/packages/core/test/render3/i18n_spec.ts
index 0db5621dc7..f4ad2fe136 100644
--- a/packages/core/test/render3/i18n_spec.ts
+++ b/packages/core/test/render3/i18n_spec.ts
@@ -84,8 +84,10 @@ describe('Runtime i18n', () => {
expect(opCodes).toEqual({
vars: 1,
expandoStartIndex: nbConsts,
- create:
- ['simple text', index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
+ create: [
+ 'simple text', nbConsts,
+ index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
+ ],
update: [],
icus: null
});
@@ -106,20 +108,25 @@ describe('Runtime i18n', () => {
expandoStartIndex: nbConsts,
create: [
'Hello ',
+ nbConsts,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
elementIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'world',
+ nbConsts + 1,
elementIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
elementIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
' and ',
+ nbConsts + 2,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
elementIndex2 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'universe',
+ nbConsts + 3,
elementIndex2 << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
elementIndex2 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
'!',
+ nbConsts + 4,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
],
update: [],
@@ -136,7 +143,8 @@ describe('Runtime i18n', () => {
expect(opCodes).toEqual({
vars: 1,
expandoStartIndex: nbConsts,
- create: ['', index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
+ create:
+ ['', nbConsts, index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
update: [
0b1, // bindings mask
4, // if no update, skip 4
@@ -157,7 +165,8 @@ describe('Runtime i18n', () => {
expect(opCodes).toEqual({
vars: 1,
expandoStartIndex: nbConsts,
- create: ['', index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
+ create:
+ ['', nbConsts, index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
update: [
0b11, // bindings mask
8, // if no update, skip 8
@@ -192,10 +201,12 @@ describe('Runtime i18n', () => {
expandoStartIndex: nbConsts,
create: [
'',
+ nbConsts,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
2 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'!',
+ nbConsts + 1,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
],
update: [
@@ -223,10 +234,12 @@ describe('Runtime i18n', () => {
spanElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'before',
+ nbConsts,
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
bElementSubTemplate << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'after',
+ nbConsts + 1,
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
spanElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
],
@@ -249,6 +262,7 @@ describe('Runtime i18n', () => {
bElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'middle',
+ nbConsts,
bElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
bElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
],
@@ -268,7 +282,7 @@ describe('Runtime i18n', () => {
const opCodes = getOpCodes(() => { i18nStart(index, MSG_DIV); }, null, nbConsts, index);
const tIcuIndex = 0;
const icuCommentNodeIndex = index + 1;
- const firstTextNode = index + 2;
+ const firstTextNodeIndex = index + 2;
const bElementNodeIndex = index + 3;
const iElementNodeIndex = index + 3;
const spanElementNodeIndex = index + 3;
@@ -279,7 +293,7 @@ describe('Runtime i18n', () => {
vars: 5,
expandoStartIndex: nbConsts,
create: [
- COMMENT_MARKER, 'ICU 1',
+ COMMENT_MARKER, 'ICU 1', icuCommentNodeIndex,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
],
update: [
@@ -300,49 +314,53 @@ describe('Runtime i18n', () => {
create: [
[
'no ',
+ firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
ELEMENT_MARKER,
'b',
+ bElementNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
bElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Attr,
'title',
'none',
'emails',
+ innerTextNode,
bElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'!',
+ lastTextNode,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
],
[
- 'one ',
+ 'one ', firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
- ELEMENT_MARKER, 'i',
+ ELEMENT_MARKER, 'i', iElementNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
- 'email',
+ 'email', innerTextNode,
iElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
],
[
- '',
+ '', firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
- ELEMENT_MARKER, 'span',
+ ELEMENT_MARKER, 'span', spanElementNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
- 'emails',
+ 'emails', innerTextNode,
spanElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
]
],
remove: [
[
- firstTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
+ firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
bElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
lastTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
],
[
- firstTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
+ firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
iElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
],
[
- firstTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
+ firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
spanElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
]
@@ -354,7 +372,7 @@ describe('Runtime i18n', () => {
3, // skip 3 if not changed
-1, // binding index
' ', // text string to concatenate to the binding value
- firstTextNode << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
+ firstTextNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
0b10, // mask for the title attribute binding
4, // skip 4 if not changed
-2, // binding index
@@ -380,10 +398,10 @@ describe('Runtime i18n', () => {
const index = 0;
const opCodes = getOpCodes(() => { i18nStart(index, MSG_DIV); }, null, nbConsts, index);
const icuCommentNodeIndex = index + 1;
- const firstTextNode = index + 2;
+ const firstTextNodeIndex = index + 2;
const nestedIcuCommentNodeIndex = index + 3;
- const lastTextNode = index + 4;
- const nestedTextNode = index + 5;
+ const lastTextNodeIndex = index + 4;
+ const nestedTextNodeIndex = index + 5;
const tIcuIndex = 1;
const nestedTIcuIndex = 0;
@@ -391,7 +409,7 @@ describe('Runtime i18n', () => {
vars: 6,
expandoStartIndex: nbConsts,
create: [
- COMMENT_MARKER, 'ICU 1',
+ COMMENT_MARKER, 'ICU 1', icuCommentNodeIndex,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
],
update: [
@@ -407,27 +425,30 @@ describe('Runtime i18n', () => {
{
type: 0,
vars: [1, 1, 1],
- expandoStartIndex: lastTextNode + 1,
+ expandoStartIndex: lastTextNodeIndex + 1,
childIcus: [[], [], []],
cases: ['cat', 'dog', 'other'],
create: [
[
- 'cats', nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
+ 'cats', nestedTextNodeIndex, nestedIcuCommentNodeIndex
+ << I18nMutateOpCode.SHIFT_PARENT |
I18nMutateOpCode.AppendChild
],
[
- 'dogs', nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
+ 'dogs', nestedTextNodeIndex, nestedIcuCommentNodeIndex
+ << I18nMutateOpCode.SHIFT_PARENT |
I18nMutateOpCode.AppendChild
],
[
- 'animals', nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
+ 'animals', nestedTextNodeIndex, nestedIcuCommentNodeIndex
+ << I18nMutateOpCode.SHIFT_PARENT |
I18nMutateOpCode.AppendChild
]
],
remove: [
- [nestedTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
- [nestedTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
- [nestedTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove]
+ [nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
+ [nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
+ [nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove]
],
update: [[], [], []]
},
@@ -439,23 +460,23 @@ describe('Runtime i18n', () => {
cases: ['0', 'other'],
create: [
[
- 'zero',
+ 'zero', firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
],
[
- '',
+ '', firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
- COMMENT_MARKER, 'nested ICU 0',
+ COMMENT_MARKER, 'nested ICU 0', nestedIcuCommentNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
- '!',
+ '!', lastTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
]
],
remove: [
- [firstTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
+ [firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
[
- firstTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
- lastTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
+ firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
+ lastTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
0 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
]
@@ -467,7 +488,7 @@ describe('Runtime i18n', () => {
3, // skip 3 if not changed
-1, // binding index
' ', // text string to concatenate to the binding value
- firstTextNode << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
+ firstTextNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
0b10, // mask for inner ICU main binding
3, // skip 3 if not changed
-2, // inner ICU main binding
@@ -656,6 +677,45 @@ describe('Runtime i18n', () => {
expect(fixture.html).toEqual('');
});
+ it('for multiple ICU expressions', () => {
+ const MSG_DIV = `{�0�, plural,
+ =0 {no emails!}
+ =1 {one email}
+ other {�0� emails}
+ } - {�0�, select,
+ other {(�0�)}
+ }`;
+ const fixture = prepareFixture(() => {
+ elementStart(0, 'div');
+ i18n(1, MSG_DIV);
+ elementEnd();
+ }, null, 2);
+
+ // Template should be empty because there is no update template function
+ expect(fixture.html).toEqual(' -
');
+ });
+
+ it('for multiple ICU expressions inside html', () => {
+ const MSG_DIV = `�#2�{�0�, plural,
+ =0 {no emails!}
+ =1 {one email}
+ other {�0� emails}
+ }�/#2��#3�{�0�, select,
+ other {(�0�)}
+ }�/#3�`;
+ const fixture = prepareFixture(() => {
+ elementStart(0, 'div');
+ i18nStart(1, MSG_DIV);
+ element(2, 'span');
+ element(3, 'span');
+ i18nEnd();
+ elementEnd();
+ }, null, 4);
+
+ // Template should be empty because there is no update template function
+ expect(fixture.html).toEqual('
');
+ });
+
it('for ICU expressions inside templates', () => {
const MSG_DIV = `�*2:1��#1:1�{�0:1�, plural,
=0 {no emails!}
@@ -1014,6 +1074,118 @@ describe('Runtime i18n', () => {
expect(fixture.html).toEqual('no emails!
');
});
+ it('for multiple ICU expressions', () => {
+ const MSG_DIV = `{�0�, plural,
+ =0 {no emails!}
+ =1 {one email}
+ other {�0� emails}
+ } - {�0�, select,
+ other {(�0�)}
+ }`;
+ const ctx = {value0: 0, value1: 'emails label'};
+
+ const fixture = prepareFixture(
+ () => {
+ elementStart(0, 'div');
+ i18n(1, MSG_DIV);
+ elementEnd();
+ },
+ () => {
+ i18nExp(bind(ctx.value0));
+ i18nExp(bind(ctx.value1));
+ i18nApply(1);
+ },
+ 2, 2);
+ expect(fixture.html)
+ .toEqual('no emails! - (0)
');
+
+ // Change detection cycle, no model changes
+ fixture.update();
+ expect(fixture.html)
+ .toEqual('no emails! - (0)
');
+
+ ctx.value0 = 1;
+ fixture.update();
+ expect(fixture.html).toEqual('one email - (1)
');
+
+ ctx.value0 = 10;
+ fixture.update();
+ expect(fixture.html)
+ .toEqual(
+ '10 emails - (10)
');
+
+ ctx.value1 = '10 emails';
+ fixture.update();
+ expect(fixture.html)
+ .toEqual(
+ '10 emails - (10)
');
+
+ ctx.value0 = 0;
+ fixture.update();
+ expect(fixture.html)
+ .toEqual('no emails! - (0)
');
+ });
+
+ it('for multiple ICU expressions', () => {
+ const MSG_DIV = `�#2�{�0�, plural,
+ =0 {no emails!}
+ =1 {one email}
+ other {�0� emails}
+ }�/#2��#3�{�0�, select,
+ other {(�0�)}
+ }�/#3�`;
+ const ctx = {value0: 0, value1: 'emails label'};
+
+ const fixture = prepareFixture(
+ () => {
+ elementStart(0, 'div');
+ i18nStart(1, MSG_DIV);
+ element(2, 'span');
+ element(3, 'span');
+ i18nEnd();
+ elementEnd();
+ },
+ () => {
+ i18nExp(bind(ctx.value0));
+ i18nExp(bind(ctx.value1));
+ i18nApply(1);
+ },
+ 4, 2);
+ expect(fixture.html)
+ .toEqual(
+ 'no emails!(0)
');
+
+ // Change detection cycle, no model changes
+ fixture.update();
+ expect(fixture.html)
+ .toEqual(
+ 'no emails!(0)
');
+
+ ctx.value0 = 1;
+ fixture.update();
+ expect(fixture.html)
+ .toEqual(
+ 'one email(1)
');
+
+ ctx.value0 = 10;
+ fixture.update();
+ expect(fixture.html)
+ .toEqual(
+ '10 emails(10)
');
+
+ ctx.value1 = '10 emails';
+ fixture.update();
+ expect(fixture.html)
+ .toEqual(
+ '10 emails(10)
');
+
+ ctx.value0 = 0;
+ fixture.update();
+ expect(fixture.html)
+ .toEqual(
+ 'no emails!(0)
');
+ });
+
it('for nested ICU expressions', () => {
const MSG_DIV = `{�0�, plural,
=0 {zero}