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();
  }
}