La Raya · backend (4): multilingüe en la frontera — i18n y traducción automática

Publicado el 14/06/2026

La Raya tiene una peculiaridad que no es un detalle: está en una frontera. El contenido llega en español (Extremadura) y en portugués (Alentejo, Beira), y un usuario de un lado puede querer leer lo del otro lado en su idioma —o un visitante, en inglés—. Así que el multilingüismo no era un "nice to have", era parte del producto.

Por qué traducción automática (y autoalojada)

Traducir a mano miles de eventos y noticias scrapeadas es inviable: volvemos al problema de siempre, nadie lo va a mantener. Tenía que ser automático.

Elegí LibreTranslate autoalojado en vez de una API de pago. Motivos: el volumen es alto (todo lo scrapeado), no quiero una factura que crece con el contenido, y los datos son públicos pero prefiero no mandarlos a terceros si puedo evitarlo. A cambio asumes operarlo tú (un servicio más en el Docker) y una calidad algo inferior a las APIs comerciales —aceptable para fichas de eventos y titulares—.

El flujo: al guardar un ítem, se detecta el idioma de origen, y se traduce a los idiomas objetivo que falten (español, portugués, inglés). Las traducciones se guardan en un modelo Traduccion aparte, y la API sirve el idioma que pida la app mediante una cabecera (X-Accept-Language). La traducción se lanza en segundo plano para no bloquear el guardado.

La regla que más me costó ver: qué NO traducir

Aquí está la lección de verdad de este capítulo. Mi primera versión traducía todo, títulos incluidos. Y estaba mal.

Resulta que muchos títulos son nombres propios que no deben traducirse:

  • El título de un evento suele ser el nombre de una obra, un grupo, una feria: "Concierto de Vetusta Morla", "Festival Boom". Traducir eso es un destrozo ("Concert of Old Morla"…).
  • En cambio, el título de una noticia es texto informativo: ahí sí tiene sentido traducirlo ("El Ayuntamiento aprueba…" → "A Câmara aprova…").

Así que la regla quedó: en eventos se conserva el título original y se traduce solo el cuerpo; en noticias se traduce también el titular. Parece obvio escrito así, pero es de esas cosas que solo ves cuando miras las traducciones reales y te chirría un nombre de grupo "traducido". Hubo también un fix para no romper el cuerpo cuando la traducción fallaba (fallback al original) y para no pedir traducción a idiomas de origen no soportados (evitar errores repetidos y ruido en los logs).

Operar un traductor propio no es gratis

Tener LibreTranslate dentro de la infraestructura trajo su propia ración de fixes, casi todos de despliegue (los cuento en el capítulo de producción): hacer la URL inyectable por entorno, ampliar el timeout del healthcheck, no bloquear el arranque del backend si el traductor aún no responde, y silenciar errores repetidos cuando una petición no era traducible. Nada heroico, pero recordatorio de que cada servicio que autoalojas es una pieza más que mantener.

Lo que me llevo

  • El idioma es producto, no decoración, sobre todo en un proyecto de frontera. Diseñarlo desde el dato (modelo de traducciones + cabecera de idioma) es mucho mejor que parchearlo en la UI.
  • No todo texto es traducible. Distinguir nombre propio de texto informativo (título de evento vs titular de noticia) marca la diferencia entre algo cuidado y algo que da grima.
  • Autoalojar ahorra dinero y cuesta operación. Para volumen alto de datos públicos compensa, pero presupuesta el mantenimiento, no solo el ahorro.

Con el backend lleno de contenido y multilingüe, llegó el momento menos glamuroso y más doloroso de todo proyecto: ponerlo en producción de verdad. La próxima entrada es la crónica honesta de Docker, Dokploy, Celery y el día que, sencillamente, nada arrancaba.