From 05858e26abf6c0fa4f37286d83d61eefdfb96ab3 Mon Sep 17 00:00:00 2001 From: nickpons666 Date: Fri, 6 Mar 2026 20:53:20 -0600 Subject: [PATCH] =?UTF-8?q?feat(panel):=20panel=20web=20multiling=C3=BCe?= =?UTF-8?q?=20con=20cach=C3=A9=20de=20traducciones=20en=20MySQL=20y=20sopo?= =?UTF-8?q?rte=20nest-asyncio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- botdiscord/translate.py | 43 ++++++------ panel/main.py | 41 +++++++++++ panel/templates/config.html | 58 +++++++++------- panel/templates/dashboard.html | 67 ++++++++++-------- panel/templates/languages.html | 121 ++++++++++++++++++--------------- panel/templates/login.html | 30 ++++++-- requirements.txt | 1 + 7 files changed, 230 insertions(+), 131 deletions(-) diff --git a/botdiscord/translate.py b/botdiscord/translate.py index c2fb748..ba20d45 100644 --- a/botdiscord/translate.py +++ b/botdiscord/translate.py @@ -1,4 +1,6 @@ import aiohttp +import re +import asyncio from botdiscord.config import get_libretranslate_url, get_languages from botdiscord.database import get_available_languages, get_bot_languages @@ -47,14 +49,6 @@ REVERSE_MAPPING = {} FLAG_MAPPING = {} NAME_TO_CODE = {} -import aiohttp -import re -import asyncio -from botdiscord.config import get_libretranslate_url, get_languages -from botdiscord.database import get_available_languages, get_bot_languages - -# ... (mantener funciones load_lang_mappings y getters de mapping iguales) - async def _do_translate_request(session, url, text, target_code): """Función interna para realizar una única petición de traducción.""" # Verificamos si el segmento tiene al menos una letra de cualquier idioma @@ -73,11 +67,8 @@ async def _do_translate_request(session, url, text, target_code): if resp.status == 200: data = await resp.json() translated = data.get("translatedText", text) - # Si el resultado es igual al original, puede ser que el motor fallara, - # devolvemos el texto original tal cual. return translated else: - # En caso de error de API (404, 500, etc) devolvemos el original return text except Exception: return text @@ -85,17 +76,12 @@ async def _do_translate_request(session, url, text, target_code): async def translate_text(text: str, target_lang: str) -> str: url = get_libretranslate_url() if not url: - return "Error: URL de LibreTranslate no configurada" + return text target_code = NAME_TO_CODE.get(target_lang, target_lang) - # Segmentación mejorada: - # Divide por signos de puntuación (. ! ?) o saltos de línea, - # permitiendo que después haya espacios, comas u otros signos. - # Usamos un grupo de captura para no perder los delimitadores. + # Segmentación mejorada segments = re.split(r'([.!?]+\s*|\n+)', text) - - # Filtramos segmentos vacíos que puedan quedar tras el split segments = [s for s in segments if s] async with aiohttp.ClientSession() as session: @@ -107,6 +93,26 @@ async def translate_text(text: str, target_lang: str) -> str: return "".join(translated_segments) +def translate_text_sync(text: str, target_lang: str) -> str: + """Versión síncrona de translate_text para usar en filtros de Jinja2.""" + if not text or not target_lang or target_lang == "es": + return text + + try: + import nest_asyncio + nest_asyncio.apply() + except ImportError: + # Si no está instalado, no podemos usar el bridge en FastAPI + return text + + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + return loop.run_until_complete(translate_text(text, target_lang)) + def get_lang_mapping(bot_type: str = None) -> dict: load_lang_mappings(bot_type) return LANG_MAPPING.copy() @@ -121,7 +127,6 @@ def get_flag_mapping(bot_type: str = None) -> dict: def get_name_to_code(bot_type: str = None) -> dict: load_lang_mappings(bot_type) - print(f"[DEBUG] get_name_to_code returning: {NAME_TO_CODE}") return NAME_TO_CODE.copy() def get_lang_flag(lang_code: str) -> str: diff --git a/panel/main.py b/panel/main.py index 443604b..8bd6b90 100644 --- a/panel/main.py +++ b/panel/main.py @@ -10,6 +10,8 @@ from fastapi.responses import RedirectResponse from pydantic import BaseModel from botdiscord.config import load_config, get_web_config, get_libretranslate_url, get_db_type +from botdiscord.database import get_ui_translation, save_ui_translation +from botdiscord.translate import translate_text app = FastAPI(title="Panel de Configuración - Bots de Traducción") @@ -17,6 +19,45 @@ SCRIPT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) templates = Jinja2Templates(directory=os.path.join(SCRIPT_DIR, "panel", "templates")) +# Filtro de traducción para Jinja2 +def translate_filter(text, lang="es"): + if lang == "es" or not text: + return text + + # Buscamos en caché de la base de datos (acceso rápido) + from botdiscord.database import get_ui_translation, save_ui_translation + cached = get_ui_translation(text, lang) + if cached: + return cached + + # Si no está en caché, traducimos de forma síncrona para la UI + from botdiscord.translate import translate_text_sync + translated = translate_text_sync(text, lang) + + if translated and translated != text: + save_ui_translation(text, lang, translated) + + return translated + +templates.env.filters["translate"] = translate_filter + +@app.get("/set-lang/{lang}") +async def set_lang(lang: str, request: Request): + # Validamos que el idioma sea de 2 letras + if len(lang) != 2: + return RedirectResponse(url="/dashboard") + + response = RedirectResponse(url=request.headers.get("referer", "/dashboard")) + response.set_cookie(key="panel_lang", value=lang, max_age=31536000) # 1 año + return response + +def get_panel_lang(request: Request): + lang = request.cookies.get("panel_lang") + if not lang: + accept_lang = request.headers.get("accept-language", "es") + lang = accept_lang.split(",")[0].split("-")[0] + return lang + class LoginForm(BaseModel): username: str password: str diff --git a/panel/templates/config.html b/panel/templates/config.html index 37eaa60..a978a9f 100644 --- a/panel/templates/config.html +++ b/panel/templates/config.html @@ -1,9 +1,10 @@ +{% set lang = request.cookies.get('panel_lang', 'es') %} - + - Configuración - Bots de Traducción + {{ "Configuración - Bots de Traducción" | translate(lang) }} @@ -11,41 +12,51 @@
-

