Añadir internacionalización por path con i18next en React y Next.js Server Side (SSR)

Tiempo de lectura: 4 minutos

Hoy vamos a aprender cómo podemos añadir internacionalización Server Side para que directamente nuestro servidor de Next.js nos devuelva las páginas traducidas para beneficiar al SEO.

Lo primero que tenemos que hacer es instalar las dependencias necesarias:

npm install i18next next-i18next @types/i18next @types/react-i18next --save

Después vamos a crear un archivo de configuración next-i18next.config.js en la raíz del proyecto:

module.exports = {
  debug: process.env.NODE_ENV === 'development',
  i18n: {
    locales: ['es', 'en'],
    defaultLocale: 'es',
  },
};

En mi caso voy a traducir al español y al inglés. Por defecto he seleccionado el idioma español.

Ahora vamos a importar la configuración en nuestro archivo next.config.msj, añadimos:

/** @type {import('next').NextConfig} */
import pkg from './next-i18next.config.js';
const { i18n } = pkg;
const nextConfig = {
  i18n,
};

export default nextConfig;

Bien, ahora tenemos que crear los ficheros de traducciones.

Para ello vamos a public y creamos una carpeta llamada locales, dentro creamos dos carpetas, una llamada es/ y otra llamada en/:

Aquí dentro vamos a crear nuestros ficheros .json de traducciones. Vamos a crear uno llamado common.json (siempre tenemos que tener creado common.json ya que es el de por defecto) y lo creamos tanto en es/ como en en/.

Contenido es/common.json

{
    "title_app": "Mi prueba con i18next",
  }

Contenido en/common.json

{
    "title_app": "My test with i18next",
  }

Ahora tenemos que ir o crear un archivo _app.tsx dentro de pages/ (aquí te explico cómo crearlo) y añadimos:

import { appWithTranslation } from 'next-i18next';

function MyApp({ Component, pageProps }: AppProps) {
  .....
}

export default appWithTranslation(MyApp);

Debemos añadir el import y el export default proporcionados.

Pare crear el redireccionamiento de idioma de página por path vamos a crear un middleware.tsx este archivo (aquí te explico qué es un middleware en Next.js)

import { NextRequest, NextResponse } from 'next/server'

const PUBLIC_FILE = /\.(.*)$/

export async function middleware(req: NextRequest) {
  if (
    req.nextUrl.pathname.startsWith('/_next') ||
    req.nextUrl.pathname.includes('/api/') ||
    PUBLIC_FILE.test(req.nextUrl.pathname)
  ) {
    return
  }

  if (req.nextUrl.locale === 'default') {
    const locale = req.cookies.get('NEXT_LOCALE')?.value || 'es'

    // Si el locale es 'es', no redirigir
    if (locale === 'es') {
      return
    }

    return NextResponse.redirect(
      new URL(`/${locale}${req.nextUrl.pathname}${req.nextUrl.search}`, req.url)
    )
  }
}

En este archivo he indicado que redireccione todas las llamadas sin interferir en el enrutamiento inicial de Next.js incluyendo o no el path del idioma /en o /es.

He forzado que por defecto no me incluya el path para el idioma defecto (es) de esta forma las traducciones se comportarán:

misitio.web/ -> Español

misitio.web/en -> Inglés

Ahora nos falta configurar cada pantalla dónde queramos aplicar las traducciones Server Side Rendering (SSR)

Vamos por ejemplo a index.tsx y añadimos:

Importamos i18next:

import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

Añadimos el hook de traducción de i18next:

const Home: React.FC<HomeProps> = ({ articles }) => {

  const { t } = useTranslation();

....

Para usar la traducción pondremos:

        {t('common:title_app')}

Indicamos common: para hacer referencia al archivo en el que está la traducción.

Ahora podremos pasar a la parte del renderizado en la parte del servidor, ya que tal como está solo va a traducir por lado de cliente.

Vamos o creamos la función export async function getServerSideProps(context: any) {

Esta función aplica en renderizado Server Side.

Añadimos:

  let { locale } = context;

  // Si locale no está definido, establecer un valor predeterminado
  if (!locale) {
    locale = 'es'; // Reemplaza 'es' con tu localización predeterminada
  }

  return {
      props: {
        ...(await serverSideTranslations(locale, ['common'])),
      },
    };

Obtenemos el locale, ya que puede detectar por defecto la traducción o bien del path o bien del propio navegador.

Indico que si no logra obtener el locale, use «es» por defecto.

Finalmente, devuelvo serverSideTranslations al componente de React. Aquí incluyo los archivos que voy a necesitar, en mi caso es solo common.json si queremos añadir más sería así:

        ...(await serverSideTranslations(locale, ['common', "traducciones_2" , "traducciones_3"])),

Si queremos traducir dentro del propio getServerSideProps tendremos que hacer lo siguiente:

  let { locale } = context;

//obtenemos la carpeta de traducciones:
const translations = (await serverSideTranslations(locale, ['common'])) as SSRConfig & { [key: string]: any }; 

//Seleccionamos la clave que queremos utilizar
const textoTraducido = translations._nextI18Next && translations._nextI18Next.initialI18nStore[locale] && translations._nextI18Next.initialI18nStore[locale].common? translations._nextI18Next.initialI18nStore[locale].common.title_app : '',

Y fin, ya podemos comprobar que funciona.

Importante: No podemos utilizar en varios componentes anidados la función …(await serverSideTranslations(locale, [‘common’])), lo que debemos hacer es compartir la variable t entre todos los componentes desde el principal:

const Home: React.FC<HomeProps> = ({ articles, pathActual }) => {

  const { t } = useTranslation();

...

return (
    
     <TituloPrincipalH1 texto={t(`common:titulo`)} />
     <Lista elements={elements} onClick={() => { loadMore}} t={t} />
       
  );
  1. Podemos pasar el contenido de la t directamente:
 <TituloPrincipalH1 texto={t(`common:titulo`)} />

2. Podemos pasar la función t directamente:

  <Lista elements={elements} onClick={() => { loadMore}} t={t} />

*Para importarla en el otro componente

interface Props{
  elements: number[]
  t: TFunction;
}

const Lista: React.FC<Props> = ({ elements, t }) => {

Extra: componente para cambiar entre idiomas por path.

LanguajeSwitcher.tsx:

import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'

const LanguageSwitcher = () => {
    const router = useRouter()
    const [currentLanguage, setCurrentLanguage] = useState(router.locale)

    useEffect(() => {
        setCurrentLanguage(router.locale)
    }, [router.locale])

    const getLanguageLink = (lang: string) => {
        const currentPath = router.asPath
        return lang === 'es' ? currentPath.replace(/^\/en/, '') : `/en${currentPath}`
    }

    return (
        <div>
            <Link href={getLanguageLink('es')} locale={false}>
                <div style={{ color: currentLanguage === 'es' ? 'grey' : 'black' }}>Español</div>
            </Link>
            <Link href={getLanguageLink('en')} locale={false}>
                <div style={{ color: currentLanguage === 'en' ? 'grey' : 'black' }}>English</div>
            </Link>
        </div>
    )
}

export default LanguageSwitcher

Deja un comentario