@intlayer/docs 6.1.4 → 6.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/blog/ar/next-i18next_vs_next-intl_vs_intlayer.md +1135 -75
  2. package/blog/ar/nextjs-multilingual-seo-comparison.md +364 -0
  3. package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +1139 -72
  4. package/blog/de/nextjs-multilingual-seo-comparison.md +362 -0
  5. package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +224 -240
  6. package/blog/en/nextjs-multilingual-seo-comparison.md +360 -0
  7. package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +1134 -37
  8. package/blog/en-GB/nextjs-multilingual-seo-comparison.md +360 -0
  9. package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +1122 -64
  10. package/blog/es/nextjs-multilingual-seo-comparison.md +363 -0
  11. package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +1132 -75
  12. package/blog/fr/nextjs-multilingual-seo-comparison.md +362 -0
  13. package/blog/hi/nextjs-multilingual-seo-comparison.md +363 -0
  14. package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +1120 -55
  15. package/blog/it/nextjs-multilingual-seo-comparison.md +363 -0
  16. package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +1140 -76
  17. package/blog/ja/nextjs-multilingual-seo-comparison.md +362 -0
  18. package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +1129 -73
  19. package/blog/ko/nextjs-multilingual-seo-comparison.md +362 -0
  20. package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +1133 -76
  21. package/blog/pt/nextjs-multilingual-seo-comparison.md +362 -0
  22. package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +1142 -74
  23. package/blog/ru/nextjs-multilingual-seo-comparison.md +370 -0
  24. package/blog/tr/nextjs-multilingual-seo-comparison.md +362 -0
  25. package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +1142 -75
  26. package/blog/zh/nextjs-multilingual-seo-comparison.md +394 -0
  27. package/dist/cjs/generated/blog.entry.cjs +16 -0
  28. package/dist/cjs/generated/blog.entry.cjs.map +1 -1
  29. package/dist/esm/generated/blog.entry.mjs +16 -0
  30. package/dist/esm/generated/blog.entry.mjs.map +1 -1
  31. package/dist/types/generated/blog.entry.d.ts +1 -0
  32. package/dist/types/generated/blog.entry.d.ts.map +1 -1
  33. package/docs/en/interest_of_intlayer.md +2 -2
  34. package/package.json +10 -10
  35. package/src/generated/blog.entry.ts +16 -0
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  createdAt: 2025-08-23
3
- updatedAt: 2025-08-23
3
+ updatedAt: 2025-09-29
4
4
  title: next-i18next vs next-intl vs Intlayer
5
- description: Comparación de next-i18next con next-intl e Intlayer para la internacionalización (i18n) de una aplicación Next.js
5
+ description: Comparar next-i18next con next-intl e Intlayer para la internacionalización (i18n) de una aplicación Next.js
6
6
  keywords:
7
7
  - next-intl
8
8
  - next-i18next
@@ -19,8 +19,11 @@ slugs:
19
19
 
20
20
  # next-i18next VS next-intl VS intlayer | Internacionalización (i18n) en Next.js
21
21
 
22
- Esta guía compara tres opciones de i18n ampliamente utilizadas para **Next.js**: **next-intl**, **next-i18next** e **Intlayer**.
23
- Nos centramos en **Next.js 13+ App Router** (con **React Server Components**) y evaluamos:
22
+ Veamos las similitudes y diferencias entre tres opciones de i18n para Next.js: next-i18next, next-intl e Intlayer.
23
+
24
+ Esto no es un tutorial completo. Es una comparación para ayudarte a elegir.
25
+
26
+ Nos enfocamos en **Next.js 13+ App Router** (con **React Server Components**) y evaluamos:
24
27
 
25
28
  1. **Arquitectura y organización del contenido**
26
29
  2. **TypeScript y seguridad**
@@ -30,133 +33,1188 @@ Nos centramos en **Next.js 13+ App Router** (con **React Server Components**) y
30
33
  6. **Experiencia del desarrollador (DX), herramientas y mantenimiento**
31
34
  7. **SEO y escalabilidad para proyectos grandes**
32
35
 
