Skip to content

Commit 196f3e0

Browse files
committed
fix: WEB-862 Localise the error as per the selected language
1 parent d4fa504 commit 196f3e0

File tree

16 files changed

+344
-400
lines changed

16 files changed

+344
-400
lines changed

package-lock.json

Lines changed: 91 additions & 368 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/core/http/error-handler.interceptor.ts

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Injectable, inject } from '@angular/core';
1111
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
1212

1313
/** rxjs Imports */
14-
import { EMPTY, Observable } from 'rxjs';
14+
import { Observable } from 'rxjs';
1515
import { catchError } from 'rxjs/operators';
1616

1717
/** Environment Configuration */
@@ -20,13 +20,14 @@ import { environment } from '../../../environments/environment';
2020
/** Custom Services */
2121
import { Logger } from '../logger/logger.service';
2222
import { AlertService } from '../alert/alert.service';
23-
import { TranslateService } from '@ngx-translate/core'; // Added import for TranslateService
23+
import { TranslateService } from '@ngx-translate/core';
2424

2525
/** Initialize Logger */
2626
const log = new Logger('ErrorHandlerInterceptor');
2727

2828
/**
2929
* Http Request interceptor to add a default error handler to requests.
30+
* Supports localisation of error messages using Fineract's userMessageGlobalisationCode.
3031
*/
3132
@Injectable()
3233
export class ErrorHandlerInterceptor implements HttpInterceptor {
@@ -42,60 +43,98 @@ export class ErrorHandlerInterceptor implements HttpInterceptor {
4243

4344
/**
4445
* Error handler.
46+
* Uses userMessageGlobalisationCode from Fineract error response to localise error messages.
4547
*/
4648
private handleError(response: HttpErrorResponse, request: HttpRequest<any>): Observable<HttpEvent<any>> {
4749
const status = response.status;
48-
let errorMessage = response.error.developerMessage || response.message;
49-
if (response.error.errors) {
50+
let errorMessage = response.error?.developerMessage || response.message;
51+
let globalisationCode: string | null = null;
52+
53+
// Extract globalisation code and message from Fineract error response
54+
if (response.error?.errors) {
5055
if (response.error.errors[0]) {
56+
globalisationCode = response.error.errors[0].userMessageGlobalisationCode || null;
5157
errorMessage = response.error.errors[0].defaultUserMessage || response.error.errors[0].developerMessage;
5258
}
5359
}
5460

61+
// Also check top-level userMessageGlobalisationCode
62+
if (response.error?.userMessageGlobalisationCode) {
63+
globalisationCode = response.error.userMessageGlobalisationCode;
64+
}
65+
66+
// If we have a globalisation code, try to translate it with variable substitution
67+
if (globalisationCode) {
68+
const translated = this.translate.instant(globalisationCode, response.error?.errors?.[0] || response.error || {});
69+
// Only use translation if the key actually exists (translate returns the key itself if not found)
70+
if (translated !== globalisationCode) {
71+
errorMessage = translated;
72+
}
73+
}
74+
5575
const isClientImage404 = status === 404 && request.url.includes('/clients/') && request.url.includes('/images');
5676

5777
if (!environment.production && !isClientImage404) {
5878
log.error(`Request Error: ${errorMessage}`);
5979
}
6080

61-
if (status === 401 || (environment.oauth.enabled && status === 400)) {
62-
this.alertService.alert({ type: 'Authentication Error', message: 'Invalid User Details. Please try again!' });
63-
} else if (status === 403 && errorMessage === 'The provided one time token is invalid') {
64-
this.alertService.alert({ type: 'Invalid Token', message: 'Invalid Token. Please try again!' });
81+
// Check specific 403 error (invalid token) BEFORE generic 403 (higher priority)
82+
if (status === 403 && globalisationCode === 'error.token.invalid') {
83+
this.alertService.alert({
84+
type: this.translate.instant('error.token.invalid.type'),
85+
message: this.translate.instant('error.token.invalid.message')
86+
});
87+
} else if (status === 401) {
88+
// Allow Fineract translations for 401 errors
89+
this.alertService.alert({
90+
type: this.translate.instant('error.auth.type'),
91+
message: errorMessage || this.translate.instant('error.auth.message')
92+
});
93+
} else if (environment.oauth.enabled && status === 400) {
94+
this.alertService.alert({
95+
type: this.translate.instant('error.auth.type'),
96+
message: this.translate.instant('error.auth.message')
97+
});
6598
} else if (status === 400) {
6699
this.alertService.alert({
67-
type: 'Bad Request',
68-
message: errorMessage || 'Invalid parameters were passed in the request!'
100+
type: this.translate.instant('error.bad.request.type'),
101+
message: errorMessage || this.translate.instant('error.bad.request.message')
69102
});
70103
} else if (status === 403) {
71104
this.alertService.alert({
72-
type: 'Unauthorized Request',
73-
message: errorMessage || 'You are not authorized for this request!'
105+
type: this.translate.instant('error.unauthorized.type'),
106+
message: errorMessage || this.translate.instant('error.unauthorized.message')
74107
});
75108
} else if (status === 404) {
76-
// Check if this is an image request that should be silently handled (client profile image)
77109
if (isClientImage404) {
78-
// Don't show alerts for missing client images
79-
// This is an expected condition, not an error
80-
return EMPTY;
110+
// Return observable of null for missing client images so imaging service can handle gracefully
111+
return new Observable((observer) => {
112+
observer.next(null);
113+
observer.complete();
114+
});
81115
} else {
82116
this.alertService.alert({
83-
type: this.translate.instant('error.resource.not.found'),
84-
message: errorMessage || 'Resource does not exist!'
117+
type: this.translate.instant('error.resource.not.found.type'),
118+
message: errorMessage || this.translate.instant('error.resource.not.found.message')
85119
});
86120
}
87121
} else if (status === 500) {
122+
// Allow Fineract translations for 500 errors
88123
this.alertService.alert({
89-
type: 'Internal Server Error',
90-
message: 'Internal Server Error. Please try again later.'
124+
type: this.translate.instant('error.server.internal.type'),
125+
message: errorMessage || this.translate.instant('error.server.internal.message')
91126
});
92127
} else if (status === 501) {
128+
// Allow Fineract translations for 501 errors
93129
this.alertService.alert({
94130
type: this.translate.instant('error.resource.notImplemented.type'),
95-
message: this.translate.instant('error.resource.notImplemented.message')
131+
message: errorMessage || this.translate.instant('error.resource.notImplemented.message')
96132
});
97133
} else {
98-
this.alertService.alert({ type: 'Unknown Error', message: 'Unknown Error. Please try again later.' });
134+
this.alertService.alert({
135+
type: this.translate.instant('error.unknown.type'),
136+
message: this.translate.instant('error.unknown.message')
137+
});
99138
}
100139

101140
throw response;

src/assets/translations/cs-CS.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44
"Remember me": "Zapamatuj si mě",
55
"error.resource.notImplemented.type": "Nenaimplementovaná chyba",
66
"error.resource.notImplemented.message": "Funkce není implementována!",
7+
"error.auth.type": "Chyba ověřování",
8+
"error.auth.message": "Neplatné údaje o uživateli. Zkuste prosím znovu!",
9+
"error.token.invalid.type": "Neplatný token",
10+
"error.token.invalid.message": "Neplatný token. Zkuste prosím znovu!",
11+
"error.bad.request.type": "Špatný požadavek",
12+
"error.bad.request.message": "V požadavku byly předány neplatné parametry!",
13+
"error.unauthorized.type": "Neautorizovaný požadavek",
14+
"error.unauthorized.message": "Nemáte oprávnění pro tento požadavek!",
15+
"error.resource.not.found.type": "Zdroj nenalezen",
16+
"error.resource.not.found.message": "Zdroj neexistuje!",
17+
"error.server.internal.type": "Interní chyba serveru",
18+
"error.server.internal.message": "Interní chyba serveru. Zkuste prosím později.",
19+
"error.unknown.type": "Neznámá chyba",
20+
"error.unknown.message": "Neznámá chyba. Zkuste prosím později.",
721
"errors": {
822
"accountingRule": {
923
"duplicateName": "Omlouváme se, ale účetní pravidlo s tímto názvem již existuje."

src/assets/translations/de-DE.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44
"Remember me": "Erinnere dich an mich",
55
"error.resource.notImplemented.type": "Nicht implementierter Fehler",
66
"error.resource.notImplemented.message": "Nicht implementierte Funktion!",
7+
"error.auth.type": "Authentifizierungsfehler",
8+
"error.auth.message": "Ungültige Benutzerdetails. Bitte versuchen Sie es erneut!",
9+
"error.token.invalid.type": "Ungültiger Token",
10+
"error.token.invalid.message": "Ungültiger Token. Bitte versuchen Sie es erneut!",
11+
"error.bad.request.type": "Ungültige Anfrage",
12+
"error.bad.request.message": "Ungültige Parameter wurden in der Anfrage übergeben!",
13+
"error.unauthorized.type": "Unbefugte Anfrage",
14+
"error.unauthorized.message": "Sie sind nicht berechtigt für diese Anfrage!",
15+
"error.resource.not.found.type": "Ressource nicht gefunden",
16+
"error.resource.not.found.message": "Die Ressource existiert nicht!",
17+
"error.server.internal.type": "Interner Serverfehler",
18+
"error.server.internal.message": "Interner Serverfehler. Bitte versuchen Sie es später erneut.",
19+
"error.unknown.type": "Unbekannter Fehler",
20+
"error.unknown.message": "Unbekannter Fehler. Bitte versuchen Sie es später erneut.",
721
"errors": {
822
"accountingRule": {
923
"duplicateName": "Entschuldigung, aber eine Buchungsregel mit diesem Namen existiert bereits."

src/assets/translations/en-US.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44
"Remember me": "Remember me",
55
"error.resource.notImplemented.type": "Not Implemented Error",
66
"error.resource.notImplemented.message": "Not implemented functionality!",
7+
"error.auth.type": "Authentication Error",
8+
"error.auth.message": "Invalid User Details. Please try again!",
9+
"error.token.invalid.type": "Invalid Token",
10+
"error.token.invalid.message": "Invalid Token. Please try again!",
11+
"error.bad.request.type": "Bad Request",
12+
"error.bad.request.message": "Invalid parameters were passed in the request!",
13+
"error.unauthorized.type": "Unauthorized Request",
14+
"error.unauthorized.message": "You are not authorized for this request!",
15+
"error.resource.not.found.type": "Resource Not Found",
16+
"error.resource.not.found.message": "Resource does not exist!",
17+
"error.server.internal.type": "Internal Server Error",
18+
"error.server.internal.message": "Internal Server Error. Please try again later.",
19+
"error.unknown.type": "Unknown Error",
20+
"error.unknown.message": "Unknown Error. Please try again later.",
721
"errors": {
822
"accountingRule": {
923
"duplicateName": "Sorry, but an Accounting Rule with this Name exists already."

src/assets/translations/es-CL.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@
88
},
99
"error.resource.notImplemented.type": "Error no implementado",
1010
"error.resource.notImplemented.message": "¡Funcionalidad no implementada!",
11+
"error.auth.type": "Error de autenticación",
12+
"error.auth.message": "Datos de usuario inválidos. ¡Por favor, inténtelo de nuevo!",
13+
"error.token.invalid.type": "Token inválido",
14+
"error.token.invalid.message": "Token inválido. ¡Por favor, inténtelo de nuevo!",
15+
"error.bad.request.type": "Solicitud incorrecta",
16+
"error.bad.request.message": "Se enviaron parámetros no válidos en la solicitud.",
17+
"error.unauthorized.type": "Solicitud no autorizada",
18+
"error.unauthorized.message": "No está autorizado para esta solicitud.",
19+
"error.resource.not.found.type": "Recurso no encontrado",
20+
"error.resource.not.found.message": "¡El recurso no existe!",
21+
"error.server.internal.type": "Error interno del servidor",
22+
"error.server.internal.message": "Error interno del servidor. Por favor, inténtelo más tarde.",
23+
"error.unknown.type": "Error desconocido",
24+
"error.unknown.message": "Error desconocido. Por favor, inténtelo más tarde.",
1125
"linkedSavingsAccountOwnership": "La cuenta de ahorro vinculada no pertenece al cliente seleccionado.",
1226
"clientNotInGSIM": "El cliente con ID {{id}} no está presente en GSIM.",
1327
"Capitalized Income amount adjusted already adjusted": "Monto de Ingreso Capitalizado ajustado ya ajustado",

src/assets/translations/es-MX.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@
88
},
99
"error.resource.notImplemented.type": "Error no implementado",
1010
"error.resource.notImplemented.message": "¡Funcionalidad no implementada!",
11+
"error.auth.type": "Error de autenticación",
12+
"error.auth.message": "Datos de usuario inválidos. ¡Por favor, inténtelo de nuevo!",
13+
"error.token.invalid.type": "Token inválido",
14+
"error.token.invalid.message": "Token inválido. ¡Por favor, inténtelo de nuevo!",
15+
"error.bad.request.type": "Solicitud incorrecta",
16+
"error.bad.request.message": "Se enviaron parámetros no válidos en la solicitud.",
17+
"error.unauthorized.type": "Solicitud no autorizada",
18+
"error.unauthorized.message": "No está autorizado para esta solicitud.",
19+
"error.resource.not.found.type": "Recurso no encontrado",
20+
"error.resource.not.found.message": "¡El recurso no existe!",
21+
"error.server.internal.type": "Error interno del servidor",
22+
"error.server.internal.message": "Error interno del servidor. Por favor, inténtelo más tarde.",
23+
"error.unknown.type": "Error desconocido",
24+
"error.unknown.message": "Error desconocido. Por favor, inténtelo más tarde.",
1125
"linkedSavingsAccountOwnership": "La cuenta de ahorro vinculada no pertenece al cliente seleccionado.",
1226
"clientNotInGSIM": "El cliente con ID {{id}} no está presente en GSIM.",
1327
"Capitalized Income amount adjusted already adjusted": "Monto de Ingreso Capitalizado ajustado ya ajustado",

src/assets/translations/fr-FR.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@
88
},
99
"error.resource.notImplemented.type": "Erreur non implémentée",
1010
"error.resource.notImplemented.message": "Fonctionnalité non implémentée !",
11-
"linkedSavingsAccountOwnership": "Le compte d'épargne lié n'appartient pas au client sélectionné.",
11+
"error.auth.type": "Erreur d'authentification",
12+
"error.auth.message": "Détails utilisateur invalides. Veuillez réessayer!",
13+
"error.token.invalid.type": "Token invalide",
14+
"error.token.invalid.message": "Token invalide. Veuillez réessayer!",
15+
"error.bad.request.type": "Mauvaise demande",
16+
"error.bad.request.message": "Des paramètres invalides ont été transmis dans la demande!",
17+
"error.unauthorized.type": "Demande non autorisée",
18+
"error.unauthorized.message": "Vous n'êtes pas autorisé pour cette demande!",
19+
"error.resource.not.found.type": "Ressource non trouvée",
20+
"error.resource.not.found.message": "La ressource n'existe pas!",
21+
"error.server.internal.type": "Erreur interne du serveur",
22+
"error.server.internal.message": "Erreur interne du serveur. Veuillez réessayer plus tard.",
23+
"error.unknown.type": "Erreur inconnue",
24+
"error.unknown.message": "Erreur inconnue. Veuillez réessayer plus tard.",
1225
"clientNotInGSIM": "Le client avec l'ID {{id}} n'est pas membre du GSIM.",
1326
"Capitalized Income amount adjusted already adjusted": "Montant du revenu capitalisé ajusté déjà ajusté",
1427
"Capitalized Income Adjustment amount must be lower or equal to": "Le montant de l'ajustement du revenu capitalisé doit être inférieur ou égal à",

src/assets/translations/it-IT.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@
88
},
99
"error.resource.notImplemented.type": "Errore non implementato",
1010
"error.resource.notImplemented.message": "Funzionalità non implementata!",
11-
"linkedSavingsAccountOwnership": "Il conto di risparmio collegato non appartiene al cliente selezionato.",
11+
"error.auth.type": "Errore di autenticazione",
12+
"error.auth.message": "Dettagli utente non validi. Per favore riprova!",
13+
"error.token.invalid.type": "Token non valido",
14+
"error.token.invalid.message": "Token non valido. Per favore riprova!",
15+
"error.bad.request.type": "Richiesta non valida",
16+
"error.bad.request.message": "Parametri non validi sono stati trasmessi nella richiesta!",
17+
"error.unauthorized.type": "Richiesta non autorizzata",
18+
"error.unauthorized.message": "Non sei autorizzato per questa richiesta!",
19+
"error.resource.not.found.type": "Risorsa non trovata",
20+
"error.resource.not.found.message": "La risorsa non esiste!",
21+
"error.server.internal.type": "Errore interno del server",
22+
"error.server.internal.message": "Errore interno del server. Per favore riprova più tardi.",
23+
"error.unknown.type": "Errore sconosciuto",
24+
"error.unknown.message": "Errore sconosciuto. Per favore riprova più tardi.",
1225
"clientNotInGSIM": "Il cliente con ID {{id}} non è presente in GSIM.",
1326
"Capitalized Income amount adjusted already adjusted": "Importo del reddito capitalizzato rettificato già rettificato",
1427
"Capitalized Income Adjustment amount must be lower or equal to": "L'importo dell'adeguamento del reddito capitalizzato deve essere inferiore o uguale a",

src/assets/translations/ko-KO.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@
88
},
99
"error.resource.notImplemented.type": "구현되지 않은 오류",
1010
"error.resource.notImplemented.message": "구현되지 않은 기능입니다!",
11-
"linkedSavingsAccountOwnership": "연결된 저축 계좌가 선택한 클라이언트에 속하지 않습니다.",
11+
"error.auth.type": "인증 오류",
12+
"error.auth.message": "잘못된 사용자 세부정보입니다. 다시 시도해주세요!",
13+
"error.token.invalid.type": "유효하지 않은 토큰",
14+
"error.token.invalid.message": "유효하지 않은 토큰입니다. 다시 시도해주세요!",
15+
"error.bad.request.type": "잘못된 요청",
16+
"error.bad.request.message": "요청에 잘못된 매개변수가 전달되었습니다!",
17+
"error.unauthorized.type": "승인되지 않은 요청",
18+
"error.unauthorized.message": "이 요청에 대해 권한이 없습니다!",
19+
"error.resource.not.found.type": "리소스를 찾을 수 없음",
20+
"error.resource.not.found.message": "리소스가 존재하지 않습니다!",
21+
"error.server.internal.type": "내부 서버 오류",
22+
"error.server.internal.message": "내부 서버 오류입니다. 나중에 다시 시도해주세요.",
23+
"error.unknown.type": "알 수 없는 오류",
24+
"error.unknown.message": "알 수 없는 오류입니다. 나중에 다시 시도해주세요.",
1225
"clientNotInGSIM": "ID가 {{id}}인 클라이언트가 GSIM에 없습니다.",
1326
"Capitalized Income amount adjusted already adjusted": "자본화된 소득 금액은 이미 조정되었습니다.",
1427
"Capitalized Income Adjustment amount must be lower or equal to": "자본화된 소득 조정 금액은 다음보다 낮거나 같아야 합니다.",

0 commit comments

Comments
 (0)