From 2637f399e2d1f337d63b2d9aa2559d73aca63bfd Mon Sep 17 00:00:00 2001
From: RIHTARSIC Joze <joze.rihtarsic@ext.ec.europa.eu>
Date: Tue, 3 Sep 2024 14:23:40 +0200
Subject: [PATCH] [EDELIVERY-12747] add configuration options for update/create
 user alerts and fix language loading from filesystem

---
 smp-angular/src/assets/i18n/ro.json           | 852 ------------------
 .../smp/config/enums/SMPPropertyEnum.java     |  25 +
 .../init/SMPLocaleFileSystemInitializer.java  |   8 +-
 .../ec/edelivery/smp/i18n/SMPLocale.java      |  47 -
 .../smp/services/ConfigurationService.java    |  30 +
 .../smp/services/CredentialsAlertService.java |  18 +-
 .../services/SMPLanguageResourceService.java  | 286 ++++++
 .../smp/services/SMPLocaleService.java        |  71 --
 .../services/mail/MailTemplateService.java    |  43 +-
 .../src/main/resources/mail-messages/en.json  |  22 +
 .../mail-messages/mail-messages_en.properties |  99 --
 .../SMPLanguageResourceServiceTest.java       | 137 +++
 .../smp/services/mail/MailTemplateTest.java   |  31 +-
 .../META-INF/resources/ui/assets/i18n/en.json |   6 +
 .../smp/ui/external/LocaleController.java     |  34 +-
 .../smp/ui/internal/UserAdminController.java  |   1 +
 16 files changed, 573 insertions(+), 1137 deletions(-)
 delete mode 100644 smp-angular/src/assets/i18n/ro.json
 delete mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/i18n/SMPLocale.java
 create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMPLanguageResourceService.java
 delete mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMPLocaleService.java
 create mode 100644 smp-server-library/src/main/resources/mail-messages/en.json
 delete mode 100644 smp-server-library/src/main/resources/mail-messages/mail-messages_en.properties
 create mode 100644 smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMPLanguageResourceServiceTest.java
 create mode 100644 smp-server-library/src/test/resources/META-INF/resources/ui/assets/i18n/en.json

