From 7315a68ac667b776ee28a702ae846c25111f922c Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Tue, 12 Mar 2019 16:33:14 +0100 Subject: [PATCH] fix(ivy): only remove missing placeholders with runtime i18n (#29252) Dynamic nodes are created at the end of the view stack, but we were removing all the placeholders between `i18nStart` and the last created node index, instead of removing everything between `i18nStart` and `i18nEnd`. This caused errors when dynamic nodes where created in multiple i18n blocks because we would remove all of the dynamic nodes created in the previous i18n blocks. PR Close #29252 --- packages/core/src/render3/i18n.ts | 9 ++-- packages/core/test/i18n_integration_spec.ts | 11 +++++ packages/core/test/render3/i18n_spec.ts | 49 +++++++++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 3349450dce..60ec580fe2 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -649,16 +649,14 @@ function i18nEndFirstPass(tView: TView) { const tI18n = tView.data[rootIndex + HEADER_OFFSET] as TI18n; ngDevMode && assertDefined(tI18n, `You should call i18nStart before i18nEnd`); - // The last placeholder that was added before `i18nEnd` - const previousOrParentTNode = getPreviousOrParentTNode(); - const visitedNodes = readCreateOpCodes(rootIndex, tI18n.create, tI18n.icus, viewData); - // Find the last node that was added before `i18nEnd` - let lastCreatedNode = previousOrParentTNode; + let lastCreatedNode = getPreviousOrParentTNode(); if (lastCreatedNode.child) { lastCreatedNode = findLastNode(lastCreatedNode.child); } + const visitedNodes = readCreateOpCodes(rootIndex, tI18n.create, tI18n.icus, viewData); + // Remove deleted nodes for (let i = rootIndex + 1; i <= lastCreatedNode.index - HEADER_OFFSET; i++) { if (visitedNodes.indexOf(i) === -1) { @@ -719,7 +717,6 @@ function readCreateOpCodes( currentTNode !, `You need to create or select a node before you can insert it into the DOM`); previousTNode = appendI18nNode(currentTNode !, destinationTNode, previousTNode); - destinationTNode.next = null; break; case I18nMutateOpCode.Select: const nodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF; diff --git a/packages/core/test/i18n_integration_spec.ts b/packages/core/test/i18n_integration_spec.ts index e04bc596e0..e89b15139c 100644 --- a/packages/core/test/i18n_integration_spec.ts +++ b/packages/core/test/i18n_integration_spec.ts @@ -610,4 +610,15 @@ onlyInIvy('Ivy i18n logic').describe('i18n', function() { .toEqual(`Contenu`); }); }); + + it('should handle multiple i18n sections', () => { + const template = ` +
Section 1
+
Section 2
+
Section 3
+ `; + const fixture = getFixtureWithOverrides({template}); + expect(fixture.nativeElement.innerHTML) + .toBe('
Section 1
Section 2
Section 3
'); + }); }); diff --git a/packages/core/test/render3/i18n_spec.ts b/packages/core/test/render3/i18n_spec.ts index a23584352e..1bd175fd6a 100644 --- a/packages/core/test/render3/i18n_spec.ts +++ b/packages/core/test/render3/i18n_spec.ts @@ -1285,6 +1285,55 @@ describe('Runtime i18n', () => { `
trad 1hellotrad
`); }); + it('should support multiple sibling i18n blocks', () => { + // Translated template: + //
+ //
Section 1
+ //
Section 2
+ //
Section 3
+ //
+ + const MSG_DIV_1 = `Section 1`; + const MSG_DIV_2 = `Section 2`; + const MSG_DIV_3 = `Section 3`; + + class MyApp { + static ngComponentDef = defineComponent({ + type: MyApp, + selectors: [['my-app']], + factory: () => new MyApp(), + consts: 7, + vars: 0, + template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { + elementStart(1, 'div'); + { i18n(2, MSG_DIV_1); } + elementEnd(); + elementStart(3, 'div'); + { i18n(4, MSG_DIV_2); } + elementEnd(); + elementStart(5, 'div'); + { i18n(6, MSG_DIV_3); } + elementEnd(); + } + elementEnd(); + } + if (rf & RenderFlags.Update) { + i18nApply(2); + i18nApply(4); + i18nApply(6); + } + } + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(fixture.html) + .toEqual(`
Section 1
Section 2
Section 3
`); + }); + it('should support attribute translations on removed elements', () => { // Translated template: //