Back to 0fee
0fee

Internacionalización: 15 idiomas en el backend, 5 en el frontend

Cómo 0fee.dev implementa 15 idiomas en el backend y 5 en el frontend con SolidJS Context API, RTL en árabe y más de 1.350 claves.

Juste A. Gnimavo (Thales) & Claude | March 27, 2026 8 min 0fee
EN/ FR/ ES
i18ninternationalizationsolidjs-contextrtltranslations

Una plataforma de pagos que sirve a desarrolladores en más de 200 países no puede ser solo en inglés. Las páginas de checkout del backend soportan 15 idiomas incluyendo árabe con diseño RTL. El panel de control del frontend soporta 5 idiomas con más de 1.350 claves de traducción cada uno. Todo el sistema fue construido sin ninguna biblioteca i18n de terceros -- solo SolidJS Context API, interfaces TypeScript, detección de idioma del navegador y persistencia en localStorage.

Este artículo cubre la arquitectura de traducción de doble capa, la implementación con SolidJS Context, el desafío RTL, la detección de idioma del navegador y la sesión de corrección de acentos que arregló docenas de diacríticos faltantes.

Dos capas de traducción

El sistema usa diferentes cantidades de traducciones para diferentes superficies:

CapaIdiomasClaves por idiomaRazón
Backend (checkout)15~40Cadenas cortas específicas del checkout
Frontend (panel)5~1.350Interfaz completa de la aplicación

El backend necesita más idiomas porque las páginas de checkout están orientadas al cliente -- un cliente en Japón pagando a través del checkout alojado debería ver texto en japonés. El frontend necesita menos idiomas pero más profundidad porque es el panel del desarrollador -- y la mayoría de desarrolladores que construyen integraciones de pago leen inglés, francés, español, portugués o chino.

Backend: 15 idiomas

El sistema de traducción del backend es un único módulo Python con diccionarios anidados:

python# backend/locales/translations.py

SUPPORTED_LANGUAGES = [
    ("en", "English"),
    ("fr", "Francais"),
    ("es", "Espanol"),
    ("pt", "Portugues"),
    ("de", "Deutsch"),
    ("it", "Italiano"),
    ("nl", "Nederlands"),
    ("ar", "العربية"),
    ("zh", "中文"),
    ("ja", "日本語"),
    ("ko", "한국어"),
    ("tr", "Turkce"),
    ("ru", "Русский"),
    ("sw", "Kiswahili"),
    ("ha", "Hausa"),
]

TRANSLATIONS = {
    "en": {
        "checkout_title": "Checkout",
        "select_country": "Select your country",
        "select_method": "Choose payment method",
        "enter_phone": "Enter your phone number",
        "phone_placeholder": "Phone number",
        "enter_otp": "Enter the verification code sent to your phone",
        "otp_placeholder": "6-digit code",
        "pay_button": "Pay {amount} {currency}",
        "processing": "Processing your payment...",
        "check_phone": "Check your phone for the payment prompt",
        "success_title": "Payment Successful",
        "success_message": "Your payment of {amount} {currency} has been processed",
        "failed_title": "Payment Failed",
        "failed_message": "We could not process your payment",
        "retry": "Try Again",
        "cancel": "Cancel",
        "back": "Back",
        "powered_by": "Powered by 0fee.dev",
        "sandbox_notice": "Test mode - no real charges",
    },
    "fr": {
        "checkout_title": "Paiement",
        # ... traducciones en francés
    },
    "ar": {
        "checkout_title": "الدفع",
        # ... traducciones en árabe
    },
    "sw": {
        "checkout_title": "Malipo",
        # ... traducciones en suajili
    },
    "ha": {
        "checkout_title": "Biya",
        # ... traducciones en hausa
    },
    # ... 10 idiomas más
}

El suajili y el hausa están incluidos porque son ampliamente hablados en África Oriental y Occidental respectivamente -- mercados clave para 0fee.

Soporte RTL para árabe

El árabe requiere diseño de derecha a izquierda. La plantilla de checkout detecta el árabe y aplica RTL:

html<html lang="{{ lang }}" {% if lang == 'ar' %}dir="rtl"{% endif %}>
css/* Sobreescrituras específicas para RTL */
[dir="rtl"] .checkout-form {
    direction: rtl;
    text-align: right;
}

[dir="rtl"] .phone-input {
    flex-direction: row-reverse;
}

[dir="rtl"] .back-button {
    flex-direction: row-reverse;
}