diff --git a/smp-angular/src/assets/i18n/ro.json b/smp-angular/src/assets/i18n/ro.json
deleted file mode 100644
index c9d1c25a4..000000000
--- a/smp-angular/src/assets/i18n/ro.json
+++ /dev/null
@@ -1,852 +0,0 @@
-{
-
-  "column.selection.link.all": "Tot",
-  "column.selection.link.none": "Nimic",
-  "column.selection.link.show": "Toate coloanele",
-  "column.selection.link.hide": "Ascunde coloanele",
-
-  "cancel.dialog.text": "Doriti sa anulati toate modificarile care nu au fost inca salvate?",
-  "cancel.dialog.title": "Modificari in Curs",
-
-  "certificate.dialog.button.close": "Inchide",
-  "certificate.dialog.title": "Detalii Certificat",
-
-  "confirmation.dialog.button.no": "Nu",
-  "confirmation.dialog.button.yes": "Da",
-
-  "credentials.dialog.button.close": "Inchide",
-  "credentials.dialog.button.copy.access.token": "Copiaza valoarea Tokenului de access",
-  "credentials.dialog.button.import": "Importa",
-  "credentials.dialog.button.generate.access.token": "Genereaza un Token de access",
-  "credentials.dialog.button.save.certificate": "Salveaza Certificatul",
-  "credentials.dialog.error.read.certificate": "O eroare a fost detectata la citirea Certificatului. Verificati daca fisierul incarcat este un Certificat valid",
-  "credentials.dialog.error.upload.certificate": "O eroare a fost detectata la incarcarea Certificatului [{{fileName}}]. {{errorDescription}}",
-  "credentials.dialog.success.generate.token": "Tokenul cu identificatorul: \"{{responseIdentifier}}\" si valoarea: \"{{responseValue}}\" a fost generat!<br /><br />Copiati valoarea Tokenului de access si salvati-o intr-un loc sigur.<br /><br />Nu veti mai putea sa obtineti valoarea Tokenului de access o data ce apasati pe <b>Inchide</b>.",
-  "credentials.dialog.label.active": "Activ",
-  "credentials.dialog.label.certificate.validity.period": "Perioada de valabilitate a Certificatului",
-  "credentials.dialog.label.description": "Descriere",
-  "credentials.dialog.label.subject.name": "Subiect",
-  "credentials.dialog.label.smp.certificate.id": " Identificator certificat SMP",
-  "credentials.dialog.label.invalid.from": "Data \"Activ de la\" invalida",
-  "credentials.dialog.label.invalid.expire.on": "Data \"Expira la\" invalida",
-  "credentials.dialog.label.issuer.name": "Nume emitent",
-  "credentials.dialog.label.serial.number": "Numar de serie",
-  "credentials.dialog.label.validity": "Periada de valabilitate",
-  "credentials.dialog.placeholder.end.date": "Data finala",
-  "credentials.dialog.placeholder.expire.on": "Expira la",
-  "credentials.dialog.placeholder.start.date": "Date initiala",
-  "credentials.dialog.placeholder.valid.from": "Valabil de la",
-
-  "dialog.button.close": "Inchide",
-  "dialog.button.no": "Nu",
-  "dialog.button.ok": "OK",
-  "dialog.button.yes": "Da",
-
-  "document.property.dialog.button.ok": "OK",
-  "document.property.dialog.button.cancel": "Anuleaza",
-  "document.property.dialog.error.property.already.exists": "Proprietatea exista deja!",
-  "document.property.dialog.label.close": "Inchide",
-  "document.property.dialog.label.property.description": "Descrierea proprietatii:",
-  "document.property.dialog.label.property.name": "Numele proprietatii:",
-  "document.property.dialog.label.property.type": "Tipul proprietatii:",
-  "document.property.dialog.label.property.value": "Valoarea proprietatii:",
-  "document.property.dialog.title.new.mode": "Creeaza Propietatea Document",
-  "document.property.dialog.title.edit.mode": "Schimbati Proprietatea Document",
-
-  "expired.password.dialog.text": "Parola dumneavoastra este mai veche de trei luni. Va rugam sa o schimbati cat mai repede posibil!",
-  "expired.password.dialog.title": "Parola pe cale sa expire",
-
-  "manage.members.dialog.button.close": "Inchide",
-  "manage.members.dialog.title.invite.mode": "Invita Membru {{membershipType}}",
-  "manage.members.dialog.title.edit.mode": "Modifica Membru {{membershipType}}",
-
-  "member.dialog.button.close": "Inchide",
-  "member.dialog.button.save": "Salveaza",
-  "member.dialog.hint.choose.role": "Alege rolul membrului",
-  "member.dialog.hint.type.username": "Introduceti numele utilizatorului sau numele complet necesar localizarii utilizatorului si alegeti utilizatorul dorit din lista de rezultate",
-  "member.dialog.label.choose.user": "Alegeti Utilizatorul de invitat",
-  "member.dialog.label.invite.members": "Sunteti pe cale sa invitati membrii la {{target}}.",
-  "member.dialog.placeholder.role.type": "Rolul membrului",
-  "manage.dialog.title.invite.mode": "Invita membrul {{membershipType}}",
-  "manage.dialog.title.edit.mode": "Schimbati membrul {{membershipType}}",
-  "member.dialog.tooltip.role.type": "Tipul rolului detinut de membru.",
-
-  "object.properties.dialog.button.close": "Inchide",
-  "object.properties.dialog.header.key": "Cheie",
-  "object.properties.dialog.header.value": "Valoare",
-
-  "password.change.dialog.button.confirm": "Seteaza/modificati parola",
-  "password.change.dialog.button.close": "Inchide",
-  "password.change.dialog.error.old.password.reused": "Noua parola nu trebuie sa fie egala cu vechea parola actuala!",
-  "password.change.dialog.error.passwords.mismatch": "Confirmare parolei noi nu se potriveste cu noua parola!",
-  "password.change.dialog.label.confirm.new.password": "Confirmarea parolei noi",
-  "password.change.dialog.label.username": "Schimbati parola pentru Utilizator",
-  "password.change.dialog.label.new.password": "Parola noua",
-  "password.change.dialog.label.password.admin": "Parola de administrator pentru [{{username}}]",
-  "password.change.dialog.label.password.user": "Parola actuala",
-  "password.change.dialog.legend.required.fields": "* campuri obligatorii",
-  "password.change.dialog.title": "Seteaza/Schimba Parola",
-  "password.change.dialog.success.password.change.admin": "Parola a fost setata/schimbata cu success.",
-  "password.change.dialog.success.password.change.user": "Parola a fost setata/schimbata cu success. Conectati-va din nou la aplicatie folosind noua parola!",
-
-  "property.details.dialog.button.ok": "OK",
-  "property.details.dialog.button.cancel": "Anuleaza",
-  "property.details.dialog.error.invalid.property": "Proprietate invalida",
-  "property.details.dialog.error.validation": "A aparut o eroare la Validarea proprietatii",
-  "property.details.dialog.label.use.system.default.value": "Utilizati valoarea implicata a Sistemului",
-  "property.details.dialog.label.property.value": "Valoarea Proprietatii:",
-  "property.details.dialog.legend.description": "Descriere:",
-  "property.details.dialog.title.edit.mode": "Modificaarea Proprietatii {{type}}",
-  "property.details.dialog.title.new.mode": "Proprietate Noua {{type}}",
-
-  "session.expiration.dialog.button.expire": "Extinde",
-  "session.expiration.dialog.button.logout": "Deconectare",
-  "session.expiration.dialog.title": "Extindeti sesiunea",
-  "session.expiration.dialog.label.session.about.to.expire": "Sesiunea dvs. este pe cale sa expire in <b>{{timeLeft}}</b> secunde!<br />Doriti sa va deconectati acum sau sa o prelungiti pentru inca <b>{{timeout}}</b> secunde?",
-
-  "alert.panel.title": "Alerte",
-  "alert.panel.user.title": "{{value}} (E-mail: '{{mailTo}}')",
-
-  "certificate.panel.title": "Datele certificatului selectat",
-  "certificate.panel.label.alias": "Alias",
-  "certificate.panel.label.crl": "Adresa URL a listei de revocare a certificatelor",
-  "certificate.panel.label.issuer": "Emitent",
-  "certificate.panel.label.public.key.type": "Tipul cheii publice",
-  "certificate.panel.label.serial.number": "Numar de serie",
-  "certificate.panel.label.smp.certificate.id": "Identificator certificat SMP",
-  "certificate.panel.label.subject.name": "Numele subiectului",
-  "certificate.panel.label.valid.from": "Valabil de la",
-  "certificate.panel.label.valid.to": "Valabil pana la",
-  "certificate.panel.placeholder.alias": "Alias",
-  "certificate.panel.placeholder.crl": "Certificate revocation list URL",
-  "certificate.panel.placeholder.issuer": "Emitent",
-  "certificate.panel.placeholder.public.key.type": "Tipul cheii publice",
-  "certificate.panel.placeholder.smp.certificate.id": "Identificator certificat SMP",
-  "certificate.panel.placeholder.serial.number": "Numar de serie",
-  "certificate.panel.placeholder.subject.name": "Numele subiectului",
-  "certificate.panel.placeholder.valid.from": "Valabil pana la data",
-  "certificate.panel.placeholder.valid.to": "Valabil pana la data",
-
-  "document.properties.panel.label.filter": "Filtru",
-  "document.properties.panel.label.property": "Proprietate",
-  "document.properties.panel.label.value": "Valoare",
-  "document.properties.panel.label.no.properties.found": "Nu s-au gasit proprietati ale Documentului",
-  "document.properties.panel.label.select.page": "Selectati pagina cu proprietati",
-  "document.properties.panel.label.expand.collapse": "Extinde/Restrange",
-  "document.properties.panel.tooltip.expand.collapse": "Extinde/Restrange panoul cu proprietati",
-  "document.properties.panel.label.create": "Creeaza",
-  "document.properties.panel.label.delete.remove": "Sterge",
-  "document.properties.panel.label.edit": "Schimba",
-  "document.properties.panel.label.reset": "Reseteaza modificarile",
-  "document.properties.panel.tooltip.create": "Creeaza proprietate noua",
-  "document.properties.panel.tooltip.edit": "Schimba proprietate",
-  "document.properties.panel.tooltip.delete.remove": "Sterge proprietatea selectata",
-  "document.properties.panel.tooltip.reset": "Reseteaza modificarile",
-
-  "membership.panel.button.invite.member": "Invita membru",
-  "membership.panel.button.edit.membership": "Schimba",
-  "membership.panel.button.delete.membership": "Sterge",
-  "membership.panel.delete.confirmation.dialog.title": "Sterge membru",
-  "membership.panel.delete.confirmation.dialog.description": "Actiunea va sterge membrul [\"{{username}}\"]!<br/><br/>Doriti sa continuati?",
-  "membership.panel.label.filter": "Filtru membri",
-  "membership.panel.label.full.name": "Numele complet",
-  "membership.panel.label.member.of": "Membru al",
-  "membership.panel.label.no.filter.results": "Niciun membru direct care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "membership.panel.label.no.data.found": "Fara membri directi pentru domeniu",
-  "membership.panel.label.role.type": "Tipul rolului",
-  "membership.panel.label.username": "Nume de utilizator",
-  "membership.panel.placeholder.filter": "Filtru membri",
-  "membership.panel.title.domain": "Membrii directi de Domeniu {{value}}",
-  "membership.panel.title.group": "Membrii directi de Grup {{value}}",
-  "membership.panel.title.resource": "Membrii directi de Resursa",
-  "membership.panel.tooltip.invite.member": "Invita membru nou",
-  "membership.panel.tooltip.edit.membership": "Schimba calitatea de membru a utilizatorului selectat",
-  "membership.panel.tooltip.delete.membership": "Sterge calitatea de membru selectata",
-
-  "user.profile.panel.button.reset": "Reseteaza",
-  "user.profile.panel.button.save": "Salveaza",
-  "user.profile.panel.button.update.password": "Setati/modificati parola",
-  "user.profile.panel.hint.choose.member.role": "Alegeti rolul membrului",
-  "user.profile.panel.error.email": "Adresa de e-mail este invalida!",
-  "user.profile.panel.label.active": "Activ",
-  "user.profile.panel.label.application.role": "Rolul in aplicatie",
-  "user.profile.panel.label.email": "Adresa de e-mail",
-  "user.profile.panel.label.example": "Exemplu de data/ora",
-  "user.profile.panel.label.full.name": "Nume complet (nume si prenume)",
-  "user.profile.panel.label.language.bg": "Bulgara",
-  "user.profile.panel.label.language.cs": "Ceha",
-  "user.profile.panel.label.language.da": "Daneza",
-  "user.profile.panel.label.language.de": "Germana",
-  "user.profile.panel.label.language.el": "Greaca",
-  "user.profile.panel.label.language.en": "Engleza",
-  "user.profile.panel.label.language.es": "Spaniola",
-  "user.profile.panel.label.language.et": "Estoniana",
-  "user.profile.panel.label.language.fi": "Finlandeza",
-  "user.profile.panel.label.language.fr": "Franceza",
-  "user.profile.panel.label.language.hr": "Croata",
-  "user.profile.panel.label.language.hu": "Maghiara",
-  "user.profile.panel.label.language.it": "Italiana",
-  "user.profile.panel.label.language.lt": "Lituaniana",
-  "user.profile.panel.label.language.lv": "Letona",
-  "user.profile.panel.label.language.mt": "Malteza",
-  "user.profile.panel.label.language.nl": "Olandeza",
-  "user.profile.panel.label.language.pl": "Poloneza",
-  "user.profile.panel.label.language.pt": "Portugheza",
-  "user.profile.panel.label.language.ro": "Romana",
-  "user.profile.panel.label.language.sk": "Slovaca",
-  "user.profile.panel.label.language.sl": "Slovena",
-  "user.profile.panel.label.language.sv": "Suedeza",
-  "user.profile.panel.label.last.set": "Modificat ultima data la",
-  "user.profile.panel.label.locale": "Local (fomatare data/ora)",
-  "user.profile.panel.label.login.failed.attempts": "Incercari esuate",
-  "user.profile.panel.label.login.last.failed.attempt": "Ultima incercare esuata",
-  "user.profile.panel.label.new.user": "Va rugam sa completati detaliile utilizatorului si sa dati click pe \"Salveaza\" pentru a crea un nou utilizator",
-  "user.profile.panel.label.username": "Nume utilizator",
-  "user.profile.panel.label.password.expiration.date": "Parola expira la",
-  "user.profile.panel.label.suspended.until.date": "Dezactivat pana la",
-  "user.profile.panel.label.theme": "Tema",
-  "user.profile.panel.placeholder.choose.date": "Alegeti o data",
-  "user.profile.panel.placeholder.password.expiration.date": "Valid pana la",
-  "user.profile.panel.placeholder.login.failed.attempts": "Incercari esuate",
-  "user.profile.panel.text.account": "Date cont",
-  "user.profile.panel.text.credentials": "Resetati numele de utilizator si parola acestuia pentru autentificarea in aplicatie",
-  "user.profile.panel.text.user.profile": "Datele si setarile profilului utilizatorului",
-  "user.profile.panel.title.account": "Cont",
-  "user.profile.panel.title.credentials": "Nume de utilizator/parola",
-  "user.profile.panel.title.user.profile": "Profil utilizator",
-  "user.profile.panel.tooltip.application.role": "Rolul in aplicatie al utilizatorului",
-  "user.profile.panel.tooltip.password.expiration.date": "Parola implicita setata de Administratorul Sistemului! Utilizatorul trebuie sa isi schimbe parola imediat!",
-  "user.profile.panel.value.password.expiration.date": "Parola implicita sau nula",
-
-  "row.limiter.placeholder": "Randuri",
-
-  "search.table.button.cancel": "Anuleaza",
-  "search.table.button.delete": "Sterge",
-  "search.table.button.edit": "Modifica",
-  "search.table.button.new": "Nou rand",
-  "search.table.button.save": "Salveaza",
-  "search.table.button.search": "Cauta",
-  "search.table.dirty.confirmation.dialog.title": "Datele nu sunt inca salvate",
-  "search.table.dirty.confirmation.dialog.description": "Actiunea va reimprospata toate datele, iar datele care nu sunt salvate se vor pierde.<br/><br/>Doriti să continuati?",
-  "search.table.error.delete": "Eroare la stergere",
-  "search.table.error.update": "Operatia 'actualizare' nu a fost finalizata cu succes.",
-  "search.table.tooltip.edit": "Modifica",
-  "search.table.tooltip.delete": "Sterge",
-  "search.table.success.update": "Operatia 'actualizare' a fost finalizata cu succes",
-
-  "group.dialog.button.close": "Inchide",
-  "group.dialog.button.save": "Salveaza",
-  "group.dialog.description": "Pentru a crea un grup nou, introduceti un nume unic de grup corespunzator domeniului si dati clic pe Salvare.",
-  "group.dialog.hint.group.name": "Nume unic de grup corespunzator domeniului",
-  "group.dialog.hint.group.visibility": "Alegeti vizibilitatea grupului",
-  "group.dialog.label.group.name": "Numele grupului",
-  "group.dialog.label.group.description": "Descriere grupului",
-  "group.dialog.label.group.visibility": "Vizibilitatea grupului",
-  "group.dialog.placeholder.group.visibility": "Vizibilitatea grupului",
-  "group.dialog.tooltip.group.visibility": "Vizibilitatea grupului.",
-
-  "domain.group.button.create": "Creeaza",
-  "domain.group.button.delete": "Sterge",
-  "domain.group.button.edit": "Modifica datele",
-  "domain.group.button.group.members": "Membrii grupului",
-  "domain.group.delete.confirmation.dialog.title": "Sterge Grupul {{groupName}} din DomiSMP",
-  "domain.group.delete.confirmation.dialog.description": "Actiunea va sterge definitiv grupul!<br/><br/>Doriti sa continuati?",
-  "domain.group.error.delete": "Nu se poate sterge grupul din cauza datelor de domeniu nevalide. Este selectat grupul?",
-  "domain.group.group.details.dialog.title": "Detaliile Grupului",
-  "domain.group.label.filter": "Filtru membri",
-  "domain.group.label.group.name": "Numele grupului",
-  "domain.group.label.group.description": "Descriere",
-  "domain.group.label.group.visibility": "Vizibilitate",
-  "domain.group.label.no.filter.results": "Niciun grup care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "domain.group.label.no.data.found": "Domeniul nu are grupuri",
-  "domain.group.label.select.pages": "Selectati paginile",
-  "domain.group.manage.members.dialog.title": "Gestionarea Membrilor Resursei",
-  "domain.group.success.delete": "Grupul de domeniu [{{groupName}}] sters",
-  "domain.group.title.domains.groups": "Grupuri de domeniu",
-  "domain.group.title.domains.groups.for.domain.code": "Grupurile de domeniu pentru [{{domainCode}}]",
-  "domain.group.tooltip.create": "Creeaza un grup nou",
-  "domain.group.tooltip.delete": "Stergeti grupul selectat",
-  "domain.group.tooltip.edit": "Modificati grupul selectat",
-  "domain.group.tooltip.filter": "Filtru membri",
-  "domain.group.tooltip.group.members": "Membrii grup",
-
-  "edit.domain.label.configuration": "Configurare",
-  "edit.domain.label.domain.code": "Cod de domeniu",
-  "edit.domain.label.domain.members": "Membrii domeniu",
-  "edit.domain.label.filter": "Filtru coduri de domeniu",
-  "edit.domain.label.group": "Grup",
-  "edit.domain.label.no.filter.results": "Nu exista date care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "edit.domain.label.user.not.administrator": "Utilizatorul nu este Administrator al niciunui domeniu",
-  "edit.domain.placeholder.domain.code": "Cod de domeniu",
-  "edit.domain.text": "Panoul de administrare a domeniilor este un instrument pentru Administratorii de domenii cu care acestia pot gestiona membrii si grupurile acestor domenii",
-  "edit.domain.title": "Modifica Domenii",
-
-  "resource.dialog.button.close": "Inchide",
-  "resource.dialog.button.save": "Salveaza",
-  "resource.dialog.description": "Pentru a crea a noua resursa introduceti identificatorul unic si schema dupa care dati click pe Salveaza.",
-  "resource.dialog.label.resource.type": "Tipul de resursa selectat",
-  "resource.dialog.label.resource.id": "Identificatorul resursei",
-  "resource.dialog.label.resource.id.mandatory": "Identificator participantului nu trebuie sa fie gol si trebuie sa contina pana la 255 de caractere.",
-  "resource.dialog.label.resource.id.already.exists": "Identificator participantului pentru schema data este deja definit in baza de date!",
-  "resource.dialog.label.resource.scheme": "Schema resursei",
-  "resource.dialog.label.resource.scheme.mandatory": "Schema participantului nu poate fi goala.",
-  "resource.dialog.label.resource.visibility": "Vizibilitatea resursei",
-  "resource.dialog.placeholder.resource.type": "Selectati tipul resursei",
-  "resource.dialog.placeholder.resource.visibility": "Vizibilitatea resursei",
-  "resource.dialog.tooltip.resource.type": "Selectati tipul resursei.",
-  "resource.dialog.tooltip.resource.visibility": "Vizibilitatea resursei.",
-
-  "group.resource.panel.button.create": "Creeaza",
-  "group.resource.panel.button.delete": "Sterge",
-  "group.resource.panel.button.edit": "Modifica",
-  "group.resource.panel.button.members": "Membrii resursei",
-  "group.resource.panel.delete.confirmation.dialog.title": "Stergeti resursa cu schema din DomiSMP",
-  "group.resource.panel.delete.confirmation.dialog.description": "Actiunea va sterge definitiv resursa [{{identifierScheme}}] cu identificatorul: [{{identifierValue}}]!<br/><br/>Doriti sa continuati?",
-  "group.resource.panel.label.filter": "Filtru resurse",
-  "group.resource.panel.label.identifier.scheme": "Schema",
-  "group.resource.panel.label.identifier.value": "Identificator",
-  "group.resource.panel.label.no.filter.results": "Nicio resursa care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "group.resource.panel.label.no.data.found": "Grupul nu are resurse",
-  "group.resource.panel.label.select.pages": "Selectati pagina",
-  "group.resource.panel.manage.members.dialog.title": "Gestionarea Membrilor Resursei",
-  "group.resource.panel.placeholder.filter": "Filtru resurse",
-  "group.resource.panel.resource.details.dialog.title": "Detaliile Resursei",
-  "group.resource.panel.error.delete.group": "Nu se poate sterge grupul din cauza datelor de domeniu nevalide. Este selectat grupul?",
-  "group.resource.panel.error.delete.resource": "Nu se poate sterge resursa din cauza datelor nevalide despre resursa. Este resursa selectata?",
-  "group.resource.panel.success.delete": "Resursa [{{identifierScheme}}] cu identificatorul [{{identifierValue}}] a fost stearsa.",
-  "group.resource.panel.title": "Resursele grupului {{groupName}}",
-  "group.resource.panel.tooltip.create": "Creeaza",
-  "group.resource.panel.tooltip.delete": "Sterge resursa selectata",
-  "group.resource.panel.tooltip.edit": "Modifica datele",
-  "group.resource.panel.tooltip.members": "Membrii grupului",
-
-  "edit.group.label.resources": "Resurse",
-  "edit.group.label.members": "Membrii",
-  "edit.group.label.user.not.administrator": "Utilizatorul nu este Administrator al niciunui grup",
-  "edit.group.label.select.domain": "Grup selectat",
-  "edit.group.label.select.group": "Grupul selectat",
-  "edit.group.placeholder.select.domain": "Selecteaza domeniu",
-  "edit.group.placeholder.select.group": "Selecteaza grup",
-  "edit.group.text": "Panoul de administrare a grupurilor este un instrument pentru Administratorilor de grupuri cu care acestia pot gestiona membrii grupurilor si resursele",
-  "edit.group.title": "Modifica Grup",
-  "edit.group.tooltip.select.domain": "Domeniu selectat.",
-  "edit.group.tooltip.select.group": "Grup selectat.",
-
-  "document.wizard.dialog.button.cancel": "Anuleaza",
-  "document.wizard.dialog.button.ok": "OK",
-  "document.wizard.dialog.title": "Expert Extensie Resursa",
-
-  "resource.details.panel.label.resource.id": "Identificator resursa",
-  "resource.details.panel.label.resource.name": "Modifica documentul resursei",
-  "resource.details.panel.label.resource.scheme": "Schema resursa",
-  "resource.details.panel.label.resource.type": "Tip resursa",
-  "resource.details.panel.label.resource.visibility": "Vizibilitate resursa",
-  "resource.details.panel.label.resource.visibility.private": "Resursa privata este accesibila doar membrilor resursei!",
-  "resource.details.panel.label.resource.visibility.private.group": "Resursa apartine grupului privat. Doar membrii grupului si membrii grupului resursei o pot accesa!",
-  "resource.details.panel.label.resource.visibility.private.domain": "Resursa apartine domeniului privat. Doar membrii domeniului, membrii grupului domeniului si membrii grupului resursei o pot accesa!",
-  "resource.details.panel.label.resource.visibility.public": "Resursa este publica in grupul public si in domeniul public. Datele resursei sunt accesibile tuturor utilizatorilor.",
-  "resource.details.panel.button.edit": "Modifica documentul",
-  "resource.details.panel.placeholder.resource.type": "Tipul resursei pentru resursa",
-  "resource.details.panel.tooltip.resource.type": "Selectati tipul pentru resursa.",
-  "resource.details.panel.tooltip.resource.visibility": "Vizibilitatea resursei.",
-  "resource.details.panel.tooltip.show.resource": "Afisati resursa",
-  "resource.details.panel.title": "Resurse",
-
-  "resource.document.panel.button.back": "Inapoi",
-  "resource.document.panel.button.cancel": "Anuleaza",
-  "resource.document.panel.button.document.wizard": "Expert document",
-  "resource.document.panel.button.generate": "Genereaza",
-  "resource.document.panel.button.validate": "Valideaza",
-  "resource.document.panel.button.save": "Salveaza",
-  "resource.document.panel.cancel.confirmation.dialog.description": "Doriti sa anulati toate modificarile din document?",
-  "resource.document.panel.cancel.confirmation.dialog.title": "Anulati modificarile",
-  "resource.document.panel.document.wizard.dialog.title": "Expert resursa",
-  "resource.document.panel.label.created.on": "Creat la:",
-  "resource.document.panel.label.show.version": "Afisati versiunea:",
-  "resource.document.panel.placeholder.version": "Toate versiunile documentului",
-  "resource.document.panel.success.save": "Documentul este salvat cu versiunea curenta [{{currentResourceVersion}}].",
-  "resource.document.panel.success.generate": "Documentul este generat.",
-  "resource.document.panel.success.valid": "Documentul este valid.",
-  "resource.document.panel.title": "Resurse",
-  "resource.document.panel.tooltip.document.wizard": "Afiseaza dialogul expert document",
-  "resource.document.panel.tooltip.generate": "Genereaza resursa",
-  "resource.document.panel.tooltip.save": "Salveaza resursa",
-  "resource.document.panel.tooltip.validate": "Valideaza resursa",
-  "resource.document.panel.tooltip.version": "Selecteaza versiunea de afisat.",
-
-  "subresource.document.panel.button.back": "Inapoi",
-  "subresource.document.panel.button.cancel": "Anuleaza",
-  "subresource.document.panel.button.document.wizard": "Expert document",
-  "subresource.document.panel.button.generate": "Genereaza",
-  "subresource.document.panel.button.validate": "Valideaza",
-  "subresource.document.panel.button.save": "Salveaza",
-  "subresource.document.panel.cancel.confirmation.dialog.description": "Doriti sa anulati toate modificarile din document?",
-  "subresource.document.panel.cancel.confirmation.dialog.title": "Anulati modificarile",
-  "subresource.document.panel.label.created.on": "Creat la:",
-  "subresource.document.panel.label.show.version": "Afisati versiunea:",
-  "subresource.document.panel.placeholder.show.version": "Toate versiunile documentului",
-  "subresource.document.panel.success.generate": "Documentul este generat.",
-  "subresource.document.panel.success.save": "Documentul este salvat cu versiunea curenta [{{currentResourceVersion}}].",
-  "subresource.document.panel.success.valid": "Documentul este valid.",
-  "subresource.document.panel.tooltip.document.wizard": "Afiseaza dialogul expert document",
-  "subresource.document.panel.tooltip.generate": "Genereaza resursa",
-  "subresource.document.panel.tooltip.save": "Salveaza resursa",
-  "subresource.document.panel.tooltip.show.version": "Selecteaza versiunea de afisat.",
-  "subresource.document.panel.tooltip.validate": "Valideaza resursa",
-
-  "subresource.document.wizard.button.cancel": "Anuleaza",
-  "subresource.document.wizard.button.ok": "OK",
-  "subresource.document.wizard.error.read": "A aparut o eroare la citirea certificatului. Verificati daca fisierul incarcat are un tip de certificat valid",
-  "subresource.document.wizard.error.upload": "A aparut o eroare la incarcarea fisierului certificat [{{fileName}}] {{errorDescription}}",
-  "subresource.document.wizard.button.upload.certificate": "Incarca certificat",
-  "subresource.document.wizard.label.access.point": "Adresa URL a punctului de acces",
-  "subresource.document.wizard.label.access.point.mandatory": "Adresa URL a punctului de acces este obligatorie!",
-  "subresource.document.wizard.label.process.id": "Identificator de proces",
-  "subresource.document.wizard.label.process.id.mandatory": "Identificatorul de proces este obligatoriu!",
-  "subresource.document.wizard.label.process.scheme": "Schema proces",
-  "subresource.document.wizard.label.service.description": "Descriere serviciu",
-  "subresource.document.wizard.label.technical.contact.url": "Adresa URL de contact tehnic",
-  "subresource.document.wizard.label.transport.profile": "Profil transport",
-  "subresource.document.wizard.label.transport.profile.mandatory": "Profilul de transport (de ex: 'bdxr-transport-ebms3-as4-v1p0') este obligatoriu!",
-  "subresource.document.wizard.label.upload.certificate.mandatory": "Un certificatul x509 valid este obligatoriu!",
-  "subresource.document.wizard.legend.required.fields": "* campuri obligatorii",
-  "subresource.document.wizard.placeholder.upload.certificate": "Incarcati un certificat sau inserati valoarea in camp",
-  "subresource.document.wizard.title": "Expert subresursa",
-  "subresource.document.wizard.tooltip.access.point": "Adresa unui punct final, ca URL.",
-  "subresource.document.wizard.tooltip.process.id": "Partea de valoare a identificatorului procesului.",
-  "subresource.document.wizard.tooltip.process.scheme": "Partea schema a identificatorului procesului.",
-  "subresource.document.wizard.tooltip.service.description": "Descrierea serviciului",
-  "subresource.document.wizard.tooltip.technical.contact.url": "Contactul tehnic pentru serviciu",
-  "subresource.document.wizard.tooltip.transport.profile": "Indica tipul de metoda de transport utilizata intre punctele de acces pentru a schimba mesaje. Consulati specificatiile tehnice ale retelei de schimb de mesaje pentru valorile corecte.",
-
-  "subresource.dialog.button.close": "Inchide",
-  "subresource.dialog.button.create": "Creeaza",
-  "subresource.dialog.description": "Pentru a crea o subresursa noua introduceti identificatorul unic si schema dupa care dati click pe Salveaza.",
-  "subresource.dialog.label.resource.type": "Tip resursa selectata",
-  "subresource.dialog.label.subresource.id": "Identificator subresursa",
-  "subresource.dialog.label.subresource.scheme": "Schema subresursa",
-  "subresource.dialog.placeholder.resource.type": "Tipul selecta al subresursei",
-  "subresource.dialog.tooltip.resource.type": "Selectati tipul subresursei.",
-
-  "subresource.panel.button.create": "Creaza",
-  "subresource.panel.button.delete": "Sterge",
-  "subresource.panel.button.edit": "Modifica",
-  "subresource.panel.error.delete.resource.data": "Nu se poate sterge subresursa din cauza datelor nevalide despre resursa. Este resursa selectata?",
-  "subresource.panel.error.delete.subresource.data": "Nu se poate sterge subresursa din cauza datelor nevalide despre subresursa. Este subresursa selectata?",
-  "subresource.panel.delete.confirmation.dialog.description": "Actiunea va sterge definiti subresursa [{{identifierScheme}}] si identificatorul: [{{identifierValue}}]!<br/><br/>Doriti sa continuati?",
-  "subresource.panel.delete.confirmation.dialog.title": "Sterge resursa cu schema din DomiSMP",
-  "subresource.panel.label.filter": "Filtru resursa",
-  "subresource.panel.label.identifier.scheme": "Schema",
-  "subresource.panel.label.identifier.value": "Identificator",
-  "subresource.panel.label.no.filter.results": "Nicio resursa care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "subresource.panel.label.no.data.found": "Resursa nu are subresurse",
-  "subresource.panel.label.subresource.name": "Modifica documentul subresursei",
-  "subresource.panel.label.select.pages": "Selectati pagina",
-  "subresource.panel.subresource.dialog.title": "Creaza Subresursa",
-  "subresource.panel.placeholder.filter": "Filtru resurse",
-  "subresource.panel.success.delete": "Subresursa [{{identifierScheme}}] si identificatorul: [{{identifierValue}}] sters.",
-  "subresource.panel.title": "Subresurse",
-  "subresource.panel.tooltip.create": "Creaza",
-  "subresource.panel.tooltip.delete": "Sterge resursa selectata",
-  "subresource.panel.tooltip.edit": "Modifica",
-
-  "edit.resource.label.filter": "Filtru resurse",
-  "edit.resource.label.identifier.scheme": "Schema",
-  "edit.resource.label.identifier.value": "Identificator",
-  "edit.resource.label.members": "Membri",
-  "edit.resource.label.no.filter.results": "Nicio resursa care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "edit.resource.label.no.filter.results.admin": "Nicio resursa care sa se potriveasca cu filtrul",
-  "edit.resource.label.no.data.found": "Grupul nu are resurse",
-  "edit.resource.label.resource.details": "Detalii resursa",
-  "edit.resource.label.select.pages": "Selectati pagina",
-  "edit.resource.label.selected.domain": "Domeniul selectat",
-  "edit.resource.label.selected.group": "Grupul selectat",
-  "edit.resource.label.subresources": "Subresursa",
-  "edit.resource.label.user.not.administrator": "Utilizatorul nu este Administrator al niciunei resurse",
-  "edit.resource.placeholder.filter": "Filtru resurse",
-  "edit.resource.placeholder.selected.domain": "Domeniul selectat",
-  "edit.resource.placeholder.selected.group": "Grupul selectat",
-  "edit.resource.text": "Panoul de administrare a resurselor este un instrument pentru Administratorii de resurse cu care acestia pot gestiona resurse",
-  "edit.resource.title": "Modifica resursa",
-  "edit.resource.tooltip.selected.domain": "Domeniul selectat.",
-  "edit.resource.tooltip.selected.group": "Grupul selectat.",
-
-  "login.button.login": "Autentifica",
-  "login.button.password.reset": "Solicitati resetarea parolei",
-  "login.button.sso.login": "SSO Autentifica",
-  "login.dialog.password.expiration.dialog.description": "Parola dvs. este pe cale sa expire la {{expirationDate}}! Va rugam sa va schimbati parola inainte de data expirarii!",
-  "login.dialog.password.expiration.dialog.title": "Avertizare! Parola dvs. este pe cale sa expire",
-  "login.error.generic.error": "A aparut o eroare generica ({{errorStatus}}) in timpul conectarii.",
-  "login.error.inactive.user": "Utilizatorul este inactiv. Va rugam sa contactati Administratorul.",
-  "login.error.invalid.credentials": "{{errorStatus}} Combinatia nume de utilizator/parola pe care ati furnizat-o nu este valida. Incercati din nou sau contactati Administratorul.",
-  "login.error.invalid.username": "Va rugam sa introduceti un nume de utilizator valid.",
-  "login.error.invalid.username.or.password": "Va rugam sa introduceti un nume de utilizator si o parola valide.",
-  "login.error.smp.not.running": "Nu se poate autentifica. SMP nu ruleaza.",
-  "login.label.login": "Autentificare",
-  "login.label.logout": "Deconectare",
-  "login.label.password": "Parola",
-  "login.label.password.reset": "Solicitati resetarea parolei",
-  "login.label.sso.login": "Autentificare SSO",
-  "login.label.login.username": "Nume utilizator",
-  "login.label.password.reset.username": "Nume utilizator",
-  "login.title.smp.login": "Autentificare SMP",
-  "login.title.sso.login": "Autentificare SSO:",
-  "login.success.confirmation.email.sent": "A fost trimis un e-mail de confirmare la adresa dvs. de e-mail inregistrata pentru utilizatorul [{{userId}}]. Va rugam sa urmati instructiunel din e-mail pentru a finaliza procedul de resetare a contului. Daca nu ati primit acest e-mail, incercati mai tarziu sau contactati Administratorul",
-
-  "resource.search.button.clear.all": "Goleste tot",
-  "resource.search.error.fetch.resource.metadata": "A aparut o eroare la preluarea metadatelor resurselor",
-  "resource.search.label.document.type": "Tip document",
-  "resource.search.label.domain": "Domeniu",
-  "resource.search.label.no.subresources": "Nicio subresursa",
-  "resource.search.label.open.url": "Deschide adresa URL",
-  "resource.search.label.resource.id": "Identificator resursa",
-  "resource.search.label.resource.scheme": "Schema resursa",
-  "resource.search.label.column.document.type": "Tip document",
-  "resource.search.label.column.domain": "Domeniu",
-  "resource.search.label.column.resource.id": "Identificator resursa",
-  "resource.search.label.column.resource.scheme": "Schema resursa",
-  "resource.search.label.column.resource.url": "Adresa URL resursa",
-  "resource.search.label.column.subresource.count": "Numar Subr.",
-  "resource.search.label.subresource.id": "Identificator Subresursa",
-  "resource.search.label.subresource.id.scheme": "Schema identificatoro subresursa",
-  "resource.search.placeholder.document.type": "Tip document",
-  "resource.search.placeholder.domain": "Domeniu",
-  "resource.search.title": "Cautare",
-  "resource.search.tooltip.document.type": "Tip document",
-  "resource.search.tooltip.domain": "Domeniu",
-
-  "default.password.dialog.button.ok": "OK",
-  "default.password.dialog.title": "Folositi parola implicita pentru acest utilizator. Va recomandam sa o schimbati folosind consola utilizatorului.",
-
-  "reset.credentials.button.cancel": "Anuleaza",
-  "reset.credentials.button.update.password": "Seteaza/schimba parola",
-  "reset.credentials.error.passwords.mismatch": "Confirmare parolei noi nu se potriveste cu noua parola!",
-  "reset.credentials.error.old.password.reused": "Noua parola nu trebuie sa fie egala cu vechea parola actuala!",
-  "reset.credentials.label.username": "Introduceti Nume utilizator",
-  "reset.credentials.label.new.password": "Parola noua",
-  "reset.credentials.label.new.password.confirmation": "Confirmati Parola noua",
-  "reset.credentials.legend.required.fields": "* campuri obligatorii",
-  "reset.credentials.success.password.reset": "Parola a fost resetata cu succes. Conectati-va din nou la aplicatie folosind noua parola",
-
-  "domain.panel.button.reset": "Reseteaza",
-  "domain.panel.button.save": "Salveaza",
-  "domain.panel.label.domain": "Cod de Domeniu [{{value}}]",
-  "domain.panel.label.domain.already.exists": "Codul de Domeniu exista deja!",
-  "domain.panel.label.domain.default.resource.type": "Tipul de resursa implicit pentru domeniu",
-  "domain.panel.label.domain.mandatory": "Codul de domeniu trebuie sa fie constituit doar din caractere si din numere si trebuie sa contina pana la 63 de caractere.",
-  "domain.panel.label.domain.visibility": "Vizibilitatea domeniului",
-  "domain.panel.label.signature.cert.alias": "Certificatul semnaturii de raspuns (Semnatura CertAlias)",
-  "domain.panel.text": "Introduceti datele si dati click pe 'Salveaza' pentru a crea un domeniu nou",
-  "domain.panel.title": "Detalii Domeniu",
-  "domain.panel.hint.domain": "Pentru integrarea cu API-ul serviciu web: proprietatea de Domeniu",
-  "domain.panel.hint.domain.visibility": "Vizibilitatea domeniului. In cazul unui utilizator Intern, acesta trebuie sa fie autentificat pentru a putea citi resursele domeniului",
-  "domain.panel.hint.signature.cert.alias": "O valoare goala va face ca raspunsurile Resursa sa nu fie semnate de SMP!",
-  "domain.panel.tooltip.domain": "Codul de domeniu al SMP. Codul trebuie sa fie unic si este folosit in antetul HTTP 'Domain' sau in portiunea de URL atunci cand se preia/creeaza resursa folosind API-ul serviciu web",
-  "domain.panel.tooltip.domain.default.resource.type": "Tipul de resursa implicit pentru domeniu.",
-  "domain.panel.tooltip.domain.visibility": "Vizibilitatea domeniului.",
-  "domain.panel.tooltip.signature.cert.alias": "Certificatul este folosit la semnarea raspunsurilor REST pentru domeniu.",
-  "domain.panel.warning.domain.configuration.option.admin.member": "<li>adaugati un membru de domeniu cu rolul 'ADMIN' din fila Membri!</li>",
-  "domain.panel.warning.domain.configuration.option.resource.type": "<li>selectati cel putin un tip de resursa din fila Tipuri de Resursa</li>",
-  "domain.panel.warning.domain.configuration.option.signature.key": "<li>selectati cheia care va fi folosita pentru semnarea raspunsurilor domeniului!</li>",
-  "domain.panel.warning.domain.configuration.prefix": "Pentru a finaliza configurarea domeniului, va rugam: <ul>",
-
-  "domain.properties.button.reset": "Reseteaza",
-  "domain.properties.button.save": "Salveaza",
-  "domain.properties.label.domain.property": "Proprietatea Domeniului",
-  "domain.properties.label.domain.value": "Valoarea Domeniului",
-  "domain.properties.label.system.default": "Implicita Sistem",
-  "domain.properties.label.no.properties.found": "Nu s-au gasit proprietati ale Domeniului",
-  "domain.properties.panel.title": "Proprietati ale Domeniului",
-
-  "domain.resource.type.panel.button.reset": "Reseteaza",
-  "domain.resource.type.panel.button.save": "Salveaza",
-  "domain.resource.type.label.domain.resource.type.configuration": "Configurarea tipului resursei de domeniu",
-
-  "domain.sml.integration.panel.button.register": "Inregistreaza",
-  "domain.sml.integration.panel.button.reset": "Reseteaza",
-  "domain.sml.integration.panel.button.save": "Salveaza",
-  "domain.sml.integration.panel.button.unregister": "Anulati inregistrarea",
-  "domain.sml.integration.panel.hint.sml.domain": "Componenta specifica domeniului a zonei SML DNS (de ex. 'mydomain' in cazul 'mydomain.sml.dns.zone').",
-  "domain.sml.integration.panel.hint.smp.id": "Identificatorul SMP folosit pentru SML",
-  "domain.sml.integration.panel.label.smp.id": "Identificatorul SML SMP",
-  "domain.sml.integration.panel.label.smp.id.already.exists": "Identificatorul SML SMP exista deja!",
-  "domain.sml.integration.panel.label.smp.id.mandatory": "Identificatorul SML SMP trebuie sa contina pana la 63 de caractere, trebuie sa fie constituit doar din caractere alfanumerice si cratime, nu trebuie sa inceapa cu o cifra sau o cratima si nu trebuie sa se termine cu o cratima.",
-  "domain.sml.integration.panel.label.smp.client.certificate.alias": "Alias-ul Certificatului Client SML",
-  "domain.sml.integration.panel.label.sml.client.certificate.toggle": "Folositi antetul HTTP 'ClientCert' la autentificare.",
-  "domain.sml.integration.panel.label.sml.domain": "Domeniu SML",
-  "domain.sml.integration.panel.label.sml.domain.already.exists": "Domeniul cu subdomeniu gol SML exista deja!",
-  "domain.sml.integration.panel.label.sml.domain.mandatory": "Domeniul SML trebuie sa contina pana la 63 characters, trebuie sa contina doar caractere alfanumerice si cratime, nu trebuie sa inceapa cu o cifra sau cratima si nu trebuie sa se termine cu o cratima.",
-  "domain.sml.integration.panel.label.sml.subdomain.already.exists": "Subdomeniul SML este deja definit!",
-  "domain.sml.integration.panel.register.confirmation.dialog.description": "Actiunea va inregistra domeniul: [{{domainCode}}] si toate resursele acestuia in SML.<br/><br/>Doriti sa continuati?",
-  "domain.sml.integration.panel.register.confirmation.dialog.title": "Inregistrarea domeniului in SML",
-  "domain.sml.integration.panel.error.register": "A aparut o eroare la inregistrarea domeniului: {{domainCode}}",
-  "domain.sml.integration.panel.error.register.unknown.error": "Eroare necunoscuta. Verificati log-urile aplicatiei.",
-  "domain.sml.integration.panel.error.unregister": "A aparut o eroare la anularea inregistratii domeniului: {{domainCode}}",
-  "domain.sml.integration.panel.error.unregister.unknown.error": "Eroare necunoscuta. Verificati log-urile aplicatiei.",
-  "domain.sml.integration.panel.success.register": "Domeniul [{{domainCode}}] inregistrat in SML!",
-  "domain.sml.integration.panel.success.unregister": "Inregistrarea domeniul [{{domainCode}}] anulata din SML!",
-  "domain.sml.integration.panel.text": "Domeniul este inregistrat in SML!</p>Domeniul inregistrat nu poate fi sters si nici nu poate sa i se modifice identificatorul SMP SML",
-  "domain.sml.integration.panel.title": "Datele de integrare SML",
-  "domain.sml.integration.panel.tooltip.sml.domain": "Componenta specifica domeniului a zonei SML DNS (de ex. 'mydomain' in cazul 'mydomain.sml.dns.zone' sau lasati necompletat pentru valoare implicita a sml.dns.zone). Nota: are doar valoare informativa, zona SML DNS utilizata pentru publicare se bazeaza pe configuratia SML.",
-  "domain.sml.integration.panel.tooltip.smp.client.certificate.alias": "Certificatul Client folosit pentru autentificarea SML. Antetul HTTP 'Client-Cert' al SML este generat de asemenea din certificat",
-  "domain.sml.integration.panel.tooltip.smp.client.certificate.alias.default.option": "Alegeti certificatul pentru semnarea raspunsurilor SOAP",
-  "domain.sml.integration.panel.unregister.confirmation.dialog.description": "Actiunea va anula inregistrarea domeniului: [{{domainCode}}] si a tuturor resurselor acestuia din SML.<br/><br/>Doriti sa continuati?",
-  "domain.sml.integration.panel.unregister.confirmation.dialog.title": "Anulari Inregistrarea Domeniului din SML",
-
-  "admin.domain.button.create": "Creeaza Domeniu",
-  "admin.domain.button.delete": "Sterge Selectia",
-  "admin.domain.label.configuration": "Configurare",
-  "admin.domain.label.domain.data": "Date de domeniu",
-  "admin.domain.label.domain.code": "Codul de domeniu",
-  "admin.domain.label.filter": "Filtru coduri de domenii",
-  "admin.domain.label.no.filter.results": "Nu exista date care sa se potriveasca cu filtrul {{filterValue}}",
-  "admin.domain.label.no.data.found": "Nu exista date",
-  "admin.domain.label.members": "Membrii",
-  "admin.domain.label.select.page": "Selectati pagina",
-  "admin.domain.label.no.domains.selected": "Niciun domeniu selectat.",
-  "admin.domain.label.resource.type": "Tipul Resursei",
-  "admin.domain.label.sml.integration": "Integrare SML",
-  "admin.domain.error": "Eroare: {{actionMessage}}",
-  "admin.domain.success.create": "Domeniul: [{{domainCode}}] a fost creat!",
-  "admin.domain.success.remove": "Domeniul: [{{domainCode}}] este eliminat!",
-  "admin.domain.text": "Panoul de administrare a domeniilor este un instrument cu care se pot crea si sterge domenii in DomiSMP",
-  "admin.domain.title": "Administrarea Domeniilor",
-
-  "extension.panel.extension.description": "Descriere",
-  "extension.panel.label.description": "Descriere",
-  "extension.panel.label.extension.name": "Numele extensiei",
-  "extension.panel.label.extension.version": "Versiunea extensiei",
-  "extension.panel.label.resource.definitions": "Definitiile resursei",
-  "extension.panel.label.name": "Nume",
-  "extension.panel.label.id": "Identificator",
-  "extension.panel.label.url.segment": "Segment adresa URL",
-  "extension.panel.label.mime.type": "Tipul MIME",
-  "extension.panel.label.select.page": "Selectati pagina",
-  "extension.panel.label.show.selected.resources": "Afisati resursa selectata",
-  "extension.panel.placeholder.extension.name": "Numele extensiei",
-  "extension.panel.placeholder.extension.version": "Versiunea",
-  "extension.panel.title": "Datele Extensiei",
-
-  "resource.details.dialog.button.cancel": "Anuleaza",
-  "resource.details.dialog.resource.description": "Selectati datele de definire a resursei",
-  "resource.details.dialog.subresources.description": "Definitia Subresursei",
-  "resource.details.dialog.extension.description": "Descriere",
-  "resource.details.dialog.label.name": "Numele",
-  "resource.details.dialog.label.id": "Identificator",
-  "resource.details.dialog.label.url.segment": "Segment adresa URL",
-  "resource.details.dialog.label.mime.type": "Tipul MIME",
-  "resource.details.dialog.label.description": "Descriere",
-  "resource.details.dialog.label.resource.id": "Identificator resursa",
-  "resource.details.dialog.label.resource.name": "Numele resursei",
-  "resource.details.dialog.label.resource.mime.type": "Tipul MIME al resursei",
-  "resource.details.dialog.label.select.page": "Selectati pagina",
-  "resource.details.dialog.label.url": "Segment adreasa URL/antet HTTP",
-  "resource.details.dialog.placeholder.resource.id": "Identificator resursa",
-  "resource.details.dialog.placeholder.resource.name": "Nume resursa",
-  "resource.details.dialog.placeholder.resource.mime.type": "Tip MIME resursa",
-  "resource.details.dialog.placeholder.url": "Segment adreasa URL/antet HTTP",
-  "resource.details.dialog.title": "Detalii de Definire ale Resursei",
-
-  "extensions.label.filter": "Filtru nume extensii",
-  "extensions.label.no.filter.results": "Nu exista date care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "extensions.label.no.data.found": "Nu exista date",
-  "extensions.label.name": "Nume",
-  "extensions.label.version": "Versiune",
-  "extensions.label.select.page": "Selectati pagina",
-  "extensions.label.no.extension.selected": "Nicio extensie selectata.",
-  "extensions.text": "Extensii inregistrate in DomiSMP.<br />DomiSMP accepta tipuri de documente prin extensie personalizata. Extensiile implementeaza instrumente pentru validarea si generarea resurselor si subresurselor.<br /> Extensiile pot implementa, de asemenea, logica personalizata pentru scanarea atasamentelor (de ex. detectii de virusi).",
-  "extensions.title": "Extensii",
-
-  "keystore.import.dialog.button.import": "Importa",
-  "keystore.import.dialog.button.cancel": "Anuleaza",
-  "keystore.import.dialog.error.import": "A aparut o eroare la importul keystore: {{fileName}}",
-  "keystore.import.dialog.error.upload": "A aparut o eroare la incarcarea fisierului keystore {{fileName}}",
-  "keystore.import.dialog.error.generic": "A aparut o eroare la citirea keystore.",
-  "keystore.import.dialog.error.generic.message": "Verificati daca fisierul incarcat este un tip de keystore valid.",
-  "keystore.import.dialog.hint.password": "Cheile si keystore-ul trebuie sa utilizeze aceeasi parola!",
-  "keystore.import.dialog.label.choose": "Alegeti keystore-ul",
-  "keystore.import.dialog.label.keystore.file.name": "Alegeti un fisier - keystore!",
-  "keystore.import.dialog.label.keystore.type.mandatory": "Keystore type is required!",
-  "keystore.import.dialog.label.password.mandatory": "Parola este obligatorie! (Cheile si keystore-ul trebuie sa utilizeze aceeasi parola!)",
-  "keystore.import.dialog.legend.required.fields": "* campuri obligatorii",
-  "keystore.import.dialog.placeholder.keystore.file.name": "Numele fisierului keystore",
-  "keystore.import.dialog.placeholder.keystore.type": "Tipul keystore-ului",
-  "keystore.import.dialog.placeholder.keystore.type.jks": "Keystore Java (JKS)",
-  "keystore.import.dialog.placeholder.keystore.type.pkcs12": "PKCS #12 (PKCS12)",
-  "keystore.import.dialog.placeholder.password": "Parola",
-  "keystore.import.dialog.title": "Importare Keystore",
-  "keystore.import.dialog.warning.ignored.aliases": "Urmatoarele alias-uri au fost ignorate deoarece erau deja prezente in keystore-ul curent: {{ignoredAliases}}",
-
-  "admin.keystore.button.import.keystore": "Importa keystore",
-  "admin.keystore.button.delete.key": "Sterge cheia selectata",
-  "admin.keystore.label.alias": "Alias",
-  "admin.keystore.label.certificate": "Certificat",
-  "admin.keystore.label.key.pair": "Pereche de chei",
-  "admin.keystore.label.type": "Tip",
-  "admin.keystore.label.no.certificate.selected": "Niciun certificat selectat.",
-  "admin.keystore.label.invalid.certificate": "Certificat nevalid: {{reason}}",
-  "admin.keystore.label.filter": "Filtru alias-uri certificate/chei",
-  "admin.keystore.label.no.filter.results": "Nu exista date care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "admin.keystore.label.no.data.found": "Nu exista date",
-  "admin.keystore.label.select.page": "Selectati pagina",
-  "admin.keystore.delete.confirmation.dialog.description": "Actiunea va sterge definitiv cheia din keystore!<br/><br/>Doriti sa continuati?",
-  "admin.keystore.delete.confirmation.dialog.title": "Sterge cheia [{{alias}}] din keystore",
-  "admin.keystore.success.certificates.added": "Certificate adaugate [{{aliases}}]",
-  "admin.keystore.success.certificates.deleted": "Certificate sterse [{{aliases}}]",
-  "admin.keystore.success.errors.detected": "Erori detectate [{{errors}}]",
-  "admin.keystore.placeholder.filter": "alias",
-  "admin.keystore.text": "Keystore-ul contine chei pentru semnarea raspunsurilor si chei client pentru integrarea cu SML.",
-  "admin.keystore.title": "Administrare Keystore",
-
-  "property.label.column.property": "Proprietate",
-  "property.label.column.value": "Valoare",
-  "property.label.column.title.property": "Cheia de proprietate.",
-  "property.label.column.title.value": "Valoare proprietatii.",
-  "property.label.filter": "Filtru nume proprietati",
-  "property.label.new.value": "Noua valoare: '{{value}}'.",
-  "property.label.server.restart.needed": "Este necesara repornirea serverului!",
-  "property.label.server.restart.time": "Timp de actualizare programat la: {{date}}",
-  "property.title": "Proprietati",
-  "property.tooltip.filter": "Filtru dupa numele proprietatilor",
-
-  "admin.truststore.button.add.certificate": "Adauga certificat",
-  "admin.truststore.button.delete.certificate": "Sterge selectia",
-  "admin.truststore.error": "Eroare: {{actionMessage}}",
-  "admin.truststore.delete.confirmation.dialog.description": "Actiunea va sterge definitiv certificatul din truststore!<br/><br/>Doriti sa continuati?",
-  "admin.truststore.delete.confirmation.dialog.title": "Sterge certificatul {{alias}} din truststore",
-  "admin.truststore.label.alias": "Alias",
-  "admin.truststore.label.certificate.trustiness": "Cand truststore este gol, increderea in certificat nu este validata!",
-  "admin.truststore.label.filter": "Filtru alias-uri certificate",
-  "admin.truststore.label.invalid.certificate": "Certificat nevalid: {{reason}}",
-  "admin.truststore.label.no.filter.results": "Nu exista date care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "admin.truststore.label.no.data.found": "Nu exista date",
-  "admin.truststore.label.no.certificates.selected": "Niciun certificat selectat.",
-  "admin.truststore.label.select.page": "Selectati pagina",
-  "admin.truststore.placeholder.filter": "Alias",
-  "admin.truststore.success.import": "Certificatul: [{{certificateId}}] avand alias [{{alias}}] este importat!",
-  "admin.truststore.success.remove": "Certificatul: [{{certificateId}}] avand alias [{{alias}}] este sters!",
-  "admin.truststore.text": "Truststore contine certificatele in care exista incredere.<br />Certificatele folosite pentru autentificare trebuie sa fie continute in truststore.",
-  "admin.truststore.title": "Administrare Truststore",
-
-  "admin.user.button.create": "Creaza utilizator",
-  "admin.user.button.delete": "Sterge selectia",
-  "admin.user.delete.confirmation.dialog.description": "Actiunea va sterge definitiv utilizatorul!<br/><br/>Doriti sa continuati?",
-  "admin.user.delete.confirmation.dialog.title": "Sterge utilizatorul {{username}} din DomiSMP",
-  "admin.user.label.filter": "Filtru utilizatori",
-  "admin.user.label.full.name": "Nume complet",
-  "admin.user.label.username": "Nume utilizator",
-  "admin.user.label.no.filter.results": "Nu exista utilizatori care sa se potriveasca cu filtrul \"{{filterValue}}\"",
-  "admin.user.label.no.data.found": "Nu exista date",
-  "admin.user.label.select.page": "Selectati pagina",
-  "admin.user.label.no.user.selected": "Niciun utilizator selectat.",
-  "admin.user.placeholder.filter": "Nume utilizator sau nume complet",
-  "admin.user.success.create": "Utilizatorul [{{username}}] a fost creat!",
-  "admin.user.success.delete": "Utilizatorul [{{username}}] a fost sters!",
-  "admin.user.success.password.updated": "Parola utilizatorului actualizata!",
-  "admin.user.success.update": "Utilizatorul [{{username}}] actualizat!",
-  "admin.user.text": "Panoul de administrarea a utilizatorilor este un instrument cu care se pot crea si sterge utilizatori din DomiSMP",
-  "admin.user.title": "Administrarea Utilizatorilor",
-
-  "dns.query.panel.label.dns.query": "Interogarea DNS nu a functionat",
-  "dns.query.panel.label.resolved.dns.entries": "Intrari DNS rezolvate",
-  "dns.query.panel.title": "Domeniu DNS",
-
-  "dns.tools.button.generate.lookup.query": "Genereaza interogare",
-  "dns.tools.label.resource.id": "Identificator resursa",
-  "dns.tools.label.resource.scheme": "Schema resursa",
-  "dns.tools.label.resource.top.domain": "Domeniu de top",
-  "dns.tools.description": "Introduceti datele DNS-ului pentru a crea o interogare DNS",
-  "dns.tools.title": "Interogare DNS",
-
-  "access.token.panel.button.delete": "Sterge",
-  "access.token.panel.button.save": "Salveaza",
-  "access.token.panel.error.invalid.end.date": "Data expira la nevalida",
-  "access.token.panel.error.invalid.start.date": "Data activ de la nevalida",
-  "access.token.panel.label.active": "Activ",
-  "access.token.panel.label.description": "Descriere",
-  "access.token.panel.label.login.failed.attempts": "Incercari esuate",
-  "access.token.panel.label.login.last.failed.attempt": "Ultima incercare esuata",
-  "access.token.panel.label.token.expired": "Token-ul de acces a expirat",
-  "access.token.panel.label.suspended.until": "Dezactivat pana la",
-  "access.token.panel.label.validity.dates": "Introduceti un interval valid de date",
-  "access.token.panel.placeholder.end.date": "Data de incheiere",
-  "access.token.panel.placeholder.start.date": "Data de inceput",
-
-  "user.access.tokens.button.create": "Creaza Token de acces",
-  "user.access.tokens.label.access.tokens": "Token-uri de access",
-  "user.access.tokens.label.select.page": "Selectati pagina",
-  "user.access.tokens.text": "Puteti genera un Token de acces pentru fiecare aplicatie care are nevoie de acces la API-ul DomiSMP.",
-  "user.access.tokens.title": "Token de Access",
-  "user.access.tokens.credentials.dialog.title": "Nou Token de access creat",
-  "user.access.tokens.delete.confirmation.dialog.description": "Actiunea va sterge Token-ul de acces: \"{{credentialName}}\"!<br /><br />Doriti sa continuati?",
-  "user.access.tokens.delete.confirmation.dialog.title": "Sterge Token-ul de access",
-  "user.access.tokens.update.confirmation.dialog.description": "Actiunea va actualiza Token-ul de acces: \"{{credentialName}}\"!<br /><br />Doriti sa continuati?",
-  "user.access.tokens.update.confirmation.dialog.title": "Actualizeaza Token-ul de acces",
-  "user.access.tokens.tooltip.create": "Creaza Token de access nou",
-
-  "user.certificate.panel.button.delete": "Sterge",
-  "user.certificate.panel.button.show.details": "Arata detaliile",
-  "user.certificate.panel.button.save": "Salveaza",
-  "user.certificate.panel.label.active": "Activ",
-  "user.certificate.panel.label.certificate.id": "Identificator certificat",
-  "user.certificate.panel.label.description": "Descriere",
-  "user.certificate.panel.label.invalid.end.date": "Data expira la nevalida",
-  "user.certificate.panel.label.invalid.certificate": "Certificate nevalid: {{reason}}",
-  "user.certificate.panel.label.invalid.start.date": "Data activ de la nevalida",
-  "user.certificate.panel.label.validity.dates": "Perioada de valabilitate a certificatului",
-  "user.certificate.panel.placeholder.end.date": "Data de incheiere",
-  "user.certificate.panel.placeholder.start.date": "Data de inceput",
-
-  "user.certificates.button.import": "Importa certificat nou",
-  "user.certificates.label.user.certificate": "Certificate utilizatori",
-  "user.certificates.label.select.page": "Selectati pagina",
-  "user.certificates.credentials.dialog.title": "Importa Certificat",
-  "user.certificates.delete.confirmation.dialog.description": "Actiunea va sterge certificatul: \"{{credentialName}}\"!<br /><br />Doriti sa continuati?",
-  "user.certificates.delete.confirmation.dialog.title": "Sterge Certificat",
-  "user.certificates.update.confirmation.dialog.description": "Actiunea va actualiza datele certificatului:<br />{{credentialName}}!<br /><br />Doriti sa continuati?",
-  "user.certificates.update.confirmation.dialog.title": "Actualizeaza Certificat",
-  "user.certificates.text": "Inregistreaza un certificat de utilizator X509 pentru a avea acces la API-ul DomiSMP API utilizand autentificarea TLS reciproca.",
-  "user.certificates.title": "Certificate de utilizator X509",
-  "user.certificates.tooltip.import": "Importa certificat nou",
-
-  "toolbar.component.button.change.password": "Modifica parola",
-  "toolbar.component.button.logout": "Deconectare",
-  "toolbar.component.button.not.logged.in": "Neconectat",
-  "toolbar.component.button.open.cas.profile": "Deschideti datele CAS ale utilizatorului",
-  "toolbar.component.label.system.administrator.role": "Administrator de sistem",
-  "toolbar.component.label.user.role": "Utilizator SMP",
-
-  "app.component.button.collapse": "Restrangeti bara laterala",
-  "app.component.tooltip.collapse": "Retrangeti",
-  "app.component.tooltip.expand": "Extindeti",
-
-  "navigation.label.home": "Pagina de start",
-  "navigation.label.search": "Cautare",
-  "navigation.label.search.resources": "Resurse",
-  "navigation.label.search.dns.tools": "Instrumente DNS",
-  "navigation.label.login": "Authentificare",
-  "navigation.label.reset.token": "Resetare Token",
-  "navigation.label.edit": "Administrare",
-  "navigation.label.edit.domains": "Editare Domenii",
-  "navigation.label.edit.groups": "Editare Grupuri",
-  "navigation.label.edit.resources": "Editare Resurse",
-  "navigation.label.edit.resource.document": "Editare Documentul Resursei",
-  "navigation.label.edit.subresource.document": "Editare Documentul Subresursei",
-  "navigation.label.system.settings": "Setari de Sistem",
-  "navigation.label.system.settings.alerts": "Alerte",
-  "navigation.label.system.settings.domains": "Domenii",
-  "navigation.label.system.settings.extensions": "Extensii",
-  "navigation.label.system.settings.keystores": "Keystore",
-  "navigation.label.system.settings.properties": "Proprietati",
-  "navigation.label.system.settings.truststores": "Truststore",
-  "navigation.label.system.settings.users": "Utilizatori",
-  "navigation.label.user.settings": "Setari Utilizator",
-  "navigation.label.user.settings.profile": "Profil",
-  "navigation.label.user.settings.access.tokens": "Token-uri de Acces",
-  "navigation.label.user.settings.certificates": "Certificate",
-  "navigation.label.user.settings.alerts": "Alertele mele",
-  "navigation.label.user.settings.membership": "Calitate de Membru",
-  "navigation.tooltip.search.resources": "Cauta resurse inregistrate",
-  "navigation.tooltip.search.tools": "Cauta resurse inregistrate",
-  "navigation.tooltip.search.dns.tools": "Instrumente de cautare DNS"
-}
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/enums/SMPPropertyEnum.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/enums/SMPPropertyEnum.java
index 931c13213..767c619dc 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/enums/SMPPropertyEnum.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/enums/SMPPropertyEnum.java
@@ -314,6 +314,31 @@ public enum SMPPropertyEnum {
             OPTIONAL, NOT_ENCRYPTED, NO_RESTART_NEEDED, STRING,
             "^(.{0,255})$", "Subject must have less than 256 character"),
 
