UNPKG

ngx-i18nsupport

Version:

Some tooling to be used with the Angular 2 i18n workflow

935 lines (870 loc) 63.2 kB
import * as fs from "fs"; import {XliffMerge} from './xliff-merge'; import {IConfigFile} from './i-xliff-merge-options'; import {CommandOutput} from '../common/command-output'; import WritableStream = NodeJS.WritableStream; import {WriterToString} from '../common/writer-to-string'; import {FileUtil} from '../common/file-util'; import {ITranslationMessagesFile, ITransUnit} from 'ngx-i18nsupport-lib'; import {TranslationMessagesFileReader} from './translation-messages-file-reader'; import {format} from 'util'; import {getApiKey} from '../autotranslate/auto-translate-service.spec'; import {STATE_FINAL, STATE_NEW, STATE_TRANSLATED} from 'ngx-i18nsupport-lib/dist'; import {XmlReader} from './xml-reader'; /** * Created by martin on 18.02.2017. * Testcases for XliffMerge Format XLIFF 1.2. */ describe('XliffMerge XLIFF 1.2 format tests', () => { /** * Workdir, not in git. * Cleaned up for every test. * Tests, that work on files, copy everything they need into this directory. * @type {string} */ let WORKDIR = 'test/work/'; let SRCDIR = 'test/testdata/i18n/'; let ENCODING = 'UTF-8'; /** * Helper function to read Xliff from File * @type {string} */ function readXliff(path: string): ITranslationMessagesFile { if (!path) { throw new Error('oops, no file'); } try { return TranslationMessagesFileReader.fromFile('xlf', path, ENCODING); } catch (err) { console.log(format('error reading %s: "%s"', path, err.message)); return null; } } describe('Merge process checks for format xlf', () => { let MASTER1FILE = 'ngExtractedMaster1.xlf'; let MASTER2FILE = 'ngExtractedMaster2.xlf'; let MASTER1SRC = SRCDIR + MASTER1FILE; let MASTER2SRC = SRCDIR + MASTER2FILE; let MASTERFILE = 'messages.xlf'; let MASTER = WORKDIR + MASTERFILE; let ID_TRANSLATED_SCHLIESSEN = "1ead0ad1063d0c9e005fe56c9529aef4c1ef9d21"; // an ID from ngExtractedMaster1.xlf let ID_REMOVED_STARTSEITE = "c536247d71822c272f8e9155f831e0efb5aa0d31"; // an ID that will be removed in master2 let ID_REMOVED_SUCHEN = "d17aee1ddf9fe1c0afe8440e02ef5ab906a69699"; // another removed ID let ID_WITH_PLACEHOLDER = "af0819ea4a5db68737ebcabde2f5e432b66352e8"; let ID_MISSING_SOURCEREF = '57e605bfa130afb4de4ee40e496e854a9e8a28a7'; let ID_DESCRIPTION_CHANGE = 'DescriptionAndMeaning1'; let ID_DESCRIPTION_ADD = 'AddDescriptionAndMeaning'; let ID_DESCRIPTION_REMOVE = 'RemoveDescriptionAndMeaning'; beforeEach(() => { if (!fs.existsSync(WORKDIR)){ fs.mkdirSync(WORKDIR); } // cleanup workdir FileUtil.deleteFolderContentRecursive(WORKDIR); }); it('should fix source language, if the masters lang is not the default', (done) => { FileUtil.copy(MASTER1SRC, MASTER); let master: ITranslationMessagesFile = readXliff(MASTER); expect(master.sourceLanguage()).toBe('en'); // master is german, but ng-18n extracts it as en let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); expect(ws.writtenData()).toContain('master says to have source-language="en"'); expect(ws.writtenData()).toContain('changed master source-language="en" to "de"'); let newmaster: ITranslationMessagesFile = readXliff(MASTER); expect(newmaster.sourceLanguage()).toBe('de'); // master is german done(); }); it('should generate translated file for default language de from master', (done) => { FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, useSourceAsTarget: false } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); let langFile: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('de')); expect(langFile.sourceLanguage()).toBe('de'); expect(langFile.targetLanguage()).toBe('de'); langFile.forEachTransUnit((tu: ITransUnit) => { expect(tu.targetContent()).toBe(tu.sourceContent()); expect(tu.targetState()).toBe('final'); }); done(); }); it('should generate translated file for all languages', (done) => { FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); let langFileGerman: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('de')); expect(langFileGerman.sourceLanguage()).toBe('de'); expect(langFileGerman.targetLanguage()).toBe('de'); langFileGerman.forEachTransUnit((tu: ITransUnit) => { expect(tu.targetContent()).toBe(tu.sourceContent()); expect(tu.targetState()).toBe('final'); }); let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); expect(langFileEnglish.sourceLanguage()).toBe('de'); expect(langFileEnglish.targetLanguage()).toBe('en'); langFileEnglish.forEachTransUnit((tu: ITransUnit) => { expect(tu.targetContent()).toBe(tu.sourceContent()); expect(tu.targetState()).toBe('new'); }); done(); }); it('should generate translated file for all languages with empty targets for non default languages', (done) => { FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, useSourceAsTarget: false } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); let langFileGerman: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('de')); expect(langFileGerman.sourceLanguage()).toBe('de'); expect(langFileGerman.targetLanguage()).toBe('de'); langFileGerman.forEachTransUnit((tu: ITransUnit) => { expect(tu.targetContent()).toBe(tu.sourceContent()); expect(tu.targetState()).toBe('final'); }); let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); expect(langFileEnglish.sourceLanguage()).toBe('de'); expect(langFileEnglish.targetLanguage()).toBe('en'); langFileEnglish.forEachTransUnit((tu: ITransUnit) => { expect(tu.targetContent()).toBe(''); expect(tu.targetState()).toBe('new'); }); done(); }); it('should generate translated file for all languages with set praefix and suffix (#70)', (done) => { FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, targetPraefix: '%%', targetSuffix: '!!', } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); let langFileGerman: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('de')); expect(langFileGerman.sourceLanguage()).toBe('de'); expect(langFileGerman.targetLanguage()).toBe('de'); langFileGerman.forEachTransUnit((tu: ITransUnit) => { if (!tu.targetContent().startsWith('{VAR')) { expect(tu.targetContent()).toBe('%%' + tu.sourceContent() + '!!'); } else { expect(tu.targetContent()).toBe(tu.sourceContent()); } expect(tu.targetState()).toBe('final'); }); let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); expect(langFileEnglish.sourceLanguage()).toBe('de'); expect(langFileEnglish.targetLanguage()).toBe('en'); langFileEnglish.forEachTransUnit((tu: ITransUnit) => { if (!tu.targetContent().startsWith('{VAR')) { expect(tu.targetContent()).toBe('%%' + tu.sourceContent() + '!!'); } else { expect(tu.targetContent()).toBe(tu.sourceContent()); } expect(tu.targetState()).toBe('new'); }); done(); }); it('should merge translated file for all languages', (done) => { FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); // now translate some texts in the English version let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tu: ITransUnit = langFileEnglish.transUnitWithId(ID_TRANSLATED_SCHLIESSEN); expect(tu).toBeTruthy(); tu.translate('Close'); TranslationMessagesFileReader.save(langFileEnglish); // next step, use another master FileUtil.copy(MASTER2SRC, MASTER); ws = new WriterToString(); commandOut = new CommandOutput(ws); xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); expect(ws.writtenData()).toContain('merged 12 trans-units from master to "en"'); expect(ws.writtenData()).toContain('removed 5 unused trans-units in "en"'); // look, that the new file contains the old translation langFileEnglish = readXliff(xliffMergeCmd.generatedI18nFile('en')); expect(langFileEnglish.transUnitWithId(ID_TRANSLATED_SCHLIESSEN).targetContent()).toBe('Close'); // look, that the removed IDs are really removed. expect(langFileEnglish.transUnitWithId(ID_REMOVED_STARTSEITE)).toBeFalsy(); expect(langFileEnglish.transUnitWithId(ID_REMOVED_SUCHEN)).toBeFalsy(); done(); }); it('should merge changed source content with explicit ID to default language file (#81)', (done) => { const ID_SOURCE_CHANGE = 'sourcechanged'; const ID_SOURCE_CHANGE_STATE_FINAL = 'sourcechanged_state_final'; const TRANSLATED_FILE = 'WithSourceContentChange.de.xlf'; FileUtil.copy(SRCDIR + 'ngExtractedMasterWithSourceContentChange.xlf', MASTER); FileUtil.copy(SRCDIR + TRANSLATED_FILE,WORKDIR + 'messages.de.xlf'); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en'], verbose: true}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); expect(ws.writtenData()).toContain('WARNING: transferred 2 changed source content from master to "de"'); // check that source is changed let langFileGerman: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('de')); let tu: ITransUnit = langFileGerman.transUnitWithId(ID_SOURCE_CHANGE); expect(tu).toBeTruthy(); expect(tu.sourceContent()).toBe('Test Änderung Source (geändert!)'); expect(tu.targetContent()).toBe('Test Änderung Source (geändert!)'); expect(tu.targetState()).toBe(STATE_FINAL); done(); }); it('should merge changed source content to already translated files', (done) => { const ID_SOURCE_CHANGE = 'sourcechanged'; const ID_SOURCE_CHANGE_STATE_FINAL = 'sourcechanged_state_final'; const TRANSLATED_FILE = 'WithSourceContentChange.en.xlf'; FileUtil.copy(SRCDIR + 'ngExtractedMasterWithSourceContentChange.xlf', MASTER); FileUtil.copy(SRCDIR + TRANSLATED_FILE,WORKDIR + 'messages.en.xlf'); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en'], verbose: true}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); expect(ws.writtenData()).toContain('WARNING: transferred 2 changed source content from master to "en"'); // check that source is changed let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tu: ITransUnit = langFileEnglish.transUnitWithId(ID_SOURCE_CHANGE); expect(tu).toBeTruthy(); expect(tu.sourceContent()).toBe('Test Änderung Source (geändert!)'); expect(tu.targetState()).toBe(STATE_NEW); let tuFinal: ITransUnit = langFileEnglish.transUnitWithId(ID_SOURCE_CHANGE_STATE_FINAL); expect(tuFinal).toBeTruthy(); expect(tuFinal.sourceContent()).toBe('Test Änderung Source (state final, geändert!)'); expect(tuFinal.targetState()).toBe(STATE_TRANSLATED); done(); }); it('should merge missing source refs to already translated files', (done) => { let MISSING_SOURCEREF_FILE = 'missingSourcerefs.en.xlf'; FileUtil.copy(MASTER1SRC, MASTER); FileUtil.copy(SRCDIR + MISSING_SOURCEREF_FILE, WORKDIR + 'messages.en.xlf'); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); expect(ws.writtenData()).toContain('WARNING: transferred 2 source references'); // check that there is a translated english version with missing sourcerefs let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tu: ITransUnit = langFileEnglish.transUnitWithId(ID_MISSING_SOURCEREF); expect(tu).toBeTruthy(); expect(tu.targetContent()).toBe('News'); expect(tu.sourceReferences().length).toBe(2); done(); }); it('should merge changed descriptions and meanings to already translated files', (done) => { const TRANSLATED_FILE = 'WithDescriptions.en.xlf'; FileUtil.copy(SRCDIR + 'ngExtractedMasterWithDescriptions.xlf', MASTER); FileUtil.copy(SRCDIR + TRANSLATED_FILE,WORKDIR + 'messages.en.xlf'); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en'], verbose: true}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); expect(ws.writtenData()).toContain('WARNING: transferred 3 changed descriptions/meanings from master to "en"'); // check that description and meaning are changed let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tu: ITransUnit = langFileEnglish.transUnitWithId(ID_DESCRIPTION_CHANGE); expect(tu).toBeTruthy(); expect(tu.description()).toBe('changed description'); expect(tu.meaning()).toBe('changed meaning'); // added description let tuAdded: ITransUnit = langFileEnglish.transUnitWithId(ID_DESCRIPTION_ADD); expect(tuAdded).toBeTruthy(); expect(tuAdded.description()).toBe('added description'); expect(tuAdded.meaning()).toBe('added meaning'); // removed description let tuRemoved: ITransUnit = langFileEnglish.transUnitWithId(ID_DESCRIPTION_REMOVE); expect(tuRemoved).toBeTruthy(); expect(tuRemoved.description()).toBeNull(); expect(tuRemoved.meaning()).toBeNull(); done(); }); it('should preserve order when merging new units (#96)', (done) => { FileUtil.copy(SRCDIR + 'preserveOrderMaster1.xlf', MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'en', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['en', 'de']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); // next step, use new master that has added 3 units FileUtil.copy(SRCDIR + 'preserveOrderMaster2.xlf', MASTER); ws = new WriterToString(); commandOut = new CommandOutput(ws); xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['en', 'de']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); expect(ws.writtenData()).toContain('merged 3 trans-units from master to "de"'); // look, that the new file contains the new units at the correct position const langFileGerman = readXliff(xliffMergeCmd.generatedI18nFile('de')); const addedTu = langFileGerman.transUnitWithId('addedunit1'); expect(addedTu).toBeTruthy(); expect(addedTu.sourceContent()).toBe('added unit 1'); // check position expect(langFileGerman.editedContent().replace(/(\r\n|\n|\r)/gm,"")).toMatch(/addedunit1.*firstunit.*addedunit2.*lastunit.*addedunit3/); done(); }); it('should not remove trailing line break when merging', (done) => { FileUtil.copy(MASTER1SRC, MASTER); const masterContent = FileUtil.read(MASTER, XmlReader.DEFAULT_ENCODING); expect(masterContent.endsWith('\n')).toBeTruthy('master file should end with EOL'); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); const newContent = FileUtil.read(xliffMergeCmd.generatedI18nFile('de'), XmlReader.DEFAULT_ENCODING); expect(newContent.endsWith('\n')).toBeTruthy('file should end with EOL'); done(); }); it('should translate messages with placeholder', (done) => { FileUtil.copy(MASTER2SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); // now translate some texts in the English version let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tu: ITransUnit = langFileEnglish.transUnitWithId(ID_WITH_PLACEHOLDER); expect(tu).toBeTruthy(); tu.translate('Item <x id="INTERPOLATION"/> of <x id="INTERPOLATION_1"/> added.'); TranslationMessagesFileReader.save(langFileEnglish); // look, that the new file contains the translation langFileEnglish = readXliff(xliffMergeCmd.generatedI18nFile('en')); expect(langFileEnglish.transUnitWithId(ID_WITH_PLACEHOLDER).targetContent()).toBe('Item <x id="INTERPOLATION"/> of <x id="INTERPOLATION_1"/> added.'); expect(langFileEnglish.transUnitWithId(ID_WITH_PLACEHOLDER).targetContentNormalized().asDisplayString()).toBe('Item {{0}} of {{1}} added.'); done(); }); it('should translate messages with 2 custom tags with different ids (#84)', (done) => { const ID_2_CUSTOM_TAGS = '8856d298b6fa89a339475c5d5cd20f2d2afcfbf7'; FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); // now translate some texts in the English version let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tu: ITransUnit = langFileEnglish.transUnitWithId(ID_2_CUSTOM_TAGS); expect(tu).toBeTruthy(); expect(tu.sourceContentNormalized().asDisplayString()).toBe('Neues <bs-activity-stream-element></bs-activity-stream-element> wurde gemeldet durch <bs-activity-stream-element id="1"></bs-activity-stream-element>'); const translation = tu.sourceContentNormalized().translate('New <bs-activity-stream-element></bs-activity-stream-element> was reported by <bs-activity-stream-element id="1"></bs-activity-stream-element>'); tu.translate(translation); TranslationMessagesFileReader.save(langFileEnglish); // look, that the new file contains the translation langFileEnglish = readXliff(xliffMergeCmd.generatedI18nFile('en')); expect(langFileEnglish.transUnitWithId(ID_2_CUSTOM_TAGS).targetContent()).toBe('New <x id="START_TAG_BS-ACTIVITY-STREAM-ELEMENT" ctype="x-bs-activity-stream-element" equiv-text="&lt;bs-activity-stream-element>"/><x id="CLOSE_TAG_BS-ACTIVITY-STREAM-ELEMENT" ctype="x-bs-activity-stream-element"/> was reported by <x id="START_TAG_BS-ACTIVITY-STREAM-ELEMENT_1" ctype="x-bs-activity-stream-element" equiv-text="&lt;bs-activity-stream-element>"/><x id="CLOSE_TAG_BS-ACTIVITY-STREAM-ELEMENT" ctype="x-bs-activity-stream-element"/>'); expect(langFileEnglish.transUnitWithId(ID_2_CUSTOM_TAGS).targetContentNormalized().asDisplayString()).toBe('New <bs-activity-stream-element></bs-activity-stream-element> was reported by <bs-activity-stream-element id="1"></bs-activity-stream-element>'); done(); }); it('should translate ICU message with placeholder (#83)', (done) => { const ID_ICU_WITH_PLACEHOLDER = '8856d298b6fa89a339475c5d5cd20f2d2afcfbf8'; FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); // now translate some texts in the English version let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tu: ITransUnit = langFileEnglish.transUnitWithId(ID_ICU_WITH_PLACEHOLDER); expect(tu).toBeTruthy(); expect(tu.sourceContentNormalized().asDisplayString()).toBe('<ICU-Message/>'); const t = '{VAR_PLURAL, plural, =1 {Crash <x id="INTERPOLATION" equiv-text="{{ a }}"/> was} other {Crashes <x id="INTERPOLATION" equiv-text="{{ a}}"/> were} }'; const translation = tu.sourceContentNormalized().translateICUMessage({"=1": 'Crash <x id="INTERPOLATION" equiv-text="{{ a }}"/> was'}); tu.translate(translation); TranslationMessagesFileReader.save(langFileEnglish); // look, that the new file contains the translation langFileEnglish = readXliff(xliffMergeCmd.generatedI18nFile('en')); expect(langFileEnglish.transUnitWithId(ID_ICU_WITH_PLACEHOLDER).targetContentNormalized().asDisplayString()).toBe('<ICU-Message/>'); expect(langFileEnglish.transUnitWithId(ID_ICU_WITH_PLACEHOLDER).targetContentNormalized().getICUMessage().asNativeString()).toContain('Crash'); done(); }); it('allowIdChange feature, should merge only white space changed content with changed ID to already translated files (#65)', (done) => { const ID_ORIGINAL = 'originalId'; const ID_CHANGED = 'changedId'; const TRANSLATED_FILE = 'WithIdChange.en.xlf'; FileUtil.copy(SRCDIR + 'ngExtractedMasterWithIdChange.xlf', MASTER); FileUtil.copy(SRCDIR + TRANSLATED_FILE,WORKDIR + 'messages.en.xlf'); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, allowIdChange: true } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en'], verbose: true}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); expect(ws.writtenData()).toContain('WARNING: found 2 changed id\'s in "en"'); // check that changed id is merged let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tuChanged: ITransUnit = langFileEnglish.transUnitWithId(ID_CHANGED); expect(tuChanged).toBeTruthy(); expect(tuChanged.sourceContent().trim()).toBe('Test kleine Änderung, nur white spaces!'); expect(tuChanged.targetState()).toBe(STATE_TRANSLATED); expect(tuChanged.targetContent()).toBe('Test for a small white space change'); let tuOriginal: ITransUnit = langFileEnglish.transUnitWithId(ID_ORIGINAL); expect(tuOriginal).toBeFalsy(); done(); }); it('allowIdChange feature, should merge untranslated only white space changed content with changed ID, but should set preserve state "new" (#68)', (done) => { const ID_ORIGINAL = 'originalIdUntranslated'; const ID_CHANGED = 'changedIdUntranslated'; const TRANSLATED_FILE = 'WithIdChange.en.xlf'; FileUtil.copy(SRCDIR + 'ngExtractedMasterWithIdChange.xlf', MASTER); FileUtil.copy(SRCDIR + TRANSLATED_FILE,WORKDIR + 'messages.en.xlf'); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, allowIdChange: true } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en'], verbose: true}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); expect(ws.writtenData()).toContain('WARNING: found 2 changed id\'s in "en"'); // check that changed id is merged let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tuChanged: ITransUnit = langFileEnglish.transUnitWithId(ID_CHANGED); expect(tuChanged).toBeTruthy(); expect(tuChanged.sourceContent().trim()).toBe('Unübersetzt'); expect(tuChanged.targetState()).toBe(STATE_NEW); expect(tuChanged.targetContent()).toBe(''); // not translated let tuOriginal: ITransUnit = langFileEnglish.transUnitWithId(ID_ORIGINAL); expect(tuOriginal).toBeFalsy(); done(); }); it('should report an error with filename when there is something wrong', (done) => { FileUtil.copy(SRCDIR + 'schrott.xlf', MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); // we activate ngxtranslate support, so that the wrong close tag in schrott produces an error let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, supportNgxTranslate: true } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); try { xliffMergeCmd.run(); } catch (e) {} expect(ws.writtenData()).toContain('ERROR'); expect(ws.writtenData()).toContain('messages.de.xlf'); done(); }); it('should use beautify when requested (#64, #88)', (done) => { FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); const unformattedContent = XmlReader.readXmlFileContent(xliffMergeCmd.generatedI18nFile('en')); expect(unformattedContent.content).toContain('<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">'); // to debug formatting: FileUtil.copy(xliffMergeCmd.generatedI18nFile('en'), SRCDIR + 'nobeautify'); // next step, same with beautify FileUtil.deleteFile(xliffMergeCmd.generatedI18nFile('en')); ws = new WriterToString(); commandOut = new CommandOutput(ws); profileContent = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, beautifyOutput: true } }; xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); const formattedContent = XmlReader.readXmlFileContent(xliffMergeCmd.generatedI18nFile('en')); expect(formattedContent.content).toContain('\n <source>Nachrichten</source>'); // to debug formatting: FileUtil.copy(xliffMergeCmd.generatedI18nFile('en'), SRCDIR + 'beautify'); done(); }); it('should not add empty lines when using beautify whith complex content (#97)', (done) => { FileUtil.copy(SRCDIR + 'issue97emptylines.xlf', MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'en', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, beautifyOutput: true } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['en', 'ru']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); const formattedContent1 = XmlReader.readXmlFileContent(xliffMergeCmd.generatedI18nFile('ru')); // to debug formatting: FileUtil.copy(xliffMergeCmd.generatedI18nFile('ru'), SRCDIR + 'beautify1'); // next step, once again with beautify ws = new WriterToString(); commandOut = new CommandOutput(ws); profileContent = { xliffmergeOptions: { defaultLanguage: 'en', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, beautifyOutput: true } }; xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['en', 'ru']}, profileContent); xliffMergeCmd.run(); expect(ws.writtenData()).not.toContain('ERROR'); const formattedContent2 = XmlReader.readXmlFileContent(xliffMergeCmd.generatedI18nFile('ru')); expect(formattedContent2.content).toMatch(/I accept the\r?\n[ \t]*<x/); expect(formattedContent2.content).not.toMatch(/I accept the\r?\n[ \t]*\r?\n[ \t]*<x/); // to debug formatting: FileUtil.copy(xliffMergeCmd.generatedI18nFile('ru'), SRCDIR + 'beautify2'); done(); }); describe('autotranslate via google translate', () => { let apikey: string; const ID_NACHRICHTEN = '57e605bfa130afb4de4ee40e496e854a9e8a28a7'; const ID_BESCHREIBUNG_WITH_PLACEHOLDER = 'a52ba049c16778bdb2e5a19a41acaadf87b104dc'; const ID_ICUMESSAGE = 'efec69fdcf74bd6d640b2a771558b7b09e271c28'; beforeEach(() => { apikey = getApiKey(); }); it('should detect invalid key', (done) => { if (!apikey) { // skip test done(); return; } FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, autotranslate: true, apikey: 'lmaa' } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run((retcode) => { expect(ws.writtenData()).toContain('API key not valid'); done(); }); }); it('should auto translate file', (done) => { if (!apikey) { // skip test done(); return; } FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, autotranslate: true, apikey: apikey } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(() => { expect(ws.writtenData()).not.toContain('ERROR'); let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); expect(langFileEnglish.targetLanguage()).toBe('en'); let tu = langFileEnglish.transUnitWithId(ID_NACHRICHTEN); expect(tu.sourceContent()).toBe('Nachrichten'); expect(tu.targetContent()).toBe('news'); expect(tu.targetState()).toBe('translated'); let tuICU = langFileEnglish.transUnitWithId(ID_ICUMESSAGE); expect(tuICU.sourceContent()).toContain('VAR_PLURAL'); expect(tuICU.targetContent()).toBe('{VAR_PLURAL, plural, =0 {no sheep} =1 {1 sheep} other {x sheep}}'); expect(tuICU.targetState()).toBe('translated'); done(); }); }); it('should auto translate file with region code (which will be ignored)', (done) => { if (!apikey) { // skip test done(); return; } FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de-de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, autotranslate: true, apikey: apikey } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de-de', 'en-us']}, profileContent); xliffMergeCmd.run((retcode) => { expect(ws.writtenData()).not.toContain('ERROR'); let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en-us')); expect(langFileEnglish).toBeTruthy(); if (langFileEnglish) { expect(langFileEnglish.targetLanguage()).toBe('en-us'); let tu = langFileEnglish.transUnitWithId(ID_BESCHREIBUNG_WITH_PLACEHOLDER); expect(tu.sourceContentNormalized().asDisplayString()).toBe('Beschreibung zu {{0}} ({{1}})'); expect(tu.targetContentNormalized().asDisplayString()).toBe('Description of {{0}} ({{1}})'); expect(tu.targetState()).toBe('translated'); } done(); }); }); it('should detect unsupported language when using auto translate', (done) => { if (!apikey) { // skip test done(); return; } FileUtil.copy(MASTER1SRC, MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', languages: ['de', 'xy', 'en'], srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, autotranslate: true, apikey: apikey } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {verbose: true}, profileContent); xliffMergeCmd.run((retcode) => { expect(ws.writtenData()).toContain('ERROR'); expect(ws.writtenData()).toContain('"xy" not supported'); let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); expect(langFileEnglish).toBeTruthy(); if (langFileEnglish) { expect(langFileEnglish.targetLanguage()).toBe('en'); let tu = langFileEnglish.transUnitWithId(ID_BESCHREIBUNG_WITH_PLACEHOLDER); expect(tu.sourceContentNormalized().asDisplayString()).toBe('Beschreibung zu {{0}} ({{1}})'); expect(tu.targetContentNormalized().asDisplayString()).toBe('Description of {{0}} ({{1}})'); expect(tu.targetState()).toBe('translated'); } let langFileXy: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('xy')); expect(langFileXy).toBeTruthy(); if (langFileXy) { expect(langFileXy.targetLanguage()).toBe('xy'); let tu = langFileXy.transUnitWithId(ID_BESCHREIBUNG_WITH_PLACEHOLDER); expect(tu.sourceContentNormalized().asDisplayString()).toBe('Beschreibung zu {{0}} ({{1}})'); expect(tu.targetContentNormalized().asDisplayString()).toBe('Beschreibung zu {{0}} ({{1}})'); expect(tu.targetState()).toBe('new'); } done(); }); }); it('should auto translate text containing linefeeds (originated by #78)', (done) => { if (!apikey) { // skip test done(); return; } FileUtil.copy(SRCDIR + 'autotranslateMaster1.xlf', MASTER); let ws: WriterToString = new WriterToString(); let commandOut = new CommandOutput(ws); let profileContent: IConfigFile = { xliffmergeOptions: { defaultLanguage: 'de', srcDir: WORKDIR, genDir: WORKDIR, i18nFile: MASTERFILE, autotranslate: true, apikey: apikey } }; let xliffMergeCmd = XliffMerge.createFromOptions(commandOut, {languages: ['de', 'en']}, profileContent); xliffMergeCmd.run(() => { expect(ws.writtenData()).not.toContain('ERROR'); let langFileEnglish: ITranslationMessagesFile = readXliff(xliffMergeCmd.generatedI18nFile('en')); let tu1 = langFileEnglish.transUnitWithId('totallyEmptyline1'); expect(tu1.sourceContent().trim()).toBe(''); expect(tu1.targetContent()).toBe(''); expect(tu1.targetState()).toBe('translated'); let tu2 = langFileEnglish.transUnitWithId('nearlytotallyEmptyline'); expect(tu2.sourceContent()).toContain('START_BOLD_TEXT'); // translation is nearly equal to source, because there is no translatable text expect(tu2.targetContent().trim()).toEqual(tu2.sourceContent().trim()); expect(tu2.targetState()).toBe('translated'); let tu3 = langFileEnglish.transUnitWithId('emptylineText'); expect(tu3.sourceContent()).toContain('Text mit Zeilenumbruch'); expect(tu3.targetContent()).toBe('Text with line break'); expect(tu3.targetState()).toBe('translated'); done(); }); }); it('should auto translate english text to french containing apostrophes (originated by #94)', (done) => { if (!apikey) {