{"version":3,"sources":["../../../packages/core/data/localization-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAuB,MAAM,MAAM,CAAC;AAOvD,OAAO,EAAE,QAAQ,EAAqB,MAAM,YAAY,CAAC;AASzD;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACvC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACtB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,qBAAa,mBAAmB,CAAC,CAAC;IAuBX,OAAO,CAAC,OAAO,CAAC;IAtBnC,OAAO,CAAC,MAAM,CAAC,6BAA6B,CAAoB;IAChE,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAmB;IACtD,OAAO,CAAC,MAAM,CAAC,0BAA0B,CAAuB;IAChE,OAAO,CAAC,MAAM,CAAC,eAAe,CAA2B;IACzD,OAAc,qBAAqB,SAA4B;IAC/D,OAAc,wBAAwB,SAA+B;IACrE,OAAc,eAAe,EAAE,QAAQ,EAAE,CAAuD;IAChG,OAAc,gBAAgB,EAAE,QAAQ,EAAE,CAAwD;IAElG,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6C;IAC7E,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,0BAA0B,CAAS;IAE3C,OAAO,CAAC,IAAI,CAAoB;IAChC,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;OAIG;gBACwB,OAAO,CAAC,EAAE,0BAA0B;IAmB/D;;;OAGG;IACH,IAAW,QAAQ,IAAI,SAAS,CAE/B;IAED;;OAEG;IACI,oBAAoB,IAAI,MAAM;IAIrC;;OAEG;IACI,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS;IAI/D;;;OAGG;IACI,UAAU,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAa7C;;OAEG;IACI,sBAAsB,IAAI,GAAG;IAKpC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;;;;OAKG;IACI,WAAW,IAAI,SAAS;IA0C/B;;;OAGG;IACI,qBAAqB,IAAI,UAAU,CAAC,CAAC,CAAC;IAuB7C,OAAO,CAAC,mBAAmB;IAMpB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO;IAUxD,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,kBAAkB;IA0B1B,OAAO,CAAC,sBAAsB;CA2CjC","file":"localization-manager.d.ts","sourcesContent":["import { Observable, of, throwError, zip } from 'rxjs';\r\nimport { AjaxError, AjaxResponse } from 'rxjs/ajax';\r\nimport { catchError, map } from 'rxjs/operators';\r\nimport { LogLevel } from '../diagnostics/log-level';\r\nimport { LogRecord } from '../diagnostics/log-record';\r\nimport { Logging } from '../diagnostics/logging';\r\nimport { Http } from './http';\r\nimport { Language, LanguageInventory } from './language';\r\n\r\n/**\r\n * Interface that contains all the possible localized resources in the json file\r\n */\r\ninterface LocalizedResources<T> {\r\n    Strings: T;\r\n}\r\n\r\n/**\r\n * Options to initialize the localization manager\r\n */\r\nexport interface LocalizationManagerOptions {\r\n    /**\r\n     * The URL path (relative or absolute)\r\n     * to the resources folder containing the json files with the localized assets.\r\n     * defaults to '/assets/strings' if not specified.\r\n     * Ex: '/assets/strings'\r\n     */\r\n    resourcesPath?: string;\r\n\r\n    /**\r\n     * The Azure locale to start localization manager with\r\n     */\r\n    azureLocale?: string;\r\n}\r\n\r\nexport interface LocaleSet {\r\n    /**\r\n     * The locale ID covering all reginal locale.\r\n     */\r\n    id: string;\r\n\r\n    /**\r\n     * The neutral ID used for loading string resources.\r\n     */\r\n    neutral: string;\r\n}\r\n\r\n/**\r\n * Class to retrieve localized resources based on the user locale\r\n * This class lets you load resources from a json file in an\r\n * arbitrary location and determines what locale resources to return\r\n * on the user preference\r\n */\r\nexport class LocalizationManager<T> {\r\n    private static defaultResourcesStringsFolder = 'assets/strings';\r\n    private static resourcesStringsFile = '/strings.json';\r\n    private static resourcesStringsFileFormat = '/{0}/strings.json';\r\n    private static languageManager = new LanguageInventory();\r\n    public static localStorageLocaleKey = 'locale:@msft-sme/shell';\r\n    public static localStorageLocaleSetKey = 'localeSet:@msft-sme/shell';\r\n    public static neutralCultures: Language[] = LocalizationManager.languageManager.neutralCultures;\r\n    public static regionalCultures: Language[] = LocalizationManager.languageManager.regionalCultures;\r\n\r\n    private readonly defaultLocaleId = LocalizationManager.neutralCultures[0].id;\r\n    private resourcesStringFormat: string;\r\n    private resourcesStringDefaultFile: string;\r\n\r\n    private http: Http = new Http();\r\n    private localeIdInternal: LocaleSet;\r\n    private resourcesPath: string;\r\n\r\n    /**\r\n     * Initializes a new instance of the LocalizationManager class that reads the localized assets from\r\n     * the given locations.\r\n     * @param options? The options to initialize the localization manager.\r\n     */\r\n    public constructor(private options?: LocalizationManagerOptions) {\r\n        const resourcesPath = options && options.resourcesPath;\r\n        if (resourcesPath) {\r\n            this.resourcesPath = MsftSme.trimEnd(resourcesPath.trim(), '/');\r\n        } else {\r\n            this.resourcesPath = LocalizationManager.defaultResourcesStringsFolder;\r\n        }\r\n\r\n        const azureLocale = options && options.azureLocale;\r\n        if (azureLocale) {\r\n            const azureLocaleSet = this.normalizeAzureLocaleId(azureLocale);\r\n            if (azureLocaleSet) {\r\n                this.localeIdInternal = azureLocaleSet;\r\n            }\r\n        }\r\n\r\n        this.initializeResources();\r\n    }\r\n\r\n    /**\r\n     * Gets current locale.\r\n     * @return string the locale string.\r\n     */\r\n    public get localeId(): LocaleSet {\r\n        return this.localeIdInternal;\r\n    }\r\n\r\n    /**\r\n     * Gets the navigator language\r\n     */\r\n    public getNavigatorLanguage(): string {\r\n        return navigator.language;\r\n    }\r\n\r\n    /**\r\n     * Creates a LocaleSet from a string\r\n     */\r\n    public createLocaleSet(id: string, neutral?: string): LocaleSet {\r\n        return { id: id, neutral: neutral || id };\r\n    }\r\n\r\n    /**\r\n     * Sets the current locale in persistent storage\r\n     * @param localeId the string representing the locale selected by the user. Ex: 'es' or 'en'\r\n     */\r\n    public saveLocale(localeSet: LocaleSet): void {\r\n        if (!this.checkBothAvailable(localeSet)) {\r\n            throw new Error(`The locale specified, ${localeSet.id} and ${localeSet.neutral} were not recognized.`);\r\n        }\r\n\r\n        this.localeIdInternal = localeSet;\r\n\r\n        MsftSme.LocalStorageHandler.setItem(LocalizationManager.localStorageLocaleSetKey, JSON.stringify(localeSet));\r\n\r\n        MsftSme.LocalStorageHandler.setItem(LocalizationManager.localStorageLocaleKey, localeSet.neutral);\r\n        this.updateDocumentLanguage();\r\n    }\r\n\r\n    /**\r\n     * Updates the lang attribute of the document to reflect the current locale.\r\n     */\r\n    public updateDocumentLanguage(): any {\r\n        const locale = this.getLocaleId();\r\n        document.documentElement.setAttribute('lang', locale.id);\r\n    }\r\n\r\n    /**\r\n     * Ensures Resources are Initialized\r\n     */\r\n    private initializeResources() {\r\n        const self = MsftSme.self();\r\n        const locale = this.getLocaleId();\r\n        self.Resources = <MsftSme.MsftSmeResources>{\r\n            strings: {},\r\n            localeId: locale.neutral,\r\n            localeRegionalId: locale.id\r\n        };\r\n    }\r\n\r\n    /**\r\n     * Gets the current locale.\r\n     * Throughout code, locale code is using <lower case>-<upper case> format which is standard on both Microsoft Edge and Google Chrome.\r\n     * ex) en-US, de-DE, ja-JP and so on.\r\n     * @returns The current locale selected by the user\r\n     */\r\n    public getLocaleId(): LocaleSet {\r\n        if (this.localeIdInternal) {\r\n            return this.localeIdInternal;\r\n        }\r\n\r\n        const storageLocaleSet = MsftSme.LocalStorageHandler.getItem(LocalizationManager.localStorageLocaleSetKey);\r\n        let localeSet: LocaleSet;\r\n        if (storageLocaleSet) {\r\n            localeSet = JSON.parse(storageLocaleSet);\r\n            if (this.checkBothAvailable(localeSet)) {\r\n                return this.localeIdInternal = localeSet;\r\n            }\r\n        }\r\n\r\n        // Now we read the browser locales and if it's supported, then we use that otherwise use default locale\r\n        const navigatorLanguage = this.getNavigatorLanguage();\r\n        if (!navigatorLanguage) {\r\n            return this.localeIdInternal = { id: this.defaultLocaleId, neutral: this.defaultLocaleId };\r\n        }\r\n\r\n        localeSet = { id: navigatorLanguage, neutral: navigatorLanguage };\r\n        if (this.checkBothAvailable(localeSet)) {\r\n            return this.localeIdInternal = localeSet;\r\n        }\r\n\r\n        // Try with the neutral part only\r\n        const neutral = navigatorLanguage.split('-')[0].toLowerCase();\r\n        const found = LocalizationManager.neutralCultures.find(item => item.id.split('-')[0] === neutral);\r\n        if (found) {\r\n            // adjust default string language.\r\n            localeSet.neutral = found.id;\r\n            if (this.checkIdAvailable(localeSet)) {\r\n                return this.localeIdInternal = localeSet;\r\n            }\r\n\r\n            // cannot found the regional code.\r\n            return this.localeIdInternal = { id: found.id, neutral: found.id };\r\n        }\r\n\r\n        return this.localeIdInternal = { id: this.defaultLocaleId, neutral: this.defaultLocaleId };\r\n    }\r\n\r\n    /**\r\n     * Fetches the localized strings from the server based on the current culture.\r\n     * @returns an observable with object the localized strings.\r\n     */\r\n    public fetchLocalizedStrings(): Observable<T> {\r\n        this.configureFetchFiles();\r\n        this.getLocaleId();\r\n        if (this.localeId.neutral === this.defaultLocaleId) {\r\n            // Only fetch the default locale\r\n            return this.fetchDefaultStrings();\r\n        }\r\n\r\n        return zip(\r\n            this.fetchDefaultStrings(),\r\n            this.fetchLocaleStrings())\r\n            .pipe(\r\n                catchError(() => of(null)),\r\n                map(([fetchDefault, fetchLocale]) => {\r\n                    // get the english strings and replace those properties of the localized json\r\n                    if (fetchLocale) {\r\n                        return MsftSme.deepAssign({}, fetchDefault, fetchLocale);\r\n                    }\r\n\r\n                    return fetchDefault;\r\n                }));\r\n    }\r\n\r\n    private configureFetchFiles(): void {\r\n        const version = MsftSme.self() && MsftSme.self().Environment && MsftSme.self().Environment.version || 'no-version';\r\n        this.resourcesStringFormat = `${this.resourcesPath}${LocalizationManager.resourcesStringsFileFormat}?v=${version}`;\r\n        this.resourcesStringDefaultFile = `${this.resourcesPath}${LocalizationManager.resourcesStringsFile}?v=${version}`;\r\n    }\r\n\r\n    public checkBothAvailable(localeSet: LocaleSet): boolean {\r\n        if (!localeSet || !localeSet.neutral || !localeSet.id) {\r\n            return false;\r\n        }\r\n\r\n        const checkNeutral = LocalizationManager.neutralCultures.find(item => item.id === localeSet.neutral);\r\n        const checkId = LocalizationManager.regionalCultures.find(item => item.id === localeSet.id);\r\n        return !!checkNeutral && !!checkId;\r\n    }\r\n\r\n    private checkIdAvailable(localeSet: LocaleSet): boolean {\r\n        const checkId = LocalizationManager.regionalCultures.find(item => item.id === localeSet.id);\r\n        return !!checkId;\r\n    }\r\n\r\n    private fetchDefaultStrings(): Observable<T> {\r\n        return this.http.get(this.resourcesStringDefaultFile)\r\n            .pipe(\r\n                catchError((error: AjaxError) => {\r\n                    if (error.status >= 400) {\r\n                        // If we got any error, we just reply with that error and the map function will handle it\r\n                        Logging.log(<LogRecord>{\r\n                            source: 'LocalizationManager',\r\n                            level: LogLevel.Error,\r\n                            message:\r\n                                `Error ${error.status} received when getting default localized strings for the ` +\r\n                                `${this.defaultLocaleId} locale. The error was: ${error.message}`\r\n                        });\r\n                    }\r\n\r\n                    return throwError(() => error);\r\n                }),\r\n                map((result: AjaxResponse<any>) => {\r\n                    return (<LocalizedResources<T>>result.response).Strings;\r\n                }));\r\n    }\r\n\r\n    private fetchLocaleStrings(): Observable<T> {\r\n        return this.http.get(this.resourcesStringFormat.format(this.localeId.neutral))\r\n            .pipe(\r\n                catchError((error: AjaxError) => {\r\n                    if (error.status >= 400) {\r\n                        // If we got any error, we just reply with that error and the map function will handle it\r\n                        Logging.log(<LogRecord>{\r\n                            source: 'LocalizationManager',\r\n                            level: LogLevel.Warning,\r\n                            message:\r\n                                `Error ${error.status} received when getting localized strings for the ` +\r\n                                `user specified locale: '${this.localeId.neutral}'. The error was: ${error.message}`\r\n                        });\r\n                        return of({});\r\n                    }\r\n                }),\r\n                map((result: AjaxResponse<any>) => {\r\n                    // If response has body, use that\r\n                    if (!result || !result.response) {\r\n                        return null;\r\n                    }\r\n\r\n                    return (<LocalizedResources<T>>result.response).Strings;\r\n                }));\r\n    }\r\n\r\n    private normalizeAzureLocaleId(azureLocale: string): LocaleSet {\r\n        if (MsftSme.isNullOrWhiteSpace(azureLocale)) {\r\n            return null;\r\n        }\r\n\r\n        const neutralCultures: { [index: string]: string } = {};\r\n        neutralCultures['de'] = 'de-DE';\r\n        neutralCultures['en'] = 'en-US';\r\n        neutralCultures['es'] = 'es-ES';\r\n        neutralCultures['fr'] = 'fr-FR';\r\n        neutralCultures['it'] = 'it-IT';\r\n        neutralCultures['hu'] = 'hu-HU';\r\n        neutralCultures['nl'] = 'nl-NL';\r\n        neutralCultures['pl'] = 'pl-PL';\r\n        neutralCultures['pt'] = 'pt-BR';\r\n        neutralCultures['sv'] = 'sv-SE';\r\n        neutralCultures['tr'] = 'tr-TR';\r\n        neutralCultures['cs'] = 'cs-CZ';\r\n        neutralCultures['ru'] = 'ru-RU';\r\n        neutralCultures['zh'] = 'zh-CN';\r\n        neutralCultures['zh-hans'] = 'zh-CN';\r\n        neutralCultures['zh-chs'] = 'zh-CN';\r\n        neutralCultures['zh-hant'] = 'zh-TW';\r\n        neutralCultures['zh-cht'] = 'zh-TW';\r\n        neutralCultures['ja'] = 'ja-JP';\r\n        neutralCultures['ko'] = 'ko-KR';\r\n\r\n        const languages = azureLocale.split('.');\r\n        const segments = languages[1].split('-');\r\n        const id = `${segments[0]}-${segments[1].toUpperCase()}`;\r\n        const neutral = neutralCultures[languages[0]];\r\n        if (!neutral) {\r\n            return null;\r\n        }\r\n\r\n        const localeSet: LocaleSet = { id, neutral };\r\n        if (this.checkBothAvailable(localeSet)) {\r\n            return localeSet;\r\n        }\r\n\r\n        // cannot found the regional code.\r\n        return <LocaleSet>{ id: localeSet.neutral, neutral: localeSet.neutral };\r\n    }\r\n}\r\n"]}