2016-07-15 09:42:33 -07:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2016-08-01 12:19:09 -07:00
|
|
|
import {HtmlParser} from '../ml_parser/html_parser';
|
|
|
|
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
2016-07-21 13:56:58 -07:00
|
|
|
import {ParseError} from '../parse_util';
|
2016-07-15 09:42:33 -07:00
|
|
|
|
2016-08-05 12:08:43 -07:00
|
|
|
import {extractMessages} from './extractor_merger';
|
2017-01-19 14:42:25 -08:00
|
|
|
import * as i18n from './i18n_ast';
|
|
|
|
import {PlaceholderMapper, Serializer} from './serializers/serializer';
|
|
|
|
|
2016-07-15 09:42:33 -07:00
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
/**
|
|
|
|
* A container for message extracted from the templates.
|
|
|
|
*/
|
|
|
|
export class MessageBundle {
|
2017-01-19 14:42:25 -08:00
|
|
|
private _messages: i18n.Message[] = [];
|
2016-07-15 09:42:33 -07:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
private _htmlParser: HtmlParser, private _implicitTags: string[],
|
|
|
|
private _implicitAttrs: {[k: string]: string[]}) {}
|
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
updateFromTemplate(html: string, url: string, interpolationConfig: InterpolationConfig):
|
|
|
|
ParseError[] {
|
2016-07-15 09:42:33 -07:00
|
|
|
const htmlParserResult = this._htmlParser.parse(html, url, true, interpolationConfig);
|
|
|
|
|
|
|
|
if (htmlParserResult.errors.length) {
|
2016-07-21 13:56:58 -07:00
|
|
|
return htmlParserResult.errors;
|
2016-07-15 09:42:33 -07:00
|
|
|
}
|
|
|
|
|
2016-08-05 12:08:43 -07:00
|
|
|
const i18nParserResult = extractMessages(
|
2016-07-15 09:42:33 -07:00
|
|
|
htmlParserResult.rootNodes, interpolationConfig, this._implicitTags, this._implicitAttrs);
|
|
|
|
|
2016-08-04 17:46:31 -07:00
|
|
|
if (i18nParserResult.errors.length) {
|
|
|
|
return i18nParserResult.errors;
|
|
|
|
}
|
|
|
|
|
2016-10-28 19:53:42 -07:00
|
|
|
this._messages.push(...i18nParserResult.messages);
|
2016-07-15 09:42:33 -07:00
|
|
|
}
|
|
|
|
|
2017-01-19 14:42:25 -08:00
|
|
|
// Return the message in the internal format
|
|
|
|
// The public (serialized) format might be different, see the `write` method.
|
|
|
|
getMessages(): i18n.Message[] { return this._messages; }
|
|
|
|
|
|
|
|
write(serializer: Serializer): string {
|
|
|
|
const messages: {[id: string]: i18n.Message} = {};
|
|
|
|
const mapperVisitor = new MapPlaceholderNames();
|
|
|
|
|
|
|
|
// Deduplicate messages based on their ID
|
|
|
|
this._messages.forEach(message => {
|
|
|
|
const id = serializer.digest(message);
|
|
|
|
if (!messages.hasOwnProperty(id)) {
|
|
|
|
messages[id] = message;
|
|
|
|
}
|
|
|
|
});
|
2016-08-09 21:05:04 -07:00
|
|
|
|
2017-01-19 14:42:25 -08:00
|
|
|
// Transform placeholder names using the serializer mapping
|
|
|
|
const msgList = Object.keys(messages).map(id => {
|
|
|
|
const mapper = serializer.createNameMapper(messages[id]);
|
|
|
|
const src = messages[id];
|
|
|
|
const nodes = mapper ? mapperVisitor.convert(src.nodes, mapper) : src.nodes;
|
|
|
|
return new i18n.Message(nodes, {}, {}, src.meaning, src.description, id);
|
|
|
|
});
|
|
|
|
|
|
|
|
return serializer.write(msgList);
|
|
|
|
}
|
2016-07-15 09:42:33 -07:00
|
|
|
}
|
2017-01-19 14:42:25 -08:00
|
|
|
|
|
|
|
// Transform an i18n AST by renaming the placeholder nodes with the given mapper
|
|
|
|
class MapPlaceholderNames implements i18n.Visitor {
|
|
|
|
convert(nodes: i18n.Node[], mapper: PlaceholderMapper): i18n.Node[] {
|
|
|
|
return mapper ? nodes.map(n => n.visit(this, mapper)) : nodes;
|
|
|
|
}
|
|
|
|
|
|
|
|
visitText(text: i18n.Text, mapper: PlaceholderMapper): i18n.Text {
|
|
|
|
return new i18n.Text(text.value, text.sourceSpan);
|
|
|
|
}
|
|
|
|
|
|
|
|
visitContainer(container: i18n.Container, mapper: PlaceholderMapper): i18n.Container {
|
|
|
|
const children = container.children.map(n => n.visit(this, mapper));
|
|
|
|
return new i18n.Container(children, container.sourceSpan);
|
|
|
|
}
|
|
|
|
|
|
|
|
visitIcu(icu: i18n.Icu, mapper: PlaceholderMapper): i18n.Icu {
|
|
|
|
const cases: {[k: string]: i18n.Node} = {};
|
|
|
|
Object.keys(icu.cases).forEach(key => cases[key] = icu.cases[key].visit(this, mapper));
|
|
|
|
const msg = new i18n.Icu(icu.expression, icu.type, cases, icu.sourceSpan);
|
|
|
|
msg.expressionPlaceholder = icu.expressionPlaceholder;
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
visitTagPlaceholder(ph: i18n.TagPlaceholder, mapper: PlaceholderMapper): i18n.TagPlaceholder {
|
|
|
|
const startName = mapper.toPublicName(ph.startName);
|
|
|
|
const closeName = ph.closeName ? mapper.toPublicName(ph.closeName) : ph.closeName;
|
|
|
|
const children = ph.children.map(n => n.visit(this, mapper));
|
|
|
|
return new i18n.TagPlaceholder(
|
|
|
|
ph.tag, ph.attrs, startName, closeName, children, ph.isVoid, ph.sourceSpan);
|
|
|
|
}
|
|
|
|
|
|
|
|
visitPlaceholder(ph: i18n.Placeholder, mapper: PlaceholderMapper): i18n.Placeholder {
|
|
|
|
return new i18n.Placeholder(ph.value, mapper.toPublicName(ph.name), ph.sourceSpan);
|
|
|
|
}
|
|
|
|
|
|
|
|
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, mapper: PlaceholderMapper): i18n.IcuPlaceholder {
|
|
|
|
return new i18n.IcuPlaceholder(ph.value, mapper.toPublicName(ph.name), ph.sourceSpan);
|
|
|
|
}
|
|
|
|
}
|