test(localize): tidy up translation parser tests (#36745)
There was a lot of duplication and multiline backtick strings that made it hard to maintain. PR Close #36745
This commit is contained in:
parent
3962908d36
commit
519f2baff0
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import {ɵmakeTemplateObject} from '@angular/localize';
|
import {ɵmakeTemplateObject} from '@angular/localize';
|
||||||
import {SimpleJsonTranslationParser} from '../../../../src/translate/translation_files/translation_parsers/simple_json_translation_parser';
|
import {SimpleJsonTranslationParser} from '../../../../src/translate/translation_files/translation_parsers/simple_json_translation_parser';
|
||||||
|
import {ParsedTranslationBundle} from '../../../../src/translate/translation_files/translation_parsers/translation_parser';
|
||||||
|
|
||||||
describe('SimpleJsonTranslationParser', () => {
|
describe('SimpleJsonTranslationParser', () => {
|
||||||
describe('canParse()', () => {
|
describe('canParse()', () => {
|
||||||
|
@ -22,27 +23,41 @@ describe('SimpleJsonTranslationParser', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parse()', () => {
|
for (const withHint of [true, false]) {
|
||||||
it('should extract the locale from the JSON contents', () => {
|
describe(`parse() [${withHint ? 'with' : 'without'} hint]`, () => {
|
||||||
const parser = new SimpleJsonTranslationParser();
|
const doParse: (fileName: string, contents: string) => ParsedTranslationBundle =
|
||||||
const result = parser.parse('/some/file.json', '{"locale": "en", "translations": {}}');
|
withHint ? (fileName, contents) => {
|
||||||
expect(result.locale).toEqual('en');
|
const parser = new SimpleJsonTranslationParser();
|
||||||
});
|
const hint = parser.canParse(fileName, contents);
|
||||||
|
if (!hint) {
|
||||||
|
throw new Error('expected contents to be valid');
|
||||||
|
}
|
||||||
|
return parser.parse(fileName, contents, hint);
|
||||||
|
} : (fileName, contents) => {
|
||||||
|
const parser = new SimpleJsonTranslationParser();
|
||||||
|
return parser.parse(fileName, contents);
|
||||||
|
};
|
||||||
|
|
||||||
it('should extract and process the translations from the JSON contents', () => {
|
|
||||||
const parser = new SimpleJsonTranslationParser();
|
it('should extract the locale from the JSON contents', () => {
|
||||||
const result = parser.parse('/some/file.json', `{
|
const result = doParse('/some/file.json', '{"locale": "en", "translations": {}}');
|
||||||
|
expect(result.locale).toEqual('en');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract and process the translations from the JSON contents', () => {
|
||||||
|
const result = doParse('/some/file.json', `{
|
||||||
"locale": "fr",
|
"locale": "fr",
|
||||||
"translations": {
|
"translations": {
|
||||||
"Hello, {$ph_1}!": "Bonjour, {$ph_1}!"
|
"Hello, {$ph_1}!": "Bonjour, {$ph_1}!"
|
||||||
}
|
}
|
||||||
}`);
|
}`);
|
||||||
expect(result.translations).toEqual({
|
expect(result.translations).toEqual({
|
||||||
'Hello, {$ph_1}!': {
|
'Hello, {$ph_1}!': {
|
||||||
messageParts: ɵmakeTemplateObject(['Bonjour, ', '!'], ['Bonjour, ', '!']),
|
messageParts: ɵmakeTemplateObject(['Bonjour, ', '!'], ['Bonjour, ', '!']),
|
||||||
placeholderNames: ['ph_1']
|
placeholderNames: ['ph_1']
|
||||||
},
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
});
|
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {ɵcomputeMsgId, ɵmakeParsedTranslation} from '@angular/localize';
|
import {ɵcomputeMsgId, ɵmakeParsedTranslation} from '@angular/localize';
|
||||||
import {Diagnostics} from '../../../../src/diagnostics';
|
import {ParsedTranslationBundle} from '../../../../src/translate/translation_files/translation_parsers/translation_parser';
|
||||||
import {XtbTranslationParser} from '../../../../src/translate/translation_files/translation_parsers/xtb_translation_parser';
|
import {XtbTranslationParser} from '../../../../src/translate/translation_files/translation_parsers/xtb_translation_parser';
|
||||||
|
|
||||||
describe('XtbTranslationParser', () => {
|
describe('XtbTranslationParser', () => {
|
||||||
|
@ -24,593 +24,299 @@ describe('XtbTranslationParser', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parse() [without hint]', () => {
|
for (const withHint of [true, false]) {
|
||||||
it('should extract the locale from the file contents', () => {
|
describe(`parse() [${withHint ? 'with' : 'without'} hint]`, () => {
|
||||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
const doParse: (fileName: string, XTB: string) => ParsedTranslationBundle =
|
||||||
<translationbundle lang='fr'>
|
withHint ? (fileName, XTB) => {
|
||||||
<translation id="8841459487341224498">rab</translation>
|
const parser = new XtbTranslationParser();
|
||||||
</translationbundle>`;
|
const hint = parser.canParse(fileName, XTB);
|
||||||
const parser = new XtbTranslationParser();
|
if (!hint) {
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
throw new Error('expected XTB to be valid');
|
||||||
expect(result.locale).toEqual('fr');
|
}
|
||||||
});
|
return parser.parse(fileName, XTB, hint);
|
||||||
|
} : (fileName, XTB) => {
|
||||||
|
const parser = new XtbTranslationParser();
|
||||||
|
return parser.parse(fileName, XTB);
|
||||||
|
};
|
||||||
|
|
||||||
it('should extract basic messages', () => {
|
const expectToFail:
|
||||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
(fileName: string, XLIFF: string, errorMatcher: RegExp, diagnosticMessage: string) =>
|
||||||
<!DOCTYPE translationbundle [<!ELEMENT translationbundle (translation)*>
|
void = withHint ? (fileName, XLIFF, _errorMatcher, diagnosticMessage) => {
|
||||||
<!ATTLIST translationbundle lang CDATA #REQUIRED>
|
const result = doParse(fileName, XLIFF);
|
||||||
|
expect(result.diagnostics.messages.length).toEqual(1);
|
||||||
|
expect(result.diagnostics.messages[0].message).toEqual(diagnosticMessage);
|
||||||
|
} : (fileName, XLIFF, errorMatcher, _diagnosticMessage) => {
|
||||||
|
expect(() => doParse(fileName, XLIFF)).toThrowError(errorMatcher);
|
||||||
|
};
|
||||||
|
|
||||||
<!ELEMENT translation (#PCDATA|ph)*>
|
it('should extract the locale from the file contents', () => {
|
||||||
<!ATTLIST translation id CDATA #REQUIRED>
|
const XTB = [
|
||||||
|
`<?xml version="1.0" encoding="UTF-8"?>`,
|
||||||
<!ELEMENT ph EMPTY>
|
`<translationbundle lang='fr'>`,
|
||||||
<!ATTLIST ph name CDATA #REQUIRED>
|
` <translation id="8841459487341224498">rab</translation>`,
|
||||||
]>
|
`</translationbundle>`,
|
||||||
<translationbundle>
|
].join('\n');
|
||||||
<translation id="8841459487341224498">rab</translation>
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
</translationbundle>`;
|
expect(result.locale).toEqual('fr');
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
|
||||||
|
|
||||||
expect(result.translations['8841459487341224498']).toEqual(ɵmakeParsedTranslation(['rab']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with simple placeholders', () => {
|
|
||||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="8877975308926375834"><ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/></translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
|
||||||
|
|
||||||
expect(result.translations['8877975308926375834'])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['', 'rab', ''], ['START_PARAGRAPH', 'CLOSE_PARAGRAPH']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with simple ICU expressions', () => {
|
|
||||||
const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="7717087045075616176">*<ph name="ICU"/>*</translation>
|
|
||||||
<translation id="5115002811911870583">{VAR_PLURAL, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
|
||||||
|
|
||||||
expect(result.translations['7717087045075616176'])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['*', '*'], ['ICU']));
|
|
||||||
expect(result.translations['5115002811911870583'])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(
|
|
||||||
['{VAR_PLURAL, plural, =1 {{START_PARAGRAPH}rab{CLOSE_PARAGRAPH}}}'], []));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with duplicate source messages', () => {
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="9205907420411818817">oof</translation>
|
|
||||||
<translation id="i">toto</translation>
|
|
||||||
<translation id="bar">tata</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('foo')]).toEqual(ɵmakeParsedTranslation(['oof']));
|
|
||||||
expect(result.translations['i']).toEqual(ɵmakeParsedTranslation(['toto']));
|
|
||||||
expect(result.translations['bar']).toEqual(ɵmakeParsedTranslation(['tata']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with only placeholders, which are re-ordered', () => {
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="7118057989405618448"><ph name="TAG_IMG_1"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/></translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('{$LINE_BREAK}{$TAG_IMG}{$TAG_IMG_1}')])
|
|
||||||
.toEqual(
|
|
||||||
ɵmakeParsedTranslation(['', '', '', ''], ['TAG_IMG_1', 'TAG_IMG', 'LINE_BREAK']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with empty target', () => {
|
|
||||||
/**
|
|
||||||
* Source HTML:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* <div i18n>hello <span></span></div>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="2826198357052921524"></translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('hello {$START_TAG_SPAN}{$CLOSE_TAG_SPAN}')])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with deeply nested ICUs', () => {
|
|
||||||
/**
|
|
||||||
* Source HTML:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* Test: { count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} } =other {a lot}}
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Note that the message gets split into two translation units:
|
|
||||||
* * The first one contains the outer message with an `ICU` placeholder
|
|
||||||
* * The second one is the ICU expansion itself
|
|
||||||
*
|
|
||||||
* Note that special markers `VAR_PLURAL` and `VAR_SELECT` are added, which are then
|
|
||||||
replaced by IVY at runtime with the actual values being rendered by the ICU expansion.
|
|
||||||
*/
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="980940425376233536">Le test: <ph name="ICU" equiv-text="{ count, plural, =0 {...} =other {...}}"/></translation>
|
|
||||||
<translation id="5207293143089349404">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"/>profondément imbriqué<ph name="CLOSE_PARAGRAPH"/>}}} =other {beaucoup}}</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('Test: {$ICU}')])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['Le test: ', ''], ['ICU']));
|
|
||||||
|
|
||||||
expect(
|
|
||||||
result.translations[ɵcomputeMsgId(
|
|
||||||
'{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {{START_PARAGRAPH}deeply nested{CLOSE_PARAGRAPH}}}} =other {beaucoup}}')])
|
|
||||||
.toEqual(ɵmakeParsedTranslation([
|
|
||||||
'{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {{START_PARAGRAPH}profondément imbriqué{CLOSE_PARAGRAPH}}}} =other {beaucoup}}'
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations containing multiple lines', () => {
|
|
||||||
/**
|
|
||||||
* Source HTML:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* <div i18n>multi
|
|
||||||
* lines</div>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="2340165783990709777">multi\nlignes</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('multi\nlines')])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['multi\nlignes']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should warn on unrecognised ICU messages', () => {
|
|
||||||
// See https://github.com/angular/angular/issues/14046
|
|
||||||
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="valid">This is a valid message</translation>
|
|
||||||
<translation id="invalid">{REGION_COUNT_1, plural, =0 {unused plural form} =1 {1 region} other {{REGION_COUNT_2} regions}}</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
|
|
||||||
// Parsing the file should not fail
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB);
|
|
||||||
|
|
||||||
// We should be able to read the valid message
|
|
||||||
expect(result.translations['valid'])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['This is a valid message']));
|
|
||||||
|
|
||||||
// Trying to access the invalid message should fail
|
|
||||||
expect(result.translations['invalid']).toBeUndefined();
|
|
||||||
expect(result.diagnostics.messages).toContain({
|
|
||||||
type: 'warning',
|
|
||||||
message:
|
|
||||||
`Could not parse message with id "invalid" - perhaps it has an unrecognised ICU format?\n` +
|
|
||||||
`Error: Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.)\n` +
|
|
||||||
`Error: Invalid ICU message. Missing '}'.`
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('[structure errors]', () => {
|
|
||||||
it('should throw when there are nested translationbundle tags', () => {
|
|
||||||
const XTB =
|
|
||||||
'<translationbundle><translationbundle></translationbundle></translationbundle>';
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
parser.parse('/some/file.xtb', XTB);
|
|
||||||
}).toThrowError(`Failed to parse "/some/file.xtb" as XMB/XTB format
|
|
||||||
ERRORS:
|
|
||||||
- Unexpected <translationbundle> tag. ("<translationbundle>[ERROR ->]<translationbundle></translationbundle></translationbundle>"): /some/file.xtb@0:19`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when a translation has no id attribute', () => {
|
it('should extract basic messages', () => {
|
||||||
const XTB = `
|
const XTB = [
|
||||||
<translationbundle>
|
`<?xml version="1.0" encoding="UTF-8"?>`,
|
||||||
<translation></translation>
|
`<!DOCTYPE translationbundle [`,
|
||||||
</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>`,
|
||||||
|
` <translation id="8841459487341224498">rab</translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
|
|
||||||
expect(() => {
|
expect(result.translations['8841459487341224498']).toEqual(ɵmakeParsedTranslation(['rab']));
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
parser.parse('/some/file.xtb', XTB);
|
|
||||||
}).toThrowError(/Missing required "id" attribute/);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw on duplicate translation id', () => {
|
it('should extract translations with simple placeholders', () => {
|
||||||
const XTB = `
|
const XTB = [
|
||||||
<translationbundle>
|
`<?xml version="1.0" encoding="UTF-8"?>`,
|
||||||
<translation id="deadbeef"></translation>
|
`<translationbundle>`,
|
||||||
<translation id="deadbeef"></translation>
|
` <translation id="8877975308926375834"><ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/></translation>`,
|
||||||
</translationbundle>`;
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
|
|
||||||
expect(() => {
|
expect(result.translations['8877975308926375834'])
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
parser.parse('/some/file.xtb', XTB);
|
|
||||||
}).toThrowError(/Duplicated translations for message "deadbeef"/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('[message errors]', () => {
|
|
||||||
it('should throw on unknown message tags', () => {
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="deadbeef">
|
|
||||||
<source/>
|
|
||||||
</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
parser.parse('/some/file.xtb', XTB);
|
|
||||||
}).toThrowError(/Invalid element found in message/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw when a placeholder misses a name attribute', () => {
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="deadbeef"><ph/></translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
parser.parse('/some/file.xtb', XTB);
|
|
||||||
}).toThrowError(/required "name" attribute/gi);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('parse() [with hint]', () => {
|
|
||||||
it('should extract the locale from the file contents', () => {
|
|
||||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<translationbundle lang='fr'>
|
|
||||||
<translation id="8841459487341224498">rab</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
expect(result.locale).toEqual('fr');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract basic messages', () => {
|
|
||||||
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>
|
|
||||||
<translation id="8841459487341224498">rab</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
|
|
||||||
expect(result.translations['8841459487341224498']).toEqual(ɵmakeParsedTranslation(['rab']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with simple placeholders', () => {
|
|
||||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="8877975308926375834"><ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/></translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
|
|
||||||
expect(result.translations['8877975308926375834'])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['', 'rab', ''], ['START_PARAGRAPH', 'CLOSE_PARAGRAPH']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with simple ICU expressions', () => {
|
|
||||||
const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="7717087045075616176">*<ph name="ICU"/>*</translation>
|
|
||||||
<translation id="5115002811911870583">{VAR_PLURAL, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
|
|
||||||
expect(result.translations['7717087045075616176'])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['*', '*'], ['ICU']));
|
|
||||||
expect(result.translations['5115002811911870583'])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(
|
|
||||||
['{VAR_PLURAL, plural, =1 {{START_PARAGRAPH}rab{CLOSE_PARAGRAPH}}}'], []));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with duplicate source messages', () => {
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="9205907420411818817">oof</translation>
|
|
||||||
<translation id="i">toto</translation>
|
|
||||||
<translation id="bar">tata</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('foo')]).toEqual(ɵmakeParsedTranslation(['oof']));
|
|
||||||
expect(result.translations['i']).toEqual(ɵmakeParsedTranslation(['toto']));
|
|
||||||
expect(result.translations['bar']).toEqual(ɵmakeParsedTranslation(['tata']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with only placeholders, which are re-ordered', () => {
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="7118057989405618448"><ph name="TAG_IMG_1"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/></translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('{$LINE_BREAK}{$TAG_IMG}{$TAG_IMG_1}')])
|
|
||||||
.toEqual(
|
|
||||||
ɵmakeParsedTranslation(['', '', '', ''], ['TAG_IMG_1', 'TAG_IMG', 'LINE_BREAK']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with empty target', () => {
|
|
||||||
/**
|
|
||||||
* Source HTML:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* <div i18n>hello <span></span></div>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="2826198357052921524"></translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('hello {$START_TAG_SPAN}{$CLOSE_TAG_SPAN}')])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations with deeply nested ICUs', () => {
|
|
||||||
/**
|
|
||||||
* Source HTML:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* Test: { count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} } =other {a lot}}
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Note that the message gets split into two translation units:
|
|
||||||
* * The first one contains the outer message with an `ICU` placeholder
|
|
||||||
* * The second one is the ICU expansion itself
|
|
||||||
*
|
|
||||||
* Note that special markers `VAR_PLURAL` and `VAR_SELECT` are added, which are then
|
|
||||||
replaced by IVY at runtime with the actual values being rendered by the ICU expansion.
|
|
||||||
*/
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="980940425376233536">Le test: <ph name="ICU" equiv-text="{ count, plural, =0 {...} =other {...}}"/></translation>
|
|
||||||
<translation id="5207293143089349404">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"/>profondément imbriqué<ph name="CLOSE_PARAGRAPH"/>}}} =other {beaucoup}}</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('Test: {$ICU}')])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['Le test: ', ''], ['ICU']));
|
|
||||||
|
|
||||||
expect(
|
|
||||||
result.translations[ɵcomputeMsgId(
|
|
||||||
'{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {{START_PARAGRAPH}deeply nested{CLOSE_PARAGRAPH}}}} =other {beaucoup}}')])
|
|
||||||
.toEqual(ɵmakeParsedTranslation([
|
|
||||||
'{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {{START_PARAGRAPH}profondément imbriqué{CLOSE_PARAGRAPH}}}} =other {beaucoup}}'
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract translations containing multiple lines', () => {
|
|
||||||
/**
|
|
||||||
* Source HTML:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* <div i18n>multi
|
|
||||||
* lines</div>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="2340165783990709777">multi\nlignes</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
|
|
||||||
expect(result.translations[ɵcomputeMsgId('multi\nlines')])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['multi\nlignes']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should warn on unrecognised ICU messages', () => {
|
|
||||||
// See https://github.com/angular/angular/issues/14046
|
|
||||||
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="valid">This is a valid message</translation>
|
|
||||||
<translation id="invalid">{REGION_COUNT_1, plural, =0 {unused plural form} =1 {1 region} other {{REGION_COUNT_2} regions}}</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
|
|
||||||
// Parsing the file should not fail
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
|
|
||||||
// We should be able to read the valid message
|
|
||||||
expect(result.translations['valid'])
|
|
||||||
.toEqual(ɵmakeParsedTranslation(['This is a valid message']));
|
|
||||||
|
|
||||||
// Trying to access the invalid message should fail
|
|
||||||
expect(result.translations['invalid']).toBeUndefined();
|
|
||||||
expect(result.diagnostics.messages).toContain({
|
|
||||||
type: 'warning',
|
|
||||||
message:
|
|
||||||
`Could not parse message with id "invalid" - perhaps it has an unrecognised ICU format?\n` +
|
|
||||||
`Error: Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.)\n` +
|
|
||||||
`Error: Invalid ICU message. Missing '}'.`
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('[structure errors]', () => {
|
|
||||||
it('should throw when there are nested translationbundle tags', () => {
|
|
||||||
const XTB =
|
|
||||||
'<translationbundle><translationbundle></translationbundle></translationbundle>';
|
|
||||||
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
expect(result.diagnostics.messages.length).toEqual(1);
|
|
||||||
expect(result.diagnostics.messages[0].message)
|
|
||||||
.toEqual(
|
.toEqual(
|
||||||
`Unexpected <translationbundle> tag. ("<translationbundle>[ERROR ->]<translationbundle></translationbundle></translationbundle>"): /some/file.xtb@0:19`);
|
ɵmakeParsedTranslation(['', 'rab', ''], ['START_PARAGRAPH', 'CLOSE_PARAGRAPH']));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when a translation has no id attribute', () => {
|
it('should extract translations with simple ICU expressions', () => {
|
||||||
const XTB = `
|
const XTB = [
|
||||||
<translationbundle>
|
`<?xml version="1.0" encoding="UTF-8" ?>`,
|
||||||
<translation></translation>
|
`<translationbundle>`,
|
||||||
</translationbundle>`;
|
` <translation id="7717087045075616176">*<ph name="ICU"/>*</translation>`,
|
||||||
|
` <translation id="5115002811911870583">{VAR_PLURAL, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
|
|
||||||
const parser = new XtbTranslationParser();
|
expect(result.translations['7717087045075616176'])
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
.toEqual(ɵmakeParsedTranslation(['*', '*'], ['ICU']));
|
||||||
if (!hint) {
|
expect(result.translations['5115002811911870583'])
|
||||||
return fail('expected XTB to be valid');
|
.toEqual(ɵmakeParsedTranslation(
|
||||||
}
|
['{VAR_PLURAL, plural, =1 {{START_PARAGRAPH}rab{CLOSE_PARAGRAPH}}}'], []));
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
expect(result.diagnostics.messages.length).toEqual(1);
|
|
||||||
expect(result.diagnostics.messages[0].message)
|
|
||||||
.toEqual(`Missing required "id" attribute on <trans-unit> element. ("
|
|
||||||
<translationbundle>
|
|
||||||
[ERROR ->]<translation></translation>
|
|
||||||
</translationbundle>"): /some/file.xtb@2:12`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw on duplicate translation id', () => {
|
it('should extract translations with duplicate source messages', () => {
|
||||||
const XTB = `
|
const XTB = [
|
||||||
<translationbundle>
|
`<translationbundle>`,
|
||||||
<translation id="deadbeef"></translation>
|
` <translation id="9205907420411818817">oof</translation>`,
|
||||||
<translation id="deadbeef"></translation>
|
` <translation id="i">toto</translation>`,
|
||||||
</translationbundle>`;
|
` <translation id="bar">tata</translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
|
|
||||||
const parser = new XtbTranslationParser();
|
expect(result.translations[ɵcomputeMsgId('foo')]).toEqual(ɵmakeParsedTranslation(['oof']));
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
expect(result.translations['i']).toEqual(ɵmakeParsedTranslation(['toto']));
|
||||||
if (!hint) {
|
expect(result.translations['bar']).toEqual(ɵmakeParsedTranslation(['tata']));
|
||||||
return fail('expected XTB to be valid');
|
});
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
it('should extract translations with only placeholders, which are re-ordered', () => {
|
||||||
expect(result.diagnostics.messages.length).toEqual(1);
|
const XTB = [
|
||||||
expect(result.diagnostics.messages[0].message)
|
`<translationbundle>`,
|
||||||
.toEqual(`Duplicated translations for message "deadbeef" ("
|
` <translation id="7118057989405618448"><ph name="TAG_IMG_1"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/></translation>`,
|
||||||
<translationbundle>
|
`</translationbundle>`,
|
||||||
<translation id="deadbeef"></translation>
|
].join('\n');
|
||||||
[ERROR ->]<translation id="deadbeef"></translation>
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
</translationbundle>"): /some/file.xtb@3:12`);
|
|
||||||
|
expect(result.translations[ɵcomputeMsgId('{$LINE_BREAK}{$TAG_IMG}{$TAG_IMG_1}')])
|
||||||
|
.toEqual(
|
||||||
|
ɵmakeParsedTranslation(['', '', '', ''], ['TAG_IMG_1', 'TAG_IMG', 'LINE_BREAK']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract translations with empty target', () => {
|
||||||
|
/**
|
||||||
|
* Source HTML:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <div i18n>hello <span></span></div>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
const XTB = [
|
||||||
|
`<translationbundle>`,
|
||||||
|
` <translation id="2826198357052921524"></translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
|
|
||||||
|
expect(result.translations[ɵcomputeMsgId('hello {$START_TAG_SPAN}{$CLOSE_TAG_SPAN}')])
|
||||||
|
.toEqual(ɵmakeParsedTranslation(['']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract translations with deeply nested ICUs', () => {
|
||||||
|
/**
|
||||||
|
* Source HTML:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* Test: { count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} } =other {a
|
||||||
|
lot}}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Note that the message gets split into two translation units:
|
||||||
|
* * The first one contains the outer message with an `ICU` placeholder
|
||||||
|
* * The second one is the ICU expansion itself
|
||||||
|
*
|
||||||
|
* Note that special markers `VAR_PLURAL` and `VAR_SELECT` are added, which are then
|
||||||
|
replaced by IVY at runtime with the actual values being rendered by the ICU expansion.
|
||||||
|
*/
|
||||||
|
const XTB = [
|
||||||
|
`<translationbundle>`,
|
||||||
|
` <translation id="980940425376233536">Le test: <ph name="ICU" equiv-text="{ count, plural, =0 {...} =other {...}}"/></translation>`,
|
||||||
|
` <translation id="5207293143089349404">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"/>profondément imbriqué<ph name="CLOSE_PARAGRAPH"/>}}} =other {beaucoup}}</translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
|
|
||||||
|
expect(result.translations[ɵcomputeMsgId('Test: {$ICU}')])
|
||||||
|
.toEqual(ɵmakeParsedTranslation(['Le test: ', ''], ['ICU']));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
result.translations[ɵcomputeMsgId(
|
||||||
|
'{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {{START_PARAGRAPH}deeply nested{CLOSE_PARAGRAPH}}}} =other {beaucoup}}')])
|
||||||
|
.toEqual(ɵmakeParsedTranslation([
|
||||||
|
'{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {{START_PARAGRAPH}profondément imbriqué{CLOSE_PARAGRAPH}}}} =other {beaucoup}}'
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract translations containing multiple lines', () => {
|
||||||
|
/**
|
||||||
|
* Source HTML:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <div i18n>multi
|
||||||
|
* lines</div>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
const XTB = [
|
||||||
|
`<translationbundle>`,
|
||||||
|
` <translation id="2340165783990709777">multi\nlignes</translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
|
|
||||||
|
expect(result.translations[ɵcomputeMsgId('multi\nlines')])
|
||||||
|
.toEqual(ɵmakeParsedTranslation(['multi\nlignes']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should warn on unrecognised ICU messages', () => {
|
||||||
|
// See https://github.com/angular/angular/issues/14046
|
||||||
|
|
||||||
|
const XTB = [
|
||||||
|
`<translationbundle>`,
|
||||||
|
` <translation id="valid">This is a valid message</translation>`,
|
||||||
|
` <translation id="invalid">{REGION_COUNT_1, plural, =0 {unused plural form} =1 {1 region} other {{REGION_COUNT_2} regions}}</translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
// Parsing the file should not fail
|
||||||
|
const result = doParse('/some/file.xtb', XTB);
|
||||||
|
|
||||||
|
// We should be able to read the valid message
|
||||||
|
expect(result.translations['valid'])
|
||||||
|
.toEqual(ɵmakeParsedTranslation(['This is a valid message']));
|
||||||
|
|
||||||
|
// Trying to access the invalid message should fail
|
||||||
|
expect(result.translations['invalid']).toBeUndefined();
|
||||||
|
expect(result.diagnostics.messages).toContain({
|
||||||
|
type: 'warning',
|
||||||
|
message:
|
||||||
|
`Could not parse message with id "invalid" - perhaps it has an unrecognised ICU format?\n` +
|
||||||
|
`Error: Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.)\n` +
|
||||||
|
`Error: Invalid ICU message. Missing '}'.`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('[structure errors]', () => {
|
||||||
|
it('should throw when there are nested translationbundle tags', () => {
|
||||||
|
const XTB =
|
||||||
|
'<translationbundle><translationbundle></translationbundle></translationbundle>';
|
||||||
|
|
||||||
|
expectToFail(
|
||||||
|
'/some/file.xtb', XTB, /Failed to parse "\/some\/file.xtb" as XMB\/XTB format/,
|
||||||
|
`Unexpected <translationbundle> tag. ("<translationbundle>[ERROR ->]<translationbundle></translationbundle></translationbundle>"): /some/file.xtb@0:19`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when a translation has no id attribute', () => {
|
||||||
|
const XTB = [
|
||||||
|
`<translationbundle>`,
|
||||||
|
` <translation></translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
expectToFail('/some/file.xtb', XTB, /Missing required "id" attribute/, [
|
||||||
|
`Missing required "id" attribute on <trans-unit> element. ("<translationbundle>`,
|
||||||
|
` [ERROR ->]<translation></translation>`,
|
||||||
|
`</translationbundle>"): /some/file.xtb@1:2`,
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw on duplicate translation id', () => {
|
||||||
|
const XTB = [
|
||||||
|
`<translationbundle>`,
|
||||||
|
` <translation id="deadbeef"></translation>`,
|
||||||
|
` <translation id="deadbeef"></translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
expectToFail('/some/file.xtb', XTB, /Duplicated translations for message "deadbeef"/, [
|
||||||
|
`Duplicated translations for message "deadbeef" ("<translationbundle>`,
|
||||||
|
` <translation id="deadbeef"></translation>`,
|
||||||
|
` [ERROR ->]<translation id="deadbeef"></translation>`,
|
||||||
|
`</translationbundle>"): /some/file.xtb@2:2`,
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('[message errors]', () => {
|
||||||
|
it('should throw on unknown message tags', () => {
|
||||||
|
const XTB = [
|
||||||
|
`<translationbundle>`,
|
||||||
|
` <translation id="deadbeef">`,
|
||||||
|
` <source/>`,
|
||||||
|
` </translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
expectToFail('/some/file.xtb', XTB, /Invalid element found in message/, [
|
||||||
|
`Invalid element found in message. ("<translationbundle>`,
|
||||||
|
` <translation id="deadbeef">`,
|
||||||
|
` [ERROR ->]<source/>`,
|
||||||
|
` </translation>`,
|
||||||
|
`</translationbundle>"): /some/file.xtb@2:4`,
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when a placeholder misses a name attribute', () => {
|
||||||
|
const XTB = [
|
||||||
|
`<translationbundle>`,
|
||||||
|
` <translation id="deadbeef"><ph/></translation>`,
|
||||||
|
`</translationbundle>`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
expectToFail('/some/file.xtb', XTB, /required "name" attribute/gi, [
|
||||||
|
`Missing required "name" attribute: ("<translationbundle>`,
|
||||||
|
` <translation id="deadbeef">[ERROR ->]<ph/></translation>`,
|
||||||
|
`</translationbundle>"): /some/file.xtb@1:29`,
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
describe('[message errors]', () => {
|
|
||||||
it('should throw on unknown message tags', () => {
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="deadbeef">
|
|
||||||
<source/>
|
|
||||||
</translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
expect(result.diagnostics.messages.length).toEqual(1);
|
|
||||||
expect(result.diagnostics.messages[0].message).toEqual(`Invalid element found in message. ("
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="deadbeef">
|
|
||||||
[ERROR ->]<source/>
|
|
||||||
</translation>
|
|
||||||
</translationbundle>"): /some/file.xtb@3:14`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw when a placeholder misses a name attribute', () => {
|
|
||||||
const XTB = `
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="deadbeef"><ph/></translation>
|
|
||||||
</translationbundle>`;
|
|
||||||
|
|
||||||
const parser = new XtbTranslationParser();
|
|
||||||
const hint = parser.canParse('/some/file.xtb', XTB);
|
|
||||||
if (!hint) {
|
|
||||||
return fail('expected XTB to be valid');
|
|
||||||
}
|
|
||||||
const result = parser.parse('/some/file.xtb', XTB, hint);
|
|
||||||
expect(result.diagnostics.messages.length).toEqual(1);
|
|
||||||
expect(result.diagnostics.messages[0].message)
|
|
||||||
.toEqual(`Missing required "name" attribute: ("
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="deadbeef">[ERROR ->]<ph/></translation>
|
|
||||||
</translationbundle>"): /some/file.xtb@2:39`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue