fix(core): move generated i18n statements to the consts
field of ComponentDef (#38404)
This commit updates the code to move generated i18n statements into the `consts` field of ComponentDef to avoid invoking `$localize` function before component initialization (to better support runtime translations) and also avoid problems with lazy-loading when i18n defs may not be present in a chunk where it's referenced. Prior to this change the i18n statements were generated at the top leve: ``` var I18N_0; if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { var MSG_X = goog.getMsg(“…”); I18N_0 = MSG_X; } else { I18N_0 = $localize('...'); } defineComponent({ // ... template: function App_Template(rf, ctx) { i0.ɵɵi18n(2, I18N_0); } }); ``` This commit updates the logic to generate the following code instead: ``` defineComponent({ // ... consts: function() { var I18N_0; if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { var MSG_X = goog.getMsg(“…”); I18N_0 = MSG_X; } else { I18N_0 = $localize('...'); } return [ I18N_0 ]; }, template: function App_Template(rf, ctx) { i0.ɵɵi18n(2, 0); } }); ``` Also note that i18n template instructions now refer to the `consts` array using an index (similar to other template instructions). PR Close #38404
This commit is contained in:
parent
5f90b64328
commit
cb05c0102f
File diff suppressed because it is too large
Load Diff
@ -196,10 +196,19 @@ export function compileComponentFromMetadata(
|
|||||||
// e.g. `vars: 2`
|
// e.g. `vars: 2`
|
||||||
definitionMap.set('vars', o.literal(templateBuilder.getVarCount()));
|
definitionMap.set('vars', o.literal(templateBuilder.getVarCount()));
|
||||||
|
|
||||||
// e.g. `consts: [['one', 'two'], ['three', 'four']]
|
// Generate `consts` section of ComponentDef:
|
||||||
const consts = templateBuilder.getConsts();
|
// - either as an array:
|
||||||
if (consts.length > 0) {
|
// `consts: [['one', 'two'], ['three', 'four']]`
|
||||||
definitionMap.set('consts', o.literalArr(consts));
|
// - or as a factory function in case additional statements are present (to support i18n):
|
||||||
|
// `consts: function() { var i18n_0; if (ngI18nClosureMode) {...} else {...} return [i18n_0]; }`
|
||||||
|
const {constExpressions, prepareStatements} = templateBuilder.getConsts();
|
||||||
|
if (constExpressions.length > 0) {
|
||||||
|
let constsExpr: o.LiteralArrayExpr|o.FunctionExpr = o.literalArr(constExpressions);
|
||||||
|
// Prepare statements are present - turn `consts` into a function.
|
||||||
|
if (prepareStatements.length > 0) {
|
||||||
|
constsExpr = o.fn([], [...prepareStatements, new o.ReturnStatement(constsExpr)]);
|
||||||
|
}
|
||||||
|
definitionMap.set('consts', constsExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
definitionMap.set('template', templateFunctionExpression);
|
definitionMap.set('template', templateFunctionExpression);
|
||||||
|
@ -12,10 +12,14 @@ import * as o from '../../../output/output_ast';
|
|||||||
import * as t from '../../r3_ast';
|
import * as t from '../../r3_ast';
|
||||||
|
|
||||||
/* Closure variables holding messages must be named `MSG_[A-Z0-9]+` */
|
/* Closure variables holding messages must be named `MSG_[A-Z0-9]+` */
|
||||||
const CLOSURE_TRANSLATION_PREFIX = 'MSG_';
|
const CLOSURE_TRANSLATION_VAR_PREFIX = 'MSG_';
|
||||||
|
|
||||||
/* Prefix for non-`goog.getMsg` i18n-related vars */
|
/**
|
||||||
export const TRANSLATION_PREFIX = 'I18N_';
|
* Prefix for non-`goog.getMsg` i18n-related vars.
|
||||||
|
* Note: the prefix uses lowercase characters intentionally due to a Closure behavior that
|
||||||
|
* considers variables like `I18N_0` as constants and throws an error when their value changes.
|
||||||
|
*/
|
||||||
|
export const TRANSLATION_VAR_PREFIX = 'i18n_';
|
||||||
|
|
||||||
/** Name of the i18n attributes **/
|
/** Name of the i18n attributes **/
|
||||||
export const I18N_ATTR = 'i18n';
|
export const I18N_ATTR = 'i18n';
|
||||||
@ -166,7 +170,7 @@ export function formatI18nPlaceholderName(name: string, useCamelCase: boolean =
|
|||||||
* @returns Complete translation const prefix
|
* @returns Complete translation const prefix
|
||||||
*/
|
*/
|
||||||
export function getTranslationConstPrefix(extra: string): string {
|
export function getTranslationConstPrefix(extra: string): string {
|
||||||
return `${CLOSURE_TRANSLATION_PREFIX}${extra}`.toUpperCase();
|
return `${CLOSURE_TRANSLATION_VAR_PREFIX}${extra}`.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +36,7 @@ import {I18nContext} from './i18n/context';
|
|||||||
import {createGoogleGetMsgStatements} from './i18n/get_msg_utils';
|
import {createGoogleGetMsgStatements} from './i18n/get_msg_utils';
|
||||||
import {createLocalizeStatements} from './i18n/localize_utils';
|
import {createLocalizeStatements} from './i18n/localize_utils';
|
||||||
import {I18nMetaVisitor} from './i18n/meta';
|
import {I18nMetaVisitor} from './i18n/meta';
|
||||||
import {assembleBoundTextPlaceholders, assembleI18nBoundString, declareI18nVariable, getTranslationConstPrefix, hasI18nMeta, I18N_ICU_MAPPING_PREFIX, i18nFormatPlaceholderNames, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, placeholdersToParams, TRANSLATION_PREFIX, wrapI18nPlaceholder} from './i18n/util';
|
import {assembleBoundTextPlaceholders, assembleI18nBoundString, declareI18nVariable, getTranslationConstPrefix, hasI18nMeta, I18N_ICU_MAPPING_PREFIX, i18nFormatPlaceholderNames, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, placeholdersToParams, TRANSLATION_VAR_PREFIX, wrapI18nPlaceholder} from './i18n/util';
|
||||||
import {StylingBuilder, StylingInstruction} from './styling_builder';
|
import {StylingBuilder, StylingInstruction} from './styling_builder';
|
||||||
import {asLiteral, chainedInstruction, CONTEXT_NAME, getAttrsForDirectiveMatching, getInterpolationArgsLength, IMPLICIT_REFERENCE, invalid, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, trimTrailingNulls, unsupported} from './util';
|
import {asLiteral, chainedInstruction, CONTEXT_NAME, getAttrsForDirectiveMatching, getInterpolationArgsLength, IMPLICIT_REFERENCE, invalid, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, trimTrailingNulls, unsupported} from './util';
|
||||||
|
|
||||||
@ -103,6 +103,18 @@ export function prepareEventListenerParameters(
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collects information needed to generate `consts` field of the ComponentDef.
|
||||||
|
// When a constant requires some pre-processing, the `prepareStatements` section
|
||||||
|
// contains corresponding statements.
|
||||||
|
export interface ComponentDefConsts {
|
||||||
|
prepareStatements: o.Statement[];
|
||||||
|
constExpressions: o.Expression[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createComponentDefConsts(): ComponentDefConsts {
|
||||||
|
return {prepareStatements: [], constExpressions: []};
|
||||||
|
}
|
||||||
|
|
||||||
export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
||||||
private _dataIndex = 0;
|
private _dataIndex = 0;
|
||||||
private _bindingContext = 0;
|
private _bindingContext = 0;
|
||||||
@ -171,7 +183,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
private directiveMatcher: SelectorMatcher|null, private directives: Set<o.Expression>,
|
private directiveMatcher: SelectorMatcher|null, private directives: Set<o.Expression>,
|
||||||
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>,
|
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>,
|
||||||
private _namespace: o.ExternalReference, relativeContextFilePath: string,
|
private _namespace: o.ExternalReference, relativeContextFilePath: string,
|
||||||
private i18nUseExternalIds: boolean, private _constants: o.Expression[] = []) {
|
private i18nUseExternalIds: boolean,
|
||||||
|
private _constants: ComponentDefConsts = createComponentDefConsts()) {
|
||||||
this._bindingScope = parentBindingScope.nestedScope(level);
|
this._bindingScope = parentBindingScope.nestedScope(level);
|
||||||
|
|
||||||
// Turn the relative context file path into an identifier by replacing non-alphanumeric
|
// Turn the relative context file path into an identifier by replacing non-alphanumeric
|
||||||
@ -307,12 +320,12 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
private i18nTranslate(
|
private i18nTranslate(
|
||||||
message: i18n.Message, params: {[name: string]: o.Expression} = {}, ref?: o.ReadVarExpr,
|
message: i18n.Message, params: {[name: string]: o.Expression} = {}, ref?: o.ReadVarExpr,
|
||||||
transformFn?: (raw: o.ReadVarExpr) => o.Expression): o.ReadVarExpr {
|
transformFn?: (raw: o.ReadVarExpr) => o.Expression): o.ReadVarExpr {
|
||||||
const _ref = ref || o.variable(this.constantPool.uniqueName(TRANSLATION_PREFIX));
|
const _ref = ref || this.i18nGenerateMainBlockVar();
|
||||||
// Closure Compiler requires const names to start with `MSG_` but disallows any other const to
|
// Closure Compiler requires const names to start with `MSG_` but disallows any other const to
|
||||||
// start with `MSG_`. We define a variable starting with `MSG_` just for the `goog.getMsg` call
|
// start with `MSG_`. We define a variable starting with `MSG_` just for the `goog.getMsg` call
|
||||||
const closureVar = this.i18nGenerateClosureVar(message.id);
|
const closureVar = this.i18nGenerateClosureVar(message.id);
|
||||||
const statements = getTranslationDeclStmts(message, _ref, closureVar, params, transformFn);
|
const statements = getTranslationDeclStmts(message, _ref, closureVar, params, transformFn);
|
||||||
this.constantPool.statements.push(...statements);
|
this._constants.prepareStatements.push(...statements);
|
||||||
return _ref;
|
return _ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,6 +377,12 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
return bound;
|
return bound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates top level vars for i18n blocks (i.e. `i18n_N`).
|
||||||
|
private i18nGenerateMainBlockVar(): o.ReadVarExpr {
|
||||||
|
return o.variable(this.constantPool.uniqueName(TRANSLATION_VAR_PREFIX));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates vars with Closure-specific names for i18n blocks (i.e. `MSG_XXX`).
|
||||||
private i18nGenerateClosureVar(messageId: string): o.ReadVarExpr {
|
private i18nGenerateClosureVar(messageId: string): o.ReadVarExpr {
|
||||||
let name: string;
|
let name: string;
|
||||||
const suffix = this.fileBasedI18nSuffix.toUpperCase();
|
const suffix = this.fileBasedI18nSuffix.toUpperCase();
|
||||||
@ -426,16 +445,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
private i18nStart(span: ParseSourceSpan|null = null, meta: i18n.I18nMeta, selfClosing?: boolean):
|
private i18nStart(span: ParseSourceSpan|null = null, meta: i18n.I18nMeta, selfClosing?: boolean):
|
||||||
void {
|
void {
|
||||||
const index = this.allocateDataSlot();
|
const index = this.allocateDataSlot();
|
||||||
if (this.i18nContext) {
|
this.i18n = this.i18nContext ?
|
||||||
this.i18n = this.i18nContext.forkChildContext(index, this.templateIndex!, meta);
|
this.i18nContext.forkChildContext(index, this.templateIndex!, meta) :
|
||||||
} else {
|
new I18nContext(index, this.i18nGenerateMainBlockVar(), 0, this.templateIndex, meta);
|
||||||
const ref = o.variable(this.constantPool.uniqueName(TRANSLATION_PREFIX));
|
|
||||||
this.i18n = new I18nContext(index, ref, 0, this.templateIndex, meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate i18nStart instruction
|
// generate i18nStart instruction
|
||||||
const {id, ref} = this.i18n;
|
const {id, ref} = this.i18n;
|
||||||
const params: o.Expression[] = [o.literal(index), ref];
|
const params: o.Expression[] = [o.literal(index), this.addToConsts(ref)];
|
||||||
if (id > 0) {
|
if (id > 0) {
|
||||||
// do not push 3rd argument (sub-block id)
|
// do not push 3rd argument (sub-block id)
|
||||||
// into i18nStart call for top level i18n context
|
// into i18nStart call for top level i18n context
|
||||||
@ -507,8 +523,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
}
|
}
|
||||||
if (i18nAttrArgs.length > 0) {
|
if (i18nAttrArgs.length > 0) {
|
||||||
const index: o.Expression = o.literal(this.allocateDataSlot());
|
const index: o.Expression = o.literal(this.allocateDataSlot());
|
||||||
const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true);
|
const constIndex = this.addToConsts(o.literalArr(i18nAttrArgs));
|
||||||
this.creationInstruction(sourceSpan, R3.i18nAttributes, [index, args]);
|
this.creationInstruction(sourceSpan, R3.i18nAttributes, [index, constIndex]);
|
||||||
if (hasBindings) {
|
if (hasBindings) {
|
||||||
this.updateInstruction(sourceSpan, R3.i18nApply, [index]);
|
this.updateInstruction(sourceSpan, R3.i18nApply, [index]);
|
||||||
}
|
}
|
||||||
@ -1028,7 +1044,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
return this._pureFunctionSlots;
|
return this._pureFunctionSlots;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConsts() {
|
getConsts(): ComponentDefConsts {
|
||||||
return this._constants;
|
return this._constants;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1352,14 +1368,16 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
return o.TYPED_NULL_EXPR;
|
return o.TYPED_NULL_EXPR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const consts = this._constants.constExpressions;
|
||||||
|
|
||||||
// Try to reuse a literal that's already in the array, if possible.
|
// Try to reuse a literal that's already in the array, if possible.
|
||||||
for (let i = 0; i < this._constants.length; i++) {
|
for (let i = 0; i < consts.length; i++) {
|
||||||
if (this._constants[i].isEquivalent(expression)) {
|
if (consts[i].isEquivalent(expression)) {
|
||||||
return o.literal(i);
|
return o.literal(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return o.literal(this._constants.push(expression) - 1);
|
return o.literal(consts.push(expression) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private addAttrsToConsts(attrs: o.Expression[]): o.LiteralExpr {
|
private addAttrsToConsts(attrs: o.Expression[]): o.LiteralExpr {
|
||||||
|
@ -18,7 +18,7 @@ import {stringify} from '../util/stringify';
|
|||||||
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
|
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
|
||||||
import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_LOC_ID_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields';
|
import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_LOC_ID_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields';
|
||||||
import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction} from './interfaces/definition';
|
import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction} from './interfaces/definition';
|
||||||
import {AttributeMarker, TAttributes, TConstants} from './interfaces/node';
|
import {AttributeMarker, TAttributes, TConstantsOrFactory} from './interfaces/node';
|
||||||
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
|
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
|
||||||
import {NgModuleType} from './ng_module_ref';
|
import {NgModuleType} from './ng_module_ref';
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
|
|||||||
* Constants for the nodes in the component's view.
|
* Constants for the nodes in the component's view.
|
||||||
* Includes attribute arrays, local definition arrays etc.
|
* Includes attribute arrays, local definition arrays etc.
|
||||||
*/
|
*/
|
||||||
consts?: TConstants;
|
consts?: TConstantsOrFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of `ngContent[selector]` values that were found in the template.
|
* An array of `ngContent[selector]` values that were found in the template.
|
||||||
|
@ -15,6 +15,7 @@ import {i18nAttributesFirstPass, i18nStartFirstPass} from '../i18n/i18n_parse';
|
|||||||
import {i18nPostprocess} from '../i18n/i18n_postprocess';
|
import {i18nPostprocess} from '../i18n/i18n_postprocess';
|
||||||
import {HEADER_OFFSET} from '../interfaces/view';
|
import {HEADER_OFFSET} from '../interfaces/view';
|
||||||
import {getLView, getTView, nextBindingIndex} from '../state';
|
import {getLView, getTView, nextBindingIndex} from '../state';
|
||||||
|
import {getConstant} from '../util/view_utils';
|
||||||
|
|
||||||
import {setDelayProjection} from './all';
|
import {setDelayProjection} from './all';
|
||||||
|
|
||||||
@ -42,14 +43,15 @@ import {setDelayProjection} from './all';
|
|||||||
* `template` instruction index. A `block` that matches the sub-template in which it was declared.
|
* `template` instruction index. A `block` that matches the sub-template in which it was declared.
|
||||||
*
|
*
|
||||||
* @param index A unique index of the translation in the static block.
|
* @param index A unique index of the translation in the static block.
|
||||||
* @param message The translation message.
|
* @param messageIndex An index of the translation message from the `def.consts` array.
|
||||||
* @param subTemplateIndex Optional sub-template index in the `message`.
|
* @param subTemplateIndex Optional sub-template index in the `message`.
|
||||||
*
|
*
|
||||||
* @codeGenApi
|
* @codeGenApi
|
||||||
*/
|
*/
|
||||||
export function ɵɵi18nStart(index: number, message: string, subTemplateIndex?: number): void {
|
export function ɵɵi18nStart(index: number, messageIndex: number, subTemplateIndex?: number): void {
|
||||||
const tView = getTView();
|
const tView = getTView();
|
||||||
ngDevMode && assertDefined(tView, `tView should be defined`);
|
ngDevMode && assertDefined(tView, `tView should be defined`);
|
||||||
|
const message = getConstant<string>(tView.consts, messageIndex)!;
|
||||||
pushI18nIndex(index);
|
pushI18nIndex(index);
|
||||||
// We need to delay projections until `i18nEnd`
|
// We need to delay projections until `i18nEnd`
|
||||||
setDelayProjection(true);
|
setDelayProjection(true);
|
||||||
@ -96,13 +98,13 @@ export function ɵɵi18nEnd(): void {
|
|||||||
* `template` instruction index. A `block` that matches the sub-template in which it was declared.
|
* `template` instruction index. A `block` that matches the sub-template in which it was declared.
|
||||||
*
|
*
|
||||||
* @param index A unique index of the translation in the static block.
|
* @param index A unique index of the translation in the static block.
|
||||||
* @param message The translation message.
|
* @param messageIndex An index of the translation message from the `def.consts` array.
|
||||||
* @param subTemplateIndex Optional sub-template index in the `message`.
|
* @param subTemplateIndex Optional sub-template index in the `message`.
|
||||||
*
|
*
|
||||||
* @codeGenApi
|
* @codeGenApi
|
||||||
*/
|
*/
|
||||||
export function ɵɵi18n(index: number, message: string, subTemplateIndex?: number): void {
|
export function ɵɵi18n(index: number, messageIndex: number, subTemplateIndex?: number): void {
|
||||||
ɵɵi18nStart(index, message, subTemplateIndex);
|
ɵɵi18nStart(index, messageIndex, subTemplateIndex);
|
||||||
ɵɵi18nEnd();
|
ɵɵi18nEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +116,12 @@ export function ɵɵi18n(index: number, message: string, subTemplateIndex?: numb
|
|||||||
*
|
*
|
||||||
* @codeGenApi
|
* @codeGenApi
|
||||||
*/
|
*/
|
||||||
export function ɵɵi18nAttributes(index: number, values: string[]): void {
|
export function ɵɵi18nAttributes(index: number, attrsIndex: number): void {
|
||||||
const lView = getLView();
|
const lView = getLView();
|
||||||
const tView = getTView();
|
const tView = getTView();
|
||||||
ngDevMode && assertDefined(tView, `tView should be defined`);
|
ngDevMode && assertDefined(tView, `tView should be defined`);
|
||||||
i18nAttributesFirstPass(lView, tView, index, values);
|
const attrs = getConstant<string[]>(tView.consts, attrsIndex)!;
|
||||||
|
i18nAttributesFirstPass(lView, tView, index, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} fr
|
|||||||
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container';
|
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container';
|
||||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
||||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
|
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
|
||||||
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstants, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
||||||
import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
|
import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
|
||||||
import {SanitizerFn} from '../interfaces/sanitization';
|
import {SanitizerFn} from '../interfaces/sanitization';
|
||||||
import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
|
import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
|
||||||
@ -650,7 +650,7 @@ export function createTView(
|
|||||||
type: TViewType, viewIndex: number, templateFn: ComponentTemplate<any>|null, decls: number,
|
type: TViewType, viewIndex: number, templateFn: ComponentTemplate<any>|null, decls: number,
|
||||||
vars: number, directives: DirectiveDefListOrFactory|null, pipes: PipeDefListOrFactory|null,
|
vars: number, directives: DirectiveDefListOrFactory|null, pipes: PipeDefListOrFactory|null,
|
||||||
viewQuery: ViewQueriesFunction<any>|null, schemas: SchemaMetadata[]|null,
|
viewQuery: ViewQueriesFunction<any>|null, schemas: SchemaMetadata[]|null,
|
||||||
consts: TConstants|null): TView {
|
constsOrFactory: TConstantsOrFactory|null): TView {
|
||||||
ngDevMode && ngDevMode.tView++;
|
ngDevMode && ngDevMode.tView++;
|
||||||
const bindingStartIndex = HEADER_OFFSET + decls;
|
const bindingStartIndex = HEADER_OFFSET + decls;
|
||||||
// This length does not yet contain host bindings from child directives because at this point,
|
// This length does not yet contain host bindings from child directives because at this point,
|
||||||
@ -658,6 +658,7 @@ export function createTView(
|
|||||||
// that has a host binding, we will update the blueprint with that def's hostVars count.
|
// that has a host binding, we will update the blueprint with that def's hostVars count.
|
||||||
const initialViewLength = bindingStartIndex + vars;
|
const initialViewLength = bindingStartIndex + vars;
|
||||||
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
|
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
|
||||||
|
const consts = typeof constsOrFactory === 'function' ? constsOrFactory() : constsOrFactory;
|
||||||
const tView = blueprint[TVIEW as any] = ngDevMode ?
|
const tView = blueprint[TVIEW as any] = ngDevMode ?
|
||||||
new TViewConstructor(
|
new TViewConstructor(
|
||||||
type,
|
type,
|
||||||
|
@ -10,7 +10,7 @@ import {SchemaMetadata, ViewEncapsulation} from '../../core';
|
|||||||
import {ProcessProvidersFunction} from '../../di/interface/provider';
|
import {ProcessProvidersFunction} from '../../di/interface/provider';
|
||||||
import {Type} from '../../interface/type';
|
import {Type} from '../../interface/type';
|
||||||
|
|
||||||
import {TAttributes, TConstants} from './node';
|
import {TAttributes, TConstantsOrFactory} from './node';
|
||||||
import {CssSelectorList} from './projection';
|
import {CssSelectorList} from './projection';
|
||||||
import {TView} from './view';
|
import {TView} from './view';
|
||||||
|
|
||||||
@ -299,7 +299,7 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
|
|||||||
readonly template: ComponentTemplate<T>;
|
readonly template: ComponentTemplate<T>;
|
||||||
|
|
||||||
/** Constants associated with the component's view. */
|
/** Constants associated with the component's view. */
|
||||||
readonly consts: TConstants|null;
|
readonly consts: TConstantsOrFactory|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of `ngContent[selector]` values that were found in the template.
|
* An array of `ngContent[selector]` values that were found in the template.
|
||||||
|
@ -255,9 +255,24 @@ export type TAttributes = (string|AttributeMarker|CssSelector)[];
|
|||||||
* Constants that are associated with a view. Includes:
|
* Constants that are associated with a view. Includes:
|
||||||
* - Attribute arrays.
|
* - Attribute arrays.
|
||||||
* - Local definition arrays.
|
* - Local definition arrays.
|
||||||
|
* - Translated messages (i18n).
|
||||||
*/
|
*/
|
||||||
export type TConstants = (TAttributes|string)[];
|
export type TConstants = (TAttributes|string)[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function that returns an array of consts. Consts can be represented as a function in case
|
||||||
|
* any additional statements are required to define consts in the list. An example is i18n where
|
||||||
|
* additional i18n calls are generated, which should be executed when consts are requested for the
|
||||||
|
* first time.
|
||||||
|
*/
|
||||||
|
export type TConstantsFactory = () => TConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TConstants type that describes how the `consts` field is generated on ComponentDef: it can be
|
||||||
|
* either an array or a factory function that returns that array.
|
||||||
|
*/
|
||||||
|
export type TConstantsOrFactory = TConstants|TConstantsFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binding data (flyweight) for a particular node that is shared between all templates
|
* Binding data (flyweight) for a particular node that is shared between all templates
|
||||||
* of a specific type.
|
* of a specific type.
|
||||||
|
@ -12,6 +12,7 @@ import {getTranslationForTemplate} from '@angular/core/src/render3/i18n/i18n_par
|
|||||||
import {noop} from '../../../compiler/src/render3/view/util';
|
import {noop} from '../../../compiler/src/render3/view/util';
|
||||||
import {setDelayProjection, ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all';
|
import {setDelayProjection, ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all';
|
||||||
import {I18nUpdateOpCodes, TI18n, TIcu} from '../../src/render3/interfaces/i18n';
|
import {I18nUpdateOpCodes, TI18n, TIcu} from '../../src/render3/interfaces/i18n';
|
||||||
|
import {TConstants} from '../../src/render3/interfaces/node';
|
||||||
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
|
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
|
||||||
import {getNativeByIndex} from '../../src/render3/util/view_utils';
|
import {getNativeByIndex} from '../../src/render3/util/view_utils';
|
||||||
|
|
||||||
@ -57,26 +58,29 @@ describe('Runtime i18n', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function prepareFixture(
|
function prepareFixture(
|
||||||
createTemplate: () => void, updateTemplate: (() => void)|null, nbConsts = 0,
|
createTemplate: () => void, updateTemplate: (() => void)|null, nbConsts = 0, nbVars = 0,
|
||||||
nbVars = 0): TemplateFixture {
|
consts: TConstants = []): TemplateFixture {
|
||||||
return new TemplateFixture(createTemplate, updateTemplate || noop, nbConsts, nbVars);
|
return new TemplateFixture(
|
||||||
|
createTemplate, updateTemplate || noop, nbConsts, nbVars, null, null, null, undefined,
|
||||||
|
consts);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOpCodes(
|
function getOpCodes(
|
||||||
createTemplate: () => void, updateTemplate: (() => void)|null, nbConsts: number,
|
messageOrAtrs: string|string[], createTemplate: () => void, updateTemplate: (() => void)|null,
|
||||||
index: number): TI18n|I18nUpdateOpCodes {
|
nbConsts: number, index: number): TI18n|I18nUpdateOpCodes {
|
||||||
const fixture = prepareFixture(createTemplate, updateTemplate, nbConsts);
|
const fixture =
|
||||||
|
prepareFixture(createTemplate, updateTemplate, nbConsts, undefined, [messageOrAtrs]);
|
||||||
const tView = fixture.hostView[TVIEW];
|
const tView = fixture.hostView[TVIEW];
|
||||||
return tView.data[index + HEADER_OFFSET] as TI18n;
|
return tView.data[index + HEADER_OFFSET] as TI18n;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('i18nStart', () => {
|
describe('i18nStart', () => {
|
||||||
it('for text', () => {
|
it('for text', () => {
|
||||||
const MSG_DIV = `simple text`;
|
const message = 'simple text';
|
||||||
const nbConsts = 1;
|
const nbConsts = 1;
|
||||||
const index = 0;
|
const index = 0;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(message, () => {
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, 0);
|
||||||
}, null, nbConsts, index) as TI18n;
|
}, null, nbConsts, index) as TI18n;
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
@ -91,13 +95,13 @@ describe('Runtime i18n', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('for elements', () => {
|
it('for elements', () => {
|
||||||
const MSG_DIV = `Hello <20>#2<>world<6C>/#2<> and <20>#3<>universe<73>/#3<>!`;
|
const message = `Hello <20>#2<>world<6C>/#2<> and <20>#3<>universe<73>/#3<>!`;
|
||||||
// Template: `<div>Hello <div>world</div> and <span>universe</span>!`
|
// Template: `<div>Hello <div>world</div> and <span>universe</span>!`
|
||||||
// 3 consts for the 2 divs and 1 span + 1 const for `i18nStart` = 4 consts
|
// 3 consts for the 2 divs and 1 span + 1 const for `i18nStart` = 4 consts
|
||||||
const nbConsts = 4;
|
const nbConsts = 4;
|
||||||
const index = 1;
|
const index = 1;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(message, () => {
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, 0);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
@ -124,11 +128,11 @@ describe('Runtime i18n', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('for simple bindings', () => {
|
it('for simple bindings', () => {
|
||||||
const MSG_DIV = `Hello <20>0<EFBFBD>!`;
|
const message = `Hello <20>0<EFBFBD>!`;
|
||||||
const nbConsts = 2;
|
const nbConsts = 2;
|
||||||
const index = 1;
|
const index = 1;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(message, () => {
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, 0);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect((opCodes as any).update.debug).toEqual([
|
expect((opCodes as any).update.debug).toEqual([
|
||||||
@ -148,11 +152,11 @@ describe('Runtime i18n', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('for multiple bindings', () => {
|
it('for multiple bindings', () => {
|
||||||
const MSG_DIV = `Hello <20>0<EFBFBD> and <20>1<EFBFBD>, again <20>0<EFBFBD>!`;
|
const message = `Hello <20>0<EFBFBD> and <20>1<EFBFBD>, again <20>0<EFBFBD>!`;
|
||||||
const nbConsts = 2;
|
const nbConsts = 2;
|
||||||
const index = 1;
|
const index = 1;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(message, () => {
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, 0);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
@ -176,17 +180,15 @@ describe('Runtime i18n', () => {
|
|||||||
// </span>
|
// </span>
|
||||||
// !
|
// !
|
||||||
// </div>
|
// </div>
|
||||||
const MSG_DIV =
|
const message =
|
||||||
`<EFBFBD>0<EFBFBD> is rendered as: <20>*2:1<><31>#1:1<>before<72>*2:2<><32>#1:2<>middle<6C>/#1:2<><32>/*2:2<>after<65>/#1:1<><31>/*2:1<>!`;
|
`<EFBFBD>0<EFBFBD> is rendered as: <20>*2:1<><31>#1:1<>before<72>*2:2<><32>#1:2<>middle<6C>/#1:2<><32>/*2:2<>after<65>/#1:1<><31>/*2:1<>!`;
|
||||||
|
|
||||||
/**** Root template ****/
|
/**** Root template ****/
|
||||||
// <20>0<EFBFBD> is rendered as: <20>*2:1<><31>/*2:1<>!
|
// <20>0<EFBFBD> is rendered as: <20>*2:1<><31>/*2:1<>!
|
||||||
let nbConsts = 3;
|
let nbConsts = 3;
|
||||||
let index = 1;
|
let index = 1;
|
||||||
const firstTextNode = 3;
|
let opCodes = getOpCodes(message, () => {
|
||||||
const rootTemplate = 2;
|
ɵɵi18nStart(index, 0);
|
||||||
let opCodes = getOpCodes(() => {
|
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
@ -207,10 +209,8 @@ describe('Runtime i18n', () => {
|
|||||||
// <20>#1:1<>before<72>*2:2<>middle<6C>/*2:2<>after<65>/#1:1<>
|
// <20>#1:1<>before<72>*2:2<>middle<6C>/*2:2<>after<65>/#1:1<>
|
||||||
nbConsts = 3;
|
nbConsts = 3;
|
||||||
index = 0;
|
index = 0;
|
||||||
const spanElement = 1;
|
opCodes = getOpCodes(message, () => {
|
||||||
const bElementSubTemplate = 2;
|
ɵɵi18nStart(index, 0, 1);
|
||||||
opCodes = getOpCodes(() => {
|
|
||||||
ɵɵi18nStart(index, MSG_DIV, 1);
|
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
@ -233,9 +233,8 @@ describe('Runtime i18n', () => {
|
|||||||
// middle
|
// middle
|
||||||
nbConsts = 2;
|
nbConsts = 2;
|
||||||
index = 0;
|
index = 0;
|
||||||
const bElement = 1;
|
opCodes = getOpCodes(message, () => {
|
||||||
opCodes = getOpCodes(() => {
|
ɵɵi18nStart(index, 0, 2);
|
||||||
ɵɵi18nStart(index, MSG_DIV, 2);
|
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
@ -252,15 +251,15 @@ describe('Runtime i18n', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('for ICU expressions', () => {
|
it('for ICU expressions', () => {
|
||||||
const MSG_DIV = `{<7B>0<EFBFBD>, plural,
|
const message = `{<7B>0<EFBFBD>, plural,
|
||||||
=0 {no <b title="none">emails</b>!}
|
=0 {no <b title="none">emails</b>!}
|
||||||
=1 {one <i>email</i>}
|
=1 {one <i>email</i>}
|
||||||
other {<EFBFBD>0<EFBFBD> <span title="<22>1<EFBFBD>">emails</span>}
|
other {<EFBFBD>0<EFBFBD> <span title="<22>1<EFBFBD>">emails</span>}
|
||||||
}`;
|
}`;
|
||||||
const nbConsts = 1;
|
const nbConsts = 1;
|
||||||
const index = 0;
|
const index = 0;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(message, () => {
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, 0);
|
||||||
}, null, nbConsts, index) as TI18n;
|
}, null, nbConsts, index) as TI18n;
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
@ -337,7 +336,7 @@ describe('Runtime i18n', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('for nested ICU expressions', () => {
|
it('for nested ICU expressions', () => {
|
||||||
const MSG_DIV = `{<7B>0<EFBFBD>, plural,
|
const message = `{<7B>0<EFBFBD>, plural,
|
||||||
=0 {zero}
|
=0 {zero}
|
||||||
other {<EFBFBD>0<EFBFBD> {<EFBFBD>1<EFBFBD>, select,
|
other {<EFBFBD>0<EFBFBD> {<EFBFBD>1<EFBFBD>, select,
|
||||||
cat {cats}
|
cat {cats}
|
||||||
@ -347,16 +346,9 @@ describe('Runtime i18n', () => {
|
|||||||
}`;
|
}`;
|
||||||
const nbConsts = 1;
|
const nbConsts = 1;
|
||||||
const index = 0;
|
const index = 0;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(message, () => {
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, 0);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
const icuCommentNodeIndex = index + 1;
|
|
||||||
const firstTextNodeIndex = index + 2;
|
|
||||||
const nestedIcuCommentNodeIndex = index + 3;
|
|
||||||
const lastTextNodeIndex = index + 4;
|
|
||||||
const nestedTextNodeIndex = index + 5;
|
|
||||||
const tIcuIndex = 1;
|
|
||||||
const nestedTIcuIndex = 0;
|
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 9,
|
vars: 9,
|
||||||
@ -443,31 +435,31 @@ describe('Runtime i18n', () => {
|
|||||||
|
|
||||||
describe(`i18nAttribute`, () => {
|
describe(`i18nAttribute`, () => {
|
||||||
it('for text', () => {
|
it('for text', () => {
|
||||||
const MSG_title = `Hello world!`;
|
const message = `Hello world!`;
|
||||||
const MSG_div_attr = ['title', MSG_title];
|
const attrs = ['title', message];
|
||||||
const nbConsts = 2;
|
const nbConsts = 2;
|
||||||
const index = 1;
|
const index = 1;
|
||||||
const fixture = prepareFixture(() => {
|
const fixture = prepareFixture(() => {
|
||||||
ɵɵelementStart(0, 'div');
|
ɵɵelementStart(0, 'div');
|
||||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
ɵɵi18nAttributes(index, 0);
|
||||||
ɵɵelementEnd();
|
ɵɵelementEnd();
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index, [attrs]);
|
||||||
const tView = fixture.hostView[TVIEW];
|
const tView = fixture.hostView[TVIEW];
|
||||||
const opCodes = tView.data[index + HEADER_OFFSET] as I18nUpdateOpCodes;
|
const opCodes = tView.data[index + HEADER_OFFSET] as I18nUpdateOpCodes;
|
||||||
|
|
||||||
expect(opCodes).toEqual([]);
|
expect(opCodes).toEqual([]);
|
||||||
expect(
|
expect(
|
||||||
(getNativeByIndex(0, fixture.hostView as LView) as any as Element).getAttribute('title'))
|
(getNativeByIndex(0, fixture.hostView as LView) as any as Element).getAttribute('title'))
|
||||||
.toEqual(MSG_title);
|
.toEqual(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('for simple bindings', () => {
|
it('for simple bindings', () => {
|
||||||
const MSG_title = `Hello <20>0<EFBFBD>!`;
|
const message = `Hello <20>0<EFBFBD>!`;
|
||||||
const MSG_div_attr = ['title', MSG_title];
|
const attrs = ['title', message];
|
||||||
const nbConsts = 2;
|
const nbConsts = 2;
|
||||||
const index = 1;
|
const index = 1;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(attrs, () => {
|
||||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
ɵɵi18nAttributes(index, 0);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual(debugMatch([
|
expect(opCodes).toEqual(debugMatch([
|
||||||
@ -476,12 +468,12 @@ describe('Runtime i18n', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('for multiple bindings', () => {
|
it('for multiple bindings', () => {
|
||||||
const MSG_title = `Hello <20>0<EFBFBD> and <20>1<EFBFBD>, again <20>0<EFBFBD>!`;
|
const message = `Hello <20>0<EFBFBD> and <20>1<EFBFBD>, again <20>0<EFBFBD>!`;
|
||||||
const MSG_div_attr = ['title', MSG_title];
|
const attrs = ['title', message];
|
||||||
const nbConsts = 2;
|
const nbConsts = 2;
|
||||||
const index = 1;
|
const index = 1;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(attrs, () => {
|
||||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
ɵɵi18nAttributes(index, 0);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual(debugMatch([
|
expect(opCodes).toEqual(debugMatch([
|
||||||
@ -490,12 +482,12 @@ describe('Runtime i18n', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('for multiple attributes', () => {
|
it('for multiple attributes', () => {
|
||||||
const MSG_title = `Hello <20>0<EFBFBD>!`;
|
const message = `Hello <20>0<EFBFBD>!`;
|
||||||
const MSG_div_attr = ['title', MSG_title, 'aria-label', MSG_title];
|
const attrs = ['title', message, 'aria-label', message];
|
||||||
const nbConsts = 2;
|
const nbConsts = 2;
|
||||||
const index = 1;
|
const index = 1;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(attrs, () => {
|
||||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
ɵɵi18nAttributes(index, 0);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual(debugMatch([
|
expect(opCodes).toEqual(debugMatch([
|
||||||
@ -643,4 +635,4 @@ describe('Runtime i18n', () => {
|
|||||||
.toThrowError();
|
.toThrowError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
Loading…
x
Reference in New Issue
Block a user