diff --git a/packages/localize/src/tools/src/translate/translation_files/translation_parsers/xliff1_translation_parser.ts b/packages/localize/src/tools/src/translate/translation_files/translation_parsers/xliff1_translation_parser.ts index 0cbe064a24..2656cc8584 100644 --- a/packages/localize/src/tools/src/translate/translation_files/translation_parsers/xliff1_translation_parser.ts +++ b/packages/localize/src/tools/src/translate/translation_files/translation_parsers/xliff1_translation_parser.ts @@ -59,12 +59,24 @@ export class Xliff1TranslationParser implements TranslationParser(); + for (const file of files) { + const locale = getAttribute(file, 'target-language'); + if (locale !== undefined) { + localesFound.add(locale); + bundle.locale = locale; + } + visitAll(translationVisitor, file.children, bundle); + } + + if (localesFound.size > 1) { + addParseDiagnostic( + diagnostics, element.sourceSpan, + `More than one locale found in translation file: ${JSON.stringify(Array.from(localesFound))}. Using "${bundle.locale}"`, + ParseErrorLevel.WARNING); + } return bundle; } diff --git a/packages/localize/src/tools/src/translate/translation_files/translation_parsers/xliff2_translation_parser.ts b/packages/localize/src/tools/src/translate/translation_files/translation_parsers/xliff2_translation_parser.ts index 0dbd7f54ec..debeafcd10 100644 --- a/packages/localize/src/tools/src/translate/translation_files/translation_parsers/xliff2_translation_parser.ts +++ b/packages/localize/src/tools/src/translate/translation_files/translation_parsers/xliff2_translation_parser.ts @@ -40,13 +40,6 @@ export class Xliff2TranslationParser implements TranslationParser addParseError(diagnostics, e)); - if (element.children.length === 0) { - addParseDiagnostic( - diagnostics, element.sourceSpan, 'Missing expected element', - ParseErrorLevel.WARNING); - return {locale: undefined, translations: {}, diagnostics}; - } - const locale = getAttribute(element, 'trgLang'); const files = element.children.filter(isFileElement); if (files.length === 0) { @@ -61,8 +54,9 @@ export class Xliff2TranslationParser implements TranslationParser { }); describe('parse() [without hint]', () => { - it('should extract the locale from the file contents', () => { - const XLIFF = ` + it('should extract the locale from the last `` element to contain a `target-language` attribute', + () => { + const XLIFF = ` + + + - - + + + + + + + + + + `; - const parser = new Xliff1TranslationParser(); - const result = parser.parse('/some/file.xlf', XLIFF); - expect(result.locale).toEqual('fr'); - }); + const parser = new Xliff1TranslationParser(); + const hint = parser.canParse('/some/file.xlf', XLIFF); + if (!hint) { + return fail('expected XLIFF to be valid'); + } + const result = parser.parse('/some/file.xlf', XLIFF, hint); + expect(result.locale).toEqual('de'); + }); it('should return an undefined locale if there is no locale in the file', () => { const XLIFF = ` @@ -437,6 +453,58 @@ describe('Xliff1TranslationParser', () => { .toEqual(ɵmakeParsedTranslation(['Weiter'])); }); + it('should merge messages from each `` element', () => { + /** + * Source HTML: + * + * ``` + *
translatable attribute
+ * ``` + + * ``` + *
translatable element with placeholders {{ interpolation}}
+ * ``` + */ + const XLIFF = ` + + + + + translatable attribute + etubirtta elbatalsnart + + file.ts + 1 + + + + + + + + translatable element with placeholders + tnemele elbatalsnart sredlohecalp htiw + + file.ts + 2 + + + + + `; + const parser = new Xliff1TranslationParser(); + const result = parser.parse('/some/file.xlf', XLIFF); + + expect(result.translations[ɵcomputeMsgId('translatable attribute')]) + .toEqual(ɵmakeParsedTranslation(['etubirtta elbatalsnart'])); + expect( + result.translations[ɵcomputeMsgId( + 'translatable element {$START_BOLD_TEXT}with placeholders{$LOSE_BOLD_TEXT} {$INTERPOLATION}')]) + .toEqual(ɵmakeParsedTranslation( + ['', ' tnemele elbatalsnart ', 'sredlohecalp htiw', ''], + ['INTERPOLATION', 'START_BOLD_TEXT', 'CLOSE_BOLD_TEXT'])); + }); + describe('[structure errors]', () => { it('should throw when a trans-unit has no translation', () => { const XLIFF = ` @@ -547,22 +615,34 @@ describe('Xliff1TranslationParser', () => { }); describe('parse() [with hint]', () => { - it('should extract the locale from the file contents', () => { - const XLIFF = ` + it('should extract the locale from the last `` element to contain a `target-language` attribute', + () => { + const XLIFF = ` + + + - - + + + + + + + + + + `; - const parser = new Xliff1TranslationParser(); - const hint = parser.canParse('/some/file.xlf', XLIFF); - if (!hint) { - return fail('expected XLIFF to be valid'); - } - const result = parser.parse('/some/file.xlf', XLIFF, hint); - expect(result.locale).toEqual('fr'); - }); + const parser = new Xliff1TranslationParser(); + const hint = parser.canParse('/some/file.xlf', XLIFF); + if (!hint) { + return fail('expected XLIFF to be valid'); + } + const result = parser.parse('/some/file.xlf', XLIFF, hint); + expect(result.locale).toEqual('de'); + }); it('should return an undefined locale if there is no locale in the file', () => { const XLIFF = ` @@ -1005,6 +1085,62 @@ describe('Xliff1TranslationParser', () => { .toEqual(ɵmakeParsedTranslation(['Weiter'])); }); + it('should merge messages from each `` element', () => { + /** + * Source HTML: + * + * ``` + *
translatable attribute
+ * ``` + * + * ``` + *
translatable element with placeholders {{ interpolation}}
+ * ``` + */ + const XLIFF = ` + + + + + translatable attribute + etubirtta elbatalsnart + + file.ts + 1 + + + + + + + + translatable element with placeholders + tnemele elbatalsnart sredlohecalp htiw + + file.ts + 2 + + + + + `; + const parser = new Xliff1TranslationParser(); + const hint = parser.canParse('/some/file.xlf', XLIFF); + if (!hint) { + return fail('expected XLIFF to be valid'); + } + const result = parser.parse('/some/file.xlf', XLIFF, hint); + + expect(result.translations[ɵcomputeMsgId('translatable attribute')]) + .toEqual(ɵmakeParsedTranslation(['etubirtta elbatalsnart'])); + expect( + result.translations[ɵcomputeMsgId( + 'translatable element {$START_BOLD_TEXT}with placeholders{$LOSE_BOLD_TEXT} {$INTERPOLATION}')]) + .toEqual(ɵmakeParsedTranslation( + ['', ' tnemele elbatalsnart ', 'sredlohecalp htiw', ''], + ['INTERPOLATION', 'START_BOLD_TEXT', 'CLOSE_BOLD_TEXT'])); + }); + describe('[structure errors]', () => { it('should provide a diagnostic error when a trans-unit has no translation', () => { const XLIFF = ` diff --git a/packages/localize/src/tools/test/translate/translation_files/translation_parsers/xliff2_translation_parser_spec.ts b/packages/localize/src/tools/test/translate/translation_files/translation_parsers/xliff2_translation_parser_spec.ts index 1ee37f708c..869149b1c9 100644 --- a/packages/localize/src/tools/test/translate/translation_files/translation_parsers/xliff2_translation_parser_spec.ts +++ b/packages/localize/src/tools/test/translate/translation_files/translation_parsers/xliff2_translation_parser_spec.ts @@ -87,7 +87,7 @@ describe('Xliff2TranslationParser', () => { * Source HTML: * * ``` - *
translatable element >with placeholders {{ interpolation}}
+ *
translatable element with placeholders {{ interpolation}}
* ``` */ const XLIFF = ` @@ -373,6 +373,57 @@ describe('Xliff2TranslationParser', () => { .toEqual(ɵmakeParsedTranslation(['Translated first sentence.'])); }); + it('should merge messages from each `` element', () => { + /** + * Source HTML: + * + * ``` + *
translatable attribute
+ * ``` + * + * ``` + *
translatable element with placeholders {{ interpolation}}
+ * ``` + */ + const XLIFF = ` + + + + + file.ts:2 + + + translatable attribute + etubirtta elbatalsnart + + + + + + + file.ts:3 + + + translatable element with placeholders + tnemele elbatalsnart sredlohecalp htiw + + + + `; + const parser = new Xliff2TranslationParser(); + const result = parser.parse('/some/file.xlf', XLIFF); + + expect(result.translations[ɵcomputeMsgId('translatable attribute', '')]) + .toEqual(ɵmakeParsedTranslation(['etubirtta elbatalsnart'])); + expect( + result.translations[ɵcomputeMsgId( + 'translatable element {$START_BOLD_TEXT}with placeholders{$LOSE_BOLD_TEXT} {$INTERPOLATION}')]) + .toEqual(ɵmakeParsedTranslation( + ['', ' tnemele elbatalsnart ', 'sredlohecalp htiw', ''], + ['INTERPOLATION', 'START_BOLD_TEXT', 'CLOSE_BOLD_TEXT'])); + + }); + describe('[structure errors]', () => { it('should throw when a trans-unit has no translation', () => { const XLIFF = ` @@ -866,6 +917,61 @@ describe('Xliff2TranslationParser', () => { .toEqual(ɵmakeParsedTranslation(['Translated first sentence.'])); }); + it('should merge messages from each `` element', () => { + /** + * Source HTML: + * + * ``` + *
translatable attribute
+ * ``` + * + * ``` + *
translatable element with placeholders {{ interpolation}}
+ * ``` + */ + const XLIFF = ` + + + + + file.ts:2 + + + translatable attribute + etubirtta elbatalsnart + + + + + + + file.ts:3 + + + translatable element with placeholders + tnemele elbatalsnart sredlohecalp htiw + + + + `; + const parser = new Xliff2TranslationParser(); + const hint = parser.canParse('/some/file.xlf', XLIFF); + if (!hint) { + return fail('expected XLIFF to be valid'); + } + const result = parser.parse('/some/file.xlf', XLIFF, hint); + + expect(result.translations[ɵcomputeMsgId('translatable attribute', '')]) + .toEqual(ɵmakeParsedTranslation(['etubirtta elbatalsnart'])); + expect( + result.translations[ɵcomputeMsgId( + 'translatable element {$START_BOLD_TEXT}with placeholders{$LOSE_BOLD_TEXT} {$INTERPOLATION}')]) + .toEqual(ɵmakeParsedTranslation( + ['', ' tnemele elbatalsnart ', 'sredlohecalp htiw', ''], + ['INTERPOLATION', 'START_BOLD_TEXT', 'CLOSE_BOLD_TEXT'])); + + }); + describe('[structure errors]', () => { it('should provide a diagnostic error when a trans-unit has no translation', () => { const XLIFF = `