refactor(compiler): move handling of translations to the ConstantPool (#22942)
PR Close #22942
This commit is contained in:

committed by
Matias Niemelä

parent
d98e9e7c7f
commit
bcaa07b0ac
@ -11,8 +11,16 @@ import {OutputContext, error} from './util';
|
||||
|
||||
const CONSTANT_PREFIX = '_c';
|
||||
|
||||
// Closure variables holding messages must be named `MSG_[A-Z0-9]+`
|
||||
const TRANSLATION_PREFIX = 'MSG_';
|
||||
|
||||
export const enum DefinitionKind {Injector, Directive, Component, Pipe}
|
||||
|
||||
/**
|
||||
* Closure uses `goog.getMsg(message)` to lookup translations
|
||||
*/
|
||||
const GOOG_GET_MSG = 'goog.getMsg';
|
||||
|
||||
/**
|
||||
* Context to use when producing a key.
|
||||
*
|
||||
@ -68,6 +76,7 @@ class FixupExpression extends o.Expression {
|
||||
*/
|
||||
export class ConstantPool {
|
||||
statements: o.Statement[] = [];
|
||||
private translations = new Map<string, o.Expression>();
|
||||
private literals = new Map<string, FixupExpression>();
|
||||
private literalFactories = new Map<string, o.Expression>();
|
||||
private injectorDefinitions = new Map<any, FixupExpression>();
|
||||
@ -103,6 +112,40 @@ export class ConstantPool {
|
||||
return fixup;
|
||||
}
|
||||
|
||||
// Generates closure specific code for translation.
|
||||
//
|
||||
// ```
|
||||
// /**
|
||||
// * @desc description?
|
||||
// * @meaning meaning?
|
||||
// */
|
||||
// const MSG_XYZ = goog.getMsg('message');
|
||||
// ```
|
||||
getTranslation(message: string, meta: {description?: string, meaning?: string}): o.Expression {
|
||||
// The identity of an i18n message depends on the message and its meaning
|
||||
const key = meta.meaning ? `${message}\u0000\u0000${meta.meaning}` : message;
|
||||
|
||||
const exp = this.translations.get(key);
|
||||
|
||||
if (exp) {
|
||||
return exp;
|
||||
}
|
||||
|
||||
const docStmt = i18nMetaToDocStmt(meta);
|
||||
if (docStmt) {
|
||||
this.statements.push(docStmt);
|
||||
}
|
||||
|
||||
// Call closure to get the translation
|
||||
const variable = o.variable(this.freshTranslationName());
|
||||
const fnCall = o.variable(GOOG_GET_MSG).callFn([o.literal(message)]);
|
||||
const msgStmt = variable.set(fnCall).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]);
|
||||
this.statements.push(msgStmt);
|
||||
|
||||
this.translations.set(key, variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext, forceShared: boolean = false):
|
||||
o.Expression {
|
||||
const definitions = this.definitionsOf(kind);
|
||||
@ -213,26 +256,37 @@ export class ConstantPool {
|
||||
|
||||
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
|
||||
|
||||
private freshTranslationName(): string {
|
||||
return this.uniqueName(TRANSLATION_PREFIX).toUpperCase();
|
||||
}
|
||||
|
||||
private keyOf(expression: o.Expression) {
|
||||
return expression.visitExpression(new KeyVisitor(), KEY_CONTEXT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor used to determine if 2 expressions are equivalent and can be shared in the
|
||||
* `ConstantPool`.
|
||||
*
|
||||
* When the id (string) generated by the visitor is equal, expressions are considered equivalent.
|
||||
*/
|
||||
class KeyVisitor implements o.ExpressionVisitor {
|
||||
visitLiteralExpr(ast: o.LiteralExpr): string {
|
||||
return `${typeof ast.value === 'string' ? '"' + ast.value + '"' : ast.value}`;
|
||||
}
|
||||
|
||||
visitLiteralArrayExpr(ast: o.LiteralArrayExpr, context: object): string {
|
||||
return `[${ast.entries.map(entry => entry.visitExpression(this, context)).join(',')}]`;
|
||||
}
|
||||
|
||||
visitLiteralMapExpr(ast: o.LiteralMapExpr, context: object): string {
|
||||
const mapKey =
|
||||
(entry: o.LiteralMapEntry) => {
|
||||
const quote = entry.quoted ? '"' : '';
|
||||
return `${quote}${entry.key}${quote}`;
|
||||
} const mapEntry = (entry: o.LiteralMapEntry) =>
|
||||
`${mapKey(entry)}:${entry.value.visitExpression(this, context)}`;
|
||||
const mapKey = (entry: o.LiteralMapEntry) => {
|
||||
const quote = entry.quoted ? '"' : '';
|
||||
return `${quote}${entry.key}${quote}`;
|
||||
};
|
||||
const mapEntry = (entry: o.LiteralMapEntry) =>
|
||||
`${mapKey(entry)}:${entry.value.visitExpression(this, context)}`;
|
||||
return `{${ast.entries.map(mapEntry).join(',')}`;
|
||||
}
|
||||
|
||||
@ -241,13 +295,7 @@ class KeyVisitor implements o.ExpressionVisitor {
|
||||
`EX:${ast.value.runtime.name}`;
|
||||
}
|
||||
|
||||
visitReadVarExpr(ast: o.ReadVarExpr): string {
|
||||
if (!ast.name) {
|
||||
invalid(ast);
|
||||
}
|
||||
return ast.name as string;
|
||||
}
|
||||
|
||||
visitReadVarExpr = invalid;
|
||||
visitWriteVarExpr = invalid;
|
||||
visitWriteKeyExpr = invalid;
|
||||
visitWritePropExpr = invalid;
|
||||
@ -273,3 +321,20 @@ function invalid<T>(arg: o.Expression | o.Statement): never {
|
||||
function isVariable(e: o.Expression): e is o.ReadVarExpr {
|
||||
return e instanceof o.ReadVarExpr;
|
||||
}
|
||||
|
||||
// Converts i18n meta informations for a message (description, meaning) to a JsDoc statement
|
||||
// formatted as expected by the Closure compiler.
|
||||
function i18nMetaToDocStmt(meta: {description?: string, id?: string, meaning?: string}):
|
||||
o.JSDocCommentStmt|null {
|
||||
const tags: o.JSDocTag[] = [];
|
||||
|
||||
if (meta.description) {
|
||||
tags.push({tagName: o.JSDocTagName.Desc, text: meta.description});
|
||||
}
|
||||
|
||||
if (meta.meaning) {
|
||||
tags.push({tagName: o.JSDocTagName.Meaning, text: meta.meaning});
|
||||
}
|
||||
|
||||
return tags.length == 0 ? null : new o.JSDocCommentStmt(tags);
|
||||
}
|
||||
|
Reference in New Issue
Block a user