Skip to content

Commit a8fe562

Browse files
committed
fix-Present the GlobalisationCode and translated it to the local languages as part of the message
modified: src/app/core/error-handler/error-handler.service.ts modified: src/app/core/http/error-handler.interceptor.ts
1 parent fc579f5 commit a8fe562

15 files changed

Lines changed: 136 additions & 22 deletions

File tree

src/app/core/error-handler/error-handler.service.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,24 @@ import { MatSnackBar } from '@angular/material/snack-bar';
2929
import { HttpErrorResponse } from '@angular/common/http';
3030
import { Observable, throwError } from 'rxjs';
3131
import { Router } from '@angular/router';
32+
import { TranslateService } from '@ngx-translate/core';
3233

3334
export interface ErrorMessage {
3435
title: string;
3536
message: string;
3637
action?: string;
3738
}
3839

40+
export interface FineractErrorDetail {
41+
userMessageGlobalisationCode?: string;
42+
defaultUserMessage?: string;
43+
}
44+
export interface FineractErrorResponse {
45+
userMessageGlobalisationCode?: string;
46+
defaultUserMessage?: string;
47+
errors?: FineractErrorDetail[];
48+
}
49+
3950
/**
4051
* Centralized error handler service for consistent error messaging
4152
* across the application. Converts HTTP errors into user-friendly
@@ -49,7 +60,9 @@ export class ErrorHandlerService {
4960
// eslint-disable-next-line @angular-eslint/prefer-inject
5061
private snackBar: MatSnackBar,
5162
// eslint-disable-next-line @angular-eslint/prefer-inject
52-
private router: Router
63+
private router: Router,
64+
// eslint-disable-next-line @angular-eslint/prefer-inject
65+
private translate: TranslateService
5366
) {}
5467

5568
/**
@@ -189,4 +202,58 @@ export class ErrorHandlerService {
189202
panelClass: ['info-snackbar']
190203
});
191204
}
205+
206+
/**
207+
* Translates a Fineract error response into a single, translated message.
208+
* Falls back to defaultUserMessage when translation keys are missing.
209+
*/
210+
translateFineractError(errorResponse: FineractErrorResponse | null | undefined): string {
211+
if (!errorResponse || typeof errorResponse !== 'object') {
212+
return '';
213+
}
214+
215+
const messages: string[] = [];
216+
217+
if (errorResponse.userMessageGlobalisationCode) {
218+
const mainMsg = this.getMessageForCode(
219+
errorResponse.userMessageGlobalisationCode,
220+
errorResponse.defaultUserMessage
221+
);
222+
if (mainMsg) {
223+
messages.push(mainMsg);
224+
}
225+
} else if (errorResponse.defaultUserMessage) {
226+
messages.push(errorResponse.defaultUserMessage);
227+
}
228+
if (Array.isArray(errorResponse.errors)) {
229+
errorResponse.errors.forEach((error: FineractErrorDetail) => {
230+
if (!error || typeof error !== 'object') {
231+
return;
232+
}
233+
if (error.userMessageGlobalisationCode) {
234+
const nestedMsg = this.getMessageForCode(error.userMessageGlobalisationCode, error.defaultUserMessage);
235+
if (nestedMsg) {
236+
messages.push(nestedMsg);
237+
}
238+
} else if (error.defaultUserMessage) {
239+
messages.push(error.defaultUserMessage);
240+
}
241+
});
242+
}
243+
const uniqueMessages = Array.from(new Set(messages.filter((m) => !!m && typeof m === 'string')));
244+
return uniqueMessages.join(' ');
245+
}
246+
247+
private getMessageForCode(code: string, defaultMessage?: string): string {
248+
if (!code) {
249+
return defaultMessage || '';
250+
}
251+
252+
const translated = this.translate.instant(code);
253+
if (translated && translated !== code) {
254+
return translated;
255+
}
256+
257+
return defaultMessage || '';
258+
}
192259
}

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

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ 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';
24+
import { ErrorHandlerService } from '../error-handler/error-handler.service';
2425

