import { NextResponse } from "next/server"; import { listActiveLanguageCodes } from "@/lib/db/languages"; import { replaceTranslations, type TranslationTable } from "@/lib/db/translations"; import { requireRole, UnauthorizedError, ForbiddenError } from "@/lib/auth/session"; import { isSameOrigin } from "@/lib/origin"; // Übersetzt einen deutschen Text via DeepL in alle aktiven, von DeepL // unterstützten Sprachen und schreibt das Ergebnis in die passende // *_translations-Tabelle. Ohne DEEPL_API_KEY läuft die Route kontrolliert // ins Leere (configured: false), damit das UI die manuelle Eingabe anbietet. const ALLOWED_TABLES: Record = { dishes: { idColumn: "id", translationTable: "dish_translations" }, sections: { idColumn: "key", translationTable: "section_translations" }, allergens: { idColumn: "code", translationTable: "allergen_translations" }, additives: { idColumn: "code", translationTable: "additive_translations" }, closed_days: { idColumn: "closed_date", translationTable: "closed_day_translations" }, }; // Unsere Sprachcodes -> DeepL target_lang. Sprachen ohne Eintrag hier sind // von DeepL nicht unterstützt und bleiben bei manueller Eingabe. const DEEPL_TARGET_LANG: Record = { en: "EN", pl: "PL", fr: "FR", es: "ES", it: "IT", nl: "NL", pt: "PT-PT", ru: "RU", ja: "JA", zh: "ZH", cs: "CS", da: "DA", el: "EL", fi: "FI", hu: "HU", id: "ID", ko: "KO", nb: "NB", ro: "RO", sk: "SK", sl: "SL", sv: "SV", tr: "TR", uk: "UK", bg: "BG", et: "ET", lv: "LV", lt: "LT", }; async function translateWithDeepL(text: string, targetLang: string, apiKey: string) { const host = apiKey.endsWith(":fx") ? "https://api-free.deepl.com" : "https://api.deepl.com"; const res = await fetch(`${host}/v2/translate`, { method: "POST", headers: { Authorization: `DeepL-Auth-Key ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ text: [text], target_lang: targetLang, source_lang: "DE", }), }); if (!res.ok) { throw new Error(`DeepL antwortete mit ${res.status}: ${await res.text()}`); } const data = await res.json(); return data.translations?.[0]?.text as string | undefined; } export async function POST(request: Request) { if (!isSameOrigin(request)) { return NextResponse.json({ error: "Nicht erlaubt." }, { status: 403 }); } try { await requireRole(["staff", "admin"]); } catch (err) { if (err instanceof UnauthorizedError) { return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 }); } if (err instanceof ForbiddenError) { return NextResponse.json({ error: "Nicht berechtigt." }, { status: 403 }); } throw err; } const body = await request.json().catch(() => null); const { table, id, text } = body ?? {}; const config = ALLOWED_TABLES[table]; if (!config || typeof text !== "string" || !text.trim()) { return NextResponse.json({ error: "Ungültige Eingaben." }, { status: 400 }); } // Bei einem noch nicht gespeicherten (neuen) Eintrag gibt es keine // Zeile zum Aktualisieren – nur übersetzen, das Speichern übernimmt // anschließend das reguläre Formular. const persist = typeof id === "string" && id.length > 0; const apiKey = process.env.DEEPL_API_KEY; if (!apiKey) { return NextResponse.json({ configured: false }); } const activeCodes = (await listActiveLanguageCodes()).filter((c) => c !== "de"); const targets = activeCodes .map((code) => ({ code, target: DEEPL_TARGET_LANG[code.toLowerCase()] })) .filter((t): t is { code: string; target: string } => Boolean(t.target)); try { const results = await Promise.all( targets.map(async (t) => ({ code: t.code, text: await translateWithDeepL(text, t.target, apiKey), })) ); const translations: Record = {}; for (const r of results) { if (r.text) translations[r.code] = r.text; } if (Object.keys(translations).length === 0) { throw new Error("Keine Übersetzung erhalten."); } if (persist) { await replaceTranslations(config.translationTable, id, translations); } return NextResponse.json({ configured: true, translations }); } catch (err) { return NextResponse.json( { error: err instanceof Error ? err.message : "Übersetzung fehlgeschlagen." }, { status: 502 } ); } }