Mehrsprachige Oberfläche + lokalisierte Gericht-/Bereichsnamen

Alle Seiten und Komponenten (öffentliche Speisekarte, Login, Kantinen-
und Admin-Bereich, eigenes Konto) verwenden jetzt die Wörterbücher aus
lib/i18n statt fest codierter deutscher Texte. Gerichte/Bereiche/
Allergene/Zusatzstoffe zeigen den Namen zur aktuell gewählten Sprache;
Gerichte- und Kennzeichnungs-Formulare bekommen Englisch-/Polnisch-
Felder mit Übersetzen-Button (siehe vorherigen Commit). Datumsformat-
ierung (formatDateLong) nimmt jetzt ein locale-Argument statt fix
'de-DE'.
This commit is contained in:
2026-07-18 02:08:03 +02:00
parent 30a02ba7b7
commit a6165de10c
14 changed files with 586 additions and 328 deletions
+28 -25
View File
@@ -2,7 +2,9 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { createClient } from "@/lib/supabase/client";
import { formatMessage } from "@/lib/i18n/shared";
import type { Profile, Role } from "@/lib/types";
import { useDict } from "@/components/i18n/LocaleProvider";
interface UserForm {
id: string | null; // null = neuer Benutzer
@@ -22,13 +24,13 @@ const EMPTY_FORM: UserForm = {
passwordRepeat: "",
};
const ROLE_LABELS: Record<Role, string> = {
admin: "Administrator",
staff: "Kantinenmitarbeiter",
};
export default function UserManager() {
const supabase = useMemo(() => createClient(), []);
const dict = useDict();
const roleLabels: Record<Role, string> = {
admin: dict.common.roleAdmin,
staff: dict.common.roleStaff,
};
const [profiles, setProfiles] = useState<Profile[]>([]);
const [me, setMe] = useState<string | null>(null);
const [form, setForm] = useState<UserForm | null>(null);
@@ -41,10 +43,11 @@ export default function UserManager() {
supabase.from("profiles").select("*").order("created_at"),
supabase.auth.getUser(),
]);
if (profileError) setError(`Laden fehlgeschlagen: ${profileError.message}`);
if (profileError)
setError(formatMessage(dict.userManager.loadError, { message: profileError.message }));
else setProfiles(profileData as Profile[]);
setMe(userData.user?.id ?? null);
}, [supabase]);
}, [supabase, dict]);
useEffect(() => {
load();
@@ -77,7 +80,7 @@ export default function UserManager() {
e.preventDefault();
if (!form) return;
if (form.password && form.password !== form.passwordRepeat) {
setError("Die Passwörter stimmen nicht überein.");
setError(dict.userManager.passwordMismatch);
return;
}
const ok = form.id
@@ -102,7 +105,7 @@ export default function UserManager() {
async function remove(profile: Profile) {
if (
!window.confirm(
`Benutzer „${profile.email}“ wirklich löschen? Der Zugang wird dauerhaft entfernt.`
formatMessage(dict.userManager.confirmDelete, { email: profile.email })
)
)
return;
@@ -113,14 +116,14 @@ export default function UserManager() {
<div className="max-w-3xl">
<div className="flex items-center gap-3 mb-4">
<h1 className="text-xl font-bold text-[var(--c-primary)] mr-auto">
Benutzer
{dict.userManager.title}
</h1>
<button
type="button"
className="btn btn-add"
onClick={() => setForm(EMPTY_FORM)}
>
Neuer Benutzer
{dict.userManager.newUser}
</button>
</div>
@@ -133,12 +136,12 @@ export default function UserManager() {
{form && (
<form onSubmit={save} className="card mb-4 space-y-3">
<h2 className="font-semibold">
{form.id ? "Benutzer bearbeiten" : "Neuer Benutzer"}
{form.id ? dict.userManager.editUser : dict.userManager.newUser}
</h2>
{!form.id && (
<div>
<label className="label" htmlFor="user-email">
E-Mail-Adresse
{dict.common.email}
</label>
<input
id="user-email"
@@ -152,7 +155,7 @@ export default function UserManager() {
)}
<div>
<label className="label" htmlFor="user-name">
Name
{dict.common.name}
</label>
<input
id="user-name"
@@ -163,7 +166,7 @@ export default function UserManager() {
</div>
<div className="max-w-60">
<label className="label" htmlFor="user-role">
Rolle
{dict.common.role}
</label>
<select
id="user-role"
@@ -173,13 +176,13 @@ export default function UserManager() {
setForm({ ...form, role: e.target.value as Role })
}
>
<option value="staff">Kantinenmitarbeiter</option>
<option value="admin">Administrator</option>
<option value="staff">{dict.common.roleStaff}</option>
<option value="admin">{dict.common.roleAdmin}</option>
</select>
</div>
<div className="max-w-60">
<label className="label" htmlFor="user-password">
{form.id ? "Neues Passwort (optional)" : "Passwort"}
{form.id ? dict.userManager.newPasswordLabel : dict.common.password}
</label>
<input
id="user-password"
@@ -194,7 +197,7 @@ export default function UserManager() {
</div>
<div className="max-w-60">
<label className="label" htmlFor="user-password-repeat">
Passwort wiederholen
{dict.userManager.repeatPasswordLabel}
</label>
<input
id="user-password-repeat"
@@ -211,14 +214,14 @@ export default function UserManager() {
</div>
<div className="flex gap-2">
<button type="submit" className="btn btn-add" disabled={busy}>
Speichern
{dict.common.save}
</button>
<button
type="button"
className="btn btn-neutral"
onClick={() => setForm(null)}
>
Abbrechen
{dict.common.cancel}
</button>
</div>
</form>
@@ -230,9 +233,9 @@ export default function UserManager() {
<span className="mr-auto">
<span className="font-semibold">{p.full_name || p.email}</span>{" "}
<span className="text-gray-500">
– {p.email} – {ROLE_LABELS[p.role]}
– {p.email} – {roleLabels[p.role]}
</span>
{p.id === me && <span className="text-gray-400"> (du)</span>}
{p.id === me && <span className="text-gray-400"> {dict.common.you}</span>}
</span>
<button
type="button"
@@ -248,7 +251,7 @@ export default function UserManager() {
})
}
>
Bearbeiten
{dict.common.edit}
</button>
{p.id !== me && (
<button
@@ -256,7 +259,7 @@ export default function UserManager() {
className="btn btn-del"
onClick={() => remove(p)}
>
Löschen
{dict.common.delete}
</button>
)}
</li>