⚙️ Configuración

+

⚙️ {{ "Configuración" | translate(lang) }}

- La configuración se lee desde las variables de entorno de Docker. + {{ "La configuración se lee desde las variables de entorno de Docker." | translate(lang) }}
-
Tokens de Bots
+
{{ "Tokens de Bots" | translate(lang) }}
- +
- +
- +
@@ -56,21 +67,21 @@
-
Panel Web
+
{{ "Panel Web" | translate(lang) }}
- +
- +
- +
@@ -83,39 +94,39 @@
-
Base de Datos
+
{{ "Base de Datos" | translate(lang) }}
- +
{% if config.database.type == 'mysql' %}
- +
- +
- +
- +
{% else %}
- +
@@ -125,5 +136,6 @@
+ diff --git a/panel/templates/dashboard.html b/panel/templates/dashboard.html index 37df4fd..445e8a9 100644 --- a/panel/templates/dashboard.html +++ b/panel/templates/dashboard.html @@ -1,9 +1,10 @@ +{% set lang = request.cookies.get('panel_lang', 'es') %} - + - Dashboard - Bots de Traducción + {{ "Dashboard - Bots de Traducción" | translate(lang) }} @@ -11,24 +12,35 @@
-

📊 Dashboard

+

📊 {{ "Dashboard" | translate(lang) }}

Discord
-

Bot de traducción para servidores de Discord

- Configurar +

{{ "Bot de traducción para servidores de Discord" | translate(lang) }}

+ {{ "Configurar" | translate(lang) }}
@@ -37,8 +49,8 @@
Telegram
-

Bot de traducción para grupos de Telegram

- Configurar +

{{ "Bot de traducción para grupos de Telegram" | translate(lang) }}

+ {{ "Configurar" | translate(lang) }}
@@ -46,9 +58,9 @@
-
Idiomas
-

Administrar idiomas disponibles

- Administrar +
{{ "Idiomas" | translate(lang) }}
+

