"use client"; // Verwaltung der Sprachpakete (nur Admin): Sprachen aktivieren/ // deaktivieren (bis auf eine einzige reduzierbar), Vorlage der // aktuellen Begriffe herunterladen, Übersetzungsdatei hochladen, neue // Sprache anlegen/löschen. Deutsch ist die feste Basis-Sprache und // nicht löschbar. import { useCallback, useEffect, useState } from "react"; import { formatMessage } from "@/lib/i18n/shared"; import { useDict } from "@/components/i18n/LocaleProvider"; interface LanguageItem { code: string; label: string; is_base: boolean; active: boolean; percent: number; outdated: boolean; missingCount: number; } export default function LanguageManager() { const dict = useDict(); const [languages, setLanguages] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); const [busy, setBusy] = useState(false); const [newCode, setNewCode] = useState(""); const [newLabel, setNewLabel] = useState(""); const load = useCallback(async () => { const res = await fetch("/api/admin/languages"); if (!res.ok) { setError(formatMessage(dict.languageManager.loadError, { message: String(res.status) })); } else { setLanguages(await res.json()); } setLoading(false); }, [dict]); useEffect(() => { load(); }, [load]); async function toggleActive(item: LanguageItem) { setError(null); setNotice(null); setBusy(true); const res = await fetch(`/api/admin/languages/${item.code}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ active: !item.active }), }); setBusy(false); if (!res.ok) { const data = await res.json().catch(() => ({})); setError( data.error === "cannot_deactivate_last" ? dict.languageManager.cannotDeactivateLast : formatMessage(dict.languageManager.saveError, { message: data.error ?? String(res.status) }) ); return; } await load(); } async function deleteLanguage(item: LanguageItem) { if (!window.confirm(formatMessage(dict.languageManager.confirmDelete, { label: item.label }))) return; setError(null); setNotice(null); setBusy(true); const res = await fetch(`/api/admin/languages/${item.code}`, { method: "DELETE" }); setBusy(false); if (!res.ok) { const data = await res.json().catch(() => ({})); setError( data.error === "cannot_delete_base" ? dict.languageManager.cannotDeleteBase : formatMessage(dict.languageManager.deleteError, { message: data.error ?? String(res.status) }) ); return; } await load(); } async function downloadTemplate() { setError(null); const res = await fetch("/api/admin/languages/template"); if (!res.ok) { setError(formatMessage(dict.languageManager.loadError, { message: String(res.status) })); return; } const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "sprachvorlage.json"; a.click(); URL.revokeObjectURL(url); } async function uploadImport(code: string, e: React.ChangeEvent) { const file = e.target.files?.[0]; e.target.value = ""; if (!file) return; setError(null); setNotice(null); setBusy(true); const fd = new FormData(); fd.append("file", file); const res = await fetch(`/api/admin/languages/${code}/import`, { method: "POST", body: fd, }); setBusy(false); if (!res.ok) { const data = await res.json().catch(() => ({})); setError(formatMessage(dict.languageManager.importError, { message: data.error ?? String(res.status) })); return; } const data = (await res.json()) as { imported: number; skipped: { key: string; reason: string }[]; }; let msg = formatMessage(dict.languageManager.importSuccess, { count: data.imported }); if (data.skipped.length > 0) { const first = data.skipped[0]; const [reason, placeholder] = first.reason.split(":"); if (reason === "missing_placeholder") { msg += " " + formatMessage(dict.languageManager.importMissingPlaceholder, { key: first.key, placeholder: `{${placeholder}}`, }); if (data.skipped.length > 1) msg += ` (+${data.skipped.length - 1})`; } } setNotice(msg); await load(); } async function createLanguage(e: React.FormEvent) { e.preventDefault(); setError(null); setNotice(null); const code = newCode.trim().toLowerCase(); const label = newLabel.trim(); if (!/^[a-z]{2,5}$/.test(code)) { setError(dict.languageManager.invalidCode); return; } if (!label) return; setBusy(true); const res = await fetch("/api/admin/languages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code, label }), }); setBusy(false); if (!res.ok) { const data = await res.json().catch(() => ({})); setError( data.error === "duplicate_code" ? formatMessage(dict.languageManager.duplicateCode, { code }) : formatMessage(dict.languageManager.saveError, { message: data.error ?? String(res.status) }) ); return; } setNewCode(""); setNewLabel(""); await load(); } return (

{dict.languageManager.title}

{dict.languageManager.hint}

{error && (

{error}

)} {notice &&

{notice}

}

{dict.languageManager.title}

{loading ? (

{dict.common.loading}

) : (
    {languages.map((item) => (
  • {item.label} {item.is_base ? ( ({dict.languageManager.baseBadge}) ) : ( {item.outdated ? formatMessage(dict.languageManager.outdated, { count: item.missingCount }) : formatMessage(dict.languageManager.completeness, { percent: item.percent })} )} {!item.is_base && ( <> )}
  • ))}
)}

{dict.languageManager.newLanguageTitle}

setNewCode(e.target.value)} required />
setNewLabel(e.target.value)} required />
); }