refactor(ivy): cleanup runtime i18n code (#24805)

Fixes #24785

PR Close #24805
This commit is contained in:
Olivier Combe 2018-07-09 20:59:43 +02:00 committed by Victor Berchet
parent c0e3852384
commit 72dd10f78f
2 changed files with 200 additions and 128 deletions

View File

@ -9,7 +9,7 @@
import {assertEqual, assertLessThan} from './assert'; import {assertEqual, assertLessThan} from './assert';
import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions'; import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions';
import {RENDER_PARENT} from './interfaces/container'; 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 {BINDING_INDEX, HEADER_OFFSET, TVIEW} from './interfaces/view';
import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation'; import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation';
import {stringify} from './util'; import {stringify} from './util';
@ -22,8 +22,10 @@ export const enum I18nInstructions {
Text = 1 << 29, Text = 1 << 29,
Element = 2 << 29, Element = 2 << 29,
Expression = 3 << 29, Expression = 3 << 29,
CloseNode = 4 << 29, TemplateRoot = 4 << 29,
RemoveNode = 5 << 29, Any = 5 << 29,
CloseNode = 6 << 29,
RemoveNode = 7 << 29,
/** Used to decode the number encoded with the instruction. */ /** Used to decode the number encoded with the instruction. */
IndexMask = (1 << 29) - 1, IndexMask = (1 << 29) - 1,
/** Used to test the type of instruction. */ /** Used to test the type of instruction. */
@ -46,7 +48,7 @@ export type I18nExpInstruction = number | string;
export type PlaceholderMap = { export type PlaceholderMap = {
[name: string]: number [name: string]: number
}; };
const i18nTagRegex = /\{\$([^}]+)\}/g; const i18nTagRegex = /{\$([^}]+)}/g;
/** /**
* Takes a translation string, the initial list of placeholders (elements and expressions) and the * Takes a translation string, the initial list of placeholders (elements and expressions) and the
@ -62,8 +64,8 @@ const i18nTagRegex = /\{\$([^}]+)\}/g;
* their indexes. * their indexes.
* @param expressions An array containing, for each template, the maps of expression placeholders * @param expressions An array containing, for each template, the maps of expression placeholders
* and their indexes. * and their indexes.
* @param tmplContainers An array of template container placeholders whose content should be ignored * @param templateRoots An array of template roots whose content should be ignored when
* when generating the instructions for their parent template. * 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 * @param lastChildIndex The index of the last child of the i18n node. Used when the i18n block is
* an ng-container. * an ng-container.
* *
@ -71,13 +73,13 @@ const i18nTagRegex = /\{\$([^}]+)\}/g;
*/ */
export function i18nMapping( export function i18nMapping(
translation: string, elements: (PlaceholderMap | null)[] | null, translation: string, elements: (PlaceholderMap | null)[] | null,
expressions?: (PlaceholderMap | null)[] | null, tmplContainers?: string[] | null, expressions?: (PlaceholderMap | null)[] | null, templateRoots?: string[] | null,
lastChildIndex?: number | null): I18nInstruction[][] { lastChildIndex?: number | null): I18nInstruction[][] {
const translationParts = translation.split(i18nTagRegex); const translationParts = translation.split(i18nTagRegex);
const instructions: I18nInstruction[][] = []; const instructions: I18nInstruction[][] = [];
generateMappingInstructions( generateMappingInstructions(
0, translationParts, instructions, elements, expressions, tmplContainers, lastChildIndex); 0, translationParts, instructions, elements, expressions, templateRoots, lastChildIndex);
return instructions; return instructions;
} }
@ -96,8 +98,8 @@ export function i18nMapping(
* their indexes. * their indexes.
* @param expressions An array containing, for each template, the maps of expression placeholders * @param expressions An array containing, for each template, the maps of expression placeholders
* and their indexes. * and their indexes.
* @param tmplContainers An array of template container placeholders whose content should be ignored * @param templateRoots An array of template roots whose content should be ignored when
* when generating the instructions for their parent template. * 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 * @param lastChildIndex The index of the last child of the i18n node. Used when the i18n block is
* an ng-container. * an ng-container.
* @returns the current index in `translationParts` * @returns the current index in `translationParts`
@ -105,12 +107,16 @@ export function i18nMapping(
function generateMappingInstructions( function generateMappingInstructions(
index: number, translationParts: string[], instructions: I18nInstruction[][], index: number, translationParts: string[], instructions: I18nInstruction[][],
elements: (PlaceholderMap | null)[] | null, expressions?: (PlaceholderMap | null)[] | null, 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 tmplIndex = instructions.length;
const tmplInstructions: I18nInstruction[] = []; const tmplInstructions: I18nInstruction[] = [];
const phVisited = []; const phVisited: string[] = [];
let openedTagCount = 0; let openedTagCount = 0;
let maxIndex = 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); instructions.push(tmplInstructions);
@ -120,22 +126,27 @@ function generateMappingInstructions(
// Odd indexes are placeholders // Odd indexes are placeholders
if (index & 1) { if (index & 1) {
let phIndex; let phIndex;
if (currentElements && currentElements[value] !== undefined) {
if (elements && elements[tmplIndex] && phIndex = currentElements[value];
typeof(phIndex = elements[tmplIndex] ![value]) !== 'undefined') {
// The placeholder represents a DOM element // The placeholder represents a DOM element
// Add an instruction to move the element // Add an instruction to move the 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); tmplInstructions.push(phIndex | I18nInstructions.Element);
phVisited.push(value);
openedTagCount++; openedTagCount++;
} else if ( }
expressions && expressions[tmplIndex] && phVisited.push(value);
typeof(phIndex = expressions[tmplIndex] ![value]) !== 'undefined') { } else if (currentExpressions && currentExpressions[value] !== undefined) {
phIndex = currentExpressions[value];
// The placeholder represents an expression // The placeholder represents an expression
// Add an instruction to move the expression // Add an instruction to move the expression
tmplInstructions.push(phIndex | I18nInstructions.Expression); tmplInstructions.push(phIndex | I18nInstructions.Expression);
phVisited.push(value); phVisited.push(value);
} else { // It is a closing tag } else {
// It is a closing tag
tmplInstructions.push(I18nInstructions.CloseNode); tmplInstructions.push(I18nInstructions.CloseNode);
if (tmplIndex > 0) { if (tmplIndex > 0) {
@ -148,14 +159,14 @@ function generateMappingInstructions(
} }
} }
if (typeof phIndex !== 'undefined' && phIndex > maxIndex) { if (phIndex !== undefined && phIndex > maxIndex) {
maxIndex = phIndex; maxIndex = phIndex;
} }
if (tmplContainers && tmplContainers.indexOf(value) !== -1 && if (templateRoots && templateRoots.indexOf(value) !== -1 &&
tmplContainers.indexOf(value) >= tmplIndex) { templateRoots.indexOf(value) >= tmplIndex) {
index = generateMappingInstructions( index = generateMappingInstructions(
index, translationParts, instructions, elements, expressions, tmplContainers, index, translationParts, instructions, elements, expressions, templateRoots,
lastChildIndex); 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) { if (elements) {
const tmplElements = elements[tmplIndex]; 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) { if (expressions) {
const tmplExpressions = expressions[tmplIndex]; const tmplExpressions = expressions[tmplIndex];
@ -222,9 +233,7 @@ function generateMappingInstructions(
if (ngDevMode) { if (ngDevMode) {
assertLessThan(i.toString(2).length, 28, `Index ${i} is too big and will overflow`); 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 tmplInstructions.push(i | I18nInstructions.Any);
// their children, all we need to do is to append them
tmplInstructions.push(i | I18nInstructions.Expression);
} }
} }
@ -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 // Template containers also have a comment node for the `ViewContainerRef` that should be moved
if (node.tNode.type === TNodeType.Container && node.dynamicLContainerNode) { 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); appendChild(parentNode, node.dynamicLContainerNode.native || null, viewData);
if (firstTemplatePass) { if (firstTemplatePass) {
node.tNode.dynamicContainerNode = node.dynamicLContainerNode.tNode; node.tNode.dynamicContainerNode = node.dynamicLContainerNode.tNode;
@ -302,8 +309,10 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]):
localParentNode = element; localParentNode = element;
break; break;
case I18nInstructions.Expression: case I18nInstructions.Expression:
const expr: LNode = load(instruction & I18nInstructions.IndexMask); case I18nInstructions.TemplateRoot:
localPreviousNode = appendI18nNode(expr, localParentNode, localPreviousNode); case I18nInstructions.Any:
const node: LNode = load(instruction & I18nInstructions.IndexMask);
localPreviousNode = appendI18nNode(node, localParentNode, localPreviousNode);
break; break;
case I18nInstructions.Text: case I18nInstructions.Text:
if (ngDevMode) { if (ngDevMode) {

View File

@ -9,8 +9,8 @@
import {NgForOfContext} from '@angular/common'; import {NgForOfContext} from '@angular/common';
import {Component} from '../../src/core'; import {Component} from '../../src/core';
import {defineComponent} from '../../src/render3/definition'; import {defineComponent} from '../../src/render3/definition';
import {i18nApply, i18nExpMapping, i18nInterpolation, i18nInterpolationV, i18nMapping} from '../../src/render3/i18n'; import {I18nExpInstruction, I18nInstruction, 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 {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 {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgForOf} from './common_with_def'; import {NgForOf} from './common_with_def';
import {ComponentFixture, TemplateFixture} from './render_util'; 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). // Open tag placeholders are never re-used (closing tag placeholders can be).
const MSG_DIV_SECTION_1 = const MSG_DIV_SECTION_1 =
`{$START_C}trad 1{$END_C}{$START_A}trad 2{$START_B}trad 3{$END_B}{$END_A}`; `{$START_C}trad 1{$END_C}{$START_A}trad 2{$START_B}trad 3{$END_B}{$END_A}`;
const i18n_1 = let i18n_1: I18nInstruction[][];
i18nMapping(MSG_DIV_SECTION_1, [{START_A: 1, START_B: 2, START_REMOVE_ME: 3, START_C: 4}]);
// Initial template: // Initial template:
// <div i18n> // <div i18n>
// <a> // <a>
@ -41,19 +40,21 @@ describe('Runtime i18n', () => {
// </a> // </a>
// </div> // </div>
function createTemplate() { 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'); elementStart(0, 'div');
{ // Start of translated section 1 { // Start of translated section 1
// - i18n sections do not contain any text() instruction // - i18n sections do not contain any text() instruction
elementStart(1, 'a'); // START_A elementStart(1, 'a'); // START_A
{ {
elementStart(2, 'b'); // START_B element(2, 'b'); // START_B
elementEnd(); element(3, 'remove-me'); // START_REMOVE_ME
elementStart(3, 'remove-me'); // START_REMOVE_ME
elementEnd();
} }
elementEnd(); elementEnd();
elementStart(4, 'c'); // START_C element(4, 'c'); // START_C
elementEnd();
} // End of translated section 1 } // End of translated section 1
elementEnd(); elementEnd();
i18nApply(1, i18n_1[0]); i18nApply(1, i18n_1[0]);
@ -65,7 +66,7 @@ describe('Runtime i18n', () => {
it('should support expressions', () => { it('should support expressions', () => {
const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; 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 { class MyApp {
exp1 = '1'; exp1 = '1';
@ -86,6 +87,10 @@ describe('Runtime i18n', () => {
// </div> // </div>
template: (rf: RenderFlags, ctx: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
if (!i18n_1) {
i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 1, 'EXP_2': 2}]);
}
elementStart(0, 'div'); elementStart(0, 'div');
{ {
// Start of translated section 1 // Start of translated section 1
@ -121,7 +126,7 @@ describe('Runtime i18n', () => {
it('should support expressions on removed nodes', () => { it('should support expressions on removed nodes', () => {
const MSG_DIV_SECTION_1 = `message`; const MSG_DIV_SECTION_1 = `message`;
const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 1}]); let i18n_1: I18nInstruction[][];
class MyApp { class MyApp {
exp1 = '1'; exp1 = '1';
@ -141,6 +146,10 @@ describe('Runtime i18n', () => {
// </div> // </div>
template: (rf: RenderFlags, ctx: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
if (!i18n_1) {
i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 1}]);
}
elementStart(0, 'div'); elementStart(0, 'div');
{ {
// Start of translated section 1 // Start of translated section 1
@ -172,7 +181,7 @@ describe('Runtime i18n', () => {
it('should support expressions in attributes', () => { it('should support expressions in attributes', () => {
const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; 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 { class MyApp {
exp1: any = '1'; exp1: any = '1';
@ -189,10 +198,7 @@ describe('Runtime i18n', () => {
// <div i18n i18n-title title="start {{exp2}} middle {{exp1}} end"></div> // <div i18n i18n-title title="start {{exp2}} middle {{exp1}} end"></div>
template: (rf: RenderFlags, ctx: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'div'); element(0, 'div'); // translated section 1
// Start of translated section 1
// End of translated section 1
elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'title', i18nInterpolation(i18n_1, 2, ctx.exp1, ctx.exp2)); 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', () => { 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_DIV_SECTION_1 = `{$EXP_1} {$START_P}trad {$EXP_2}{$END_P}`;
const MSG_ATTR_1 = `start {$EXP_2} middle {$EXP_1} end`; const MSG_ATTR_1 = `start {$EXP_2} middle {$EXP_1} end`;
const i18n_1 = i18nMapping( let i18n_1: I18nInstruction[][];
MSG_DIV_SECTION_1, let i18n_2: I18nExpInstruction[];
[{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});
class MyApp { class MyApp {
exp1 = '1'; exp1 = '1';
@ -255,16 +258,28 @@ describe('Runtime i18n', () => {
// </div> // </div>
template: (rf: RenderFlags, ctx: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { 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'); elementStart(0, 'div');
{ {
// Start of translated section 1 // Start of translated section 1
text(1); // EXP_1 text(1); // EXP_1
elementStart(2, 'remove-me-1'); // START_REMOVE_ME_1 elementStart(2, 'remove-me-1'); // START_REMOVE_ME_1
{ {
elementStart(3, 'remove-me-2'); // START_REMOVE_ME_2 element(3, 'remove-me-2'); // START_REMOVE_ME_2
elementEnd(); element(4, 'remove-me-3'); // START_REMOVE_ME_3
elementStart(4, 'remove-me-3'); // START_REMOVE_ME_3
elementEnd();
} }
elementEnd(); elementEnd();
elementStart(5, 'p'); // START_P elementStart(5, 'p'); // START_P
@ -305,9 +320,9 @@ describe('Runtime i18n', () => {
const MSG_DIV_SECTION_1 = `trad {$EXP_1}`; const MSG_DIV_SECTION_1 = `trad {$EXP_1}`;
const MSG_DIV_SECTION_2 = `{$START_C}trad{$END_C}`; const MSG_DIV_SECTION_2 = `{$START_C}trad{$END_C}`;
const MSG_ATTR_1 = `start {$EXP_2} middle {$EXP_1} end`; const MSG_ATTR_1 = `start {$EXP_2} middle {$EXP_1} end`;
const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 2}]); let i18n_1: I18nInstruction[][];
const i18n_2 = i18nMapping(MSG_DIV_SECTION_2, [{START_C: 5}]); let i18n_2: I18nInstruction[][];
const i18n_3 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0, EXP_2: 1}); let i18n_3: I18nExpInstruction[];
class MyApp { class MyApp {
exp1 = '1'; exp1 = '1';
@ -340,6 +355,16 @@ describe('Runtime i18n', () => {
// </div> // </div>
template: (rf: RenderFlags, ctx: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { 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(0, 'div');
{ {
elementStart(1, 'a'); elementStart(1, 'a');
@ -353,8 +378,7 @@ describe('Runtime i18n', () => {
elementStart(4, 'b'); elementStart(4, 'b');
{ {
// Start of translated section 2 // Start of translated section 2
elementStart(5, 'c'); // START_C element(5, 'c'); // START_C
elementEnd();
// End of translated section 2 // End of translated section 2
} }
elementEnd(); elementEnd();
@ -393,7 +417,7 @@ describe('Runtime i18n', () => {
it('should support containers', () => { it('should support containers', () => {
const MSG_DIV_SECTION_1 = `valeur: {$EXP_1}`; const MSG_DIV_SECTION_1 = `valeur: {$EXP_1}`;
// The indexes are based on the main template function // 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 { class MyApp {
exp1 = '1'; exp1 = '1';
@ -417,6 +441,10 @@ describe('Runtime i18n', () => {
// ) after // ) after
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, myApp: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
if (!i18n_1) {
i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 0}]);
}
text(0, 'before ('); text(0, 'before (');
container(1); container(1);
text(2, ') after'); 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 // 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. // translated might also be the children of the same parent.
// This is why we need to pass the `lastChildIndex` to `i18nMapping` // 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: // Initial template:
// <div i18n> // <div i18n>
// <a></a> // <a></a>
@ -476,20 +504,20 @@ describe('Runtime i18n', () => {
// <d></d> // <d></d>
// </div> // </div>
function createTemplate() { 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(0, 'div');
{ {
elementStart(1, 'a'); element(1, 'a');
elementEnd();
{ {
// Start of translated section 1 // Start of translated section 1
elementStart(2, 'b'); // START_B element(2, 'b'); // START_B
elementEnd(); element(3, 'c'); // START_C
elementStart(3, 'c'); // START_C
elementEnd();
// End of translated section 1 // End of translated section 1
} }
elementStart(4, 'd'); element(4, 'd');
elementEnd();
} }
elementEnd(); elementEnd();
i18nApply(2, i18n_1[0]); i18nApply(2, i18n_1[0]);
@ -502,8 +530,7 @@ describe('Runtime i18n', () => {
it('should support embedded templates', () => { it('should support embedded templates', () => {
const MSG_DIV_SECTION_1 = `{$START_LI}valeur: {$EXP_1}!{$END_LI}`; const MSG_DIV_SECTION_1 = `{$START_LI}valeur: {$EXP_1}!{$END_LI}`;
// The indexes are based on each template function // The indexes are based on each template function
const i18n_1 = i18nMapping( let i18n_1: I18nInstruction[][];
MSG_DIV_SECTION_1, [{START_LI: 1}, {START_LI: 0}], [null, {EXP_1: 1}], ['START_LI']);
class MyApp { class MyApp {
items: string[] = ['1', '2']; items: string[] = ['1', '2'];
@ -522,6 +549,12 @@ describe('Runtime i18n', () => {
// </ul> // </ul>
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, myApp: MyApp) => {
if (rf & RenderFlags.Create) { 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'); elementStart(0, 'ul');
{ {
// Start of translated section 1 // Start of translated section 1
@ -581,9 +614,7 @@ describe('Runtime i18n', () => {
const MSG_DIV_SECTION_1 = const MSG_DIV_SECTION_1 =
`{$START_LI_0}valeur: {$EXP_1}!{$END_LI_0}{$START_LI_1}valeur bis: {$EXP_2}!{$END_LI_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 // The indexes are based on each template function
const i18n_1 = i18nMapping( let i18n_1: I18nInstruction[][];
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']);
class MyApp { class MyApp {
items: string[] = ['1', '2']; items: string[] = ['1', '2'];
@ -604,6 +635,13 @@ describe('Runtime i18n', () => {
// </ul> // </ul>
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, myApp: MyApp) => {
if (rf & RenderFlags.Create) { 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'); elementStart(0, 'ul');
{ {
// Start of translated section 1 // Start of translated section 1
@ -685,9 +723,7 @@ describe('Runtime i18n', () => {
it('should support nested embedded templates', () => { it('should support nested embedded templates', () => {
const MSG_DIV_SECTION_1 = `{$START_LI}{$START_SPAN}valeur: {$EXP_1}!{$END_SPAN}{$END_LI}`; const MSG_DIV_SECTION_1 = `{$START_LI}{$START_SPAN}valeur: {$EXP_1}!{$END_SPAN}{$END_LI}`;
// The indexes are based on each template function // The indexes are based on each template function
const i18n_1 = i18nMapping( let i18n_1: I18nInstruction[][];
MSG_DIV_SECTION_1, [null, {START_LI: 0}, {START_SPAN: 0}],
[{START_LI: 1}, {START_SPAN: 1}, {EXP_1: 1}], ['START_LI', 'START_SPAN']);
class MyApp { class MyApp {
items: string[] = ['1', '2']; items: string[] = ['1', '2'];
@ -710,6 +746,13 @@ describe('Runtime i18n', () => {
// </ul> // </ul>
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, myApp: MyApp) => {
if (rf & RenderFlags.Create) { 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'); elementStart(0, 'ul');
{ {
// Start of translated section 1 // Start of translated section 1
@ -792,9 +835,7 @@ describe('Runtime i18n', () => {
const MSG_DIV_SECTION_1 = const MSG_DIV_SECTION_1 =
`{$START_LI_0}début{$END_LI_0}{$START_LI_1}valeur: {$EXP_1}{$END_LI_1}fin`; `{$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 // The indexes are based on each template function
const i18n_1 = i18nMapping( let i18n_1: I18nInstruction[][];
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']);
class MyApp { class MyApp {
items: string[] = ['first', 'second']; items: string[] = ['first', 'second'];
@ -818,11 +859,17 @@ describe('Runtime i18n', () => {
// </ul> // </ul>
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, myApp: MyApp) => {
if (rf & RenderFlags.Create) { 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'); elementStart(0, 'ul');
{ {
// Start of translated section 1 // Start of translated section 1
elementStart(1, 'li'); // START_LI_0 element(1, 'li'); // START_LI_0
elementEnd();
container(2, liTemplate, null, ['ngForOf', '']); // START_LI_1 container(2, liTemplate, null, ['ngForOf', '']); // START_LI_1
elementStart(3, 'li'); // START_LI_2 elementStart(3, 'li'); // START_LI_2
{ text(4, 'delete me'); } { text(4, 'delete me'); }
@ -859,7 +906,7 @@ describe('Runtime i18n', () => {
expect(fixture.html) expect(fixture.html)
.toEqual('<ul><li>début</li><li>valeur: first</li><li>valeur: second</li>fin</ul>'); .toEqual('<ul><li>début</li><li>valeur: first</li><li>valeur: second</li>fin</ul>');
// // Change detection cycle, no model changes // Change detection cycle, no model changes
fixture.update(); fixture.update();
expect(fixture.html) expect(fixture.html)
.toEqual('<ul><li>début</li><li>valeur: first</li><li>valeur: second</li>fin</ul>'); .toEqual('<ul><li>début</li><li>valeur: first</li><li>valeur: second</li>fin</ul>');
@ -884,8 +931,7 @@ describe('Runtime i18n', () => {
it('should be able to remove containers', () => { it('should be able to remove containers', () => {
const MSG_DIV_SECTION_1 = `loop`; const MSG_DIV_SECTION_1 = `loop`;
// The indexes are based on each template function // The indexes are based on each template function
const i18n_1 = i18nMapping( let i18n_1: I18nInstruction[][];
MSG_DIV_SECTION_1, [{START_LI: 1}, {START_LI: 0}], [null, {EXP_1: 1}], ['START_LI']);
class MyApp { class MyApp {
items: string[] = ['first', 'second']; items: string[] = ['first', 'second'];
@ -905,6 +951,12 @@ describe('Runtime i18n', () => {
// </ul> // </ul>
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, myApp: MyApp) => {
if (rf & RenderFlags.Create) { 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'); elementStart(0, 'ul');
{ {
// Start of translated section 1 // Start of translated section 1
@ -982,17 +1034,9 @@ describe('Runtime i18n', () => {
const MSG_DIV_SECTION_1 = const MSG_DIV_SECTION_1 =
`{$START_CHILD}Je suis projeté depuis {$START_B}{$EXP_1}{$END_B}{$END_CHILD}`; `{$START_CHILD}Je suis projeté depuis {$START_B}{$EXP_1}{$END_B}{$END_CHILD}`;
const i18n_1 = i18nMapping( let i18n_1: I18nInstruction[][];
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}]);
const MSG_ATTR_1 = `Enfant de {$EXP_1}`; const MSG_ATTR_1 = `Enfant de {$EXP_1}`;
const i18n_2 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0}); let i18n_2: I18nExpInstruction[];
@Component({ @Component({
selector: 'parent', selector: 'parent',
@ -1017,6 +1061,21 @@ describe('Runtime i18n', () => {
factory: () => new Parent(), factory: () => new Parent(),
template: (rf: RenderFlags, cmp: Parent) => { template: (rf: RenderFlags, cmp: Parent) => {
if (rf & RenderFlags.Create) { 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'); elementStart(0, 'div');
{ {
// Start of translated section 1 // Start of translated section 1
@ -1025,16 +1084,13 @@ describe('Runtime i18n', () => {
elementStart(2, 'b'); // START_B elementStart(2, 'b'); // START_B
{ {
text(3); // EXP_1 text(3); // EXP_1
elementStart(4, 'remove-me-1'); // START_REMOVE_ME_1 element(4, 'remove-me-1'); // START_REMOVE_ME_1
elementEnd();
} }
elementEnd(); elementEnd();
elementStart(5, 'remove-me-2'); // START_REMOVE_ME_2 element(5, 'remove-me-2'); // START_REMOVE_ME_2
elementEnd();
} }
elementEnd(); elementEnd();
elementStart(6, 'remove-me-3'); // START_REMOVE_ME_3 element(6, 'remove-me-3'); // START_REMOVE_ME_3
elementEnd();
// End of translated section 1 // End of translated section 1
} }
elementEnd(); elementEnd();
@ -1073,9 +1129,9 @@ describe('Runtime i18n', () => {
} }
const MSG_DIV_SECTION_1 = `Je suis projeté depuis {$EXP_1}`; 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 MSG_ATTR_1 = `Enfant de {$EXP_1}`;
const i18n_2 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0}); let i18n_2: I18nExpInstruction[];
@Component({ @Component({
selector: 'parent', selector: 'parent',
@ -1101,12 +1157,18 @@ describe('Runtime i18n', () => {
factory: () => new Parent(), factory: () => new Parent(),
template: (rf: RenderFlags, cmp: Parent) => { template: (rf: RenderFlags, cmp: Parent) => {
if (rf & RenderFlags.Create) { 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(0, 'div');
{ {
elementStart(1, 'child'); elementStart(1, 'child');
{ {
elementStart(2, 'any'); element(2, 'any');
elementEnd();
elementStart(3, 'b'); elementStart(3, 'b');
{ {
// Start of translated section 1 // Start of translated section 1
@ -1114,8 +1176,7 @@ describe('Runtime i18n', () => {
// End of translated section 1 // End of translated section 1
} }
elementEnd(); elementEnd();
elementStart(5, 'any'); element(5, 'any');
elementEnd();
} }
elementEnd(); elementEnd();
} }
@ -1174,7 +1235,7 @@ describe('Runtime i18n', () => {
} }
const MSG_DIV_SECTION_1 = `{$START_B}Bonjour{$END_B} Monde!`; 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({ @Component({
selector: 'parent', selector: 'parent',
@ -1191,11 +1252,14 @@ describe('Runtime i18n', () => {
factory: () => new Parent(), factory: () => new Parent(),
template: (rf: RenderFlags, cmp: Parent) => { template: (rf: RenderFlags, cmp: Parent) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
if (!i18n_1) {
i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{'START_B': 1}]);
}
elementStart(0, 'child'); elementStart(0, 'child');
{ {
// Start of translated section 1 // Start of translated section 1
elementStart(1, 'b'); // START_B element(1, 'b'); // START_B
elementEnd();
// End of translated section 1 // End of translated section 1
} }
elementEnd(); elementEnd();
@ -1232,7 +1296,7 @@ describe('Runtime i18n', () => {
} }
const MSG_DIV_SECTION_1 = `{$START_SPAN_0}Contenu{$END_SPAN_0}`; 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({ @Component({
selector: 'parent', selector: 'parent',
@ -1253,13 +1317,15 @@ describe('Runtime i18n', () => {
factory: () => new Parent(), factory: () => new Parent(),
template: (rf: RenderFlags, cmp: Parent) => { template: (rf: RenderFlags, cmp: Parent) => {
if (rf & RenderFlags.Create) { 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'); elementStart(0, 'child');
{ {
// Start of translated section 1 // Start of translated section 1
elementStart(1, 'span', ['title', 'keepMe']); // START_SPAN_0 element(1, 'span', ['title', 'keepMe']); // START_SPAN_0
elementEnd(); element(2, 'span', ['title', 'deleteMe']); // START_SPAN_1
elementStart(2, 'span', ['title', 'deleteMe']); // START_SPAN_1
elementEnd();
// End of translated section 1 // End of translated section 1
} }
elementEnd(); elementEnd();
@ -1276,7 +1342,7 @@ describe('Runtime i18n', () => {
it('i18nInterpolation should return the same value as i18nInterpolationV', () => { it('i18nInterpolation should return the same value as i18nInterpolationV', () => {
const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; 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 interpolation;
let interpolationV; let interpolationV;
@ -1295,10 +1361,7 @@ describe('Runtime i18n', () => {
// <div i18n i18n-title title="start {{exp2}} middle {{exp1}} end"></div> // <div i18n i18n-title title="start {{exp2}} middle {{exp1}} end"></div>
template: (rf: RenderFlags, ctx: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'div'); element(0, 'div'); // translated section 1
// Start of translated section 1
// End of translated section 1
elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
interpolation = i18nInterpolation(i18n_1, 2, ctx.exp1, ctx.exp2); interpolation = i18nInterpolation(i18n_1, 2, ctx.exp1, ctx.exp2);