@albi_scando/as-backbone-lib 1.0.4 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,11 +1,750 @@
|
|
|
1
|
+
import { HttpClient, HttpEventType } from '@angular/common/http';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, Injectable, signal, computed, Pipe } from '@angular/core';
|
|
4
|
+
import { HTTP_HEADERS, HTTP_RESPONSE_STATUS_CODES } from '@albi_scando/as-const-http-lib';
|
|
5
|
+
import { UserService as UserService$1 } from '@user/services/user/user.service';
|
|
6
|
+
import { LoggerService } from '@albi_scando/as-log-lib';
|
|
7
|
+
import { tap, catchError, throwError, of, forkJoin, defaultIfEmpty, map, take, defer, EMPTY, switchMap } from 'rxjs';
|
|
8
|
+
import { LOCALE_ISO_CODES, LOCALES } from '@albi_scando/as-const-languages-lib';
|
|
9
|
+
import { TRANSLATION_DELIBERATELY_UNSET } from '@albi_scando/as-const-lib/dist/types/constants/constants';
|
|
10
|
+
import { HttpService as HttpService$1 } from '@http/services/http/http.service';
|
|
11
|
+
import { STRING_EMPTY } from '@albi_scando/as-const-lib';
|
|
12
|
+
|
|
13
|
+
class HttpService {
|
|
14
|
+
/**
|
|
15
|
+
* {@link HttpClient}
|
|
16
|
+
*/
|
|
17
|
+
httpClient = inject(HttpClient);
|
|
18
|
+
/**
|
|
19
|
+
* {@link IHttpService.expiredPasswordFn}
|
|
20
|
+
*/
|
|
21
|
+
expiredPasswordFn = () => undefined;
|
|
22
|
+
/**
|
|
23
|
+
* {@link IHttpService.unauthorizedFn}
|
|
24
|
+
*/
|
|
25
|
+
unauthorizedFn = () => undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Perform http delete request
|
|
28
|
+
*
|
|
29
|
+
* @param {Delete} httpRequest http request
|
|
30
|
+
* @returns {Observable<unknown>} response observable
|
|
31
|
+
*/
|
|
32
|
+
delete(httpRequest) {
|
|
33
|
+
const options = this.typeSafeHeaderOptions(httpRequest.headerParams);
|
|
34
|
+
return this.httpClient.delete(httpRequest.url, options);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Perform http get request
|
|
38
|
+
*
|
|
39
|
+
* @param {Get} httpRequest http request
|
|
40
|
+
* @returns {Observable<unknown>} response observable
|
|
41
|
+
*/
|
|
42
|
+
get(httpRequest) {
|
|
43
|
+
httpRequest.url = this._setQueryParams(httpRequest.queryParams, httpRequest.url);
|
|
44
|
+
const options = this.typeSafeHeaderOptions(httpRequest.headerParams);
|
|
45
|
+
return this.httpClient.get(httpRequest.url, options);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Perform http patch request
|
|
49
|
+
*
|
|
50
|
+
* @param {Patch} httpRequest http request
|
|
51
|
+
* @returns {Observable<unknown>} response observable
|
|
52
|
+
*/
|
|
53
|
+
patch(httpRequest) {
|
|
54
|
+
const options = this.typeSafeHeaderOptions(httpRequest.headerParams);
|
|
55
|
+
return this.httpClient.patch(httpRequest.url, httpRequest.body, options);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Perform http post request
|
|
59
|
+
*
|
|
60
|
+
* @param {Post} httpRequest http request
|
|
61
|
+
* @returns {Observable<unknown>} response observable
|
|
62
|
+
*/
|
|
63
|
+
post(httpRequest) {
|
|
64
|
+
const options = this.typeSafeHeaderOptions(httpRequest.headerParams);
|
|
65
|
+
return this.httpClient.post(httpRequest.url, httpRequest.body, options);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Perform http put request
|
|
69
|
+
*
|
|
70
|
+
* @param {Put} httpRequest http request
|
|
71
|
+
* @returns {Observable<unknown>} response observable
|
|
72
|
+
*/
|
|
73
|
+
put(httpRequest) {
|
|
74
|
+
const options = this.typeSafeHeaderOptions(httpRequest.headerParams);
|
|
75
|
+
return this.httpClient.put(httpRequest.url, httpRequest.body, options);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Set url query parameters
|
|
79
|
+
*
|
|
80
|
+
* @param {Map<string, string>} queryParams query parameters key-value pairs
|
|
81
|
+
* @param {string} url original httpRequest url
|
|
82
|
+
* @returns {string} new url with aquery parameters
|
|
83
|
+
*/
|
|
84
|
+
_setQueryParams(queryParams = new Map(), url) {
|
|
85
|
+
if (queryParams.size === 0) {
|
|
86
|
+
return url;
|
|
87
|
+
}
|
|
88
|
+
url += `?${Array.from(queryParams.entries()).map(([key, value]) => `${key}=${value}`).join('&')}`;
|
|
89
|
+
return url;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Type safe header options for HttpClient methods
|
|
93
|
+
* @param httpRequestHeaders header parameters key-value pairs
|
|
94
|
+
* @returns header options for HttpClient methods
|
|
95
|
+
*/
|
|
96
|
+
typeSafeHeaderOptions(httpRequestHeaders) {
|
|
97
|
+
return { headers: Object.fromEntries(this._setHeaderParams(httpRequestHeaders)) };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Set http request header parameters
|
|
101
|
+
*
|
|
102
|
+
* @param {Map<HttpHeaders, string | boolean>} headerParams header parameters key-value pairs
|
|
103
|
+
* @returns {Map<HttpHeaders, string>} header parameters key-value pairs. All values must be strings.
|
|
104
|
+
*/
|
|
105
|
+
_setHeaderParams(headerParams) {
|
|
106
|
+
if (headerParams === undefined || headerParams.size === 0) {
|
|
107
|
+
return new Map();
|
|
108
|
+
}
|
|
109
|
+
const headerParameters = new Map();
|
|
110
|
+
Array.from(headerParams.entries()).forEach(([key, value]) => {
|
|
111
|
+
headerParameters.set(key, `${value}`);
|
|
112
|
+
});
|
|
113
|
+
return headerParameters;
|
|
114
|
+
}
|
|
115
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: HttpService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
116
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: HttpService, providedIn: 'root' });
|
|
117
|
+
}
|
|
118
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: HttpService, decorators: [{
|
|
119
|
+
type: Injectable,
|
|
120
|
+
args: [{
|
|
121
|
+
providedIn: 'root',
|
|
122
|
+
}]
|
|
123
|
+
}] });
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* This interceptor is responsible for adding Authorization header parameter to every outgoing HttpRequest
|
|
127
|
+
*
|
|
128
|
+
* @param {HttpRequest<unknown>} httpRequest @see {@link HttpRequest}
|
|
129
|
+
* @param {HttpHandlerFn} next @see {@link HttpHandlerFn}
|
|
130
|
+
* @returns {Observable<HttpEvent<unknown>>} @see {@link Observable<HttpEvent>}
|
|
131
|
+
*/
|
|
132
|
+
const authorizationInterceptor = (httpRequest, next) => {
|
|
133
|
+
/**
|
|
134
|
+
* {@link UserService}
|
|
135
|
+
*/
|
|
136
|
+
const userService = inject(UserService$1);
|
|
137
|
+
const token = userService.token();
|
|
138
|
+
if (token === undefined) {
|
|
139
|
+
return next(httpRequest);
|
|
140
|
+
}
|
|
141
|
+
const clonedRequest = httpRequest.clone({
|
|
142
|
+
headers: httpRequest.headers.set(HTTP_HEADERS.AUTHORIZATION, token),
|
|
143
|
+
});
|
|
144
|
+
return next(clonedRequest);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* This interceptor logs every outgoing HttpRequest.
|
|
149
|
+
* It fulfills the same task as the developer tools Network inspector.
|
|
150
|
+
*
|
|
151
|
+
* @param {HttpRequest<unknown>} httpRequest @see {@link HttpRequest}
|
|
152
|
+
* @param {HttpHandlerFn} next @see {@link HttpHandlerFn}
|
|
153
|
+
* @returns {Observable<HttpEvent<unknown>>} @see {@link Observable<HttpEvent>}
|
|
154
|
+
*/
|
|
155
|
+
const logRequestInterceptor = (httpRequest, next) => {
|
|
156
|
+
/**
|
|
157
|
+
* {@link LoggerService}
|
|
158
|
+
*/
|
|
159
|
+
const loggerService = inject(LoggerService);
|
|
160
|
+
return next(httpRequest).pipe(tap(() => {
|
|
161
|
+
loggerService.debug(`${logRequestInterceptor.name}: - ${httpRequest.url} http request`, httpRequest);
|
|
162
|
+
}));
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* This interceptor logs the time interval (in milliseconds) between the request submission and the incoming response.
|
|
167
|
+
* It fulfills the same task as 'Waterfall' graph in the developer tools Network inspector.
|
|
168
|
+
*
|
|
169
|
+
* @param {HttpRequest<unknown>} httpRequest @see {@link HttpRequest}
|
|
170
|
+
* @param {HttpHandlerFn} next @see {@link HttpHandlerFn}
|
|
171
|
+
* @returns {Observable<HttpEvent<unknown>>} @see {@link Observable<HttpEvent>}
|
|
172
|
+
*/
|
|
173
|
+
const requestTimestampInterceptor = (httpRequest, next) => {
|
|
174
|
+
/**
|
|
175
|
+
* {@link LoggerService}
|
|
176
|
+
*/
|
|
177
|
+
const loggerService = inject(LoggerService);
|
|
178
|
+
const startTime = Date.now();
|
|
179
|
+
const formatLog = (message, isError = false) => {
|
|
180
|
+
const diff = Date.now() - startTime;
|
|
181
|
+
const logMessage = `${requestTimestampInterceptor.name}: - ${httpRequest.url} ${message} in ${diff} ms`;
|
|
182
|
+
if (isError) {
|
|
183
|
+
loggerService.error(logMessage);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
loggerService.debug(logMessage);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
return next(httpRequest).pipe(tap({
|
|
190
|
+
next: (event) => {
|
|
191
|
+
if (event.type === HttpEventType.Response) {
|
|
192
|
+
formatLog('succeeded');
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
error: () => {
|
|
196
|
+
formatLog('failed', true);
|
|
197
|
+
},
|
|
198
|
+
}));
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const EXPIRED_PASSWORD_MESSAGE = 'Expired password';
|
|
202
|
+
/**
|
|
203
|
+
* Http request interceptor to handle 401 unauthorized responses.
|
|
204
|
+
* 401 error is special, because it needs more than just a feedback.
|
|
205
|
+
* In most situations, it could be provoked by an expired token that requires a redirect to a 'Login' page.
|
|
206
|
+
* When validating credentials, it could be provoked by an expired password that requires a redirect to 'Change password' page.
|
|
207
|
+
*
|
|
208
|
+
* @param {HttpRequest<unknown>} httpRequest @see {@link HttpRequest}
|
|
209
|
+
* @param {HttpHandlerFn} next @see {@link HttpHandlerFn}
|
|
210
|
+
* @returns {Observable<HttpEvent<unknown>>} @see {@link : Observable<HttpEvent>}
|
|
211
|
+
*/
|
|
212
|
+
const unauthorizedInterceptor = (httpRequest, next) => {
|
|
213
|
+
const httpService = inject(HttpService);
|
|
214
|
+
return next(httpRequest).pipe(catchError((httpResponse) => {
|
|
215
|
+
if (httpResponse.status !== HTTP_RESPONSE_STATUS_CODES.UNAUTHORIZED) {
|
|
216
|
+
return throwError(() => httpResponse);
|
|
217
|
+
}
|
|
218
|
+
const errorMessage = httpResponse.error?.ErrMsg;
|
|
219
|
+
if (errorMessage === EXPIRED_PASSWORD_MESSAGE) {
|
|
220
|
+
httpService.expiredPasswordFn();
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
httpService.unauthorizedFn();
|
|
224
|
+
}
|
|
225
|
+
return of();
|
|
226
|
+
}));
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* A service that manages translations based on the current language.
|
|
231
|
+
*
|
|
232
|
+
* The `TranslationsService` handles fetching, storing, and updating translations
|
|
233
|
+
* for different languages. It interacts with various translation sources and
|
|
234
|
+
* provides methods to retrieve translations based on a given key.
|
|
235
|
+
*/
|
|
236
|
+
class TranslationsService {
|
|
237
|
+
/**
|
|
238
|
+
* List of translations sources (apis/files)
|
|
239
|
+
*/
|
|
240
|
+
_sources = new Set();
|
|
241
|
+
/**
|
|
242
|
+
* Current translations dictionary, mapping translation keys to their values.
|
|
243
|
+
*/
|
|
244
|
+
translations = signal(new Map(), ...(ngDevMode ? [{ debugName: "translations" }] : /* istanbul ignore next */ []));
|
|
245
|
+
/**
|
|
246
|
+
* Derived signal that indicates whether translations are currently loaded.
|
|
247
|
+
* Returns true if the translations map contains any entries.
|
|
248
|
+
*/
|
|
249
|
+
hasTranslations = computed(() => this.translations().size > 0, ...(ngDevMode ? [{ debugName: "hasTranslations" }] : /* istanbul ignore next */ []));
|
|
250
|
+
/**
|
|
251
|
+
* Derived signal for the count of currently loaded translations.
|
|
252
|
+
* Useful for debugging or monitoring translation load status.
|
|
253
|
+
*/
|
|
254
|
+
translationsCount = computed(() => this.translations().size, ...(ngDevMode ? [{ debugName: "translationsCount" }] : /* istanbul ignore next */ []));
|
|
255
|
+
/**
|
|
256
|
+
* {@link HttpService}
|
|
257
|
+
*/
|
|
258
|
+
httpService = inject(HttpService$1);
|
|
259
|
+
/**
|
|
260
|
+
* {@link LoggerService}
|
|
261
|
+
*/
|
|
262
|
+
loggerService = inject(LoggerService);
|
|
263
|
+
/**
|
|
264
|
+
* Adds new translation sources and optionally updates translations for a given language code.
|
|
265
|
+
*
|
|
266
|
+
* If no language code is provided, the translations will not be updated immediately.
|
|
267
|
+
* The translations will be updated as soon as the current language is set.
|
|
268
|
+
*
|
|
269
|
+
* @param {Set<TranslationsSource>} sources New sources to add.
|
|
270
|
+
* @param {Locales} [language=stringEmpty] Optional language code to trigger translations update.
|
|
271
|
+
* @returns {Observable<void>} An observable that completes when the translations are updated.
|
|
272
|
+
*/
|
|
273
|
+
addTranslationsSources$(sources, language) {
|
|
274
|
+
this._sources = new Set([...this._sources, ...sources]);
|
|
275
|
+
this.loggerService.debug(`Translations sources added`, this._sources);
|
|
276
|
+
return this.updateTranslations$(language, sources);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Updates translations based on the provided language code and sources.
|
|
280
|
+
*
|
|
281
|
+
* @param {Locales} language The language code to use for updating translations.
|
|
282
|
+
* @param {Set<TranslationsSource>} [sources=this._sources] Optional set of sources to update.
|
|
283
|
+
* @returns {Observable<void>} An observable that completes when the translations are updated.
|
|
284
|
+
*/
|
|
285
|
+
updateTranslations$(language, sources = this._sources) {
|
|
286
|
+
const observables = this._getUpdateTranslationsObservables(language, sources);
|
|
287
|
+
return forkJoin(Array.from(observables)).pipe(defaultIfEmpty([]), map((responses) => {
|
|
288
|
+
this.translations.update(() => this._mergeMaps(this.translations(), ...responses));
|
|
289
|
+
this.loggerService.info(`${this.constructor.name} - Setup - Translations set`, this.translations());
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* {@link ITranslationsService.translate}
|
|
294
|
+
*/
|
|
295
|
+
translate(key) {
|
|
296
|
+
const keyExists = this.translations().has(key);
|
|
297
|
+
if (!keyExists) {
|
|
298
|
+
this.loggerService.warn(`${this.constructor.name} - '${key}' translation key does not exist.`);
|
|
299
|
+
return key;
|
|
300
|
+
}
|
|
301
|
+
const value = this.translations().get(key);
|
|
302
|
+
if (value === '' || value == null) {
|
|
303
|
+
this.loggerService.warn(`${this.constructor.name} - '${key}' translation value is missing.`);
|
|
304
|
+
return key;
|
|
305
|
+
}
|
|
306
|
+
if (value === TRANSLATION_DELIBERATELY_UNSET) {
|
|
307
|
+
return key;
|
|
308
|
+
}
|
|
309
|
+
return value.replace('\\n', '\n');
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Merge all translations update observables into a single set object
|
|
313
|
+
* @param {Locales} language required language
|
|
314
|
+
* @param {string} sources source object
|
|
315
|
+
* @returns {Set<Observable<Map<string, string>>>} a set of translations
|
|
316
|
+
* @private
|
|
317
|
+
*/
|
|
318
|
+
_getUpdateTranslationsObservables(language, sources) {
|
|
319
|
+
const observables = new Set();
|
|
320
|
+
sources.forEach((source) => {
|
|
321
|
+
const observable = this._fetchTranslations$(language, source);
|
|
322
|
+
observables.add(observable);
|
|
323
|
+
});
|
|
324
|
+
return observables;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Fetch translations process, from JSON file path or API url
|
|
328
|
+
* @param {Locales} language required language
|
|
329
|
+
* @param {string} source source object
|
|
330
|
+
* @returns {Observable<Map<string, string>>} a map with all required translations
|
|
331
|
+
* @private
|
|
332
|
+
*/
|
|
333
|
+
_fetchTranslations$(language, source) {
|
|
334
|
+
const request = {
|
|
335
|
+
key: `translations:${language}:${source}`,
|
|
336
|
+
url: `${source}`,
|
|
337
|
+
};
|
|
338
|
+
const observable = this.httpService
|
|
339
|
+
.get(request)
|
|
340
|
+
.pipe(map((response) => this._filterTranslations(language, response)));
|
|
341
|
+
return observable;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* API fetched translations are already filtered correctly by current language.
|
|
345
|
+
* JSON are not. Treat them equally and filter out unneeded translations.
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* Generic JSON response is of type
|
|
349
|
+
* {
|
|
350
|
+
* key1: {
|
|
351
|
+
* fr: 'frenchTranslatedValue',
|
|
352
|
+
* en: 'englishTranslatedValue',
|
|
353
|
+
* it: 'italianTranslatedValue'
|
|
354
|
+
* ...
|
|
355
|
+
* },
|
|
356
|
+
* key2: {
|
|
357
|
+
* fr: 'frenchTranslatedValue',
|
|
358
|
+
* en: 'englishTranslatedValue',
|
|
359
|
+
* it: 'italianTranslatedValue'
|
|
360
|
+
* ...
|
|
361
|
+
* },
|
|
362
|
+
* ...
|
|
363
|
+
* }
|
|
364
|
+
*
|
|
365
|
+
* If language is 'en', then original response will be filtered as follow
|
|
366
|
+
*
|
|
367
|
+
* {
|
|
368
|
+
* key1: 'englishTranslatedValue',
|
|
369
|
+
* key2: 'englishTranslatedValue',
|
|
370
|
+
* ...
|
|
371
|
+
* }
|
|
372
|
+
*
|
|
373
|
+
* @param {Locales} language filter translations upon this language code
|
|
374
|
+
* @param {any} response fetched translations
|
|
375
|
+
* @returns {Translations} this source filtered translations
|
|
376
|
+
* @private
|
|
377
|
+
*/
|
|
378
|
+
_filterTranslations(language, response) {
|
|
379
|
+
const isoCode = LOCALE_ISO_CODES[language];
|
|
380
|
+
const translations = new Map(Object.entries(Object.entries(response)
|
|
381
|
+
.map(([key, value]) => {
|
|
382
|
+
const val = value[isoCode];
|
|
383
|
+
return { [key]: val };
|
|
384
|
+
})
|
|
385
|
+
.reduce((acc, translation) => Object.assign(acc, translation), {})));
|
|
386
|
+
return translations;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Merge multiple map objects into single map
|
|
390
|
+
* @param {Map<K, V>[]} maps maps to merge
|
|
391
|
+
* @returns {Map<K, V>} merged maps
|
|
392
|
+
* @private
|
|
393
|
+
*/
|
|
394
|
+
_mergeMaps(...maps) {
|
|
395
|
+
const mergedMap = new Map();
|
|
396
|
+
maps.forEach((map) => {
|
|
397
|
+
map.forEach((value, key) => {
|
|
398
|
+
mergedMap.set(key, value);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
return mergedMap;
|
|
402
|
+
}
|
|
403
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TranslationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
404
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TranslationsService, providedIn: 'root' });
|
|
405
|
+
}
|
|
406
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TranslationsService, decorators: [{
|
|
407
|
+
type: Injectable,
|
|
408
|
+
args: [{
|
|
409
|
+
providedIn: 'root',
|
|
410
|
+
}]
|
|
411
|
+
}] });
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Pipe that transforms a key into a translated string based on the provided language.
|
|
415
|
+
* It allows to avoid calling function {@link TranslationsService.translate} in template
|
|
416
|
+
* (highly inefficient for function being triggered on every changeDetection), exploiting
|
|
417
|
+
* signal powerness.
|
|
418
|
+
*/
|
|
419
|
+
class TranslatePipe {
|
|
420
|
+
/**
|
|
421
|
+
* @type {TranslationsService}
|
|
422
|
+
*/
|
|
423
|
+
translationsService = inject(TranslationsService);
|
|
424
|
+
/**
|
|
425
|
+
* Transforms the given key into the corresponding translation.
|
|
426
|
+
*
|
|
427
|
+
* @param {string} key The key for the translation.
|
|
428
|
+
* @returns {string} The translated string.
|
|
429
|
+
*/
|
|
430
|
+
transform(key) {
|
|
431
|
+
return this.translationsService.translate(key);
|
|
432
|
+
}
|
|
433
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
434
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: TranslatePipe, isStandalone: true, name: "translate" });
|
|
435
|
+
}
|
|
436
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TranslatePipe, decorators: [{
|
|
437
|
+
type: Pipe,
|
|
438
|
+
args: [{
|
|
439
|
+
name: 'translate',
|
|
440
|
+
standalone: true,
|
|
441
|
+
}]
|
|
442
|
+
}] });
|
|
443
|
+
|
|
444
|
+
var languages = [
|
|
445
|
+
"fr",
|
|
446
|
+
"en",
|
|
447
|
+
"it"
|
|
448
|
+
];
|
|
449
|
+
var defaultConfigJSON$1 = {
|
|
450
|
+
languages: languages
|
|
451
|
+
};
|
|
452
|
+
|
|
1
453
|
/**
|
|
2
|
-
*
|
|
454
|
+
* Default language
|
|
3
455
|
*/
|
|
4
|
-
const
|
|
456
|
+
const DEFAULT_LANGUAGE = LOCALES.ENGLISH_UNITED_KINGDOM;
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* A service that manages different languages operations.
|
|
460
|
+
*/
|
|
461
|
+
class LanguageService {
|
|
462
|
+
/**
|
|
463
|
+
* Boolean value triggered when there is an ongoing language change process
|
|
464
|
+
*/
|
|
465
|
+
isLoadingLanguage = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingLanguage" }] : /* istanbul ignore next */ []));
|
|
466
|
+
/**
|
|
467
|
+
* Allowed locales
|
|
468
|
+
*/
|
|
469
|
+
allowedLocales = new Set();
|
|
470
|
+
/**
|
|
471
|
+
* Derived signal for the current language/locale from the user service.
|
|
472
|
+
* Updates automatically when user's locale changes.
|
|
473
|
+
*/
|
|
474
|
+
currentLanguage = computed(() => this.userService.locale(), ...(ngDevMode ? [{ debugName: "currentLanguage" }] : /* istanbul ignore next */ []));
|
|
475
|
+
/**
|
|
476
|
+
* {@link LoggerService}
|
|
477
|
+
*/
|
|
478
|
+
loggerService = inject(LoggerService);
|
|
479
|
+
/**
|
|
480
|
+
* {@link TranslationsService}
|
|
481
|
+
*/
|
|
482
|
+
translationsService = inject(TranslationsService);
|
|
483
|
+
/**
|
|
484
|
+
* {@link UserService}
|
|
485
|
+
*/
|
|
486
|
+
userService = inject(UserService$1);
|
|
487
|
+
/**
|
|
488
|
+
* Constructor
|
|
489
|
+
* @constructor
|
|
490
|
+
* @ignore
|
|
491
|
+
*/
|
|
492
|
+
constructor() {
|
|
493
|
+
const config = defaultConfigJSON$1;
|
|
494
|
+
const setup = {
|
|
495
|
+
locales: new Set(config['locales']),
|
|
496
|
+
currentLocale: config['currentLocale'],
|
|
497
|
+
};
|
|
498
|
+
this.setup$(setup).pipe(take(1)).subscribe();
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* {@link ILanguageService.setup$}
|
|
502
|
+
*/
|
|
503
|
+
setup$(setup) {
|
|
504
|
+
return defer(() => {
|
|
505
|
+
if (setup.locales !== null && setup.locales !== undefined) {
|
|
506
|
+
this.setAllowedLanguages(setup.locales);
|
|
507
|
+
}
|
|
508
|
+
return setup.currentLocale !== null && setup.currentLocale !== undefined
|
|
509
|
+
? this.setLanguage$(setup.currentLocale)
|
|
510
|
+
: this.setLanguage$();
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* {@link ILanguageService.setAllowedLanguages}
|
|
515
|
+
*/
|
|
516
|
+
setAllowedLanguages(locales) {
|
|
517
|
+
this.allowedLocales = locales;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* {@link ILanguageService.setLanguage$}
|
|
521
|
+
*/
|
|
522
|
+
setLanguage$(locale = undefined) {
|
|
523
|
+
if (locale !== null && locale !== undefined && this._isLanguageAllowed(locale) === false) {
|
|
524
|
+
this.loggerService.warn(`'${locale}' is not among valid locales. Going for auto locale.`);
|
|
525
|
+
locale = undefined;
|
|
526
|
+
}
|
|
527
|
+
if (locale === null || locale === undefined) {
|
|
528
|
+
locale = this._pickAutoLanguage();
|
|
529
|
+
this.loggerService.warn(`Auto locale set. Picked '${locale}'.`);
|
|
530
|
+
}
|
|
531
|
+
if (this._isLanguageAlreadySet(locale)) {
|
|
532
|
+
this.loggerService.warn(`'${locale}' locale is already set.`);
|
|
533
|
+
return EMPTY;
|
|
534
|
+
}
|
|
535
|
+
const resolvedLocale = locale;
|
|
536
|
+
return of(() => { this._toggleIsLoadingLanguage(true); }).pipe(tap(() => { this._updateUserLanguage(resolvedLocale); }), switchMap(() => this._updateTranslations$()), tap(() => { this._toggleIsLoadingLanguage(false); }));
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Update user locale and sync
|
|
540
|
+
* @param {Locales} locale locale to set
|
|
541
|
+
* @returns {void}
|
|
542
|
+
* @private
|
|
543
|
+
*/
|
|
544
|
+
_updateUserLanguage(locale) {
|
|
545
|
+
this.userService.setLocale(locale);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Update app translations according to current user locale
|
|
549
|
+
* @returns {Observable<void>}
|
|
550
|
+
* @private
|
|
551
|
+
*/
|
|
552
|
+
_updateTranslations$() {
|
|
553
|
+
const locale = this.userService.locale();
|
|
554
|
+
if (locale === null || locale === undefined) {
|
|
555
|
+
this.loggerService.error(`User locale is not set. Cannot update translations.`);
|
|
556
|
+
return EMPTY;
|
|
557
|
+
}
|
|
558
|
+
return this.translationsService.updateTranslations$(locale);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Check if locale is already set
|
|
562
|
+
* @param {Locales} locale locale to set
|
|
563
|
+
* @returns {boolean} true if provided locale is already set
|
|
564
|
+
* @private
|
|
565
|
+
*/
|
|
566
|
+
_isLanguageAlreadySet(locale) {
|
|
567
|
+
return locale === this.userService.locale();
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Check if locale is among allowed ones
|
|
571
|
+
* @param {Locales} locale locale to set
|
|
572
|
+
* @returns {boolean} true if provided locale is among allowed ones
|
|
573
|
+
* @private
|
|
574
|
+
*/
|
|
575
|
+
_isLanguageAllowed(locale) {
|
|
576
|
+
return this.allowedLocales.has(locale);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Pick a valid locale automatically.
|
|
580
|
+
* Try to get it from:
|
|
581
|
+
* 1. user locale
|
|
582
|
+
* 2. navigator locale
|
|
583
|
+
* 3. default locale (en)
|
|
584
|
+
* 4. first allowed locale
|
|
585
|
+
* @returns {Locales} allowed locale according to rules above.
|
|
586
|
+
* @private
|
|
587
|
+
*/
|
|
588
|
+
_pickAutoLanguage() {
|
|
589
|
+
let language = this.userService.locale();
|
|
590
|
+
if (language === null || language === undefined || !this._isLanguageAllowed(language)) {
|
|
591
|
+
language = this._getNavigatorLanguage();
|
|
592
|
+
if (!this._isLanguageAllowed(language)) {
|
|
593
|
+
language = DEFAULT_LANGUAGE;
|
|
594
|
+
if (!this._isLanguageAllowed(language)) {
|
|
595
|
+
language = [...this.allowedLocales][0];
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return language;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Get navigator language
|
|
603
|
+
* @returns {Locales} navigator language 'it'/'fr'
|
|
604
|
+
* @private
|
|
605
|
+
*/
|
|
606
|
+
_getNavigatorLanguage() {
|
|
607
|
+
const navigatorLocale = this._getNavigatorLocale();
|
|
608
|
+
return (navigatorLocale.includes('-')
|
|
609
|
+
? navigatorLocale.split('-')[0]
|
|
610
|
+
: navigatorLocale);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Get navigator locale
|
|
614
|
+
* @returns {string} navigator locale 'it-IT'/'fr-FR'
|
|
615
|
+
* @private
|
|
616
|
+
*/
|
|
617
|
+
_getNavigatorLocale() {
|
|
618
|
+
return window.navigator.language;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Toggle isLoadingNewLanguage property
|
|
622
|
+
* @param {boolean | null} value value to set
|
|
623
|
+
* @returns {void}
|
|
624
|
+
* @private
|
|
625
|
+
*/
|
|
626
|
+
_toggleIsLoadingLanguage(value) {
|
|
627
|
+
value = value ?? !this.isLoadingLanguage();
|
|
628
|
+
this.isLoadingLanguage.set(value);
|
|
629
|
+
}
|
|
630
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: LanguageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
631
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: LanguageService, providedIn: 'root' });
|
|
632
|
+
}
|
|
633
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: LanguageService, decorators: [{
|
|
634
|
+
type: Injectable,
|
|
635
|
+
args: [{
|
|
636
|
+
providedIn: 'root',
|
|
637
|
+
}]
|
|
638
|
+
}], ctorParameters: () => [] });
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* User levels constants.
|
|
642
|
+
*/
|
|
643
|
+
const USER_LEVELS = {
|
|
644
|
+
GUEST: 'guest',
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
var language = {
|
|
648
|
+
username: "user",
|
|
649
|
+
updateAPI: ""
|
|
650
|
+
};
|
|
651
|
+
var defaultConfigJSON = {
|
|
652
|
+
language: language
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const DEFAULT_USERNAME = 'guest';
|
|
656
|
+
const DEFAULT_LEVEL = 'guest';
|
|
657
|
+
|
|
658
|
+
class UserService {
|
|
659
|
+
/**
|
|
660
|
+
* {@link IUserService.username}
|
|
661
|
+
*/
|
|
662
|
+
username = signal(undefined, ...(ngDevMode ? [{ debugName: "username" }] : /* istanbul ignore next */ []));
|
|
663
|
+
/**
|
|
664
|
+
* {@link IUserService.token}
|
|
665
|
+
*/
|
|
666
|
+
token = signal(undefined, ...(ngDevMode ? [{ debugName: "token" }] : /* istanbul ignore next */ []));
|
|
667
|
+
/**
|
|
668
|
+
* @see IUserService.locale
|
|
669
|
+
*/
|
|
670
|
+
locale = signal(undefined, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
671
|
+
/**
|
|
672
|
+
* Derived signal that indicates whether the user is logged in.
|
|
673
|
+
* Returns true if both username and token are set and not empty.
|
|
674
|
+
*/
|
|
675
|
+
isLoggedIn = computed(() => {
|
|
676
|
+
const username = this.username();
|
|
677
|
+
const token = this.token();
|
|
678
|
+
return (username !== null &&
|
|
679
|
+
username !== undefined &&
|
|
680
|
+
username !== STRING_EMPTY &&
|
|
681
|
+
token !== null &&
|
|
682
|
+
token !== undefined &&
|
|
683
|
+
token !== STRING_EMPTY);
|
|
684
|
+
}, ...(ngDevMode ? [{ debugName: "isLoggedIn" }] : /* istanbul ignore next */ []));
|
|
685
|
+
/**
|
|
686
|
+
* Constructor
|
|
687
|
+
* @constructor
|
|
688
|
+
* @ignore
|
|
689
|
+
*/
|
|
690
|
+
constructor() {
|
|
691
|
+
this.initialize();
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* @see IUserService.setup$
|
|
695
|
+
*/
|
|
696
|
+
setup$(setup) {
|
|
697
|
+
this.reset();
|
|
698
|
+
this.setUsername(setup.username);
|
|
699
|
+
return of(undefined);
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* @see IUserService.reset
|
|
703
|
+
*/
|
|
704
|
+
reset() {
|
|
705
|
+
this.initialize();
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* @see IUserService.setUsername
|
|
709
|
+
*/
|
|
710
|
+
setUsername(username) {
|
|
711
|
+
this.username.set(username);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* @see IUserService.setLocale
|
|
715
|
+
*/
|
|
716
|
+
setLocale(locale) {
|
|
717
|
+
this.locale.set(locale);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Initialization method
|
|
721
|
+
*
|
|
722
|
+
* @returns {void}
|
|
723
|
+
*/
|
|
724
|
+
initialize() {
|
|
725
|
+
const config = defaultConfigJSON;
|
|
726
|
+
const setup = {
|
|
727
|
+
username: config.language?.username ?? DEFAULT_USERNAME,
|
|
728
|
+
level: DEFAULT_LEVEL,
|
|
729
|
+
api: {
|
|
730
|
+
patchUserDataAPIUrl: config.language?.updateAPI ?? STRING_EMPTY,
|
|
731
|
+
},
|
|
732
|
+
};
|
|
733
|
+
this.setup$(setup).pipe(take(1)).subscribe();
|
|
734
|
+
}
|
|
735
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: UserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
736
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: UserService, providedIn: 'root' });
|
|
737
|
+
}
|
|
738
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: UserService, decorators: [{
|
|
739
|
+
type: Injectable,
|
|
740
|
+
args: [{
|
|
741
|
+
providedIn: 'root',
|
|
742
|
+
}]
|
|
743
|
+
}], ctorParameters: () => [] });
|
|
5
744
|
|
|
6
745
|
/**
|
|
7
746
|
* Generated bundle index. Do not edit.
|
|
8
747
|
*/
|
|
9
748
|
|
|
10
|
-
export {
|
|
749
|
+
export { HttpService, LanguageService, TranslatePipe, TranslationsService, USER_LEVELS, UserService, authorizationInterceptor, logRequestInterceptor, requestTimestampInterceptor, unauthorizedInterceptor };
|
|
11
750
|
//# sourceMappingURL=albi_scando-as-backbone-lib.mjs.map
|