Spaces:
Running
Running
// locale-manager.service.ts | |
// Path: /flare-ui/src/app/services/locale-manager.service.ts | |
import { Injectable } from '@angular/core'; | |
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; | |
import { Observable, of, throwError } from 'rxjs'; | |
import { map, catchError, retry, timeout } from 'rxjs/operators'; | |
import { AuthService } from './auth.service'; | |
export interface Locale { | |
code: string; | |
name: string; | |
english_name: string; | |
} | |
export interface LocaleDetails extends Locale { | |
native_name?: string; | |
direction: string; | |
date_format: string; | |
time_format: string; | |
datetime_format: string; | |
currency: string; | |
currency_symbol: string; | |
decimal_separator: string; | |
thousands_separator: string; | |
week_starts_on: number; | |
months?: string[]; | |
days?: string[]; | |
am_pm?: string[]; | |
common_phrases?: { [key: string]: string }; | |
} | |
({ | |
providedIn: 'root' | |
}) | |
export class LocaleManagerService { | |
private apiUrl = '/api'; | |
private adminUrl = `${this.apiUrl}/admin`; | |
private localesCache?: Locale[]; | |
private cacheTimestamp?: number; | |
private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutes | |
private readonly REQUEST_TIMEOUT = 10000; // 10 seconds | |
constructor( | |
private http: HttpClient, | |
private authService: AuthService | |
) {} | |
private getAuthHeaders(): HttpHeaders { | |
const token = this.authService.getToken(); | |
if (!token) { | |
throw new Error('No authentication token available'); | |
} | |
return new HttpHeaders({ | |
'Authorization': `Bearer ${token}`, | |
'Content-Type': 'application/json' | |
}); | |
} | |
getAvailableLocales(): Observable<Locale[]> { | |
try { | |
// Check cache validity | |
if (this.localesCache && this.cacheTimestamp) { | |
const now = Date.now(); | |
if (now - this.cacheTimestamp < this.CACHE_DURATION) { | |
return of(this.localesCache); | |
} | |
} | |
return this.http.get<{ locales: Locale[], default: string }>( | |
`${this.adminUrl}/locales`, | |
{ headers: this.getAuthHeaders() } | |
).pipe( | |
timeout(this.REQUEST_TIMEOUT), | |
retry({ count: 2, delay: 1000 }), | |
map(response => { | |
this.localesCache = response.locales; | |
this.cacheTimestamp = Date.now(); | |
return response.locales; | |
}), | |
catchError(error => this.handleError(error, 'getAvailableLocales')) | |
); | |
} catch (error) { | |
return this.handleError(error, 'getAvailableLocales'); | |
} | |
} | |
getLocaleDetails(code: string): Observable<LocaleDetails | null> { | |
if (!code) { | |
return throwError(() => new Error('Locale code is required')); | |
} | |
try { | |
return this.http.get<LocaleDetails>( | |
`${this.adminUrl}/locales/${encodeURIComponent(code)}`, | |
{ headers: this.getAuthHeaders() } | |
).pipe( | |
timeout(this.REQUEST_TIMEOUT), | |
retry({ count: 2, delay: 1000 }), | |
catchError(error => { | |
// For 404, return null instead of throwing | |
if (error.status === 404) { | |
console.warn(`Locale '${code}' not found`); | |
return of(null); | |
} | |
return this.handleError(error, 'getLocaleDetails'); | |
}) | |
); | |
} catch (error) { | |
return this.handleError(error, 'getLocaleDetails'); | |
} | |
} | |
validateLanguages(languages: string[]): Observable<string[]> { | |
if (!languages || languages.length === 0) { | |
return of([]); | |
} | |
try { | |
return this.getAvailableLocales().pipe( | |
map(locales => { | |
const availableCodes = locales.map(l => l.code); | |
const invalidLanguages = languages.filter(lang => !availableCodes.includes(lang)); | |
if (invalidLanguages.length > 0) { | |
console.warn('Invalid languages detected:', invalidLanguages); | |
} | |
return invalidLanguages; | |
}), | |
catchError(error => { | |
console.error('Error validating languages:', error); | |
// Return all languages as invalid if validation fails | |
return of(languages); | |
}) | |
); | |
} catch (error) { | |
return this.handleError(error, 'validateLanguages'); | |
} | |
} | |
clearCache(): void { | |
this.localesCache = undefined; | |
this.cacheTimestamp = undefined; | |
} | |
private handleError(error: any, operation: string): Observable<any> { | |
console.error(`LocaleManagerService.${operation} error:`, error); | |
// Handle authentication errors | |
if (error?.status === 401) { | |
this.authService.logout(); | |
return throwError(() => ({ | |
...error, | |
message: 'Authentication required' | |
})); | |
} | |
// Handle race condition errors | |
if (error?.status === 409) { | |
return throwError(() => ({ | |
...error, | |
message: error.error?.message || 'Resource was modified by another user', | |
isRaceCondition: true | |
})); | |
} | |
// Handle network errors | |
if (error?.status === 0 || error?.name === 'TimeoutError') { | |
return throwError(() => ({ | |
...error, | |
message: 'Network connection error', | |
isNetworkError: true | |
})); | |
} | |
// For specific operations, provide fallback data | |
if (operation === 'getAvailableLocales' && !error?.status) { | |
// Fallback locales if API fails | |
const fallback = [ | |
{ code: 'tr-TR', name: 'Türkçe', english_name: 'Turkish' }, | |
{ code: 'en-US', name: 'English', english_name: 'English (US)' } | |
]; | |
this.localesCache = fallback; | |
this.cacheTimestamp = Date.now(); | |
console.warn('Using fallback locales due to error'); | |
return of(fallback); | |
} | |
// Default error handling | |
const errorMessage = error?.error?.message || error?.message || 'Unknown error occurred'; | |
return throwError(() => ({ | |
...error, | |
message: errorMessage, | |
operation: operation, | |
timestamp: new Date().toISOString() | |
})); | |
} | |
// Helper method to check if cache is stale | |
isCacheStale(): boolean { | |
if (!this.cacheTimestamp) return true; | |
return Date.now() - this.cacheTimestamp > this.CACHE_DURATION; | |
} | |
// Force refresh locales | |
refreshLocales(): Observable<Locale[]> { | |
this.clearCache(); | |
return this.getAvailableLocales(); | |
} | |
} |