{{ "Administrar idiomas disponibles" | translate(lang) }}

+ {{ "Administrar" | translate(lang) }}
@@ -56,46 +68,46 @@
-
📋 Estado de Configuración
+
📋 {{ "Estado de Configuración" | translate(lang) }}
- - + + - - + + - - + + - - + + - + {% if config.database.type == 'mysql' %} - + - + - + {% else %} - + {% endif %} @@ -103,5 +115,6 @@ + diff --git a/panel/templates/languages.html b/panel/templates/languages.html index c37ed6f..a8ddbc9 100644 --- a/panel/templates/languages.html +++ b/panel/templates/languages.html @@ -1,9 +1,10 @@ +{% set lang = request.cookies.get('panel_lang', 'es') %} - + - Idiomas - Bots de Traducción + {{ "Idiomas - Bots de Traducción" | translate(lang) }} @@ -11,11 +12,21 @@ @@ -24,7 +35,7 @@
Token Discord{{ '✅ Configurado' if config.discord.token and config.discord.token != 'TU_DISCORD_BOT_TOKEN' else '❌ No configurado' }}{{ "Token Discord" | translate(lang) }}{{ '✅ Configurado' | translate(lang) if config.discord.token and config.discord.token != 'TU_DISCORD_BOT_TOKEN' else '❌ No configurado' | translate(lang) }}
Token Telegram{{ '✅ Configurado' if config.telegram.token and config.telegram.token != 'TU_TELEGRAM_BOT_TOKEN' else '❌ No configurado' }}{{ "Token Telegram" | translate(lang) }}{{ '✅ Configurado' | translate(lang) if config.telegram.token and config.telegram.token != 'TU_TELEGRAM_BOT_TOKEN' else '❌ No configurado' | translate(lang) }}
LibreTranslate URL{{ config.libretranslate.url if config.libretranslate.url else '❌ No configurado' }}{{ "LibreTranslate URL" | translate(lang) }}{{ config.libretranslate.url if config.libretranslate.url else '❌ No configurado' | translate(lang) }}
Idiomas activos{{ config.languages.enabled|length }} idiomas{{ "Idiomas activos" | translate(lang) }}{{ config.languages.enabled|length }} {{ "idiomas" | translate(lang) }}
Tipo de Base de Datos{{ "Tipo de Base de Datos" | translate(lang) }} {{ config.database.type|upper }}
Host MySQL{{ "Host MySQL" | translate(lang) }} {{ config.database.host }}:{{ config.database.port }}
Base de Datos{{ "Base de Datos" | translate(lang) }} {{ config.database.name }}
Usuario MySQL{{ "Usuario MySQL" | translate(lang) }} {{ config.database.user }}
Ruta SQLite{{ "Ruta SQLite" | translate(lang) }} {{ config.database.path }}
- - - + + + - {% for lang in available_languages %} + {% for lang_item in available_languages %} - - - + + +
BanderaCódigoNombre{{ "Bandera" | translate(lang) }}{{ "Código" | translate(lang) }}{{ "Nombre" | translate(lang) }} Discord Telegram
{{ lang.flag if lang.flag else '🏳️' }}{{ lang.code }}{{ lang.name }}{{ lang_item.flag if lang_item.flag else '🏳️' }}{{ lang_item.code }}{{ lang_item.name }} - {% if lang.code in discord_languages %} + {% if lang_item.code in discord_languages %} {% else %} {% endif %} - {% if lang.code in telegram_languages %} + {% if lang_item.code in telegram_languages %} {% else %} diff --git a/panel/templates/login.html b/panel/templates/login.html index 16d8f24..d70c12b 100644 --- a/panel/templates/login.html +++ b/panel/templates/login.html @@ -1,36 +1,52 @@ +{% set lang = request.cookies.get('panel_lang', 'es') %} - + - Bots de Traducción - Login + {{ "Bots de Traducción - Login" | translate(lang) }} + +
+ +
+ diff --git a/requirements.txt b/requirements.txt index ed9dbf2..fe1beec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ pydantic>=2.0.0 python-dotenv>=1.0.0 python-multipart>=0.0.9 mysql-connector-python>=8.0.0 +nest-asyncio