feat(i18n): support plural and gender special forms
This commit is contained in:
parent
7c9717bba8
commit
88b0a239c4
|
@ -23,6 +23,8 @@ import {
|
||||||
HtmlAttrAst,
|
HtmlAttrAst,
|
||||||
HtmlAst,
|
HtmlAst,
|
||||||
HtmlCommentAst,
|
HtmlCommentAst,
|
||||||
|
HtmlExpansionAst,
|
||||||
|
HtmlExpansionCaseAst,
|
||||||
htmlVisitAll
|
htmlVisitAll
|
||||||
} from './html_ast';
|
} from './html_ast';
|
||||||
import {HtmlParser} from './html_parser';
|
import {HtmlParser} from './html_parser';
|
||||||
|
@ -158,4 +160,7 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
|
||||||
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
|
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
|
||||||
visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
|
visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
|
||||||
visitText(ast: HtmlTextAst, context: any): any { return null; }
|
visitText(ast: HtmlTextAst, context: any): any { return null; }
|
||||||
|
visitExpansion(ast: HtmlExpansionAst, context: any): any { return null; }
|
||||||
|
|
||||||
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,24 @@ export class HtmlTextAst implements HtmlAst {
|
||||||
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitText(this, context); }
|
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitText(this, context); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class HtmlExpansionAst implements HtmlAst {
|
||||||
|
constructor(public switchValue: string, public type: string, public cases: HtmlExpansionCaseAst[],
|
||||||
|
public sourceSpan: ParseSourceSpan, public switchValueSourceSpan: ParseSourceSpan) {}
|
||||||
|
visit(visitor: HtmlAstVisitor, context: any): any {
|
||||||
|
return visitor.visitExpansion(this, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HtmlExpansionCaseAst implements HtmlAst {
|
||||||
|
constructor(public value: string, public expression: HtmlAst[],
|
||||||
|
public sourceSpan: ParseSourceSpan, public valueSourceSpan: ParseSourceSpan,
|
||||||
|
public expSourceSpan: ParseSourceSpan) {}
|
||||||
|
|
||||||
|
visit(visitor: HtmlAstVisitor, context: any): any {
|
||||||
|
return visitor.visitExpansionCase(this, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class HtmlAttrAst implements HtmlAst {
|
export class HtmlAttrAst implements HtmlAst {
|
||||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||||
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitAttr(this, context); }
|
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitAttr(this, context); }
|
||||||
|
@ -34,6 +52,8 @@ export interface HtmlAstVisitor {
|
||||||
visitAttr(ast: HtmlAttrAst, context: any): any;
|
visitAttr(ast: HtmlAttrAst, context: any): any;
|
||||||
visitText(ast: HtmlTextAst, context: any): any;
|
visitText(ast: HtmlTextAst, context: any): any;
|
||||||
visitComment(ast: HtmlCommentAst, context: any): any;
|
visitComment(ast: HtmlCommentAst, context: any): any;
|
||||||
|
visitExpansion(ast: HtmlExpansionAst, context: any): any;
|
||||||
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[], context: any = null): any[] {
|
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[], context: any = null): any[] {
|
||||||
|
|
|
@ -14,6 +14,8 @@ import {
|
||||||
HtmlElementAst,
|
HtmlElementAst,
|
||||||
HtmlTextAst,
|
HtmlTextAst,
|
||||||
HtmlCommentAst,
|
HtmlCommentAst,
|
||||||
|
HtmlExpansionAst,
|
||||||
|
HtmlExpansionCaseAst,
|
||||||
HtmlAst
|
HtmlAst
|
||||||
} from './html_ast';
|
} from './html_ast';
|
||||||
import {HtmlParser, HtmlParseTreeResult} from './html_parser';
|
import {HtmlParser, HtmlParseTreeResult} from './html_parser';
|
||||||
|
@ -84,6 +86,13 @@ export class LegacyHtmlAstTransformer implements HtmlAstVisitor {
|
||||||
|
|
||||||
visitText(ast: HtmlTextAst, context: any): HtmlTextAst { return ast; }
|
visitText(ast: HtmlTextAst, context: any): HtmlTextAst { return ast; }
|
||||||
|
|
||||||
|
visitExpansion(ast: HtmlExpansionAst, context: any): any {
|
||||||
|
let cases = ast.cases.map(c => c.visit(this, null));
|
||||||
|
return new HtmlExpansionAst(ast.switchValue, ast.type, cases, ast.sourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return ast; }
|
||||||
|
|
||||||
private _rewriteLongSyntax(ast: HtmlAttrAst): HtmlAttrAst {
|
private _rewriteLongSyntax(ast: HtmlAttrAst): HtmlAttrAst {
|
||||||
let m = RegExpWrapper.firstMatch(LONG_SYNTAX_REGEXP, ast.name);
|
let m = RegExpWrapper.firstMatch(LONG_SYNTAX_REGEXP, ast.name);
|
||||||
let attrName = ast.name;
|
let attrName = ast.name;
|
||||||
|
@ -211,9 +220,10 @@ export class LegacyHtmlAstTransformer implements HtmlAstVisitor {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LegacyHtmlParser extends HtmlParser {
|
export class LegacyHtmlParser extends HtmlParser {
|
||||||
parse(sourceContent: string, sourceUrl: string): HtmlParseTreeResult {
|
parse(sourceContent: string, sourceUrl: string,
|
||||||
|
parseExpansionForms: boolean = false): HtmlParseTreeResult {
|
||||||
let transformer = new LegacyHtmlAstTransformer();
|
let transformer = new LegacyHtmlAstTransformer();
|
||||||
let htmlParseTreeResult = super.parse(sourceContent, sourceUrl);
|
let htmlParseTreeResult = super.parse(sourceContent, sourceUrl, parseExpansionForms);
|
||||||
|
|
||||||
let rootNodes = htmlParseTreeResult.rootNodes.map(node => node.visit(transformer, null));
|
let rootNodes = htmlParseTreeResult.rootNodes.map(node => node.visit(transformer, null));
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,8 @@ import {
|
||||||
HtmlAttrAst,
|
HtmlAttrAst,
|
||||||
HtmlTextAst,
|
HtmlTextAst,
|
||||||
HtmlCommentAst,
|
HtmlCommentAst,
|
||||||
|
HtmlExpansionAst,
|
||||||
|
HtmlExpansionCaseAst,
|
||||||
htmlVisitAll
|
htmlVisitAll
|
||||||
} from './html_ast';
|
} from './html_ast';
|
||||||
|
|
||||||
|
@ -268,7 +270,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||||
|
|
||||||
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
|
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
|
||||||
|
|
||||||
visitElement(element: HtmlElementAst, parent: ElementContext): any {
|
visitElement(element: HtmlElementAst, component: ElementContext): any {
|
||||||
var nodeName = element.name;
|
var nodeName = element.name;
|
||||||
var preparsedElement = preparseElement(element);
|
var preparsedElement = preparseElement(element);
|
||||||
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||||
|
@ -770,6 +772,9 @@ class NonBindableVisitor implements HtmlAstVisitor {
|
||||||
var ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
|
var ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
|
||||||
return new TextAst(ast.value, ngContentIndex, ast.sourceSpan);
|
return new TextAst(ast.value, ngContentIndex, ast.sourceSpan);
|
||||||
}
|
}
|
||||||
|
visitExpansion(ast: HtmlExpansionAst, context: any): any { return ast; }
|
||||||
|
|
||||||
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return ast; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoundElementOrDirectiveProperty {
|
class BoundElementOrDirectiveProperty {
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
import {
|
||||||
|
HtmlAst,
|
||||||
|
HtmlAstVisitor,
|
||||||
|
HtmlElementAst,
|
||||||
|
HtmlAttrAst,
|
||||||
|
HtmlTextAst,
|
||||||
|
HtmlCommentAst,
|
||||||
|
HtmlExpansionAst,
|
||||||
|
HtmlExpansionCaseAst,
|
||||||
|
htmlVisitAll
|
||||||
|
} from 'angular2/src/compiler/html_ast';
|
||||||
|
|
||||||
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands special forms into elements.
|
||||||
|
*
|
||||||
|
* For example,
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* { messages.length, plural,
|
||||||
|
* =0 {zero}
|
||||||
|
* =1 {one}
|
||||||
|
* =other {more than one}
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* will be expanded into
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <ul [ngPlural]="messages.length">
|
||||||
|
* <template [ngPluralCase]="0"><li i18n="plural_0">zero</li></template>
|
||||||
|
* <template [ngPluralCase]="1"><li i18n="plural_1">one</li></template>
|
||||||
|
* <template [ngPluralCase]="other"><li i18n="plural_other">more than one</li></template>
|
||||||
|
* </ul>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class Expander implements HtmlAstVisitor {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
visitElement(ast: HtmlElementAst, context: any): any {
|
||||||
|
return new HtmlElementAst(ast.name, ast.attrs, htmlVisitAll(this, ast.children), ast.sourceSpan,
|
||||||
|
ast.startSourceSpan, ast.endSourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitAttr(ast: HtmlAttrAst, context: any): any { return ast; }
|
||||||
|
|
||||||
|
visitText(ast: HtmlTextAst, context: any): any { return ast; }
|
||||||
|
|
||||||
|
visitComment(ast: HtmlCommentAst, context: any): any { return ast; }
|
||||||
|
|
||||||
|
visitExpansion(ast: HtmlExpansionAst, context: any): any {
|
||||||
|
return ast.type == "plural" ? _expandPluralForm(ast) : _expandDefaultForm(ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any {
|
||||||
|
throw new BaseException("Should not be reached");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _expandPluralForm(ast: HtmlExpansionAst): HtmlElementAst {
|
||||||
|
let children = ast.cases.map(
|
||||||
|
c => new HtmlElementAst(
|
||||||
|
`template`,
|
||||||
|
[
|
||||||
|
new HtmlAttrAst("[ngPluralCase]", c.value, c.valueSourceSpan),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
new HtmlElementAst(
|
||||||
|
`li`, [new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)],
|
||||||
|
c.expression, c.sourceSpan, c.sourceSpan, c.sourceSpan)
|
||||||
|
],
|
||||||
|
c.sourceSpan, c.sourceSpan, c.sourceSpan));
|
||||||
|
let switchAttr = new HtmlAttrAst("[ngPlural]", ast.switchValue, ast.switchValueSourceSpan);
|
||||||
|
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
|
||||||
|
ast.sourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _expandDefaultForm(ast: HtmlExpansionAst): HtmlElementAst {
|
||||||
|
let children = ast.cases.map(
|
||||||
|
c => new HtmlElementAst(
|
||||||
|
`template`,
|
||||||
|
[
|
||||||
|
new HtmlAttrAst("[ngSwitchWhen]", c.value, c.valueSourceSpan),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
new HtmlElementAst(
|
||||||
|
`li`, [new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)],
|
||||||
|
c.expression, c.sourceSpan, c.sourceSpan, c.sourceSpan)
|
||||||
|
],
|
||||||
|
c.sourceSpan, c.sourceSpan, c.sourceSpan));
|
||||||
|
let switchAttr = new HtmlAttrAst("[ngSwitch]", ast.switchValue, ast.switchValueSourceSpan);
|
||||||
|
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
|
||||||
|
ast.sourceSpan);
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ import {
|
||||||
HtmlAttrAst,
|
HtmlAttrAst,
|
||||||
HtmlTextAst,
|
HtmlTextAst,
|
||||||
HtmlCommentAst,
|
HtmlCommentAst,
|
||||||
|
HtmlExpansionAst,
|
||||||
|
HtmlExpansionCaseAst,
|
||||||
htmlVisitAll
|
htmlVisitAll
|
||||||
} from 'angular2/src/compiler/html_ast';
|
} from 'angular2/src/compiler/html_ast';
|
||||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
@ -14,6 +16,7 @@ import {RegExpWrapper, NumberWrapper, isPresent} from 'angular2/src/facade/lang'
|
||||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||||
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
|
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
|
||||||
import {Message, id} from './message';
|
import {Message, id} from './message';
|
||||||
|
import {Expander} from './expander';
|
||||||
import {
|
import {
|
||||||
messageFromAttribute,
|
messageFromAttribute,
|
||||||
I18nError,
|
I18nError,
|
||||||
|
@ -119,19 +122,25 @@ export class I18nHtmlParser implements HtmlParser {
|
||||||
constructor(private _htmlParser: HtmlParser, private _parser: Parser,
|
constructor(private _htmlParser: HtmlParser, private _parser: Parser,
|
||||||
private _messagesContent: string, private _messages: {[key: string]: HtmlAst[]}) {}
|
private _messagesContent: string, private _messages: {[key: string]: HtmlAst[]}) {}
|
||||||
|
|
||||||
parse(sourceContent: string, sourceUrl: string): HtmlParseTreeResult {
|
parse(sourceContent: string, sourceUrl: string,
|
||||||
|
parseExpansionForms: boolean = false): HtmlParseTreeResult {
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
|
|
||||||
let res = this._htmlParser.parse(sourceContent, sourceUrl);
|
let res = this._htmlParser.parse(sourceContent, sourceUrl, parseExpansionForms);
|
||||||
if (res.errors.length > 0) {
|
if (res.errors.length > 0) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
let nodes = this._recurse(res.rootNodes);
|
let nodes = this._recurse(this._expandNodes(res.rootNodes));
|
||||||
return this.errors.length > 0 ? new HtmlParseTreeResult([], this.errors) :
|
return this.errors.length > 0 ? new HtmlParseTreeResult([], this.errors) :
|
||||||
new HtmlParseTreeResult(nodes, []);
|
new HtmlParseTreeResult(nodes, []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _expandNodes(nodes: HtmlAst[]): HtmlAst[] {
|
||||||
|
let e = new Expander();
|
||||||
|
return htmlVisitAll(e, nodes);
|
||||||
|
}
|
||||||
|
|
||||||
private _processI18nPart(p: Part): HtmlAst[] {
|
private _processI18nPart(p: Part): HtmlAst[] {
|
||||||
try {
|
try {
|
||||||
return p.hasI18n ? this._mergeI18Part(p) : this._recurseIntoI18nPart(p);
|
return p.hasI18n ? this._mergeI18Part(p) : this._recurseIntoI18nPart(p);
|
||||||
|
@ -360,5 +369,9 @@ class _CreateNodeMapping implements HtmlAstVisitor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitExpansion(ast: HtmlExpansionAst, context: any): any { return null; }
|
||||||
|
|
||||||
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; }
|
||||||
|
|
||||||
visitComment(ast: HtmlCommentAst, context: any): any { return ""; }
|
visitComment(ast: HtmlCommentAst, context: any): any { return ""; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
|
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
|
||||||
import {Message, id} from './message';
|
import {Message, id} from './message';
|
||||||
|
import {Expander} from './expander';
|
||||||
import {
|
import {
|
||||||
I18nError,
|
I18nError,
|
||||||
Part,
|
Part,
|
||||||
|
@ -121,15 +122,20 @@ export class MessageExtractor {
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
|
|
||||||
let res = this._htmlParser.parse(template, sourceUrl);
|
let res = this._htmlParser.parse(template, sourceUrl, true);
|
||||||
if (res.errors.length > 0) {
|
if (res.errors.length > 0) {
|
||||||
return new ExtractionResult([], res.errors);
|
return new ExtractionResult([], res.errors);
|
||||||
} else {
|
} else {
|
||||||
this._recurse(res.rootNodes);
|
this._recurse(this._expandNodes(res.rootNodes));
|
||||||
return new ExtractionResult(this.messages, this.errors);
|
return new ExtractionResult(this.messages, this.errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _expandNodes(nodes: HtmlAst[]): HtmlAst[] {
|
||||||
|
let e = new Expander();
|
||||||
|
return htmlVisitAll(e, nodes);
|
||||||
|
}
|
||||||
|
|
||||||
private _extractMessagesFromPart(p: Part): void {
|
private _extractMessagesFromPart(p: Part): void {
|
||||||
if (p.hasI18n) {
|
if (p.hasI18n) {
|
||||||
this.messages.push(p.createMessage(this._parser));
|
this.messages.push(p.createMessage(this._parser));
|
||||||
|
|
|
@ -6,6 +6,8 @@ import {
|
||||||
HtmlAttrAst,
|
HtmlAttrAst,
|
||||||
HtmlTextAst,
|
HtmlTextAst,
|
||||||
HtmlCommentAst,
|
HtmlCommentAst,
|
||||||
|
HtmlExpansionAst,
|
||||||
|
HtmlExpansionCaseAst,
|
||||||
htmlVisitAll
|
htmlVisitAll
|
||||||
} from 'angular2/src/compiler/html_ast';
|
} from 'angular2/src/compiler/html_ast';
|
||||||
import {isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
|
||||||
|
@ -179,6 +181,10 @@ 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; }
|
||||||
|
|
||||||
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; }
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import {
|
||||||
HtmlAttrAst,
|
HtmlAttrAst,
|
||||||
HtmlTextAst,
|
HtmlTextAst,
|
||||||
HtmlCommentAst,
|
HtmlCommentAst,
|
||||||
|
HtmlExpansionAst,
|
||||||
|
HtmlExpansionCaseAst,
|
||||||
htmlVisitAll
|
htmlVisitAll
|
||||||
} from 'angular2/src/compiler/html_ast';
|
} from 'angular2/src/compiler/html_ast';
|
||||||
import {ParseError, ParseLocation} from 'angular2/src/compiler/parse_util';
|
import {ParseError, ParseLocation} from 'angular2/src/compiler/parse_util';
|
||||||
|
@ -70,6 +72,19 @@ class _Humanizer implements HtmlAstVisitor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitExpansion(ast: HtmlExpansionAst, context: any): any {
|
||||||
|
var res = this._appendContext(ast, [HtmlExpansionAst, ast.switchValue, ast.type]);
|
||||||
|
this.result.push(res);
|
||||||
|
htmlVisitAll(this, ast.cases);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any {
|
||||||
|
var res = this._appendContext(ast, [HtmlExpansionCaseAst, ast.value]);
|
||||||
|
this.result.push(res);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private _appendContext(ast: HtmlAst, input: any[]): any[] {
|
private _appendContext(ast: HtmlAst, input: any[]): any[] {
|
||||||
if (!this.includeSourceSpan) return input;
|
if (!this.includeSourceSpan) return input;
|
||||||
input.push(ast.sourceSpan.toString());
|
input.push(ast.sourceSpan.toString());
|
||||||
|
|
|
@ -42,7 +42,7 @@ export function main() {
|
||||||
let res = deserializeXmb(`<message-bundle>${msgs}</message-bundle>`, 'someUrl');
|
let res = deserializeXmb(`<message-bundle>${msgs}</message-bundle>`, 'someUrl');
|
||||||
|
|
||||||
return new I18nHtmlParser(htmlParser, parser, res.content, res.messages)
|
return new I18nHtmlParser(htmlParser, parser, res.content, res.messages)
|
||||||
.parse(template, "someurl");
|
.parse(template, "someurl", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should delegate to the provided parser when no i18n", () => {
|
it("should delegate to the provided parser when no i18n", () => {
|
||||||
|
@ -188,6 +188,82 @@ export function main() {
|
||||||
expect(res[1].sourceSpan.start.offset).toEqual(10);
|
expect(res[1].sourceSpan.start.offset).toEqual(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should handle the plural special form", () => {
|
||||||
|
let translations: {[key: string]: string} = {};
|
||||||
|
translations[id(new Message('zero<ph name="e1">bold</ph>', "plural_0", null))] =
|
||||||
|
'ZERO<ph name="e1">BOLD</ph>';
|
||||||
|
|
||||||
|
let res = parse(`{messages.length, plural,=0 {zero<b>bold</b>}}`, translations);
|
||||||
|
|
||||||
|
expect(humanizeDom(res))
|
||||||
|
.toEqual([
|
||||||
|
[HtmlElementAst, 'ul', 0],
|
||||||
|
[HtmlAttrAst, '[ngPlural]', 'messages.length'],
|
||||||
|
[HtmlElementAst, 'template', 1],
|
||||||
|
[HtmlAttrAst, '[ngPluralCase]', '0'],
|
||||||
|
[HtmlElementAst, 'li', 2],
|
||||||
|
[HtmlTextAst, 'ZERO', 3],
|
||||||
|
[HtmlElementAst, 'b', 3],
|
||||||
|
[HtmlTextAst, 'BOLD', 4]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly set source code positions", () => {
|
||||||
|
let translations: {[key: string]: string} = {};
|
||||||
|
translations[id(new Message('<ph name="e0">bold</ph>', "plural_0", null))] =
|
||||||
|
'<ph name="e0">BOLD</ph>';
|
||||||
|
|
||||||
|
let nodes = parse(`{messages.length, plural,=0 {<b>bold</b>}}`, translations).rootNodes;
|
||||||
|
|
||||||
|
let ul: HtmlElementAst = <HtmlElementAst>nodes[0];
|
||||||
|
|
||||||
|
expect(ul.sourceSpan.start.col).toEqual(0);
|
||||||
|
expect(ul.sourceSpan.end.col).toEqual(42);
|
||||||
|
|
||||||
|
expect(ul.startSourceSpan.start.col).toEqual(0);
|
||||||
|
expect(ul.startSourceSpan.end.col).toEqual(42);
|
||||||
|
|
||||||
|
expect(ul.endSourceSpan.start.col).toEqual(0);
|
||||||
|
expect(ul.endSourceSpan.end.col).toEqual(42);
|
||||||
|
|
||||||
|
let switchExp = ul.attrs[0];
|
||||||
|
expect(switchExp.sourceSpan.start.col).toEqual(1);
|
||||||
|
expect(switchExp.sourceSpan.end.col).toEqual(16);
|
||||||
|
|
||||||
|
let template: HtmlElementAst = <HtmlElementAst>ul.children[0];
|
||||||
|
expect(template.sourceSpan.start.col).toEqual(26);
|
||||||
|
expect(template.sourceSpan.end.col).toEqual(41);
|
||||||
|
|
||||||
|
let switchCheck = template.attrs[0];
|
||||||
|
expect(switchCheck.sourceSpan.start.col).toEqual(26);
|
||||||
|
expect(switchCheck.sourceSpan.end.col).toEqual(28);
|
||||||
|
|
||||||
|
let li: HtmlElementAst = <HtmlElementAst>template.children[0];
|
||||||
|
expect(li.sourceSpan.start.col).toEqual(26);
|
||||||
|
expect(li.sourceSpan.end.col).toEqual(41);
|
||||||
|
|
||||||
|
let b: HtmlElementAst = <HtmlElementAst>li.children[0];
|
||||||
|
expect(b.sourceSpan.start.col).toEqual(29);
|
||||||
|
expect(b.sourceSpan.end.col).toEqual(32);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle other special forms", () => {
|
||||||
|
let translations: {[key: string]: string} = {};
|
||||||
|
translations[id(new Message('m', "gender_male", null))] = 'M';
|
||||||
|
|
||||||
|
let res = parse(`{person.gender, gender,=male {m}}`, translations);
|
||||||
|
|
||||||
|
expect(humanizeDom(res))
|
||||||
|
.toEqual([
|
||||||
|
[HtmlElementAst, 'ul', 0],
|
||||||
|
[HtmlAttrAst, '[ngSwitch]', 'person.gender'],
|
||||||
|
[HtmlElementAst, 'template', 1],
|
||||||
|
[HtmlAttrAst, '[ngSwitchWhen]', 'male'],
|
||||||
|
[HtmlElementAst, 'li', 2],
|
||||||
|
[HtmlTextAst, 'M', 3],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
describe("errors", () => {
|
describe("errors", () => {
|
||||||
it("should error when giving an invalid template", () => {
|
it("should error when giving an invalid template", () => {
|
||||||
expect(humanizeErrors(parse("<a>a</b>", {}).errors))
|
expect(humanizeErrors(parse("<a>a</b>", {}).errors))
|
||||||
|
|
|
@ -176,6 +176,24 @@ export function main() {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should extract messages from special forms", () => {
|
||||||
|
let res = extractor.extract(`
|
||||||
|
<div>
|
||||||
|
{messages.length, plural,
|
||||||
|
=0 {You have <b>no</b> messages}
|
||||||
|
=1 {You have one message}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
"someurl");
|
||||||
|
|
||||||
|
expect(res.messages)
|
||||||
|
.toEqual([
|
||||||
|
new Message('You have <ph name="e1">no</ph> messages', "plural_0", null),
|
||||||
|
new Message('You have one message', "plural_1", null)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should remove duplicate messages", () => {
|
it("should remove duplicate messages", () => {
|
||||||
let res = extractor.extract(`
|
let res = extractor.extract(`
|
||||||
<!-- i18n: meaning|desc1 -->message<!-- /i18n -->
|
<!-- i18n: meaning|desc1 -->message<!-- /i18n -->
|
||||||
|
|
Loading…
Reference in New Issue