Files
octto-engenharia/frontend/src/app/api/translate/route.ts

204 lines
6.3 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import prisma from '@/lib/prisma';
const LIBRETRANSLATE_URL = process.env.LIBRETRANSLATE_URL || 'https://libretranslate.stackbyte.cloud';
// Cache em memória para evitar queries repetidas na mesma sessão
const memoryCache = new Map<string, string>();
export async function POST(request: NextRequest) {
try {
const { text, source = 'pt', target = 'en' } = await request.json();
if (!text || typeof text !== 'string') {
return NextResponse.json({ error: 'Texto é obrigatório' }, { status: 400 });
}
// Se origem e destino são iguais, retorna o texto original
if (source === target) {
return NextResponse.json({ translatedText: text });
}
const cacheKey = `${source}:${target}:${text}`;
// 1. Verificar cache em memória (mais rápido)
if (memoryCache.has(cacheKey)) {
return NextResponse.json({ translatedText: memoryCache.get(cacheKey), cached: 'memory' });
}
// 2. Verificar banco de dados
const dbTranslation = await prisma.translation.findUnique({
where: {
sourceText_sourceLang_targetLang: {
sourceText: text,
sourceLang: source,
targetLang: target,
},
},
});
if (dbTranslation) {
// Salvar em memória para próximas requisições
memoryCache.set(cacheKey, dbTranslation.translatedText);
return NextResponse.json({ translatedText: dbTranslation.translatedText, cached: 'database' });
}
// 3. Chamar LibreTranslate (só se não tiver no banco)
console.log(`[Translate] Chamando LibreTranslate para: "${text.substring(0, 30)}..."`);
const response = await fetch(`${LIBRETRANSLATE_URL}/translate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
q: text,
source: source,
target: target,
format: 'text',
}),
});
if (!response.ok) {
console.error('LibreTranslate error:', await response.text());
return NextResponse.json({ translatedText: text }); // Fallback: retorna original
}
const data = await response.json();
const translatedText = data.translatedText || text;
// 4. Salvar no banco de dados (persistente)
try {
await prisma.translation.create({
data: {
sourceText: text,
sourceLang: source,
targetLang: target,
translatedText: translatedText,
},
});
console.log(`[Translate] Salvo no banco: "${text.substring(0, 30)}..." -> "${translatedText.substring(0, 30)}..."`);
} catch (dbError) {
// Pode falhar se já existir (race condition), ignorar
console.log('[Translate] Já existe no banco (race condition)');
}
// 5. Salvar em memória
memoryCache.set(cacheKey, translatedText);
return NextResponse.json({ translatedText, cached: false });
} catch (error) {
console.error('Translation error:', error);
return NextResponse.json({ error: 'Erro ao traduzir' }, { status: 500 });
}
}
// Endpoint para traduzir múltiplos textos de uma vez
export async function PUT(request: NextRequest) {
try {
const { texts, source = 'pt', target = 'en' } = await request.json();
if (!texts || !Array.isArray(texts)) {
return NextResponse.json({ error: 'Array de textos é obrigatório' }, { status: 400 });
}
if (source === target) {
return NextResponse.json({ translations: texts });
}
const results: string[] = [];
const toTranslate: { index: number; text: string }[] = [];
// Verificar quais já existem no banco
for (let i = 0; i < texts.length; i++) {
const text = texts[i];
if (!text) {
results[i] = text || '';
continue;
}
const cacheKey = `${source}:${target}:${text}`;
// Verificar memória
if (memoryCache.has(cacheKey)) {
results[i] = memoryCache.get(cacheKey)!;
continue;
}
// Verificar banco
const dbTranslation = await prisma.translation.findUnique({
where: {
sourceText_sourceLang_targetLang: {
sourceText: text,
sourceLang: source,
targetLang: target,
},
},
});
if (dbTranslation) {
results[i] = dbTranslation.translatedText;
memoryCache.set(cacheKey, dbTranslation.translatedText);
} else {
toTranslate.push({ index: i, text });
}
}
// Se todos estão em cache, retorna direto
if (toTranslate.length === 0) {
return NextResponse.json({ translations: results, allCached: true });
}
// Traduzir os que faltam (em paralelo, mas com limite)
const BATCH_SIZE = 5; // Traduzir 5 por vez para não sobrecarregar
for (let i = 0; i < toTranslate.length; i += BATCH_SIZE) {
const batch = toTranslate.slice(i, i + BATCH_SIZE);
await Promise.all(
batch.map(async ({ index, text }) => {
try {
const response = await fetch(`${LIBRETRANSLATE_URL}/translate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ q: text, source, target, format: 'text' }),
});
if (response.ok) {
const data = await response.json();
const translatedText = data.translatedText || text;
results[index] = translatedText;
// Salvar no banco
try {
await prisma.translation.create({
data: {
sourceText: text,
sourceLang: source,
targetLang: target,
translatedText,
},
});
} catch (e) {
// Ignorar se já existe
}
memoryCache.set(`${source}:${target}:${text}`, translatedText);
} else {
results[index] = text;
}
} catch (e) {
results[index] = text;
}
})
);
}
return NextResponse.json({ translations: results });
} catch (error) {
console.error('Batch translation error:', error);
return NextResponse.json({ error: 'Erro ao traduzir' }, { status: 500 });
}
}