import { throwError as observableThrowError , Observable , BehaviorSubject , of , forkJoin } from 'rxjs' ; import { Inject , Injectable } from '@angular/core' ; import { AutoTranslateDisabledReason , AutoTranslateDisabledReasonKey , AutoTranslateServiceAPI , Language } from './auto-translate-service-api' ; import { APP_CONFIG , AppConfig } from '../app.config' ; import { HttpClient } from '@angular/common/http' ; import { isNullOrUndefined } from '../common/util' ; import { catchError , map } from 'rxjs/operators' ; /**
 * Types form google translate api.
 */ interface GetSupportedLanguagesRequest { target : string ; } interface LanguagesResource { language : string ; name : string ; } interface LanguagesListResponse { languages : LanguagesResource [ ] ; } interface TranslateTextRequest { q : string [ ] ; target : string ; source : string ; format ? : string ; model ? : string ; } interface TranslationsResource { detectedSourceLanguage ? : string ; model ? : string ; translatedText : string ; } interface TranslationsListResponse { translations : TranslationsResource [ ] ; } const MAX_SEGMENTS = 128 ; /**
 * Auto Translate Service using Google Translate.
 */ @ Injectable ( ) export class AutoTranslateGoogleService extends AutoTranslateServiceAPI { private _rootUrl : string ; private _apiKey : string ; private failByDesign : boolean ; /**
     * Cache of supported languages.
     */ private _subjects : { [ target ] : BehaviorSubject < Language [ ] > ; } ; /**
     * Reason, that currently disables the API.
     * (no key, invalid key)
     */ private _permanentFailReason : AutoTranslateDisabledReason ; param lang return public static stripRegioncode ( lang ) { if ( isNullOrUndefined ( lang ) ) { return null ; } const langLower = lang . toLowerCase ( ) ; for ( let i = 0 ; i < langLower . length ; i ++ ) { const c = langLower . charAt ( i ) ; if ( c < 'a' || c > 'z' ) { return langLower . substring ( 0 , i ) ; } } return langLower ; } constructor ( @ Inject ( APP_CONFIG ) app_config , private httpClient ) { super ( ) ; this . _rootUrl = app_config . GOOGLETRANSLATE_API_ROOT_URL ; this . setApiKey ( app_config . GOOGLETRANSLATE_API_KEY ) ; this . failByDesign = false ; if ( app_config . GOOGLETRANSLATE_PROVOKE_FAILURES === true ) { this . failByDesign = true ; } } public apiKey ( ) { return this . _apiKey ; } public setApiKey ( key ) { this . _apiKey = key ; this . _permanentFailReason = null ; this . _subjects = { } ; if ( ! this . _apiKey ) { this . _permanentFailReason = { reason : AutoTranslateDisabledReasonKey . NO_KEY } ; } } param source param target public canAutoTranslate ( source , target ) { return this . supportedLanguages ( ) . pipe ( map ( ( languages ) => { const s = AutoTranslateGoogleService . stripRegioncode ( source ) ; const t = AutoTranslateGoogleService . stripRegioncode ( target ) ; if ( ! s || languages . findIndex ( ( lang ) => lang . language === s ) < 0 ) { return false ; } return ( t && languages . findIndex ( ( lang ) => lang . language === t ) >= 0 ) ; } ) ) ; } param source param target return public disabledReason ( source , target ) { return this . supportedLanguages ( ) . pipe ( map ( ( languages ) => { if ( languages . length === 0 ) { return this . _permanentFailReason ; } const s = AutoTranslateGoogleService . stripRegioncode ( source ) ; if ( ! s || languages . findIndex ( ( lang ) => lang . language === s ) < 0 ) { return { reason : AutoTranslateDisabledReasonKey . SOURCE_LANG_NOT_SUPPORTED } ; } const t = AutoTranslateGoogleService . stripRegioncode ( target ) ; if ( ! t || languages . findIndex ( ( lang ) => lang . language === t ) < 0 ) { return { reason : AutoTranslateDisabledReasonKey . TARGET_LANG_NOT_SUPPORTED } ; } return null ; } ) ) ; } param target supportedLanguages ( target ? ) { if ( ! target ) { target = 'en' ; } else { target = AutoTranslateGoogleService . stripRegioncode ( target ) ; } if ( ! this . _subjects [ target ] ) { if ( this . _apiKey ) { this . _permanentFailReason = { reason : AutoTranslateDisabledReasonKey . NO_PROVIDER } ; } else { this . _permanentFailReason = { reason : AutoTranslateDisabledReasonKey . NO_KEY } ; } this . _subjects [ target ] = new BehaviorSubject < Language [ ] > ( [ ] ) ; if ( this . _apiKey ) { const languagesRequestUrl = this . _rootUrl + 'language/translate/v2/languages' + '?key=' + this . _apiKey + '&target=' + target ; this . httpClient . get < { data : LanguagesListResponse ; } > ( languagesRequestUrl ) . pipe ( catchError ( ( error ) => { if ( this . isInvalidApiKeyError ( error ) ) { this . _permanentFailReason = { reason : AutoTranslateDisabledReasonKey . INVALID_KEY } ; } else { this . _permanentFailReason = { reason : AutoTranslateDisabledReasonKey . CONNECT_PROBLEM , details : JSON . stringify ( error . body ) } ; } return [ ] ; } ) , map ( ( response ) => { const result = response . data ; return result . languages ; } ) ) . subscribe ( ( languages ) => { this . _subjects [ target ] . next ( languages ) ; } ) ; } } return this . _subjects [ target ] ; } private isInvalidApiKeyError ( error ) { if ( ! error ) { return false ; } if ( error . status === 400 ) { const body = error . body ; if ( body ) { return JSON . stringify ( body ) . indexOf ( 'API key not valid' ) >= 0 ; } } return false ; } param message param from param to return public translate ( message , from , to ) { if ( ! this . _apiKey ) { return observableThrowError ( 'error, no api key' ) ; } from = AutoTranslateGoogleService . stripRegioncode ( from ) ; to = AutoTranslateGoogleService . stripRegioncode ( to ) ; const translateRequest = { q : [ message ] , target : to , source : from , } ; const realUrl = this . _rootUrl + 'language/translate/v2' + '?key=' + this . _apiKey ; return this . httpClient . post < { data : TranslationsListResponse ; } > ( realUrl , translateRequest ) . pipe ( map ( ( response ) => { const result = response . data ; return result . translations [ 0 ] . translatedText ; } ) ) ; } param messages param from param to return public translateMultipleStrings ( messages , from , to ) { if ( ! this . _apiKey ) { return observableThrowError ( 'error, no api key' ) ; } if ( messages . length === 0 ) { return of ( [ ] ) ; } from = AutoTranslateGoogleService . stripRegioncode ( from ) ; to = AutoTranslateGoogleService . stripRegioncode ( to ) ; const allRequests = this . splitMessagesToGoogleLimit ( messages ) . map ( ( partialMessages ) => { return this . limitedTranslateMultipleStrings ( partialMessages , from , to ) ; } ) ; return forkJoin ( allRequests ) . pipe ( map ( ( allTranslations ) => { let all = [ ] ; for ( let i = 0 ; i < allTranslations . length ; i ++ ) { all = all . concat ( allTranslations [ i ] ) ; } return all ; } ) ) ; } param messages param from param to return private limitedTranslateMultipleStrings ( messages , from , to ) { if ( ! this . _apiKey ) { return observableThrowError ( 'error, no api key' ) ; } from = AutoTranslateGoogleService . stripRegioncode ( from ) ; to = AutoTranslateGoogleService . stripRegioncode ( to ) ; const translateRequest = { q : messages , target : to , source : from , } ; const realUrl = this . _rootUrl + 'language/translate/v2' + '?key=' + this . _apiKey ; return this . httpClient . post < { data : TranslationsListResponse ; } > ( realUrl , translateRequest ) . pipe ( map ( ( response ) => { const result = response . data ; return result . translations . map ( ( translation ) => { if ( this . failByDesign ) { if ( translation . translatedText . indexOf ( '{' ) >= 0 ) { return 'oopsi' ; } if ( translation . translatedText . indexOf ( '<' ) >= 0 ) { return 'oopsala' ; } } return translation . translatedText ; } ) ; } ) ) ; } param messages return private splitMessagesToGoogleLimit ( messages ) { if ( messages . length <= MAX_SEGMENTS ) { return [ messages ] ; } const result = [ ] ; let currentPackage = [ ] ; let packageSize = 0 ; for ( let i = 0 ; i < messages . length ; i ++ ) { currentPackage . push ( messages [ i ] ) ; packageSize ++ ; if ( packageSize >= MAX_SEGMENTS ) { result . push ( currentPackage ) ; currentPackage = [ ] ; packageSize = 0 ; } } if ( currentPackage . length > 0 ) { result . push ( currentPackage ) ; } return result ; } }