+
+    ALERT_USER_CREATED_ENABLED("smp.alert.user.created.enabled",
+            "true", "Enable/disable the user creation alert",
+            OPTIONAL, NOT_ENCRYPTED, NO_RESTART_NEEDED, BOOLEAN),
+    ALERT_USER_CREATED_LEVEL("smp.alert.user.created.level",
+            "HIGH", "User creation alert level. Values: {LOW, MEDIUM, HIGH}",
+            OPTIONAL, NOT_ENCRYPTED, NO_RESTART_NEEDED, STRING,
+            "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"),
+    ALERT_USER_CREATED_MAIL_SUBJECT("smp.alert.user.created.mail.subject",
+            "New DomiSMP User created", "User creation mail subject.",
+            OPTIONAL, NOT_ENCRYPTED, NO_RESTART_NEEDED, STRING,
+            "^(.{0,255})$", "Subject must have less than 256 character"),
+
+    ALERT_USER_UPDATED_ENABLED("smp.alert.user.updated.enabled",
+            "true", "Enable/disable the user creation alert",
+            OPTIONAL, NOT_ENCRYPTED, NO_RESTART_NEEDED, BOOLEAN),
+    ALERT_USER_UPDATED_LEVEL("smp.alert.user.updated.level",
+            "HIGH", "User creation alert level. Values: {LOW, MEDIUM, HIGH}",
+            OPTIONAL, NOT_ENCRYPTED, NO_RESTART_NEEDED, STRING,
+            "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"),
+    ALERT_USER_UPDATED_MAIL_SUBJECT("smp.alert.user.updated.mail.subject",
+            "DomiSMP User was updated by administrator", "User update mail subject.",
+            OPTIONAL, NOT_ENCRYPTED, NO_RESTART_NEEDED, STRING,
+            "^(.{0,255})$", "Subject must have less than 256 character"),
+
     ALERT_ACCESS_TOKEN_BEFORE_EXPIRATION_ENABLED("smp.alert.accessToken.imminent_expiration.enabled",
             "true", "Enable/disable the imminent accessToken expiration alert",
             OPTIONAL, NOT_ENCRYPTED, NO_RESTART_NEEDED, BOOLEAN),
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/init/SMPLocaleFileSystemInitializer.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/init/SMPLocaleFileSystemInitializer.java
index 418096284..cbedd2b57 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/init/SMPLocaleFileSystemInitializer.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/init/SMPLocaleFileSystemInitializer.java
@@ -2,8 +2,7 @@ package eu.europa.ec.edelivery.smp.config.init;
 
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
-import eu.europa.ec.edelivery.smp.services.ConfigurationService;
-import eu.europa.ec.edelivery.smp.services.SMPLocaleService;
+import eu.europa.ec.edelivery.smp.services.SMPLanguageResourceService;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PostConstruct;
@@ -14,16 +13,15 @@ import javax.annotation.PostConstruct;
  *
  * @since 5.1
  * @author Sebastian-Ion TINCU
