194 lines
5.9 KiB
TypeScript
194 lines
5.9 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import prisma from '@/lib/prisma';
|
|
import { cookies } from 'next/headers';
|
|
import jwt from 'jsonwebtoken';
|
|
import { Prisma } from '@prisma/client';
|
|
|
|
const LIBRETRANSLATE_URL = process.env.LIBRETRANSLATE_URL || 'https://libretranslate.stackbyte.cloud';
|
|
const SUPPORTED_LOCALES = ['en', 'es'];
|
|
|
|
// Autenticação
|
|
async function authenticate() {
|
|
const cookieStore = await cookies();
|
|
const token = cookieStore.get('auth_token')?.value;
|
|
|
|
if (!token) return null;
|
|
|
|
try {
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string };
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: decoded.userId },
|
|
select: { id: true, email: true, name: true }
|
|
});
|
|
return user;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Traduzir um texto
|
|
async function translateText(text: string, targetLang: string): Promise<string> {
|
|
if (!text || text.trim() === '' || targetLang === 'pt') return text;
|
|
|
|
// Verificar cache no banco primeiro
|
|
const cached = await prisma.translation.findUnique({
|
|
where: {
|
|
sourceText_sourceLang_targetLang: {
|
|
sourceText: text,
|
|
sourceLang: 'pt',
|
|
targetLang: targetLang,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (cached) {
|
|
return cached.translatedText;
|
|
}
|
|
|
|
try {
|
|
console.log(`[i18n] Traduzindo: "${text.substring(0, 30)}..." para ${targetLang}`);
|
|
|
|
const response = await fetch(`${LIBRETRANSLATE_URL}/translate`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ q: text, source: 'pt', target: targetLang, format: 'text' }),
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
const translatedText = data.translatedText || text;
|
|
|
|
// Salvar no cache
|
|
try {
|
|
await prisma.translation.create({
|
|
data: {
|
|
sourceText: text,
|
|
sourceLang: 'pt',
|
|
targetLang: targetLang,
|
|
translatedText,
|
|
},
|
|
});
|
|
} catch {
|
|
// Ignorar se já existe
|
|
}
|
|
|
|
return translatedText;
|
|
}
|
|
} catch (error) {
|
|
console.error(`[i18n] Erro ao traduzir para ${targetLang}:`, error);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
// Traduzir objeto recursivamente
|
|
async function translateContent(content: unknown, targetLang: string): Promise<unknown> {
|
|
if (typeof content === 'string') {
|
|
return await translateText(content, targetLang);
|
|
}
|
|
|
|
if (Array.isArray(content)) {
|
|
const results = [];
|
|
for (const item of content) {
|
|
results.push(await translateContent(item, targetLang));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
if (content && typeof content === 'object') {
|
|
const result: Record<string, unknown> = {};
|
|
for (const [key, value] of Object.entries(content)) {
|
|
// Não traduzir campos técnicos
|
|
if (['icon', 'image', 'img', 'url', 'href', 'id', 'slug', 'src', 'link'].includes(key)) {
|
|
result[key] = value;
|
|
} else {
|
|
result[key] = await translateContent(value, targetLang);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
// POST /api/admin/translate-pages - Traduzir todas as páginas para EN e ES
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const user = await authenticate();
|
|
if (!user) {
|
|
return NextResponse.json({ error: 'Não autorizado' }, { status: 401 });
|
|
}
|
|
|
|
const body = await request.json().catch(() => ({}));
|
|
const slugFilter = body.slug; // Opcional: traduzir só uma página específica
|
|
|
|
// Buscar todas as páginas em português
|
|
const ptPages = await prisma.pageContent.findMany({
|
|
where: slugFilter
|
|
? { slug: slugFilter, locale: 'pt' }
|
|
: { locale: 'pt' }
|
|
});
|
|
|
|
if (ptPages.length === 0) {
|
|
return NextResponse.json({ error: 'Nenhuma página encontrada para traduzir' }, { status: 404 });
|
|
}
|
|
|
|
const results: { slug: string; locale: string; status: string }[] = [];
|
|
|
|
for (const page of ptPages) {
|
|
for (const targetLocale of SUPPORTED_LOCALES) {
|
|
try {
|
|
console.log(`[i18n] Traduzindo página "${page.slug}" para ${targetLocale}...`);
|
|
|
|
const translatedContent = await translateContent(page.content, targetLocale) as Prisma.InputJsonValue;
|
|
|
|
await prisma.pageContent.upsert({
|
|
where: { slug_locale: { slug: page.slug, locale: targetLocale } },
|
|
update: { content: translatedContent },
|
|
create: { slug: page.slug, locale: targetLocale, content: translatedContent }
|
|
});
|
|
|
|
results.push({ slug: page.slug, locale: targetLocale, status: 'success' });
|
|
console.log(`[i18n] ✓ Página "${page.slug}" traduzida para ${targetLocale}`);
|
|
} catch (error) {
|
|
console.error(`[i18n] ✗ Erro ao traduzir "${page.slug}" para ${targetLocale}:`, error);
|
|
results.push({ slug: page.slug, locale: targetLocale, status: 'error' });
|
|
}
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: `Tradução concluída para ${ptPages.length} página(s)`,
|
|
results
|
|
});
|
|
} catch (error) {
|
|
console.error('Erro ao traduzir páginas:', error);
|
|
return NextResponse.json({ error: 'Erro ao traduzir páginas' }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// GET /api/admin/translate-pages - Status das traduções
|
|
export async function GET() {
|
|
try {
|
|
const pages = await prisma.pageContent.findMany({
|
|
select: { slug: true, locale: true, updatedAt: true },
|
|
orderBy: [{ slug: 'asc' }, { locale: 'asc' }]
|
|
});
|
|
|
|
// Agrupar por slug
|
|
const grouped: Record<string, { pt?: Date; en?: Date; es?: Date }> = {};
|
|
|
|
for (const page of pages) {
|
|
if (!grouped[page.slug]) {
|
|
grouped[page.slug] = {};
|
|
}
|
|
grouped[page.slug][page.locale as 'pt' | 'en' | 'es'] = page.updatedAt;
|
|
}
|
|
|
|
return NextResponse.json({ pages: grouped });
|
|
} catch (error) {
|
|
console.error('Erro ao buscar status:', error);
|
|
return NextResponse.json({ error: 'Erro ao buscar status' }, { status: 500 });
|
|
}
|
|
}
|