# Simple i18n Translation

Fuente conceptual: MiroFish `backend/app/utils/locale.py` (AGPL-3.0)
Reimplementar desde cero. No copiar codigo.

## Funciones a implementar

### 1. t (translate)

- **Dominio:** core
- **Lang:** Python
- **Purity:** impure (lee estado thread-local)
- **Signature:** `t(key: str, locale: str | None = None, **kwargs) -> str`
- **Descripcion:** Traduce una clave con dot-path notation al idioma actual. Soporta interpolacion de variables con `{nombre}`.
- **Algoritmo:**
  1. Determinar locale (parametro > thread-local > default)
  2. Obtener diccionario de traducciones para ese locale
  3. Navegar dot-path: `"report.taskStarted"` → `translations["report"]["taskStarted"]`
  4. Si no existe: fallback al locale default
  5. Si tampoco existe: retornar la key tal cual
  6. Si hay kwargs: reemplazar `{key}` → `value` para cada kwarg
- **Ejemplo:**
  ```python
  # translations = {"en": {"report": {"sectionStart": "Generating section: {title}"}}}
  t("report.sectionStart", locale="en", title="Introduction")
  # → "Generating section: Introduction"
  ```
- **Deps:** ninguna (stdlib)
- **Tests:** key existente, key inexistente (retorna key), interpolacion, dot-path profundo, fallback a locale default

### 2. load_translations

- **Dominio:** core
- **Lang:** Python
- **Purity:** impure (lee archivos)
- **Signature:** `load_translations(locales_dir: str) -> dict[str, dict]`
- **Descripcion:** Carga todos los archivos JSON de un directorio de locales. Cada archivo `{locale}.json` se carga como diccionario.
- **Algoritmo:**
  1. Listar archivos `.json` en locales_dir
  2. Para cada archivo: cargar JSON, key = nombre sin extension
  3. Retornar dict de dicts
- **Deps:** `json`, `os`
