import { STATE_NEW , STATE_TRANSLATED , STATE_FINAL } from '../api/constants' ; import { ITranslationMessagesFile } from '../api/i-translation-messages-file' ; import { INormalizedMessage } from '../api/i-normalized-message' ; import { ITransUnit } from '../api/i-trans-unit' ; import { INote } from '../api/i-note' ; import { DOMUtilities } from './dom-utilities' ; import { AbstractTransUnit } from './abstract-trans-unit' ; import { XliffMessageParser } from './xliff-message-parser' ; import { ParsedMessage } from './parsed-message' ; import { AbstractMessageParser } from './abstract-message-parser' ; import { isNullOrUndefined } from 'util' ; /**
 * Created by martin on 01.05.2017.
 * A Translation Unit in an XLIFF 1.2 file.
 */ export class XliffTransUnit extends AbstractTransUnit implements ITransUnit { constructor ( _element , _id , _translationMessagesFile ) { super ( _element , _id , _translationMessagesFile ) ; } public sourceContent ( ) { const sourceElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; return DOMUtilities . getXMLContent ( sourceElement ) ; } param newContent public setSourceContent ( newContent ) { let source = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; if ( ! source ) { source = this . _element . appendChild ( this . _element . ownerDocument . createElement ( 'source' ) ) ; } DOMUtilities . replaceContentWithXMLContent ( source , newContent ) ; } /**
     * Return a parser used for normalized messages.
     */ protected messageParser ( ) { return new XliffMessageParser ( ) ; } /**
     * The original text value, that is to be translated, as normalized message.
     */ public createSourceContentNormalized ( ) { const sourceElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; if ( sourceElement ) { return this . messageParser ( ) . createNormalizedMessageFromXML ( sourceElement , null ) ; } else { return null ; } } /**
     * the translated value (containing all markup, depends on the concrete format used).
     */ public targetContent ( ) { const targetElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; return DOMUtilities . getXMLContent ( targetElement ) ; } /**
     * the translated value, but all placeholders are replaced with {{n}} (starting at 0)
     * and all embedded html is replaced by direct html markup.
     */ targetContentNormalized ( ) { const targetElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; return new XliffMessageParser ( ) . createNormalizedMessageFromXML ( targetElement , this . sourceContentNormalized ( ) ) ; } /**
     * State of the translation as stored in the xml.
     */ public nativeTargetState ( ) { const targetElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; if ( targetElement ) { return targetElement . getAttribute ( 'state' ) ; } else { return null ; } } param nativeState protected setNativeTargetState ( nativeState ) { const targetElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; if ( targetElement ) { targetElement . setAttribute ( 'state' , nativeState ) ; } } param state returns throws protected mapStateToNativeState ( state ) { switch ( state ) { case STATE_NEW : return 'new' ; case STATE_TRANSLATED : return 'translated' ; case STATE_FINAL : return 'final' ; default : throw new Error ( 'unknown state ' + state ) ; } } param nativeState protected mapNativeStateToState ( nativeState ) { switch ( nativeState ) { case 'new' : return STATE_NEW ; case 'needs-translation' : return STATE_NEW ; case 'translated' : return STATE_TRANSLATED ; case 'needs-adaptation' : return STATE_TRANSLATED ; case 'needs-l10n' : return STATE_TRANSLATED ; case 'needs-review-adaptation' : return STATE_TRANSLATED ; case 'needs-review-l10n' : return STATE_TRANSLATED ; case 'needs-review-translation' : return STATE_TRANSLATED ; case 'final' : return STATE_FINAL ; case 'signed-off' : return STATE_FINAL ; default : return STATE_NEW ; } } /**
     * All the source elements in the trans unit.
     * The source element is a reference to the original template.
     * It contains the name of the template file and a line number with the position inside the template.
     * It is just a help for translators to find the context for the translation.
     * This is set when using Angular 4.0 or greater.
     * Otherwise it just returns an empty array.
     */ public sourceReferences ( ) { const sourceElements = this . _element . getElementsByTagName ( 'context-group' ) ; const sourceRefs = [ ] ; for ( let i = 0 ; i < sourceElements . length ; i ++ ) { const elem = sourceElements . item ( i ) ; if ( elem . getAttribute ( 'purpose' ) === 'location' ) { const contextElements = elem . getElementsByTagName ( 'context' ) ; let sourcefile = null ; let linenumber = 0 ; for ( let j = 0 ; j < contextElements . length ; j ++ ) { const contextElem = contextElements . item ( j ) ; if ( contextElem . getAttribute ( 'context-type' ) === 'sourcefile' ) { sourcefile = DOMUtilities . getPCDATA ( contextElem ) ; } if ( contextElem . getAttribute ( 'context-type' ) === 'linenumber' ) { linenumber = Number . parseInt ( DOMUtilities . getPCDATA ( contextElem ) , 10 ) ; } } sourceRefs . push ( { sourcefile : sourcefile , linenumber : linenumber } ) ; } } return sourceRefs ; } param sourceRefs public setSourceReferences ( sourceRefs ) { this . removeAllSourceReferences ( ) ; sourceRefs . forEach ( ( ref ) => { const contextGroup = this . _element . ownerDocument . createElement ( 'context-group' ) ; contextGroup . setAttribute ( 'purpose' , 'location' ) ; const contextSource = this . _element . ownerDocument . createElement ( 'context' ) ; contextSource . setAttribute ( 'context-type' , 'sourcefile' ) ; contextSource . appendChild ( this . _element . ownerDocument . createTextNode ( ref . sourcefile ) ) ; const contextLine = this . _element . ownerDocument . createElement ( 'context' ) ; contextLine . setAttribute ( 'context-type' , 'linenumber' ) ; contextLine . appendChild ( this . _element . ownerDocument . createTextNode ( ref . linenumber . toString ( 10 ) ) ) ; contextGroup . appendChild ( contextSource ) ; contextGroup . appendChild ( contextLine ) ; this . _element . appendChild ( contextGroup ) ; } ) ; } private removeAllSourceReferences ( ) { const sourceElements = this . _element . getElementsByTagName ( 'context-group' ) ; const toBeRemoved = [ ] ; for ( let i = 0 ; i < sourceElements . length ; i ++ ) { const elem = sourceElements . item ( i ) ; if ( elem . getAttribute ( 'purpose' ) === 'location' ) { toBeRemoved . push ( elem ) ; } } toBeRemoved . forEach ( ( elem ) => { elem . parentNode . removeChild ( elem ) ; } ) ; } /**
     * The description set in the template as value of the i18n-attribute.
     * e.g. i18n="mydescription".
     * In xliff this is stored as a note element with attribute from="description".
     */ public description ( ) { const noteElem = this . findNoteElementWithFromAttribute ( 'description' ) ; if ( noteElem ) { return DOMUtilities . getPCDATA ( noteElem ) ; } else { return null ; } } param description public setDescription ( description ) { let noteElem = this . findNoteElementWithFromAttribute ( 'description' ) ; if ( description ) { if ( isNullOrUndefined ( noteElem ) ) { noteElem = this . createNoteElementWithFromAttribute ( 'description' , description ) ; } else { DOMUtilities . replaceContentWithXMLContent ( noteElem , description ) ; } } else { if ( ! isNullOrUndefined ( noteElem ) ) { this . removeNoteElementWithFromAttribute ( 'description' ) ; } } } param attrValue return private findNoteElementWithFromAttribute ( attrValue ) { const noteElements = this . _element . getElementsByTagName ( 'note' ) ; for ( let i = 0 ; i < noteElements . length ; i ++ ) { const noteElem = noteElements . item ( i ) ; if ( noteElem . getAttribute ( 'from' ) === attrValue ) { return noteElem ; } } return null ; } return private findAllAdditionalNoteElements ( ) { const noteElements = this . _element . getElementsByTagName ( 'note' ) ; const result = [ ] ; for ( let i = 0 ; i < noteElements . length ; i ++ ) { const noteElem = noteElements . item ( i ) ; const fromAttribute = noteElem . getAttribute ( 'from' ) ; if ( fromAttribute !== 'description' && fromAttribute !== 'meaning' ) { result . push ( noteElem ) ; } } return result ; } param fromAttrValue param content return private createNoteElementWithFromAttribute ( fromAttrValue , content ) { const noteElement = this . _element . ownerDocument . createElement ( 'note' ) ; if ( fromAttrValue ) { noteElement . setAttribute ( 'from' , fromAttrValue ) ; } noteElement . setAttribute ( 'priority' , '1' ) ; if ( content ) { DOMUtilities . replaceContentWithXMLContent ( noteElement , content ) ; } this . _element . appendChild ( noteElement ) ; return noteElement ; } param attrValue private removeNoteElementWithFromAttribute ( attrValue ) { const noteElement = this . findNoteElementWithFromAttribute ( attrValue ) ; if ( noteElement ) { this . _element . removeChild ( noteElement ) ; } } /**
     * Remove all note elements where attribute "from" is not description or meaning.
     */ private removeAllAdditionalNoteElements ( ) { const noteElements = this . findAllAdditionalNoteElements ( ) ; noteElements . forEach ( ( noteElement ) => { this . _element . removeChild ( noteElement ) ; } ) ; } /**
     * The meaning (intent) set in the template as value of the i18n-attribute.
     * This is the part in front of the | symbol.
     * e.g. i18n="meaning|mydescription".
     * In xliff this is stored as a note element with attribute from="meaning".
     */ public meaning ( ) { const noteElem = this . findNoteElementWithFromAttribute ( 'meaning' ) ; if ( noteElem ) { return DOMUtilities . getPCDATA ( noteElem ) ; } else { return null ; } } param meaning public setMeaning ( meaning ) { let noteElem = this . findNoteElementWithFromAttribute ( 'meaning' ) ; if ( meaning ) { if ( isNullOrUndefined ( noteElem ) ) { noteElem = this . createNoteElementWithFromAttribute ( 'meaning' , meaning ) ; } else { DOMUtilities . replaceContentWithXMLContent ( noteElem , meaning ) ; } } else { if ( ! isNullOrUndefined ( noteElem ) ) { this . removeNoteElementWithFromAttribute ( 'meaning' ) ; } } } /**
     * Get all notes of the trans-unit.
     * Notes are remarks made by a translator.
     * (description and meaning are not included here!)
     */ public notes ( ) { const noteElememts = this . findAllAdditionalNoteElements ( ) ; return noteElememts . map ( elem => { return { from : elem . getAttribute ( 'from' ) , text : DOMUtilities . getPCDATA ( elem ) } ; } ) ; } /**
     * Test, wether setting of notes is supported.
     * If not, setNotes will do nothing.
     * xtb does not support this, all other formats do.
     */ public supportsSetNotes ( ) { return true ; } param newNotes throws public setNotes ( newNotes ) { if ( ! isNullOrUndefined ( newNotes ) ) { this . checkNotes ( newNotes ) ; } this . removeAllAdditionalNoteElements ( ) ; if ( ! isNullOrUndefined ( newNotes ) ) { newNotes . forEach ( ( note ) => { const noteElem = this . createNoteElementWithFromAttribute ( note . from , note . text ) ; } ) ; } } param translation protected translateNative ( translation ) { let target = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; if ( ! target ) { const source = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; target = DOMUtilities . createFollowingSibling ( 'target' , source ) ; } DOMUtilities . replaceContentWithXMLContent ( target , < string > translation ) ; this . setTargetState ( STATE_TRANSLATED ) ; } /**
     * Copy source to target to use it as dummy translation.
     * Returns a changed copy of this trans unit.
     * receiver is not changed.
     * (internal usage only, a client should call importNewTransUnit on ITranslationMessageFile)
     */ public cloneWithSourceAsTarget ( isDefaultLang , copyContent , targetFile ) { const element = < Element > this . _element . cloneNode ( true ) ; const clone = new XliffTransUnit ( element , this . _id , targetFile ) ; clone . useSourceAsTarget ( isDefaultLang , copyContent ) ; return clone ; } /**
     * Copy source to target to use it as dummy translation.
     * (internal usage only, a client should call createTranslationFileForLang on ITranslationMessageFile)
     */ public useSourceAsTarget ( isDefaultLang , copyContent ) { const source = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; let target = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; if ( ! target ) { target = DOMUtilities . createFollowingSibling ( 'target' , source ) ; } if ( isDefaultLang || copyContent ) { const sourceString = DOMUtilities . getXMLContent ( source ) ; let newTargetString = sourceString ; if ( ! this . isICUMessage ( sourceString ) ) { newTargetString = this . translationMessagesFile ( ) . getNewTransUnitTargetPraefix ( ) + sourceString + this . translationMessagesFile ( ) . getNewTransUnitTargetSuffix ( ) ; } DOMUtilities . replaceContentWithXMLContent ( target , newTargetString ) ; } else { DOMUtilities . replaceContentWithXMLContent ( target , '' ) ; } if ( isDefaultLang ) { target . setAttribute ( 'state' , this . mapStateToNativeState ( STATE_FINAL ) ) ; } else { target . setAttribute ( 'state' , this . mapStateToNativeState ( STATE_NEW ) ) ; } } }