33
- > **resumen**: Los tres pueden localizar una aplicación Next.js. Si deseas **contenido con alcance por componente**, **tipos estrictos en TypeScript**, **verificaciones de claves faltantes en tiempo de compilación**, **diccionarios optimizados por tree-shaking** y **helpers de App Router y SEO de primera clase**, **Intlayer** es la opción más completa y moderna.
36
+ > **resumen**: Los tres pueden localizar una aplicación Next.js. Si quieres **contenido con alcance por componente**, **tipos estrictos en TypeScript**, **verificaciones de claves faltantes en tiempo de compilación**, **diccionarios optimizados (tree-shaken)** y **helpers de primera clase para App Router y SEO**, **Intlayer** es la opción más completa y moderna.
37
+
38
+ > Una confusión común entre los desarrolladores es pensar que `next-intl` es la versión de Next.js de `react-intl`. No lo es: `next-intl` es mantenido por [Amann](https://github.com/amannn), mientras que `react-intl` es mantenido por [FormatJS](https://github.com/formatjs/formatjs).
34
39
 
35
40
  ---
36
41
 
37
- ## Posicionamiento a alto nivel
42
+ ## En resumen
38
43
 
39
- - **next-intl** - Formateo de mensajes ligero y sencillo con soporte sólido para Next.js. Los catálogos centralizados son comunes; la experiencia del desarrollador (DX) es simple, pero la seguridad y el mantenimiento a gran escala siguen siendo principalmente tu responsabilidad.
40
- - **next-i18next** - i18next con la apariencia de Next.js. Ecosistema maduro y características mediante plugins (por ejemplo, ICU), pero la configuración puede ser extensa y los catálogos tienden a centralizarse a medida que los proyectos crecen.
41
- - **Intlayer** - Modelo de contenido centrado en componentes para Next.js, **tipado estricto en TS**, **verificaciones en tiempo de compilación**, **tree-shaking**, **middleware integrado y helpers de SEO**, **Editor Visual/CMS** opcional y **traducciones asistidas por IA**.
44
+ - **next-intl** - Formateo de mensajes ligero y sencillo con un sólido soporte para Next.js. Los catálogos centralizados son comunes; la experiencia del desarrollador (DX) es simple, pero la seguridad y el mantenimiento a gran escala siguen siendo principalmente tu responsabilidad.
45
+ - **next-i18next** - i18next con apariencia de Next.js. Ecosistema maduro y características mediante plugins (por ejemplo, ICU), pero la configuración puede ser extensa y los catálogos tienden a centralizarse a medida que los proyectos crecen.
46
+ - **Intlayer** - Modelo de contenido centrado en componentes para Next.js, **tipado estricto en TS**, **verificaciones en tiempo de compilación**, **tree-shaking**, **middleware y ayudas SEO integradas**, **Editor Visual/CMS** opcional y **traducciones asistidas por IA**.
42
47
 
43
48
  ---
44
49
 
45
- ## Comparación de características lado a lado (enfocado en Next.js)
50
+ | Library | GitHub Stars | Total Commits | Last Commit | First Version | NPM Version | NPM Downloads |
51
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
52
+ | `aymericzip/intlayer` | [![GitHub Repo stars](https://img.shields.io/github/stars/aymericzip/intlayer?style=for-the-badge&label=%E2%AD%90%20stars)](https://github.com/aymericzip/intlayer/stargazers) | [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/aymericzip/intlayer?style=for-the-badge&label=commits)](https://github.com/aymericzip/intlayer/commits) | [![Last Commit](https://img.shields.io/github/last-commit/aymericzip/intlayer?style=for-the-badge)](https://github.com/aymericzip/intlayer/commits) | April 2024 | [![npm](https://img.shields.io/npm/v/intlayer?style=for-the-badge)](https://www.npmjs.com/package/intlayer) | [![npm downloads](https://img.shields.io/npm/dm/intlayer?style=for-the-badge)](https://www.npmjs.com/package/intlayer) |
53
+ | `amannn/next-intl` | [![GitHub Repo stars](https://img.shields.io/github/stars/amannn/next-intl?style=for-the-badge&label=%E2%AD%90%20stars)](https://github.com/amannn/next-intl/stargazers) | [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/amannn/next-intl?style=for-the-badge&label=commits)](https://github.com/amannn/next-intl/commits) | [![Last Commit](https://img.shields.io/github/last-commit/amannn/next-intl?style=for-the-badge)](https://github.com/amannn/next-intl/commits) | Nov 2020 | [![npm](https://img.shields.io/npm/v/next-intl?style=for-the-badge)](https://www.npmjs.com/package/next-intl) | [![npm downloads](https://img.shields.io/npm/dm/next-intl?style=for-the-badge)](https://www.npmjs.com/package/next-intl) |
54
+ | `i18next/i18next` | [![GitHub Repo stars](https://img.shields.io/github/stars/i18next/i18next?style=for-the-badge&label=%E2%AD%90%20stars)](https://github.com/i18next/i18next/stargazers) | [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/i18next/i18next?style=for-the-badge&label=commits)](https://github.com/i18next/i18next/commits) | [![Last Commit](https://img.shields.io/github/last-commit/i18next/i18next?style=for-the-badge)](https://github.com/i18next/i18next/commits) | Jan 2012 | [![npm](https://img.shields.io/npm/v/i18next?style=for-the-badge)](https://www.npmjs.com/package/i18next) | [![npm downloads](https://img.shields.io/npm/dm/i18next?style=for-the-badge)](https://www.npmjs.com/package/i18next) |
55
+ | `i18next/next-i18next` | [![GitHub Repo stars](https://img.shields.io/github/stars/i18next/next-i18next?style=for-the-badge&label=%E2%AD%90%20stars)](https://github.com/i18next/next-i18next/stargazers) | [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/i18next/next-i18next?style=for-the-badge&label=commits)](https://github.com/i18next/next-i18next/commits) | [![Last Commit](https://img.shields.io/github/last-commit/i18next/next-i18next?style=for-the-badge)](https://github.com/i18next/next-i18next/commits) | Nov 2018 | [![npm](https://img.shields.io/npm/v/next-i18next?style=for-the-badge)](https://www.npmjs.com/package/next-i18next) | [![npm downloads](https://img.shields.io/npm/dm/next-i18next?style=for-the-badge)](https://www.npmjs.com/package/next-i18next) |
46
56
 
47
- | Característica | `next-intlayer` (Intlayer) | `next-intl` | `next-i18next` |
57
+ > Las insignias se actualizan automáticamente. Las instantáneas variarán con el tiempo.
58
+
59
+ ---
60
+
61
+ ## Comparación de Funciones Lado a Lado (enfocado en Next.js)
62
+
63
+ | Función | `next-intlayer` (Intlayer) | `next-intl` | `next-i18next` |
48
64
  | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
49
- | **Traducciones Cerca de los Componentes** | ✅ Sí, contenido colocalizado con cada componente | ❌ No | ❌ No |
65
+ | **Traducciones Cerca de los Componentes** | ✅ Sí, contenido ubicado junto a cada componente | ❌ No | ❌ No |
50
66
  | **Integración con TypeScript** | ✅ Avanzada, tipos estrictos generados automáticamente | ✅ Buena | ⚠️ Básica |
51
- | **Detección de Traducciones Faltantes** | ✅ Resaltado de errores en TypeScript y error/advertencia en tiempo de compilación | ⚠️ Recurso alternativo en tiempo de ejecución | ⚠️ Recurso alternativo en tiempo de ejecución |
52
- | **Contenido Enriquecido (JSX/Markdown/componentes)** | ✅ Soporte directo | ❌ No diseñado para nodos enriquecidos | ⚠️ Limitado |
67
+ | **Detección de Traducción Faltante** | ✅ Resaltado de errores en TypeScript y error/advertencia en tiempo de compilación | ⚠️ Recurso alternativo en tiempo de ejecución | ⚠️ Recurso alternativo en tiempo de ejecución |
68
+ | **Contenido enriquecido (JSX/Markdown/componentes)** | ✅ Soporte directo | ❌ No diseñado para nodos enriquecidos | ⚠️ Limitado |
53
69
  | **Traducción impulsada por IA** | ✅ Sí, soporta múltiples proveedores de IA. Usable con tus propias claves API. Considera el contexto de tu aplicación y el alcance del contenido | ❌ No | ❌ No |
54
- | **Editor Visual** | ✅ Sí, Editor Visual local + CMS opcional; puede externalizar contenido de la base de código; embebible | ❌ No / disponible a través de plataformas externas de localización | ❌ No / disponible a través de plataformas externas de localización |
55
- | **Enrutamiento Localizado** | ✅ Sí, soporta rutas localizadas desde el inicio (funciona con Next.js y Vite) | ✅ Integrado, App Router soporta el segmento `[locale]` | ✅ Integrado |
70
+ | **Editor Visual** | ✅ Sí, Editor Visual local + CMS opcional; puede externalizar contenido de la base de código; integrable | ❌ No / disponible a través de plataformas externas de localización | ❌ No / disponible a través de plataformas externas de localización |
71
+ | **Enrutamiento Localizado** | ✅ Sí, soporta rutas localizadas desde el inicio (funciona con Next.js y Vite) | ✅ Incorporado, App Router soporta el segmento `[locale]` | ✅ Incorporado |
56
72
  | **Generación Dinámica de Rutas** | ✅ Sí | ✅ Sí | ✅ Sí |
57
73
  | **Pluralización** | ✅ Patrones basados en enumeraciones | ✅ Bueno | ✅ Bueno |
58
74
  | **Formato (fechas, números, monedas)** | ✅ Formateadores optimizados (Intl en el núcleo) | ✅ Bueno (helpers de Intl) | ✅ Bueno (helpers de Intl) |
59
75
  | **Formato de contenido** | ✅ .tsx, .ts, .js, .json, .md, .txt, (.yaml en desarrollo) | ✅ .json, .js, .ts | ⚠️ .json |
60
76
  | **Soporte ICU** | ⚠️ En desarrollo | ✅ Sí | ⚠️ A través de plugin (`i18next-icu`) |
61
- | **Ayudantes SEO (hreflang, sitemap)** | ✅ Herramientas integradas: ayudantes para sitemap, robots.txt, metadatos | ✅ Bueno | ✅ Bueno |
62
- | **Ecosistema / Comunidad** | ⚠️ Más pequeño pero creciendo rápido y reactivo | ✅ Mediano, enfocado en Next.js | ✅ Mediano, enfocado en Next.js |
63
- | **Renderizado del lado del servidor y Componentes del Servidor** | ✅ Sí, optimizado para SSR / Componentes del Servidor de React | ⚠️ Soportado a nivel de página pero es necesario pasar funciones t en el árbol de componentes para los componentes hijos del servidor | ⚠️ Soportado a nivel de página pero es necesario pasar funciones t en el árbol de componentes para los componentes hijos del servidor |
64
- | **Tree-shaking (cargar solo contenido usado)** | ✅ Sí, por componente en tiempo de compilación mediante plugins de Babel/SWC | ⚠️ Parcial | ⚠️ Parcial |
65
- | **Carga diferida** | ✅ Sí, por localización / por diccionario | ✅ Sí (por ruta/por localización), requiere gestión de espacios de nombres | ✅ Sí (por ruta/por localización), requiere gestión de espacios de nombres |
66
- | **Eliminación de contenido no usado** | ✅ Sí, por diccionario en tiempo de compilación | ❌ No, puede ser gestionado manualmente con gestión de espacios de nombres | ❌ No, puede ser gestionado manualmente con gestión de espacios de nombres |
67
- | **Gestión de Proyectos Grandes** | ✅ Fomenta la modularidad, adecuado para sistemas de diseño | ✅ Modular con configuración | ✅ Modular con configuración |
77
+ | **Ayudas SEO (hreflang, sitemap)** | ✅ Herramientas integradas: ayudas para sitemap, robots.txt, metadatos | ✅ Bueno | ✅ Bueno |
78
+ | **Ecosistema / Comunidad** | ⚠️ Más pequeño pero creciendo rápido y reactivo | ✅ Bueno | ✅ Bueno |
79
+ | **Renderizado del lado del servidor y Componentes del servidor** | ✅ Sí, optimizado para SSR / Componentes del servidor de React | ⚠️ Soportado a nivel de página pero es necesario pasar funciones t en el árbol de componentes para los componentes hijos del servidor | ⚠️ Soportado a nivel de página pero es necesario pasar funciones t en el árbol de componentes para los componentes hijos del servidor |
80
+ | **Tree-shaking (cargar solo el contenido usado)** | ✅ Sí, por componente en tiempo de compilación mediante plugins de Babel/SWC | ⚠️ Parcial | ⚠️ Parcial |
81
+ | **Carga diferida (Lazy loading)** | ✅ Sí, por localización / por diccionario | ✅ Sí (por ruta/por localización), requiere gestión de espacios de nombres | ✅ Sí (por ruta/por localización), requiere gestión de espacios de nombres |
82
+ | **Eliminación de contenido no utilizado** | ✅ Sí, por diccionario en tiempo de compilación | ❌ No, puede gestionarse manualmente con la gestión de espacios de nombres | ❌ No, puede gestionarse manualmente con la gestión de espacios de nombres |
83
+ | **Gestión de proyectos grandes** | ✅ Fomenta la modularidad, adecuado para sistemas de diseño | ✅ Modular con configuración | ✅ Modular con configuración |
84
+ | **Prueba de traducciones faltantes (CLI/CI)** | ✅ CLI: `npx intlayer content test` (auditoría compatible con CI) | ⚠️ No incorporado; la documentación sugiere `npx @lingual/i18n-check` | ⚠️ No incorporado; depende de herramientas de i18next / runtime `saveMissing` |
68
85
 
69
86
  ---
70
87
 
71
- ## Comparación detallada
88
+ ## Introducción
72
89
 
73
- ### 1) Arquitectura y escalabilidad
90
+ Next.js te ofrece soporte integrado para enrutamiento internacionalizado (por ejemplo, segmentos de localización). Pero esa función no realiza traducciones por sí sola. Aún necesitas una biblioteca para mostrar contenido localizado a tus usuarios.
74
91
 
75
- - **next-intl / next-i18next**: Por defecto usan **catálogos centralizados** por localización (más **espacios de nombres** en i18next). Funciona bien al principio, pero a menudo se convierte en una gran área compartida con aumento del acoplamiento y cambios frecuentes en las claves.
76
- - **Intlayer**: Fomenta diccionarios **por componente** (o por funcionalidad) **ubicados junto** al código al que sirven. Esto reduce la carga cognitiva, facilita la duplicación/migración de piezas de la interfaz y disminuye los conflictos entre equipos. El contenido no utilizado es naturalmente más fácil de detectar y eliminar.
92
+ Existen muchas bibliotecas i18n, pero en el mundo de Next.js hoy en día, tres están ganando popularidad: next-i18next, next-intl e Intlayer.
93
+
94
+ ---
77
95
 
78
- **Por qué importa:** En bases de código grandes o configuraciones de sistemas de diseño, el **contenido modular** escala mejor que los catálogos monolíticos.
96
+ ## Arquitectura y escalabilidad
97
+
98
+ - **next-intl / next-i18next**: Por defecto usan **catálogos centralizados** por idioma (más **espacios de nombres** en i18next). Funciona bien al principio, pero a menudo se convierte en una gran área compartida con un aumento del acoplamiento y la rotación de claves.
99
+ - **Intlayer**: Fomenta diccionarios **por componente** (o por característica) **co-localizados** con el código que sirven. Esto reduce la carga cognitiva, facilita la duplicación/migración de piezas de la interfaz y disminuye los conflictos entre equipos. El contenido no utilizado es naturalmente más fácil de detectar y eliminar.
100
+
101
+ **Por qué importa:** En grandes bases de código o configuraciones de sistemas de diseño, el **contenido modular** escala mejor que los catálogos monolíticos.
79
102
 
80
103
  ---
81
104
 
82
- ### 2) TypeScript y seguridad
105
+ ## Tamaños de los paquetes y dependencias
106
+
107
+ Después de construir la aplicación, el bundle es el JavaScript que el navegador cargará para renderizar la página. Por lo tanto, el tamaño del bundle es importante para el rendimiento de la aplicación.
108
+
109
+ Dos componentes son importantes en el contexto de un bundle de aplicación multilingüe:
110
+
111
+ - El código de la aplicación
112
+ - El contenido cargado por el navegador
113
+
114
+ ## Código de la Aplicación
115
+
116
+ La importancia del código de la aplicación es mínima en este caso. Las tres soluciones son tree-shakables, lo que significa que las partes no utilizadas del código no se incluyen en el bundle.
117
+
118
+ Aquí hay una comparación del tamaño del bundle de JavaScript cargado por el navegador para una aplicación multilingüe con las tres soluciones.
119
+
120
+ Si no necesitamos ningún formateador en la aplicación, la lista de funciones exportadas después del tree-shaking será:
121
+
122
+ - **next-intlayer**: `useIntlayer`, `useLocale`, `NextIntlClientProvider`, (El tamaño del paquete es 180.6 kB -> 78.6 kB (gzip))
123
+ - **next-intl**: `useTranslations`, `useLocale`, `NextIntlClientProvider`, (El tamaño del paquete es 101.3 kB -> 31.4 kB (gzip))
124
+ - **next-i18next**: `useTranslation`, `useI18n`, `I18nextProvider`, (El tamaño del paquete es 80.7 kB -> 25.5 kB (gzip))
125
+
126
+ Estas funciones son solo envoltorios alrededor del contexto/estado de React, por lo que el impacto total de la biblioteca i18n en el tamaño del paquete es mínimo.
127
+
128
+ > Intlayer es ligeramente más grande que `next-intl` y `next-i18next` porque incluye más lógica en la función `useIntlayer`. Esto está relacionado con la integración de markdown y `intlayer-editor`.
129
+
130
+ ## Contenido y Traducciones
131
+
132
+ Esta parte a menudo es ignorada por los desarrolladores, pero consideremos el caso de una aplicación compuesta por 10 páginas en 10 idiomas. Supongamos que cada página integra un contenido 100% único para simplificar el cálculo (en realidad, mucho contenido es redundante entre páginas, por ejemplo, título de la página, encabezado, pie de página, etc.).
133
+
134
+ Un usuario que quiera visitar la página `/fr/about` cargará el contenido de una página en un idioma dado. Ignorar la optimización del contenido significaría cargar innecesariamente el 8,200% `((1 + (((10 páginas - 1) × (10 idiomas - 1)))) × 100)` del contenido de la aplicación. ¿Ves el problema? Incluso si este contenido sigue siendo texto, y aunque probablemente prefieras pensar en optimizar las imágenes de tu sitio, estás enviando contenido inútil por todo el mundo y haciendo que las computadoras de los usuarios lo procesen sin motivo.
135
+
136
+ Dos problemas importantes:
137
+
138
+ - **División por ruta:**
139
+
140
+ > Si estoy en la página `/about`, no quiero cargar el contenido de la página `/home`
141
+
142
+ - **División por localización:**
143
+
144
+ > Si estoy en la página `/fr/about`, no quiero cargar el contenido de la página `/en/about`
145
+
146
+ De nuevo, las tres soluciones son conscientes de estos problemas y permiten gestionar estas optimizaciones. La diferencia entre las tres soluciones es la experiencia del desarrollador (DX).
147
+
148
+ `next-intl` y `next-i18next` utilizan un enfoque centralizado para gestionar las traducciones, permitiendo dividir el JSON por localización y por subarchivos. En `next-i18next`, llamamos a los archivos JSON 'namespaces'; `next-intl` permite declarar mensajes. En `intlayer`, llamamos a los archivos JSON 'diccionarios'.
149
+
150
+ - En el caso de `next-intl`, al igual que `next-i18next`, el contenido se carga a nivel de página/layout, luego este contenido se carga en un proveedor de contexto. Esto significa que el desarrollador debe gestionar manualmente los archivos JSON que se cargarán para cada página.
151
+
152
+ > En la práctica, esto implica que los desarrolladores a menudo omiten esta optimización, prefiriendo cargar todo el contenido en el proveedor de contexto de la página por simplicidad.
153
+
154
+ - En el caso de `intlayer`, todo el contenido se carga en la aplicación. Luego, un plugin (`@intlayer/babel` / `@intlayer/swc`) se encarga de optimizar el paquete cargando solo el contenido usado en la página. Por lo tanto, el desarrollador no necesita gestionar manualmente los diccionarios que se cargarán. Esto permite una mejor optimización, mejor mantenibilidad y reduce el tiempo de desarrollo.
83
155
 
84
- - **next-intl**: Soporte sólido para TypeScript, pero **las claves no están estrictamente tipadas por defecto**; deberás mantener los patrones de seguridad manualmente.
85
- - **next-i18next**: Tipos base para hooks; **la tipificación estricta de claves requiere herramientas/configuración adicional**.
86
- - **Intlayer**: **Genera tipos estrictos** a partir de tu contenido. La **autocompletación en el IDE** y los **errores en tiempo de compilación** detectan errores tipográficos y claves faltantes antes del despliegue.
156
+ A medida que la aplicación crece (especialmente cuando varios desarrolladores trabajan en la aplicación), es común olvidar eliminar contenido que ya no se usa de los archivos JSON.
87
157
 
88
- **Por qué es importante:** La tipificación fuerte desplaza las fallas hacia la **izquierda** (CI/compilación) en lugar de hacia la **derecha** (tiempo de ejecución).
158
+ > Tenga en cuenta que todos los JSON se cargan en todos los casos (next-intl, next-i18next, intlayer).
159
+
160
+ Por eso el enfoque de Intlayer es más eficiente: si un componente ya no se usa, su diccionario no se carga en el paquete.
161
+
162
+ Cómo la biblioteca maneja los fallback también es importante. Consideremos que la aplicación está en inglés por defecto, y el usuario visita la página `/fr/about`. Si faltan traducciones en francés, consideraremos el fallback en inglés.
163
+
164
+ En el caso de `next-intl` y `next-i18next`, la biblioteca requiere cargar el JSON relacionado con la configuración regional actual, pero también con la configuración regional de reserva. Por lo tanto, considerando que todo el contenido ha sido traducido, cada página cargará un 100% de contenido innecesario. **En comparación, `intlayer` procesa la reserva en tiempo de construcción del diccionario. Así, cada página cargará solo el contenido utilizado.**
165
+
166
+ Aquí un ejemplo del impacto de la optimización del tamaño del paquete usando `intlayer` en una aplicación vite + react:
167
+
168
+ | Paquete optimizado | Paquete no optimizado |
169
+ | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
170
+ | ![paquete optimizado](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png) | ![paquete no optimizado](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png) |
89
171
 
90
172
  ---
91
173
 
92
- ### 3) Manejo de traducciones faltantes
174
+ ## TypeScript y seguridad
175
+
176
+ <Columns>
177
+ <Column>
178
+
179
+ **next-intl**
180
+
181
+ - Soporte sólido para TypeScript, pero **las claves no están estrictamente tipadas por defecto**; deberás mantener los patrones de seguridad manualmente.
182
+
183
+ </Column>
184
+ <Column>
93
185
 
94
- - **next-intl / next-i18next**: Dependen de **respaldo en tiempo de ejecución** (por ejemplo, mostrar la clave o la configuración regional predeterminada). La compilación no falla.
95
- - **Intlayer**: **Detección en tiempo de compilación** con **advertencias/errores** para locales o claves faltantes.
186
+ **next-i18next**
96
187
 
97
- **Por qué es importante:** Detectar vacíos durante la compilación previene “cadenas misteriosas” en producción y se alinea con políticas estrictas de lanzamiento.
188
+ - Tipos base para hooks; **la tipificación estricta de claves requiere herramientas/configuración adicional**.
189
+
190
+ </Column>
191
+ <Column>
192
+
193
+ **intlayer**
194
+
195
+ - **Genera tipos estrictos** a partir de tu contenido. La **autocompletación del IDE** y los **errores en tiempo de compilación** detectan errores tipográficos y claves faltantes antes del despliegue.
196
+
197
+ </Column>
198
+ </Columns>
199
+
200
+ **Por qué es importante:** La tipificación fuerte desplaza los fallos hacia la **izquierda** (CI/compilación) en lugar de hacia la **derecha** (tiempo de ejecución).
98
201
 
99
202
  ---
100
203
 
101
- ### 4) Enrutamiento, middleware y estrategia de URL
204
+ ## Manejo de traducciones faltantes
205
+
206
+ **next-intl**
207
+
208
+ - Se basa en **respaldo en tiempo de ejecución** (por ejemplo, mostrar la clave o la configuración regional predeterminada). La compilación no falla.
209
+
210
+ **next-i18next**
102
211
 
103
- - Los tres funcionan con **enrutamiento localizado de Next.js** en el App Router.
104
- - **Intlayer** va más allá con **middleware i18n** (detección de locale vía headers/cookies) y **helpers** para generar URLs localizadas y etiquetas `<link rel="alternate" hreflang="…">`.
212
+ - Se basa en **respaldo en tiempo de ejecución** (por ejemplo, mostrar la clave o la configuración regional predeterminada). La compilación no falla.
105
213
 
106
- **Por qué es importante:** Menos capas personalizadas; **UX consistente** y **SEO limpio** en todas las locales.
214
+ **intlayer**
215
+
216
+ - **Detección en tiempo de compilación** con **advertencias/errores** para configuraciones regionales o claves faltantes.
217
+
218
+ **Por qué es importante:** Detectar vacíos durante la compilación evita “cadenas misteriosas” en producción y se alinea con estrictas políticas de lanzamiento.
107
219
 
108
220
  ---
109
221
 
110
- ### 5) Alineación con Componentes del Servidor (RSC)
222
+ ## Enrutamiento, middleware y estrategia de URL
223
+
224
+ <Columns>
225
+ <Column>
226
+
227
+ **next-intl**
228
+
229
+ - Funciona con **enrutamiento localizado de Next.js** en el App Router.
230
+
231
+ </Column>
232
+ <Column>
233
+
234
+ **next-i18next**
235
+
236
+ - Funciona con **enrutamiento localizado de Next.js** en el App Router.
111
237
 
112
- - **Todos** soportan Next.js 13+.
113
- - **Intlayer** suaviza la **frontera servidor/cliente** con una API consistente y proveedores diseñados para RSC, para que no tengas que pasar formateadores o funciones t a través de árboles de componentes.
238
+ </Column>
239
+ <Column>
114
240
 
115
- **Por qué es importante:** Modelo mental más limpio y menos casos límite en árboles híbridos.
241
+ **intlayer**
242
+
243
+ - Todo lo anterior, además de **middleware i18n** (detección de locale vía headers/cookies) y **helpers** para generar URLs localizadas y etiquetas `<link rel="alternate" hreflang="…">`.
244
+
245
+ </Column>
246
+ </Columns>
247
+
248
+ **Por qué es importante:** Menos capas de integración personalizadas; **UX consistente** y **SEO limpio** en todas las locales.
116
249
 
117
250
  ---
118
251
 
119
- ### 6) Rendimiento y comportamiento de carga
252
+ ## Alineación con Componentes de Servidor (RSC)
253
+
254
+ <Columns>
255
+ <Column>
256
+
257
+ **next-intl**
258
+
259
+ - Soporta Next.js 13+. A menudo requiere pasar funciones t/formatters a través de árboles de componentes en configuraciones híbridas.
260
+
261
+ </Column>
262
+ <Column>
263
+
264
+ **next-i18next**
120
265
 
121
- - **next-intl / next-i18next**: Control parcial mediante **namespaces** y **divisiones a nivel de ruta**; riesgo de incluir cadenas no usadas si no se mantiene la disciplina.
122
- - **Intlayer**: Realiza **tree-shaking** en la compilación y **carga perezosa por diccionario/locale**. El contenido no usado no se incluye.
266
+ - Compatible con Next.js 13+. Restricciones similares al pasar utilidades de traducción a través de límites.
123
267
 
124
- **Por qué es importante:** Paquetes más pequeños y arranque más rápido, especialmente en sitios con múltiples locales.
268
+ </Column>
269
+ <Column>
270
+
271
+ **intlayer**
272
+
273
+ - Compatible con Next.js 13+ y suaviza la **frontera servidor/cliente** con una API consistente y proveedores orientados a RSC, evitando el traslado de formateadores o funciones t.
274
+
275
+ </Column>
276
+ </Columns>
277
+
278
+ **Por qué importa:** Modelo mental más limpio y menos casos límite en árboles híbridos.
125
279
 
126
280
  ---
127
281
 
128
- ### 7) Experiencia de desarrollo (DX), herramientas y mantenimiento
282
+ ## DX, herramientas y mantenimiento
283
+
284
+ <Columns>
285
+ <Column>
286
+
287
+ **next-intl**
288
+
289
+ - Comúnmente se usa junto con plataformas externas de localización y flujos editoriales.
290
+
291
+ </Column>
292
+ <Column>
293
+
294
+ **next-i18next**
129
295
 
130
- - **next-intl / next-i18next**: Normalmente conectarás plataformas externas para traducciones y flujos editoriales.
131
- - **Intlayer**: Incluye un **Editor Visual gratuito** y un **CMS opcional** (compatible con Git o externalizado). Además, una **extensión para VSCode** para la creación de contenido y **traducciones asistidas por IA** usando tus propias claves de proveedor.
296
+ - Comúnmente se usa junto con plataformas externas de localización y flujos editoriales.
297
+
298
+ </Column>
299
+ <Column>
300
+
301
+ **intlayer**
302
+
303
+ - Incluye un **Editor Visual gratuito** y un **CMS opcional** (compatible con Git o externalizado), además de una **extensión para VSCode** y **traducciones asistidas por IA** utilizando tus propias claves de proveedor.
304
+
305
+ </Column>
306
+ </Columns>
132
307
 
133
308
  **Por qué es importante:** Reduce los costos operativos y acorta el ciclo entre desarrolladores y autores de contenido.
134
309
 
310
+ ## Integración con plataformas de localización (TMS)
311
+
312
+ Las grandes organizaciones suelen depender de Sistemas de Gestión de Traducción (TMS) como **Crowdin**, **Phrase**, **Lokalise**, **Localizely** o **Localazy**.
313
+
314
+ - **Por qué les importa a las empresas**
315
+ - **Colaboración y roles**: Participan múltiples actores: desarrolladores, gerentes de producto, traductores, revisores, equipos de marketing.
316
+ - **Escalabilidad y eficiencia**: localización continua, revisión en contexto.
317
+
318
+ - **next-intl / next-i18next**
319
+ - Normalmente utilizan **catálogos JSON centralizados**, por lo que la exportación/importación con TMS es sencilla.
320
+ - Ecosistemas maduros y ejemplos/integraciones para las plataformas mencionadas.
321
+
322
+ - **Intlayer**
323
+ - Fomenta **diccionarios descentralizados por componente** y soporta contenido en **TypeScript/TSX/JS/JSON/MD**.
324
+ - Esto mejora la modularidad en el código, pero puede dificultar la integración plug-and-play con TMS cuando una herramienta espera archivos JSON centralizados y planos.
325
+ - Intlayer ofrece alternativas: **traducciones asistidas por IA** (usando tus propias claves de proveedor), un **Editor Visual/CMS**, y flujos de trabajo **CLI/CI** para detectar y completar vacíos.
326
+
327
+ > Nota: `next-intl` y `i18next` también aceptan catálogos en TypeScript. Si tu equipo almacena mensajes en archivos `.ts` o los descentraliza por funcionalidad, puedes enfrentar fricciones similares con el TMS. Sin embargo, muchas configuraciones de `next-intl` permanecen centralizadas en una carpeta `locales/`, lo que facilita un poco la refactorización a JSON para el TMS.
328
+
329
+ ## Experiencia del Desarrollador
330
+
331
+ Esta parte realiza una comparación profunda entre las tres soluciones. En lugar de considerar casos simples, como se describe en la documentación de 'primeros pasos' para cada solución, consideraremos un caso de uso real, más similar a un proyecto real.
332
+
333
+ ### Estructura de la aplicación
334
+
335
+ La estructura de la aplicación es importante para asegurar un buen mantenimiento de tu base de código.
336
+
337
+ <Tab defaultTab="next-intl" group='techno'>
338
+
339
+ <TabItem label="next-i18next" value="next-i18next">
340
+
341
+ ```bash
342
+ .
343
+ ├── public
344
+ │ └── locales
345
+ │ ├── en
346
+ │ │ ├── home.json
347
+ │ │ └── navbar.json
348
+ │ ├── fr
349
+ │ │ ├── home.json
350
+ │ │ └── navbar.json
351
+ │ └── es
352
+ │ ├── home.json
353
+ │ └── navbar.json
354
+ ├── next-i18next.config.js
355
+ └── src
356
+ ├── middleware.ts
357
+ ├── app
358
+ │ └── home.tsx
359
+ └── components
360
+ └── Navbar
361
+ └── index.tsx
362
+ ```
363
+
364
+ </TabItem>
365
+ <TabItem label="next-intl" value="next-intl">
366
+
367
+ ```bash
368
+ .
369
+ ├── locales
370
+ │ ├── en
371
+ │ │ ├── home.json
372
+ │ │ └── navbar.json
373
+ │ ├── fr
374
+ │ │ ├── home.json
375
+ │ │ └── navbar.json
376
+ │ └── es
377
+ │ ├── home.json
378
+ │ └── navbar.json
379
+ ├── i18n.ts
380
+ └── src
381
+ ├── middleware.ts
382
+ ├── app
383
+ │ └── home.tsx
384
+ └── components
385
+ └── Navbar
386
+ └── index.tsx
387
+ ```
388
+
389
+ </TabItem>
390
+ <TabItem label="intlayer" value="intlayer">
391
+
392
+ ```bash
393
+ .
394
+ ├── intlayer.config.ts
395
+ └── src
396
+ ├── middleware.ts
397
+ ├── app
398
+ │ └── home
399
+ │ └── index.tsx
400
+ │ └── index.content.ts
401
+ └── components
402
+ └── Navbar
403
+ ├── index.tsx
404
+ └── index.content.ts
405
+ ```
406
+
407
+ </TabItem>
408
+ </Tab>
409
+
410
+ #### Comparación
411
+
412
+ - **next-intl / next-i18next**: Catálogos centralizados (JSON; namespaces/mensajes). Estructura clara, se integra bien con plataformas de traducción, pero puede llevar a más ediciones cruzadas entre archivos a medida que las aplicaciones crecen.
413
+ - **Intlayer**: Diccionarios `.content.{ts|js|json}` por componente, ubicados junto a los componentes. Facilita la reutilización de componentes y el razonamiento local; añade archivos y depende de herramientas en tiempo de compilación.
414
+
415
+ #### Configuración y carga de contenido
416
+
417
+ Como se mencionó anteriormente, debe optimizar cómo se importa cada archivo JSON en su código.
418
+ Cómo la biblioteca maneja la carga de contenido es importante.
419
+
420
+ <Tab defaultTab="next-intl" group='techno'>
421
+ <TabItem label="next-i18next" value="next-i18next">
422
+
423
+ ```tsx fileName="next-i18next.config.js"
424
+ module.exports = {
425
+ i18n: {
426
+ locales: ["en", "fr", "es"],
427
+ defaultLocale: "en",
428
+ },
429
+ };
430
+ ```
431
+
432
+ ```tsx fileName="src/app/_app.tsx"
433
+ import { appWithTranslation } from "next-i18next";
434
+
435
+ const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;
436
+
437
+ export default appWithTranslation(MyApp);
438
+ ```
439
+
440
+ ```tsx fileName="src/app/[locale]/about/page.tsx"
441
+ import type { GetStaticProps } from "next";
442
+ import { serverSideTranslations } from "next-i18next/serverSideTranslations";
443
+ import { useTranslation } from "next-i18next";
444
+ import { I18nextProvider, initReactI18next } from "react-i18next";
445
+ import { createInstance } from "i18next";
446
+ import { ClientComponent, ServerComponent } from "@components";
447
+
448
+ export default function HomePage({ locale }: { locale: string }) {
449
+ // Declare explícitamente el namespace utilizado por este componente
450
+ const resources = await loadMessagesFor(locale); // tu cargador (JSON, etc.)
451
+
452
+ const i18n = createInstance();
453
+ i18n.use(initReactI18next).init({
454
+ lng: locale,
455
+ fallbackLng: "en",
456
+ resources,
457
+ ns: ["common", "about"],
458
+ defaultNS: "common",
459
+ interpolation: { escapeValue: false },
460
+ });
461
+
462
+ const { t } = useTranslation("about");
463
+
464
+ return (
465
+ <I18nextProvider i18n={i18n}>
466
+ <main>
467
+ <h1>{t("title")}</h1>
468
+ <ClientComponent />
469
+ <ServerComponent />
470
+ </main>
471
+ </I18nextProvider>
472
+ );
473
+ }
474
+
475
+ export const getStaticProps: GetStaticProps = async ({ locale }) => {
476
+ // Solo precargar los namespaces necesarios para ESTA página
477
+ return {
478
+ props: {
479
+ ...(await serverSideTranslations(locale ?? "en", ["common", "about"])),
480
+ },
481
+ };
482
+ };
483
+ ```
484
+
485
+ </TabItem>
486
+ <TabItem label="next-intl" value="next-intl">
487
+
488
+ ```tsx fileName="i18n.ts"
489
+ import { getRequestConfig } from "next-intl/server";
490
+ import { notFound } from "next/navigation";
491
+
492
+ // Puede ser importado desde una configuración compartida
493
+ const locales = ["en", "fr", "es"];
494
+
495
+ export default getRequestConfig(async ({ locale }) => {
496
+ // Validar que el parámetro `locale` entrante sea válido
497
+ if (!locales.includes(locale as any)) notFound();
498
+
499
+ return {
500
+ messages: (await import(`../messages/${locale}.json`)).default,
501
+ };
502
+ });
503
+ ```
504
+
505
+ ```tsx fileName="src/app/[locale]/about/layout.tsx"
506
+ import { NextIntlClientProvider } from "next-intl";
507
+ import { getMessages, unstable_setRequestLocale } from "next-intl/server";
508
+ import pick from "lodash/pick";
509
+
510
+ export default async function LocaleLayout({
511
+ children,
512
+ params,
513
+ }: {
514
+ children: React.ReactNode;
515
+ params: { locale: string };
516
+ }) {
517
+ const { locale } = params;
518
+
519
+ // Establece la configuración regional activa para esta renderización del servidor (RSC)
520
+ unstable_setRequestLocale(locale);
521
+
522
+ // Los mensajes se cargan del lado del servidor a través de src/i18n/request.ts
523
+ // (ver documentación de next-intl). Aquí solo enviamos un subconjunto al cliente
524
+ // que es necesario para los componentes del cliente (optimización de carga).
525
+ const messages = await getMessages();
526
+ const clientMessages = pick(messages, ["common", "about"]);
527
+
528
+ return (
529
+ <html lang={locale}>
530
+ <body>
531
+ <NextIntlClientProvider locale={locale} messages={clientMessages}>
532
+ {children}
533
+ </NextIntlClientProvider>
534
+ </body>
535
+ </html>
536
+ );
537
+ }
538
+ ```
539
+
540
+ ```tsx fileName="src/app/[locale]/about/page.tsx"
541
+ import { getTranslations } from "next-intl/server";
542
+ import { ClientComponent, ServerComponent } from "@components";
543
+
544
+ export default async function LandingPage({
545
+ params,
546
+ }: {
547
+ params: { locale: string };
548
+ }) {
549
+ // Carga estrictamente del lado del servidor (no hidratado en el cliente)
550
+ const t = await getTranslations("about");
551
+
552
+ return (
553
+ <main>
554
+ <h1>{t("title")}</h1>
555
+ <ClientComponent />
556
+ <ServerComponent />
557
+ </main>
558
+ );
559
+ }
560
+ ```
561
+
562
+ </TabItem>
563
+ <TabItem label="intlayer" value="intlayer">
564
+
565
+ ```tsx fileName="intlayer.config.ts"
566
+ export default {
567
+ internationalization: {
568
+ locales: ["en", "fr", "es"],
569
+ defaultLocale: "en",
570
+ },
571
+ };
572
+ ```
573
+
574
+ ```tsx fileName="src/app/[locale]/layout.tsx"
575
+ import { getHTMLTextDir } from "intlayer";
576
+ import {
577
+ IntlayerClientProvider,
578
+ generateStaticParams,
579
+ type NextLayoutIntlayer,
580
+ } from "next-intlayer";
581
+
582
+ export const dynamic = "force-static";
583
+
584
+ const LandingLayout: NextLayoutIntlayer = async ({ children, params }) => {
585
+ const { locale } = await params;
586
+
587
+ return (
588
+ <html lang={locale} dir={getHTMLTextDir(locale)}>
589
+ <IntlayerClientProvider locale={locale}>
590
+ {children}
591
+ </IntlayerClientProvider>
592
+ </html>
593
+ );
594
+ };
595
+
596
+ export default LandingLayout;
597
+ ```
598
+
599
+ ```tsx fileName="src/app/[locale]/about/page.tsx"
600
+ import { PageContent } from "@components/PageContent";
601
+ import type { NextPageIntlayer } from "next-intlayer";
602
+ import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
603
+ import { ClientComponent, ServerComponent } from "@components";
604
+
605
+ const LandingPage: NextPageIntlayer = async ({ params }) => {
606
+ const { locale } = await params;
607
+ const { title } = useIntlayer("about", locale);
608
+
609
+ return (
610
+ <IntlayerServerProvider locale={locale}>
611
+ <main>
612
+ <h1>{title}</h1>
613
+ <ClientComponent />
614
+ <ServerComponent />
615
+ </main>
616
+ </IntlayerServerProvider>
617
+ );
618
+ };
619
+
620
+ export default LandingPage;
621
+ ```
622
+
623
+ </TabItem>
624
+ </Tab>
625
+
626
+ #### Comparación
627
+
628
+ Los tres soportan la carga de contenido y proveedores por localización.
629
+
630
+ - Con **next-intl/next-i18next**, normalmente cargas mensajes/espacios de nombres seleccionados por ruta y colocas los proveedores donde sea necesario.
631
+
632
+ - Con **Intlayer**, se añade un análisis en tiempo de compilación para inferir el uso, lo que puede reducir el cableado manual y permitir un único proveedor raíz.
633
+
634
+ Elige entre control explícito y automatización según la preferencia del equipo.
635
+
636
+ ### Uso en un componente cliente
637
+
638
+ Tomemos un ejemplo de un componente cliente que renderiza un contador.
639
+
640
+ <Tab defaultTab="next-intl" group='techno'>
641
+ <TabItem label="next-i18next" value="next-i18next">
642
+
643
+ **Traducciones (deben ser JSON reales en `public/locales/...`)**
644
+
645
+ ```json fileName="public/locales/en/about.json"
646
+ {
647
+ "counter": {
648
+ "label": "Counter",
649
+ "increment": "Increment"
650
+ }
651
+ }
652
+ ```
653
+
654
+ ```json fileName="public/locales/fr/about.json"
655
+ {
656
+ "counter": {
657
+ "label": "Compteur",
658
+ "increment": "Incrémenter"
659
+ }
660
+ }
661
+ ```
662
+
663
+ **Componente cliente**
664
+
665
+ ```tsx fileName="src/components/ClientComponentExample.tsx"
666
+ "use client";
667
+
668
+ import React, { useMemo, useState } from "react";
669
+ import { useTranslation } from "next-i18next";
670
+
671
+ const ClientComponentExample = () => {
672
+ const { t, i18n } = useTranslation("about");
673
+ const [count, setCount] = useState(0);
674
+
675
+ // next-i18next no expone useNumber; usar Intl.NumberFormat
676
+ const numberFormat = new Intl.NumberFormat(i18n.language);
677
+
678
+ return (
679
+ <div>
680
+ <p>{numberFormat.format(count)}</p>
681
+ <button
682
+ aria-label={t("counter.label")}
683
+ onClick={() => setCount((count) => count + 1)}
684
+ >
685
+ {t("counter.increment")}
686
+ </button>
687
+ </div>
688
+ );
689
+ };
690
+ ```
691
+
692
+ > No olvides agregar el espacio de nombres "about" en las serverSideTranslations de la página
693
+ > Aquí tomamos la versión de react 19.x.x, pero para versiones inferiores, necesitarás usar useMemo para almacenar la instancia del formateador ya que es una función pesada
694
+
695
+ </TabItem>
696
+ <TabItem label="next-intl" value="next-intl">
697
+
698
+ **Traducciones (misma estructura; cárgalas en los mensajes de next-intl como prefieras)**
699
+
700
+ ```json fileName="locales/en/about.json"
701
+ {
702
+ "counter": {
703
+ "label": "Counter",
704
+ "increment": "Increment"
705
+ }
706
+ }
707
+ ```
708
+
709
+ ```json fileName="locales/fr/about.json"
710
+ {
711
+ "counter": {
712
+ "label": "Compteur",
713
+ "increment": "Incrémenter"
714
+ }
715
+ }
716
+ ```
717
+
718
+ **Componente cliente**
719
+
720
+ ```tsx fileName="src/components/ClientComponentExample.tsx"
721
+ "use client";
722
+
723
+ import React, { useState } from "react";
724
+ import { useTranslations, useFormatter } from "next-intl";
725
+
726
+ const ClientComponentExample = () => {
727
+ // Alcance directamente al objeto anidado
728
+ const t = useTranslations("about.counter");
729
+ const format = useFormatter();
730
+ const [count, setCount] = useState(0);
731
+
732
+ return (
733
+ <div>
734
+ <p>{format.number(count)}</p>
735
+ <button
736
+ aria-label={t("label")}
737
+ onClick={() => setCount((count) => count + 1)}
738
+ >
739
+ {t("increment")}
740
+ </button>
741
+ </div>
742
+ );
743
+ };
744
+ ```
745
+
746
+ > No olvides añadir el mensaje "about" en el mensaje cliente de la página
747
+
748
+ </TabItem>
749
+ <TabItem label="intlayer" value="intlayer">
750
+
751
+ **Contenido**
752
+
753
+ ```ts fileName="src/components/ClientComponentExample/index.content.ts"
754
+ import { t, type Dictionary } from "intlayer";
755
+
756
+ const counterContent = {
757
+ key: "counter",
758
+ content: {
759
+ label: t({ es: "Contador", en: "Counter", fr: "Compteur" }),
760
+ increment: t({ es: "Incrementar", en: "Increment", fr: "Incrémenter" }),
761
+ },
762
+ } satisfies Dictionary;
763
+
764
+ export default counterContent;
765
+ ```
766
+
767
+ **Componente cliente**
768
+
769
+ ```tsx fileName="src/components/ClientComponentExample/index.tsx"
770
+ "use client";
771
+
772
+ import React, { useState } from "react";
773
+ import { useNumber, useIntlayer } from "next-intlayer";
774
+
775
+ const ClientComponentExample = () => {
776
+ const [count, setCount] = useState(0);
777
+ const { label, increment } = useIntlayer("counter"); // devuelve cadenas
778
+ const { number } = useNumber();
779
+
780
+ return (
781
+ <div>
782
+ <p>{number(count)}</p>
783
+ <button aria-label={label} onClick={() => setCount((count) => count + 1)}>
784
+ {increment}
785
+ </button>
786
+ </div>
787
+ );
788
+ };
789
+ ```
790
+
791
+ </TabItem>
792
+ </Tab>
793
+
794
+ #### Comparación
795
+
796
+ - **Formato de números**
797
+ - **next-i18next**: no tiene `useNumber`; usa `Intl.NumberFormat` (o i18next-icu).
798
+ - **next-intl**: `useFormatter().number(value)`.
799
+ - **Intlayer**: `useNumber()` incorporado.
800
+
801
+ - **Claves**
802
+ - Mantén una estructura anidada (`about.counter.label`) y delimita tu hook en consecuencia (`useTranslation("about")` + `t("counter.label")` o `useTranslations("about.counter")` + `t("label")`).
803
+
804
+ - **Ubicación de archivos**
805
+ - **next-i18next** espera JSON en `public/locales/{lng}/{ns}.json`.
806
+ - **next-intl** es flexible; carga mensajes como configures.
807
+ - **Intlayer** almacena contenido en diccionarios TS/JS y resuelve por clave.
808
+
135
809
  ---
136
810
 
137
- ## ¿Cuándo elegir cuál?
811
+ ### Uso en un componente de servidor
812
+
813
+ Tomaremos el caso de un componente de interfaz de usuario (UI). Este componente es un componente del servidor y debe poder insertarse como hijo de un componente cliente. (página (componente servidor) -> componente cliente -> componente servidor). Como este componente puede insertarse como hijo de un componente cliente, no puede ser asíncrono.
814
+
815
+ <Tab defaultTab="next-intl" group='techno'>
816
+ <TabItem label="next-i18next" value="next-i18next">
817
+
818
+ ```tsx fileName="src/pages/about.tsx"
819
+ import type { GetStaticProps } from "next";
820
+ import { useTranslation } from "next-i18next";
821
+
822
+ type ServerComponentProps = {
823
+ count: number;
824
+ };
825
+
826
+ const ServerComponent = ({ count }: ServerComponentProps) => {
827
+ const { t, i18n } = useTranslation("about");
828
+ const formatted = new Intl.NumberFormat(i18n.language).format(count);
829
+
830
+ return (
831
+ <div>
832
+ <p>{formatted}</p>
833
+ <button aria-label={t("counter.label")}>{t("counter.increment")}</button>
834
+ </div>
835
+ );
836
+ };
837
+ ```
838
+
839
+ </TabItem>
840
+ <TabItem label="next-intl" value="next-intl">
841
+
842
+ ```tsx fileName="src/components/ServerComponent.tsx"
843
+ type ServerComponentProps = {
844
+ count: number;
845
+ t: (key: string) => string;
846
+ };
847
+
848
+ const ServerComponent = ({ t, count }: ServerComponentProps) => {
849
+ const formatted = new Intl.NumberFormat(i18n.language).format(count);
850
+
851
+ return (
852
+ <div>
853
+ <p>{formatted}</p>
854
+ <button aria-label={t("label")}>{t("increment")}</button>
855
+ </div>
856
+ );
857
+ };
858
+ ```
859
+
860
+ > Como el componente del servidor no puede ser async, necesitas pasar las traducciones y la función formateadora como props.
861
+ >
862
+ > - `const t = await getTranslations("about.counter");`
863
+ > - `const format = await getFormatter();`
864
+
865
+ </TabItem>
866
+ <TabItem label="intlayer" value="intlayer">
867
+
868
+ ```tsx fileName="src/components/ServerComponent.tsx"
869
+ import { useIntlayer, useNumber } from "next-intlayer/server";
870
+
871
+ const ServerComponent = ({ count }: { count: number }) => {
872
+ const { label, increment } = useIntlayer("counter");
873
+ const { number } = useNumber();
874
+
875
+ return (
876
+ <div>
877
+ <p>{number(count)}</p>
878
+ <button aria-label={label}>{increment}</button>
879
+ </div>
880
+ );
881
+ };
882
+ ```
883
+
884
+ </TabItem>
885
+ </Tab>
886
+
887
+ > Intlayer expone hooks **seguros para el servidor** a través de `next-intlayer/server`. Para funcionar, `useIntlayer` y `useNumber` utilizan una sintaxis similar a los hooks del cliente, pero dependen internamente del contexto del servidor (`IntlayerServerProvider`).
888
+
889
+ ### Metadatos / Sitemap / Robots
890
+
891
+ Traducir contenido es genial. Pero la gente suele olvidar que el objetivo principal de la internacionalización es hacer que tu sitio web sea más visible para el mundo. La i18n es una palanca increíble para mejorar la visibilidad de tu sitio web.
892
+
893
+ Aquí tienes una lista de buenas prácticas relacionadas con el SEO multilingüe.
138
894
 
139
- - **Elige next-intl** si quieres una solución **mínima**, te sientes cómodo con catálogos centralizados y tu aplicación es de tamaño **pequeño a mediano**.
140
- - **Elige next-i18next** si necesitas el **ecosistema de plugins de i18next** (por ejemplo, reglas avanzadas ICU mediante plugins) y tu equipo ya conoce i18next, aceptando **más configuración** para mayor flexibilidad.
141
- - **Elige Intlayer** si valoras el **contenido con alcance por componente**, **TypeScript estricto**, **garantías en tiempo de compilación**, **tree-shaking** y herramientas integradas para enrutamiento/SEO/editor - especialmente para **Next.js App Router** y **bases de código grandes y modulares**.
895
+ - establecer etiquetas meta hreflang en la etiqueta `<head>`
896
+ > Ayuda a los motores de búsqueda a entender qué idiomas están disponibles en la página
897
+ - listar todas las traducciones de páginas en el sitemap.xml usando el esquema XML `http://www.w3.org/1999/xhtml`
898
+ >
899
+ - no olvidar excluir las páginas con prefijo del robots.txt (por ejemplo, `/dashboard`, y `/fr/dashboard`, `/es/dashboard`)
900
+ >
901
+ - usar un componente Link personalizado para redirigir a la página más localizada (por ejemplo, en francés `<a href="/fr/about">A propos</a>`)
902
+ >
903
+
904
+ Los desarrolladores a menudo olvidan referenciar correctamente sus páginas entre los distintos locales.
905
+
906
+ <Tab defaultTab="next-intl" group='techno'>
907
+
908
+ <TabItem label="next-i18next" value="next-i18next">
909
+
910
+ ```ts fileName="i18n.config.ts"
911
+ export const locales = ["en", "fr"] as const;
912
+ export type Locale = (typeof locales)[number];
913
+ export const defaultLocale: Locale = "en";
914
+
915
+ export function localizedPath(locale: string, path: string) {
916
+ return locale === defaultLocale ? path : "/" + locale + path;
917
+ }
918
+
919
+ const ORIGIN = "https://example.com";
920
+ export function abs(locale: string, path: string) {
921
+ return ORIGIN + localizedPath(locale, path);
922
+ }
923
+ ```
924
+
925
+ ```tsx fileName="src/app/[locale]/about/layout.tsx"
926
+ import type { Metadata } from "next";
927
+ import { locales, defaultLocale, localizedPath } from "@/i18n.config";
928
+
929
+ export async function generateMetadata({
930
+ params,
931
+ }: {
932
+ params: { locale: string };
933
+ }): Promise<Metadata> {
934
+ const { locale } = params;
935
+
936
+ // Importa dinámicamente el archivo JSON correcto
937
+ const messages = (
938
+ await import("@/../public/locales/" + locale + "/about.json")
939
+ ).default;
940
+
941
+ const languages = Object.fromEntries(
942
+ locales.map((locale) => [locale, localizedPath(locale, "/about")])
943
+ );
944
+
945
+ return {
946
+ title: messages.title,
947
+ description: messages.description,
948
+ alternates: {
949
+ canonical: localizedPath(locale, "/about"),
950
+ languages: { ...languages, "x-default": "/about" },
951
+ },
952
+ };
953
+ }
954
+
955
+ export default async function AboutPage() {
956
+ return <h1>Acerca de</h1>;
957
+ }
958
+ ```
959
+
960
+ ```ts fileName="src/app/sitemap.ts"
961
+ import type { MetadataRoute } from "next";
962
+ import { locales, defaultLocale, abs } from "@/i18n.config";
963
+
964
+ export default function sitemap(): MetadataRoute.Sitemap {
965
+ const languages = Object.fromEntries(
966
+ locales.map((locale) => [locale, abs(locale, "/about")])
967
+ );
968
+ return [
969
+ {
970
+ url: abs(defaultLocale, "/about"),
971
+ lastModified: new Date(),
972
+ changeFrequency: "monthly",
973
+ priority: 0.7,
974
+ alternates: { languages },
975
+ },
976
+ ];
977
+ }
978
+ ```
979
+
980
+ ```ts fileName="src/app/robots.ts"
981
+ import type { MetadataRoute } from "next";
982
+ import { locales, defaultLocale, localizedPath } from "@/i18n.config";
983
+
984
+ const ORIGIN = "https://example.com";
985
+
986
+ const expandAllLocales = (path: string) => [
987
+ localizedPath(defaultLocale, path),
988
+ ...locales
989
+ .filter((locale) => locale !== defaultLocale)
990
+ .map((locale) => localizedPath(locale, path)),
991
+ ];
992
+
993
+ export default function robots(): MetadataRoute.Robots {
994
+ const disallow = [
995
+ ...expandAllLocales("/dashboard"),
996
+ ...expandAllLocales("/admin"),
997
+ ];
998
+
999
+ return {
1000
+ rules: { userAgent: "*", allow: ["/"], disallow },
1001
+ host: ORIGIN,
1002
+ sitemap: ORIGIN + "/sitemap.xml",
1003
+ };
1004
+ }
1005
+ ```
1006
+
1007
+ </TabItem>
1008
+ <TabItem label="next-intl" value="next-intl">
1009
+
1010
+ ```tsx fileName="src/app/[locale]/about/layout.tsx"
1011
+ import type { Metadata } from "next";
1012
+ import { locales, defaultLocale } from "@/i18n";
1013
+ import { getTranslations } from "next-intl/server";
1014
+
1015
+ function localizedPath(locale: string, path: string) {
1016
+ return locale === defaultLocale ? path : "/" + locale + path;
1017
+ }
1018
+
1019
+ export async function generateMetadata({
1020
+ params,
1021
+ }: {
1022
+ params: { locale: string };
1023
+ }): Promise<Metadata> {
1024
+ const { locale } = params;
1025
+ const t = await getTranslations({ locale, namespace: "about" });
1026
+
1027
+ const url = "/about";
1028
+ const languages = Object.fromEntries(
1029
+ locales.map((locale) => [locale, localizedPath(locale, url)])
1030
+ );
1031
+
1032
+ return {
1033
+ title: t("title"),
1034
+ description: t("description"),
1035
+ alternates: {
1036
+ canonical: localizedPath(locale, url),
1037
+ languages: { ...languages, "x-default": url },
1038
+ },
1039
+ };
1040
+ }
1041
+
1042
+ // ... Resto del código de la página
1043
+ ```
1044
+
1045
+ ```tsx fileName="src/app/sitemap.ts"
1046
+ import type { MetadataRoute } from "next";
1047
+ import { locales, defaultLocale } from "@/i18n";
1048
+
1049
+ const origin = "https://example.com";
1050
+
1051
+ const formatterLocalizedPath = (locale: string, path: string) =>
1052
+ locale === defaultLocale ? origin + path : origin + "/" + locale + path;
1053
+
1054
+ export default function sitemap(): MetadataRoute.Sitemap {
1055
+ const aboutLanguages = Object.fromEntries(
1056
+ locales.map((l) => [l, formatterLocalizedPath(l, "/about")])
1057
+ );
1058
+
1059
+ return [
1060
+ {
1061
+ url: formatterLocalizedPath(defaultLocale, "/about"),
1062
+ lastModified: new Date(),
1063
+ changeFrequency: "monthly",
1064
+ priority: 0.7,
1065
+ alternates: { languages: aboutLanguages },
1066
+ },
1067
+ ];
1068
+ }
1069
+ ```
1070
+
1071
+ ```tsx fileName="src/app/robots.ts"
1072
+ import type { MetadataRoute } from "next";
1073
+ import { locales, defaultLocale } from "@/i18n";
1074
+
1075
+ const origin = "https://example.com";
1076
+ const withAllLocales = (path: string) => [
1077
+ path,
1078
+ ...locales
1079
+ .filter((locale) => locale !== defaultLocale)
1080
+ .map((locale) => "/" + locale + path),
1081
+ ];
1082
+
1083
+ export default function robots(): MetadataRoute.Robots {
1084
+ const disallow = [
1085
+ ...withAllLocales("/dashboard"),
1086
+ ...withAllLocales("/admin"),
1087
+ ];
1088
+
1089
+ return {
1090
+ rules: { userAgent: "*", allow: ["/"], disallow },
1091
+ host: origin,
1092
+ sitemap: origin + "/sitemap.xml",
1093
+ };
1094
+ }
1095
+ ```
1096
+
1097
+ </TabItem>
1098
+ <TabItem label="intlayer" value="intlayer">
1099
+
1100
+ ```typescript fileName="src/app/[locale]/about/layout.tsx"
1101
+ import { getIntlayer, getMultilingualUrls } from "intlayer";
1102
+ import type { Metadata } from "next";
1103
+ import type { LocalPromiseParams } from "next-intlayer";
1104
+
1105
+ export const generateMetadata = async ({
1106
+ params,
1107
+ }: LocalPromiseParams): Promise<Metadata> => {
1108
+ const { locale } = await params;
1109
+
1110
+ const metadata = getIntlayer("page-metadata", locale);
1111
+
1112
+ const multilingualUrls = getMultilingualUrls("/about");
1113
+
1114
+ return {
1115
+ ...metadata,
1116
+ alternates: {
1117
+ canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
1118
+ languages: { ...multilingualUrls, "x-default": "/about" },
1119
+ },
1120
+ };
1121
+ };
1122
+
1123
+ // ... Resto del código de la página
1124
+ ```
1125
+
1126
+ ```tsx fileName="src/app/sitemap.ts"
1127
+ import { getMultilingualUrls } from "intlayer";
1128
+ import type { MetadataRoute } from "next";
1129
+
1130
+ const sitemap = (): MetadataRoute.Sitemap => [
1131
+ {
1132
+ url: "https://example.com/about",
1133
+ alternates: {
1134
+ languages: { ...getMultilingualUrls("https://example.com/about") },
1135
+ },
1136
+ },
1137
+ ];
1138
+ ```
1139
+
1140
+ ```tsx fileName="src/app/robots.ts"
1141
+ import { getMultilingualUrls } from "intlayer";
1142
+ import type { MetadataRoute } from "next";
1143
+
1144
+ // Obtiene todas las URLs multilingües a partir de una lista de URLs
1145
+ const getAllMultilingualUrls = (urls: string[]) =>
1146
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
1147
+
1148
+ // Configuración del archivo robots.txt con reglas para los rastreadores
1149
+ const robots = (): MetadataRoute.Robots => ({
1150
+ rules: {
1151
+ userAgent: "*",
1152
+ allow: ["/"],
1153
+ disallow: getAllMultilingualUrls(["/dashboard"]),
1154
+ },
1155
+ host: "https://example.com",
1156
+ sitemap: "https://example.com/sitemap.xml",
1157
+ });
1158
+
1159
+ export default robots;
1160
+ ```
1161
+
1162
+ </TabItem>
1163
+ </Tab>
1164
+
1165
+ > Intlayer proporciona una función `getMultilingualUrls` para generar URLs multilingües para tu sitemap.
1166
+
1167
+ ---
142
1168
 
143
1169
  ---
144
1170
 
145
- ## Notas prácticas de migración (next-intl / next-i18next → Intlayer)
1171
+ ## Y el ganador es…
1172
+
1173
+ No es sencillo. Cada opción tiene sus ventajas y desventajas. Así es como lo veo:
1174
+
1175
+ <Columns>
1176
+ <Column>
1177
+
1178
+ **next-intl**
1179
+
1180
+ - la más simple, ligera, con menos decisiones impuestas. Si quieres una solución **mínima**, te sientes cómodo con catálogos centralizados y tu aplicación es de tamaño **pequeño a mediano**.
1181
+
1182
+ </Column>
1183
+ <Column>
1184
+
1185
+ **next-i18next**
1186
+
1187
+ - madura, llena de funciones, con muchos plugins comunitarios, pero con un costo de configuración más alto. Si necesitas el **ecosistema de plugins de i18next** (por ejemplo, reglas ICU avanzadas mediante plugins) y tu equipo ya conoce i18next, aceptando **más configuración** para mayor flexibilidad.
1188
+
1189
+ </Column>
1190
+ <Column>
1191
+
1192
+ **Intlayer**
1193
+
1194
+ - construido para Next.js moderno, con contenido modular, seguridad de tipos, herramientas y menos código repetitivo. Si valoras el **contenido con alcance de componente**, **TypeScript estricto**, **garantías en tiempo de compilación**, **tree-shaking**, y herramientas integradas para enrutamiento/SEO/editor - especialmente para **Next.js App Router**, sistemas de diseño y **bases de código grandes y modulares**.
1195
+
1196
+ </Column>
1197
+ </Columns>
1198
+
1199
+ Si prefieres una configuración mínima y aceptas algo de cableado manual, next-intl es una buena opción. Si necesitas todas las funciones y no te importa la complejidad, next-i18next funciona. Pero si quieres una solución moderna, escalable y modular con herramientas integradas, Intlayer busca ofrecerte eso listo para usar.
1200
+
1201
+ > **Alternativa para equipos empresariales**: Si necesita una solución bien probada que funcione perfectamente con plataformas de localización establecidas como **Crowdin**, **Phrase** u otros sistemas profesionales de gestión de traducciones, considere **next-intl** o **next-i18next** por su ecosistema maduro e integraciones comprobadas.
1202
+
1203
+ > **Hoja de ruta futura**: Intlayer también planea desarrollar plugins que funcionen sobre las soluciones **i18next** y **next-intl**. Esto le brindará las ventajas de Intlayer para automatización, sintaxis y gestión de contenido, manteniendo al mismo tiempo la seguridad y estabilidad que proporcionan estas soluciones establecidas en el código de su aplicación.
1204
+
1205
+ ## Estrellas en GitHub
1206
+
1207
+ Las estrellas de GitHub son un indicador fuerte de la popularidad de un proyecto, la confianza de la comunidad y la relevancia a largo plazo. Aunque no son una medida directa de la calidad técnica, reflejan cuántos desarrolladores encuentran útil el proyecto, siguen su progreso y probablemente lo adopten. Para estimar el valor de un proyecto, las estrellas ayudan a comparar la tracción entre alternativas y proporcionan información sobre el crecimiento del ecosistema.
146
1208
 
147
- - **Comience por función**: Mueva una ruta o componente a la vez a **diccionarios locales**.
148
- - **Mantenga los catálogos antiguos en paralelo**: Haga una transición gradual durante la migración; evite un cambio radical.
149
- - **Active las comprobaciones estrictas**: Permita que la detección en tiempo de compilación revele las brechas temprano.
150
- - **Adopte middleware y ayudantes**: Estandarice la detección de locales y las etiquetas SEO en todo el sitio.
151
- - **Mida los paquetes**: Espere **reducciones en el tamaño del paquete** a medida que se elimina contenido no utilizado.
1209
+ [![Gráfico de Historial de Estrellas](https://api.star-history.com/svg?repos=i18next/next-i18next&repos=amannn/next-intl&repos=aymericzip/intlayer&type=Date)](https://www.star-history.com/#i18next/next-i18next&amannn/next-intl&aymericzip/intlayer)
152
1210
 
153
1211
  ---
154
1212
 
155
1213
  ## Conclusión
156
1214
 
157
- Las tres bibliotecas tienen éxito en la localización básica. La diferencia está en **cuánto trabajo debe hacer** para lograr una configuración robusta y escalable en **Next.js moderno**:
1215
+ Las tres bibliotecas tienen éxito en la localización básica. La diferencia es **cuánto trabajo debes hacer** para lograr una configuración robusta y escalable en **Next.js moderno**:
158
1216
 
159
- - Con **Intlayer**, el **contenido modular**, **TS estricto**, **seguridad en tiempo de compilación**, **paquetes optimizados (tree-shaken)** y **herramientas de App Router + SEO de primera clase** son **valores predeterminados**, no tareas.
1217
+ - Con **Intlayer**, el **contenido modular**, **TS estricto**, **seguridad en tiempo de compilación**, **paquetes optimizados por tree-shaking** y **herramientas de App Router + SEO de primera clase** son **predeterminados**, no tareas.
160
1218
  - Si tu equipo valora la **mantenibilidad y velocidad** en una aplicación multilingüe impulsada por componentes, Intlayer ofrece la experiencia **más completa** hoy en día.
161
1219
 
162
1220
  Consulta el documento ['¿Por qué Intlayer?'](https://intlayer.org/doc/why) para más detalles.