Fotografía mía de cuerpo completo con grandes montañas de fondo.R. Agustín Morán
  • Blog
  • Sobre mí
  • Portafolio
  • Proyectos
  • Fotos
Ricardo Agustín Morán R.
  • LinkedIn
  • GitHub
  • Instagram
Sitemap
  • Home
  • Portafolio
  • Proyectos
  • Sobre mí
  • Fotos
  • Mi misión
  • Experiencia
  • Educación
  • Sitemap.xml
  • Blog
Contacto

Puedes contactarme a través de mis redes sociales o vía email: agustin.moranr@gmail.com ¡Estaré encantado de conocerte!

©

Ricardo Agustín Morán Rivas

. 2025 🇲🇽
Hecho con cariño.

En este artículo aprenderás uno de los múltiples enfoques que existen para cambiar el idioma del contenido de una página dinámica implementada con Strapi y Next.js.

Este no es un tutorial desde 0 para crear un sitio web con internacionalización.

El objetivo central de este artículo es mostrarte cómo redireccionar a un usuario a la ruta correcta si este último cambia el idioma del sitio web estando sobre una ruta dinámica.

Imagen ilustrativa que muestra la redirección de rutas.

Esto aplica cuando estás utilizando el tipo de dato UUID de Strapi para consultar una página que es dinámica.

¿Por qué? 🤷‍♀️

Explicación de la problemática:

Para ejemplificar, he creado el siguiente proyecto de Strapi:

El proyecto está configurado para gestionar su contenido en Español e Inglés, donde el idioma por defecto es el Español.

Configuración del Plugin de Internacionalización de Strapi.

Además, cuenta con un Collection Type de Blog, el cual tiene un campo de tipo Text para el Título del Blog y un campo de tipo UUID para el Slug del mismo.

Imagen del Content Type para nuestros Blogs en Strapi.

Por último, tenemos un Blog de ejemplo que en su versión en Español cuenta con el Título: "Mi Blog Súper Épico" y el Slug: mi-blog-super-epico.

Imagen del Blog de ejemplo "Mi Blog Super Épico" en Español.

Y en su versión en Inglés cuenta con el Título: "My Super Epic Blog" y el Slug: my-super-epic-blog.

Imagen del Blog de ejemplo "Mi Blog Super Épico" en inglés.

Al habilitar contenido internacionalizado en Strapi, este añade una propiedad de filtrado llamada "locale", la cual permite al desarrollador filtrar su contenido por locale.

En nuestro caso, al tener solo dos idiomas. La propiedad locale puede tomar los valores "es", "en" o "all". Donde "all" le especifica a Strapi que consulte en todos las localizaciones configuradas.

Comúnmente podrías pensar que puedes consultar la información de un registro utilizando su campo UUID en un locale diferente. Pero no es el caso en Strapi.

Tomando nuestro proyecto de Strapi como ejemplo, si intentas consultar el título del Blog Mi Blog Súper Épico en su versión en Inglés, utilizando su Slug en Español, la consulta no devolverá información: 🚨

¿Por qué esto es importante? 🥀

Comúnmente los sitios web cuentan con un único Menú o Selector de Idioma, el cual se encarga de añadir un Sufijo a la ruta actual con el locale o idioma seleccionado por el usuario.

Ejemplo de botón de cambio de idioma en funcionamiento.

El ejemplo anterior muestra un sitio web que he creado utilizando Next.js para integrarlo con nuestro proyecto de Strapi. Puedes visitarlo para ver la versión final de este ejemplo en: i18nBlog. También puedes ver el código completo en el siguiente repositorio.

Blog que cambia de idioma en una ruta dinámica.

El sitio web en Next.js cuenta con un botón que al hacer click sobre él alterna el idioma de la página entre Español e Inglés. A nivel de rutas tendríamos lo siguiente:

  • Ruta en Español: https://ejemplo.com/
  • Ruta en Inglés: https://ejemplo.com/en

Tomando en cuenta lo anterior y retomando nuestro ejemplo, imaginemos que accedemos a Mi Blog Súper Épico en Español:

https://ejemplo.com/blog/mi-blog-super-epico.

Si cambiamos el idioma de nuestro sitio web a Inglés, nuestra ruta cambiaría a:

https://ejemplo.com/en/blog/mi-blog-super-epico.

¿Notas cuál es el problema?

