Spaces:
Running
Running
Update flare-ui/src/app/services/locale-manager.service.ts
Browse files
flare-ui/src/app/services/locale-manager.service.ts
CHANGED
@@ -1,7 +1,10 @@
|
|
|
|
|
|
|
|
1 |
import { Injectable } from '@angular/core';
|
2 |
-
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
3 |
-
import { Observable, of } from 'rxjs';
|
4 |
-
import { map, catchError } from 'rxjs/operators';
|
5 |
import { AuthService } from './auth.service';
|
6 |
|
7 |
export interface Locale {
|
@@ -33,6 +36,9 @@ export interface LocaleDetails extends Locale {
|
|
33 |
export class LocaleManagerService {
|
34 |
private apiUrl = '/api';
|
35 |
private localesCache?: Locale[];
|
|
|
|
|
|
|
36 |
|
37 |
constructor(
|
38 |
private http: HttpClient,
|
@@ -41,6 +47,10 @@ export class LocaleManagerService {
|
|
41 |
|
42 |
private getAuthHeaders(): HttpHeaders {
|
43 |
const token = this.authService.getToken();
|
|
|
|
|
|
|
|
|
44 |
return new HttpHeaders({
|
45 |
'Authorization': `Bearer ${token}`,
|
46 |
'Content-Type': 'application/json'
|
@@ -48,49 +58,154 @@ export class LocaleManagerService {
|
|
48 |
}
|
49 |
|
50 |
getAvailableLocales(): Observable<Locale[]> {
|
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 |
getLocaleDetails(code: string): Observable<LocaleDetails | null> {
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
}
|
83 |
|
84 |
validateLanguages(languages: string[]): Observable<string[]> {
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
}
|
92 |
|
93 |
clearCache(): void {
|
94 |
this.localesCache = undefined;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
}
|
96 |
}
|
|
|
1 |
+
// locale-manager.service.ts
|
2 |
+
// Path: /flare-ui/src/app/services/locale-manager.service.ts
|
3 |
+
|
4 |
import { Injectable } from '@angular/core';
|
5 |
+
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
6 |
+
import { Observable, of, throwError } from 'rxjs';
|
7 |
+
import { map, catchError, retry, timeout } from 'rxjs/operators';
|
8 |
import { AuthService } from './auth.service';
|
9 |
|
10 |
export interface Locale {
|
|
|
36 |
export class LocaleManagerService {
|
37 |
private apiUrl = '/api';
|
38 |
private localesCache?: Locale[];
|
39 |
+
private cacheTimestamp?: number;
|
40 |
+
private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
41 |
+
private readonly REQUEST_TIMEOUT = 10000; // 10 seconds
|
42 |
|
43 |
constructor(
|
44 |
private http: HttpClient,
|
|
|
47 |
|
48 |
private getAuthHeaders(): HttpHeaders {
|
49 |
const token = this.authService.getToken();
|
50 |
+
if (!token) {
|
51 |
+
throw new Error('No authentication token available');
|
52 |
+
}
|
53 |
+
|
54 |
return new HttpHeaders({
|
55 |
'Authorization': `Bearer ${token}`,
|
56 |
'Content-Type': 'application/json'
|
|
|
58 |
}
|
59 |
|
60 |
getAvailableLocales(): Observable<Locale[]> {
|
61 |
+
try {
|
62 |
+
// Check cache validity
|
63 |
+
if (this.localesCache && this.cacheTimestamp) {
|
64 |
+
const now = Date.now();
|
65 |
+
if (now - this.cacheTimestamp < this.CACHE_DURATION) {
|
66 |
+
return of(this.localesCache);
|
67 |
+
}
|
68 |
+
}
|
69 |
|
70 |
+
return this.http.get<{ locales: Locale[], default: string }>(
|
71 |
+
`${this.apiUrl}/locales`,
|
72 |
+
{ headers: this.getAuthHeaders() }
|
73 |
+
).pipe(
|
74 |
+
timeout(this.REQUEST_TIMEOUT),
|
75 |
+
retry({ count: 2, delay: 1000 }),
|
76 |
+
map(response => {
|
77 |
+
this.localesCache = response.locales;
|
78 |
+
this.cacheTimestamp = Date.now();
|
79 |
+
return response.locales;
|
80 |
+
}),
|
81 |
+
catchError(error => this.handleError(error, 'getAvailableLocales'))
|
82 |
+
);
|
83 |
+
} catch (error) {
|
84 |
+
return this.handleError(error, 'getAvailableLocales');
|
85 |
+
}
|
|
|
|
|
86 |
}
|
87 |
|
88 |
getLocaleDetails(code: string): Observable<LocaleDetails | null> {
|
89 |
+
if (!code) {
|
90 |
+
return throwError(() => new Error('Locale code is required'));
|
91 |
+
}
|
92 |
+
|
93 |
+
try {
|
94 |
+
return this.http.get<LocaleDetails>(
|
95 |
+
`${this.apiUrl}/locales/${encodeURIComponent(code)}`,
|
96 |
+
{ headers: this.getAuthHeaders() }
|
97 |
+
).pipe(
|
98 |
+
timeout(this.REQUEST_TIMEOUT),
|
99 |
+
retry({ count: 2, delay: 1000 }),
|
100 |
+
catchError(error => {
|
101 |
+
// For 404, return null instead of throwing
|
102 |
+
if (error.status === 404) {
|
103 |
+
console.warn(`Locale '${code}' not found`);
|
104 |
+
return of(null);
|
105 |
+
}
|
106 |
+
return this.handleError(error, 'getLocaleDetails');
|
107 |
+
})
|
108 |
+
);
|
109 |
+
} catch (error) {
|
110 |
+
return this.handleError(error, 'getLocaleDetails');
|
111 |
+
}
|
112 |
}
|
113 |
|
114 |
validateLanguages(languages: string[]): Observable<string[]> {
|
115 |
+
if (!languages || languages.length === 0) {
|
116 |
+
return of([]);
|
117 |
+
}
|
118 |
+
|
119 |
+
try {
|
120 |
+
return this.getAvailableLocales().pipe(
|
121 |
+
map(locales => {
|
122 |
+
const availableCodes = locales.map(l => l.code);
|
123 |
+
const invalidLanguages = languages.filter(lang => !availableCodes.includes(lang));
|
124 |
+
|
125 |
+
if (invalidLanguages.length > 0) {
|
126 |
+
console.warn('Invalid languages detected:', invalidLanguages);
|
127 |
+
}
|
128 |
+
|
129 |
+
return invalidLanguages;
|
130 |
+
}),
|
131 |
+
catchError(error => {
|
132 |
+
console.error('Error validating languages:', error);
|
133 |
+
// Return all languages as invalid if validation fails
|
134 |
+
return of(languages);
|
135 |
+
})
|
136 |
+
);
|
137 |
+
} catch (error) {
|
138 |
+
return this.handleError(error, 'validateLanguages');
|
139 |
+
}
|
140 |
}
|
141 |
|
142 |
clearCache(): void {
|
143 |
this.localesCache = undefined;
|
144 |
+
this.cacheTimestamp = undefined;
|
145 |
+
}
|
146 |
+
|
147 |
+
private handleError(error: any, operation: string): Observable<any> {
|
148 |
+
console.error(`LocaleManagerService.${operation} error:`, error);
|
149 |
+
|
150 |
+
// Handle authentication errors
|
151 |
+
if (error?.status === 401) {
|
152 |
+
this.authService.logout();
|
153 |
+
return throwError(() => ({
|
154 |
+
...error,
|
155 |
+
message: 'Authentication required'
|
156 |
+
}));
|
157 |
+
}
|
158 |
+
|
159 |
+
// Handle race condition errors
|
160 |
+
if (error?.status === 409) {
|
161 |
+
return throwError(() => ({
|
162 |
+
...error,
|
163 |
+
message: error.error?.message || 'Resource was modified by another user',
|
164 |
+
isRaceCondition: true
|
165 |
+
}));
|
166 |
+
}
|
167 |
+
|
168 |
+
// Handle network errors
|
169 |
+
if (error?.status === 0 || error?.name === 'TimeoutError') {
|
170 |
+
return throwError(() => ({
|
171 |
+
...error,
|
172 |
+
message: 'Network connection error',
|
173 |
+
isNetworkError: true
|
174 |
+
}));
|
175 |
+
}
|
176 |
+
|
177 |
+
// For specific operations, provide fallback data
|
178 |
+
if (operation === 'getAvailableLocales' && !error?.status) {
|
179 |
+
// Fallback locales if API fails
|
180 |
+
const fallback = [
|
181 |
+
{ code: 'tr-TR', name: 'Türkçe', english_name: 'Turkish' },
|
182 |
+
{ code: 'en-US', name: 'English', english_name: 'English (US)' }
|
183 |
+
];
|
184 |
+
this.localesCache = fallback;
|
185 |
+
this.cacheTimestamp = Date.now();
|
186 |
+
console.warn('Using fallback locales due to error');
|
187 |
+
return of(fallback);
|
188 |
+
}
|
189 |
+
|
190 |
+
// Default error handling
|
191 |
+
const errorMessage = error?.error?.message || error?.message || 'Unknown error occurred';
|
192 |
+
return throwError(() => ({
|
193 |
+
...error,
|
194 |
+
message: errorMessage,
|
195 |
+
operation: operation,
|
196 |
+
timestamp: new Date().toISOString()
|
197 |
+
}));
|
198 |
+
}
|
199 |
+
|
200 |
+
// Helper method to check if cache is stale
|
201 |
+
isCacheStale(): boolean {
|
202 |
+
if (!this.cacheTimestamp) return true;
|
203 |
+
return Date.now() - this.cacheTimestamp > this.CACHE_DURATION;
|
204 |
+
}
|
205 |
+
|
206 |
+
// Force refresh locales
|
207 |
+
refreshLocales(): Observable<Locale[]> {
|
208 |
+
this.clearCache();
|
209 |
+
return this.getAvailableLocales();
|
210 |
}
|
211 |
}
|