Workstream 1: Projekt-Fundament & Design-System (Phase 0)

Greenfield-Next.js-15-App-Router-Gerüst (TS strict) mit:
- Route-Groups (auth)/(app) inkl. loading/error/not-found je Group;
  Guard-Slot-Kommentar im (app)/layout.tsx (vom Auth-WS zu füllen).
- "Amtlich"/Netzknoten-Designsystem: Tailwind-Tokens (Navy #1B3A5B,
  Signalrot #E2231A, Anthrazit, Nebelgrau, bereit/Wartung), tabular-nums,
  Serif-Display/Inter-Sans via CSS-Variablen, Inline-SVG-Logo.
- Radix-Basiskomponenten (button/input/label/badge/tabs/dialog/select/
  switch/slider); StatusBadge entspricht asset_status.
- Kanonisches src/lib/env.ts (Zod, Fail-Fast) mit ALLEN DB-/Auth-/Geo-Slots
  inkl. AUTH_URL; isHttps-Ableitung. Zentrale i18n-Tabelle de.ts + t().
- Drizzle-Setup: client.ts (Pool-Singleton), leeres schema/index.ts-Barrel
  (KEIN Migrations-Eigentümer), drizzle.config.ts, .env.example.
- next.config.ts: output:standalone, experimental.authInterrupts,
  Security-Header. Vitest + Fail-Fast-Env-Test (TDD, 5/5 grün).

Bewusst KEINE Auth-Logik und KEINE fachlichen Tabellen.

Verifikation: typecheck/lint/test grün; npm run build erzeugt
.next/standalone/server.js; curl /anmelden -> lang="de" + FlorianNetz.
next/font/google durch CSS-Variablen ersetzt (air-gapped-Build).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthias Hochmeister
2026-06-08 16:57:01 +02:00
parent 6ebcd270ad
commit 4707844dbc
45 changed files with 10966 additions and 1 deletions

55
src/lib/i18n/de.ts Normal file
View File

@@ -0,0 +1,55 @@
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.",
},
status: {
einsatzbereit: "einsatzbereit",
wartung: "Wartung",
ausser_dienst: "außer Dienst",
},
search: {
meinStandort: "Meinen Standort verwenden",
suchen: "Suchen",
keineTreffer: "Keine Treffer.",
luftlinie: "Luftlinie (geschätzt)",
},
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",
},
} 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;
}