Exacto, que tenemos el locale de nuestro sitio web en Inglés (en), pero el Slug sigue siendo: mi-blog-super-epico (español). Y cómo vimos en nuestra consulta anterior de GraphQL, necesitamos el Slug en Inglés para obtener la información, es decir: my-super-epic-blog .

¿Cómo solucionamos esta problemática?

Una solución muy sencilla sería redireccionar a la ruta con el Slug en el locale correcto cuando se detecta que el usuario cambió el idioma del sitio web.

Imagen ilustrativa que muestra la redirección de rutas.

✨Muy bien, implementemos esta solución.✨

En mi ejemplo estoy utilizando Next.js 14 con el App Router. Repito, este artículo soluciona un caso específico, por lo que no profundizaré en cómo crear un sitio web con internacionalización en Next.js. Pero puedes aprender aquí: Next.js Internationalization.)

Para comenzar, añadiremos la base para tener una página dinámica en Next.js que consulte nuestra API de Strapi y muestre el Título de nuestro Blog Mi Blog Súper Épico.

Hasta aquí tendríamos la implementación base para consultar nuestro Blog por Slug. Pero actualmente, si el usuario cambia el idioma del sitio web mientras está en la página dinámica de nuestro Blog, la consulta fallará y no obtendremos su información. Esto es lo que nos interesa, así que solucionémoslo.

Redireccionando páginas dinámicas al cambiar el idioma de un sitio web.

Para redireccionar correctamente a la página correcta de nuestro Blog, modifiquemos nuestra consulta de GraphQL para además del título, también obtengamos los Slugs y los Locales del Blog en todas sus localizaciones utilizando el atributo localizations de Strapi:

✨¡Listo!✨

Ahora nuestro sitio web redirecciona correctamente nuestra página de dinámica de Blog cuando el usuario cambia el idioma del sitio web.

Puedes implementar esta funcionalidad en las páginas dinámicas dentro de tu sitio web que tengan versiones en diferentes idiomas.

Muchas gracias si has llegado hasta acá. Espero que este artículo te haya sido de ayuda y ahora sepas cómo gestionar páginas dinámicas que utilizan Slugs dinámicos en Strapi.

Aprende a Internacionalizar Rutas Dinámicas Utilizando Strapi y Next.js

TIEMPO DE LECTURA
~ 12 Minutos
PUBLICADO
29 / may / 2024

Aquí es donde viene lo interesante.

Utilizando estos Locales y Slugs, crearemos una función de utilería mapSlugsWithLocale la cual se encargará de devolver un objeto con la siguiente estructura:

typescript
{
  es: "mi-blog-super-epico",
  en: "my-super-epic-blog"
}

Muy bien, implementemos la función mapSlugsWithLocales.

Folder structure
src/
├── app/
│   └── [locale]/
│       └── blog/
│           └── [slug]/
│               └── page.tsx
├── service/
│   └── getBlogBySlug.ts
├── graphql/
│   └── queries/
│       └── getBlogBySlug.graphql
└── lib/
    └── utils/
        └── mapSlugsWithLocales.ts

Nuestra función mapSlugsWithLocales recibirá las localizaciones de nuestro Blog y la localización actual como parámetros. Posteriormente, utilizando el método Reduce, nos devolverá el output previamente descrito.

typescript
//mapSlugsWithLocales.ts
 
function mapSlugsWithLocales(
	localizations: any[],
	{ currentLocalization }: { currentLocalization: Record<string, string> },
) {
	return localizations.reduce((acc: Record<string, string>, { attributes }) => {
		acc[attributes.locale] = attributes.slug;
		return acc;
	}, currentLocalization);
}
 
export default mapSlugsWithLocales;

Finalmente, utilicemos nuestra función mapSlugsWithLocales en nuestra página dinámica page.tsx y con ayuda de la función redirect de Next.js, redireccionemos al usuario a la ruta correcta cuando detectemos que el Slug no es el correcto para la versión del idioma de nuestro sitio web.

typescript
//blog/[slug]/page.tsx
 
import getBlogBySlug from '@/service/getBlogBySlug';
import mapSlugsWithLocales from '@/lib/utils/mapSlugsWithLocales';
import { redirect } from 'next/navigation';
 
export enum LOCALES_LIST {
	ES = 'es',
	EN = 'en',
}
 
