Drei serverseitige Lese-Detailseiten (Fahrzeug, Gerät, Wehr), default-deny:
- src/lib/detail/merkmale.ts: formatMerkmal (de-AT Tausenderpunkt + NBSP,
Ja/Nein, enum-Label, „–"), toEckdaten. ICU-unabhängige Zahl-Formatierung
(formatToParts -> Punkt/Komma), da de-AT je nach ICU-Build U+202F gruppiert.
- src/lib/detail/queries.ts: read-only, wehrübergreifend; loadMerkmalRows
(Join merkmal_values↔merkmale↔merkmal_optionen via wert=value_text),
getFahrzeugDetail (+Beladung, +WehrCard), getGeraetDetail (Fahrzeug-Link
oder „im Gerätehaus"), getWehrDetail (Fuhrpark + Geräte im Haus),
getBrigadeCard. UUID-IDs.
- Komponenten: detail/{DetailHeader,EckdatenGrid,BeladungListe,StatusBadge},
kontakt/{KontaktButton (tel:/mailto:, Telefon ohne Leerzeichen, subject;
Empty-State),WehrCard}.
- Seiten (app)/{fahrzeuge,geraete,wehren}/[id]/page.tsx mit requireSession()
als erster Zeile (Default-deny in der Tiefe) + fahrzeuge/[id]/not-found.tsx.
- i18n-Keys (detail/kontakt/wehr) ergänzt; keine hartkodierten Strings.
Tests: merkmale.test.ts (11), queries.test.ts (3, gemockte DB für
„im Gerätehaus" + not-found). Playwright detail-auth.spec.ts geschrieben
(deferred: kein Server/DB in Sandbox); Detailrouten ins Gating-Manifest
aufgenommen.
Offline verifiziert: vitest src/lib/detail grün; tsc --noEmit ok; eslint
ok; next build erfolgreich (alle drei [id]-Routen vorhanden).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
221 lines
6.5 KiB
TypeScript
221 lines
6.5 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.",
|
||
keineBeladung: "Keine Beladung zugeordnet.",
|
||
imGeraetehaus: "im Gerätehaus",
|
||
leerWert: "–",
|
||
ja: "Ja",
|
||
nein: "Nein",
|
||
zugeordnetesFahrzeug: "Zugeordnetes Fahrzeug",
|
||
kategorie: "Kategorie",
|
||
fahrzeuge: "Fahrzeuge",
|
||
keineFahrzeuge: "Keine Fahrzeuge erfasst.",
|
||
geraeteImHaus: "Geräte im Gerätehaus",
|
||
keineGeraeteImHaus: "Keine Geräte im Gerätehaus.",
|
||
nichtGefunden: "Nicht gefunden.",
|
||
},
|
||
kontakt: {
|
||
titel: "Kontakt",
|
||
anrufen: "Anrufen",
|
||
email: "E-Mail schreiben",
|
||
keine: "Keine Kontaktdaten hinterlegt.",
|
||
betreff: "FlorianNetz – Anfrage",
|
||
},
|
||
wehr: {
|
||
wehrfuehrer: "Wehrführer",
|
||
adresse: "Adresse",
|
||
},
|
||
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",
|
||
},
|
||
verwaltung: {
|
||
titel: "Verwaltung",
|
||
navProfil: "Profil",
|
||
navFahrzeuge: "Fahrzeuge",
|
||
navGeraete: "Geräte",
|
||
navBenutzer: "Benutzer",
|
||
speichern: "Speichern",
|
||
abbrechen: "Abbrechen",
|
||
loeschen: "Löschen",
|
||
anlegen: "Anlegen",
|
||
bearbeiten: "Bearbeiten",
|
||
neu: "Neu",
|
||
name: "Name",
|
||
funkrufname: "Funkrufname",
|
||
notiz: "Notiz",
|
||
status: "Status",
|
||
kategorie: "Kategorie",
|
||
zuordnung: "Zuordnung",
|
||
imGeraetehaus: "im Gerätehaus",
|
||
vorlage: "Fahrzeug-Vorlage",
|
||
keineVorlage: "Ohne Vorlage (frei)",
|
||
vorlageWaehlen: "Vorlage wählen",
|
||
merkmale: "Merkmale",
|
||
keineMerkmale: "Für diese Auswahl sind keine Merkmale hinterlegt.",
|
||
fahrzeugAnlegen: "Fahrzeug anlegen",
|
||
fahrzeugBearbeiten: "Fahrzeug bearbeiten",
|
||
geraetAnlegen: "Gerät anlegen",
|
||
geraetBearbeiten: "Gerät bearbeiten",
|
||
keineFahrzeuge: "Noch keine Fahrzeuge erfasst.",
|
||
keineGeraete: "Noch keine Geräte erfasst.",
|
||
keineBenutzer: "Noch keine Benutzer erfasst.",
|
||
profilTitel: "Wehr-Profil",
|
||
profilGespeichert: "Profil gespeichert.",
|
||
geocodeOk: "Adresse geokodiert.",
|
||
geocodeWarnung:
|
||
"Adresse konnte nicht geokodiert werden. Daten wurden dennoch gespeichert.",
|
||
strasse: "Straße",
|
||
plz: "PLZ",
|
||
ort: "Ort",
|
||
email: "E-Mail",
|
||
telefon: "Telefon",
|
||
wehrfuehrer: "Wehrführer",
|
||
funkrufnameSchema: "Funkrufname-Schema",
|
||
rolle: "Rolle",
|
||
rolleAdmin: "Wehr-Admin",
|
||
rolleRead: "Lesend",
|
||
benutzerAnlegen: "Benutzer anlegen",
|
||
deaktivieren: "Deaktivieren",
|
||
aktiv: "aktiv",
|
||
inaktiv: "inaktiv",
|
||
authLokal: "lokal",
|
||
authAuthentik: "Authentik",
|
||
tempPasswort:
|
||
"Einmal-Passwort (nur jetzt sichtbar, bitte sicher übergeben):",
|
||
loeschenBestaetigen: "Wirklich löschen?",
|
||
pflichtfeld: "Pflichtfeld",
|
||
},
|
||
} 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;
|
||
}
|