diff --git a/modules/@angular/compiler/src/i18n/digest.ts b/modules/@angular/compiler/src/i18n/digest.ts
new file mode 100644
index 0000000000..798e192dfc
--- /dev/null
+++ b/modules/@angular/compiler/src/i18n/digest.ts
@@ -0,0 +1,75 @@
+/**
+ * @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
+ */
+
+import * as i18n from './i18n_ast';
+
+export function digestMessage(message: i18n.Message): string {
+ return strHash(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
+}
+
+/**
+ * String hash function similar to java.lang.String.hashCode().
+ * The hash code for a string is computed as
+ * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
+ * where s[i] is the ith character of the string and n is the length of
+ * the string. We mod the result to make it between 0 (inclusive) and 2^32 (exclusive).
+ *
+ * Based on goog.string.hashCode from the Google Closure library
+ * https://github.com/google/closure-library/
+ *
+ * @internal
+ */
+// TODO(vicb): better algo (less collisions) ?
+export function strHash(str: string): string {
+ let result: number = 0;
+ for (var i = 0; i < str.length; ++i) {
+ // Normalize to 4 byte range, 0 ... 2^32.
+ result = (31 * result + str.charCodeAt(i)) >>> 0;
+ }
+ return result.toString(16);
+}
+
+/**
+ * Serialize the i18n ast to something xml-like in order to generate an UID.
+ *
+ * The visitor is also used in the i18n parser tests
+ *
+ * @internal
+ */
+class _SerializerVisitor implements i18n.Visitor {
+ visitText(text: i18n.Text, context: any): any { return text.value; }
+
+ visitContainer(container: i18n.Container, context: any): any {
+ return `[${container.children.map(child => child.visit(this)).join(', ')}]`;
+ }
+
+ visitIcu(icu: i18n.Icu, context: any): any {
+ let strCases = Object.keys(icu.cases).map((k: string) => `${k} {${icu.cases[k].visit(this)}}`);
+ return `{${icu.expression}, ${icu.type}, ${strCases.join(', ')}}`;
+ }
+
+ visitTagPlaceholder(ph: i18n.TagPlaceholder, context: any): any {
+ return ph.isVoid ?
+ `` :
+ `${ph.children.map(child => child.visit(this)).join(', ')}`;
+ }
+
+ visitPlaceholder(ph: i18n.Placeholder, context: any): any {
+ return `${ph.value}`;
+ }
+
+ visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any {
+ return `${ph.value.visit(this)}`;
+ }
+}
+
+const serializerVisitor = new _SerializerVisitor();
+
+export function serializeNodes(nodes: i18n.Node[]): string[] {
+ return nodes.map(a => a.visit(serializerVisitor, null));
+}
diff --git a/modules/@angular/compiler/src/i18n/extractor_merger.ts b/modules/@angular/compiler/src/i18n/extractor_merger.ts
index 9f367e143e..45900b276b 100644
--- a/modules/@angular/compiler/src/i18n/extractor_merger.ts
+++ b/modules/@angular/compiler/src/i18n/extractor_merger.ts
@@ -9,9 +9,9 @@
import * as html from '../ml_parser/ast';
import {InterpolationConfig} from '../ml_parser/interpolation_config';
+import {digestMessage} from './digest';
import * as i18n from './i18n_ast';
-import * as i18nParser from './i18n_parser';
-import * as msgBundle from './message_bundle';
+import {createI18nMessageFactory} from './i18n_parser';
import {I18nError} from './parse_util';
import {TranslationBundle} from './translation_bundle';
@@ -299,7 +299,7 @@ class _Visitor implements html.Visitor {
this._msgCountAtSectionStart = void 0;
this._errors = [];
this._messages = [];
- this._createI18nMessage = i18nParser.createI18nMessageFactory(interpolationConfig);
+ this._createI18nMessage = createI18nMessageFactory(interpolationConfig);
}
// looks for translatable attributes
@@ -338,7 +338,7 @@ class _Visitor implements html.Visitor {
// translate the given message given the `TranslationBundle`
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
if (message && this._mode === _VisitorMode.Merge) {
- const id = msgBundle.digestMessage(message);
+ const id = digestMessage(message);
const nodes = this._translations.get(id);
if (nodes) {
@@ -374,16 +374,20 @@ class _Visitor implements html.Visitor {
if (i18nAttributeMeanings.hasOwnProperty(attr.name)) {
const meaning = i18nAttributeMeanings[attr.name];
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
- const id = msgBundle.digestMessage(message);
+ const id = digestMessage(message);
const nodes = this._translations.get(id);
- if (!nodes) {
+ if (nodes) {
+ if (nodes[0] instanceof html.Text) {
+ const value = (nodes[0] as html.Text).value;
+ translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
+ } else {
+ this._reportError(
+ el, `Unexpected translation for attribute "${attr.name}" (id="${id}")`);
+ }
+ } else {
this._reportError(
el, `Translation unavailable for attribute "${attr.name}" (id="${id}")`);
}
- if (nodes[0] instanceof html.Text) {
- const value = (nodes[0] as html.Text).value;
- translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
- }
} else {
translatedAttributes.push(attr);
}
diff --git a/modules/@angular/compiler/src/i18n/message_bundle.ts b/modules/@angular/compiler/src/i18n/message_bundle.ts
index 3cd1d09d10..e3b7dbb0d1 100644
--- a/modules/@angular/compiler/src/i18n/message_bundle.ts
+++ b/modules/@angular/compiler/src/i18n/message_bundle.ts
@@ -10,15 +10,17 @@ import {HtmlParser} from '../ml_parser/html_parser';
import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {ParseError} from '../parse_util';
-import * as extractor from './extractor_merger';
-import * as i18n from './i18n_ast';
+import {digestMessage} from './digest';
+import {extractMessages} from './extractor_merger';
+import {Message} from './i18n_ast';
import {Serializer} from './serializers/serializer';
+
/**
* A container for message extracted from the templates.
*/
export class MessageBundle {
- private _messageMap: {[id: string]: i18n.Message} = {};
+ private _messageMap: {[id: string]: Message} = {};
constructor(
private _htmlParser: HtmlParser, private _implicitTags: string[],
@@ -32,7 +34,7 @@ export class MessageBundle {
return htmlParserResult.errors;
}
- const i18nParserResult = extractor.extractMessages(
+ const i18nParserResult = extractMessages(
htmlParserResult.rootNodes, interpolationConfig, this._implicitTags, this._implicitAttrs);
if (i18nParserResult.errors.length) {
@@ -45,69 +47,3 @@ export class MessageBundle {
write(serializer: Serializer): string { return serializer.write(this._messageMap); }
}
-
-export function digestMessage(message: i18n.Message): string {
- return strHash(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
-}
-
-/**
- * String hash function similar to java.lang.String.hashCode().
- * The hash code for a string is computed as
- * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
- * where s[i] is the ith character of the string and n is the length of
- * the string. We mod the result to make it between 0 (inclusive) and 2^32 (exclusive).
- *
- * Based on goog.string.hashCode from the Google Closure library
- * https://github.com/google/closure-library/
- *
- * @internal
- */
-// TODO(vicb): better algo (less collisions) ?
-export function strHash(str: string): string {
- let result: number = 0;
- for (var i = 0; i < str.length; ++i) {
- // Normalize to 4 byte range, 0 ... 2^32.
- result = (31 * result + str.charCodeAt(i)) >>> 0;
- }
- return result.toString(16);
-}
-
-/**
- * Serialize the i18n ast to something xml-like in order to generate an UID.
- *
- * The visitor is also used in the i18n parser tests
- *
- * @internal
- */
-class _SerializerVisitor implements i18n.Visitor {
- visitText(text: i18n.Text, context: any): any { return text.value; }
-
- visitContainer(container: i18n.Container, context: any): any {
- return `[${container.children.map(child => child.visit(this)).join(', ')}]`;
- }
-
- visitIcu(icu: i18n.Icu, context: any): any {
- let strCases = Object.keys(icu.cases).map((k: string) => `${k} {${icu.cases[k].visit(this)}}`);
- return `{${icu.expression}, ${icu.type}, ${strCases.join(', ')}}`;
- }
-
- visitTagPlaceholder(ph: i18n.TagPlaceholder, context: any): any {
- return ph.isVoid ?
- `` :
- `${ph.children.map(child => child.visit(this)).join(', ')}`;
- }
-
- visitPlaceholder(ph: i18n.Placeholder, context: any): any {
- return `${ph.value}`;
- }
-
- visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any {
- return `${ph.value.visit(this)}`;
- }
-}
-
-const serializerVisitor = new _SerializerVisitor();
-
-export function serializeNodes(nodes: i18n.Node[]): string[] {
- return nodes.map(a => a.visit(serializerVisitor, null));
-}
diff --git a/modules/@angular/compiler/test/i18n/digest_spec.ts b/modules/@angular/compiler/test/i18n/digest_spec.ts
new file mode 100644
index 0000000000..e84477a8ff
--- /dev/null
+++ b/modules/@angular/compiler/test/i18n/digest_spec.ts
@@ -0,0 +1,38 @@
+/**
+ * @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
+ */
+
+import {beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
+import {strHash} from '../../src/i18n/digest';
+
+export function main(): void {
+ describe('strHash', () => {
+ it('should return a hash value', () => {
+ // https://github.com/google/closure-library/blob/1fb19a857b96b74e6523f3e9d33080baf25be046/closure/goog/string/string_test.js#L1115
+ expectHash('', 0);
+ expectHash('foo', 101574);
+ expectHash('\uAAAAfoo', 1301670364);
+ expectHash('a', 92567585, 5);
+ expectHash('a', 2869595232, 6);
+ expectHash('a', 3058106369, 7);
+ expectHash('a', 312017024, 8);
+ expectHash('a', 2929737728, 1024);
+ });
+ });
+}
+
+function expectHash(text: string, decimal: number, repeat: number = 1) {
+ let acc = text;
+ for (let i = 1; i < repeat; i++) {
+ acc += text;
+ }
+
+ const hash = strHash(acc);
+ expect(typeof(hash)).toEqual('string');
+ expect(hash.length > 0).toBe(true);
+ expect(parseInt(hash, 16)).toEqual(decimal);
+}
\ No newline at end of file
diff --git a/modules/@angular/compiler/test/i18n/extractor_merger_spec.ts b/modules/@angular/compiler/test/i18n/extractor_merger_spec.ts
index 5c61d04d8a..c46b5a9add 100644
--- a/modules/@angular/compiler/test/i18n/extractor_merger_spec.ts
+++ b/modules/@angular/compiler/test/i18n/extractor_merger_spec.ts
@@ -8,9 +8,9 @@
import {beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
+import {digestMessage, serializeNodes as serializeI18nNodes} from '../../src/i18n/digest';
import {extractMessages, mergeTranslations} from '../../src/i18n/extractor_merger';
import * as i18n from '../../src/i18n/i18n_ast';
-import {digestMessage, serializeNodes as serializeI18nNodes} from '../../src/i18n/message_bundle';
import {TranslationBundle} from '../../src/i18n/translation_bundle';
import * as html from '../../src/ml_parser/ast';
import {HtmlParser} from '../../src/ml_parser/html_parser';
diff --git a/modules/@angular/compiler/test/i18n/i18n_parser_spec.ts b/modules/@angular/compiler/test/i18n/i18n_parser_spec.ts
index 8160b175fc..fbb3e941a9 100644
--- a/modules/@angular/compiler/test/i18n/i18n_parser_spec.ts
+++ b/modules/@angular/compiler/test/i18n/i18n_parser_spec.ts
@@ -10,7 +10,7 @@ import {extractMessages} from '@angular/compiler/src/i18n/extractor_merger';
import {Message} from '@angular/compiler/src/i18n/i18n_ast';
import {ddescribe, describe, expect, iit, it} from '@angular/core/testing/testing_internal';
-import {serializeNodes} from '../../src/i18n/message_bundle';
+import {serializeNodes} from '../../src/i18n/digest';
import {HtmlParser} from '../../src/ml_parser/html_parser';
import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_config';
diff --git a/modules/@angular/compiler/test/i18n/message_bundle_spec.ts b/modules/@angular/compiler/test/i18n/message_bundle_spec.ts
index aeae83ce48..7204ebec04 100644
--- a/modules/@angular/compiler/test/i18n/message_bundle_spec.ts
+++ b/modules/@angular/compiler/test/i18n/message_bundle_spec.ts
@@ -10,7 +10,8 @@ import * as i18n from '@angular/compiler/src/i18n/i18n_ast';
import {Serializer} from '@angular/compiler/src/i18n/serializers/serializer';
import {beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
-import {MessageBundle, serializeNodes, strHash} from '../../src/i18n/message_bundle';
+import {serializeNodes} from '../../src/i18n/digest';
+import {MessageBundle} from '../../src/i18n/message_bundle';
import {HtmlParser} from '../../src/ml_parser/html_parser';
import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_config';
@@ -39,20 +40,6 @@ export function main(): void {
]);
});
});
-
- describe('strHash', () => {
- it('should return a hash value', () => {
- // https://github.com/google/closure-library/blob/1fb19a857b96b74e6523f3e9d33080baf25be046/closure/goog/string/string_test.js#L1115
- expectHash('', 0);
- expectHash('foo', 101574);
- expectHash('\uAAAAfoo', 1301670364);
- expectHash('a', 92567585, 5);
- expectHash('a', 2869595232, 6);
- expectHash('a', 3058106369, 7);
- expectHash('a', 312017024, 8);
- expectHash('a', 2929737728, 1024);
- });
- });
});
}
@@ -68,16 +55,4 @@ class _TestSerializer implements Serializer {
function humanizeMessages(catalog: MessageBundle): string[] {
return catalog.write(new _TestSerializer()).split('//');
-}
-
-function expectHash(text: string, decimal: number, repeat: number = 1) {
- let acc = text;
- for (let i = 1; i < repeat; i++) {
- acc += text;
- }
-
- const hash = strHash(acc);
- expect(typeof(hash)).toEqual('string');
- expect(hash.length > 0).toBe(true);
- expect(parseInt(hash, 16)).toEqual(decimal);
}
\ No newline at end of file