From c50deadee892aad9f84fa7fb2e8e3645fd2239c9 Mon Sep 17 00:00:00 2001 From: Renato Date: Wed, 5 Aug 2026 07:13:07 +0800 Subject: [PATCH] feat: traduccion con contexto (sinopsis+tags) para titulos naturales de fansub --- scripts/translate_existing.js | 47 ++++++++++++++++++++++------------- src/lib/translate.ts | 20 +++++++-------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/scripts/translate_existing.js b/scripts/translate_existing.js index 4a32716..7b7a76b 100644 --- a/scripts/translate_existing.js +++ b/scripts/translate_existing.js @@ -11,17 +11,27 @@ try { db.exec("ALTER TABLE posts ADD COLUMN title_es TEXT"); } catch {} const PROXY_URL = "http://127.0.0.1:6446/v1"; const PROXY_KEY = "tw0rxo314kvga9lfbp2nj5sde7hiq6uc"; -const SYSTEM_PROMPT = `Sos un traductor de títulos de manga al español latinoamericano. -Recibís UN título de manga (en inglés, japonés romanizado, katakana, kanji o chino). -Devolvés SOLO la traducción al español, sin comillas, sin corchetes de autor, -sin explicaciones, sin "Traducción:" ni puntos finales. -Mantené el tono del título original. -Normas: -- No traduzcas nombres propios (personajes, artistas, marcas). -- Si el título ya está en español o es un nombre propio, devolvelo tal cual. -- Traducí términos como "Haha no Hi" -> "Día de la Madre". -- Si hay números de capítulo/volumen/año, mantenelos. -- Respondé en UNA línea.`; +const SYSTEM_PROMPT = `Sos el editor de títulos de un fansub de manga que traduce del japonés/inglés al español latinoamericano. +Recibís el título de un manga/doujinshi JUNTO CON su sinopsis y tags como contexto. +Tu trabajo es formular un título NATURAL en español para ese manga, como lo titularía una editorial real. +REGLAS: +- No traduzcas el título literalmente. Usá el contexto (tags + sinopsis) para entender DE QUÉ TRATA el manga y titulalo en español de forma natural y atractiva. +- Si el título original ya es claro en español, adaptalo levemente. Si es confuso, reformulalo basándote en la sinopsis. +- Mantené nombres propios (personajes, artistas, marcas, franquicias) sin traducir. +- Mantené números de capítulo/volumen/año. +- Si hay un término japonés de uso común en el fandom (ecchi, harem, netorare, yuri, etc.) podés dejarlo o traducirlo según suene mejor. +- El resultado debe leerse como un título de manga en español, no como una traducción de Google. +- Respondé SOLO con el título final, UNA línea, sin comillas, sin explicaciones, sin corchetes de autor.`; + +// Arma el mensaje de usuario con título + contexto (tags + sinopsis). +function buildUserPrompt(post) { + let tags = post.tags || []; + if (typeof tags === "string") { try { tags = JSON.parse(tags); } catch { tags = []; } } + const lines = [`Título original: ${post.title}`]; + if (Array.isArray(tags) && tags.length) lines.push(`Tags: ${tags.slice(0, 25).join(", ")}`); + if (post.summary && post.summary.length > 20) lines.push(`Sinopsis: ${post.summary.slice(0, 600)}`); + return lines.join("\n"); +} // Modelos en orden de preferencia (benchmark + tests reales 2026-08): // nemotron-3-ultra-550b y nemotron-3-nano-omni traducen BIEN con max_tokens>=600. @@ -98,10 +108,11 @@ function isValidTranslation(text) { return true; } -async function translate(title) { - const clean = (title || "").trim(); +async function translate(post) { + const clean = (post.title || "").trim(); if (!clean) return null; if (isProbablySpanish(clean)) return clean; + const userMsg = buildUserPrompt(post); // Bucle sobre modelos con backtracking: si todos fallan, reintentar tras pausa for (let attempt = 0; attempt < 3; attempt++) { @@ -109,7 +120,7 @@ async function translate(title) { const retryAt = deadModels.get(model) || 0; if (Date.now() < retryAt) continue; try { - const r = await tryModel(model, clean); + const r = await tryModel(model, userMsg); if (r.ok) return r.text; if (r.rateLimited) { // Marcar como muerto por 5 min @@ -130,15 +141,17 @@ async function translate(title) { async function main() { const limit = parseInt(process.argv[2] || "0", 10); - const posts = db.prepare("SELECT id, gid, title, title_es FROM posts").all(); + const retake = process.env.RETAKE === "1"; + const posts = db.prepare("SELECT id, gid, title, title_es, tags, summary FROM posts").all(); let toTranslate = posts.filter(p => !p.title_es); + if (retake) toTranslate = posts.filter(p => p.title_es); // re-traducir los existentes if (limit > 0) toTranslate = toTranslate.slice(0, limit); - console.log(`Total: ${posts.length} | sin traducción: ${toTranslate.length}${limit ? ` (lote ${limit})` : ""}`); + console.log(`Total: ${posts.length} | a procesar: ${toTranslate.length}${retake ? " (RETAKE: re-traduciendo existentes)" : ""}${limit ? ` (lote ${limit})` : ""}`); let ok = 0, fail = 0; for (let i = 0; i < toTranslate.length; i++) { const p = toTranslate[i]; - const t = await translate(p.title); + const t = await translate(p); if (t) { db.prepare("UPDATE posts SET title_es = ?, updated_at = datetime('now') WHERE id = ?").run(t, p.id); ok++; diff --git a/src/lib/translate.ts b/src/lib/translate.ts index ba9a4fa..ff28eac 100644 --- a/src/lib/translate.ts +++ b/src/lib/translate.ts @@ -7,17 +7,15 @@ import "server-only" const PROXY_URL = process.env.FREE_IDE_PROXY_URL || "http://127.0.0.1:6446/v1" const PROXY_KEY = process.env.FREE_IDE_PROXY_KEY || "tw0rxo314kvga9lfbp2nj5sde7hiq6uc" -const SYSTEM_PROMPT = `Sos un traductor de títulos de manga al español latinoamericano. -Recibís UN título de manga (en inglés, japonés romanizado, katakana, kanji o chino). -Devolvés SOLO la traducción al español, sin comillas, sin corchetes de autor, -sin explicaciones, sin "Traducción:" ni puntos finales. -Mantené el tono del título original. -Normas: -- No traduzcas nombres propios (personajes, artistas, marcas). -- Si el título ya está en español o es un nombre propio, devolvelo tal cual. -- Traducí términos como "Haha no Hi" -> "Día de la Madre". -- Si hay números de capítulo/volumen/año, mantenelos. -- Respondé en UNA línea.` +const SYSTEM_PROMPT = `Sos el editor de títulos de un fansub de manga que traduce del japonés/inglés al español latinoamericano. +Recibís el título de un manga/doujinshi. Tu trabajo es ADAPTARLO a un título natural en español, como lo titularía una editorial de manga real. +REGLAS: +- NO traduzcas palabra por palabra. Reescribí el título para que suene natural y atractivo en español, manteniendo el significado. +- Mantené nombres propios (personajes, artistas, marcas, franquicias) sin traducir. +- Mantené números de capítulo/volumen/año. +- Si hay un término japonés de uso común en el fandom (ecchi, harem, netorare, yuri, etc.) podés dejarlo o traducirlo según suene mejor. +- El resultado debe leerse como un título de manga en español, no como una traducción de Google. +- Respondé SOLO con el título final, UNA línea, sin comillas, sin explicaciones, sin corchetes de autor.` // Llama al proxy y devuelve el texto traducido (o null si falla). export async function translateTitle(title: string): Promise {