|
|
|
@ -1,10 +1,11 @@ |
|
|
|
import { registerLocaleData } from '@angular/common'; |
|
|
|
import { Injectable, Injector, isDevMode, Optional, SkipSelf } from '@angular/core'; |
|
|
|
import { from, Observable, Subject } from 'rxjs'; |
|
|
|
import { BehaviorSubject, combineLatest, from, Observable, Subject } from 'rxjs'; |
|
|
|
import { filter, map, mapTo, switchMap } from 'rxjs/operators'; |
|
|
|
import { ABP } from '../models/common'; |
|
|
|
import { LocalizationWithDefault } from '../models/localization'; |
|
|
|
import { ApplicationConfigurationDto } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models'; |
|
|
|
import { localizations$ } from '../tokens/localization.token'; |
|
|
|
import { CORE_OPTIONS } from '../tokens/options.token'; |
|
|
|
import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils'; |
|
|
|
import { interpolate } from '../utils/string-utils'; |
|
|
|
@ -16,6 +17,12 @@ export class LocalizationService { |
|
|
|
private latestLang = this.sessionState.getLanguage(); |
|
|
|
private _languageChange$ = new Subject<string>(); |
|
|
|
|
|
|
|
private localLocalizations$ = new BehaviorSubject( |
|
|
|
new Map<string, Map<string, Record<string, string>>>(), |
|
|
|
); |
|
|
|
|
|
|
|
private localizations$ = new BehaviorSubject(new Map<string, Record<string, string>>()); |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns currently selected language |
|
|
|
*/ |
|
|
|
@ -38,6 +45,62 @@ export class LocalizationService { |
|
|
|
if (otherInstance) throw new Error('LocalizationService should have only one instance.'); |
|
|
|
|
|
|
|
this.listenToSetLanguage(); |
|
|
|
this.initLocalizationValues(); |
|
|
|
} |
|
|
|
|
|
|
|
private initLocalizationValues() { |
|
|
|
localizations$.subscribe(val => this.addLocalization(val)); |
|
|
|
|
|
|
|
const remoteLocalizations$ = this.configState.getDeep$('localization.values') as Observable< |
|
|
|
Record<string, Record<string, string>> |
|
|
|
>; |
|
|
|
const currentLanguage$ = this.sessionState.getLanguage$(); |
|
|
|
|
|
|
|
const localLocalizations$ = combineLatest([currentLanguage$, this.localLocalizations$]).pipe( |
|
|
|
map(([currentLang, localizations]) => localizations.get(currentLang)), |
|
|
|
); |
|
|
|
|
|
|
|
combineLatest([remoteLocalizations$, localLocalizations$]) |
|
|
|
.pipe( |
|
|
|
map(([remote, local]) => { |
|
|
|
if (remote) { |
|
|
|
Object.entries(remote).forEach(entry => { |
|
|
|
const resourceName = entry[0]; |
|
|
|
const remoteTexts = entry[1]; |
|
|
|
let resource = local.get(resourceName) || {}; |
|
|
|
resource = { ...resource, ...remoteTexts }; |
|
|
|
|
|
|
|
local.set(resourceName, resource); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
return local; |
|
|
|
}), |
|
|
|
) |
|
|
|
.subscribe(val => this.localizations$.next(val)); |
|
|
|
} |
|
|
|
|
|
|
|
addLocalization(localizations?: ABP.Localization[]) { |
|
|
|
if (!localizations) return; |
|
|
|
|
|
|
|
const localizationMap = this.localLocalizations$.value; |
|
|
|
|
|
|
|
localizations.forEach(loc => { |
|
|
|
const cultureMap = |
|
|
|
localizationMap.get(loc.culture) || new Map<string, Record<string, string>>(); |
|
|
|
|
|
|
|
loc.resources.forEach(res => { |
|
|
|
let resource: Record<string, string> = cultureMap.get(res.resourceName) || {}; |
|
|
|
|
|
|
|
resource = { ...resource, ...res.texts }; |
|
|
|
|
|
|
|
cultureMap.set(res.resourceName, resource); |
|
|
|
}); |
|
|
|
|
|
|
|
localizationMap.set(loc.culture, cultureMap); |
|
|
|
}); |
|
|
|
|
|
|
|
this.localLocalizations$.next(localizationMap); |
|
|
|
} |
|
|
|
|
|
|
|
private listenToSetLanguage() { |
|
|
|
@ -70,15 +133,15 @@ export class LocalizationService { |
|
|
|
get(key: string | LocalizationWithDefault, ...interpolateParams: string[]): Observable<string> { |
|
|
|
return this.configState |
|
|
|
.getAll$() |
|
|
|
.pipe(map(state => getLocalization(state, key, ...interpolateParams))); |
|
|
|
.pipe(map(state => this.getLocalization(state, key, ...interpolateParams))); |
|
|
|
} |
|
|
|
|
|
|
|
getResource(resourceName: string) { |
|
|
|
return this.configState.getDeep(`localization.values.${resourceName}`); |
|
|
|
return this.localizations$.value.get(resourceName); |
|
|
|
} |
|
|
|
|
|
|
|
getResource$(resourceName: string) { |
|
|
|
return this.configState.getDeep$(`localization.values.${resourceName}`); |
|
|
|
return this.localizations$.pipe(map(res => res.get(resourceName))); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -87,7 +150,7 @@ export class LocalizationService { |
|
|
|
* @param interpolateParams Values to intepolate. |
|
|
|
*/ |
|
|
|
instant(key: string | LocalizationWithDefault, ...interpolateParams: string[]): string { |
|
|
|
return getLocalization(this.configState.getAll(), key, ...interpolateParams); |
|
|
|
return this.getLocalization(this.configState.getAll(), key, ...interpolateParams); |
|
|
|
} |
|
|
|
|
|
|
|
localize(resourceName: string, key: string, defaultValue: string): Observable<string> { |
|
|
|
@ -117,60 +180,62 @@ export class LocalizationService { |
|
|
|
const localization = this.configState.getOne('localization'); |
|
|
|
return createLocalizerWithFallback(localization)(resourceNames, keys, defaultValue); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function getLocalization( |
|
|
|
state: ApplicationConfigurationDto, |
|
|
|
key: string | LocalizationWithDefault, |
|
|
|
...interpolateParams: string[] |
|
|
|
) { |
|
|
|
if (!key) key = ''; |
|
|
|
let defaultValue: string; |
|
|
|
private getLocalization( |
|
|
|
state: ApplicationConfigurationDto, |
|
|
|
key: string | LocalizationWithDefault, |
|
|
|
...interpolateParams: string[] |
|
|
|
) { |
|
|
|
if (!key) key = ''; |
|
|
|
let defaultValue: string; |
|
|
|
|
|
|
|
if (typeof key !== 'string') { |
|
|
|
defaultValue = key.defaultValue; |
|
|
|
key = key.key; |
|
|
|
} |
|
|
|
if (typeof key !== 'string') { |
|
|
|
defaultValue = key.defaultValue; |
|
|
|
key = key.key; |
|
|
|
} |
|
|
|
|
|
|
|
const keys = key.split('::') as string[]; |
|
|
|
const warn = (message: string) => { |
|
|
|
if (isDevMode) console.warn(message); |
|
|
|
}; |
|
|
|
const keys = key.split('::') as string[]; |
|
|
|
const warn = (message: string) => { |
|
|
|
if (isDevMode) console.warn(message); |
|
|
|
}; |
|
|
|
|
|
|
|
if (keys.length < 2) { |
|
|
|
warn('The localization source separator (::) not found.'); |
|
|
|
return defaultValue || (key as string); |
|
|
|
} |
|
|
|
if (!state.localization) return defaultValue || keys[1]; |
|
|
|
if (keys.length < 2) { |
|
|
|
warn('The localization source separator (::) not found.'); |
|
|
|
return defaultValue || (key as string); |
|
|
|
} |
|
|
|
if (!state.localization) return defaultValue || keys[1]; |
|
|
|
|
|
|
|
const sourceName = keys[0] || state.localization.defaultResourceName; |
|
|
|
const sourceKey = keys[1]; |
|
|
|
const sourceName = keys[0] || state.localization.defaultResourceName; |
|
|
|
const sourceKey = keys[1]; |
|
|
|
|
|
|
|
if (sourceName === '_') { |
|
|
|
return defaultValue || sourceKey; |
|
|
|
} |
|
|
|
if (sourceName === '_') { |
|
|
|
return defaultValue || sourceKey; |
|
|
|
} |
|
|
|
|
|
|
|
if (!sourceName) { |
|
|
|
warn('Localization source name is not specified and the defaultResourceName was not defined!'); |
|
|
|
if (!sourceName) { |
|
|
|
warn( |
|
|
|
'Localization source name is not specified and the defaultResourceName was not defined!', |
|
|
|
); |
|
|
|
|
|
|
|
return defaultValue || sourceKey; |
|
|
|
} |
|
|
|
return defaultValue || sourceKey; |
|
|
|
} |
|
|
|
|
|
|
|
const source = state.localization.values[sourceName]; |
|
|
|
if (!source) { |
|
|
|
warn('Could not find localization source: ' + sourceName); |
|
|
|
return defaultValue || sourceKey; |
|
|
|
} |
|
|
|
const source = this.localizations$.value.get(sourceName); |
|
|
|
if (!source) { |
|
|
|
warn('Could not find localization source: ' + sourceName); |
|
|
|
return defaultValue || sourceKey; |
|
|
|
} |
|
|
|
|
|
|
|
let localization = source[sourceKey]; |
|
|
|
if (typeof localization === 'undefined') { |
|
|
|
return defaultValue || sourceKey; |
|
|
|
} |
|
|
|
let localization = source[sourceKey]; |
|
|
|
if (typeof localization === 'undefined') { |
|
|
|
return defaultValue || sourceKey; |
|
|
|
} |
|
|
|
|
|
|
|
interpolateParams = interpolateParams.filter(params => params != null); |
|
|
|
if (localization) localization = interpolate(localization, interpolateParams); |
|
|
|
interpolateParams = interpolateParams.filter(params => params != null); |
|
|
|
if (localization) localization = interpolate(localization, interpolateParams); |
|
|
|
|
|
|
|
if (typeof localization !== 'string') localization = ''; |
|
|
|
if (typeof localization !== 'string') localization = ''; |
|
|
|
|
|
|
|
return localization || defaultValue || (key as string); |
|
|
|
return localization || defaultValue || (key as string); |
|
|
|
} |
|
|
|
} |
|
|
|
|