[dir="rtl"] .back-button svg {
    transform: rotate(180deg);
}

La flecha de retroceso, el diseño de entrada de teléfono y la alineación del texto se invierten. Cada icono que implica dirección (flechas, chevrones) debe ser reflejado.

Frontend: 5 idiomas con más de 1.350 claves

El frontend usa un enfoque más estructurado con interfaces TypeScript para seguridad de tipos:

Definiciones de tipos

typescript// frontend/src/locales/types.ts
export interface Translations {
  common: {
    loading: string;
    error: string;
    success: string;
    cancel: string;
    save: string;
    // ... más de 50 cadenas comunes
  };
  nav: {
    dashboard: string;
    apps: string;
    transactions: string;
    // ... etiquetas de navegación
  };
  dashboard: {
    title: string;
    totalTransactions: string;
    // ... cadenas específicas del panel
  };
  // ... más de 15 secciones más
}

SolidJS Context API

Las traducciones se proporcionan a través de un Context de SolidJS:

typescript// frontend/src/contexts/LanguageContext.tsx
import { createContext, useContext, createSignal, ParentComponent } from "solid-js";
import { en } from "../locales/en";
import { fr } from "../locales/fr";
import { es } from "../locales/es";
import { pt } from "../locales/pt";
import { zh } from "../locales/zh";
import type { Translations } from "../locales/types";

const LANGUAGES: Record<string, Translations> = { en, fr, es, pt, zh };

export const LanguageProvider: ParentComponent = (props) => {
  const detectLanguage = (): string => {
    const saved = localStorage.getItem("lang");
    if (saved && LANGUAGES[saved]) return saved;

    const browserLang = navigator.language.split("-")[0];
    if (LANGUAGES[browserLang]) return browserLang;

    return "en";
  };

  const [lang, setLang] = createSignal(detectLanguage());
  const t = () => LANGUAGES[lang()] || en;

  const updateLang = (newLang: string) => {
    setLang(newLang);
    localStorage.setItem("lang", newLang);
  };

  return (
    <LanguageContext.Provider
      value={{ t, lang, setLang: updateLang, availableLanguages: [
        { code: "en", name: "English" },
        { code: "fr", name: "Francais" },
        { code: "es", name: "Espanol" },
        { code: "pt", name: "Portugues" },
        { code: "zh", name: "中文" },
      ] }}
    >
      {props.children}
    </LanguageContext.Provider>
  );
};

Uso en componentes

tsximport { useLanguage } from "../contexts/LanguageContext";

function TransactionsPage() {
  const { t } = useLanguage();

  return (
    <div>
      <h1>{t().transactions.title}</h1>
      <table>
        <thead>
          <tr>
            <th>{t().transactions.tableHeaders.id}</th>
            <th>{t().transactions.tableHeaders.amount}</th>
            <th>{t().transactions.tableHeaders.status}</th>
          </tr>
        </thead>
      </table>
    </div>
  );
}

La sesión de corrección de acentos (083)

La sesión 082 creó los archivos de traducción en español, portugués y chino y completó las 1.350 claves por idioma. Sin embargo, las traducciones al francés carecían de acentos -- un problema crítico para la base de usuarios francófonos africanos de 0fee.

La sesión 083 fue dedicada a arreglar esto. Más de 50 palabras recibieron diacríticos franceses correctos. El compilador TypeScript no detectó ninguno de estos errores porque son valores de cadena, no problemas de sintaxis. Por eso es importante el testing automatizado para traducciones -- y por qué tenemos una regla a nivel de proyecto contra reproducir defectos de entrada en el contenido generado.

Detección de idioma del navegador

La detección de idioma sigue una prioridad de tres pasos:

1. localStorage (elección explícita del usuario)
2. navigator.language (configuración de idioma del navegador)
3. Predeterminado: Inglés

Esto significa que un desarrollador francófono que visite 0fee.dev por primera vez verá el panel en francés sin ninguna configuración. Puede cambiar a otro idioma en cualquier momento, y su elección persiste entre sesiones.

Decisiones de arquitectura

Tres elecciones deliberadas dieron forma al sistema i18n:

  1. Sin biblioteca externa. Consideramos i18next y typesafe-i18n pero decidimos que SolidJS Context con interfaces TypeScript proporciona todo lo que necesitamos: claves con seguridad de tipos, cambio reactivo de idioma y cero sobrecarga de dependencias.
  1. Diferentes cantidades de idiomas por capa. El backend necesita amplia cobertura (15 idiomas) para el checkout orientado al cliente. El frontend necesita cobertura profunda (más de 1.350 claves) para el panel del desarrollador. Intentar mantener 1.350 claves en 15 idiomas sería impracticable.
  1. Importaciones estáticas, no carga dinámica. Los cinco archivos de idioma se importan en tiempo de build e incluyen en el bundle. Esto añade ~30KB por idioma al bundle pero elimina los retrasos de carga al cambiar de idioma. Para cinco idiomas, la compensación es aceptable.

