From 5bc7527ce76dcfe3aed62d6f40ffe21d9f5e4b8c Mon Sep 17 00:00:00 2001 From: jmbardallo Date: Wed, 8 Jul 2026 13:54:44 +0200 Subject: [PATCH] feat(user): add language preference to user profile and header - Add language field to JwtPayload interface and getter in AuthService - Header applies JWT language on init (server takes priority over localStorage) - Switching language from header PATCHes preference to API immediately - Add language selector to add/edit user modal (admin) - Add userLanguage and userLanguageHint i18n keys to es/en/cat locales --- .../add-user-modal.component.html | 10 ++++++++ .../add-user-modal.component.ts | 12 +++++++-- .../src/app/layout/header/header.component.ts | 25 +++++++++++++++++-- ogWebconsole/src/app/services/auth.service.ts | 6 +++++ ogWebconsole/src/locale/cat.json | 2 ++ ogWebconsole/src/locale/en.json | 2 ++ ogWebconsole/src/locale/es.json | 2 ++ 7 files changed, 55 insertions(+), 4 deletions(-) diff --git a/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.html b/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.html index 6f90c11..92949bd 100644 --- a/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.html +++ b/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.html @@ -39,6 +39,16 @@ + + {{ 'userLanguage' | translate }} + + + {{ lang.label }} + + + {{ 'userLanguageHint' | translate }} + + {{ 'clientsPerPage' | translate }} diff --git a/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.ts b/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.ts index 308da61..b90e7f3 100644 --- a/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.ts +++ b/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.ts @@ -36,6 +36,11 @@ export class AddUserModalComponent implements OnInit { { value: 'list', name: 'Listado' }, ]; + get availableLanguages() { + const langs = this.configService.languages; + return Object.keys(langs).map(code => ({ code, label: langs[code] })); + } + constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any, @@ -52,6 +57,7 @@ export class AddUserModalComponent implements OnInit { role: ['', Validators.required], groupsView: ['card', Validators.required], clientsPerPage: [null, [Validators.min(1)]], + language: ['es'], organizationalUnits: [[]] }); @@ -87,7 +93,8 @@ export class AddUserModalComponent implements OnInit { role: response.userGroups.length > 0 ? response.userGroups[0]['@id'] : null, organizationalUnits: organizationalUnitIds, groupsView: response.groupsView, - clientsPerPage: response.clientsPerPage ?? null + clientsPerPage: response.clientsPerPage ?? null, + language: response.language ?? 'es' }); this.userId = response['@id']; @@ -114,7 +121,8 @@ export class AddUserModalComponent implements OnInit { enabled: true, userGroups: [this.userForm.value.role], groupsView: this.userForm.value.groupsView, - clientsPerPage: this.userForm.value.clientsPerPage || null + clientsPerPage: this.userForm.value.clientsPerPage || null, + language: this.userForm.value.language || null }; if (!this.userId && this.userForm.value.password) { diff --git a/ogWebconsole/src/app/layout/header/header.component.ts b/ogWebconsole/src/app/layout/header/header.component.ts index 4196b4f..7c19339 100644 --- a/ogWebconsole/src/app/layout/header/header.component.ts +++ b/ogWebconsole/src/app/layout/header/header.component.ts @@ -6,6 +6,7 @@ import { BreakpointObserver } from '@angular/cdk/layout'; import { AuthService } from '@services/auth.service'; import { TranslateService } from '@ngx-translate/core'; import { ConfigService } from '@services/config.service'; +import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-header', @@ -28,13 +29,21 @@ export class HeaderComponent implements OnInit { constructor( public dialog: MatDialog, public auth: AuthService, - private breakpointObserver: BreakpointObserver + private breakpointObserver: BreakpointObserver, + private http: HttpClient ) { } ngOnInit(): void { this.breakpointObserver.observe(['(max-width: 576px)']).subscribe((result) => { this.isSmallScreen = result.matches; - }) + }); + + // Aplicar idioma guardado: el del JWT (servidor) tiene prioridad sobre localStorage + const jwtLang = this.auth.language; + const localLang = localStorage.getItem('language'); + const langToApply = jwtLang || localLang || 'es'; + this.translate.use(langToApply); + localStorage.setItem('language', langToApply); } get availableLanguages() { @@ -66,6 +75,18 @@ export class HeaderComponent implements OnInit { switchLanguage(lang: string) { this.translate.use(lang); localStorage.setItem('language', lang); + + // Persistir en servidor si el usuario está autenticado + const uuid = this.auth.uuid; + if (uuid) { + this.http.patch( + `${this.config.apiUrl}/users/${uuid}`, + { language: lang }, + { headers: { 'Content-Type': 'application/merge-patch+json' } } + ).subscribe({ + error: (err) => console.error('Error saving language preference:', err) + }); + } } getCurrentLanguage(): string { diff --git a/ogWebconsole/src/app/services/auth.service.ts b/ogWebconsole/src/app/services/auth.service.ts index 3c18231..c88d1b0 100644 --- a/ogWebconsole/src/app/services/auth.service.ts +++ b/ogWebconsole/src/app/services/auth.service.ts @@ -18,6 +18,7 @@ interface JwtPayload { uuid: string; groupsView: string; clientsPerPage: number | null; + language: string | null; } @Injectable({ providedIn: 'root' }) @@ -103,6 +104,11 @@ export class AuthService { return this.tokenPayload?.clientsPerPage ?? null; } + /** Idioma preferido del usuario */ + get language(): string | null { + return this.tokenPayload?.language ?? null; + } + /** * Devuelve el rol principal del usuario según prioridad. */ diff --git a/ogWebconsole/src/locale/cat.json b/ogWebconsole/src/locale/cat.json index 973cc78..76153a0 100644 --- a/ogWebconsole/src/locale/cat.json +++ b/ogWebconsole/src/locale/cat.json @@ -238,6 +238,8 @@ "clientSelectStepText": "Selecciona un client per veure les traces associades.", "clientsPerPage": "Clients per pàgina", "clientsPerPageHint": "Nombre de clients mostrats per pàgina al llistat", + "userLanguage": "Idioma preferit", + "userLanguageHint": "Idioma de la interfície per a aquest usuari", "clientSuccessfullyRemovedFromThe": "Client eliminat de la xarxa correctament", "clientsViewStepText": "Visualització dels clients de la unitat organitzativa seleccionada", "clientUpdatedSuccessfully": "Client actualitzat correctament", diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index a1f3b36..f91ed17 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -238,6 +238,8 @@ "clientSelectStepText": "Select a client to see the associated traces.", "clientsPerPage": "Clients per page", "clientsPerPageHint": "Number of clients shown per page in the list", + "userLanguage": "Preferred language", + "userLanguageHint": "Interface language for this user", "clientSuccessfullyRemovedFromThe": "Client successfully removed from the network", "clientsViewStepText": "Display of the selected organizational unit's clients", "clientUpdatedSuccessfully": "Client updated successfully", diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index f77a672..b6b5511 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -238,6 +238,8 @@ "clientSelectStepText": "Selecciona un cliente para ver las trazas asociadas.", "clientsPerPage": "Clientes por página", "clientsPerPageHint": "Número de clientes mostrados por página en el listado", + "userLanguage": "Idioma preferido", + "userLanguageHint": "Idioma de la interfaz para este usuario", "clientSuccessfullyRemovedFromThe": "Cliente eliminado de la red con éxito", "clientsViewStepText": "Visualización de los clientes de la unidad organizativa seleccionada", "clientUpdatedSuccessfully": "Cliente actualizado con éxito",