feat(i18n): update I18nHtmlParser to accept parsed messages
This commit is contained in:
parent
d7e1175df0
commit
756121acc1
|
@ -17,19 +17,19 @@ import {Message, id} from './message';
|
||||||
import {
|
import {
|
||||||
messageFromAttribute,
|
messageFromAttribute,
|
||||||
I18nError,
|
I18nError,
|
||||||
isI18nAttr,
|
I18N_ATTR_PREFIX,
|
||||||
|
I18N_ATTR,
|
||||||
partition,
|
partition,
|
||||||
Part,
|
Part,
|
||||||
stringifyNodes,
|
stringifyNodes,
|
||||||
meaning
|
meaning
|
||||||
} from './shared';
|
} from './shared';
|
||||||
|
|
||||||
const I18N_ATTR = "i18n";
|
const _I18N_ATTR = "i18n";
|
||||||
const PLACEHOLDER_ELEMENT = "ph";
|
const _PLACEHOLDER_ELEMENT = "ph";
|
||||||
const NAME_ATTR = "name";
|
const _NAME_ATTR = "name";
|
||||||
const I18N_ATTR_PREFIX = "i18n-";
|
const _I18N_ATTR_PREFIX = "i18n-";
|
||||||
let PLACEHOLDER_REGEXP = RegExpWrapper.create(`\\<ph(\\s)+name=("(\\d)+")\\/\\>`);
|
let _PLACEHOLDER_EXPANDED_REGEXP = RegExpWrapper.create(`\\<ph(\\s)+name=("(\\d)+")\\>\\<\\/ph\\>`);
|
||||||
let PLACEHOLDER_EXPANDED_REGEXP = RegExpWrapper.create(`\\<ph(\\s)+name=("(\\d)+")\\>\\<\\/ph\\>`);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an i18n-ed version of the parsed template.
|
* Creates an i18n-ed version of the parsed template.
|
||||||
|
@ -94,7 +94,7 @@ let PLACEHOLDER_EXPANDED_REGEXP = RegExpWrapper.create(`\\<ph(\\s)+name=("(\\d)+
|
||||||
* This is how the merging works:
|
* This is how the merging works:
|
||||||
*
|
*
|
||||||
* 1. Use the stringify function to get the message id. Look up the message in the map.
|
* 1. Use the stringify function to get the message id. Look up the message in the map.
|
||||||
* 2. Parse the translated message. At this point we have two trees: the original tree
|
* 2. Get the translated message. At this point we have two trees: the original tree
|
||||||
* and the translated tree, where all the elements are replaced with placeholders.
|
* and the translated tree, where all the elements are replaced with placeholders.
|
||||||
* 3. Use the original tree to create a mapping Index:number -> HtmlAst.
|
* 3. Use the original tree to create a mapping Index:number -> HtmlAst.
|
||||||
* 4. Walk the translated tree.
|
* 4. Walk the translated tree.
|
||||||
|
@ -115,7 +115,7 @@ export class I18nHtmlParser implements HtmlParser {
|
||||||
errors: ParseError[];
|
errors: ParseError[];
|
||||||
|
|
||||||
constructor(private _htmlParser: HtmlParser, private _parser: Parser,
|
constructor(private _htmlParser: HtmlParser, private _parser: Parser,
|
||||||
private _messages: {[key: string]: string}) {}
|
private _messagesContent: string, private _messages: {[key: string]: HtmlAst[]}) {}
|
||||||
|
|
||||||
parse(sourceContent: string, sourceUrl: string): HtmlParseTreeResult {
|
parse(sourceContent: string, sourceUrl: string): HtmlParseTreeResult {
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
|
@ -149,17 +149,8 @@ export class I18nHtmlParser implements HtmlParser {
|
||||||
throw new I18nError(p.sourceSpan, `Cannot find message for id '${messageId}'`);
|
throw new I18nError(p.sourceSpan, `Cannot find message for id '${messageId}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the message and expand a placeholder so <ph/> becomes <ph></ph>
|
let parsedMessage = this._messages[messageId];
|
||||||
// we need to do it cause we use HtmlParser to parse the message
|
return this._mergeTrees(p, parsedMessage, p.children);
|
||||||
let message = _expandPlaceholder(this._messages[messageId]);
|
|
||||||
let parsedMessage = this._htmlParser.parse(message, "source");
|
|
||||||
|
|
||||||
if (parsedMessage.errors.length > 0) {
|
|
||||||
this.errors = this.errors.concat(parsedMessage.errors);
|
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
return this._mergeTrees(p, message, parsedMessage.rootNodes, p.children);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _recurseIntoI18nPart(p: Part): HtmlAst[] {
|
private _recurseIntoI18nPart(p: Part): HtmlAst[] {
|
||||||
|
@ -189,14 +180,13 @@ export class I18nHtmlParser implements HtmlParser {
|
||||||
return ListWrapper.flatten(ps.map(p => this._processI18nPart(p)));
|
return ListWrapper.flatten(ps.map(p => this._processI18nPart(p)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _mergeTrees(p: Part, translatedSource: string, translated: HtmlAst[],
|
private _mergeTrees(p: Part, translated: HtmlAst[], original: HtmlAst[]): HtmlAst[] {
|
||||||
original: HtmlAst[]): HtmlAst[] {
|
|
||||||
let l = new _CreateNodeMapping();
|
let l = new _CreateNodeMapping();
|
||||||
htmlVisitAll(l, original);
|
htmlVisitAll(l, original);
|
||||||
|
|
||||||
// merge the translated tree with the original tree.
|
// merge the translated tree with the original tree.
|
||||||
// we do it by preserving the source code position of the original tree
|
// we do it by preserving the source code position of the original tree
|
||||||
let merged = this._mergeTreesHelper(translatedSource, translated, l.mapping);
|
let merged = this._mergeTreesHelper(translated, l.mapping);
|
||||||
|
|
||||||
// if the root element is present, we need to create a new root element with its attributes
|
// if the root element is present, we need to create a new root element with its attributes
|
||||||
// translated
|
// translated
|
||||||
|
@ -217,11 +207,10 @@ export class I18nHtmlParser implements HtmlParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _mergeTreesHelper(translatedSource: string, translated: HtmlAst[],
|
private _mergeTreesHelper(translated: HtmlAst[], mapping: HtmlAst[]): HtmlAst[] {
|
||||||
mapping: HtmlAst[]): HtmlAst[] {
|
|
||||||
return translated.map(t => {
|
return translated.map(t => {
|
||||||
if (t instanceof HtmlElementAst) {
|
if (t instanceof HtmlElementAst) {
|
||||||
return this._mergeElementOrInterpolation(t, translatedSource, translated, mapping);
|
return this._mergeElementOrInterpolation(t, translated, mapping);
|
||||||
|
|
||||||
} else if (t instanceof HtmlTextAst) {
|
} else if (t instanceof HtmlTextAst) {
|
||||||
return t;
|
return t;
|
||||||
|
@ -232,52 +221,51 @@ export class I18nHtmlParser implements HtmlParser {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _mergeElementOrInterpolation(t: HtmlElementAst, translatedSource: string,
|
private _mergeElementOrInterpolation(t: HtmlElementAst, translated: HtmlAst[],
|
||||||
translated: HtmlAst[], mapping: HtmlAst[]): HtmlAst {
|
mapping: HtmlAst[]): HtmlAst {
|
||||||
let name = this._getName(t);
|
let name = this._getName(t);
|
||||||
let type = name[0];
|
let type = name[0];
|
||||||
let index = NumberWrapper.parseInt(name.substring(1), 10);
|
let index = NumberWrapper.parseInt(name.substring(1), 10);
|
||||||
let originalNode = mapping[index];
|
let originalNode = mapping[index];
|
||||||
|
|
||||||
if (type == "t") {
|
if (type == "t") {
|
||||||
return this._mergeTextInterpolation(t, <HtmlTextAst>originalNode, translatedSource);
|
return this._mergeTextInterpolation(t, <HtmlTextAst>originalNode);
|
||||||
} else if (type == "e") {
|
} else if (type == "e") {
|
||||||
return this._mergeElement(t, <HtmlElementAst>originalNode, mapping, translatedSource);
|
return this._mergeElement(t, <HtmlElementAst>originalNode, mapping);
|
||||||
} else {
|
} else {
|
||||||
throw new BaseException("should not be reached");
|
throw new BaseException("should not be reached");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getName(t: HtmlElementAst): string {
|
private _getName(t: HtmlElementAst): string {
|
||||||
if (t.name != PLACEHOLDER_ELEMENT) {
|
if (t.name != _PLACEHOLDER_ELEMENT) {
|
||||||
throw new I18nError(
|
throw new I18nError(
|
||||||
t.sourceSpan,
|
t.sourceSpan,
|
||||||
`Unexpected tag "${t.name}". Only "${PLACEHOLDER_ELEMENT}" tags are allowed.`);
|
`Unexpected tag "${t.name}". Only "${_PLACEHOLDER_ELEMENT}" tags are allowed.`);
|
||||||
}
|
}
|
||||||
let names = t.attrs.filter(a => a.name == NAME_ATTR);
|
let names = t.attrs.filter(a => a.name == _NAME_ATTR);
|
||||||
if (names.length == 0) {
|
if (names.length == 0) {
|
||||||
throw new I18nError(t.sourceSpan, `Missing "${NAME_ATTR}" attribute.`);
|
throw new I18nError(t.sourceSpan, `Missing "${_NAME_ATTR}" attribute.`);
|
||||||
}
|
}
|
||||||
return names[0].value;
|
return names[0].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _mergeTextInterpolation(t: HtmlElementAst, originalNode: HtmlTextAst,
|
private _mergeTextInterpolation(t: HtmlElementAst, originalNode: HtmlTextAst): HtmlTextAst {
|
||||||
translatedSource: string): HtmlTextAst {
|
|
||||||
let split =
|
let split =
|
||||||
this._parser.splitInterpolation(originalNode.value, originalNode.sourceSpan.toString());
|
this._parser.splitInterpolation(originalNode.value, originalNode.sourceSpan.toString());
|
||||||
let exps = isPresent(split) ? split.expressions : [];
|
let exps = isPresent(split) ? split.expressions : [];
|
||||||
|
|
||||||
let messageSubstring =
|
let messageSubstring =
|
||||||
translatedSource.substring(t.startSourceSpan.end.offset, t.endSourceSpan.start.offset);
|
this._messagesContent.substring(t.startSourceSpan.end.offset, t.endSourceSpan.start.offset);
|
||||||
let translated =
|
let translated =
|
||||||
this._replacePlaceholdersWithExpressions(messageSubstring, exps, originalNode.sourceSpan);
|
this._replacePlaceholdersWithExpressions(messageSubstring, exps, originalNode.sourceSpan);
|
||||||
|
|
||||||
return new HtmlTextAst(translated, originalNode.sourceSpan);
|
return new HtmlTextAst(translated, originalNode.sourceSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _mergeElement(t: HtmlElementAst, originalNode: HtmlElementAst, mapping: HtmlAst[],
|
private _mergeElement(t: HtmlElementAst, originalNode: HtmlElementAst,
|
||||||
translatedSource: string): HtmlElementAst {
|
mapping: HtmlAst[]): HtmlElementAst {
|
||||||
let children = this._mergeTreesHelper(translatedSource, t.children, mapping);
|
let children = this._mergeTreesHelper(t.children, mapping);
|
||||||
return new HtmlElementAst(originalNode.name, this._i18nAttributes(originalNode), children,
|
return new HtmlElementAst(originalNode.name, this._i18nAttributes(originalNode), children,
|
||||||
originalNode.sourceSpan, originalNode.startSourceSpan,
|
originalNode.sourceSpan, originalNode.startSourceSpan,
|
||||||
originalNode.endSourceSpan);
|
originalNode.endSourceSpan);
|
||||||
|
@ -286,30 +274,46 @@ export class I18nHtmlParser implements HtmlParser {
|
||||||
private _i18nAttributes(el: HtmlElementAst): HtmlAttrAst[] {
|
private _i18nAttributes(el: HtmlElementAst): HtmlAttrAst[] {
|
||||||
let res = [];
|
let res = [];
|
||||||
el.attrs.forEach(attr => {
|
el.attrs.forEach(attr => {
|
||||||
if (isI18nAttr(attr.name)) {
|
if (attr.name.startsWith(I18N_ATTR_PREFIX) || attr.name == I18N_ATTR) return;
|
||||||
let messageId = id(messageFromAttribute(this._parser, el, attr));
|
|
||||||
let expectedName = attr.name.substring(5);
|
|
||||||
let m = el.attrs.filter(a => a.name == expectedName)[0];
|
|
||||||
|
|
||||||
if (StringMapWrapper.contains(this._messages, messageId)) {
|
let i18ns = el.attrs.filter(a => a.name == `i18n-${attr.name}`);
|
||||||
let split = this._parser.splitInterpolation(m.value, m.sourceSpan.toString());
|
if (i18ns.length == 0) {
|
||||||
let exps = isPresent(split) ? split.expressions : [];
|
res.push(attr);
|
||||||
let message = this._replacePlaceholdersWithExpressions(
|
return;
|
||||||
_expandPlaceholder(this._messages[messageId]), exps, m.sourceSpan);
|
|
||||||
res.push(new HtmlAttrAst(m.name, message, m.sourceSpan));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new I18nError(m.sourceSpan, `Cannot find message for id '${messageId}'`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let i18n = i18ns[0];
|
||||||
|
let messageId = id(messageFromAttribute(this._parser, el, i18n));
|
||||||
|
|
||||||
|
if (StringMapWrapper.contains(this._messages, messageId)) {
|
||||||
|
let updatedMessage = this._replaceInterpolationInAttr(attr, this._messages[messageId]);
|
||||||
|
res.push(new HtmlAttrAst(attr.name, updatedMessage, attr.sourceSpan));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new I18nError(attr.sourceSpan, `Cannot find message for id '${messageId}'`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _replaceInterpolationInAttr(attr: HtmlAttrAst, msg: HtmlAst[]): string {
|
||||||
|
let split = this._parser.splitInterpolation(attr.value, attr.sourceSpan.toString());
|
||||||
|
let exps = isPresent(split) ? split.expressions : [];
|
||||||
|
|
||||||
|
let first = msg[0];
|
||||||
|
let last = msg[msg.length - 1];
|
||||||
|
|
||||||
|
let start = first.sourceSpan.start.offset;
|
||||||
|
let end =
|
||||||
|
last instanceof HtmlElementAst ? last.endSourceSpan.end.offset : last.sourceSpan.end.offset;
|
||||||
|
let messageSubstring = this._messagesContent.substring(start, end);
|
||||||
|
|
||||||
|
return this._replacePlaceholdersWithExpressions(messageSubstring, exps, attr.sourceSpan);
|
||||||
|
};
|
||||||
|
|
||||||
private _replacePlaceholdersWithExpressions(message: string, exps: string[],
|
private _replacePlaceholdersWithExpressions(message: string, exps: string[],
|
||||||
sourceSpan: ParseSourceSpan): string {
|
sourceSpan: ParseSourceSpan): string {
|
||||||
return RegExpWrapper.replaceAll(PLACEHOLDER_EXPANDED_REGEXP, message, (match) => {
|
return RegExpWrapper.replaceAll(_PLACEHOLDER_EXPANDED_REGEXP, message, (match) => {
|
||||||
let nameWithQuotes = match[2];
|
let nameWithQuotes = match[2];
|
||||||
let name = nameWithQuotes.substring(1, nameWithQuotes.length - 1);
|
let name = nameWithQuotes.substring(1, nameWithQuotes.length - 1);
|
||||||
let index = NumberWrapper.parseInt(name, 10);
|
let index = NumberWrapper.parseInt(name, 10);
|
||||||
|
@ -343,11 +347,4 @@ class _CreateNodeMapping implements HtmlAstVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
visitComment(ast: HtmlCommentAst, context: any): any { return ""; }
|
visitComment(ast: HtmlCommentAst, context: any): any { return ""; }
|
||||||
}
|
|
||||||
|
|
||||||
function _expandPlaceholder(input: string): string {
|
|
||||||
return RegExpWrapper.replaceAll(PLACEHOLDER_REGEXP, input, (match) => {
|
|
||||||
let nameWithQuotes = match[2];
|
|
||||||
return `<ph name=${nameWithQuotes}></ph>`;
|
|
||||||
});
|
|
||||||
}
|
}
|
|
@ -16,10 +16,10 @@ import {Message, id} from './message';
|
||||||
import {
|
import {
|
||||||
I18nError,
|
I18nError,
|
||||||
Part,
|
Part,
|
||||||
|
I18N_ATTR_PREFIX,
|
||||||
partition,
|
partition,
|
||||||
meaning,
|
meaning,
|
||||||
description,
|
description,
|
||||||
isI18nAttr,
|
|
||||||
stringifyNodes,
|
stringifyNodes,
|
||||||
messageFromAttribute
|
messageFromAttribute
|
||||||
} from './shared';
|
} from './shared';
|
||||||
|
@ -161,7 +161,7 @@ export class MessageExtractor {
|
||||||
|
|
||||||
private _extractMessagesFromAttributes(p: HtmlElementAst): void {
|
private _extractMessagesFromAttributes(p: HtmlElementAst): void {
|
||||||
p.attrs.forEach(attr => {
|
p.attrs.forEach(attr => {
|
||||||
if (isI18nAttr(attr.name)) {
|
if (attr.name.startsWith(I18N_ATTR_PREFIX)) {
|
||||||
try {
|
try {
|
||||||
this.messages.push(messageFromAttribute(this._parser, p, attr));
|
this.messages.push(messageFromAttribute(this._parser, p, attr));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -12,8 +12,8 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||||
import {Message} from './message';
|
import {Message} from './message';
|
||||||
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
|
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
|
||||||
|
|
||||||
const I18N_ATTR = "i18n";
|
export const I18N_ATTR = "i18n";
|
||||||
const I18N_ATTR_PREFIX = "i18n-";
|
export const I18N_ATTR_PREFIX = "i18n-";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An i18n error.
|
* An i18n error.
|
||||||
|
@ -80,10 +80,6 @@ function _isClosingComment(n: HtmlAst): boolean {
|
||||||
return n instanceof HtmlCommentAst && isPresent(n.value) && n.value == "/i18n";
|
return n instanceof HtmlCommentAst && isPresent(n.value) && n.value == "/i18n";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isI18nAttr(n: string): boolean {
|
|
||||||
return n.startsWith(I18N_ATTR_PREFIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _findI18nAttr(p: HtmlElementAst): HtmlAttrAst {
|
function _findI18nAttr(p: HtmlElementAst): HtmlAttrAst {
|
||||||
let i18n = p.attrs.filter(a => a.name == I18N_ATTR);
|
let i18n = p.attrs.filter(a => a.name == I18N_ATTR);
|
||||||
return i18n.length == 0 ? null : i18n[0];
|
return i18n.length == 0 ? null : i18n[0];
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {Message, id} from 'angular2/src/i18n/message';
|
||||||
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
|
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
|
||||||
import {Lexer} from 'angular2/src/core/change_detection/parser/lexer';
|
import {Lexer} from 'angular2/src/core/change_detection/parser/lexer';
|
||||||
|
|
||||||
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {HtmlParser, HtmlParseTreeResult} from 'angular2/src/compiler/html_parser';
|
import {HtmlParser, HtmlParseTreeResult} from 'angular2/src/compiler/html_parser';
|
||||||
import {
|
import {
|
||||||
HtmlAst,
|
HtmlAst,
|
||||||
|
@ -26,6 +27,7 @@ import {
|
||||||
HtmlCommentAst,
|
HtmlCommentAst,
|
||||||
htmlVisitAll
|
htmlVisitAll
|
||||||
} from 'angular2/src/compiler/html_ast';
|
} from 'angular2/src/compiler/html_ast';
|
||||||
|
import {serializeXmb, deserializeXmb} from 'angular2/src/i18n/xmb_serializer';
|
||||||
import {ParseError, ParseLocation} from 'angular2/src/compiler/parse_util';
|
import {ParseError, ParseLocation} from 'angular2/src/compiler/parse_util';
|
||||||
import {humanizeDom} from '../../test/compiler/html_ast_spec_utils';
|
import {humanizeDom} from '../../test/compiler/html_ast_spec_utils';
|
||||||
|
|
||||||
|
@ -34,7 +36,13 @@ export function main() {
|
||||||
function parse(template: string, messages: {[key: string]: string}): HtmlParseTreeResult {
|
function parse(template: string, messages: {[key: string]: string}): HtmlParseTreeResult {
|
||||||
var parser = new Parser(new Lexer());
|
var parser = new Parser(new Lexer());
|
||||||
let htmlParser = new HtmlParser();
|
let htmlParser = new HtmlParser();
|
||||||
return new I18nHtmlParser(htmlParser, parser, messages).parse(template, "someurl");
|
|
||||||
|
let msgs = '';
|
||||||
|
StringMapWrapper.forEach(messages, (v, k) => msgs += `<msg id="${k}">${v}</msg>`);
|
||||||
|
let res = deserializeXmb(`<message-bundle>${msgs}</message-bundle>`, 'someUrl');
|
||||||
|
|
||||||
|
return new I18nHtmlParser(htmlParser, parser, res.content, res.messages)
|
||||||
|
.parse(template, "someurl");
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should delegate to the provided parser when no i18n", () => {
|
it("should delegate to the provided parser when no i18n", () => {
|
||||||
|
@ -112,6 +120,18 @@ export function main() {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should preserve non-i18n attributes", () => {
|
||||||
|
let translations: {[key: string]: string} = {};
|
||||||
|
translations[id(new Message('message', null, null))] = 'another message';
|
||||||
|
|
||||||
|
expect(humanizeDom(parse('<div i18n value="b">message</div>', translations)))
|
||||||
|
.toEqual([
|
||||||
|
[HtmlElementAst, 'div', 0],
|
||||||
|
[HtmlAttrAst, 'value', "b"],
|
||||||
|
[HtmlTextAst, 'another message', 1]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should extract from partitions', () => {
|
it('should extract from partitions', () => {
|
||||||
let translations: {[key: string]: string} = {};
|
let translations: {[key: string]: string} = {};
|
||||||
translations[id(new Message('message1', 'meaning1', null))] = 'another message1';
|
translations[id(new Message('message1', 'meaning1', null))] = 'another message1';
|
||||||
|
@ -156,14 +176,6 @@ export function main() {
|
||||||
.toEqual([`Cannot find message for id '${mid}'`]);
|
.toEqual([`Cannot find message for id '${mid}'`]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should error when message cannot be parsed", () => {
|
|
||||||
let translations: {[key: string]: string} = {};
|
|
||||||
translations[id(new Message("some message", null, null))] = "<a>a</b>";
|
|
||||||
|
|
||||||
expect(humanizeErrors(parse("<div i18n>some message</div>", translations).errors))
|
|
||||||
.toEqual([`Unexpected closing tag "b"`]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should error when a non-placeholder element appears in translation", () => {
|
it("should error when a non-placeholder element appears in translation", () => {
|
||||||
let translations: {[key: string]: string} = {};
|
let translations: {[key: string]: string} = {};
|
||||||
translations[id(new Message("some message", null, null))] = "<a>a</a>";
|
translations[id(new Message("some message", null, null))] = "<a>a</a>";
|
||||||
|
@ -180,11 +192,6 @@ export function main() {
|
||||||
.toEqual([`Missing "name" attribute.`]);
|
.toEqual([`Missing "name" attribute.`]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should error when no matching attribute", () => {
|
|
||||||
expect(humanizeErrors(parse("<div i18n-value></div>", {}).errors))
|
|
||||||
.toEqual([`Missing attribute 'value'.`]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should error when the translation refers to an invalid expression", () => {
|
it("should error when the translation refers to an invalid expression", () => {
|
||||||
let translations: {[key: string]: string} = {};
|
let translations: {[key: string]: string} = {};
|
||||||
translations[id(new Message('hi <ph name="0"/>', null, null))] = 'hi <ph name="99"/>';
|
translations[id(new Message('hi <ph name="0"/>', null, null))] = 'hi <ph name="99"/>';
|
||||||
|
|
Loading…
Reference in New Issue