diff --git a/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts
index fc3fd6e61a..d46ad0d994 100644
--- a/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts
+++ b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts
@@ -34,9 +34,9 @@ const EXPECTED_XMB = `
]>
- other-3rdP-component
- translate me
- Welcome
+ other-3rdP-component
+ translate me
+ Welcome
`;
diff --git a/modules/@angular/compiler/src/i18n/digest.ts b/modules/@angular/compiler/src/i18n/digest.ts
index 895e7a167a..d00bf80354 100644
--- a/modules/@angular/compiler/src/i18n/digest.ts
+++ b/modules/@angular/compiler/src/i18n/digest.ts
@@ -15,7 +15,7 @@ export function digest(message: i18n.Message): string {
export function decimalDigest(message: i18n.Message): string {
const visitor = new _SerializerIgnoreIcuExpVisitor();
const parts = message.nodes.map(a => a.visit(visitor, null));
- return fingerprint(parts.join('') + `[${message.meaning}]`);
+ return computeMsgId(parts.join(''), message.meaning);
}
/**
@@ -138,7 +138,7 @@ function fk(index: number, b: number, c: number, d: number): [number, number] {
* based on:
* https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java
*/
-export function fingerprint(str: string): string {
+export function fingerprint(str: string): [number, number] {
const utf8 = utf8Encode(str);
let [hi, lo] = [hash32(utf8, 0), hash32(utf8, 102072)];
@@ -148,9 +148,18 @@ export function fingerprint(str: string): string {
lo = lo ^ -0x6b5f56d8;
}
- hi = hi & 0x7fffffff;
+ return [hi, lo];
+}
- return byteStringToDecString(words32ToByteString([hi, lo]));
+export function computeMsgId(msg: string, meaning: string): string {
+ let [hi, lo] = fingerprint(msg);
+
+ if (meaning) {
+ const [him, lom] = fingerprint(meaning);
+ [hi, lo] = add64(rol64([hi, lo], 1), [him, lom]);
+ }
+
+ return byteStringToDecString(words32ToByteString([hi & 0x7fffffff, lo]));
}
function hash32(str: string, c: number): number {
@@ -239,9 +248,19 @@ function decodeSurrogatePairs(str: string, index: number): number {
}
function add32(a: number, b: number): number {
+ return add32to64(a, b)[1];
+}
+
+function add32to64(a: number, b: number): [number, number] {
const low = (a & 0xffff) + (b & 0xffff);
- const high = (a >> 16) + (b >> 16) + (low >> 16);
- return (high << 16) | (low & 0xffff);
+ const high = (a >>> 16) + (b >>> 16) + (low >>> 16);
+ return [high >>> 16, (high << 16) | (low & 0xffff)];
+}
+
+function add64([ah, al]: [number, number], [bh, bl]: [number, number]): [number, number] {
+ const [carry, l] = add32to64(al, bl);
+ const h = add32(add32(ah, bh), carry);
+ return [h, l];
}
function sub32(a: number, b: number): number {
@@ -255,6 +274,13 @@ function rol32(a: number, count: number): number {
return (a << count) | (a >>> (32 - count));
}
+// Rotate a 64b number left `count` position
+function rol64([hi, lo]: [number, number], count: number): [number, number] {
+ const h = (hi << count) | (lo >>> (32 - count));
+ const l = (lo << count) | (hi >>> (32 - count));
+ return [h, l];
+}
+
function stringToWords32(str: string, endian: Endian): number[] {
const words32 = Array((str.length + 3) >>> 2);
@@ -317,6 +343,7 @@ function byteStringToDecString(str: string): string {
return decimal.split('').reverse().join('');
}
+// x and y decimal, lowest significant digit first
function addBigInt(x: string, y: string): string {
let sum = '';
const len = Math.max(x.length, y.length);
diff --git a/modules/@angular/compiler/test/i18n/digest_spec.ts b/modules/@angular/compiler/test/i18n/digest_spec.ts
index 3fdc817e59..07b7bbe390 100644
--- a/modules/@angular/compiler/test/i18n/digest_spec.ts
+++ b/modules/@angular/compiler/test/i18n/digest_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {fingerprint, sha1} from '../../src/i18n/digest';
+import {computeMsgId, sha1} from '../../src/i18n/digest';
export function main(): void {
describe('digest', () => {
@@ -56,37 +56,49 @@ export function main(): void {
});
describe('decimal fingerprint', () => {
- const fixtures: {[msg: string]: string} = {
- ' Spaced Out ': '3976450302996657536',
- 'Last Name': '4407559560004943843',
- 'First Name': '6028371114637047813',
- 'View': '2509141182388535183',
- 'START_BOLDNUMEND_BOLD of START_BOLDmillionsEND_BOLD': '29997634073898638',
- 'The customer\'s credit card was authorized for AMOUNT and passed all risk checks.':
- '6836487644149622036',
- 'Hello world!': '3022994926184248873',
- 'Jalape\u00f1o': '8054366208386598941',
- 'The set of SET_NAME is {XXX, ...}.': '135956960462609535',
- 'NAME took a trip to DESTINATION.': '768490705511913603',
- 'by AUTHOR (YEAR)': '7036633296476174078',
- '': '4416290763660062288',
- };
+ it('should work on well known inputs w/o meaning', () => {
+ const fixtures: {[msg: string]: string} = {
+ ' Spaced Out ': '3976450302996657536',
+ 'Last Name': '4407559560004943843',
+ 'First Name': '6028371114637047813',
+ 'View': '2509141182388535183',
+ 'START_BOLDNUMEND_BOLD of START_BOLDmillionsEND_BOLD': '29997634073898638',
+ 'The customer\'s credit card was authorized for AMOUNT and passed all risk checks.':
+ '6836487644149622036',
+ 'Hello world!': '3022994926184248873',
+ 'Jalape\u00f1o': '8054366208386598941',
+ 'The set of SET_NAME is {XXX, ...}.': '135956960462609535',
+ 'NAME took a trip to DESTINATION.': '768490705511913603',
+ 'by AUTHOR (YEAR)': '7036633296476174078',
+ '': '4416290763660062288',
+ };
- it('should work on well known inputs', () => {
- Object.keys(fixtures).forEach(msg => { expect(fingerprint(msg)).toEqual(fixtures[msg]); });
+ Object.keys(fixtures).forEach(
+ msg => { expect(computeMsgId(msg, '')).toEqual(fixtures[msg]); });
+ });
+
+ it('should work on well known inputs with meaning', () => {
+ const fixtures: {[msg: string]: [string, string]} = {
+ '7790835225175622807': ['Last Name', 'Gmail UI'],
+ '1809086297585054940': ['First Name', 'Gmail UI'],
+ '3993998469942805487': ['View', 'Gmail UI'],
+ };
+
+ Object.keys(fixtures).forEach(
+ id => { expect(computeMsgId(fixtures[id][0], fixtures[id][1])).toEqual(id); });
});
it('should support arbitrary string size', () => {
const prefix = `你好,世界`;
- let result = fingerprint(prefix);
+ let result = computeMsgId(prefix, '');
for (let size = prefix.length; size < 5000; size += 101) {
- result = prefix + fingerprint(result);
+ result = prefix + computeMsgId(result, '');
while (result.length < size) {
result += result;
}
result = result.slice(-size);
}
- expect(fingerprint(result)).toEqual('2122606631351252558');
+ expect(computeMsgId(result, '')).toEqual('2122606631351252558');
});
});
diff --git a/modules/@angular/compiler/test/i18n/integration_spec.ts b/modules/@angular/compiler/test/i18n/integration_spec.ts
index 27381a0efc..ade32405dd 100644
--- a/modules/@angular/compiler/test/i18n/integration_spec.ts
+++ b/modules/@angular/compiler/test/i18n/integration_spec.ts
@@ -163,25 +163,25 @@ class FrLocalization extends NgLocalization {
const XTB = `
- attributs i18n sur les balises
- imbriqué
- imbriqué
- avec des espaces réservés
- sur des balises non traductibles
- sur des balises traductibles
- {VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {beaucoup}}
-
- {VAR_SELECT, select, m {homme} f {femme}}
-
- sexe =
-
- dans une section traductible
-
+ attributs i18n sur les balises
+ imbriqué
+ imbriqué
+ avec des espaces réservés
+ sur des balises non traductibles
+ sur des balises traductibles
+ {VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {beaucoup}}
+
+ {VAR_SELECT, select, m {homme} f {femme}}
+
+ sexe =
+
+ dans une section traductible
+
Balises dans les commentaires html
- ca devrait marcher
+ ca devrait marcher
`;
// unused, for reference only
@@ -189,26 +189,26 @@ const XTB = `
// `fit('extract xmb', () => { console.log(toXmb(HTML)); });`
const XMB = `
- i18n attribute on tags
- nested
- nested
- <i>with placeholders</i>
- on not translatable node
- on translatable node
- {VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} }
-
+ i18n attribute on tags
+ nested
+ nested
+ <i>with placeholders</i>
+ on not translatable node
+ on translatable node
+ {VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} }
+
- {VAR_SELECT, select, m {male} f {female} }
-
- sex =
-
- in a translatable section
-
+ {VAR_SELECT, select, m {male} f {female} }
+
+ sex =
+
+ in a translatable section
+
<h1>Markers in html comments</h1>
<div></div>
<div></div>
- it <b>should</b> work
+ it <b>should</b> work
`;
diff --git a/modules/@angular/compiler/test/i18n/serializers/xmb_spec.ts b/modules/@angular/compiler/test/i18n/serializers/xmb_spec.ts
index 0b00577c0a..319370102e 100644
--- a/modules/@angular/compiler/test/i18n/serializers/xmb_spec.ts
+++ b/modules/@angular/compiler/test/i18n/serializers/xmb_spec.ts
@@ -43,10 +43,10 @@ export function main(): void {
]>
- translatable element <b>with placeholders</b>
- {VAR_PLURAL, plural, =0 {<p>test</p>} }
- foo
- {VAR_PLURAL, plural, =0 {{VAR_GENDER, gender, other {<p>deeply nested</p>} } } }
+ translatable element <b>with placeholders</b>
+ {VAR_PLURAL, plural, =0 {<p>test</p>} }
+ foo
+ {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<p>deeply nested</p>} } } }
`;