diff --git a/modules/@angular/compiler/src/html_lexer.ts b/modules/@angular/compiler/src/html_lexer.ts
index 0e25d640f4..bfd1cd7777 100644
--- a/modules/@angular/compiler/src/html_lexer.ts
+++ b/modules/@angular/compiler/src/html_lexer.ts
@@ -1,5 +1,5 @@
-import {StringWrapper, NumberWrapper, isPresent, isBlank, serializeEnum} from '../src/facade/lang';
-import {ListWrapper} from '../src/facade/collection';
+import {StringWrapper, NumberWrapper, isPresent, isBlank} from './facade/lang';
+import {ListWrapper} from './facade/collection';
import {ParseLocation, ParseError, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {getHtmlTagDefinition, HtmlTagContentType, NAMED_ENTITIES} from './html_tags';
diff --git a/modules/@angular/compiler/src/i18n/i18n_html_parser.ts b/modules/@angular/compiler/src/i18n/i18n_html_parser.ts
index 8081c3f399..b167ad0dde 100644
--- a/modules/@angular/compiler/src/i18n/i18n_html_parser.ts
+++ b/modules/@angular/compiler/src/i18n/i18n_html_parser.ts
@@ -1,5 +1,5 @@
-import {HtmlParser, HtmlParseTreeResult} from '../html_parser';
-import {ParseSourceSpan, ParseError} from '../parse_util';
+import {HtmlParser, HtmlParseTreeResult} from "../html_parser";
+import {ParseSourceSpan, ParseError} from "../parse_util";
import {
HtmlAst,
HtmlAstVisitor,
@@ -10,31 +10,28 @@ import {
HtmlExpansionAst,
HtmlExpansionCaseAst,
htmlVisitAll
-} from '../html_ast';
-import {ListWrapper, StringMapWrapper} from '../../src/facade/collection';
-import {RegExpWrapper, NumberWrapper, isPresent} from '../../src/facade/lang';
-import {BaseException} from '../../src/facade/exceptions';
-import {Parser} from '../expression_parser/parser';
-import {id} from './message';
-import {expandNodes} from './expander';
+} from "../html_ast";
+import {ListWrapper, StringMapWrapper} from "../facade/collection";
+import {RegExpWrapper, NumberWrapper, isPresent} from "../facade/lang";
+import {BaseException} from "../facade/exceptions";
+import {Parser} from "../expression_parser/parser";
+import {id} from "./message";
+import {expandNodes} from "./expander";
import {
- messageFromAttribute,
+ messageFromI18nAttribute,
I18nError,
I18N_ATTR_PREFIX,
I18N_ATTR,
partition,
Part,
- stringifyNodes,
- meaning,
getPhNameFromBinding,
- dedupePhName
-} from './shared';
+ dedupePhName,
+ messageFromAttribute
+} from "./shared";
-const _I18N_ATTR = "i18n";
const _PLACEHOLDER_ELEMENT = "ph";
const _NAME_ATTR = "name";
-const _I18N_ATTR_PREFIX = "i18n-";
-let _PLACEHOLDER_EXPANDED_REGEXP = RegExpWrapper.create(`\\\\<\\/ph\\>`);
+let _PLACEHOLDER_EXPANDED_REGEXP = /<\/ph>/gi;
/**
* Creates an i18n-ed version of the parsed template.
@@ -120,13 +117,15 @@ export class I18nHtmlParser implements HtmlParser {
errors: ParseError[];
constructor(private _htmlParser: HtmlParser, private _parser: Parser,
- private _messagesContent: string, private _messages: {[key: string]: HtmlAst[]}) {}
+ private _messagesContent: string, private _messages: {[key: string]: HtmlAst[]},
+ private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
parse(sourceContent: string, sourceUrl: string,
parseExpansionForms: boolean = false): HtmlParseTreeResult {
this.errors = [];
let res = this._htmlParser.parse(sourceContent, sourceUrl, true);
+
if (res.errors.length > 0) {
return res;
} else {
@@ -184,7 +183,7 @@ export class I18nHtmlParser implements HtmlParser {
}
private _recurse(nodes: HtmlAst[]): HtmlAst[] {
- let ps = partition(nodes, this.errors);
+ let ps = partition(nodes, this.errors, this._implicitTags);
return ListWrapper.flatten(ps.map(p => this._processI18nPart(p)));
}
@@ -281,17 +280,26 @@ export class I18nHtmlParser implements HtmlParser {
private _i18nAttributes(el: HtmlElementAst): HtmlAttrAst[] {
let res = [];
+ let implicitAttrs: string[] =
+ isPresent(this._implicitAttrs[el.name]) ? this._implicitAttrs[el.name] : [];
+
el.attrs.forEach(attr => {
if (attr.name.startsWith(I18N_ATTR_PREFIX) || attr.name == I18N_ATTR) return;
- let i18ns = el.attrs.filter(a => a.name == `i18n-${attr.name}`);
+ let message;
+
+ let i18ns = el.attrs.filter(a => a.name == `${I18N_ATTR_PREFIX}${attr.name}`);
+
if (i18ns.length == 0) {
- res.push(attr);
- return;
+ if (implicitAttrs.indexOf(attr.name) == -1) {
+ res.push(attr);
+ return;
+ }
+ message = messageFromAttribute(this._parser, attr);
+ } else {
+ message = messageFromI18nAttribute(this._parser, el, i18ns[0]);
}
- let i18n = i18ns[0];
- let message = messageFromAttribute(this._parser, el, i18n);
let messageId = id(message);
if (StringMapWrapper.contains(this._messages, messageId)) {
diff --git a/modules/@angular/compiler/src/i18n/message_extractor.ts b/modules/@angular/compiler/src/i18n/message_extractor.ts
index a1c65cc1db..4b67314ef7 100644
--- a/modules/@angular/compiler/src/i18n/message_extractor.ts
+++ b/modules/@angular/compiler/src/i18n/message_extractor.ts
@@ -1,29 +1,19 @@
-import {HtmlParser} from '../html_parser';
-import {ParseSourceSpan, ParseError} from '../parse_util';
-import {
- HtmlAst,
- HtmlAstVisitor,
- HtmlElementAst,
- HtmlAttrAst,
- HtmlTextAst,
- HtmlCommentAst,
- htmlVisitAll
-} from '../html_ast';
-import {isPresent} from '../../src/facade/lang';
-import {StringMapWrapper} from '../../src/facade/collection';
-import {Parser} from '../expression_parser/parser';
-import {Message, id} from './message';
-import {expandNodes} from './expander';
+import {HtmlParser} from "../html_parser";
+import {ParseError} from "../parse_util";
+import {HtmlAst, HtmlElementAst} from "../html_ast";
+import {isPresent} from "../facade/lang";
+import {StringMapWrapper} from "../facade/collection";
+import {Parser} from "../expression_parser/parser";
+import {Message, id} from "./message";
+import {expandNodes} from "./expander";
import {
I18nError,
Part,
I18N_ATTR_PREFIX,
partition,
- meaning,
- description,
- stringifyNodes,
+ messageFromI18nAttribute,
messageFromAttribute
-} from './shared';
+} from "./shared";
/**
* All messages extracted from a template.
@@ -116,7 +106,8 @@ export class MessageExtractor {
messages: Message[];
errors: ParseError[];
- constructor(private _htmlParser: HtmlParser, private _parser: Parser) {}
+ constructor(private _htmlParser: HtmlParser, private _parser: Parser,
+ private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
extract(template: string, sourceUrl: string): ExtractionResult {
this.messages = [];
@@ -146,7 +137,7 @@ export class MessageExtractor {
private _recurse(nodes: HtmlAst[]): void {
if (isPresent(nodes)) {
- let ps = partition(nodes, this.errors);
+ let ps = partition(nodes, this.errors, this._implicitTags);
ps.forEach(p => this._extractMessagesFromPart(p));
}
}
@@ -161,10 +152,15 @@ export class MessageExtractor {
}
private _extractMessagesFromAttributes(p: HtmlElementAst): void {
+ let transAttrs: string[] =
+ isPresent(this._implicitAttrs[p.name]) ? this._implicitAttrs[p.name] : [];
+ let explicitAttrs: string[] = [];
+
p.attrs.forEach(attr => {
if (attr.name.startsWith(I18N_ATTR_PREFIX)) {
try {
- this.messages.push(messageFromAttribute(this._parser, p, attr));
+ explicitAttrs.push(attr.name.substring(I18N_ATTR_PREFIX.length));
+ this.messages.push(messageFromI18nAttribute(this._parser, p, attr));
} catch (e) {
if (e instanceof I18nError) {
this.errors.push(e);
@@ -174,5 +170,10 @@ export class MessageExtractor {
}
}
});
+
+ p.attrs.filter(attr => !attr.name.startsWith(I18N_ATTR_PREFIX))
+ .filter(attr => explicitAttrs.indexOf(attr.name) == -1)
+ .filter(attr => transAttrs.indexOf(attr.name) > -1)
+ .forEach(attr => this.messages.push(messageFromAttribute(this._parser, attr)));
}
}
diff --git a/modules/@angular/compiler/src/i18n/shared.ts b/modules/@angular/compiler/src/i18n/shared.ts
index 6b2414dd67..c0ae76acf4 100644
--- a/modules/@angular/compiler/src/i18n/shared.ts
+++ b/modules/@angular/compiler/src/i18n/shared.ts
@@ -27,7 +27,7 @@ export class I18nError extends ParseError {
// Man, this is so ugly!
-export function partition(nodes: HtmlAst[], errors: ParseError[]): Part[] {
+export function partition(nodes: HtmlAst[], errors: ParseError[], implicitTags: string[]): Part[] {
let res = [];
for (let i = 0; i < nodes.length; ++i) {
@@ -47,7 +47,8 @@ export function partition(nodes: HtmlAst[], errors: ParseError[]): Part[] {
} else if (n instanceof HtmlElementAst) {
let i18n = _findI18nAttr(n);
- res.push(new Part(n, null, n.children, isPresent(i18n) ? i18n.value : null, isPresent(i18n)));
+ let hasI18n: boolean = isPresent(i18n) || implicitTags.indexOf(n.name) > -1;
+ res.push(new Part(n, null, n.children, isPresent(i18n) ? i18n.value : null, hasI18n));
} else if (n instanceof HtmlTextAst) {
res.push(new Part(null, n, null, null, false));
}
@@ -99,17 +100,28 @@ export function description(i18n: string): string {
return parts.length > 1 ? parts[1] : null;
}
-export function messageFromAttribute(parser: Parser, p: HtmlElementAst,
- attr: HtmlAttrAst): Message {
- let expectedName = attr.name.substring(5);
+/**
+ * Extract a translation string given an `i18n-` prefixed attribute.
+ *
+ * @internal
+ */
+export function messageFromI18nAttribute(parser: Parser, p: HtmlElementAst,
+ i18nAttr: HtmlAttrAst): Message {
+ let expectedName = i18nAttr.name.substring(5);
let matching = p.attrs.filter(a => a.name == expectedName);
if (matching.length > 0) {
- let value = removeInterpolation(matching[0].value, matching[0].sourceSpan, parser);
- return new Message(value, meaning(attr.value), description(attr.value));
- } else {
- throw new I18nError(p.sourceSpan, `Missing attribute '${expectedName}'.`);
+ return messageFromAttribute(parser, matching[0], meaning(i18nAttr.value),
+ description(i18nAttr.value));
}
+
+ throw new I18nError(p.sourceSpan, `Missing attribute '${expectedName}'.`);
+}
+
+export function messageFromAttribute(parser: Parser, attr: HtmlAttrAst, meaning: string = null,
+ description: string = null): Message {
+ let value = removeInterpolation(attr.value, attr.sourceSpan, parser);
+ return new Message(value, meaning, description);
}
export function removeInterpolation(value: string, source: ParseSourceSpan,
diff --git a/modules/@angular/compiler/test/i18n/i18n_html_parser_spec.ts b/modules/@angular/compiler/test/i18n/i18n_html_parser_spec.ts
index 2985501805..6e4d59703b 100644
--- a/modules/@angular/compiler/test/i18n/i18n_html_parser_spec.ts
+++ b/modules/@angular/compiler/test/i18n/i18n_html_parser_spec.ts
@@ -1,38 +1,19 @@
-import {
- beforeEach,
- ddescribe,
- describe,
- expect,
- iit,
- inject,
- it,
- xdescribe,
- xit
-} from '@angular/core/testing/testing_internal';
-
-import {I18nHtmlParser} from '@angular/compiler/src/i18n/i18n_html_parser';
-import {Message, id} from '@angular/compiler/src/i18n/message';
-import {Parser} from '@angular/compiler/src/expression_parser/parser';
-import {Lexer} from '@angular/compiler/src/expression_parser/lexer';
-
-import {StringMapWrapper} from '../../src/facade/collection';
-import {HtmlParser, HtmlParseTreeResult} from '@angular/compiler/src/html_parser';
-import {
- HtmlAst,
- HtmlAstVisitor,
- HtmlElementAst,
- HtmlAttrAst,
- HtmlTextAst,
- HtmlCommentAst,
- htmlVisitAll
-} from '@angular/compiler/src/html_ast';
-import {serializeXmb, deserializeXmb} from '@angular/compiler/src/i18n/xmb_serializer';
-import {ParseError, ParseLocation} from '@angular/compiler/src/parse_util';
-import {humanizeDom} from '@angular/compiler/test/html_ast_spec_utils';
+import {describe, expect, it, iit, ddescribe} from "@angular/core/testing/testing_internal";
+import {I18nHtmlParser} from "@angular/compiler/src/i18n/i18n_html_parser";
+import {Message, id} from "@angular/compiler/src/i18n/message";
+import {Parser} from "@angular/compiler/src/expression_parser/parser";
+import {Lexer} from "@angular/compiler/src/expression_parser/lexer";
+import {StringMapWrapper} from "../../src/facade/collection";
+import {HtmlParser, HtmlParseTreeResult} from "@angular/compiler/src/html_parser";
+import {HtmlElementAst, HtmlAttrAst, HtmlTextAst} from "@angular/compiler/src/html_ast";
+import {deserializeXmb} from "@angular/compiler/src/i18n/xmb_serializer";
+import {ParseError} from "@angular/compiler/src/parse_util";
+import {humanizeDom} from "@angular/compiler/test/html_ast_spec_utils";
export function main() {
describe('I18nHtmlParser', () => {
- function parse(template: string, messages: {[key: string]: string}): HtmlParseTreeResult {
+ function parse(template: string, messages: {[key: string]: string}, implicitTags: string[] = [],
+ implicitAttrs: {[k: string]: string[]} = {}): HtmlParseTreeResult {
var parser = new Parser(new Lexer());
let htmlParser = new HtmlParser();
@@ -40,7 +21,8 @@ export function main() {
StringMapWrapper.forEach(messages, (v, k) => msgs += `${v}`);
let res = deserializeXmb(`${msgs}`, 'someUrl');
- return new I18nHtmlParser(htmlParser, parser, res.content, res.messages)
+ return new I18nHtmlParser(htmlParser, parser, res.content, res.messages, implicitTags,
+ implicitAttrs)
.parse(template, "someurl", true);
}
@@ -331,6 +313,44 @@ export function main() {
.toEqual(["Invalid interpolation name '99'"]);
});
+ describe('implicit translation', () => {
+ it("should support attributes", () => {
+ let translations: {[key: string]: string} = {};
+ translations[id(new Message("some message", null, null))] = "another message";
+
+ expect(humanizeDom(parse("", translations, [],
+ {'i18n-el': ['value']})))
+ .toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlAttrAst, 'value', 'another message']]);
+ });
+
+ it("should support attributes with meaning and description", () => {
+ let translations: {[key: string]: string} = {};
+ translations[id(new Message("some message", "meaning", "description"))] =
+ "another message";
+
+ expect(humanizeDom(parse(
+ "",
+ translations, [], {'i18n-el': ['value']})))
+ .toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlAttrAst, 'value', 'another message']]);
+ });
+
+ it("should support elements", () => {
+ let translations: {[key: string]: string} = {};
+ translations[id(new Message("message", null, null))] = "another message";
+
+ expect(humanizeDom(parse("message", translations, ['i18n-el'])))
+ .toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlTextAst, 'another message', 1]]);
+ });
+
+ it("should support elements with meaning and description", () => {
+ let translations: {[key: string]: string} = {};
+ translations[id(new Message("message", "meaning", "description"))] = "another message";
+
+ expect(humanizeDom(parse("message",
+ translations, ['i18n-el'])))
+ .toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlTextAst, 'another message', 1]]);
+ });
+ });
});
});
}
diff --git a/modules/@angular/compiler/test/i18n/message_extractor_spec.ts b/modules/@angular/compiler/test/i18n/message_extractor_spec.ts
index eb4b9ce5ce..9438888e0d 100644
--- a/modules/@angular/compiler/test/i18n/message_extractor_spec.ts
+++ b/modules/@angular/compiler/test/i18n/message_extractor_spec.ts
@@ -23,7 +23,7 @@ export function main() {
beforeEach(() => {
let htmlParser = new HtmlParser();
var parser = new Parser(new Lexer());
- extractor = new MessageExtractor(htmlParser, parser);
+ extractor = new MessageExtractor(htmlParser, parser, ['i18n-tag'], {'i18n-el': ['trans']});
});
it('should extract from elements with the i18n attr', () => {
@@ -205,6 +205,30 @@ export function main() {
]);
});
+ describe('implicit translation', () => {
+ it('should extract from elements', () => {
+ let res = extractor.extract("message", "someurl");
+ expect(res.messages).toEqual([new Message("message", null, null)]);
+ });
+
+ it('should extract meaning and description from elements when present', () => {
+ let res =
+ extractor.extract("message", "someurl");
+ expect(res.messages).toEqual([new Message("message", "meaning", "description")]);
+ });
+
+ it('should extract from attributes', () => {
+ let res = extractor.extract(``, "someurl");
+ expect(res.messages).toEqual([new Message("message", null, null)]);
+ });
+
+ it('should extract meaning and description from attributes when present', () => {
+ let res = extractor.extract(``,
+ "someurl");
+ expect(res.messages).toEqual([new Message("message", "meaning", "desc")]);
+ });
+ });
+
describe("errors", () => {
it('should error on i18n attributes without matching "real" attributes', () => {
let res = extractor.extract(`