Files
Florian-netz/src/lib/i18n/de.ts
Matthias Hochmeister e97e16d254 Workstream 6: Admin-Panel — Taxonomie & Bereitstellung (Phase 4)
Platform-Admin-only Oberflächen und Domänenlogik:

- codes.ts erweitert um allradCode/normalizeCode/codesMatch (Allrad-Infix
  kanonisch; Suche importiert weiterhin expandNameQuery). Pure-Unit-Tests.
- slug.ts (Idempotenz-Key-Erzeugung) + Tests.
- audit.ts: writeAudit mit EINER Signatur und optionalem typisierten tx.
- provisioning.ts: createBrigadeWithFirstAdmin (Geocoding inline, argon2id,
  Audit brigade.create/user.create) + resetUserPassword (Audit user.reset).
- Zod-Validierung: merkmal/template/equipment-category/brigade (+ Tests).
- Server Actions (jede mit Guard als erster Anweisung, default-deny):
  merkmale (CRUD, Delete blockiert bei Referenz), proposals (promote/merge mit
  Typ-Kompatibilität), templates (Merkmale/Vorgabewerte/Aliasse), equipment-
  categories, brigades (Bereitstellung/Reset). Audit in allen Schreib-Actions.
- (admin)-Route-Group: Layout mit requirePlatformAdmin als erster Zeile,
  AdminNav, DataTable, loading/error; Seiten für Merkmale (+Editor), Vorschläge
  (Merge), Vorlagen (+Detail mit Merkmal-/Alias-Editor und Allrad-Hinweis),
  Geräte-Kategorien (+Detail), Wehren (Liste/neu/Detail mit Passwort-Reset),
  paginierter Audit-Viewer mit Filter. Jede Seite ruft zusätzlich den Guard.
- i18n: admin-Strings in zentraler de.ts.
- Playwright-Specs (deferred, nicht ausgeführt): admin-gating,
  admin-merkmal-proposal, admin-brigade-provision.

Schema NICHT neu definiert — nur importiert. codes.ts ist hier Eigentümer.

Offline-Verifikation: tsc --noEmit grün; eslint grün; vitest run grün
(119 passed, 7 DB-roundtrip skipped); next build Exit 0; drizzle-kit check ok.
DB-/Server-/Browser-abhängige Schritte deferred (kein Postgres/Server im
Sandbox).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:30:52 +02:00

142 lines
4.1 KiB
TypeScript

export const de = {
app: { name: "FlorianNetz" },
nav: {
fahrzeuge: "Fahrzeuge",
geraete: "Geräte",
wehren: "Wehren",
verwaltung: "Verwaltung",
admin: "Administration",
},
auth: {
anmelden: "Anmelden",
abmelden: "Abmelden",
erforderlich: "Anmeldung erforderlich.",
email: "E-Mail",
passwort: "Passwort",
mitAuthentik: "Mit Authentik anmelden",
oderLokal: "oder mit lokalem Konto",
fehlgeschlagen: "Anmeldung fehlgeschlagen. Bitte Eingaben prüfen.",
zuVieleVersuche: "Zu viele Fehlversuche. Bitte später erneut versuchen.",
},
status: {
einsatzbereit: "einsatzbereit",
wartung: "Wartung",
ausser_dienst: "außer Dienst",
},
search: {
meinStandort: "Meinen Standort verwenden",
suchen: "Suchen",
keineTreffer: "Keine Treffer.",
luftlinie: "Luftlinie (geschätzt)",
adresse: "Adresse",
adressePlaceholder: "Adresse oder Ort",
suchbegriff: "Suchbegriff",
suchbegriffPlaceholder: "Name oder Funkrufname …",
nameOrtPlz: "Name, Ort oder PLZ …",
filter: "Filter",
filterZuruecksetzen: "Filter zurücksetzen",
keineFilter: "Keine Filter verfügbar.",
nurEinsatzbereit: "Nur einsatzbereit",
von: "von",
bis: "bis",
egal: "egal",
ja: "Ja",
nein: "Nein",
tabFahrzeuge: "Fahrzeuge",
tabGeraete: "Geräte",
tabWehren: "Wehren",
treffer: "Treffer",
standort: "Standort für Eintreffzeit",
keinFunkrufname: "kein Funkrufname",
eintreffzeitOffen: "Eintreffzeit: Standort wählen",
ergebnisse: "Ergebnisse",
},
detail: {
eckdaten: "Eckdaten",
beladung: "Beladung",
keineEckdaten: "Keine Eckdaten erfasst.",
imGeraetehaus: "im Gerätehaus",
},
fehler: {
allgemein: "Es ist ein Fehler aufgetreten.",
keineBerechtigung: "Keine Berechtigung.",
nichtGefunden: "Seite nicht gefunden.",
},
aktion: {
erneutVersuchen: "Erneut versuchen",
laden: "Wird geladen …",
zurueckZurStartseite: "Zur Startseite",
},
admin: {
titel: "Administration",
navMerkmale: "Merkmale",
navVorschlaege: "Vorschläge",
navVorlagen: "Fahrzeug-Vorlagen",
navKategorien: "Geräte-Kategorien",
navWehren: "Wehren",
navAudit: "Audit-Log",
speichern: "Speichern",
abbrechen: "Abbrechen",
loeschen: "Löschen",
anlegen: "Anlegen",
bearbeiten: "Bearbeiten",
neu: "Neu",
name: "Name",
code: "Code",
typ: "Typ",
einheit: "Einheit",
geltungsbereich: "Geltungsbereich",
status: "Status",
optionen: "Optionen",
optionHinzufuegen: "Option hinzufügen",
keineEintraege: "Keine Einträge vorhanden.",
referenziertFehler:
"Merkmal wird verwendet und kann nicht gelöscht werden.",
promote: "Übernehmen",
merge: "Zusammenführen",
mergeZiel: "Ziel-Merkmal",
mergeTypFehler: "Nur Merkmale gleichen Typs können zusammengeführt werden.",
vorgabewert: "Vorgabewert",
pflicht: "Pflicht",
reihenfolge: "Reihenfolge",
alias: "Alias",
aliasse: "Aliasse",
bestaetigt: "bestätigt",
allradHinweis: "Allrad-Schreibweise",
strasse: "Straße",
plz: "PLZ",
ort: "Ort",
telefon: "Telefon",
wehrfuehrer: "Wehrführer",
adminEmail: "Admin-E-Mail",
adminName: "Admin-Name",
wehrAnlegen: "Wehr anlegen",
passwortReset: "Passwort zurücksetzen",
tempPasswort:
"Einmal-Passwort (nur jetzt sichtbar, bitte sicher übergeben):",
geocodeOk: "Adresse geokodiert.",
geocodeFehler:
"Adresse konnte nicht geokodiert werden. Wehr wurde dennoch angelegt.",
auditZeitpunkt: "Zeitpunkt",
auditAktion: "Aktion",
auditZiel: "Ziel",
auditAkteur: "Akteur",
auditFilter: "Aktion filtern",
zurueck: "Zurück",
weiter: "Weiter",
},
} as const;
type Leaf = string;
type Paths<T> = T extends Leaf
? ""
: {
[K in keyof T & string]: T[K] extends Leaf ? K : `${K}.${Paths<T[K]>}`;
}[keyof T & string];
export function t(path: Paths<typeof de>): string {
return path
.split(".")
.reduce<unknown>((o, k) => (o as Record<string, unknown>)[k], de) as string;
}