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
This commit is contained in:
parent
e6117a3a49
commit
7315a68ac6
@ -649,16 +649,14 @@ function i18nEndFirstPass(tView: TView) {
|
|||||||
const tI18n = tView.data[rootIndex + HEADER_OFFSET] as TI18n;
|
const tI18n = tView.data[rootIndex + HEADER_OFFSET] as TI18n;
|
||||||
ngDevMode && assertDefined(tI18n, `You should call i18nStart before i18nEnd`);
|
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`
|
// Find the last node that was added before `i18nEnd`
|
||||||
let lastCreatedNode = previousOrParentTNode;
|
let lastCreatedNode = getPreviousOrParentTNode();
|
||||||
if (lastCreatedNode.child) {
|
if (lastCreatedNode.child) {
|
||||||
lastCreatedNode = findLastNode(lastCreatedNode.child);
|
lastCreatedNode = findLastNode(lastCreatedNode.child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const visitedNodes = readCreateOpCodes(rootIndex, tI18n.create, tI18n.icus, viewData);
|
||||||
|
|
||||||
// Remove deleted nodes
|
// Remove deleted nodes
|
||||||
for (let i = rootIndex + 1; i <= lastCreatedNode.index - HEADER_OFFSET; i++) {
|
for (let i = rootIndex + 1; i <= lastCreatedNode.index - HEADER_OFFSET; i++) {
|
||||||
if (visitedNodes.indexOf(i) === -1) {
|
if (visitedNodes.indexOf(i) === -1) {
|
||||||
@ -719,7 +717,6 @@ function readCreateOpCodes(
|
|||||||
currentTNode !,
|
currentTNode !,
|
||||||
`You need to create or select a node before you can insert it into the DOM`);
|
`You need to create or select a node before you can insert it into the DOM`);
|
||||||
previousTNode = appendI18nNode(currentTNode !, destinationTNode, previousTNode);
|
previousTNode = appendI18nNode(currentTNode !, destinationTNode, previousTNode);
|
||||||
destinationTNode.next = null;
|
|
||||||
break;
|
break;
|
||||||
case I18nMutateOpCode.Select:
|
case I18nMutateOpCode.Select:
|
||||||
const nodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
|
const nodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
|
||||||
|
@ -610,4 +610,15 @@ onlyInIvy('Ivy i18n logic').describe('i18n', function() {
|
|||||||
.toEqual(`<div-query><!--ng-container-->Contenu<!--container--></div-query>`);
|
.toEqual(`<div-query><!--ng-container-->Contenu<!--container--></div-query>`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle multiple i18n sections', () => {
|
||||||
|
const template = `
|
||||||
|
<div i18n>Section 1</div>
|
||||||
|
<div i18n>Section 2</div>
|
||||||
|
<div i18n>Section 3</div>
|
||||||
|
`;
|
||||||
|
const fixture = getFixtureWithOverrides({template});
|
||||||
|
expect(fixture.nativeElement.innerHTML)
|
||||||
|
.toBe('<div>Section 1</div><div>Section 2</div><div>Section 3</div>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1285,6 +1285,55 @@ describe('Runtime i18n', () => {
|
|||||||
`<div><a>trad 1</a>hello<b title="start 2 middle 1 end"><e></e><c>trad</c></b></div>`);
|
`<div><a>trad 1</a>hello<b title="start 2 middle 1 end"><e></e><c>trad</c></b></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support multiple sibling i18n blocks', () => {
|
||||||
|
// Translated template:
|
||||||
|
// <div>
|
||||||
|
// <div i18n>Section 1</div>
|
||||||
|
// <div i18n>Section 2</div>
|
||||||
|
// <div i18n>Section 3</div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
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(`<div><div>Section 1</div><div>Section 2</div><div>Section 3</div></div>`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should support attribute translations on removed elements', () => {
|
it('should support attribute translations on removed elements', () => {
|
||||||
// Translated template:
|
// Translated template:
|
||||||
// <div i18n i18n-title title="start {{exp2}} middle {{exp1}} end">
|
// <div i18n i18n-title title="start {{exp2}} middle {{exp1}} end">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user