- * @see eu.europa.ec.edelivery.smp.i18n.SMPLocale
  */
 @Component
 public class SMPLocaleFileSystemInitializer {
 
     private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPLocaleFileSystemInitializer.class);
 
-    private final SMPLocaleService smpLocaleService;
+    private final SMPLanguageResourceService smpLocaleService;
 
-    public SMPLocaleFileSystemInitializer(SMPLocaleService smpLocaleService) {
+    public SMPLocaleFileSystemInitializer(SMPLanguageResourceService smpLocaleService) {
         this.smpLocaleService = smpLocaleService;
     }
 
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/i18n/SMPLocale.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/i18n/SMPLocale.java
deleted file mode 100644
index 1502f16be..000000000
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/i18n/SMPLocale.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package eu.europa.ec.edelivery.smp.i18n;
-
-import org.apache.commons.lang3.StringUtils;
-
-import java.util.EnumSet;
-
-/**
- * Locale enumeration for which DomiSMP is providing existing translations.
- *
- * @since 5.1
- * @author Sebastian-Ion TINCU
- */
-public enum SMPLocale {
-
-    /**
-     * The default English locale
-     */
-    EN_US ("en", "English"),
-
-    /**
-     * The Romanian locale
-     */
-    RO_RO ("ro", "Romanian");
-
-    private final String code;
-    private final String language;
-
-    SMPLocale(String code, String language) {
-        this.code = code;
-        this.language = language;
-    }
-
-    public String getCode() {
-        return code;
-    }
-
-    public String getLanguage() {
-        return language;
-    }
-
-    public static SMPLocale fromCodeDefaultingToEnglish(String code) {
-        return EnumSet.allOf(SMPLocale.class).stream()
-                .filter(locale -> StringUtils.equalsIgnoreCase(locale.getCode(), code))
-                .findAny()
-                .orElse(EN_US);
-    }
-}
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java
index c21c5153a..5a634afd7 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java
@@ -447,6 +447,36 @@ public class ConfigurationService {
         return configurationDAO.getCachedPropertyValue(ALERT_USER_LOGIN_FAILURE_MAIL_SUBJECT);
     }
 
+    //-----------------------
+    // user Created
+    public Boolean getAlertUserCreatedEnabled() {
+        return configurationDAO.getCachedPropertyValue(ALERT_USER_CREATED_ENABLED);
+    }
+
+    public AlertLevelEnum getAlertUserCreatedLevel() {
+        String level = configurationDAO.getCachedPropertyValue(ALERT_USER_CREATED_LEVEL);
+        return AlertLevelEnum.valueOf(level);
+    }
+
+    public String getAlertUserCreatedSubject() {
+        return configurationDAO.getCachedPropertyValue(ALERT_USER_CREATED_MAIL_SUBJECT);
+    }
+
+    //-----------------------
+    // user updated
+    public Boolean getAlertUserUpdatedEnabled() {
+        return configurationDAO.getCachedPropertyValue(ALERT_USER_UPDATED_ENABLED);
+    }
+
+    public AlertLevelEnum getAlertUserUpdatedLevel() {
+        String level = configurationDAO.getCachedPropertyValue(ALERT_USER_UPDATED_LEVEL);
+        return AlertLevelEnum.valueOf(level);
+    }
+
+    public String getAlertUserUpdatedSubject() {
+        return configurationDAO.getCachedPropertyValue(ALERT_USER_UPDATED_MAIL_SUBJECT);
+    }
+
     //-----------------------
     // user suspended
     public Boolean getAlertUserSuspendedEnabled() {
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CredentialsAlertService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CredentialsAlertService.java
index bf3ec4e85..82dbb170d 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CredentialsAlertService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CredentialsAlertService.java
@@ -311,9 +311,14 @@ public class CredentialsAlertService {
      */
     public void alertUserCreated(DBUser user) {
 
+        boolean userCreatedAlertEnabled = configurationService.getAlertUserCreatedEnabled();
+        if (!userCreatedAlertEnabled) {
+            LOG.debug("Suppress alert: Alert user created is disabled!");
+            return;
+        }
         String mailTo = user.getEmailAddress();
-        String mailSubject = "New user created";
-        AlertLevelEnum alertLevel = AlertLevelEnum.HIGH;
+        String mailSubject = configurationService.getAlertUserCreatedSubject();
+        AlertLevelEnum alertLevel = configurationService.getAlertUserCreatedLevel();
         AlertTypeEnum alertType = AlertTypeEnum.USER_CREATED;
 
         DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
@@ -335,9 +340,14 @@ public class CredentialsAlertService {
      */
     public void alertUserUpdated(DBUser user) {
 
+        boolean userCreatedAlertEnabled = configurationService.getAlertUserUpdatedEnabled();
+        if (!userCreatedAlertEnabled) {
+            LOG.debug("Suppress alert: Alert user updated is disabled!");
+            return;
+        }
         String mailTo = user.getEmailAddress();
-        String mailSubject = "User data updated";
-        AlertLevelEnum alertLevel = AlertLevelEnum.HIGH;
+        String mailSubject = configurationService.getAlertUserUpdatedSubject();
+        AlertLevelEnum alertLevel = configurationService.getAlertUserUpdatedLevel();
         AlertTypeEnum alertType = AlertTypeEnum.USER_UPDATED;
 
         DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMPLanguageResourceService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMPLanguageResourceService.java
new file mode 100644
index 000000000..cba2400f1
--- /dev/null
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMPLanguageResourceService.java
@@ -0,0 +1,286 @@
+package eu.europa.ec.edelivery.smp.services;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import eu.europa.ec.edelivery.smp.config.enums.SMPEnvPropertyEnum;
+import eu.europa.ec.edelivery.smp.logging.SMPLogger;
+import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Properties;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.apache.commons.lang3.StringUtils.lowerCase;
+import static org.apache.commons.lang3.StringUtils.trimToEmpty;
+
+/**
+ * Service providing operations for managing language resources (e.g. updating language files on the disk).
+ *
+ * @author Sebastian-Ion TINCU
+ * @since 5.1
+ */
+@Service
+public class SMPLanguageResourceService {
+
+    public static final String LANGUAGE_FILENAME_UI_PREFIX = "ui_";
+    public static final String LANGUAGE_RESOURCE_UI_FOLDER = "/META-INF/resources/ui/assets/i18n/";
+    public static final String LANGUAGE_RESOURCE_UI_DEFAULT = LANGUAGE_RESOURCE_UI_FOLDER + "en.json";
+
+    public static final String LANGUAGE_FILENAME_MAIL_PREFIX = "mail-messages_";
+    public static final String LANGUAGE_RESOURCE_MAIL_FOLDER = "/mail-messages/";
+    public static final String LANGUAGE_RESOURCE_MAIL_DEFAULT = LANGUAGE_RESOURCE_MAIL_FOLDER + "en.json";
+
+    private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPLanguageResourceService.class);
+
+    private final ConfigurationService configurationService;
+    private final ResourcePatternResolver resourceResolver;
+
+    public SMPLanguageResourceService(ConfigurationService configurationService,
+                                      ResourcePatternResolver resourceResolver) {
+        this.configurationService = configurationService;
+        this.resourceResolver = resourceResolver;
+    }
+
+
+    /**
+     * Method for getting the language file for a specific ISO 639 language code.
+     *
+     * @param langCode       the ISO 639 language code (e.g. "en" for English, "fr" for French,
+     *                       "de" for German, etc.) or null for the default language
+     * @param filenamePrefix the prefix of the language file (e.g. "ui_" for "ui_en.json")
+     * @return the path to the language file
+     */
+    public Path getLanguageFile(String filenamePrefix, String langCode) {
+        File localeFolder = configurationService.getLocaleFolder();
+        String languageFileName = getLanguageFilename(filenamePrefix, langCode);
+
+        if (localeFolder != null && !localeFolder.exists() && !localeFolder.mkdirs()) {
+            LOG.error("Failed to create locale folder [{}]", localeFolder);
+            return null;
+        }
+        return new File(localeFolder, languageFileName).toPath().toAbsolutePath();
+    }
+
+    @Cacheable("mail-templates-translations")
+    public Properties getMailProperties(String langCode) {
+        Resource langRes = getTranslationResourceFile(LANGUAGE_FILENAME_MAIL_PREFIX, langCode, LANGUAGE_RESOURCE_MAIL_DEFAULT);
+        ObjectMapper mapper = jsonObjectMapper();
+        try (InputStream target = langRes.getInputStream()) {
+            // Read JSON nodes from input streams
+            JsonNode jsonTranslation = mapper.readTree(target);
+            Properties properties = new Properties();
+            jsonTranslation.fieldNames().forEachRemaining(fieldName ->
+                properties.setProperty(fieldName, jsonTranslation.get(fieldName).asText());
+            );
+            return properties;
+        } catch (IOException e) {
+            LOG.error("Error occurred while merging the translation files", e);
+            return new Properties();
+        }
+    }
+
+    public Resource getTranslationResourceFile(String prefix, String code, String defaultResourceFile) {
+
+        Path langResourcePath = getLanguageFile(prefix, code);
+        if (langResourcePath != null && langResourcePath.toFile().exists()) {
+            LOG.debug("Returning local mail translation file [{}]", langResourcePath.toAbsolutePath());
+            return new FileSystemResource(langResourcePath);
+        } else {
+            LOG.warn("Local translation file [{}] does not exist. Return default translation [{}]!", code, defaultResourceFile);
+            ClassPathResource defResource = new ClassPathResource(defaultResourceFile);
+            if (defResource.exists()) {
+                return defResource;
+            } else {
+                LOG.error("Default locale file [{}] does not exist in classpath!", defaultResourceFile);
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Method for updating the language files on the disk. The method will copy the language files from the classpath
+     * to the locale folder. If the locale folder does not exist, the method will try to create it.
+     * The method will not overwrite existing properties in the translation files.  If a property is missing in the
+     * existing translation file, the method will add it from the classpath translation file.
+     */
+    public void updateLocalesOnDisk() {
+        updateLocalesOnDisk(LANGUAGE_FILENAME_UI_PREFIX, LANGUAGE_RESOURCE_UI_FOLDER + "*.json");
+        updateLocalesOnDisk(LANGUAGE_FILENAME_MAIL_PREFIX, LANGUAGE_RESOURCE_MAIL_FOLDER + "*.json");
+    }
+
+    public void updateLocalesOnDisk(String filenamePrefix, String resourcePathPattern) {
+        // Get all the language files from the classpath
+        Resource[] resources;
+        try {
+            resources = resourceResolver.getResources("classpath:" + resourcePathPattern);
+        } catch (IOException e) {
+            LOG.error("An error occurred while reading the language files from the classpath", e);
+            return;
+        }
+
+        // Copy the language files to the locale folder
+        for (Resource resource : resources) {
+            String filename = resource.getFilename();
+            if (filename == null) {
+                LOG.warn("Resource [{}] does not have a filename!", resource);
+                continue;
+            }
+            String langCode = filename.substring(0, filename.indexOf('.'));
+            Path localeFile = getLanguageFile(filenamePrefix, langCode);
+            if (localeFile == null) {
+                LOG.warn("Can not generate 'locale/language' file [{}]! Check if local folder defined in property [{}] " +
+                        "has writing permissions and the folder exists", localeFile, SMPEnvPropertyEnum.LOCALE_FOLDER.getProperty());
+                continue;
+            }
+            copyOrUpdateTranslation(localeFile, resource);
+        }
+    }
+
+    /**
+     * Method for copying a resource to a locale folder. If the IOException occurs, the method will silently log an error.
+     * The error can be caused by the lack of writing permissions or the absence of the locale folder or if the file already exists.
+     *
+     * @param resource   the resource to copy
+     * @param localeFile the path to the locale file
+     */
+    protected static void copyResourceToLocaleFolder(Resource resource, Path localeFile) {
+        try (InputStream inputStream = resource.getInputStream()) {
+            Files.copy(inputStream, localeFile);
+        } catch (IOException e) {
+            LOG.error("An error occurred while updating locale file [{}]", localeFile, e);
+        }
+    }
+
+    /**
+     * Method creates the language filename for a specific ISO 639 language code
+     * and a prefix. If the prefix is null, it will be ignored. The prefix and the language code
+     * will be normalized (lower case and trimmed).
+     *
+     * @param langCode the ISO 639 language code
+     * @return the filename for the language file
+     */
+    private String getLanguageFilename(String prefix, String langCode) {
+        return normalize(prefix) + normalize(langCode) + ".json";
+    }
+
+    /**
+     * Method returns normalized token (lower case and trimmed).
+     *
+     * @param token token to normalize
+     * @return normalized token
+     */
+    private String normalize(String token) {
+        return trimToEmpty(lowerCase(token));
+    }
+
+    /**
+     * The method will merge the existing translation file with the classpath translation file. The merge will add
+     * missing properties from the classpath translation file into the existing translation file, but it will not
+     * overwrite existing properties.
+     *
+     * @param localFilePath       the path to the local translation file
+     * @param resourceTranslation
+     */
+    public static void copyOrUpdateTranslation(Path localFilePath, Resource resourceTranslation) {
+        File localFile = localFilePath.toFile();
+        LOG.info("Updating translation file [{}]", localFile);
+        if (!localFile.exists()) {
+            LOG.debug("The local translation file [{}] does not exist!", localFile);
+            copyResourceToLocaleFolder(resourceTranslation, localFilePath);
+            return;
+        }
+        // update file
+        ObjectMapper mapper = jsonObjectMapper();
+        JsonNode mergedJson = null;
+        boolean changed = false;
+        try (InputStream target = new FileInputStream(localFilePath.toFile());
+             InputStream classpathTranslation = resourceTranslation.getInputStream()) {
+            // Read JSON nodes from input streams
+            JsonNode jsonReference = mapper.readTree(classpathTranslation);
+            mergedJson = mapper.readTree(target);
+            // Merge the JSON nodes
+            changed = mergeTranslationJson(mergedJson, jsonReference);
+        } catch (IOException e) {
+            LOG.error("Error occurred while merging the translation files", e);
+            return;
+        }
+
+        if (!changed) {
+            LOG.info("No changes were made to the translation file [{}]", localFile);
+            return;
+        }
+
+        // Write the merged JSON to the output file
+        try (OutputStream os = new FileOutputStream(localFilePath.toFile())) {
+            // Write the merged JSON to the output file
+            mapper.writerWithDefaultPrettyPrinter().writeValue(os, mergedJson);
+        } catch (IOException e) {
+            LOG.error("Error occurred while writing the merged translation file", e);
+        }
+    }
+
+    /**
+     * Merge two JSON nodes recursively. Method will add missing properties from
+     * the reference node into the target node, but it will not overwrite existing
+     * properties. If any changes were made to the target node, the method returns
+     * true else false.
+     * <p>
+     * The merge is used to add new translations to the existing language
+     * files.
+     *
+     * @param targetNode    the main node to be updates
+     * @param referenceNode the reference node
+     * @return true if any changes were made to the target node else false.
+     */
+    protected static boolean mergeTranslationJson(JsonNode targetNode, JsonNode referenceNode) {
+        AtomicBoolean changed = new AtomicBoolean(false);
+        referenceNode.fieldNames().forEachRemaining(fieldName -> {
+            JsonNode jsonNode = targetNode.get(fieldName);
+            // If the node is an object, recursively merge
+            boolean mergeChanged = changed.get();
+            if (jsonNode != null && jsonNode.isObject()) {
+                mergeChanged |= mergeTranslationJson(jsonNode, referenceNode.get(fieldName));
+            } else if (jsonNode == null && targetNode instanceof ObjectNode) {
+                // add new field
+                ((ObjectNode) targetNode).set(fieldName, referenceNode.get(fieldName));
+                mergeChanged = true;
+            }
+            changed.set(mergeChanged);
+        });
+        return changed.get();
+    }
+
+
+    /**
+     * Method for creating a new instance of the {@link ObjectMapper} with the default configuration.
+     *
+     * @return the new instance of the {@link ObjectMapper}
+     */
+    public static ObjectMapper jsonObjectMapper() {
+        ObjectMapper objectMapper = JsonMapper.builder()
+                .findAndAddModules()
+                .build();
+        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+        dateFormat.setTimeZone(TimeZone.getDefault());
+        objectMapper.setDateFormat(dateFormat);
+        return objectMapper;
+    }
+
+}
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMPLocaleService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMPLocaleService.java
deleted file mode 100644
index 3605bb2c0..000000000
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMPLocaleService.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package eu.europa.ec.edelivery.smp.services;
-
-import eu.europa.ec.edelivery.smp.config.enums.SMPEnvPropertyEnum;
-import eu.europa.ec.edelivery.smp.i18n.SMPLocale;
-import eu.europa.ec.edelivery.smp.logging.SMPLogger;
-import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.Resource;
-import org.springframework.stereotype.Service;
-
-import java.io.File;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.util.EnumSet;
-
-/**
- * Service providing operations for managing locales (e.g. updating locales on the disk).
- *
- * @since 5.1
- * @author Sebastian-Ion TINCU
- */
-@Service
-public class SMPLocaleService {
-
-    private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPLocaleService.class);
-
-    private final ConfigurationService configurationService;
-
-    public SMPLocaleService(ConfigurationService configurationService) {
-        this.configurationService = configurationService;
-    }
-
-    public Path getLocaleFile(SMPLocale locale) {
-        File localeFolder = configurationService.getLocaleFolder();
-        if (!localeFolder.exists() && !localeFolder.mkdirs()) {
-            LOG.error("Failed to create locale folder [{}]", localeFolder);
-            return null;
-        }
-        return new File(localeFolder, locale.getCode() + ".json").toPath().toAbsolutePath();
-    }
-
-    public Resource getLocaleResource(SMPLocale locale) {
-        return new ClassPathResource("META-INF/resources/ui/assets/i18n/" + locale.getCode() + ".json");
-    }
-
-    public void updateLocalesOnDisk() {
-        EnumSet.allOf(SMPLocale.class).forEach(this::updateLocaleOnDisk);
-    }
-
-    private void updateLocaleOnDisk(SMPLocale locale) {
-        Resource resource = getLocaleResource(locale);
-        if (resource.exists()) {
-            Path localeFile = getLocaleFile(locale);
-            if (localeFile == null) {
-                LOG.warn("Can not generate 'locale/language' file [{}]! Check if local folder defined in property [{}] " +
-                        "has writing permissions and the folder exists", localeFile, SMPEnvPropertyEnum.LOCALE_FOLDER.getProperty());
-                return;
-            }
-            if (Files.exists(localeFile)) {
-                LOG.warn("Language file [{}] already exists, and it will be replaced with up-to-date translations", localeFile);
-            }
-            try(InputStream inputStream = resource.getInputStream()) {
-                Files.copy(inputStream, localeFile, StandardCopyOption.REPLACE_EXISTING);
-            } catch (Exception e) {
-                LOG.error("An error occurred while updating locale file [{}]", localeFile, e);
-            }
-        }
-    }
-}
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateService.java
index c58f815f5..0abf9a4ac 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateService.java
@@ -20,11 +20,11 @@ package eu.europa.ec.edelivery.smp.services.mail;
 
 import eu.europa.ec.edelivery.smp.exceptions.ErrorCode;
 import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException;
+import eu.europa.ec.edelivery.smp.services.SMPLanguageResourceService;
 import eu.europa.ec.edelivery.smp.utils.StringNamedSubstitutor;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
-import org.springframework.cache.annotation.Cacheable;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -39,16 +39,21 @@ import java.util.Properties;
  * @author Joze Rihtarsic
  * @since 5.1
  */
-@Component
+@Service
 public class MailTemplateService {
-    private static final String DEFAULT_LANGUAGE = "en";
     private static final String MAIL_TEMPLATE = "/mail-messages/mail-template.htm";
-    private static final String MAIL_TEMPLATE_CHARSET= "UTF-8";
+    private static final String MAIL_TEMPLATE_CHARSET = "UTF-8";
     private static final String MAIL_HEADER = "MAIL_HEADER";
     private static final String MAIL_FOOTER = "MAIL_FOOTER";
     private static final String MAIL_TITLE = "MAIL_TITLE";
     private static final String MAIL_CONTENT = "MAIL_CONTENT";
 
+    SMPLanguageResourceService smpLanguageResourceService;
+
+    public MailTemplateService(SMPLanguageResourceService smpLanguageResourceService) {
+        this.smpLanguageResourceService = smpLanguageResourceService;
+    }
+
 
     public String getMailHtmlContent(MailDataModel model) {
         InputStream templateIS = MailTemplateService.class.getResourceAsStream(MAIL_TEMPLATE);
@@ -82,34 +87,8 @@ public class MailTemplateService {
 
 
     public String getMailData(MailDataModel model, String key) {
-        Properties translations = getMessageTranslations(model.getLanguage());
+        Properties translations = smpLanguageResourceService.getMailProperties(model.getLanguage());
         String dataTemplate = translations.getProperty(key);
         return StringUtils.isBlank(dataTemplate) ? dataTemplate : StringNamedSubstitutor.resolve(dataTemplate, model.getModel());
     }
-
-
-    @Cacheable("mail-templates-translations")
-    public Properties getMessageTranslations(String language) {
-
-        Properties translations = loadTranslations(language);
-        if (translations != null) {
-            return translations;
-        }
-        return loadTranslations(DEFAULT_LANGUAGE);
-    }
-
-    protected Properties loadTranslations(String language) {
-        String langResource = "/mail-messages/mail-messages_" + language + ".properties";
-        InputStream isLanguage = MailTemplateService.class.getResourceAsStream(langResource);
-        if (isLanguage != null) {
-            try {
-                Properties translations = new Properties();
-                translations.load(isLanguage);
-                return translations;
-            } catch (IOException e) {
-                throw new SMPRuntimeException(ErrorCode.INTERNAL_ERROR, "Error reading mail template", ExceptionUtils.getRootCauseMessage(e));
-            }
-        }
-        return new Properties();
-    }
 }
diff --git a/smp-server-library/src/main/resources/mail-messages/en.json b/smp-server-library/src/main/resources/mail-messages/en.json
new file mode 100644
index 000000000..f5ceef133
--- /dev/null
+++ b/smp-server-library/src/main/resources/mail-messages/en.json
@@ -0,0 +1,22 @@
+{
+  "mail.credential_changed.content" : "You're receiving this e-mail because your credential type: ${CREDENTIAL_TYPE} changed  on DomiSMP ${SMP_INSTANCE_NAME}! <p>If you did not update your credential, please inform DomiSMP administrator immediately.</p><br>If you are having trouble accessing your account, reset your password. ",
+  "mail.credential_changed.title" : "Credential type: ${CREDENTIAL_TYPE} on DomiSMP ${SMP_INSTANCE_NAME} changed!",
+  "mail.credential_expired.content" : "<p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> <p><strong>Expiration date-time: </strong> ${EXPIRATION_DATETIME}</p> <p><strong>Reporting date-time:</strong> ${REPORTING_DATETIME}</p> <p><strong>Alert level:</strong> ${ALERT_LEVEL}</p> <p><strong>SMP instance name:</strong> ${SMP_INSTANCE_NAME}</p>.",
+  "mail.credential_expired.title" : "Credential type: ${CREDENTIAL_TYPE} is expired",
+  "mail.credential_imminent_expiration.content" : "<p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p><p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p><p><strong>Expiration date-time: </strong> ${EXPIRATION_DATETIME}</p><p><strong>Reporting date-time:</strong> ${REPORTING_DATETIME}</p><p><strong>Alert level:</strong> ${ALERT_LEVEL}</p><p><strong>SMP instance name:</strong> ${SMP_INSTANCE_NAME}</p>",
+  "mail.credential_imminent_expiration.title" : "Credential type: ${CREDENTIAL_TYPE} imminent expiration",
+  "mail.credential_request_reset.content" : "You're receiving this e-mail because you requested a credential type: ${CREDENTIAL_TYPE} reset on DomiSMP ${SMP_INSTANCE_NAME} for your user account. <br/> Please go to the following page and choose a new password: <br/> <br/> <a href=\"${RESET_URL}\">${RESET_URL}</a> <br/> <br/> The link is valid for a short period of time. If the link has expired, please request a new one.",
+  "mail.credential_request_reset.title" : "Request for reset of the Credential type: ${CREDENTIAL_TYPE} on DomiSMP ${SMP_INSTANCE_NAME}",
+  "mail.credential_suspended.content" : "<p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> <p><strong>Failed login attempt count:</strong> ${FAILED_LOGIN_ATTEMPT}</p> <p><strong>Last failed login time:</strong> ${LAST_LOGIN_FAILURE_DATETIME}</p> <p><strong>Suspended util</strong> ${SUSPENDED_UNTIL_DATETIME}</p> <p><strong>Reporting time:</strong> ${REPORTING_DATETIME}</p> <p><strong>Alert level:</strong> ${ALERT_LEVEL}</p> <p><strong>SMP instance name:</strong> ${SMP_INSTANCE_NAME}</p>",
+  "mail.credential_suspended.title" : "Credential type: ${CREDENTIAL_TYPE} is temporarily suspended",
+  "mail.credential_verification_failed.content" : "<p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> <p><strong>Failed login attempt count:</strong> ${FAILED_LOGIN_ATTEMPT}</p> <p><strong>Last failed login time:</strong> ${LAST_LOGIN_FAILURE_DATETIME}</p> <p><strong>Reporting time:</strong> ${REPORTING_DATETIME}</p> <p><strong>Alert level:</strong> ${ALERT_LEVEL}</p> <p><strong>SMP instance name:</strong> ${SMP_INSTANCE_NAME}</p>",
+  "mail.credential_verification_failed.title" : "Credential type: ${CREDENTIAL_TYPE} verification failed!",
+  "mail.footer" : "DomiSMP instance: ${SMP_INSTANCE_NAME} (${SERVER_NAME}), ${CURRENT_DATETIME}",
+  "mail.header" : "eDelivery DomiSMP sample: ${SMP_INSTANCE_NAME}",
+  "mail.test_mail.content" : "This is a test mail from DomiSMP instance: ${SMP_INSTANCE_NAME} for your user account.",
+  "mail.test_mail.title" : "Test mail: ${CREDENTIAL_TYPE} on DomiSMP ${SMP_INSTANCE_NAME}",
+  "mail.user_created.content" : "You're receiving this e-mail because new user was created with :<br/><b>Username:</b>${USERNAME}<br /> <b>Name:</b>${FULL_NAME}<br /> <b>Email Address:</b>${EMAIL}<br /><b>Activated:</b>${ACTIVATED}<br /> Note: If user account is not yet activated please wait for activation mail, before first login. <br/>If you did not request this account, please inform DomiSMP administrator immediately.",
+  "mail.user_created.title" : "New user created on DomiSMP ${SMP_INSTANCE_NAME}",
+  "mail.user_updated.content" : "You're receiving this e-mail because user was updated with :<br/><b>Username:</b>${USERNAME}<br /> <b>Name:</b>${FULL_NAME}<br /> <b>Email Address:</b>${EMAIL}<br /><b>Activated:</b>${ACTIVATED}<br /><br/>If you did not request update account, please inform DomiSMP administrator immediately.",
+  "mail.user_updated.title" : "User update on DomiSMP ${SMP_INSTANCE_NAME}"
+}
\ No newline at end of file
diff --git a/smp-server-library/src/main/resources/mail-messages/mail-messages_en.properties b/smp-server-library/src/main/resources/mail-messages/mail-messages_en.properties
deleted file mode 100644
index 900b6e8bc..000000000
--- a/smp-server-library/src/main/resources/mail-messages/mail-messages_en.properties
+++ /dev/null
@@ -1,99 +0,0 @@
-###
-# #START_LICENSE#
-# smp-server-library
-# %%
-# Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP
-# %%
-# Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent
-# versions of the EUPL (the "Licence");
-# You may not use this work except in compliance with the Licence.
-# You may obtain a copy of the Licence at:
-# 
-# [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
-# 
-# Unless required by applicable law or agreed to in writing, software distributed under the Licence is
-# distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the Licence for the specific language governing permissions and limitations under the Licence.
-# #END_LICENSE#
-###
-# Email translations. Each email has a header, title, content and footer. The texts are can be HTML formatted.
-# The header and footer are common for all emails and should describe instance and purpose of the application.
-# The title and content are specific for each email type.
-
-mail.header=eDelivery DomiSMP sample: ${SMP_INSTANCE_NAME}
-mail.footer=DomiSMP instance: ${SMP_INSTANCE_NAME} (${SERVER_NAME}), ${CURRENT_DATETIME}
-
-# Email texts for password reset
-mail.test_mail.title=Test mail: ${CREDENTIAL_TYPE} on DomiSMP ${SMP_INSTANCE_NAME}
-mail.test_mail.content=This is a test mail from DomiSMP instance: ${SMP_INSTANCE_NAME} for your user account.
-
-mail.user_created.title=New user created on DomiSMP ${SMP_INSTANCE_NAME}
-mail.user_created.content=You're receiving this e-mail because new user was created with :<br/>\
-  <b>Username:</b>${USERNAME}<br /> \
-  <b>Name:</b>${FULL_NAME}<br /> \
-  <b>Email Address:</b>${EMAIL}<br />\
-  <b>Activated:</b>${ACTIVATED}<br /> \
-  Note: If user account is not yet activated please wait for activation mail, before first login. \
-  <br/>If you did not request this account, please inform DomiSMP administrator immediately.
-
-
-mail.user_updated.title=User update on DomiSMP ${SMP_INSTANCE_NAME}
-mail.user_updated.content=You're receiving this e-mail because user was updated with :<br/>\
-  <b>Username:</b>${USERNAME}<br /> \
-  <b>Name:</b>${FULL_NAME}<br /> \
-  <b>Email Address:</b>${EMAIL}<br />\
-  <b>Activated:</b>${ACTIVATED}<br />\
-  <br/>If you did not request update account, please inform DomiSMP administrator immediately.
-
-# Email texts for password reset
-mail.credential_request_reset.title=Request for reset of the Credential type: ${CREDENTIAL_TYPE} on DomiSMP ${SMP_INSTANCE_NAME}
-mail.credential_request_reset.content=You're receiving this e-mail because you requested a credential type: ${CREDENTIAL_TYPE} \
-  reset on DomiSMP ${SMP_INSTANCE_NAME} for your user account. <br/> Please go to the following page and choose a new password: \
-  <br/> <br/> <a href="${RESET_URL}">${RESET_URL}</a> <br/> <br/> The link is valid for a short period of time. If the \
-  link has expired, please request a new one.
-
-
-mail.credential_changed.title=Credential type: ${CREDENTIAL_TYPE} on DomiSMP ${SMP_INSTANCE_NAME} changed!
-mail.credential_changed.content=You're receiving this e-mail because your credential type: ${CREDENTIAL_TYPE} changed  \
-  on DomiSMP ${SMP_INSTANCE_NAME}! <p>If you did not update your credential, please inform DomiSMP administrator \
-  immediately.</p><br>If you are having trouble accessing your account, reset your password. 
-
-# Email texts for credential expired
-mail.credential_expired.title=Credential type: ${CREDENTIAL_TYPE} is expired
-mail.credential_expired.content=<p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> \
-  <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> \
-  <p><strong>Expiration date-time: </strong> ${EXPIRATION_DATETIME}</p> \
-  <p><strong>Reporting date-time:</strong> ${REPORTING_DATETIME}</p> \
-  <p><strong>Alert level:</strong> ${ALERT_LEVEL}</p> \
-  <p><strong>SMP instance name:</strong> ${SMP_INSTANCE_NAME}</p>.
-
-# Email texts for credential imminent expiration}
-mail.credential_imminent_expiration.title=Credential type: ${CREDENTIAL_TYPE} imminent expiration
-mail.credential_imminent_expiration.content=<p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p>\
-  <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p>\
-  <p><strong>Expiration date-time: </strong> ${EXPIRATION_DATETIME}</p>\
-  <p><strong>Reporting date-time:</strong> ${REPORTING_DATETIME}</p>\
-  <p><strong>Alert level:</strong> ${ALERT_LEVEL}</p>\
-  <p><strong>SMP instance name:</strong> ${SMP_INSTANCE_NAME}</p>
-
-# Email texts for credential suspended
-mail.credential_suspended.title=Credential type: ${CREDENTIAL_TYPE} is temporarily suspended
-mail.credential_suspended.content=<p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> \
-  <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> \
-  <p><strong>Failed login attempt count:</strong> ${FAILED_LOGIN_ATTEMPT}</p> \
-  <p><strong>Last failed login time:</strong> ${LAST_LOGIN_FAILURE_DATETIME}</p> \
-  <p><strong>Suspended util</strong> ${SUSPENDED_UNTIL_DATETIME}</p> \
-  <p><strong>Reporting time:</strong> ${REPORTING_DATETIME}</p> \
-  <p><strong>Alert level:</strong> ${ALERT_LEVEL}</p> \
-  <p><strong>SMP instance name:</strong> ${SMP_INSTANCE_NAME}</p>
-
-# Email texts for credential verification failed
-mail.credential_verification_failed.title=Credential type: ${CREDENTIAL_TYPE} verification failed!
-mail.credential_verification_failed.content=<p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> \
-  <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> \
-  <p><strong>Failed login attempt count:</strong> ${FAILED_LOGIN_ATTEMPT}</p> \
-  <p><strong>Last failed login time:</strong> ${LAST_LOGIN_FAILURE_DATETIME}</p> \
-  <p><strong>Reporting time:</strong> ${REPORTING_DATETIME}</p> \
-  <p><strong>Alert level:</strong> ${ALERT_LEVEL}</p> \
-  <p><strong>SMP instance name:</strong> ${SMP_INSTANCE_NAME}</p>
-
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMPLanguageResourceServiceTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMPLanguageResourceServiceTest.java
new file mode 100644
index 000000000..76b0c7c5e
--- /dev/null
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMPLanguageResourceServiceTest.java
@@ -0,0 +1,137 @@
+package eu.europa.ec.edelivery.smp.services;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Comparator;
+
+import static eu.europa.ec.edelivery.smp.services.SMPLanguageResourceService.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @since 5.1
+ * @author Joze RIHTARSIC
+ */
+class SMPLanguageResourceServiceTest {
+    File localeFolder = new File("target/locales");
+    ConfigurationService configurationService = Mockito.mock(ConfigurationService.class);
+    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+    SMPLanguageResourceService testInstance = new SMPLanguageResourceService(configurationService, resourcePatternResolver);
+
+    @BeforeEach
+    void setUp() throws IOException {
+        // clean the folder
+        if (localeFolder.exists() ) {
+            // first delete all files and then empty folders
+            Files.walk(localeFolder.toPath())
+                    .map( Path::toFile )
+                    .sorted( Comparator.comparing( File::isDirectory ) )
+                    .forEach( File::delete );
+        }
+        Mockito.when(configurationService.getLocaleFolder()).thenReturn(localeFolder);
+    }
+
+    @Test
+    void testMergeAddMissingProperty() {
+        // given
+        ObjectMapper objectMapper = new ObjectMapper();
+        ObjectNode targetNode = objectMapper.createObjectNode();
+        targetNode.put("key1", "value1");
+        targetNode.put("key2", "value2");
+
+        ObjectNode referenceNode = objectMapper.createObjectNode();
+        referenceNode.put("key1", "value-001");
+        referenceNode.put("key2", "value-002");
+        referenceNode.put("key3", "value-003");
+
+        // when
+        boolean result = mergeTranslationJson(targetNode, referenceNode);
+
+        // then
+        assertTrue(result);
+        assertEquals("value1", targetNode.get("key1").asText());
+        assertEquals("value2", targetNode.get("key2").asText());
+        assertEquals("value-003", targetNode.get("key3").asText());
+    }
+
+    @Test
+    void testMergeAddMissingProperty2() {
+        // given
+        ObjectMapper objectMapper = new ObjectMapper();
+        ObjectNode targetNode = objectMapper.createObjectNode();
+        targetNode.put("key1", "value1");
+        targetNode.put("key2", "");
+
+        ObjectNode referenceNode = objectMapper.createObjectNode();
+        referenceNode.put("key1", "value-001");
+        referenceNode.put("key2", "value-002");
+
+        // when
+        boolean result = mergeTranslationJson(targetNode, referenceNode);
+
+        // then
+        assertFalse(result);
+        assertEquals("value1", targetNode.get("key1").asText());
+        assertEquals("", targetNode.get("key2").asText());
+        assertFalse(targetNode.has("key3"));
+    }
+
+    @Test
+    void updateLocalesOnDiskCleanFolder() {
+        // given
+        assertFalse(localeFolder.exists());
+        // when
+        testInstance.updateLocalesOnDisk();
+        // then
+        assertTrue(localeFolder.exists());
+        File[] files = localeFolder.listFiles();
+        assertNotNull(files);
+        assertEquals(2, files.length);
+        assertEquals(LANGUAGE_FILENAME_UI_PREFIX + "en.json", files[0].getName());
+        assertEquals(LANGUAGE_FILENAME_MAIL_PREFIX + "en.json", files[1].getName());
+    }
+
+    @Test
+    void updateLocalesOnDiskOverwrite() throws IOException {
+        // given
+        ObjectMapper objectMapper = new ObjectMapper();
+        localeFolder.mkdirs();
+
+        String testText = "This must NOT be overwritten";
+        String testKey = "column.selection.link.all";
+        Path pathToFile = Paths.get(localeFolder.getAbsolutePath(), LANGUAGE_FILENAME_UI_PREFIX + "en.json");
+        assertFalse(pathToFile.toFile().exists());
+        // add one property
+        ObjectNode testNode = objectMapper.createObjectNode();
+        testNode.put(testKey, testText);
+        objectMapper.writeValue(pathToFile.toFile(), testNode);
+        assertTrue(pathToFile.toFile().exists());
+
+        // when
+        testInstance.updateLocalesOnDisk();
+        // then
+        assertTrue(localeFolder.exists());
+        File[] files = localeFolder.listFiles();
+        assertNotNull(files);
+        assertEquals(2, files.length);
+        assertEquals(LANGUAGE_FILENAME_UI_PREFIX + "en.json", files[0].getName());
+        assertEquals(LANGUAGE_FILENAME_MAIL_PREFIX + "en.json", files[1].getName());
+
+        JsonNode result = objectMapper.readTree(pathToFile.toFile());
+        assertEquals(testText, result.get(testKey).asText());
+        // 3 properties are added by the updateLocalesOnDisk method
+        // from the classpath resource META-INF/resources/ui/assets/i18n/en.json
+        assertEquals(4, result.size());
+    }
+}
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateTest.java
index 1bea4fd48..44c85ef04 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateTest.java
@@ -19,9 +19,20 @@
 package eu.europa.ec.edelivery.smp.services.mail;
 
 import eu.europa.ec.edelivery.smp.data.ui.enums.AlertTypeEnum;
+import eu.europa.ec.edelivery.smp.services.ConfigurationService;
+import eu.europa.ec.edelivery.smp.services.SMPLanguageResourceService;
 import eu.europa.ec.edelivery.smp.services.mail.prop.CredentialsExpirationProperties;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -30,8 +41,25 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class MailTemplateTest {
 
+    File localeFolder = new File("target/locales");
+    ConfigurationService configurationService = Mockito.mock(ConfigurationService.class);
+    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+    SMPLanguageResourceService smpLanguageResourceService = new SMPLanguageResourceService(configurationService, resourcePatternResolver);
 
-    MailTemplateService testInstance = new MailTemplateService();
+    MailTemplateService testInstance = new MailTemplateService(smpLanguageResourceService);
+
+    @BeforeEach
+    void setUp() throws IOException {
+        // clean the folder
+        if (localeFolder.exists() ) {
+            // first delete all files and then empty folders
+            Files.walk(localeFolder.toPath())
+                    .map( Path::toFile )
+                    .sorted( Comparator.comparing( File::isDirectory ) )
+                    .forEach( File::delete );
+        }
+        Mockito.when(configurationService.getLocaleFolder()).thenReturn(localeFolder);
+    }
 
     @Test
     void getMailContent() {
@@ -51,6 +79,5 @@ class MailTemplateTest {
         assertTrue(result.contains("alert level"));
         assertTrue(result.contains("credential id"));
         assertTrue(result.contains("credential name"));
-
     }
 }
diff --git a/smp-server-library/src/test/resources/META-INF/resources/ui/assets/i18n/en.json b/smp-server-library/src/test/resources/META-INF/resources/ui/assets/i18n/en.json
new file mode 100644
index 000000000..9b0e73154
--- /dev/null
+++ b/smp-server-library/src/test/resources/META-INF/resources/ui/assets/i18n/en.json
@@ -0,0 +1,6 @@
+{
+  "column.selection.link.all": "All",
+  "column.selection.link.none": "None",
+  "column.selection.link.show": "Show columns",
+  "column.selection.link.hide": "Hide columns"
+}
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/LocaleController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/LocaleController.java
index 4b30c9810..9c184efb2 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/LocaleController.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/LocaleController.java
@@ -1,17 +1,14 @@
 package eu.europa.ec.edelivery.smp.ui.external;
 
-import eu.europa.ec.edelivery.smp.i18n.SMPLocale;
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
-import eu.europa.ec.edelivery.smp.services.SMPLocaleService;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.FileSystemResource;
+import eu.europa.ec.edelivery.smp.services.SMPLanguageResourceService;
 import org.springframework.core.io.Resource;
 import org.springframework.util.MimeTypeUtils;
 import org.springframework.web.bind.annotation.*;
 
-import java.nio.file.Path;
-
+import static eu.europa.ec.edelivery.smp.services.SMPLanguageResourceService.LANGUAGE_FILENAME_UI_PREFIX;
+import static eu.europa.ec.edelivery.smp.services.SMPLanguageResourceService.LANGUAGE_RESOURCE_UI_DEFAULT;
 import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_PUBLIC_LOCALE;
 
 /**
@@ -24,29 +21,16 @@ import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_PUBLI
 @RequestMapping(value = CONTEXT_PATH_PUBLIC_LOCALE)
 public class LocaleController {
     private static final SMPLogger LOG = SMPLoggerFactory.getLogger(LocaleController.class);
-    private static final String DEFAULT_LOCALE_RESOURCE = "/META-INF/resources/ui/assets/i18n/en.json";
-    private final SMPLocaleService smpLocaleService;
 
-    public LocaleController(SMPLocaleService smpLocaleService) {
+    private final SMPLanguageResourceService smpLocaleService;
+
+    public LocaleController(SMPLanguageResourceService smpLocaleService) {
         this.smpLocaleService = smpLocaleService;
     }
 
     @GetMapping(value = "/{code}", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
-    @ResponseBody
-    public Resource getLocale(@PathVariable("code") String code) {
-        Path langResourcePath = smpLocaleService.getLocaleFile(SMPLocale.fromCodeDefaultingToEnglish(code));
-        if (langResourcePath != null && langResourcePath.toFile().exists()) {
-            LOG.debug("Returning locale file [{}]", langResourcePath.toAbsolutePath());
-            return new FileSystemResource(langResourcePath);
-        } else {
-            LOG.warn("Locale file [{}] does not exist. Return default translation!", langResourcePath.toAbsolutePath());
-            ClassPathResource defResource = new ClassPathResource(DEFAULT_LOCALE_RESOURCE);
-            if (defResource.exists()) {
-                return defResource;
-            } else {
-                LOG.error("Default locale file [{}] does not exist in classpath!", DEFAULT_LOCALE_RESOURCE);
-                return null;
-            }
-        }
+    public Resource getLanguage(@PathVariable("code") String code) {
+        LOG.debug("Requesting locale file for code: [{}]", code);
+        return smpLocaleService.getTranslationResourceFile(LANGUAGE_FILENAME_UI_PREFIX, code, LANGUAGE_RESOURCE_UI_DEFAULT);
     }
 }
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminController.java
index 4320a8f30..87e0667de 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminController.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminController.java
@@ -172,6 +172,7 @@ public class UserAdminController {
         }
 
         DBUser user = uiUserService.updateUserPassword(authorizedUserId, changeUserId, newPassword.getCurrentPassword(), newPassword.getNewPassword(),!currentUser.isCasAuthenticated());
+
         return authorizationService.sanitize(uiUserService.convertToRo(user));
     }
 
-- 
GitLab