Fundament: ISMS-Module entfernt; Craftvia-Rollen, Module, Navigation, i18n-Split
- ISMS-Routen, Actions, Server-/Lib-Code, Komponenten, Prisma-Modelle, Seeds, Importer, Skripte und ISMS-Tests entfernt (Fundament bleibt: Auth, Identity, MFA/WebAuthn, RBAC, Audit, Mail, Storage, Backup/DSGVO, Plattform-Admin) - Schema auf Fundament-Modelle reduziert; TenantSettings generisch (+phone/email) - TENANT_MODELS (db.ts, backup/topology.ts) und PII-Felder ausgedünnt - RBAC: Rollen tenant-admin/backoffice/team-lead/technician + Craftvia-Permissions - Modul-Katalog (customers, sites, teams, work_orders, imports, field, reports, emergency, documents, notifications, lotse) + Navigation aus src/lib/nav.ts - Modul-Routen mit requireModule-Layout und Platzhalterseite - Message-Katalog je Namespace (messages/<locale>/<namespace>.json), fs-Loader - check-module-guards: Modul-Key aus src/server/actions/<moduleKey>/ - Provisionierung, Admin-Konsole, Einstellungen, Files-Route, Mail entkoppelt - Seed minimal (demo/demo2, Nutzer je Rolle); Fundament-Tests auf Role/ NotificationPreference-Fixtures umgestellt Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+36
-10
@@ -1,22 +1,48 @@
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import { auth } from "@/server/auth";
|
||||
import { prisma } from "@/server/db";
|
||||
|
||||
/**
|
||||
* i18n (SPEC §7): alle UI-Strings kommen aus dem Message-Katalog.
|
||||
* i18n: alle UI-Strings kommen aus dem Message-Katalog.
|
||||
*
|
||||
* Die UI-Sprache ist eine PERSÖNLICHE Präferenz und lebt an der globalen
|
||||
* `Identity` (Option C, `uiLocale`) — sie folgt der Person über alle Mandanten.
|
||||
* Getrennt von `TenantSettings.locale` (= Vorlagen-Import-Sprache des Inhalts).
|
||||
* Katalog-Aufteilung (konfliktfrei für parallele Lanes):
|
||||
* messages/<locale>/<namespace>.json — ein File je Top-Level-Namespace.
|
||||
* Der Dateiname IST der Namespace: messages/de/customers.json → t = getTranslations("customers").
|
||||
* Neue Lane = neues File in messages/de/ UND messages/en/ — kein Code-Eingriff nötig.
|
||||
*
|
||||
* Robust: auf Login-/Session-losen Seiten (oder wenn die Session keinen
|
||||
* Identity-Kontext trägt, z. B. reine Plattform-Sessions) fällt die Sprache auf
|
||||
* `de` zurück. Fehler bei der Auflösung dürfen das Rendering nie brechen.
|
||||
* Laden: zur Laufzeit per fs aus `<cwd>/messages/<locale>/*.json` (gemerged). Für den
|
||||
* standalone-Build kopiert `outputFileTracingIncludes` (next.config.ts) den Ordner mit;
|
||||
* im Dockerfile liegt er damit automatisch unter /app/messages. In Produktion wird der
|
||||
* gemergte Katalog je Locale gecacht, im Dev bei jedem Request neu gelesen (Hot-Edit).
|
||||
*
|
||||
* Die UI-Sprache ist eine PERSÖNLICHE Präferenz an der globalen `Identity` (`uiLocale`).
|
||||
* Auf session-losen Seiten fällt sie auf `de` zurück; Fehler brechen das Rendering nie.
|
||||
*/
|
||||
const SUPPORTED = new Set(["de", "en"]);
|
||||
const DEFAULT_LOCALE = "de";
|
||||
|
||||
type Messages = Record<string, unknown>;
|
||||
const cache = new Map<string, Messages>();
|
||||
|
||||
export async function loadMessages(locale: string): Promise<Messages> {
|
||||
const cached = cache.get(locale);
|
||||
if (cached) return cached;
|
||||
|
||||
const dir = join(process.cwd(), "messages", locale);
|
||||
const files = (await readdir(dir)).filter((f) => f.endsWith(".json")).sort();
|
||||
const messages: Messages = {};
|
||||
for (const file of files) {
|
||||
const namespace = file.slice(0, -".json".length);
|
||||
messages[namespace] = JSON.parse(await readFile(join(dir, file), "utf8"));
|
||||
}
|
||||
if (process.env.NODE_ENV === "production") cache.set(locale, messages);
|
||||
return messages;
|
||||
}
|
||||
|
||||
export default getRequestConfig(async () => {
|
||||
let locale = "de";
|
||||
let locale = DEFAULT_LOCALE;
|
||||
try {
|
||||
const session = await auth();
|
||||
const identityId = session?.user?.identityId;
|
||||
@@ -28,10 +54,10 @@ export default getRequestConfig(async () => {
|
||||
if (identity?.uiLocale && SUPPORTED.has(identity.uiLocale)) locale = identity.uiLocale;
|
||||
}
|
||||
} catch {
|
||||
locale = "de";
|
||||
locale = DEFAULT_LOCALE;
|
||||
}
|
||||
return {
|
||||
locale,
|
||||
messages: (await import(`../../messages/${locale}.json`)).default,
|
||||
messages: await loadMessages(locale),
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user