La internacionalización es un elemento esencial en el desarrollo de aplicaciones web, ya que permite llegar a audiencias globales y ofrecer una experiencia personalizada a usuarios de diferentes partes del mundo.
En este artículo, exploraremos cómo integrar I18Next a un proyecto Next.js 13 con app directory, gestionando de manera eficiente la traducción y adaptación de contenidos y elementos de la interfaz de usuario a varios idiomas.
Pero si necesitas agregar internacionalización a un proyecto sencillo que no requiere de funciones avanzadas, como la interpolación que ofrece I18Next, puedes intentar con está guía Internacionalización en NextJs-13 que te permitirá obtener un código más simple y fácil de mantener.
Crear el proyecto
Empecemos creando un proyecto con Next.js 13. Si ya tienes uno, puedes saltarte este
paso.
npx create-next-app@latest// What is your project named? … next13-internationalization// Would you like to use TypeScript? … No / Yes ✅// Would you like to use ESLint? … No / Yes ✅// Would you like to use Tailwind CSS? … No / Yes ❌// Would you like to use src/ directory? … No / Yes ✅// Would you like to use App Router? (recommended) … No / Yes ✅// Would you like to customize the default import alias? … No / Yes ❌cd next13-internationalization
Read-only
Tests
Instalamos I18Next y creamos la nueva estructura
npm install i18next
Read-only
Tests
Crearemos la nueva estructura del proyecto donde usaremos el idioma para crear rutas dinámicas.
Estas rutas se crean agregando segmentos dinámicos a partir de datos dinámicos que se completan en el momento de la solicitud o se renderizan previamente en el momento de la compilación.
Este segmento dinámico lo creamos colocando el nombre de la carpeta entre corchetes, que en nuestro caso será [lng], y lo tendremos disponible como parámetros en layout, page, route y generateMetadata.
En este proyecto usaré los idiomas español e inglés, pero puedes agregar tantos como quieras.
La ruta de nuestro proyecto se verá cómo:
Ruta=>app/[lng]/page.tsxURL=>http://localhost:3000/[lng] => [lng] será reemplazado por el lenguaje que usemos.por ejemplo http://localhost:3000/es o http://localhost:3000/en donde 'es' y 'en' son nuestros datos dinámicos para la url en español e inglés respectivamente.Parámetros=>{lng:['es','en']}
Ahora agregamos la lista de idiomas para que esté disponible en el html en el archivo layout.tsx:
// src/app/[lng]/layout.tsximport{dir}from'i18next';// i18n para agregar el lenguaje al htmlconstlanguages = ['en','de'];// define los lenguajes que necesitesexportasyncfunctiongenerateStaticParams(){returnlanguages.map((lng)=>({lng}));}exportdefaultfunctionRootLayout({children,params:{lng},// lng estará disponible por parámetro}:{children: React.ReactNode,params:{lng: string,},}){return(// pasamos lng<htmllang={lng}dir={dir(lng)}><bodyclassName={inter.className}>{children}</body></html>);}
Read-only
Tests
Ahora nuestra URL funcionará utilizando /es o /en que son los lenguajes que definimos en el archivo layout.tsx, ejemplo http://localhost:3000/es.
❗️WARNING❗️Nos encontraremos con un error 404 si utilizamos la ruta sin ese parámetro (http://localhost:3000), corrigamos eso en el siguiente paso.
Detectar el idioma
Primero instalamos la dependencia accept-language:
npm install accept-language --save
Read-only
Tests
Esta dependencia analiza la cabecera "Accept-Language" de las solicitudes HTTP para obtener información sobre las preferencias de idioma del cliente y luego ayuda a seleccionar la respuesta adecuada en el idioma preferido del usuario.
Ahora agregamos los archivos settings.ts dentro de una carpeta i18n y middleware.ts en la dentro de src:
Ahora si no agregamos el parámetro a la URL, nos redireccionará al idioma de nuestro navegador.
En mi caso http://localhost:3000/ me redirecciona a http://localhost:3000/es.
Probemos cambiar nuestro parámetro a 'en' y navegar.
Esto guardará la cookie i18next que definimos con el valor del parámetro. Luego, si borramos el parámetro de la URL nos redirecciona al último lenguaje utilizado, el guardado en la cookie i18next: en.
Crear los diccionarios
El diccionario es la base de nuestra internacionalización que nos permite ofrecer la experiencia de usuario en diferentes idiomas.
Dentro de la carpeta i18n que creamos, agregamos una carpeta locales, donde organizaremos los archivos de internacionalización de una manera estructurada. Creamos carpetas dentro de locales con el nombre de un idioma específico, como 'es' para español, 'en' para inglés, como definimos en languages en el archivo settings.ts.
Dentro de cada una de estas carpetas de idioma, crearemos los archivos .json que contendrán las traducciones para cada sección, como 'home' y 'second-page'. Todos los json deben respetar la misma estructura en cada carpeta.
// la carpeta `en` y `es` serán el diccionario para las traducciones en inglés y español respectivamente.// Dentro tendremos las secciones separadas por archivos json:
.src
|__ app
|__i18n
|__settings.ts
|__locales
|__en
|__common.json
|__home.json
|__second-page.json
|__es
|__common.json
|__home.json
|__second-page.json
Read-only
Tests
I18Next en Server Components
Preparemos el hook useTranslation para las traducciones en Server Components.
El primero es una integración de React con I18Next, permite la traducción y localización de aplicaciones React de manera sencilla. Y el segundo paquete ayuda a transformar recursos a un backend i18next.
En el archivo settings.ts agregamos las opciones de configuración,
donde defaultNS define qué archivo .json del diccionario que creamos en locales se usará por defecto cuando no especificamos ninguno:
// src/app/i18n/settings.tsexportconstfallbackLng = 'es';exportconstlanguages = [fallbackLng,'en'];exportconstdefaultNS = 'common';// nombre del archivo .json de locales que usaremos por defectoexportfunctiongetOptions(lng = fallbackLng,ns: string | string[] = defaultNS){return{supportedLngs:languages,fallbackLng,lng,fallbackNS:defaultNS,defaultNS,ns,};}
Read-only
Tests
Ahora agregamos un archivo index.ts dentro de la carpeta i18n, aquí crearemos el hook useTranslation.
Estaremos creando una nueva instancia de i18n en cada llamado al hook useTranslation, que recibe el lenguaje que estamos requiriendo mostrar (lng) y el nombre del archivo .json que queremos usar (ns). Este último lo definimos en locationNS al final del index.ts, que no es más que una lista de los nombres de los archivos .json que tienen las traducciones (Esto nos evitará errores de tipeo).
Ahora usaremos el hook useTranslation en la page Home para obtener las traducciones y configuraciones necesarias para mostrar contenido multilingüe en nuestra aplicación. Para asegurarnos de que la aplicación funcione de manera fluida y que no se bloquee mientras espera que las traducciones se carguen, utilizamos async/await al llamar a useTranslation.
El hook useTranslation requiere dos parámetros:
El lenguaje: estará disponible por parámetros.
La sección: la importamos desde la definición de locationNS en el archivo i18n/index.ts para evitar errores tipográficos.
Obtendremos la función t que, con la key definida en el archivo json, nos devolverá el string que queremos mostrar:
// src/app/[lng]/page.tsximport{locationNS,useTranslation}from'../i18n';importstylesfrom'./page.module.css';constHome = async({params:{lng}}:{params:{lng: string }})=>{const{t} = awaituseTranslation(lng,locationNS.HOME);// Cambiar a la sección necesariareturn(<mainclassName={styles.main}><divclassName={styles.description}><p>{t('note')}</p></div></main>);};exportdefaultHome;// http://localhost:3000 || http://localhost:3000/es || http://localhost:3000/en
Read-only
Tests
// src/app/i18n.locales/es/home.json{"note":"Esta es la página de inicio"}// src/app/i18n.locales/en/home.json{"note":"This is the home page"}
Read-only
Tests
I18Next en Client Components
Usar nuestro hook useTranslation con async/await en un client component nos dará este error:
Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding 'use client' to a module that was originally written for the server.
Preparemos useTranslation para las traducciones en Client Components.
Instalamos el siguiente paquete que nos servirá para detectar automáticamente el idioma preferido del usuario en su navegador:
npm install i18next-browser-languagedetector
Read-only
Tests
Creamos un archivo client.ts dentro de la carpeta i18n, donde crearemos nuevamente useTranslation pero para las traducciones en client component.
// src/app/i18n.locales/es/second-page.json{"note":"Esta es la segunda página""action":"cambiar color"}// src/app/i18n.locales/en/home.json{"note":"This is the second page""action":"Change color"}
Read-only
Tests
Crear un switch de idioma
Para cambiar de idioma, lo que hacemos es usar la ruta dinámica que creamos con la carpeta [lng]. Entonces debemos navegar a la ruta del idioma que queramos.
El componente será un client component porque usaremos el hook usePathname.
Este hook (usePathname) es necesario para hacer la navegación en caso de que tengamos una url con más partes que el dominio, por ejemplo /second-page que tenemos definida en esta estructura. Tomamos el path completo y luego con una expresión regular, reemplazamos el idioma por el que elegimos en el switch.
En langRegex modificamos nuestro array de idiomas definido en settings.ts para usarlo en la expresión regular y no tener que agregar manualmente al switch algún idioma extra.
En el atributo href del elemento Link, sustituimos el idioma actual en la ruta de acceso (pathname) con el idioma que hemos seleccionado en el switch. Esto nos permite navegar a la misma página con el nuevo idioma elegido.