Files
Speisekarte/app/api/translate/route.ts
T
florian.wilde 30a02ba7b7 DeepL-Anbindung für automatische Übersetzung
app/api/translate ruft DeepL für EN+PL auf und schreibt das Ergebnis
in die jeweilige _en/_pl-Spalte (Staff/Admin-only, Service-Role-Key).
Ohne DEEPL_API_KEY (neue Docker-Variable) antwortet die Route mit
configured:false, das Frontend zeigt dann den Hinweis zur manuellen
Eingabe statt eines Fehlers.
2026-07-18 02:08:02 +02:00

101 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextResponse } from "next/server";
import { createClient } from "@/lib/supabase/server";
import { createAdminClient } from "@/lib/supabase/admin";
import { isSameOrigin } from "@/lib/origin";
// Übersetzt einen deutschen Text nach Englisch/Polnisch via DeepL und
// schreibt das Ergebnis in die _en/_pl-Spalte der angegebenen Zeile.
// Ohne DEEPL_API_KEY läuft die Route kontrolliert ins Leere (configured:
// false), damit das UI die manuelle Eingabe anbieten kann.
const ALLOWED_TABLES: Record<string, { idColumn: string; textColumn: string }> = {
dishes: { idColumn: "id", textColumn: "name" },
sections: { idColumn: "key", textColumn: "label" },
allergens: { idColumn: "code", textColumn: "name" },
additives: { idColumn: "code", textColumn: "name" },
};
async function translateWithDeepL(text: string, targetLang: "EN" | "PL", 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 });
}
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 });
const admin = createAdminClient();
const { data: profile } = await admin
.from("profiles")
.select("role")
.eq("id", user.id)
.single();
if (!profile || !["staff", "admin"].includes(profile.role)) {
return NextResponse.json({ error: "Nicht berechtigt." }, { status: 403 });
}
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 });
}
try {
const [en, pl] = await Promise.all([
translateWithDeepL(text, "EN", apiKey),
translateWithDeepL(text, "PL", apiKey),
]);
if (!en || !pl) throw new Error("Keine Übersetzung erhalten.");
if (persist) {
const { error } = await admin
.from(table)
.update({ [`${config.textColumn}_en`]: en, [`${config.textColumn}_pl`]: pl })
.eq(config.idColumn, id);
if (error) throw new Error(error.message);
}
return NextResponse.json({ configured: true, en, pl });
} catch (err) {
return NextResponse.json(
{ error: err instanceof Error ? err.message : "Übersetzung fehlgeschlagen." },
{ status: 502 }
);
}
}