feat(ICU): extract ICU messages

This commit is contained in:
Victor Berchet 2016-06-30 18:37:15 -07:00
parent 3c3e9ddb10
commit 28e8b2faab
5 changed files with 287 additions and 226 deletions

View File

@ -117,7 +117,10 @@ export class I18nHtmlParser implements HtmlParser {
// Look for the translated message and merge it back to the tree // Look for the translated message and merge it back to the tree
private _mergeI18Part(part: Part): HtmlAst[] { private _mergeI18Part(part: Part): HtmlAst[] {
let message = part.createMessage(this._expressionParser, this._interpolationConfig); let messages = part.createMessages(this._expressionParser, this._interpolationConfig);
// TODO - dirty smoke fix
let message = messages[0];
let messageId = id(message); let messageId = id(message);
if (!StringMapWrapper.contains(this._messages, messageId)) { if (!StringMapWrapper.contains(this._messages, messageId)) {

View File

@ -123,7 +123,7 @@ export class MessageExtractor {
private _extractMessagesFromPart(part: Part, interpolationConfig: InterpolationConfig): void { private _extractMessagesFromPart(part: Part, interpolationConfig: InterpolationConfig): void {
if (part.hasI18n) { if (part.hasI18n) {
this._messages.push(part.createMessage(this._expressionParser, interpolationConfig)); this._messages.push(...part.createMessages(this._expressionParser, interpolationConfig));
this._recurseToExtractMessagesFromAttributes(part.children, interpolationConfig); this._recurseToExtractMessagesFromAttributes(part.children, interpolationConfig);
} else { } else {
this._recurse(part.children, interpolationConfig); this._recurse(part.children, interpolationConfig);

View File

@ -74,10 +74,12 @@ export class Part {
this.children[0].sourceSpan.start, this.children[this.children.length - 1].sourceSpan.end); this.children[0].sourceSpan.start, this.children[this.children.length - 1].sourceSpan.end);
} }
createMessage(parser: ExpressionParser, interpolationConfig: InterpolationConfig): Message { createMessages(parser: ExpressionParser, interpolationConfig: InterpolationConfig): Message[] {
return new Message( let {message, icuMessages} = stringifyNodes(this.children, parser, interpolationConfig);
stringifyNodes(this.children, parser, interpolationConfig), meaning(this.i18n), return [
description(this.i18n)); new Message(message, meaning(this.i18n), description(this.i18n)),
...icuMessages.map(icu => new Message(icu, null))
];
} }
} }
@ -197,28 +199,33 @@ export function dedupePhName(usedNames: Map<string, number>, name: string): stri
*/ */
export function stringifyNodes( export function stringifyNodes(
nodes: HtmlAst[], expressionParser: ExpressionParser, nodes: HtmlAst[], expressionParser: ExpressionParser,
interpolationConfig: InterpolationConfig): string { interpolationConfig: InterpolationConfig): {message: string, icuMessages: string[]} {
const visitor = new _StringifyVisitor(expressionParser, interpolationConfig); const visitor = new _StringifyVisitor(expressionParser, interpolationConfig);
return htmlVisitAll(visitor, nodes).join(''); const icuMessages: string[] = [];
const message = htmlVisitAll(visitor, nodes, icuMessages).join('');
return {message, icuMessages};
} }
class _StringifyVisitor implements HtmlAstVisitor { class _StringifyVisitor implements HtmlAstVisitor {
private _index: number = 0; private _index: number = 0;
private _nestedExpansion = 0;
constructor( constructor(
private _parser: ExpressionParser, private _interpolationConfig: InterpolationConfig) {} private _expressionParser: ExpressionParser,
private _interpolationConfig: InterpolationConfig) {}
visitElement(ast: HtmlElementAst, context: any): any { visitElement(ast: HtmlElementAst, context: any): any {
let name = this._index++; const index = this._index++;
let children = this._join(htmlVisitAll(this, ast.children), ''); const children = this._join(htmlVisitAll(this, ast.children), '');
return `<ph name="e${name}">${children}</ph>`; return `<ph name="e${index}">${children}</ph>`;
} }
visitAttr(ast: HtmlAttrAst, context: any): any { return null; } visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
visitText(ast: HtmlTextAst, context: any): any { visitText(ast: HtmlTextAst, context: any): any {
let index = this._index++; const index = this._index++;
let noInterpolation = const noInterpolation = removeInterpolation(
removeInterpolation(ast.value, ast.sourceSpan, this._parser, this._interpolationConfig); ast.value, ast.sourceSpan, this._expressionParser, this._interpolationConfig);
if (noInterpolation != ast.value) { if (noInterpolation != ast.value) {
return `<ph name="t${index}">${noInterpolation}</ph>`; return `<ph name="t${index}">${noInterpolation}</ph>`;
} }
@ -227,9 +234,19 @@ class _StringifyVisitor implements HtmlAstVisitor {
visitComment(ast: HtmlCommentAst, context: any): any { return ''; } visitComment(ast: HtmlCommentAst, context: any): any { return ''; }
visitExpansion(ast: HtmlExpansionAst, context: any): any { return null; } visitExpansion(ast: HtmlExpansionAst, context: any): any {
const index = this._index++;
this._nestedExpansion++;
const content = `{${ast.switchValue}, ${ast.type}${htmlVisitAll(this, ast.cases).join('')}}`;
this._nestedExpansion--;
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; } return this._nestedExpansion == 0 ? `<ph name="x${index}">${content}</ph>` : content;
}
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any {
const expStr = htmlVisitAll(this, ast.expression).join('');
return ` ${ast.value} {${expStr}}`;
}
private _join(strs: string[], str: string): string { private _join(strs: string[], str: string): string {
return strs.filter(s => s.length > 0).join(str); return strs.filter(s => s.length > 0).join(str);

View File

@ -46,131 +46,134 @@ export function main() {
]); ]);
}); });
it('should replace attributes', () => { describe('interpolation', () => {
let translations: {[key: string]: string} = {}; it('should handle interpolation', () => {
translations[id(new Message('some message', 'meaning', null))] = 'another message'; let translations: {[key: string]: string} = {};
translations[id(new Message(
'<ph name="INTERPOLATION_0"/> and <ph name="INTERPOLATION_1"/>', null, null))] =
'<ph name="INTERPOLATION_1"/> or <ph name="INTERPOLATION_0"/>';
expect( expect(humanizeDom(parse('<div value=\'{{a}} and {{b}}\' i18n-value></div>', translations)))
humanizeDom(parse( .toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{{b}} or {{a}}']]);
'<div value=\'some message\' i18n-value=\'meaning|comment\'></div>', translations))) });
.toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', 'another message']]);
it('should handle interpolation with config', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message(
'<ph name="INTERPOLATION_0"/> and <ph name="INTERPOLATION_1"/>', null, null))] =
'<ph name="INTERPOLATION_1"/> or <ph name="INTERPOLATION_0"/>';
expect(humanizeDom(parse(
'<div value=\'{%a%} and {%b%}\' i18n-value></div>', translations, [], {},
InterpolationConfig.fromArray(['{%', '%}']))))
.toEqual([
[HtmlElementAst, 'div', 0],
[HtmlAttrAst, 'value', '{%b%} or {%a%}'],
]);
});
it('should handle interpolation with custom placeholder names', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="FIRST"/> and <ph name="SECOND"/>', null, null))] =
'<ph name="SECOND"/> or <ph name="FIRST"/>';
expect(
humanizeDom(parse(
`<div value='{{a //i18n(ph="FIRST")}} and {{b //i18n(ph="SECOND")}}' i18n-value></div>`,
translations)))
.toEqual([
[HtmlElementAst, 'div', 0],
[HtmlAttrAst, 'value', '{{b //i18n(ph="SECOND")}} or {{a //i18n(ph="FIRST")}}']
]);
});
it('should handle interpolation with duplicate placeholder names', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="FIRST"/> and <ph name="FIRST_1"/>', null, null))] =
'<ph name="FIRST_1"/> or <ph name="FIRST"/>';
expect(
humanizeDom(parse(
`<div value='{{a //i18n(ph="FIRST")}} and {{b //i18n(ph="FIRST")}}' i18n-value></div>`,
translations)))
.toEqual([
[HtmlElementAst, 'div', 0],
[HtmlAttrAst, 'value', '{{b //i18n(ph="FIRST")}} or {{a //i18n(ph="FIRST")}}']
]);
});
it('should support interpolation', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message(
'<ph name="e0">a</ph><ph name="e2"><ph name="t3">b<ph name="INTERPOLATION_0"/></ph></ph>',
null, null))] =
'<ph name="e2"><ph name="t3"><ph name="INTERPOLATION_0"/>B</ph></ph><ph name="e0">A</ph>';
expect(humanizeDom(parse('<div i18n><a>a</a><b>b{{i}}</b></div>', translations))).toEqual([
[HtmlElementAst, 'div', 0],
[HtmlElementAst, 'b', 1],
[HtmlTextAst, '{{i}}B', 2],
[HtmlElementAst, 'a', 1],
[HtmlTextAst, 'A', 2],
]);
});
}); });
it('should replace elements with the i18n attr', () => { describe('html', () => {
let translations: {[key: string]: string} = {}; it('should handle nested html', () => {
translations[id(new Message('message', 'meaning', null))] = 'another message'; let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="e0">a</ph><ph name="e2">b</ph>', null, null))] =
'<ph name="e2">B</ph><ph name="e0">A</ph>';
expect(humanizeDom(parse('<div i18n=\'meaning|desc\'>message</div>', translations))).toEqual([ expect(humanizeDom(parse('<div i18n><a>a</a><b>b</b></div>', translations))).toEqual([
[HtmlElementAst, 'div', 0], [HtmlTextAst, 'another message', 1] [HtmlElementAst, 'div', 0],
]); [HtmlElementAst, 'b', 1],
}); [HtmlTextAst, 'B', 2],
[HtmlElementAst, 'a', 1],
[HtmlTextAst, 'A', 2],
]);
});
it('should handle interpolation', () => { it('should i18n attributes of placeholder elements', () => {
let translations: {[key: string]: string} = {}; let translations: {[key: string]: string} = {};
translations[id(new Message( translations[id(new Message('<ph name="e0">a</ph>', null, null))] = '<ph name="e0">A</ph>';
'<ph name="INTERPOLATION_0"/> and <ph name="INTERPOLATION_1"/>', null, null))] = translations[id(new Message('b', null, null))] = 'B';
'<ph name="INTERPOLATION_1"/> or <ph name="INTERPOLATION_0"/>';
expect(humanizeDom(parse('<div value=\'{{a}} and {{b}}\' i18n-value></div>', translations))) expect(humanizeDom(parse('<div i18n><a value="b" i18n-value>a</a></div>', translations)))
.toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{{b}} or {{a}}']]); .toEqual([
}); [HtmlElementAst, 'div', 0],
[HtmlElementAst, 'a', 1],
[HtmlAttrAst, 'value', 'B'],
[HtmlTextAst, 'A', 2],
]);
});
it('should handle interpolation with config', () => { it('should preserve non-i18n attributes', () => {
let translations: {[key: string]: string} = {}; let translations: {[key: string]: string} = {};
translations[id(new Message( translations[id(new Message('message', null, null))] = 'another message';
'<ph name="INTERPOLATION_0"/> and <ph name="INTERPOLATION_1"/>', null, null))] =
'<ph name="INTERPOLATION_1"/> or <ph name="INTERPOLATION_0"/>';
expect(humanizeDom(parse( expect(humanizeDom(parse('<div i18n value="b">message</div>', translations))).toEqual([
'<div value=\'{%a%} and {%b%}\' i18n-value></div>', translations, [], {}, [HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', 'b'],
InterpolationConfig.fromArray(['{%', '%}'])))) [HtmlTextAst, 'another message', 1]
.toEqual([ ]);
[HtmlElementAst, 'div', 0], });
[HtmlAttrAst, 'value', '{%b%} or {%a%}'],
]);
});
it('should handle interpolation with custom placeholder names', () => { it('should replace attributes', () => {
let translations: {[key: string]: string} = {}; let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="FIRST"/> and <ph name="SECOND"/>', null, null))] = translations[id(new Message('some message', 'meaning', null))] = 'another message';
'<ph name="SECOND"/> or <ph name="FIRST"/>';
expect( expect(
humanizeDom(parse( humanizeDom(parse(
`<div value='{{a //i18n(ph="FIRST")}} and {{b //i18n(ph="SECOND")}}' i18n-value></div>`, '<div value=\'some message\' i18n-value=\'meaning|comment\'></div>', translations)))
translations))) .toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', 'another message']]);
.toEqual([ });
[HtmlElementAst, 'div', 0],
[HtmlAttrAst, 'value', '{{b //i18n(ph="SECOND")}} or {{a //i18n(ph="FIRST")}}']
]);
});
it('should handle interpolation with duplicate placeholder names', () => { it('should replace elements with the i18n attr', () => {
let translations: {[key: string]: string} = {}; let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="FIRST"/> and <ph name="FIRST_1"/>', null, null))] = translations[id(new Message('message', 'meaning', null))] = 'another message';
'<ph name="FIRST_1"/> or <ph name="FIRST"/>';
expect( expect(humanizeDom(parse('<div i18n=\'meaning|desc\'>message</div>', translations)))
humanizeDom(parse( .toEqual([[HtmlElementAst, 'div', 0], [HtmlTextAst, 'another message', 1]]);
`<div value='{{a //i18n(ph="FIRST")}} and {{b //i18n(ph="FIRST")}}' i18n-value></div>`, });
translations)))
.toEqual([
[HtmlElementAst, 'div', 0],
[HtmlAttrAst, 'value', '{{b //i18n(ph="FIRST")}} or {{a //i18n(ph="FIRST")}}']
]);
});
it('should handle nested html', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="e0">a</ph><ph name="e2">b</ph>', null, null))] =
'<ph name="e2">B</ph><ph name="e0">A</ph>';
expect(humanizeDom(parse('<div i18n><a>a</a><b>b</b></div>', translations))).toEqual([
[HtmlElementAst, 'div', 0],
[HtmlElementAst, 'b', 1],
[HtmlTextAst, 'B', 2],
[HtmlElementAst, 'a', 1],
[HtmlTextAst, 'A', 2],
]);
});
it('should support interpolation', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message(
'<ph name="e0">a</ph><ph name="e2"><ph name="t3">b<ph name="INTERPOLATION_0"/></ph></ph>',
null, null))] =
'<ph name="e2"><ph name="t3"><ph name="INTERPOLATION_0"/>B</ph></ph><ph name="e0">A</ph>';
expect(humanizeDom(parse('<div i18n><a>a</a><b>b{{i}}</b></div>', translations))).toEqual([
[HtmlElementAst, 'div', 0],
[HtmlElementAst, 'b', 1],
[HtmlTextAst, '{{i}}B', 2],
[HtmlElementAst, 'a', 1],
[HtmlTextAst, 'A', 2],
]);
});
it('should i18n attributes of placeholder elements', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="e0">a</ph>', null, null))] = '<ph name="e0">A</ph>';
translations[id(new Message('b', null, null))] = 'B';
expect(humanizeDom(parse('<div i18n><a value="b" i18n-value>a</a></div>', translations)))
.toEqual([
[HtmlElementAst, 'div', 0],
[HtmlElementAst, 'a', 1],
[HtmlAttrAst, 'value', 'B'],
[HtmlTextAst, 'A', 2],
]);
});
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', () => {