2526
/** Initialize Logger */
2627
const log = new Logger('ErrorHandlerInterceptor');
@@ -32,6 +33,7 @@ const log = new Logger('ErrorHandlerInterceptor');
3233
export class ErrorHandlerInterceptor implements HttpInterceptor {
3334
private alertService = inject(AlertService);
3435
private translate = inject(TranslateService);
36+
private errorHandlerService = inject(ErrorHandlerService);
3537

3638
/**
3739
* Intercepts a Http request and adds a default error handler.
@@ -45,33 +47,49 @@ export class ErrorHandlerInterceptor implements HttpInterceptor {
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-
if (response.error.errors[0]) {
51-
errorMessage = response.error.errors[0].defaultUserMessage || response.error.errors[0].developerMessage;
52-
}
53-
}
50+
const errorBody: any = response.error;
51+
const translatedErrorMessage = this.errorHandlerService.translateFineractError(errorBody);
52+
const developerMessage: string | undefined = errorBody?.developerMessage;
53+
const errorMessage = translatedErrorMessage || errorBody?.defaultUserMessage || response.message;
5454

5555
const isClientImage404 = status === 404 && request.url.includes('/clients/') && request.url.includes('/images');
5656

5757
if (!environment.production && !isClientImage404) {
58+
if (developerMessage) {
59+
log.error(`Request Error (developerMessage): ${developerMessage}`);
60+
}
5861
log.error(`Request Error: ${errorMessage}`);
5962
}
6063

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!' });
65-
} else if (status === 400) {
64+
// OAuth2 errors for invalid grants are returned as 400, so we need to check the URL.
65+
if (status === 401 || (environment.oauth.enabled && status === 400 && request.url.includes('/oauth/token'))) {
6666
this.alertService.alert({
67-
type: 'Bad Request',
68-
message: errorMessage || 'Invalid parameters were passed in the request!'
67+
type: this.translate.instant('error.resource.authenticationError.type'),
68+
message: this.translate.instant('error.resource.authenticationError.message')
6969
});
70-
} else if (status === 403) {
70+
} else if (status === 400) {
7171
this.alertService.alert({
72-
type: 'Unauthorized Request',
73-
message: errorMessage || 'You are not authorized for this request!'
72+
type: this.translate.instant('error.resource.badRequest.type'),
73+
message: errorMessage || this.translate.instant('error.resource.badRequest.message')
7474
});
75+
} else if (status === 403) {
76+
// The token check must use a stable identifier, not the translated message.
77+
const isInvalidToken =
78+
errorBody?.userMessageGlobalisationCode === 'error.msg.invalid.onetime.token' ||
79+
errorBody?.defaultUserMessage === 'The provided one time token is invalid' ||
80+
errorMessage === 'The provided one time token is invalid';
81+
82+
if (isInvalidToken) {
83+
this.alertService.alert({
84+
type: this.translate.instant('error.resource.invalidToken.type'),
85+
message: this.translate.instant('error.resource.invalidToken.message')
86+
});
87+
} else {
88+
this.alertService.alert({
89+
type: this.translate.instant('error.resource.unauthorizedRequest.type'),
90+
message: errorMessage || this.translate.instant('error.resource.unauthorizedRequest.message')
91+
});
92+
}
7593
} else if (status === 404) {
7694
// Check if this is an image request that should be silently handled (client profile image)
7795
if (isClientImage404) {
@@ -81,21 +99,24 @@ export class ErrorHandlerInterceptor implements HttpInterceptor {
8199
} else {
82100
this.alertService.alert({
83101
type: this.translate.instant('error.resource.not.found'),
84-
message: errorMessage || 'Resource does not exist!'
102+
message: errorMessage || this.translate.instant('error.resource.notFound.message')
85103
});
86104
}
87105
} else if (status === 500) {
88106
this.alertService.alert({
89-
type: 'Internal Server Error',
90-
message: 'Internal Server Error. Please try again later.'
107+
type: this.translate.instant('error.resource.internalServerError.type'),
108+
message: this.translate.instant('error.resource.internalServerError.message')
91109
});
92110
} else if (status === 501) {
93111
this.alertService.alert({
94112
type: this.translate.instant('error.resource.notImplemented.type'),
95113
message: this.translate.instant('error.resource.notImplemented.message')
96114
});
97115
} else {
98-
this.alertService.alert({ type: 'Unknown Error', message: 'Unknown Error. Please try again later.' });
116+
this.alertService.alert({
117+
type: this.translate.instant('error.resource.unknownError.type'),
118+
message: errorMessage || this.translate.instant('error.resource.unknownError.message')
119+
});
99120
}
100121

101122
throw response;

src/assets/translations/cs-CS.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"Remember me": "Zapamatuj si mě",
55
"error.resource.notImplemented.type": "Nenaimplementovaná chyba",
66
"error.resource.notImplemented.message": "Funkce není implementována!",
7+
"error.msg.platform.service.unavailable": "Server je v současné chvíli neschopen zpracovat požadavek. Zkuste to prosím později.",
8+
"error.msg.database.type.not.supported": "Datový typ není podporován",
79
"errors": {
810
"accountingRule": {
911
"duplicateName": "Omlouváme se, ale účetní pravidlo s tímto názvem již existuje."

src/assets/translations/de-DE.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
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.msg.platform.service.unavailable": "Der Server kann die Anfrage derzeit nicht bearbeiten. Bitte versuchen Sie es später erneut.",
8+
"error.msg.database.type.not.supported": "Der Datentyp wird nicht unterstützt",
79
"errors": {
810
"accountingRule": {
911
"duplicateName": "Entschuldigung, aber eine Buchungsregel mit diesem Namen existiert bereits."

src/assets/translations/en-US.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"Remember me": "Remember me",
55
"error.resource.notImplemented.type": "Not Implemented Error",
66
"error.resource.notImplemented.message": "Not implemented functionality!",
7+
"error.msg.platform.service.unavailable": "The server is currently unable to handle the request, please try again after some time.",
8+
"error.msg.database.type.not.supported": "Data type is not supported",
79
"errors": {
810
"accountingRule": {
911
"duplicateName": "Sorry, but an Accounting Rule with this Name exists already."

src/assets/translations/es-CL.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"APP_NAME": "Mifos® X WebApp",
33
"Logged in as": "Conectado como",
44
"Remember me": "Recordar me",
5+
"error.msg.platform.service.unavailable": "El servidor no puede procesar la solicitud en este momento. Por favor, inténtelo de nuevo más tarde.",
6+
"error.msg.database.type.not.supported": "El tipo de dato no es compatible.",
57
"errors": {
68
"accountingRule": {
79
"duplicateName": "Lo sentimos, pero ya existe una regla contable con este nombre."

src/assets/translations/es-MX.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"APP_NAME": "Mifos® X WebApp",
33
"Logged in as": "Conectado como",
44
"Remember me": "Recordar me",
5+
"error.msg.platform.service.unavailable": "El servidor no puede procesar la solicitud en este momento. Por favor, intente más tarde.",
6+
"error.msg.database.type.not.supported": "El tipo de datos no es compatible",
57
"errors": {
68
"accountingRule": {
79
"duplicateName": "Lo sentimos, pero ya existe una regla contable con este nombre."

src/assets/translations/fr-FR.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"APP_NAME": "Mifos® X WebApp",
33
"Logged in as": "connecté en tant que",
44
"Remember me": "Souviens-toi de moi",
5+
"error.msg.platform.service.unavailable": "Le serveur est actuellement incapable de traiter la requête, veuillez réessayer ultérieurement.",
6+
"error.msg.database.type.not.supported": "Le type de données n'est pas pris en charge",
57
"errors": {
68
"accountingRule": {
79
"duplicateName": "Désolé, mais une règle comptable portant ce nom existe déjà."

src/assets/translations/it-IT.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"APP_NAME": "Mifos® X WebApp",
33
"Logged in as": "Collegato come",
44
"Remember me": "Ricordati di me",
5+
"error.msg.platform.service.unavailable": "Il server non può elaborare la richiesta in questo momento. Si prega di riprovare più tardi.",
6+
"error.msg.database.type.not.supported": "Il tipo di dati non è supportato",
57
"errors": {
68
"accountingRule": {
79
"duplicateName": "Spiacenti, ma esiste già una regola contabile con questo nome."

src/assets/translations/ko-KO.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"APP_NAME": "Mifos® X WebApp",
33
"Logged in as": "다음 계정으로 로그인됨",
44
"Remember me": "날 기억해",
5+
"error.msg.platform.service.unavailable": "서버가 현재 요청을 처리할 수 없습니다. 나중에 다시 시도해주세요.",
6+
"error.msg.database.type.not.supported": "지원하지 않는 데이터 유형입니다",
57
"errors": {
68
"accountingRule": {
79
"duplicateName": "죄송합니다. 이미 동일한 이름의 회계 규칙이 존재합니다."

0 commit comments

Comments
 (0)