Saltar a contenido

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_preference o 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:

i18n.changeLanguage('en');
localStorage.setItem('blimx_lang', 'en');

Widget embebible

Tres mecanismos en orden de prioridad:

  1. Override explícito en el host page:

    <script>window.blimxLang = 'fr';</script>
    <script src=".../blimx-v3-chatbot-loader.js" data-tenant="acme"></script>
    

  2. Detección por URL pathhttps://example.com/es/contactoes. Útil para sitios multi-idioma con rutas como /es/, /en/.

  3. Atributo <html lang="..."> del documento.

El loader detecta cambios dinámicos de idioma (SPAs):

  • MutationObserver sobre <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:

{
  "es": "Precios",
  "en": "Pricing",
  "fr": "Tarifs",
  "de": "Preise",
  "pt": "Preços"
}

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

  1. Backend:

    cd backend_django
    django-admin makemessages -l ja      # crea backend_django/locale/ja/...
    # traduces los .po
    django-admin compilemessages
    

  2. Frontend dashboard:

    cp apps/dashboard/src/i18n/locales/en.json apps/dashboard/src/i18n/locales/ja.json
    # traduces el JSON
    # añades 'ja' al array de supportedLngs en i18n/setup.ts
    

  3. Widget:

    cp static/v3/i18n/en.json static/v3/i18n/ja.json
    # traduces
    # node build.js  → regenera bundle
    

  4. Catálogo del policy engine: ningún cambio (los limits son numéricos por idioma, no per-language código).

  5. Tests E2E: añade ja a 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)