Implementar multi idioma i18n en Python y Fast-API o Flask

Tiempo de lectura: 2 minutos

En este caso vamos a crear los endpoints de tal forma que el usuario pueda enviar en el request, el idioma seleccionado. También podría ser por una query en la base de datos dónde indique el idioma o por cabecera Accept-Language.

Libro - pexels

La estructura más limpia es con archivos de traducción JSON:

locales/
  es.json
  en.json
  fr.json
  de.json

Cada archivo con las cadenas traducidas:

locales/es.json

json

{
  "email": {
    "welcome": {
      "subject": "Bienvenido a La web",
      "body": "Hola {name}, bienvenido a la comunidad de la web."
    },
    "libro_vendido": {
      "subject": "Tu libro ha sido vendido",
      "body": "Hola {name}, tu libro '{titulo}' ha sido vendido."
    }
  }
}

locales/en.json

json

{
  "email": {
    "welcome": {
      "subject": "Welcome to My Web",
      "body": "Hi {name}, welcome to the book community."
    },
    "libro_vendido": {
      "subject": "Your book has been sold",
      "body": "Hi {name}, your book '{titulo}' has been sold."
    }
  }
}

Luego un helper en config/i18n.py:

python

import json
import os
from typing import Optional

_translations = {}

def load_translations():
    locales_dir = "locales"
    for filename in os.listdir(locales_dir):
        if filename.endswith(".json"):
            lang = filename.replace(".json", "")
            with open(f"{locales_dir}/{filename}", "r", encoding="utf-8") as f:
                _translations[lang] = json.load(f)

def t(key: str, lang: str = "es", **kwargs) -> str:
    keys = key.split(".")
    result = _translations.get(lang, _translations.get("es", {}))
    for k in keys:
        result = result.get(k, key)
        if not isinstance(result, dict):
            break
    if isinstance(result, str):
        return result.format(**kwargs)
    return key

Y lo usas así en cualquier email:

python

from config.i18n import t

subject = t("email.welcome.subject", lang=user.lang)
body = t("email.welcome.body", lang=user.lang, name=user.name)

Y en el startup cargas las traducciones:

python

@asynccontextmanager
async def lifespan(app: FastAPI):
    load_translations()
    await init_redis()
    ...

Y si quieres utilizar cabecera HTTP:

Cambia el parámetro por el header Accept-Language:

python

from fastapi import Header

@articles.post("/add_book")
async def add_book(
    ...,
    accept_language: Optional[str] = Header(default="es")
):
    lang = accept_language.split("-")[0].split(",")[0].lower()  # "es-ES" → "es"
    
    subject = t("email.welcome.subject", lang=lang)
    body = t("email.welcome.body", lang=lang, name=user.name)

El .split("-")[0] convierte es-ES o en-US en es o en para que coincida con tus archivos JSON.

Si quieres reutilizarlo en toda la app sin repetirlo en cada endpoint, ponlo como dependencia:

python

# config/i18n.py
def get_lang(accept_language: Optional[str] = Header(default="es")) -> str:
    return accept_language.split("-")[0].split(",")[0].lower()

# En cualquier endpoint
from config.i18n import get_lang

@articles.post("/add_book")
async def add_book(
    ...,
    lang: str = Depends(get_lang)
):
    subject = t("email.welcome.subject", lang=lang)

Así en todos los endpoints solo añades lang: str = Depends(get_lang) y ya tienes el idioma sin repetir lógica.

Deja un comentario