12 — Internacionalización (i18n)¶
Idiomas soportados¶
El SaaS soporta 5 idiomas out-of-the-box:
| Código | Idioma | Estado |
|---|---|---|
es |
Español | Completo |
en |
English | Completo |
fr |
Français | Completo |
de |
Deutsch | Completo |
pt |
Português | Completo |
Tres capas de i18n independientes¶
Cada capa traduce un subset distinto, separadas a propósito:
| Capa | Qué traduce | Dónde viven los strings |
|---|---|---|
| Backend Django | Emails (verificación, reset, factura), errores de la API legibles | backend_django/locale/<lang>/LC_MESSAGES/ (gettext) |
| Frontend dashboard | Sidebar, formularios, modales, tablas | apps/dashboard/src/i18n/locales/<lang>.json (i18next) |
| Frontend superadmin | Panel de operación | apps/superadmin/src/modules/superadmin/i18n/locales/<lang>.json |
| Widget embebible | Strings del modal del Help Center (categorías default, "no results", etc.) | static/v3/i18n/<lang>.json (vanilla) |
| Contenido del cliente final | FAQs, categorías, brand name | BD (knowledge_base.lang, knowledge_categories.translations, tenant_frontend_content.content) |
Flujo: ¿de dónde sale el idioma?¶
Backend (emails y errores)¶
1. Frontend manda header `Accept-Language: es-ES,es;q=0.9` o `?lang=es`
2. Django LocaleMiddleware (si está activo) o lectura manual del header
3. activate('es')
4. {% trans "..." %} y _("...") devuelven la versión española
En este SaaS el LocaleMiddleware está deshabilitado (no hay templates user-facing salvo emails). Los emails leen el lang directamente del
SaasUser.language_preferenceo del Account.
Frontend dashboard¶
1. i18next-browser-languagedetector lee localStorage / navigator.language
2. import './i18n/setup' al arrancar la SPA
3. Componentes usan: t('sidebar.myBots') → "Mis bots de soporte"
Cambiar idioma desde el header del dashboard:
Widget embebible¶
Tres mecanismos en orden de prioridad:
-
Override explícito en el host page:
-
Detección por URL path —
https://example.com/es/contacto→es. Útil para sitios multi-idioma con rutas como/es/,/en/. -
Atributo
<html lang="...">del documento.
El loader detecta cambios dinámicos de idioma (SPAs):
MutationObserversobre<html lang>(React Helmet, Vue i18n, Angular).- Intercepta
history.pushState/replaceState/popstate(React Router, Next.js, etc.). - Polling cada 2s como fallback (Wix, algunos temas Shopify).
Cuando detecta cambio, el iframe se recarga con el nuevo ?lang=XX.
Contenido del cliente final (FAQs)¶
Cada FAQ se almacena en un idioma específico (knowledge_base.lang). Si el visitante pide /v3/faq?language=fr, solo verá FAQs con lang='fr'.
Comportamiento si no hay FAQs en ese idioma:
- El widget muestra el listado vacío con el string traducido "No hay preguntas frecuentes en este idioma."
- No hay fallback automático a otro idioma. Si el cliente quiere coverage, debe traducir manualmente.
Categorías de FAQs¶
Las categorías se traducen con la columna translations (jsonb) de knowledge_categories:
El endpoint /v3/faq devuelve los display_name ya resueltos al idioma solicitado.
Si no hay traducción en el idioma solicitado, se usa el display_name raw como fallback.
Branding multi-idioma del bot¶
tenant_frontend_content.content.brand y texts también soportan estructura multi-idioma:
{
"supported_languages": ["es", "en", "fr"],
"default_language": "es",
"brand": {
"name": "ACME Help",
"tagline": {
"es": "¿Necesitas ayuda?",
"en": "Need help?",
"fr": "Besoin d'aide ?"
}
}
}
El widget escoge tagline[currentLang] con fallback a default_language.
Selector de idioma del widget¶
El widget muestra un selector si supported_languages tiene más de un idioma. El selector cambia el lang runtime, no recarga el iframe.
El plan Free limita supported_languages a 1 (solo default_language), independientemente de lo que tenga el bot guardado. Lo enforce el endpoint /v3/frontend/config filtrando antes de devolver.
Cómo añadir un sexto idioma¶
-
Backend:
-
Frontend dashboard:
-
Widget:
-
Catálogo del policy engine: ningún cambio (los limits son numéricos por idioma, no per-language código).
-
Tests E2E: añade
jaa la lista de idiomas en el smoke test del widget.
Tiempo estimado: ~2 horas (sin contar la traducción real).
Archivos clave¶
backend_django/locale/{es,en,fr,de,pt}/LC_MESSAGES/django.po
apps/dashboard/src/i18n/
├── setup.ts ← Init de i18next
└── locales/
├── es.json, en.json, fr.json, de.json, pt.json
apps/superadmin/src/modules/superadmin/i18n/
└── locales/{es,en,fr,de,pt}.json
static/v3/i18n/
├── es.json, en.json, fr.json, de.json, pt.json
└── (los carga LanguageManager.js del bundle)