export default async function BlogPage({
	params: { locale, slug },
}: {
	params: { locale: LOCALES_LIST; slug: string };
}) {
	//query blog data
	const blogResponse = await getBlogBySlug(slug);
 
	const { title, localizations } = blogResponse?.attributes;
 
	const localesWithSlugsMap = mapSlugsWithLocales(
		localizations?.data ?? [],
		{ currentLocalization: { [locale]: slug } }, //Current Slug and current Locale
	);
 
	const currentSlugIsInvalid = slug !== localesWithSlugsMap[locale];
 
	//Redirect to the correct route.
	if (currentSlugIsInvalid) {
		redirect(`/blog/${localesWithSlugsMap[locale]}`);
	}
 
	return (
		<div>
			<h1>{title}</h1>
		</div>
	);
}
graphql
query getBlogBySlug {
  blogs(
    locale: "en"
    filters: { 
      slug: { eq: "mi-blog-super-epico" } #utiliza "my-super-epic-blog" en su lugar.
    } 
  ) {
    data {
      attributes {
        title
      }
    }
  }
}

output:

json
{
  "data": {
    "blogs": {
      "data": []
    }
  }
}

Consideremos la siguiente estructura de carpetas

Folder structure
src/
├── app/
│   └── [locale]/
│       └── blog/
│           └── [slug]/
│               └── page.tsx
├── service/
│   └── getBlogBySlug.ts
└── graphql/
    └── queries/
        └── getBlogBySlug.graphql

El archivo page.tsx es la página dinámica para nuestro Blog. Esta recibe dos parámetros: el locale en el que se encuentra nuestro sitio web y el Slug del Blog que estamos visualizando.

typescript
// blog/[slug]/page.tsx
 
import getBlogBySlug from '@/service/getBlogBySlug';
 
export enum LOCALES_LIST {
	ES = 'es',
	EN = 'en',
}
 
export default async function BlogPage({
	params: { locale, slug },
}: {
	params: { locale: LOCALES_LIST; slug: string };
}) {
 
  //query blog data
	const blogResponse = await getBlogBySlug(slug)
   const { title } = blogResponse?.attributes;
 
	return (
		<div>
			<h1>
				{title}
			</h1>
		</div>
	);
}

El archivo getBlogBySlug.graphql contiene la consulta de Graphql para obtener el Título de nuestro Blog.

graphql
# graphql/queries/getBlogBySlug.graphql
 
query getBlogBySlug($slug: String) {
	blogs(
		locale: "all"
		filters: { slug: { eq: $slug } }
	) {
		data {
			attributes {
				title
			}
		}
	}
}

Por último el archivo getBlogBySlug.ts es el servicio encargado de manejar la consulta de GraphQL a nuestra API de Strapi.

typescript
// services/getBlogBySlug.ts
 
import { getClient } from '@/lib/client';
import { GetBlogBySlugQuery } from "@/schema/graphql"
 
export async function getBlogBySlug(slug: string) {
	let response = null;
	try {
		const client = getClient();
		const {
			error,
			data: { blogs },
		} = await client.query({
			query: GetBlogBySlugQuery,
			variables: {
				slug,
			},
		});
 
		if (error || !blogs?.data[0]) {
			throw new Error('Error retrieving blog');
		}
 
		response = blogs.data[0];
	} catch (error) {
		console.error(error);
	}
 
	return response;
}
 
export default getBlogBySlug;

Para el ejemplo estoy utilizando Apollo Client y Codegen para tipar y realizar la consulta. Pero es indiferente el cliente o manera en que tú realices la consulta a tu API de Strapi.

graphql
#getBlogBySlug.graphql
 
query getBlogsBySlug($slug: String) {
	blogs(
		locale: "all"
		filters: { slug: { eq: $slug } }
	) {
		data {
			attributes {
				title
				localizations {
					data {
						attributes {
							locale
							slug
						}
					}
				}
			}
		}
	}
}

Ejemplo de la respuesta al consultar Mi Blog Súper Épico en Español con el query anterior:

json
{
  "data": {
    "blogs": {
      "data": [
        {
          "attributes": {
            "title": "Mi Blog Súper Épico.",
            "localizations": {
              "data": [
                {
                  "attributes": {
                    "locale": "en",
                    "slug": "my-super-epic-blog"
                  }
                }
              ]
            }
          }
        }
      ]
    }
  }
}