Files
Florian-netz/src/lib/i18n/de.ts
Matthias Hochmeister 44050c7278 Workstream 8: Detailseiten & Kontakt (Phase 5)
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>
2026-06-09 11:35:34 +02:00

221 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}