diff --git a/apps/api/src/ldap/ldap.service.spec.ts b/apps/api/src/ldap/ldap.service.spec.ts index 84457bd..52c7b21 100644 --- a/apps/api/src/ldap/ldap.service.spec.ts +++ b/apps/api/src/ldap/ldap.service.spec.ts @@ -1594,7 +1594,7 @@ describe('LdapService.syncBoundGroupsForTenant — Rekonziliation gegen das Verz expect(mockSearch).not.toHaveBeenCalled(); expect(result.errors).toEqual([ - 'Gruppe Sales: ungueltiger ldapObjectGuid-Wert', + 'Gruppe Sales: ungültiger ldapObjectGuid-Wert', ]); expect(prisma.group.delete).not.toHaveBeenCalled(); }); @@ -1727,7 +1727,7 @@ describe('LdapService.syncBoundGroupsForTenant — Rekonziliation gegen das Verz } expect(prisma.group.delete).not.toHaveBeenCalled(); expect(result.errors.length).toBe(1); - expect(result.errors[0]).toContain('Nicht geloescht'); + expect(result.errors[0]).toContain('Nicht gelöscht'); }); it('is idempotent: a second run over an unchanged AD state issues no group.update or group.delete call', async () => { @@ -1832,7 +1832,7 @@ describe('LdapService.syncBoundGroupsForTenant — Rekonziliation gegen das Verz expect(prisma.group.update).not.toHaveBeenCalled(); expect(result.groupsDeleted).toBe(0); expect(result.errors).toEqual([ - "Gruppe Sales: nicht mehr unter den konfigurierten Base-DNs gefunden, existiert aber weiterhin unter 'cn=Sales,ou=Archive,dc=example,dc=com' — vermutlich im Verzeichnis verschoben, Base-DN-Konfiguration pruefen. Nicht geloescht.", + "Gruppe Sales: nicht mehr unter den konfigurierten Base-DNs gefunden, existiert aber weiterhin unter 'cn=Sales,ou=Archive,dc=example,dc=com' — vermutlich im Verzeichnis verschoben, Base-DN-Konfiguration prüfen. Nicht gelöscht.", ]); expect(groups).toHaveLength(1); }); diff --git a/apps/api/src/ldap/ldap.service.ts b/apps/api/src/ldap/ldap.service.ts index 190e0e1..46d0ba8 100644 --- a/apps/api/src/ldap/ldap.service.ts +++ b/apps/api/src/ldap/ldap.service.ts @@ -1310,7 +1310,7 @@ export class LdapService { // (T-16-01). if (!ldapObjectGuid || !/^[0-9a-f]{32}$/.test(ldapObjectGuid)) { result.errors.push( - `Gruppe ${group.name}: ungueltiger ldapObjectGuid-Wert`, + `Gruppe ${group.name}: ungültiger ldapObjectGuid-Wert`, ); continue; } @@ -1427,7 +1427,7 @@ export class LdapService { } if (wideHit) { result.errors.push( - `Gruppe ${group.name}: nicht mehr unter den konfigurierten Base-DNs gefunden, existiert aber weiterhin unter '${wideHit.dn}' — vermutlich im Verzeichnis verschoben, Base-DN-Konfiguration pruefen. Nicht geloescht.`, + `Gruppe ${group.name}: nicht mehr unter den konfigurierten Base-DNs gefunden, existiert aber weiterhin unter '${wideHit.dn}' — vermutlich im Verzeichnis verschoben, Base-DN-Konfiguration prüfen. Nicht gelöscht.`, ); continue; } diff --git a/apps/web/src/messages/de.json b/apps/web/src/messages/de.json index 0205fa8..d908614 100644 --- a/apps/web/src/messages/de.json +++ b/apps/web/src/messages/de.json @@ -128,7 +128,7 @@ "uploadCta": "Bild hochladen", "uploadSuccess": "Profilbild wurde erfolgreich aktualisiert.", "uploadError": "Hochladen fehlgeschlagen. Bitte prüfen Sie das Dateiformat und die Dateigröße.", - "ldapManagedNotice": "Ihr Passwort wird über das Verzeichnis (LDAP) verwaltet. Eine Aenderung ist hier nicht möglich.", + "ldapManagedNotice": "Ihr Passwort wird über das Verzeichnis (LDAP) verwaltet. Eine Änderung ist hier nicht möglich.", "passwordSectionTitle": "Passwort ändern", "accentColorLabel": "Akzentfarbe", "accentColorHelp": "Passt die Hauptfarbe der Oberfläche an.", @@ -173,9 +173,9 @@ "catalogTitle": "Widget hinzufügen", "deleteTooltip": "Widget entfernen", "editMode": "Dashboard bearbeiten", - "saveChanges": "Aenderungen speichern", + "saveChanges": "Änderungen speichern", "layoutLoadError": "Dashboard konnte nicht geladen werden. Bitte laden Sie die Seite neu.", - "widgetSaveError": "Aenderungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.", + "widgetSaveError": "Änderungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.", "clock": { "name": "Uhr", "description": "Zeigt die aktuelle Uhrzeit an", @@ -479,7 +479,7 @@ }, "modules": { "categoryTitle": "Module: {category}", - "openModule": "Oeffnen", + "openModule": "Öffnen", "noModules": "Keine aktiven Module in dieser Kategorie", "notFound": "Modul nicht gefunden", "notFoundDescription": "Das angeforderte Modul existiert nicht oder ist nicht aktiviert.", diff --git a/apps/web/src/messages/umlaut-dictionary.ts b/apps/web/src/messages/umlaut-dictionary.ts index b6ccd32..4804e60 100644 --- a/apps/web/src/messages/umlaut-dictionary.ts +++ b/apps/web/src/messages/umlaut-dictionary.ts @@ -42,6 +42,12 @@ export const UMLAUT_REPLACEMENTS: Record = { Ueberpruefe: 'Überprüfe', Zertifikatspruefung: 'Zertifikatsprüfung', aendern: 'ändern', + // Nachtrag 2026-09-07: die grossgeschriebenen Formen fehlten. Der Waechter + // hat sie nicht gemeldet, weil sein Verdachtsmuster case-sensitiv war und + // "Ae"/"Oe" deshalb durchrutschten — mitbehoben in umlaut-guard.spec.ts. + Aenderung: 'Änderung', + Aenderungen: 'Änderungen', + Oeffnen: 'Öffnen', geaendert: 'geändert', Moechten: 'Möchten', moeglich: 'möglich', @@ -162,4 +168,10 @@ export const UMLAUT_ALLOWLIST: readonly string[] = [ 'vertrauenswürdigen', 'ausschließen', 'entschlüsselt', + // Nachtrag 2026-09-07: Abkuerzungen mit doppeltem S. Sie tauchen erst auf, + // seit das Verdachtsmuster case-insensitiv prueft — vorher fiel jedes + // grossgeschriebene "SS" durch dasselbe Loch wie "Aenderungen". + 'RSS', + 'RSSGenerator', + 'SSL', ]; diff --git a/apps/web/src/messages/umlaut-guard.spec.ts b/apps/web/src/messages/umlaut-guard.spec.ts index e12fb4b..d1f4960 100644 --- a/apps/web/src/messages/umlaut-guard.spec.ts +++ b/apps/web/src/messages/umlaut-guard.spec.ts @@ -22,7 +22,11 @@ import { UMLAUT_ALLOWLIST, UMLAUT_REPLACEMENTS } from './umlaut-dictionary'; */ const UMLAUT_TOKEN_RE = /[A-Za-zÄÖÜäöüß]+/g; -const SUSPECT_RE = /(ae|oe|ue|ss)/; +// Case-insensitive, sonst rutscht jede grossgeschriebene Ersatzschreibung durch: +// "Aenderungen" beginnt mit "Ae", nicht mit "ae". Genau daran sind beim ersten +// Durchgang "Aenderung", "Aenderungen" und "Oeffnen" vorbeigekommen — darunter +// zwei gut sichtbare Schaltflaechen. +const SUSPECT_RE = /(ae|oe|ue|ss)/i; /** Recursively flattens a nested message object into dot-joined leaf key paths. */ function flattenKeys(obj: unknown, prefix = ''): string[] {