2016-07-21 16:56:58 -04:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 15:08:49 -04:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2016-07-21 16:56:58 -04:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2017-03-02 12:37:01 -05:00
|
|
|
import {escapeRegExp} from '@angular/compiler/src/util';
|
2016-11-02 20:40:15 -04:00
|
|
|
import {serializeNodes} from '../../../src/i18n/digest';
|
2017-01-25 18:55:17 -05:00
|
|
|
import * as i18n from '../../../src/i18n/i18n_ast';
|
2016-08-10 00:05:04 -04:00
|
|
|
import {Xtb} from '../../../src/i18n/serializers/xtb';
|
2016-07-21 16:56:58 -04:00
|
|
|
|
2017-01-25 18:55:17 -05:00
|
|
|
|
2017-12-16 17:42:55 -05:00
|
|
|
{
|
2016-07-21 16:56:58 -04:00
|
|
|
describe('XTB serializer', () => {
|
2016-11-02 20:40:15 -04:00
|
|
|
const serializer = new Xtb();
|
2016-07-21 16:56:58 -04:00
|
|
|
|
2016-11-02 20:40:15 -04:00
|
|
|
function loadAsMap(xtb: string): {[id: string]: string} {
|
2017-02-03 17:29:28 -05:00
|
|
|
const {i18nNodesByMsgId} = serializer.load(xtb, 'url');
|
2016-11-02 20:40:15 -04:00
|
|
|
const msgMap: {[id: string]: string} = {};
|
|
|
|
Object.keys(i18nNodesByMsgId).forEach(id => {
|
|
|
|
msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join('');
|
|
|
|
});
|
|
|
|
return msgMap;
|
2016-07-21 16:56:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
describe('load', () => {
|
2016-07-29 16:07:01 -04:00
|
|
|
it('should load XTB files with a doctype', () => {
|
|
|
|
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<!DOCTYPE translationbundle [<!ELEMENT translationbundle (translation)*>
|
|
|
|
<!ATTLIST translationbundle lang CDATA #REQUIRED>
|
|
|
|
|
|
|
|
<!ELEMENT translation (#PCDATA|ph)*>
|
|
|
|
<!ATTLIST translation id CDATA #REQUIRED>
|
|
|
|
|
|
|
|
<!ELEMENT ph EMPTY>
|
|
|
|
<!ATTLIST ph name CDATA #REQUIRED>
|
|
|
|
]>
|
|
|
|
<translationbundle>
|
2016-10-28 22:53:42 -04:00
|
|
|
<translation id="8841459487341224498">rab</translation>
|
2016-07-29 16:07:01 -04:00
|
|
|
</translationbundle>`;
|
|
|
|
|
2016-11-02 20:40:15 -04:00
|
|
|
expect(loadAsMap(XTB)).toEqual({'8841459487341224498': 'rab'});
|
2016-07-29 16:07:01 -04:00
|
|
|
});
|
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
it('should load XTB files without placeholders', () => {
|
2016-07-29 16:07:01 -04:00
|
|
|
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
2016-07-21 16:56:58 -04:00
|
|
|
<translationbundle>
|
2016-10-28 22:53:42 -04:00
|
|
|
<translation id="8841459487341224498">rab</translation>
|
2016-07-21 16:56:58 -04:00
|
|
|
</translationbundle>`;
|
|
|
|
|
2016-11-02 20:40:15 -04:00
|
|
|
expect(loadAsMap(XTB)).toEqual({'8841459487341224498': 'rab'});
|
2016-07-21 16:56:58 -04:00
|
|
|
});
|
|
|
|
|
2017-02-03 17:29:28 -05:00
|
|
|
it('should return the target locale', () => {
|
|
|
|
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<translationbundle lang='fr'>
|
|
|
|
<translation id="8841459487341224498">rab</translation>
|
|
|
|
</translationbundle>`;
|
|
|
|
|
|
|
|
expect(serializer.load(XTB, 'url').locale).toEqual('fr');
|
|
|
|
});
|
2016-10-28 22:53:42 -04:00
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
it('should load XTB files with placeholders', () => {
|
2016-07-29 16:07:01 -04:00
|
|
|
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
2016-07-21 16:56:58 -04:00
|
|
|
<translationbundle>
|
2016-10-28 22:53:42 -04:00
|
|
|
<translation id="8877975308926375834"><ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/></translation>
|
2016-08-10 00:05:04 -04:00
|
|
|
</translationbundle>`;
|
|
|
|
|
2016-11-02 20:40:15 -04:00
|
|
|
expect(loadAsMap(XTB)).toEqual({
|
|
|
|
'8877975308926375834': '<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>'
|
|
|
|
});
|
2016-08-10 00:05:04 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should replace ICU placeholders with their translations', () => {
|
2016-08-13 00:26:03 -04:00
|
|
|
const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
|
2016-08-10 00:05:04 -04:00
|
|
|
<translationbundle>
|
2016-11-02 20:40:15 -04:00
|
|
|
<translation id="7717087045075616176">*<ph name="ICU"/>*</translation>
|
|
|
|
<translation id="5115002811911870583">{VAR_PLURAL, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation>
|
2016-07-21 16:56:58 -04:00
|
|
|
</translationbundle>`;
|
|
|
|
|
2016-11-02 20:40:15 -04:00
|
|
|
expect(loadAsMap(XTB)).toEqual({
|
|
|
|
'7717087045075616176': `*<ph name="ICU"/>*`,
|
|
|
|
'5115002811911870583':
|
|
|
|
`{VAR_PLURAL, plural, =1 {[<ph name="START_PARAGRAPH"/>, rab, <ph name="CLOSE_PARAGRAPH"/>]}}`,
|
2016-08-10 00:05:04 -04:00
|
|
|
});
|
2016-07-21 16:56:58 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should load complex XTB files', () => {
|
2016-08-13 00:26:03 -04:00
|
|
|
const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
|
2016-07-21 16:56:58 -04:00
|
|
|
<translationbundle>
|
2016-10-28 22:53:42 -04:00
|
|
|
<translation id="8281795707202401639"><ph name="INTERPOLATION"/><ph name="START_BOLD_TEXT"/>rab<ph name="CLOSE_BOLD_TEXT"/> oof</translation>
|
2016-11-02 20:40:15 -04:00
|
|
|
<translation id="5115002811911870583">{VAR_PLURAL, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation>
|
2016-10-28 22:53:42 -04:00
|
|
|
<translation id="130772889486467622">oof</translation>
|
2016-11-02 20:40:15 -04:00
|
|
|
<translation id="4739316421648347533">{VAR_PLURAL, plural, =1 {{VAR_GENDER, gender, male {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}} }}</translation>
|
2016-07-21 16:56:58 -04:00
|
|
|
</translationbundle>`;
|
|
|
|
|
2016-11-02 20:40:15 -04:00
|
|
|
expect(loadAsMap(XTB)).toEqual({
|
|
|
|
'8281795707202401639':
|
|
|
|
`<ph name="INTERPOLATION"/><ph name="START_BOLD_TEXT"/>rab<ph name="CLOSE_BOLD_TEXT"/> oof`,
|
|
|
|
'5115002811911870583':
|
|
|
|
`{VAR_PLURAL, plural, =1 {[<ph name="START_PARAGRAPH"/>, rab, <ph name="CLOSE_PARAGRAPH"/>]}}`,
|
2016-10-28 22:53:42 -04:00
|
|
|
'130772889486467622': `oof`,
|
2016-11-02 20:40:15 -04:00
|
|
|
'4739316421648347533':
|
|
|
|
`{VAR_PLURAL, plural, =1 {[{VAR_GENDER, gender, male {[<ph name="START_PARAGRAPH"/>, rab, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}}`,
|
2016-07-21 16:56:58 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('errors', () => {
|
2017-01-25 18:55:17 -05:00
|
|
|
it('should be able to parse non-angular xtb files without error', () => {
|
|
|
|
const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
|
|
|
|
<translationbundle>
|
|
|
|
<translation id="angular">is great</translation>
|
2017-01-31 12:47:10 -05:00
|
|
|
<translation id="non angular">is <invalid>less</invalid> {count, plural, =0 {{GREAT}}}</translation>
|
2017-01-25 18:55:17 -05:00
|
|
|
</translationbundle>`;
|
|
|
|
|
|
|
|
// Invalid messages should not cause the parser to throw
|
2020-04-08 13:14:18 -04:00
|
|
|
let i18nNodesByMsgId: {[id: string]: i18n.Node[]} = undefined!;
|
2017-02-03 17:29:28 -05:00
|
|
|
expect(() => {
|
|
|
|
i18nNodesByMsgId = serializer.load(XTB, 'url').i18nNodesByMsgId;
|
|
|
|
}).not.toThrow();
|
2017-01-25 18:55:17 -05:00
|
|
|
|
|
|
|
expect(Object.keys(i18nNodesByMsgId).length).toEqual(2);
|
|
|
|
expect(serializeNodes(i18nNodesByMsgId['angular']).join('')).toEqual('is great');
|
|
|
|
// Messages that contain unsupported feature should throw on access
|
|
|
|
expect(() => {
|
|
|
|
const read = i18nNodesByMsgId['non angular'];
|
|
|
|
}).toThrowError(/xtb parse errors/);
|
|
|
|
});
|
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
it('should throw on nested <translationbundle>', () => {
|
|
|
|
const XTB =
|
|
|
|
'<translationbundle><translationbundle></translationbundle></translationbundle>';
|
|
|
|
|
|
|
|
expect(() => {
|
2016-11-02 20:40:15 -04:00
|
|
|
loadAsMap(XTB);
|
2016-07-21 16:56:58 -04:00
|
|
|
}).toThrowError(/<translationbundle> elements can not be nested/);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw when a <translation> has no id attribute', () => {
|
2016-07-29 16:07:01 -04:00
|
|
|
const XTB = `<translationbundle>
|
2016-07-21 16:56:58 -04:00
|
|
|
<translation></translation>
|
|
|
|
</translationbundle>`;
|
|
|
|
|
2020-04-08 13:14:18 -04:00
|
|
|
expect(() => {
|
|
|
|
loadAsMap(XTB);
|
|
|
|
}).toThrowError(/<translation> misses the "id" attribute/);
|
2016-07-21 16:56:58 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw when a placeholder has no name attribute', () => {
|
2016-07-29 16:07:01 -04:00
|
|
|
const XTB = `<translationbundle>
|
2016-10-28 22:53:42 -04:00
|
|
|
<translation id="1186013544048295927"><ph /></translation>
|
2016-07-21 16:56:58 -04:00
|
|
|
</translationbundle>`;
|
|
|
|
|
2020-04-08 13:14:18 -04:00
|
|
|
expect(() => {
|
|
|
|
loadAsMap(XTB);
|
|
|
|
}).toThrowError(/<ph> misses the "name" attribute/);
|
2016-07-21 16:56:58 -04:00
|
|
|
});
|
|
|
|
|
2016-11-02 20:40:15 -04:00
|
|
|
it('should throw on unknown xtb tags', () => {
|
|
|
|
const XTB = `<what></what>`;
|
2016-07-21 16:56:58 -04:00
|
|
|
|
|
|
|
expect(() => {
|
2016-11-02 20:40:15 -04:00
|
|
|
loadAsMap(XTB);
|
|
|
|
}).toThrowError(new RegExp(escapeRegExp(`Unexpected tag ("[ERROR ->]<what></what>")`)));
|
2016-07-21 16:56:58 -04:00
|
|
|
});
|
2016-08-10 00:05:04 -04:00
|
|
|
|
2016-11-02 20:40:15 -04:00
|
|
|
it('should throw on unknown message tags', () => {
|
|
|
|
const XTB = `<translationbundle>
|
|
|
|
<translation id="1186013544048295927"><b>msg should contain only ph tags</b></translation>
|
2016-07-21 16:56:58 -04:00
|
|
|
</translationbundle>`;
|
|
|
|
|
2020-04-08 13:14:18 -04:00
|
|
|
expect(() => {
|
|
|
|
loadAsMap(XTB);
|
|
|
|
})
|
2016-11-02 20:40:15 -04:00
|
|
|
.toThrowError(
|
|
|
|
new RegExp(escapeRegExp(`[ERROR ->]<b>msg should contain only ph tags</b>`)));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw on duplicate message id', () => {
|
|
|
|
const XTB = `<translationbundle>
|
|
|
|
<translation id="1186013544048295927">msg1</translation>
|
|
|
|
<translation id="1186013544048295927">msg2</translation>
|
|
|
|
</translationbundle>`;
|
2016-07-21 16:56:58 -04:00
|
|
|
|
2016-11-02 20:40:15 -04:00
|
|
|
expect(() => {
|
|
|
|
loadAsMap(XTB);
|
|
|
|
}).toThrowError(/Duplicated translations for msg 1186013544048295927/);
|
|
|
|
});
|
2016-07-21 16:56:58 -04:00
|
|
|
|
2020-04-08 13:14:18 -04:00
|
|
|
it('should throw when trying to save an xtb file', () => {
|
|
|
|
expect(() => {
|
|
|
|
serializer.write([], null);
|
|
|
|
}).toThrowError(/Unsupported/);
|
|
|
|
});
|
2016-07-21 16:56:58 -04:00
|
|
|
});
|
|
|
|
});
|
2017-02-16 11:03:18 -05:00
|
|
|
}
|