135 lines
4 KiB
TypeScript
135 lines
4 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { requireAuth } from '../../../../lib/jwt';
|
|
import type { Locale } from '../../../../lib/i18n';
|
|
|
|
type LocaleFields = { title: string; teaser: string; description: string };
|
|
const SUPPORTED_LOCALES: Locale[] = ['en', 'fi', 'sv'];
|
|
|
|
function loadApiKey() {
|
|
if (process.env.OPENAI_TRANSLATIONS_KEY) return process.env.OPENAI_TRANSLATIONS_KEY;
|
|
const newKeyPath = path.join(process.cwd(), 'creds', 'openai-translations.key');
|
|
try {
|
|
return fs.readFileSync(newKeyPath, 'utf8').trim();
|
|
} catch {
|
|
// ignore
|
|
}
|
|
if (process.env.OPENAI_API_KEY) return process.env.OPENAI_API_KEY;
|
|
const fallbackPath = path.join(process.cwd(), 'creds', 'openai.key');
|
|
try {
|
|
return fs.readFileSync(fallbackPath, 'utf8').trim();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function POST(req: Request) {
|
|
try {
|
|
await requireAuth(req);
|
|
} catch (err) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const apiKey = loadApiKey();
|
|
if (!apiKey) {
|
|
return NextResponse.json({ error: 'Missing OpenAI API key' }, { status: 500 });
|
|
}
|
|
|
|
let body: any;
|
|
try {
|
|
body = await req.json();
|
|
} catch {
|
|
return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
|
|
}
|
|
|
|
const incoming = body?.translations as Record<Locale, LocaleFields> | undefined;
|
|
const currentLocale = (body?.currentLocale as Locale) ?? 'en';
|
|
if (!incoming) {
|
|
return NextResponse.json({ error: 'Missing translations' }, { status: 400 });
|
|
}
|
|
|
|
const payload = SUPPORTED_LOCALES.reduce(
|
|
(acc, loc) => ({
|
|
...acc,
|
|
[loc]: {
|
|
title: incoming[loc]?.title || '',
|
|
teaser: incoming[loc]?.teaser || '',
|
|
description: incoming[loc]?.description || '',
|
|
},
|
|
}),
|
|
{} as Record<Locale, LocaleFields>,
|
|
);
|
|
|
|
const messages = [
|
|
{
|
|
role: 'system',
|
|
content:
|
|
'You are translating holiday rental listing copy between Finnish, Swedish, and English. Fill in missing locales, keep existing text unchanged, preserve meaning and tone, and respond with JSON only.',
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: JSON.stringify(
|
|
{
|
|
sourceLocale: currentLocale,
|
|
targetLocales: SUPPORTED_LOCALES.filter((l) => l !== currentLocale),
|
|
locales: payload,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
},
|
|
{
|
|
role: 'system',
|
|
content:
|
|
'Return JSON with top-level "locales" containing keys en, fi, sv. Each locale has title, teaser, description. Do not include explanations.',
|
|
},
|
|
];
|
|
|
|
let content = '';
|
|
try {
|
|
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model: 'gpt-4o-mini',
|
|
temperature: 0.2,
|
|
messages,
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const errText = await res.text();
|
|
return NextResponse.json({ error: 'AI request failed', detail: errText }, { status: res.status || 500 });
|
|
}
|
|
|
|
const data = await res.json();
|
|
content = data?.choices?.[0]?.message?.content ?? '';
|
|
} catch (err: any) {
|
|
return NextResponse.json({ error: 'AI request failed', detail: err?.message }, { status: 500 });
|
|
}
|
|
|
|
const jsonText = content.match(/\{[\s\S]*\}/)?.[0] ?? content;
|
|
try {
|
|
const parsed = JSON.parse(jsonText);
|
|
const locales = parsed?.locales || parsed;
|
|
if (!locales) throw new Error('missing locales');
|
|
const result = SUPPORTED_LOCALES.reduce(
|
|
(acc, loc) => ({
|
|
...acc,
|
|
[loc]: {
|
|
title: locales[loc]?.title ?? '',
|
|
teaser: locales[loc]?.teaser ?? '',
|
|
description: locales[loc]?.description ?? '',
|
|
},
|
|
}),
|
|
{} as Record<Locale, LocaleFields>,
|
|
);
|
|
return NextResponse.json({ translations: result });
|
|
} catch (err) {
|
|
return NextResponse.json({ error: 'Could not parse AI response' }, { status: 500 });
|
|
}
|
|
}
|