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>
142 lines
4.1 KiB
TypeScript
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;
|
|
}
|