fix(ui): echte Umlaute in allen sichtbaren Texten

Menüs, Dialoge, Schaltflächen, Platzhalter, Hinweise und die
Fehlermeldungen der API standen in Ersatzschreibweise (oe/ae/ue). Sie
erscheinen als Toast oder Beschriftung direkt in der Oberfläche und
gehören damit richtig geschrieben; sechs sichtbare Texte mit ss statt ß
sind gleich mit erledigt ("Schließen", "Straße", "zu groß").

Unangetastet bleibt bewusst:

- Kommentare im Quelltext - die sind durchgängig ASCII, und der Diff wäre
  dreimal so groß ohne Nutzen für den Benutzer
- Protokollzeilen (console.*) - die landen im journal, nicht in der App
- Datenbankwerte und interne Kennungen: 'uebernommen', 'empfaenger',
  'vorschlaege', 'laeuft-bereits' stecken in Enums, SQL und
  Zustandsvergleichen. Ein Umlaut darin hätte still falsche Ergebnisse
  geliefert
- server/src/mail/extract.js - die Muster decken absichtlich beide
  Schreibweisen ab: /gr(ü|ue)(ß|ss)e/
- scanner/utils/crypto.js - der Ersatzschlüssel darf sich nicht ändern,
  sonst sind gespeicherte Portal-Passwörter nicht mehr lesbar

Geprüft mit einem Rundgang durch die Ansichten und Dialoge: sichtbarer
Text plus title/aria-label/placeholder enthalten keine
Ersatzschreibweisen mehr.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sascha Wolf
2026-08-11 09:37:24 +02:00
co-authored by Claude Opus 5
parent ea55acdb5c
commit 8b7ce448a6
42 changed files with 168 additions and 168 deletions
+12 -12
View File
@@ -28,7 +28,7 @@ function emptyToNull(value) {
function normalizeDate(value) {
if (value === null || value === undefined || String(value).trim() === '') return { value: null };
const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(value).trim());
if (!match) return { error: 'Ungueltiges Datum (erwartet JJJJ-MM-TT).' };
if (!match) return { error: 'Ungültiges Datum (erwartet JJJJ-MM-TT).' };
const [, year, month, day] = match;
const probe = new Date(Number(year), Number(month) - 1, Number(day));
@@ -140,13 +140,13 @@ async function userExists(id) {
/** Prueft die Ansprechpartner-Liste `[{ lead_id, role }]`. */
async function normalizeContacts(raw) {
if (raw === undefined) return undefined;
if (!Array.isArray(raw)) return { error: 'Ansprechpartner muessen als Liste uebergeben werden.' };
if (!Array.isArray(raw)) return { error: 'Ansprechpartner müssen als Liste übergeben werden.' };
const seen = new Map();
for (const entry of raw) {
const leadId = Number(entry?.lead_id ?? entry);
if (!Number.isInteger(leadId) || leadId <= 0) {
return { error: 'Ungueltige Kontakt-ID bei den Ansprechpartnern.' };
return { error: 'Ungültige Kontakt-ID bei den Ansprechpartnern.' };
}
const role = emptyToNull(entry?.role);
if (role !== null && role.length > 120) {
@@ -209,7 +209,7 @@ router.post('/', async (req, res, next) => {
// etwas anderes gesetzt.
const stageGiven = req.body?.stage !== undefined && req.body.stage !== null && req.body.stage !== '';
if (stageGiven && !STAGES.has(req.body.stage)) {
return res.status(400).json({ error: 'Ungueltige Phase.' });
return res.status(400).json({ error: 'Ungültige Phase.' });
}
const stage = stageGiven ? req.body.stage : 'Neu';
@@ -241,7 +241,7 @@ router.post('/', async (req, res, next) => {
? req.user.id
: Number(req.body.owner_id);
if (Number.isNaN(ownerId) || !(await userExists(ownerId))) {
return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' });
return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' });
}
const contacts = await normalizeContacts(req.body?.contacts ?? []);
@@ -289,7 +289,7 @@ router.post('/', async (req, res, next) => {
// PATCH /api/opportunities/:id
router.patch('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Opportunity-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Opportunity-ID.' });
const conn = await pool.getConnection();
try {
@@ -301,7 +301,7 @@ router.patch('/:id', async (req, res, next) => {
if (!existing) return res.status(404).json({ error: 'Opportunity nicht gefunden.' });
if (!mayModify(req.user, existing)) {
return res.status(403).json({
error: 'Diese Opportunity darf nur der Ersteller, der Zustaendige oder ein Administrator aendern.',
error: 'Diese Opportunity darf nur der Ersteller, der Zuständige oder ein Administrator ändern.',
});
}
@@ -319,7 +319,7 @@ router.patch('/:id', async (req, res, next) => {
}
if (req.body?.stage !== undefined) {
if (!STAGES.has(req.body.stage)) return res.status(400).json({ error: 'Ungueltige Phase.' });
if (!STAGES.has(req.body.stage)) return res.status(400).json({ error: 'Ungültige Phase.' });
fields.push('stage = ?');
values.push(req.body.stage);
}
@@ -367,7 +367,7 @@ router.patch('/:id', async (req, res, next) => {
const ownerId =
req.body.owner_id === null || req.body.owner_id === '' ? null : Number(req.body.owner_id);
if (ownerId !== null && (Number.isNaN(ownerId) || !(await userExists(ownerId)))) {
return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' });
return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' });
}
fields.push('owner_id = ?');
values.push(ownerId);
@@ -377,7 +377,7 @@ router.patch('/:id', async (req, res, next) => {
if (contacts?.error) return res.status(400).json({ error: contacts.error });
if (fields.length === 0 && !contacts) {
return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' });
return res.status(400).json({ error: 'Keine Änderungen übergeben.' });
}
await conn.beginTransaction();
@@ -402,7 +402,7 @@ router.patch('/:id', async (req, res, next) => {
// DELETE /api/opportunities/:id - Ansprechpartner raeumt die DB per CASCADE
router.delete('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Opportunity-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Opportunity-ID.' });
try {
const [rows] = await pool.query(
@@ -413,7 +413,7 @@ router.delete('/:id', async (req, res, next) => {
if (!existing) return res.status(404).json({ error: 'Opportunity nicht gefunden.' });
if (!mayModify(req.user, existing)) {
return res.status(403).json({
error: 'Diese Opportunity darf nur der Ersteller, der Zustaendige oder ein Administrator loeschen.',
error: 'Diese Opportunity darf nur der Ersteller, der Zuständige oder ein Administrator löschen.',
});
}