Lo que aprendimos

Construir el sistema i18n nos enseñó tres cosas:

  1. Las traducciones con seguridad de tipos detectan errores estructurales pero no errores de contenido. TypeScript asegura que cada archivo de idioma tenga cada clave, pero no puede detectar acentos faltantes, traducciones incorrectas o errores gramaticales. La revisión humana (o en nuestro caso, una sesión de corrección dedicada) sigue siendo necesaria.
  1. El soporte RTL no es solo dirección del texto. El árabe requirió invertir diseños de formularios, reflejar iconos direccionales, ajustar componentes de entrada de teléfono y probar cada elemento visual. Es un cambio de sistema de diseño, no solo una propiedad CSS.
  1. La detección del navegador proporciona un buen valor predeterminado. Los navegadores de la mayoría de los desarrolladores están configurados para su idioma preferido. Usar navigator.language como la suposición inicial significa que el panel "simplemente funciona" en el idioma correcto para la mayoría de usuarios en la primera visita.

El sistema i18n fue construido en la sesión 064, expandido en la sesión 082 y corregido en la sesión 083. Sirve a desarrolladores en 5 idiomas y a clientes en 15, cubriendo los principales mercados donde opera 0fee.


Este artículo es parte de la serie "Cómo construimos 0fee.dev". 0fee.dev es un orquestador de pagos que cubre más de 53 proveedores en más de 200 países, construido por Juste A. GNIMAVO y Claude desde Abiyán sin ingenieros humanos. Sigue la serie para conocer la historia completa de la construcción.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

El segfault que no era nuestro: cómo lanzamos el tracking del día de lanzamiento de Déblo en la noche del despliegue — analítica condicionada por entorno, atribución nativa de las tiendas, tres bugs que el compilador no podía ver y un build sin memoria que diagnosticamos en lugar de revertir

El 1 de julio de 2026 — el día del lanzamiento — el riesgo nunca fue el texto. Era que las campañas de pago salieran a ciegas. Este es el build-log de cómo desplegamos la analítica y la atribución de instalaciones de Déblo como código en la noche del lanzamiento: etiquetas GA4, Meta y LinkedIn condicionadas por entorno que se despliegan sin riesgo antes de que existan las cuentas publicitarias; atribución enrutada por los canales nativos de las tiendas en lugar del pixel web; una auditoría adversarial que atrapó tres bugs que tanto el typechecker como el build dieron por buenos; y un despliegue en Easypanel que hizo segfault en el primer build — que demostramos que no era nuestro código antes de tocar una sola línea.

18 min Jul 1, 2026
deblolaunch-dayclaude-opus-4.8claude-code +26
Thales & Claude thales

Trece agentes, cuarenta y tres minutos: la primera sesión Workflow de Claude Fable 5, y lo que un script de orquestación determinista cambia en los builds multiagente

Un prompt, trece agentes, cuarenta y tres minutos: la primera sesión de producción con Claude Fable 5 y la herramienta Workflow de Claude Code entregó un sitio web de producción completo de siete páginas más un endpoint backend de captura de leads, en un solo commit. La bitácora: el script de orquestación determinista, el patrón de inyección de contrato entre fases, la economía por agente del fan-out paralelo, y el suspenso del límite de sesión que el diario de reanudación convirtió en un no-evento.

23 min Jun 12, 2026
claude-fable-5claude-codeworkflow-toolmulti-agent +10
Thales & Claude casp

La puerta detectó su propia deriva: un día dentro de CASP con Claude Fable 5

Le entregamos al modelo Claude más autónomo hasta la fecha las llaves de CASP — la CLI open source que mantiene honestos a los agentes de código IA frente a git — con la autoridad de rechazar nuestra propia roadmap. Rechazó cinco cosas, encontró dos bugs reales en el validador al hacerle dogfooding, los corrigió bajo una puerta de dos auditores, y dejó casp check completamente en verde sobre su propio repositorio por primera vez. CASP 0.3.0 es el resultado.

15 min Jun 10, 2026
caspzerosuiteworkflowai-cto +9