Customize Consent Preferences

We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.

The cookies that are categorized as "Necessary" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... 

Always Active

Necessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.

No cookies to display.

Functional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.

No cookies to display.

Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.

No cookies to display.

Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.

No cookies to display.

Advertisement cookies are used to provide visitors with customized advertisements based on the pages you visited previously and to analyze the effectiveness of the ad campaigns.

No cookies to display.

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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install i18next next-i18next @types/i18next @types/react-i18next --save
npm install i18next next-i18next @types/i18next @types/react-i18next --save
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
module.exports = {
debug: process.env.NODE_ENV === 'development',
i18n: {
locales: ['es', 'en'],
defaultLocale: 'es',
},
};
module.exports = { debug: process.env.NODE_ENV === 'development', i18n: { locales: ['es', 'en'], defaultLocale: 'es', }, };
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/** @type {import('next').NextConfig} */
import pkg from './next-i18next.config.js';
const { i18n } = pkg;
const nextConfig = {
i18n,
};
export default nextConfig;
/** @type {import('next').NextConfig} */ import pkg from './next-i18next.config.js'; const { i18n } = pkg; const nextConfig = { i18n, }; export default nextConfig;
/** @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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"title_app": "Mi prueba con i18next",
}
{ "title_app": "Mi prueba con i18next", }
{
    "title_app": "Mi prueba con i18next",
  }

Contenido en/common.json

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"title_app": "My test with i18next",
}
{ "title_app": "My test with i18next", }
{
    "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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { appWithTranslation } from 'next-i18next';
function MyApp({ Component, pageProps }: AppProps) {
.....
}
export default appWithTranslation(MyApp);
import { appWithTranslation } from 'next-i18next'; function MyApp({ Component, pageProps }: AppProps) { ..... } export default appWithTranslation(MyApp);
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)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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)
)
}
}
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) ) } }
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const Home: React.FC<HomeProps> = ({ articles }) => {
const { t } = useTranslation();
....
const Home: React.FC<HomeProps> = ({ articles }) => { const { t } = useTranslation(); ....
const Home: React.FC<HomeProps> = ({ articles }) => {

  const { t } = useTranslation();

....

Para usar la traducción pondremos:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{t('common:title_app')}
{t('common:title_app')}
        {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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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'])),
},
};
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'])), }, };
  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í:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
...(await serverSideTranslations(locale, ['common', "traducciones_2" , "traducciones_3"])),
...(await serverSideTranslations(locale, ['common', "traducciones_2" , "traducciones_3"])),
        ...(await serverSideTranslations(locale, ['common', "traducciones_2" , "traducciones_3"])),

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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 : '',
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 : '',
  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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const Home: React.FC<HomeProps> = ({ articles, pathActual }) => {
const { t } = useTranslation();
...
return (
<TituloPrincipalH1 texto={t(`common:titulo`)} />
<Lista elements={elements} onClick={() => { loadMore}} t={t} />
);
const Home: React.FC<HomeProps> = ({ articles, pathActual }) => { const { t } = useTranslation(); ... return ( <TituloPrincipalH1 texto={t(`common:titulo`)} /> <Lista elements={elements} onClick={() => { loadMore}} t={t} /> );
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:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<TituloPrincipalH1 texto={t(`common:titulo`)} />
<TituloPrincipalH1 texto={t(`common:titulo`)} />
 <TituloPrincipalH1 texto={t(`common:titulo`)} />

2. Podemos pasar la función t directamente:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<Lista elements={elements} onClick={() => { loadMore}} t={t} />
<Lista elements={elements} onClick={() => { loadMore}} t={t} />
  <Lista elements={elements} onClick={() => { loadMore}} t={t} />

*Para importarla en el otro componente

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
interface Props{
elements: number[]
t: TFunction;
}
const Lista: React.FC<Props> = ({ elements, t }) => {
interface Props{ elements: number[] t: TFunction; } const Lista: React.FC<Props> = ({ elements, t }) => {
interface Props{
  elements: number[]
  t: TFunction;
}

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

Extra: componente para cambiar entre idiomas por path.

LanguajeSwitcher.tsx:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
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
0

Deja un comentario