View File

@ -21,40 +21,9 @@ export function main() {
beforeEach(() => { beforeEach(() => {
const expParser = new ExpressionParser(new ExpressionLexer()); const expParser = new ExpressionParser(new ExpressionLexer());
const htmlParser = new HtmlParser(); const htmlParser = new HtmlParser();
// TODO: pass expression parser
extractor = new MessageExtractor(htmlParser, expParser, ['i18n-tag'], {'i18n-el': ['trans']}); extractor = new MessageExtractor(htmlParser, expParser, ['i18n-tag'], {'i18n-el': ['trans']});
}); });
it('should extract from elements with the i18n attr', () => {
let res = extractor.extract('<div i18n=\'meaning|desc\'>message</div>', 'someurl');
expect(res.messages).toEqual([new Message('message', 'meaning', 'desc')]);
});
it('should extract from elements with the i18n attr without a desc', () => {
let res = extractor.extract('<div i18n=\'meaning\'>message</div>', 'someurl');
expect(res.messages).toEqual([new Message('message', 'meaning', null)]);
});
it('should extract from elements with the i18n attr without a meaning', () => {
let res = extractor.extract('<div i18n>message</div>', 'someurl');
expect(res.messages).toEqual([new Message('message', null, null)]);
});
it('should extract from attributes', () => {
let res = extractor.extract(
`
<div
title1='message1' i18n-title1='meaning1|desc1'
title2='message2' i18n-title2='meaning2|desc2'>
</div>
`,
'someurl');
expect(res.messages).toEqual([
new Message('message1', 'meaning1', 'desc1'), new Message('message2', 'meaning2', 'desc2')
]);
});
it('should extract from partitions', () => { it('should extract from partitions', () => {
let res = extractor.extract( let res = extractor.extract(
` `
@ -79,90 +48,159 @@ export function main() {
expect(res.messages).toEqual([new Message('message1', 'meaning1', 'desc1')]); expect(res.messages).toEqual([new Message('message1', 'meaning1', 'desc1')]);
}); });
it('should replace interpolation with placeholders (text nodes)', () => { describe('ICU messages', () => {
let res = extractor.extract('<div i18n>Hi {{one}} and {{two}}</div>', 'someurl'); it('should replace icu messages with placeholders', () => {
expect(res.messages).toEqual([new Message( let res = extractor.extract('<div i18n>{count, plural, =0 {text} }</div>', 'someurl');
'<ph name="t0">Hi <ph name="INTERPOLATION_0"/> and <ph name="INTERPOLATION_1"/></ph>', expect(res.messages).toEqual([new Message(
null, null)]); '<ph name="x0">{count, plural =0 {text}}</ph>', null, null)]);
});
it('should replace HTML with placeholders in ICU cases', () => {
let res =
extractor.extract('<div i18n>{count, plural, =0 {<p>html</p>} }</div>', 'someurl');
expect(res.messages).toEqual([new Message(
'<ph name="x0">{count, plural =0 {<ph name="e1">html</ph>}}</ph>', null, null)]);
});
it('should replace interpolation with placeholders in ICU cases', () => {
let res =
extractor.extract('<div i18n>{count, plural, =0 {{{interpolation}}}}</div>', 'someurl');
expect(res.messages).toEqual([new Message(
'<ph name="x0">{count, plural =0 {<ph name="t1"><ph name="INTERPOLATION_0"/></ph>}}</ph>',
null, null)]);
});
it('should not replace nested interpolation with placeholders in ICU cases', () => {
let res = extractor.extract(
'<div i18n>{count, plural, =0 {{sex, gender, =m {{{he}}} =f {<b>she</b>}}}}</div>',
'someurl');
expect(res.messages).toEqual([new Message(
'<ph name="x0">{count, plural =0 {{sex, gender =m {<ph name="t2"><ph name="INTERPOLATION_0"/></ph>} =f {<ph name="e3">she</ph>}}}}</ph>',
null, null)]);
});
}); });
it('should replace interpolation with placeholders (attributes)', () => { describe('interpolation', () => {
let res = it('should replace interpolation with placeholders (text nodes)', () => {
extractor.extract('<div title=\'Hi {{one}} and {{two}}\' i18n-title></div>', 'someurl'); let res = extractor.extract('<div i18n>Hi {{one}} and {{two}}</div>', 'someurl');
expect(res.messages).toEqual([new Message( expect(res.messages).toEqual([new Message(
'Hi <ph name="INTERPOLATION_0"/> and <ph name="INTERPOLATION_1"/>', null, null)]); '<ph name="t0">Hi <ph name="INTERPOLATION_0"/> and <ph name="INTERPOLATION_1"/></ph>',
}); null, null)]);
});
it('should replace interpolation with named placeholders if provided (text nodes)', () => { it('should replace interpolation with placeholders (attributes)', () => {
let res = extractor.extract( let res =
` extractor.extract('<div title=\'Hi {{one}} and {{two}}\' i18n-title></div>', 'someurl');
expect(res.messages).toEqual([new Message(
'Hi <ph name="INTERPOLATION_0"/> and <ph name="INTERPOLATION_1"/>', null, null)]);
});
it('should replace interpolation with named placeholders if provided (text nodes)', () => {
let res = extractor.extract(
`
<div i18n>Hi {{one //i18n(ph="FIRST")}} and {{two //i18n(ph="SECOND")}}</div>`, <div i18n>Hi {{one //i18n(ph="FIRST")}} and {{two //i18n(ph="SECOND")}}</div>`,
'someurl'); 'someurl');
expect(res.messages).toEqual([new Message( expect(res.messages).toEqual([new Message(
'<ph name="t0">Hi <ph name="FIRST"/> and <ph name="SECOND"/></ph>', null, null)]); '<ph name="t0">Hi <ph name="FIRST"/> and <ph name="SECOND"/></ph>', null, null)]);
}); });
it('should replace interpolation with named placeholders if provided (attributes)', () => { it('should replace interpolation with named placeholders if provided (attributes)', () => {
let res = extractor.extract( let res = extractor.extract(
` `
<div title='Hi {{one //i18n(ph="FIRST")}} and {{two //i18n(ph="SECOND")}}' <div title='Hi {{one //i18n(ph="FIRST")}} and {{two //i18n(ph="SECOND")}}'
i18n-title></div>`, i18n-title></div>`,
'someurl'); 'someurl');
expect(res.messages).toEqual([new Message( expect(res.messages).toEqual([new Message(
'Hi <ph name="FIRST"/> and <ph name="SECOND"/>', null, null)]); 'Hi <ph name="FIRST"/> and <ph name="SECOND"/>', null, null)]);
});
}); });
it('should match named placeholders with extra spacing', () => { describe('placehoders', () => {
let res = extractor.extract( it('should match named placeholders with extra spacing', () => {
` let res = extractor.extract(
`
<div title='Hi {{one // i18n ( ph = "FIRST" )}} and {{two // i18n ( ph = "SECOND" )}}' <div title='Hi {{one // i18n ( ph = "FIRST" )}} and {{two // i18n ( ph = "SECOND" )}}'
i18n-title></div>`, i18n-title></div>`,
'someurl'); 'someurl');
expect(res.messages).toEqual([new Message( expect(res.messages).toEqual([new Message(
'Hi <ph name="FIRST"/> and <ph name="SECOND"/>', null, null)]); 'Hi <ph name="FIRST"/> and <ph name="SECOND"/>', null, null)]);
}); });
it('should suffix duplicate placeholder names with numbers', () => { it('should suffix duplicate placeholder names with numbers', () => {
let res = extractor.extract( let res = extractor.extract(
` `
<div title='Hi {{one //i18n(ph="FIRST")}} and {{two //i18n(ph="FIRST")}} and {{three //i18n(ph="FIRST")}}' <div title='Hi {{one //i18n(ph="FIRST")}} and {{two //i18n(ph="FIRST")}} and {{three //i18n(ph="FIRST")}}'
i18n-title></div>`, i18n-title></div>`,
'someurl'); 'someurl');
expect(res.messages).toEqual([new Message( expect(res.messages).toEqual([new Message(
'Hi <ph name="FIRST"/> and <ph name="FIRST_1"/> and <ph name="FIRST_2"/>', null, null)]); 'Hi <ph name="FIRST"/> and <ph name="FIRST_1"/> and <ph name="FIRST_2"/>', null,
null)]);
});
}); });
it('should handle html content', () => { describe('html', () => {
let res = extractor.extract( it('should extract from elements with the i18n attr', () => {
'<div i18n><div attr="value">zero<div>one</div></div><div>two</div></div>', 'someurl'); let res = extractor.extract('<div i18n=\'meaning|desc\'>message</div>', 'someurl');
expect(res.messages).toEqual([new Message( expect(res.messages).toEqual([new Message('message', 'meaning', 'desc')]);
'<ph name="e0">zero<ph name="e2">one</ph></ph><ph name="e4">two</ph>', null, null)]); });
});
it('should handle html content with interpolation', () => { it('should extract from elements with the i18n attr without a desc', () => {
let res = let res = extractor.extract('<div i18n=\'meaning\'>message</div>', 'someurl');
extractor.extract('<div i18n><div>zero{{a}}<div>{{b}}</div></div></div>', 'someurl'); expect(res.messages).toEqual([new Message('message', 'meaning', null)]);
expect(res.messages).toEqual([new Message( });
'<ph name="e0"><ph name="t1">zero<ph name="INTERPOLATION_0"/></ph><ph name="e2"><ph name="t3"><ph name="INTERPOLATION_0"/></ph></ph></ph>',
null, null)]);
});
it('should extract from nested elements', () => { it('should extract from elements with the i18n attr without a meaning', () => {
let res = extractor.extract( let res = extractor.extract('<div i18n>message</div>', 'someurl');
'<div title="message1" i18n-title="meaning1|desc1"><div i18n="meaning2|desc2">message2</div></div>', expect(res.messages).toEqual([new Message('message', null, null)]);
'someurl'); });
expect(res.messages).toEqual([
new Message('message2', 'meaning2', 'desc2'), new Message('message1', 'meaning1', 'desc1')
]);
});
it('should extract messages from attributes in i18n blocks', () => { it('should extract from attributes', () => {
let res = extractor.extract( let res = extractor.extract(
'<div i18n><div attr="value" i18n-attr="meaning|desc">message</div></div>', 'someurl'); `
expect(res.messages).toEqual([ <div
new Message('<ph name="e0">message</ph>', null, null), title1='message1' i18n-title1='meaning1|desc1'
new Message('value', 'meaning', 'desc') title2='message2' i18n-title2='meaning2|desc2'>
]); </div>
`,
'someurl');
expect(res.messages).toEqual([
new Message('message1', 'meaning1', 'desc1'), new Message('message2', 'meaning2', 'desc2')
]);
});
it('should handle html content', () => {
let res = extractor.extract(
'<div i18n><div attr="value">zero<div>one</div></div><div>two</div></div>', 'someurl');
expect(res.messages).toEqual([new Message(
'<ph name="e0">zero<ph name="e2">one</ph></ph><ph name="e4">two</ph>', null, null)]);
});
it('should handle html content with interpolation', () => {
let res =
extractor.extract('<div i18n><div>zero{{a}}<div>{{b}}</div></div></div>', 'someurl');
expect(res.messages).toEqual([new Message(
'<ph name="e0"><ph name="t1">zero<ph name="INTERPOLATION_0"/></ph><ph name="e2"><ph name="t3"><ph name="INTERPOLATION_0"/></ph></ph></ph>',
null, null)]);
});
it('should extract from nested elements', () => {
let res = extractor.extract(
'<div title="message1" i18n-title="meaning1|desc1"><div i18n="meaning2|desc2">message2</div></div>',
'someurl');
expect(res.messages).toEqual([
new Message('message2', 'meaning2', 'desc2'), new Message('message1', 'meaning1', 'desc1')
]);
});
it('should extract messages from attributes in i18n blocks', () => {
let res = extractor.extract(
'<div i18n><div attr="value" i18n-attr="meaning|desc">message</div></div>', 'someurl');
expect(res.messages).toEqual([
new Message('<ph name="e0">message</ph>', null, null),
new Message('value', 'meaning', 'desc')
]);
});
}); });
it('should remove duplicate messages', () => { it('should remove duplicate messages', () => {