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(); 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 }); } }