Internacionalización (i18n)
Mentat es multilenguaje: inglés primario, español secundario. Esta página documenta la arquitectura, cómo traducir un componente y el estado de la migración.
Decisiones de arquitectura
La web (apps/web) es output: 'export' (estático puro, sin servidor ni
middleware). Por eso el i18n es 100% del lado cliente y el idioma es una
preferencia (no un prefijo de URL como /en /es).
- Librería:
react-i18next(+i18next+i18next-browser-languagedetector). - Sin prefijo de URL: las rutas no cambian (
/actionssigue siendo/actions). - Resolución del idioma:
localStorage['mentat.locale']→navigator.language→ fallback. Persiste solo en el cliente (cache del language detector). DEFAULT_LOCALEhoy es'es'para no cambiar la experiencia actual durante la migración; se flipea a'en'cuando los catálogos estén completos (Fase A4).<html lang>se sincroniza con el idioma activo desde elI18nProvider.
Archivos clave
| Archivo | Rol |
|---|---|
lib/i18n/config.ts | init de i18next (initI18n, idempotente), detección, SUPPORTED_LOCALES, DEFAULT_LOCALE |
lib/i18n/locales/{en,es}/*.json | catálogos, un archivo por namespace |
lib/i18n/locales/{en,es}/index.ts | arma el objeto { namespace: json } |
lib/i18n/format.ts | formatDateTime(ts, locale) — fechas locale-aware (en→en-US, es→es-AR) |
components/providers/i18n-provider.tsx | monta I18nextProvider + sincroniza <html lang> |
components/app-shell/LanguageSwitcher.tsx | selector en/es (persiste + emite languageChanged) |
Cómo traducir un componente
1. Elegí/creá el namespace
Un namespace por feature (nav, dashboard, actions, …). Agregá el .json en
locales/en/ y locales/es/ y registralo en los dos index.ts. Las keys son
semánticas en inglés (stats.objects), no el texto español.
// locales/en/dashboard.json // locales/es/dashboard.json
{ "title": "Dashboard", { "title": "Dashboard",
"subtitle": "Your organization…" } "subtitle": "Estado de tu organización…" }2. Usá t() en el componente
'use client';
import { useTranslation } from 'react-i18next';
export function Foo() {
const { t } = useTranslation('dashboard');
return <h1>{t('title')}</h1>;
}Para keys compartidas (loading, save, cancel…) reusá el namespace common:
useTranslation(['dashboard', 'common']) y t('common:states.loading').
3. Fechas y números
Nunca hardcodees 'es-AR'. Usá el helper con el idioma activo:
const { i18n } = useTranslation();
const locale = i18n.resolvedLanguage ?? i18n.language;
formatDateTime(ts, locale); // en→en-US, es→es-ARGotchas:
- Páginas prerenderizadas (ej.
/login): traducirlas introduce hydration mismatch (la HTML estática queda en el idioma del build). Las páginas bajo(app)/*son client-only (el shell renderiza tras el auth) → seguras. El login se migra con un patrón SSG-safe (render conDEFAULT_LOCALEen SSR + detección diferida post-mount). noUncheckedIndexedAccessestá activo:arr[0]esT | undefined→ defaulteá.- Los enums de estado (
success/failed/partial) se muestran crudos por ahora.
Estado de la migración
| Fase | Qué | Estado |
|---|---|---|
| A0 | Andamiaje (deps, config, provider inerte) | ✅ |
| A1 | Selector de idioma + persistencia + <html lang> + nav | ✅ |
| A2 | Extracción incremental de strings | 🔄 dashboard ✅ · pendientes: actions, datasources, ontology, objects, inbox, login |
| A3 | Fechas/números/plurales | 🔄 helper formatDateTime listo |
| A4 | Flip de DEFAULT_LOCALE a 'en' | ⏳ |
| B | Backend (errores por código) + emails | ⏳ |
| C | Sitio de docs Nextra multilingüe | ⏳ |
Alcance total acordado: frontend + backend/emails + este sitio de docs. La web queda operativa bilingüe al terminar A; B y C completan el “inglés primario” de punta a punta.