diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index fdcbee7dff..279c8d5b37 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -9,7 +9,7 @@ import {assertEqual, assertLessThan} from './assert'; import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions'; import {RENDER_PARENT} from './interfaces/container'; -import {LContainerNode, LElementNode, LNode, TContainerNode, TElementNode, TNodeType} from './interfaces/node'; +import {LContainerNode, LNode, TContainerNode, TElementNode, TNodeType} from './interfaces/node'; import {BINDING_INDEX, HEADER_OFFSET, TVIEW} from './interfaces/view'; import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation'; import {stringify} from './util'; @@ -22,8 +22,10 @@ export const enum I18nInstructions { Text = 1 << 29, Element = 2 << 29, Expression = 3 << 29, - CloseNode = 4 << 29, - RemoveNode = 5 << 29, + TemplateRoot = 4 << 29, + Any = 5 << 29, + CloseNode = 6 << 29, + RemoveNode = 7 << 29, /** Used to decode the number encoded with the instruction. */ IndexMask = (1 << 29) - 1, /** Used to test the type of instruction. */ @@ -46,7 +48,7 @@ export type I18nExpInstruction = number | string; export type PlaceholderMap = { [name: string]: number }; -const i18nTagRegex = /\{\$([^}]+)\}/g; +const i18nTagRegex = /{\$([^}]+)}/g; /** * Takes a translation string, the initial list of placeholders (elements and expressions) and the @@ -62,8 +64,8 @@ const i18nTagRegex = /\{\$([^}]+)\}/g; * their indexes. * @param expressions An array containing, for each template, the maps of expression placeholders * and their indexes. - * @param tmplContainers An array of template container placeholders whose content should be ignored - * when generating the instructions for their parent template. + * @param templateRoots An array of template roots whose content should be ignored when + * generating the instructions for their parent template. * @param lastChildIndex The index of the last child of the i18n node. Used when the i18n block is * an ng-container. * @@ -71,13 +73,13 @@ const i18nTagRegex = /\{\$([^}]+)\}/g; */ export function i18nMapping( translation: string, elements: (PlaceholderMap | null)[] | null, - expressions?: (PlaceholderMap | null)[] | null, tmplContainers?: string[] | null, + expressions?: (PlaceholderMap | null)[] | null, templateRoots?: string[] | null, lastChildIndex?: number | null): I18nInstruction[][] { const translationParts = translation.split(i18nTagRegex); const instructions: I18nInstruction[][] = []; generateMappingInstructions( - 0, translationParts, instructions, elements, expressions, tmplContainers, lastChildIndex); + 0, translationParts, instructions, elements, expressions, templateRoots, lastChildIndex); return instructions; } @@ -96,8 +98,8 @@ export function i18nMapping( * their indexes. * @param expressions An array containing, for each template, the maps of expression placeholders * and their indexes. - * @param tmplContainers An array of template container placeholders whose content should be ignored - * when generating the instructions for their parent template. + * @param templateRoots An array of template roots whose content should be ignored when + * generating the instructions for their parent template. * @param lastChildIndex The index of the last child of the i18n node. Used when the i18n block is * an ng-container. * @returns the current index in `translationParts` @@ -105,12 +107,16 @@ export function i18nMapping( function generateMappingInstructions( index: number, translationParts: string[], instructions: I18nInstruction[][], elements: (PlaceholderMap | null)[] | null, expressions?: (PlaceholderMap | null)[] | null, - tmplContainers?: string[] | null, lastChildIndex?: number | null): number { + templateRoots?: string[] | null, lastChildIndex?: number | null): number { const tmplIndex = instructions.length; const tmplInstructions: I18nInstruction[] = []; - const phVisited = []; + const phVisited: string[] = []; let openedTagCount = 0; let maxIndex = 0; + let currentElements: PlaceholderMap|null = + elements && elements[tmplIndex] ? elements[tmplIndex] : null; + let currentExpressions: PlaceholderMap|null = + expressions && expressions[tmplIndex] ? expressions[tmplIndex] : null; instructions.push(tmplInstructions); @@ -120,22 +126,27 @@ function generateMappingInstructions( // Odd indexes are placeholders if (index & 1) { let phIndex; - - if (elements && elements[tmplIndex] && - typeof(phIndex = elements[tmplIndex] ![value]) !== 'undefined') { + if (currentElements && currentElements[value] !== undefined) { + phIndex = currentElements[value]; // The placeholder represents a DOM element // Add an instruction to move the element - tmplInstructions.push(phIndex | I18nInstructions.Element); + const isTemplateRoot = templateRoots && templateRoots[tmplIndex] === value; + if (isTemplateRoot) { + // This is a template root, it has no closing tag, not treating it as an element + tmplInstructions.push(phIndex | I18nInstructions.TemplateRoot); + } else { + tmplInstructions.push(phIndex | I18nInstructions.Element); + openedTagCount++; + } phVisited.push(value); - openedTagCount++; - } else if ( - expressions && expressions[tmplIndex] && - typeof(phIndex = expressions[tmplIndex] ![value]) !== 'undefined') { + } else if (currentExpressions && currentExpressions[value] !== undefined) { + phIndex = currentExpressions[value]; // The placeholder represents an expression // Add an instruction to move the expression tmplInstructions.push(phIndex | I18nInstructions.Expression); phVisited.push(value); - } else { // It is a closing tag + } else { + // It is a closing tag tmplInstructions.push(I18nInstructions.CloseNode); if (tmplIndex > 0) { @@ -148,14 +159,14 @@ function generateMappingInstructions( } } - if (typeof phIndex !== 'undefined' && phIndex > maxIndex) { + if (phIndex !== undefined && phIndex > maxIndex) { maxIndex = phIndex; } - if (tmplContainers && tmplContainers.indexOf(value) !== -1 && - tmplContainers.indexOf(value) >= tmplIndex) { + if (templateRoots && templateRoots.indexOf(value) !== -1 && + templateRoots.indexOf(value) >= tmplIndex) { index = generateMappingInstructions( - index, translationParts, instructions, elements, expressions, tmplContainers, + index, translationParts, instructions, elements, expressions, templateRoots, lastChildIndex); } @@ -165,7 +176,7 @@ function generateMappingInstructions( } } - // Check if some elements from the template are missing from the translation + // Add instructions to remove elements that are not used in the translation if (elements) { const tmplElements = elements[tmplIndex]; @@ -188,7 +199,7 @@ function generateMappingInstructions( } } - // Check if some expressions from the template are missing from the translation + // Add instructions to remove expressions that are not used in the translation if (expressions) { const tmplExpressions = expressions[tmplIndex]; @@ -222,9 +233,7 @@ function generateMappingInstructions( if (ngDevMode) { assertLessThan(i.toString(2).length, 28, `Index ${i} is too big and will overflow`); } - // We consider those additional placeholders as expressions because we don't care about - // their children, all we need to do is to append them - tmplInstructions.push(i | I18nInstructions.Expression); + tmplInstructions.push(i | I18nInstructions.Any); } } @@ -258,8 +267,6 @@ function appendI18nNode(node: LNode, parentNode: LNode, previousNode: LNode) { // Template containers also have a comment node for the `ViewContainerRef` that should be moved if (node.tNode.type === TNodeType.Container && node.dynamicLContainerNode) { - // (node.native as RComment).textContent = 'test'; - // console.log(node.native); appendChild(parentNode, node.dynamicLContainerNode.native || null, viewData); if (firstTemplatePass) { node.tNode.dynamicContainerNode = node.dynamicLContainerNode.tNode; @@ -302,8 +309,10 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]): localParentNode = element; break; case I18nInstructions.Expression: - const expr: LNode = load(instruction & I18nInstructions.IndexMask); - localPreviousNode = appendI18nNode(expr, localParentNode, localPreviousNode); + case I18nInstructions.TemplateRoot: + case I18nInstructions.Any: + const node: LNode = load(instruction & I18nInstructions.IndexMask); + localPreviousNode = appendI18nNode(node, localParentNode, localPreviousNode); break; case I18nInstructions.Text: if (ngDevMode) { diff --git a/packages/core/test/render3/i18n_spec.ts b/packages/core/test/render3/i18n_spec.ts index b3cc3a957f..d5709c2bd4 100644 --- a/packages/core/test/render3/i18n_spec.ts +++ b/packages/core/test/render3/i18n_spec.ts @@ -9,8 +9,8 @@ import {NgForOfContext} from '@angular/common'; import {Component} from '../../src/core'; import {defineComponent} from '../../src/render3/definition'; -import {i18nApply, i18nExpMapping, i18nInterpolation, i18nInterpolationV, i18nMapping} from '../../src/render3/i18n'; -import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {I18nExpInstruction, I18nInstruction, i18nApply, i18nExpMapping, i18nInterpolation, i18nInterpolationV, i18nMapping} from '../../src/render3/i18n'; +import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {NgForOf} from './common_with_def'; import {ComponentFixture, TemplateFixture} from './render_util'; @@ -21,8 +21,7 @@ describe('Runtime i18n', () => { // Open tag placeholders are never re-used (closing tag placeholders can be). const MSG_DIV_SECTION_1 = `{$START_C}trad 1{$END_C}{$START_A}trad 2{$START_B}trad 3{$END_B}{$END_A}`; - const i18n_1 = - i18nMapping(MSG_DIV_SECTION_1, [{START_A: 1, START_B: 2, START_REMOVE_ME: 3, START_C: 4}]); + let i18n_1: I18nInstruction[][]; // Initial template: //
// @@ -41,20 +40,22 @@ describe('Runtime i18n', () => { // //
function createTemplate() { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{'START_A': 1, 'START_B': 2, 'START_REMOVE_ME': 3, 'START_C': 4}]); + } + elementStart(0, 'div'); { // Start of translated section 1 // - i18n sections do not contain any text() instruction elementStart(1, 'a'); // START_A { - elementStart(2, 'b'); // START_B - elementEnd(); - elementStart(3, 'remove-me'); // START_REMOVE_ME - elementEnd(); + element(2, 'b'); // START_B + element(3, 'remove-me'); // START_REMOVE_ME } elementEnd(); - elementStart(4, 'c'); // START_C - elementEnd(); - } // End of translated section 1 + element(4, 'c'); // START_C + } // End of translated section 1 elementEnd(); i18nApply(1, i18n_1[0]); } @@ -65,7 +66,7 @@ describe('Runtime i18n', () => { it('should support expressions', () => { const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 1, EXP_2: 2}]); + let i18n_1: I18nInstruction[][]; class MyApp { exp1 = '1'; @@ -86,6 +87,10 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 1, 'EXP_2': 2}]); + } + elementStart(0, 'div'); { // Start of translated section 1 @@ -121,7 +126,7 @@ describe('Runtime i18n', () => { it('should support expressions on removed nodes', () => { const MSG_DIV_SECTION_1 = `message`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 1}]); + let i18n_1: I18nInstruction[][]; class MyApp { exp1 = '1'; @@ -141,6 +146,10 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 1}]); + } + elementStart(0, 'div'); { // Start of translated section 1 @@ -172,7 +181,7 @@ describe('Runtime i18n', () => { it('should support expressions in attributes', () => { const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {EXP_1: 0, EXP_2: 1}); + const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {'EXP_1': 0, 'EXP_2': 1}); class MyApp { exp1: any = '1'; @@ -189,10 +198,7 @@ describe('Runtime i18n', () => { //
template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { - elementStart(0, 'div'); - // Start of translated section 1 - // End of translated section 1 - elementEnd(); + element(0, 'div'); // translated section 1 } if (rf & RenderFlags.Update) { elementProperty(0, 'title', i18nInterpolation(i18n_1, 2, ctx.exp1, ctx.exp2)); @@ -218,11 +224,8 @@ describe('Runtime i18n', () => { it('should support both html elements, expressions and expressions in attributes', () => { const MSG_DIV_SECTION_1 = `{$EXP_1} {$START_P}trad {$EXP_2}{$END_P}`; const MSG_ATTR_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, - [{START_REMOVE_ME_1: 2, START_REMOVE_ME_2: 3, START_REMOVE_ME_3: 4, START_P: 5}], - [{EXP_1: 1, EXP_2: 6, EXP_3: 7}]); - const i18n_2 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0, EXP_2: 1}); + let i18n_1: I18nInstruction[][]; + let i18n_2: I18nExpInstruction[]; class MyApp { exp1 = '1'; @@ -255,16 +258,28 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{ + 'START_REMOVE_ME_1': 2, + 'START_REMOVE_ME_2': 3, + 'START_REMOVE_ME_3': 4, + 'START_P': 5 + }], + [{'EXP_1': 1, 'EXP_2': 6, 'EXP_3': 7}]); + } + if (!i18n_2) { + i18n_2 = i18nExpMapping(MSG_ATTR_1, {'EXP_1': 0, 'EXP_2': 1}); + } + elementStart(0, 'div'); { // Start of translated section 1 text(1); // EXP_1 elementStart(2, 'remove-me-1'); // START_REMOVE_ME_1 { - elementStart(3, 'remove-me-2'); // START_REMOVE_ME_2 - elementEnd(); - elementStart(4, 'remove-me-3'); // START_REMOVE_ME_3 - elementEnd(); + element(3, 'remove-me-2'); // START_REMOVE_ME_2 + element(4, 'remove-me-3'); // START_REMOVE_ME_3 } elementEnd(); elementStart(5, 'p'); // START_P @@ -305,9 +320,9 @@ describe('Runtime i18n', () => { const MSG_DIV_SECTION_1 = `trad {$EXP_1}`; const MSG_DIV_SECTION_2 = `{$START_C}trad{$END_C}`; const MSG_ATTR_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 2}]); - const i18n_2 = i18nMapping(MSG_DIV_SECTION_2, [{START_C: 5}]); - const i18n_3 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0, EXP_2: 1}); + let i18n_1: I18nInstruction[][]; + let i18n_2: I18nInstruction[][]; + let i18n_3: I18nExpInstruction[]; class MyApp { exp1 = '1'; @@ -340,6 +355,16 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 2}]); + } + if (!i18n_2) { + i18n_2 = i18nMapping(MSG_DIV_SECTION_2, [{'START_C': 5}]); + } + if (!i18n_3) { + i18n_3 = i18nExpMapping(MSG_ATTR_1, {'EXP_1': 0, 'EXP_2': 1}); + } + elementStart(0, 'div'); { elementStart(1, 'a'); @@ -353,8 +378,7 @@ describe('Runtime i18n', () => { elementStart(4, 'b'); { // Start of translated section 2 - elementStart(5, 'c'); // START_C - elementEnd(); + element(5, 'c'); // START_C // End of translated section 2 } elementEnd(); @@ -393,7 +417,7 @@ describe('Runtime i18n', () => { it('should support containers', () => { const MSG_DIV_SECTION_1 = `valeur: {$EXP_1}`; // The indexes are based on the main template function - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 0}]); + let i18n_1: I18nInstruction[][]; class MyApp { exp1 = '1'; @@ -417,6 +441,10 @@ describe('Runtime i18n', () => { // ) after template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 0}]); + } + text(0, 'before ('); container(1); text(2, ') after'); @@ -456,7 +484,7 @@ describe('Runtime i18n', () => { // its children are not the only children of their parent, some nodes which are not // translated might also be the children of the same parent. // This is why we need to pass the `lastChildIndex` to `i18nMapping` - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{START_B: 2, START_C: 3}], null, null, 4); + let i18n_1: I18nInstruction[][]; // Initial template: //
// @@ -476,20 +504,20 @@ describe('Runtime i18n', () => { // //
function createTemplate() { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{'START_B': 2, 'START_C': 3}], null, null, 4); + } + elementStart(0, 'div'); { - elementStart(1, 'a'); - elementEnd(); + element(1, 'a'); { // Start of translated section 1 - elementStart(2, 'b'); // START_B - elementEnd(); - elementStart(3, 'c'); // START_C - elementEnd(); + element(2, 'b'); // START_B + element(3, 'c'); // START_C // End of translated section 1 } - elementStart(4, 'd'); - elementEnd(); + element(4, 'd'); } elementEnd(); i18nApply(2, i18n_1[0]); @@ -502,8 +530,7 @@ describe('Runtime i18n', () => { it('should support embedded templates', () => { const MSG_DIV_SECTION_1 = `{$START_LI}valeur: {$EXP_1}!{$END_LI}`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [{START_LI: 1}, {START_LI: 0}], [null, {EXP_1: 1}], ['START_LI']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['1', '2']; @@ -522,6 +549,12 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{'START_LI': 1}, {'START_LI': 0}], [null, {'EXP_1': 1}], + ['START_LI']); + } + elementStart(0, 'ul'); { // Start of translated section 1 @@ -581,9 +614,7 @@ describe('Runtime i18n', () => { const MSG_DIV_SECTION_1 = `{$START_LI_0}valeur: {$EXP_1}!{$END_LI_0}{$START_LI_1}valeur bis: {$EXP_2}!{$END_LI_1}`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [null, {START_LI_0: 0}, {START_LI_1: 0}], - [{START_LI_0: 1, START_LI_1: 2}, {EXP_1: 1}, {EXP_2: 1}], ['START_LI_0', 'START_LI_1']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['1', '2']; @@ -604,6 +635,13 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, + [{'START_LI_0': 1, 'START_LI_1': 2}, {'START_LI_0': 0}, {'START_LI_1': 0}], + [null, {'EXP_1': 1}, {'EXP_2': 1}], ['START_LI_0', 'START_LI_1']); + } + elementStart(0, 'ul'); { // Start of translated section 1 @@ -685,9 +723,7 @@ describe('Runtime i18n', () => { it('should support nested embedded templates', () => { const MSG_DIV_SECTION_1 = `{$START_LI}{$START_SPAN}valeur: {$EXP_1}!{$END_SPAN}{$END_LI}`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [null, {START_LI: 0}, {START_SPAN: 0}], - [{START_LI: 1}, {START_SPAN: 1}, {EXP_1: 1}], ['START_LI', 'START_SPAN']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['1', '2']; @@ -710,6 +746,13 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, + [{'START_LI': 1}, {'START_LI': 0, 'START_SPAN': 1}, {'START_SPAN': 0}], + [null, null, {'EXP_1': 1}], ['START_LI', 'START_SPAN']); + } + elementStart(0, 'ul'); { // Start of translated section 1 @@ -792,9 +835,7 @@ describe('Runtime i18n', () => { const MSG_DIV_SECTION_1 = `{$START_LI_0}début{$END_LI_0}{$START_LI_1}valeur: {$EXP_1}{$END_LI_1}fin`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [{START_LI_0: 1, START_LI_2: 3}, {START_LI_1: 0}], - [{START_LI_1: 2}, {EXP_1: 1}], ['START_LI_1']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['first', 'second']; @@ -818,11 +859,17 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, + [{'START_LI_0': 1, 'START_LI_1': 2, 'START_LI_2': 3}, {'START_LI_1': 0}], + [null, {'EXP_1': 1}], ['START_LI_1']); + } + elementStart(0, 'ul'); { // Start of translated section 1 - elementStart(1, 'li'); // START_LI_0 - elementEnd(); + element(1, 'li'); // START_LI_0 container(2, liTemplate, null, ['ngForOf', '']); // START_LI_1 elementStart(3, 'li'); // START_LI_2 { text(4, 'delete me'); } @@ -859,7 +906,7 @@ describe('Runtime i18n', () => { expect(fixture.html) .toEqual(''); - // // Change detection cycle, no model changes + // Change detection cycle, no model changes fixture.update(); expect(fixture.html) .toEqual(''); @@ -884,8 +931,7 @@ describe('Runtime i18n', () => { it('should be able to remove containers', () => { const MSG_DIV_SECTION_1 = `loop`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [{START_LI: 1}, {START_LI: 0}], [null, {EXP_1: 1}], ['START_LI']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['first', 'second']; @@ -905,6 +951,12 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{'START_LI': 1}, {'START_LI': 0}], [null, {'EXP_1': 1}], + ['START_LI']); + } + elementStart(0, 'ul'); { // Start of translated section 1 @@ -982,17 +1034,9 @@ describe('Runtime i18n', () => { const MSG_DIV_SECTION_1 = `{$START_CHILD}Je suis projeté depuis {$START_B}{$EXP_1}{$END_B}{$END_CHILD}`; - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [{ - START_CHILD: 1, - START_B: 2, - START_REMOVE_ME_1: 4, - START_REMOVE_ME_2: 5, - START_REMOVE_ME_3: 6 - }], - [{EXP_1: 3}]); + let i18n_1: I18nInstruction[][]; const MSG_ATTR_1 = `Enfant de {$EXP_1}`; - const i18n_2 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0}); + let i18n_2: I18nExpInstruction[]; @Component({ selector: 'parent', @@ -1017,6 +1061,21 @@ describe('Runtime i18n', () => { factory: () => new Parent(), template: (rf: RenderFlags, cmp: Parent) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{ + 'START_CHILD': 1, + 'START_B': 2, + 'START_REMOVE_ME_1': 4, + 'START_REMOVE_ME_2': 5, + 'START_REMOVE_ME_3': 6 + }], + [{'EXP_1': 3}]); + } + if (!i18n_2) { + i18n_2 = i18nExpMapping(MSG_ATTR_1, {'EXP_1': 0}); + } + elementStart(0, 'div'); { // Start of translated section 1 @@ -1024,17 +1083,14 @@ describe('Runtime i18n', () => { { elementStart(2, 'b'); // START_B { - text(3); // EXP_1 - elementStart(4, 'remove-me-1'); // START_REMOVE_ME_1 - elementEnd(); + text(3); // EXP_1 + element(4, 'remove-me-1'); // START_REMOVE_ME_1 } elementEnd(); - elementStart(5, 'remove-me-2'); // START_REMOVE_ME_2 - elementEnd(); + element(5, 'remove-me-2'); // START_REMOVE_ME_2 } elementEnd(); - elementStart(6, 'remove-me-3'); // START_REMOVE_ME_3 - elementEnd(); + element(6, 'remove-me-3'); // START_REMOVE_ME_3 // End of translated section 1 } elementEnd(); @@ -1073,9 +1129,9 @@ describe('Runtime i18n', () => { } const MSG_DIV_SECTION_1 = `Je suis projeté depuis {$EXP_1}`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 4}]); + let i18n_1: I18nInstruction[][]; const MSG_ATTR_1 = `Enfant de {$EXP_1}`; - const i18n_2 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0}); + let i18n_2: I18nExpInstruction[]; @Component({ selector: 'parent', @@ -1101,12 +1157,18 @@ describe('Runtime i18n', () => { factory: () => new Parent(), template: (rf: RenderFlags, cmp: Parent) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 4}]); + } + if (!i18n_2) { + i18n_2 = i18nExpMapping(MSG_ATTR_1, {'EXP_1': 0}); + } + elementStart(0, 'div'); { elementStart(1, 'child'); { - elementStart(2, 'any'); - elementEnd(); + element(2, 'any'); elementStart(3, 'b'); { // Start of translated section 1 @@ -1114,8 +1176,7 @@ describe('Runtime i18n', () => { // End of translated section 1 } elementEnd(); - elementStart(5, 'any'); - elementEnd(); + element(5, 'any'); } elementEnd(); } @@ -1174,7 +1235,7 @@ describe('Runtime i18n', () => { } const MSG_DIV_SECTION_1 = `{$START_B}Bonjour{$END_B} Monde!`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{START_B: 1}]); + let i18n_1: I18nInstruction[][]; @Component({ selector: 'parent', @@ -1191,11 +1252,14 @@ describe('Runtime i18n', () => { factory: () => new Parent(), template: (rf: RenderFlags, cmp: Parent) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{'START_B': 1}]); + } + elementStart(0, 'child'); { // Start of translated section 1 - elementStart(1, 'b'); // START_B - elementEnd(); + element(1, 'b'); // START_B // End of translated section 1 } elementEnd(); @@ -1232,7 +1296,7 @@ describe('Runtime i18n', () => { } const MSG_DIV_SECTION_1 = `{$START_SPAN_0}Contenu{$END_SPAN_0}`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{START_SPAN_0: 1, START_SPAN_1: 2}]); + let i18n_1: I18nInstruction[][]; @Component({ selector: 'parent', @@ -1253,13 +1317,15 @@ describe('Runtime i18n', () => { factory: () => new Parent(), template: (rf: RenderFlags, cmp: Parent) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{'START_SPAN_0': 1, 'START_SPAN_1': 2}]); + } + elementStart(0, 'child'); { // Start of translated section 1 - elementStart(1, 'span', ['title', 'keepMe']); // START_SPAN_0 - elementEnd(); - elementStart(2, 'span', ['title', 'deleteMe']); // START_SPAN_1 - elementEnd(); + element(1, 'span', ['title', 'keepMe']); // START_SPAN_0 + element(2, 'span', ['title', 'deleteMe']); // START_SPAN_1 // End of translated section 1 } elementEnd(); @@ -1276,7 +1342,7 @@ describe('Runtime i18n', () => { it('i18nInterpolation should return the same value as i18nInterpolationV', () => { const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {EXP_1: 0, EXP_2: 1}); + const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {'EXP_1': 0, 'EXP_2': 1}); let interpolation; let interpolationV; @@ -1295,10 +1361,7 @@ describe('Runtime i18n', () => { //
template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { - elementStart(0, 'div'); - // Start of translated section 1 - // End of translated section 1 - elementEnd(); + element(0, 'div'); // translated section 1 } if (rf & RenderFlags.Update) { interpolation = i18nInterpolation(i18n_1, 2, ctx.exp1, ctx.exp2);