import { Routes , Route , NavigationExtras , Params } from '@angular/router' ; import { TranslateService } from '@ngx-translate/core' ; import { Observable , Observer } from 'rxjs' ; import { Location } from '@angular/common' ; import { CacheMechanism , LocalizeRouterSettings } from './localize-router.config' ; import { Inject , Injectable } from '@angular/core' ; import { HttpParams } from '@angular/common/http' ; const COOKIE_EXPIRY = 30 ; /**
 * Abstract class for parsing localization
 */ @ Injectable ( ) export abstract class LocalizeParser { locales : Array < string > ; currentLang : string ; routes : Routes ; defaultLang : string ; protected prefix : string ; protected escapePrefix : string ; private _translationObject : any ; private _wildcardRoute : Route ; private _languageRoute : Route ; /**
     * Loader constructor
     */ constructor ( @ Inject ( TranslateService ) private translate , @ Inject ( Location ) private location , @ Inject ( LocalizeRouterSettings ) private settings ) { } /**
     * Load routes and fetch necessary data
     */ abstract load ( routes ) ; param routes /**
     * Initialize language and routes
     */ protected init ( routes ) { let selectedLanguage ; this . routes = routes ; if ( ! this . locales || ! this . locales . length ) { return Promise . resolve ( ) ; } /** detect current language */ const locationLang = this . getLocationLang ( ) ; const browserLang = this . _getBrowserLang ( ) ; if ( this . settings . defaultLangFunction ) { this . defaultLang = this . settings . defaultLangFunction ( this . locales , this . _cachedLang , browserLang ) ; } else { this . defaultLang = this . _cachedLang || browserLang || this . locales [ 0 ] ; } selectedLanguage = locationLang || this . defaultLang ; this . translate . setDefaultLang ( this . defaultLang ) ; let children = [ ] ; /** if set prefix is enforced */ if ( this . settings . alwaysSetPrefix ) { const baseRoute = { path : '' , redirectTo : this . defaultLang , pathMatch : 'full' } ; /** extract potential wildcard route */ const wildcardIndex = routes . findIndex ( ( route ) => route . path === '**' ) ; if ( wildcardIndex !== - 1 ) { this . _wildcardRoute = routes . splice ( wildcardIndex , 1 ) [ 0 ] ; } children = this . routes . splice ( 0 , this . routes . length , baseRoute ) ; } else { children = [ ... this . routes ] ; } /** exclude certain routes */ for ( let i = children . length - 1 ; i >= 0 ; i -- ) { if ( children [ i ] . data && children [ i ] . data [ 'skipRouteLocalization' ] ) { if ( this . settings . alwaysSetPrefix ) { this . routes . push ( children [ i ] ) ; } if ( children [ i ] . redirectTo === undefined || ! ( children [ i ] . data [ 'skipRouteLocalization' ] [ 'localizeRedirectTo' ] ) ) { children . splice ( i , 1 ) ; } } } /** append children routes */ if ( children && children . length ) { if ( this . locales . length > 1 || this . settings . alwaysSetPrefix ) { this . _languageRoute = { children : children } ; this . routes . unshift ( this . _languageRoute ) ; } } /** ...and potential wildcard route */ if ( this . _wildcardRoute && this . settings . alwaysSetPrefix ) { this . routes . push ( this . _wildcardRoute ) ; } /** translate routes */ const res = this . translateRoutes ( selectedLanguage ) ; return res . toPromise ( ) ; } initChildRoutes ( routes ) { this . _translateRouteTree ( routes ) ; return routes ; } /**
     * Translate routes to selected language
     */ translateRoutes ( language ) { return new Observable < any > ( ( observer ) => { this . _cachedLang = language ; if ( this . _languageRoute ) { this . _languageRoute . path = language ; } this . translate . use ( language ) . subscribe ( ( translations ) => { this . _translationObject = translations ; this . currentLang = language ; if ( this . _languageRoute ) { if ( this . _languageRoute ) { this . _translateRouteTree ( this . _languageRoute . children ) ; } if ( this . _wildcardRoute && this . _wildcardRoute . redirectTo ) { this . _translateProperty ( this . _wildcardRoute , 'redirectTo' , true ) ; } } else { this . _translateRouteTree ( this . routes ) ; } observer . next ( void 0 ) ; observer . complete ( ) ; } ) ; } ) ; } /**
     * Translate the route node and recursively call for all it's children
     */ private _translateRouteTree ( routes ) { routes . forEach ( ( route ) => { const skipRouteLocalization = ( route . data && route . data [ 'skipRouteLocalization' ] ) ; const localizeRedirection = ! skipRouteLocalization || skipRouteLocalization [ 'localizeRedirectTo' ] ; if ( route . redirectTo && localizeRedirection ) { this . _translateProperty ( route , 'redirectTo' , ! route . redirectTo . indexOf ( '/' ) ) ; } if ( ! skipRouteLocalization ) { if ( route . path !== null && route . path !== undefined ) { this . _translateProperty ( route , 'path' ) ; } if ( route . children ) { this . _translateRouteTree ( route . children ) ; } if ( route . loadChildren && ( < any > route ) . _loadedConfig ) { this . _translateRouteTree ( ( < any > route ) . _loadedConfig . routes ) ; } } } ) ; } /**
     * Translate property
     * If first time translation then add original to route data object
     */ private _translateProperty ( route , property , prefixLang ? ) { const routeData = route . data = route . data || { } ; if ( ! routeData . localizeRouter ) { routeData . localizeRouter = { } ; } if ( ! routeData . localizeRouter [ property ] ) { routeData . localizeRouter = { ... routeData . localizeRouter , [ property ] : route [ property ] } ; } const result = this . translateRoute ( routeData . localizeRouter [ property ] ) ; ( < any > route ) [ property ] = prefixLang ? this . addPrefixToUrl ( result ) : result ; } get urlPrefix ( ) { if ( this . settings . alwaysSetPrefix || this . currentLang !== this . defaultLang ) { return this . currentLang ? this . currentLang : this . defaultLang ; } else { return '' ; } } /**
     * Add current lang as prefix to given url.
     */ addPrefixToUrl ( url ) { const plitedUrl = url . split ( '?' ) ; plitedUrl [ 0 ] = plitedUrl [ 0 ] . replace ( /\/$/ , '' ) ; return `/${ this . urlPrefix }${ plitedUrl . join ( '?' ) }` ; } /**
     * Translate route and return observable
     */ translateRoute ( path ) { const queryParts = path . split ( '?' ) ; if ( queryParts . length > 2 ) { throw Error ( 'There should be only one query parameter block in the URL' ) ; } const pathSegments = queryParts [ 0 ] . split ( '/' ) ; /** collect observables  */ return pathSegments . map ( ( part ) => part . length ? this . translateText ( part ) : part ) . join ( '/' ) + ( queryParts . length > 1 ? `?${ queryParts [ 1 ] }` : '' ) ; } /**
     * Get language from url
     */ getLocationLang ( url ? ) { const queryParamSplit = ( url || this . location . path ( ) ) . split ( /[\?;]/ ) ; let pathSlices = [ ] ; if ( queryParamSplit . length > 0 ) { pathSlices = queryParamSplit [ 0 ] . split ( '/' ) ; } if ( pathSlices . length > 1 && this . locales . indexOf ( pathSlices [ 1 ] ) !== - 1 ) { return pathSlices [ 1 ] ; } if ( pathSlices . length && this . locales . indexOf ( pathSlices [ 0 ] ) !== - 1 ) { return pathSlices [ 0 ] ; } return null ; } /**
     * Get user's language set in the browser
     */ private _getBrowserLang ( ) { return this . _returnIfInLocales ( this . translate . getBrowserLang ( ) ) ; } /**
     * Get language from local storage or cookie
     */ private get _cachedLang ( ) : string { if ( ! this . settings . useCachedLang ) { return ; } if ( this . settings . cacheMechanism === CacheMechanism . LocalStorage ) { return this . _cacheWithLocalStorage ( ) ; } if ( this . settings . cacheMechanism === CacheMechanism . SessionStorage ) { return this . _cacheWithSessionStorage ( ) ; } if ( this . settings . cacheMechanism === CacheMechanism . Cookie ) { return this . _cacheWithCookies ( ) ; } } /**
     * Save language to local storage or cookie
     */ private set _cachedLang ( value ) { if ( ! this . settings . useCachedLang ) { return ; } if ( this . settings . cacheMechanism === CacheMechanism . LocalStorage ) { this . _cacheWithLocalStorage ( value ) ; } if ( this . settings . cacheMechanism === CacheMechanism . SessionStorage ) { this . _cacheWithSessionStorage ( value ) ; } if ( this . settings . cacheMechanism === CacheMechanism . Cookie ) { this . _cacheWithCookies ( value ) ; } } /**
     * Cache value to local storage
     */ private _cacheWithLocalStorage ( value ? ) { try { if ( typeof window === 'undefined' || typeof window . localStorage === 'undefined' ) { return ; } if ( value ) { window . localStorage . setItem ( this . settings . cacheName , value ) ; return ; } return this . _returnIfInLocales ( window . localStorage . getItem ( this . settings . cacheName ) ) ; } catch ( e ) { return ; } } /**
     * Cache value to session storage
     */ private _cacheWithSessionStorage ( value ? ) { try { if ( typeof window === 'undefined' || typeof window . sessionStorage === 'undefined' ) { return ; } if ( value ) { window . sessionStorage . setItem ( this . settings . cacheName , value ) ; return ; } return this . _returnIfInLocales ( window . sessionStorage . getItem ( this . settings . cacheName ) ) ; } catch ( e ) { return ; } } /**
     * Cache value via cookies
     */ private _cacheWithCookies ( value ? ) { try { if ( typeof document === 'undefined' || typeof document . cookie === 'undefined' ) { return ; } const name = encodeURIComponent ( this . settings . cacheName ) ; if ( value ) { let cookieTemplate = `${ this . settings . cookieFormat }` ; cookieTemplate = cookieTemplate . replace ( '{{value}}' , `${ name }=${ encodeURIComponent ( value ) }` ) . replace ( /{{expires:?(\d+)?}}/g , ( fullMatch , groupMatch ) => { const days = groupMatch === undefined ? COOKIE_EXPIRY : parseInt ( groupMatch , 10 ) ; const date = new Date ( ) ; date . setTime ( date . getTime ( ) + days * 86400000 ) ; return `expires=${ date . toUTCString ( ) }` ; } ) ; document . cookie = cookieTemplate ; return ; } const regexp = new RegExp ( '(?:^' + name + '|;\\s*' + name + ')=(.*?)(?:;|$)' , 'g' ) ; const result = regexp . exec ( document . cookie ) ; return decodeURIComponent ( result [ 1 ] ) ; } catch ( e ) { return ; } } /**
     * Check if value exists in locales list
     */ private _returnIfInLocales ( value ) { if ( value && this . locales . indexOf ( value ) !== - 1 ) { return value ; } return null ; } /**
     * Get translated value
     */ private translateText ( key ) { if ( this . escapePrefix && key . startsWith ( this . escapePrefix ) ) { return key . replace ( this . escapePrefix , '' ) ; } else { if ( ! this . _translationObject ) { return key ; } const fullKey = this . prefix + key ; const res = this . translate . getParsedResult ( this . _translationObject , fullKey ) ; return res !== fullKey ? res : key ; } } param newExtras param currentQueryParams public chooseQueryParams ( newExtras , currentQueryParams ) { let queryParamsObj ; if ( newExtras && newExtras . queryParams ) { queryParamsObj = newExtras . queryParams ; } else if ( currentQueryParams ) { queryParamsObj = currentQueryParams ; } return queryParamsObj ; } param params public formatQueryParams ( params ) { return new HttpParams ( { fromObject : params } ) . toString ( ) ; } /**
     * Get translation key prefix from config
     */ public getPrefix ( ) { return this . prefix ; } /**
     * Get escape translation prefix from config
     */ public getEscapePrefix ( ) { return this . escapePrefix ; } } /**
 * Manually set configuration
 */ export class ManualParserLoader extends LocalizeParser { /**
     * CTOR
     */ constructor ( translate , location , settings , locales = [ 'en' ] , prefix = 'ROUTES.' , escapePrefix = '' ) { super ( translate , location , settings ) ; this . locales = locales ; this . prefix = prefix || '' ; this . escapePrefix = escapePrefix || '' ; } /**
     * Initialize or append routes
     */ load ( routes ) { return new Promise ( ( resolve ) => { this . init ( routes ) . then ( resolve ) ; } ) ; } } @ Injectable ( ) export class DummyLocalizeParser extends LocalizeParser { load ( routes ) { return new Promise ( ( resolve ) => { this . init ( routes ) . then ( resolve ) ; } ) ; } }