flare / flare-ui /src /app /services /locale-manager.service.ts
ciyidogan's picture
Upload 118 files
9f79da5 verified
// 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 };
}
@Injectable({
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();
}
}