Spaces:
Running
Running
File size: 6,445 Bytes
9f79da5 b74fc10 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
// 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();
}
} |