feat(ivy): support ng-content
in runtime i18n translations (#30782)
Added a new syntax for projections (`¤` will represent `ng-content` nodes) so that we can treat them specifically. When we enter an i18n block with the instruction `i18nStart`, a new `delayProjection` variable is set to true to prevent the instruction `projection` from projecting the nodes. Once we reach the `i18nEnd` instruction and encounter a projection in the translation we will project its nodes. If a projection was removed from a translation, then its nodes won't be projected at all. The variable `delayProjection` is restored to `false` at the end of `i18nEnd` so that it doesn't stop projections outside of i18n blocks. FW-1261 #resolve PR Close #30782
This commit is contained in:

committed by
Miško Hevery

parent
337b6fe003
commit
00cc905b98
@ -7,38 +7,42 @@
|
||||
*/
|
||||
|
||||
import '../util/ng_i18n_closure_mode';
|
||||
|
||||
import {getPluralCase} from '../i18n/localization';
|
||||
import {SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS, getTemplateContent} from '../sanitization/html_sanitizer';
|
||||
import {InertBodyHelper} from '../sanitization/inert_body';
|
||||
import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer';
|
||||
import {addAllToArray} from '../util/array_utils';
|
||||
import {assertDataInRange, assertDefined, assertEqual, assertGreaterThan} from '../util/assert';
|
||||
|
||||
import {attachPatchData} from './context_discovery';
|
||||
import {elementAttributeInternal, ɵɵload, ɵɵtextBinding} from './instructions/all';
|
||||
import {elementAttributeInternal, setDelayProjection, ɵɵload, ɵɵtextBinding} from './instructions/all';
|
||||
import {attachI18nOpCodesDebug} from './instructions/lview_debug';
|
||||
import {allocExpando, elementPropertyInternal, getOrCreateTNode, setInputsForProperty} from './instructions/shared';
|
||||
import {LContainer, NATIVE} from './interfaces/container';
|
||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n';
|
||||
import {TElementNode, TIcuContainerNode, TNode, TNodeType} from './interfaces/node';
|
||||
import {TElementNode, TIcuContainerNode, TNode, TNodeType, TProjectionNode} from './interfaces/node';
|
||||
import {RComment, RElement, RText} from './interfaces/renderer';
|
||||
import {SanitizerFn} from './interfaces/sanitization';
|
||||
import {StylingContext} from './interfaces/styling';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from './interfaces/view';
|
||||
import {appendChild, createTextNode, nativeRemoveNode} from './node_manipulation';
|
||||
import {appendChild, appendProjectedNodes, createTextNode, nativeRemoveNode} from './node_manipulation';
|
||||
import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from './state';
|
||||
import {NO_CHANGE} from './tokens';
|
||||
import {renderStringify} from './util/misc_utils';
|
||||
import {findComponentView} from './util/view_traversal_utils';
|
||||
import {getNativeByIndex, getNativeByTNode, getTNode, isLContainer} from './util/view_utils';
|
||||
|
||||
|
||||
const MARKER = `<EFBFBD>`;
|
||||
const ICU_BLOCK_REGEXP = /^\s*(<28>\d+:?\d*<2A>)\s*,\s*(select|plural)\s*,/;
|
||||
const SUBTEMPLATE_REGEXP = /<2F>\/?\*(\d+:\d+)<29>/gi;
|
||||
const PH_REGEXP = /<2F>(\/?[#*]\d+):?\d*<2A>/gi;
|
||||
const PH_REGEXP = /<2F>(\/?[#*!]\d+):?\d*<2A>/gi;
|
||||
const BINDING_REGEXP = /<2F>(\d+):?\d*<2A>/gi;
|
||||
const ICU_REGEXP = /({\s*<2A>\d+:?\d*<2A>\s*,\s*\S{6}\s*,[\s\S]*})/gi;
|
||||
const enum TagType {
|
||||
ELEMENT = '#',
|
||||
TEMPLATE = '*',
|
||||
PROJECTION = '!',
|
||||
}
|
||||
|
||||
// i18nPostprocess consts
|
||||
const ROOT_TEMPLATE_ID = 0;
|
||||
@ -340,6 +344,10 @@ const parentIndexStack: number[] = [];
|
||||
* and end of DOM element that were embedded in the original translation block. The placeholder
|
||||
* `index` points to the element index in the template instructions set. An optional `block` that
|
||||
* matches the sub-template in which it was declared.
|
||||
* - `<60>!{index}(:{block})<29>`/`<60>/!{index}(:{block})<29>`: *Projection Placeholder*: Marks the
|
||||
* beginning and end of <ng-content> that was embedded in the original translation block.
|
||||
* The placeholder `index` points to the element index in the template instructions set.
|
||||
* An optional `block` that matches the sub-template in which it was declared.
|
||||
* - `<60>*{index}:{block}<7D>`/`<60>/*{index}:{block}<7D>`: *Sub-template Placeholder*: Sub-templates must be
|
||||
* split up and translated separately in each angular template function. The `index` points to the
|
||||
* `template` instruction index. A `block` that matches the sub-template in which it was declared.
|
||||
@ -354,6 +362,8 @@ export function ɵɵi18nStart(index: number, message: string, subTemplateIndex?:
|
||||
const tView = getLView()[TVIEW];
|
||||
ngDevMode && assertDefined(tView, `tView should be defined`);
|
||||
i18nIndexStack[++i18nIndexStackPointer] = index;
|
||||
// We need to delay projections until `i18nEnd`
|
||||
setDelayProjection(true);
|
||||
if (tView.firstTemplatePass && tView.data[index + HEADER_OFFSET] === null) {
|
||||
i18nStartFirstPass(tView, index, message, subTemplateIndex);
|
||||
}
|
||||
@ -398,7 +408,7 @@ function i18nStartFirstPass(
|
||||
// Odd indexes are placeholders (elements and sub-templates)
|
||||
if (value.charAt(0) === '/') {
|
||||
// It is a closing tag
|
||||
if (value.charAt(1) === '#') {
|
||||
if (value.charAt(1) === TagType.ELEMENT) {
|
||||
const phIndex = parseInt(value.substr(2), 10);
|
||||
parentIndex = parentIndexStack[--parentIndexPointer];
|
||||
createOpCodes.push(phIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd);
|
||||
@ -410,7 +420,7 @@ function i18nStartFirstPass(
|
||||
phIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
||||
parentIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild);
|
||||
|
||||
if (value.charAt(0) === '#') {
|
||||
if (value.charAt(0) === TagType.ELEMENT) {
|
||||
parentIndexStack[++parentIndexPointer] = parentIndex = phIndex;
|
||||
}
|
||||
}
|
||||
@ -508,6 +518,14 @@ function appendI18nNode(tNode: TNode, parentTNode: TNode, previousTNode: TNode |
|
||||
cursor = cursor.next;
|
||||
}
|
||||
|
||||
// If the placeholder to append is a projection, we need to move the projected nodes instead
|
||||
if (tNode.type === TNodeType.Projection) {
|
||||
const tProjectionNode = tNode as TProjectionNode;
|
||||
appendProjectedNodes(
|
||||
viewData, tProjectionNode, tProjectionNode.projection, findComponentView(viewData));
|
||||
return tNode;
|
||||
}
|
||||
|
||||
appendChild(getNativeByTNode(tNode, viewData), tNode, viewData);
|
||||
|
||||
const slotValue = viewData[tNode.index];
|
||||
@ -632,6 +650,8 @@ export function ɵɵi18nEnd(): void {
|
||||
const tView = getLView()[TVIEW];
|
||||
ngDevMode && assertDefined(tView, `tView should be defined`);
|
||||
i18nEndFirstPass(tView);
|
||||
// Stop delaying projections
|
||||
setDelayProjection(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,7 +12,6 @@ import {appendProjectedNodes} from '../node_manipulation';
|
||||
import {getProjectAsAttrValue, isNodeMatchingSelectorList, isSelectorInSelectorList} from '../node_selector_matcher';
|
||||
import {getLView, setIsNotParent} from '../state';
|
||||
import {findComponentView} from '../util/view_traversal_utils';
|
||||
|
||||
import {getOrCreateTNode} from './shared';
|
||||
|
||||
|
||||
@ -103,6 +102,11 @@ export function ɵɵprojectionDef(projectionSlots?: ProjectionSlots): void {
|
||||
}
|
||||
}
|
||||
|
||||
let delayProjection = false;
|
||||
export function setDelayProjection(value: boolean) {
|
||||
delayProjection = value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts previously re-distributed projected nodes. This instruction must be preceded by a call
|
||||
@ -127,6 +131,9 @@ export function ɵɵprojection(
|
||||
// `<ng-content>` has no content
|
||||
setIsNotParent();
|
||||
|
||||
// re-distribution of projectable nodes is stored on a component's view level
|
||||
appendProjectedNodes(lView, tProjectionNode, selectorIndex, findComponentView(lView));
|
||||
// We might need to delay the projection of nodes if they are in the middle of an i18n block
|
||||
if (!delayProjection) {
|
||||
// re-distribution of projectable nodes is stored on a component's view level
|
||||
appendProjectedNodes(lView, tProjectionNode, selectorIndex, findComponentView(lView));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user