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:

committed by
Andrew Scott

parent
5f90b64328
commit
cb05c0102f
@ -196,10 +196,19 @@ export function compileComponentFromMetadata(
|
||||
// e.g. `vars: 2`
|
||||
definitionMap.set('vars', o.literal(templateBuilder.getVarCount()));
|
||||
|
||||
// e.g. `consts: [['one', 'two'], ['three', 'four']]
|
||||
const consts = templateBuilder.getConsts();
|
||||
if (consts.length > 0) {
|
||||
definitionMap.set('consts', o.literalArr(consts));
|
||||
// Generate `consts` section of ComponentDef:
|
||||
// - either as an array:
|
||||
// `consts: [['one', 'two'], ['three', 'four']]`
|
||||
// - 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);
|
||||
|
@ -12,10 +12,14 @@ import * as o from '../../../output/output_ast';
|
||||
import * as t from '../../r3_ast';
|
||||
|
||||
/* 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 **/
|
||||
export const I18N_ATTR = 'i18n';
|
||||
@ -166,7 +170,7 @@ export function formatI18nPlaceholderName(name: string, useCamelCase: boolean =
|
||||
* @returns Complete translation const prefix
|
||||
*/
|
||||
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 {createLocalizeStatements} from './i18n/localize_utils';
|
||||
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 {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;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
private _dataIndex = 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 pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>,
|
||||
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);
|
||||
|
||||
// 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(
|
||||
message: i18n.Message, params: {[name: string]: o.Expression} = {}, ref?: 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
|
||||
// start with `MSG_`. We define a variable starting with `MSG_` just for the `goog.getMsg` call
|
||||
const closureVar = this.i18nGenerateClosureVar(message.id);
|
||||
const statements = getTranslationDeclStmts(message, _ref, closureVar, params, transformFn);
|
||||
this.constantPool.statements.push(...statements);
|
||||
this._constants.prepareStatements.push(...statements);
|
||||
return _ref;
|
||||
}
|
||||
|
||||
@ -364,6 +377,12 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
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 {
|
||||
let name: string;
|
||||
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):
|
||||
void {
|
||||
const index = this.allocateDataSlot();
|
||||
if (this.i18nContext) {
|
||||
this.i18n = this.i18nContext.forkChildContext(index, this.templateIndex!, meta);
|
||||
} else {
|
||||
const ref = o.variable(this.constantPool.uniqueName(TRANSLATION_PREFIX));
|
||||
this.i18n = new I18nContext(index, ref, 0, this.templateIndex, meta);
|
||||
}
|
||||
this.i18n = this.i18nContext ?
|
||||
this.i18nContext.forkChildContext(index, this.templateIndex!, meta) :
|
||||
new I18nContext(index, this.i18nGenerateMainBlockVar(), 0, this.templateIndex, meta);
|
||||
|
||||
// generate i18nStart instruction
|
||||
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) {
|
||||
// do not push 3rd argument (sub-block id)
|
||||
// into i18nStart call for top level i18n context
|
||||
@ -507,8 +523,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
}
|
||||
if (i18nAttrArgs.length > 0) {
|
||||
const index: o.Expression = o.literal(this.allocateDataSlot());
|
||||
const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true);
|
||||
this.creationInstruction(sourceSpan, R3.i18nAttributes, [index, args]);
|
||||
const constIndex = this.addToConsts(o.literalArr(i18nAttrArgs));
|
||||
this.creationInstruction(sourceSpan, R3.i18nAttributes, [index, constIndex]);
|
||||
if (hasBindings) {
|
||||
this.updateInstruction(sourceSpan, R3.i18nApply, [index]);
|
||||
}
|
||||
@ -1028,7 +1044,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
return this._pureFunctionSlots;
|
||||
}
|
||||
|
||||
getConsts() {
|
||||
getConsts(): ComponentDefConsts {
|
||||
return this._constants;
|
||||
}
|
||||
|
||||
@ -1352,14 +1368,16 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
return o.TYPED_NULL_EXPR;
|
||||
}
|
||||
|
||||
const consts = this._constants.constExpressions;
|
||||
|
||||
// Try to reuse a literal that's already in the array, if possible.
|
||||
for (let i = 0; i < this._constants.length; i++) {
|
||||
if (this._constants[i].isEquivalent(expression)) {
|
||||
for (let i = 0; i < consts.length; i++) {
|
||||
if (consts[i].isEquivalent(expression)) {
|
||||
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 {
|
||||
|
Reference in New Issue
Block a user