-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Angular - Hybrid localization support #24731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
f62fa79
7f9c976
f585f7d
cdad015
60322bd
5bc485c
b7d61f2
ef25844
cbe5e57
6a364d9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -60,6 +60,8 @@ export class LocalizationService { | |||||
| private initLocalizationValues() { | ||||||
| localizations$.subscribe(val => this.addLocalization(val)); | ||||||
|
|
||||||
| // Backend-based localization loading (always enabled) | ||||||
| // UI localizations are merged via addLocalization() (UI > Backend priority) | ||||||
| const legacyResources$ = this.configState.getDeep$('localization.values') as Observable< | ||||||
| Record<string, Record<string, string>> | ||||||
| >; | ||||||
|
|
@@ -90,7 +92,8 @@ export class LocalizationService { | |||||
| const resourceName = entry[0]; | ||||||
| const remoteTexts = entry[1]; | ||||||
| let resource = local?.get(resourceName) || {}; | ||||||
| resource = { ...resource, ...remoteTexts }; | ||||||
| // UI > Backend priority: UI localization'lar backend'i override eder | ||||||
|
||||||
| // UI > Backend priority: UI localization'lar backend'i override eder | |
| // UI > Backend priority: UI localizations override backend localizations |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import { Injectable, inject } from '@angular/core'; | ||
| import { HttpClient } from '@angular/common/http'; | ||
| import { BehaviorSubject, distinctUntilChanged, switchMap, of } from 'rxjs'; | ||
| import { catchError, shareReplay, tap } from 'rxjs/operators'; | ||
| import { ABP } from '../models/common'; | ||
| import { LocalizationService } from './localization.service'; | ||
| import { SessionStateService } from './session-state.service'; | ||
| import { CORE_OPTIONS } from '../tokens/options.token'; | ||
|
|
||
| export interface LocalizationResource { | ||
| [resourceName: string]: Record<string, string>; | ||
| } | ||
|
||
|
|
||
| /** | ||
| * Service for managing UI localizations in ABP Angular applications. | ||
| * Automatically loads localization files based on selected language | ||
| * Merges with backend localizations (UI > Backend priority) | ||
| */ | ||
| @Injectable({ providedIn: 'root' }) | ||
| export class UILocalizationService { | ||
| private http = inject(HttpClient); | ||
| private localizationService = inject(LocalizationService); | ||
| private sessionState = inject(SessionStateService); | ||
| private options = inject(CORE_OPTIONS); | ||
|
|
||
| private loadedLocalizations$ = new BehaviorSubject<Record<string, LocalizationResource>>({}); | ||
|
|
||
| private currentLanguage$ = this.sessionState.getLanguage$(); | ||
|
|
||
| constructor() { | ||
| const uiLocalization = this.options.uiLocalization; | ||
| if (uiLocalization?.enabled) { | ||
| this.subscribeToLanguageChanges(); | ||
| } | ||
| } | ||
|
|
||
| private subscribeToLanguageChanges() { | ||
| this.currentLanguage$ | ||
| .pipe( | ||
| distinctUntilChanged(), | ||
| switchMap(culture => this.loadLocalizationFile(culture)), | ||
| shareReplay(1), | ||
|
||
| ) | ||
| .subscribe(); | ||
|
Comment on lines
+40
to
+43
|
||
| } | ||
|
|
||
| private loadLocalizationFile(culture: string) { | ||
| const config = this.options.uiLocalization; | ||
| if (!config?.enabled) return of(null); | ||
|
|
||
| const basePath = config.basePath || '/assets/localization'; | ||
| const url = `${basePath}/${culture}.json`; | ||
|
|
||
| return this.http.get<LocalizationResource>(url).pipe( | ||
|
||
| catchError(() => { | ||
| // If file not found or error occurs, return null | ||
| return of(null); | ||
| }), | ||
| tap(data => { | ||
| if (data) { | ||
| this.processLocalizationData(culture, data); | ||
| } | ||
| }), | ||
| ); | ||
| } | ||
|
|
||
| private processLocalizationData(culture: string, data: LocalizationResource) { | ||
| const abpFormat: ABP.Localization[] = [ | ||
| { | ||
| culture, | ||
| resources: Object.entries(data).map(([resourceName, texts]) => ({ | ||
| resourceName, | ||
| texts, | ||
| })), | ||
| }, | ||
| ]; | ||
| this.localizationService.addLocalization(abpFormat); | ||
|
|
||
| const current = this.loadedLocalizations$.value; | ||
| current[culture] = data; | ||
| this.loadedLocalizations$.next(current); | ||
| } | ||
|
|
||
| addAngularLocalizeLocalization( | ||
| culture: string, | ||
| resourceName: string, | ||
| translations: Record<string, string>, | ||
| ): void { | ||
| const abpFormat: ABP.Localization[] = [ | ||
| { | ||
| culture, | ||
| resources: [ | ||
| { | ||
| resourceName, | ||
| texts: translations, | ||
| }, | ||
| ], | ||
| }, | ||
| ]; | ||
| this.localizationService.addLocalization(abpFormat); | ||
|
|
||
| const current = this.loadedLocalizations$.value; | ||
| if (!current[culture]) { | ||
| current[culture] = {}; | ||
| } | ||
| if (!current[culture][resourceName]) { | ||
| current[culture][resourceName] = {}; | ||
| } | ||
| current[culture][resourceName] = { | ||
| ...current[culture][resourceName], | ||
| ...translations, | ||
| }; | ||
| this.loadedLocalizations$.next(current); | ||
| } | ||
|
|
||
| getLoadedLocalizations(culture?: string): LocalizationResource { | ||
| const lang = culture || this.sessionState.getLanguage(); | ||
| return this.loadedLocalizations$.value[lang] || {}; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says "UI-only mode" but this feature appears to be hybrid UI+backend localization merging (based on
uiLocalizationoptions and the merge logic inLocalizationService). Please adjust the comment to accurately describe what is being enabled/initialized here.