Haz tu pregunta y obtén un resumen del documento referenciando esta página y el proveedor AI de tu elección
Historial de versiones
- "Añadir comparativa de estrellas de GitHub"v8.9.818/5/2026
- "Inicio del benchmark"v8.7.56/1/2026
El contenido de esta página ha sido traducido con una IA.
Ver la última versión del contenido original en inglésSi tienes una idea para mejorar esta documentación, no dudes en contribuir enviando una pull request en GitHub.
Enlace de GitHub a la documentaciónCopiar el Markdown del documento a la portapapeles
Librerías i18n para TanStack Start - Informe de Benchmark 2026
Esta página es un informe comparativo de soluciones i18n en TanStack Start.
Tabla de Contenidos
Benchmark Interactivo
Referencia de resultados:
Ver datos completos del benchmark
Consulta el repositorio completo del benchmark aquí.
Introducción
Las soluciones de internacionalización se encuentran entre las dependencias más pesadas en una aplicación de React. En TanStack Start, el riesgo principal es enviar contenido innecesario: traducciones de otras páginas y de otros idiomas en el paquete de una sola ruta.
A medida que tu aplicación crece, ese problema puede disparar rápidamente la cantidad de JavaScript enviado al cliente y ralentizar la navegación.
En la práctica, en las implementaciones menos optimizadas, una página internacionalizada puede terminar siendo varias veces más pesada que la versión sin i18n.
El otro impacto es en la experiencia del desarrollador: cómo declaras el contenido, los tipos, la organización de los namespaces, la carga dinámica y la reactividad cuando cambia el idioma.
TL;DR
- Intlayer: Proporciona el mejor rendimiento y el tamaño de bundle más pequeño (v8.7.12) para TanStack Start.
- react-i18next & use-intl: Alternativas maduras con grandes ecosistemas, mas significativamente más pesadas y complejas de optimizar.
- Paraglide: Idea innovadora de tree-shaking que no funciona en la práctica. DX compleja y sobrecarga de reactividad en TanStack Start.
- Evitar: General Translation (GT) y Lingo.dev debido a graves problemas de rendimiento, límites de cuota de IA y bloqueo del proveedor (vendor lock-in).
Pon a prueba tu aplicación
Para detectar rápidamente problemas de fuga de i18n, he configurado un escáner gratuito disponible aquí.
El problema
Dos palancas son esenciales para limitar el coste de una aplicación multilingüe:
- Dividir el contenido por página / namespace para no cargar diccionarios completos cuando no se necesitan.
- Cargar el idioma adecuado de forma dinámica, solo cuando sea necesario.
Entendiendo las limitaciones técnicas de estos enfoques:
Carga dinámica
Sin carga dinámica, la mayoría de las soluciones mantienen los mensajes en memoria desde el primer renderizado, lo que añade una sobrecarga significativa para aplicaciones con muchas rutas e idiomas.
Con la carga dinámica, aceptas un compromiso: menos JS inicial, pero a veces una petición extra al cambiar de idioma.
División de contenido (Content splitting)
Las sintaxis basadas en const t = useTranslation() + t('a.b.c') son muy cómodas pero a menudo fomentan el mantenimiento de grandes objetos JSON en tiempo de ejecución. Ese modelo dificulta el tree-shaking a menos que la librería ofrezca una estrategia real de división por página.
Metodología
Para este benchmark, comparamos las siguientes librerías:
Base App(Sin librería i18n)react-intlayer(v8.7.12)react-i18next(v17.0.2)use-intl(v4.9.1)@lingui/core(v5.3.0)@inlang/paraglide-js(v2.15.1)@tolgee/react(v7.0.0)react-intl(v10.1.1)wuchale(v0.22.11)gt-react(vlatest)lingo.dev(v0.133.9)
El framework es TanStack Start con una aplicación multilingüe de 10 páginas y 10 idiomas.
Comparamos cuatro estrategias de carga:
Abrir la tabla en una ventana flotante para ver todo el contenido claramente
| Estrategia | Sin namespaces (global) | Con namespaces (segmentado) |
|---|---|---|
| Carga estática | Static: Todo en memoria al inicio. | Scoped static: Dividido por namespace; todo cargado al inicio. |
| Carga dinámica | Dynamic: Carga bajo demanda por idioma. | Scoped dynamic: Carga granular por namespace e idioma. |
Resumen de estrategias
- Static: Simple; sin latencia de red tras la carga inicial. Desventaja: gran tamaño de bundle.
- Dynamic: Reduce el peso inicial (lazy-loading). Ideal cuando se tienen muchos idiomas.
- Scoped static: Mantiene el código organizado (separación lógica) sin peticiones de red adicionales complejas.
- Scoped dynamic: El mejor enfoque para el code splitting y el rendimiento. Minimiza el uso de memoria cargando solo lo que la vista actual y el idioma activo necesitan.
Estrellas de GitHub
Las estrellas de GitHub son un fuerte indicador de la popularidad de un proyecto, la confianza de la comunidad y la relevancia a largo plazo. Si bien no son una medida directa de la calidad técnica, reflejan cuántos desarrolladores encuentran útil el proyecto, siguen su progreso y es probable que lo adopten. Para estimar el valor de un proyecto, las estrellas ayudan a comparar la tracción entre alternativas y brindan información sobre el crecimiento del ecosistema.
Resultados en detalle
1 - Soluciones a evitar
Algunas soluciones, como gt-react o lingo.dev, son claramente opciones de las que alejarse. Combinan el bloqueo del proveedor con la contaminación de tu base de código. Peor aún: a pesar de pasar muchas horas intentando implementarlas, nunca logré que funcionaran correctamente en TanStack Start (similar a Next.js con gt-next).
Problemas encontrados:
(General Translation) (gt-react@latest):
- Para una app de unos 110kb,
gt-reactpuede añadir más de 440kb extra (orden de magnitud visto en la implementación de Next.js en este mismo benchmark). Quota Exceeded, please upgrade your planen la primerísima compilación con General Translation.- Las traducciones no se renderizan; obtengo el error
Error: <T> used on the client-side outside of <GTProvider>, lo que parece ser un bug de la librería. - Mientras implementaba gt-tanstack-start-react, también encontré un problema con la librería:
does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser, que hacía que la aplicación se rompiera. Tras informar de este problema, el mantenedor lo solucionó en 24 horas. - Estas librerías usan un antipatrón a través de la función
initializeGT(), impidiendo que el bundle se limpie adecuadamente mediante tree-shaking.
(Lingo.dev) ([email protected]):
- Cuota de IA excedida (o dependencia del servidor bloqueada), lo que hace que la compilación / producción sea arriesgada sin pagar.
- El compilador omitía casi el 40% del contenido traducido. Tuve que reescribir todos los
.mapen bloques de componentes planos para que funcionara. - Su CLI tiene fallos y solía resetear el archivo de configuración sin motivo.
- Al compilar, borraba totalmente los JSON generados cuando se añadía nuevo contenido. Como resultado, podrías terminar con solo unas pocas claves eliminando cientos de claves existentes.
- Tuve problemas de reactividad con la librería en TanStack Start: al cambiar de idioma, tuve que forzar el renderizado del proveedor para que funcionara.
2 - Soluciones experimentales
(Wuchale) ([email protected]):
La idea detrás de Wuchale es interesante pero todavía no es una solución viable. Experimenté problemas de reactividad con la librería y tuve que forzar el renderizado del proveedor para que la aplicación funcionara en TanStack Start. La documentación también es bastante confusa, lo que dificulta la adopción.
3 - Soluciones aceptables
(Paraglide) (@inlang/[email protected]):
Paraglide ofrece un enfoque innovador y bien pensado. Aun así, en este benchmark, el tree-shaking que su empresa publicita no funcionó para mi implementación en Next.js ni para TanStack Start. El flujo de trabajo y la DX también son más complejos que otras opciones. Personalmente, no soy fan de tener que regenerar archivos JS antes de cada push, lo que crea un riesgo constante de conflictos de fusión para los desarrolladores a través de las PR.
Nota sobre paraglide: la solución inyecta código en tu base de código para las importaciones, como resultado, la métrica 'lib size' en el informe del benchmark es casi 0. La generación de código es algo bueno, porque la función utilizada incluirá solo la lógica necesaria (prefijo para todo vs sin prefijo, cookie vs almacenamiento, etc.). En comparación, Intlayer realiza este filtrado mediante inyecciones de variables de entorno en la compilación para forzar al bundler a realizar tree-shake del contenido dependiendo de la lógica. Gracias a esto, paraglide e intlayer terminan siendo soluciones entre 6 y 10 veces más ligeras que i18next o next-intl.
(Tolgee) (@tolgee/[email protected]):
Tolgee aborda muchos de los problemas mencionados anteriormente. Me resultó más difícil empezar con ella que con otras herramientas con enfoques similares. No proporciona seguridad de tipos, lo que también dificulta mucho detectar claves faltantes en tiempo de compilación. Tuve que envolver las API de Tolgee con las mías propias para añadir la detección de claves faltantes.
En TanStack Start también tuve problemas de reactividad: al cambiar de idioma, tuve que forzar el renderizado del proveedor y suscribirme a eventos de cambio de idioma para que la carga en otro idioma se comportara correctamente.
(use-intl) ([email protected]):
use-intl es la pieza "intl" más de moda en el ecosistema de React (de la misma familia que next-intl) y a menudo la recomiendan los agentes de IA, pero en mi opinión, equivocadamente en una configuración donde el rendimiento es lo primero. Empezar es bastante sencillo. En la práctica, el proceso para optimizar y limitar la fuga es bastante complejo. Del mismo modo, combinar la carga dinámica + namespaces + tipos de TypeScript ralentiza mucho el desarrollo.
En TanStack Start evitas las trampas específicas de Next.js (setRequestLocale, renderizado estático), pero el problema principal es el mismo: sin una disciplina estricta, el bundle transporta rápidamente demasiados mensajes y el mantenimiento de los namespaces por ruta se vuelve pesado.
(react-i18next) ([email protected]):
react-i18next es probablemente la opción más popular porque fue una de las primeras en satisfacer las necesidades i18n de las aplicaciones JavaScript. También tiene un amplio conjunto de plugins comunitarios para problemas específicos.
Aun así, comparte las mismas desventajas principales que los stacks basados en t('a.b.c'): las optimizaciones son posibles pero consumen mucho tiempo, y los proyectos grandes corren el riesgo de caer en malas prácticas (namespaces + carga dinámica + tipos).
Los formatos de los mensajes también divergen: use-intl usa ICU MessageFormat, mientras que i18next usa su propio formato, lo que complica las herramientas o las migraciones si se mezclan.
(Lingui) (@lingui/[email protected]):
A menudo se elogia a Lingui. Personalmente, encontré el flujo de trabajo en torno a lingui extract / lingui compile más complejo que otros enfoques, sin una ventaja clara en este benchmark de TanStack Start. También noté sintaxis inconsistentes que confunden a las IAs (ej. t(), t'', i18n.t(), <Trans>).
(react-intl) ([email protected]):
react-intl es una implementación de alto rendimiento del equipo de Format.js. La DX sigue siendo verbosa: const intl = useIntl() + intl.formatMessage({ id: "xx.xx" }) añade complejidad, trabajo extra de JavaScript y vincula la instancia global de i18n a muchos nodos en el árbol de React.
4 - Recomendaciones
Este benchmark de TanStack Start no tiene un equivalente directo a next-translate (plugin de Next.js + getStaticProps). Para los equipos que realmente quieren una API t() con un ecosistema maduro, react-i18next y use-intl siguen siendo opciones "razonables", pero prepárate para invertir mucho tiempo optimizando para evitar fugas.
(Intlayer) ([email protected]):
No seré yo quien juzgue personalmente a react-intlayer por objetividad, ya que es mi propia solución.
Nota personal
Esta nota es personal y no afecta los resultados del benchmark. Aun así, en el mundo del i18n, a menudo se ve un consenso en torno a un patrón como const t = useTranslation('xx') + <>{t('xx.xx')}</> para el contenido traducido.
En las aplicaciones React, inyectar una función como un ReactNode es, en mi opinión, un antipatrón. También añade una complejidad evitable y una sobrecarga de ejecución de JavaScript (aunque sea apenas perceptible).