2019-07-30 18:02:17 +01:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2019-07-30 18:02:17 +01:00
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
import * as i18n from '../../../i18n/i18n_ast';
|
|
|
|
import * as o from '../../../output/output_ast';
|
2020-08-31 16:27:04 +01:00
|
|
|
import {ParseLocation, ParseSourceSpan} from '../../../parse_util';
|
2019-07-30 18:02:17 +01:00
|
|
|
|
|
|
|
import {serializeIcuNode} from './icu_serializer';
|
|
|
|
import {formatI18nPlaceholderName} from './util';
|
|
|
|
|
|
|
|
export function createLocalizeStatements(
|
|
|
|
variable: o.ReadVarExpr, message: i18n.Message,
|
|
|
|
params: {[name: string]: o.Expression}): o.Statement[] {
|
|
|
|
const {messageParts, placeHolders} = serializeI18nMessageForLocalize(message);
|
2020-06-18 11:49:18 +01:00
|
|
|
const sourceSpan = getSourceSpan(message);
|
2020-08-31 16:27:04 +01:00
|
|
|
const expressions = placeHolders.map(ph => params[ph.text]);
|
2020-06-18 11:49:18 +01:00
|
|
|
const localizedString =
|
|
|
|
o.localizedString(message, messageParts, placeHolders, expressions, sourceSpan);
|
|
|
|
const variableInitialization = variable.set(localizedString);
|
|
|
|
return [new o.ExpressionStatement(variableInitialization)];
|
2019-07-30 18:02:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This visitor walks over an i18n tree, capturing literal strings and placeholders.
|
|
|
|
*
|
|
|
|
* The result can be used for generating the `$localize` tagged template literals.
|
|
|
|
*/
|
|
|
|
class LocalizeSerializerVisitor implements i18n.Visitor {
|
2020-08-31 16:27:04 +01:00
|
|
|
visitText(text: i18n.Text, context: o.MessagePiece[]): any {
|
|
|
|
if (context[context.length - 1] instanceof o.LiteralPiece) {
|
2019-09-04 16:27:29 +01:00
|
|
|
// Two literal pieces in a row means that there was some comment node in-between.
|
|
|
|
context[context.length - 1].text += text.value;
|
|
|
|
} else {
|
2021-08-03 14:48:52 -07:00
|
|
|
context.push(new o.LiteralPiece(text.value, text.sourceSpan));
|
2019-09-04 16:27:29 +01:00
|
|
|
}
|
2019-07-30 18:02:17 +01:00
|
|
|
}
|
|
|
|
|
2020-08-31 16:27:04 +01:00
|
|
|
visitContainer(container: i18n.Container, context: o.MessagePiece[]): any {
|
2019-07-30 18:02:17 +01:00
|
|
|
container.children.forEach(child => child.visit(this, context));
|
|
|
|
}
|
|
|
|
|
2020-08-31 16:27:04 +01:00
|
|
|
visitIcu(icu: i18n.Icu, context: o.MessagePiece[]): any {
|
|
|
|
context.push(new o.LiteralPiece(serializeIcuNode(icu), icu.sourceSpan));
|
2019-07-30 18:02:17 +01:00
|
|
|
}
|
|
|
|
|
2020-08-31 16:27:04 +01:00
|
|
|
visitTagPlaceholder(ph: i18n.TagPlaceholder, context: o.MessagePiece[]): any {
|
2020-08-31 16:27:44 +01:00
|
|
|
context.push(this.createPlaceholderPiece(ph.startName, ph.startSourceSpan ?? ph.sourceSpan));
|
2019-07-30 18:02:17 +01:00
|
|
|
if (!ph.isVoid) {
|
|
|
|
ph.children.forEach(child => child.visit(this, context));
|
2020-08-31 16:27:44 +01:00
|
|
|
context.push(this.createPlaceholderPiece(ph.closeName, ph.endSourceSpan ?? ph.sourceSpan));
|
2019-07-30 18:02:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 16:27:04 +01:00
|
|
|
visitPlaceholder(ph: i18n.Placeholder, context: o.MessagePiece[]): any {
|
|
|
|
context.push(this.createPlaceholderPiece(ph.name, ph.sourceSpan));
|
2019-07-30 18:02:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any {
|
2020-08-31 16:27:04 +01:00
|
|
|
context.push(this.createPlaceholderPiece(ph.name, ph.sourceSpan));
|
|
|
|
}
|
|
|
|
|
|
|
|
private createPlaceholderPiece(name: string, sourceSpan: ParseSourceSpan): o.PlaceholderPiece {
|
|
|
|
return new o.PlaceholderPiece(
|
|
|
|
formatI18nPlaceholderName(name, /* useCamelCase */ false), sourceSpan);
|
2019-07-30 18:02:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const serializerVisitor = new LocalizeSerializerVisitor();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Serialize an i18n message into two arrays: messageParts and placeholders.
|
|
|
|
*
|
|
|
|
* These arrays will be used to generate `$localize` tagged template literals.
|
|
|
|
*
|
|
|
|
* @param message The message to be serialized.
|
|
|
|
* @returns an object containing the messageParts and placeholders.
|
|
|
|
*/
|
|
|
|
export function serializeI18nMessageForLocalize(message: i18n.Message):
|
2020-08-31 16:27:04 +01:00
|
|
|
{messageParts: o.LiteralPiece[], placeHolders: o.PlaceholderPiece[]} {
|
|
|
|
const pieces: o.MessagePiece[] = [];
|
2019-07-30 18:02:17 +01:00
|
|
|
message.nodes.forEach(node => node.visit(serializerVisitor, pieces));
|
|
|
|
return processMessagePieces(pieces);
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:49:18 +01:00
|
|
|
function getSourceSpan(message: i18n.Message): ParseSourceSpan {
|
|
|
|
const startNode = message.nodes[0];
|
|
|
|
const endNode = message.nodes[message.nodes.length - 1];
|
|
|
|
return new ParseSourceSpan(
|
2021-08-03 14:48:52 -07:00
|
|
|
startNode.sourceSpan.start, endNode.sourceSpan.end, startNode.sourceSpan.fullStart,
|
2020-10-28 21:35:06 +00:00
|
|
|
startNode.sourceSpan.details);
|
2020-06-18 11:49:18 +01:00
|
|
|
}
|
|
|
|
|
2019-07-30 18:02:17 +01:00
|
|
|
/**
|
|
|
|
* Convert the list of serialized MessagePieces into two arrays.
|
|
|
|
*
|
|
|
|
* One contains the literal string pieces and the other the placeholders that will be replaced by
|
|
|
|
* expressions when rendering `$localize` tagged template literals.
|
|
|
|
*
|
|
|
|
* @param pieces The pieces to process.
|
|
|
|
* @returns an object containing the messageParts and placeholders.
|
|
|
|
*/
|
2020-08-31 16:27:04 +01:00
|
|
|
function processMessagePieces(pieces: o.MessagePiece[]):
|
|
|
|
{messageParts: o.LiteralPiece[], placeHolders: o.PlaceholderPiece[]} {
|
|
|
|
const messageParts: o.LiteralPiece[] = [];
|
|
|
|
const placeHolders: o.PlaceholderPiece[] = [];
|
2019-07-30 18:02:17 +01:00
|
|
|
|
2020-08-31 16:27:04 +01:00
|
|
|
if (pieces[0] instanceof o.PlaceholderPiece) {
|
2019-07-30 18:02:17 +01:00
|
|
|
// The first piece was a placeholder so we need to add an initial empty message part.
|
2020-08-31 16:27:04 +01:00
|
|
|
messageParts.push(createEmptyMessagePart(pieces[0].sourceSpan.start));
|
2019-07-30 18:02:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < pieces.length; i++) {
|
|
|
|
const part = pieces[i];
|
2020-08-31 16:27:04 +01:00
|
|
|
if (part instanceof o.LiteralPiece) {
|
|
|
|
messageParts.push(part);
|
2019-07-30 18:02:17 +01:00
|
|
|
} else {
|
2020-08-31 16:27:04 +01:00
|
|
|
placeHolders.push(part);
|
|
|
|
if (pieces[i - 1] instanceof o.PlaceholderPiece) {
|
2019-07-30 18:02:17 +01:00
|
|
|
// There were two placeholders in a row, so we need to add an empty message part.
|
2020-10-28 21:01:31 +00:00
|
|
|
messageParts.push(createEmptyMessagePart(pieces[i - 1].sourceSpan.end));
|
2019-07-30 18:02:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-31 16:27:04 +01:00
|
|
|
if (pieces[pieces.length - 1] instanceof o.PlaceholderPiece) {
|
2019-07-30 18:02:17 +01:00
|
|
|
// The last piece was a placeholder so we need to add a final empty message part.
|
2020-08-31 16:27:04 +01:00
|
|
|
messageParts.push(createEmptyMessagePart(pieces[pieces.length - 1].sourceSpan.end));
|
2019-07-30 18:02:17 +01:00
|
|
|
}
|
|
|
|
return {messageParts, placeHolders};
|
2019-09-13 12:46:05 +01:00
|
|
|
}
|
2020-08-31 16:27:04 +01:00
|
|
|
|
|
|
|
function createEmptyMessagePart(location: ParseLocation): o.LiteralPiece {
|
|
|
|
return new o.LiteralPiece('', new ParseSourceSpan(location, location));
|
|
|
|
}
|