Cómo integrar next-intl en Next.js 15 para internacionalización: Guía paso a paso
Tutorial paso a paso para integrar next-intl para traducción en un proyecto Next.js 15.

La traducción (i18n) es esencial para las aplicaciones web modernas. Con Next.js 15, la forma recomendada de agregar traducción es usando next-intl. En esta guía, aprenderás cómo configurar next-intl en un proyecto Next.js 15 de manera práctica y efectiva.
🚀 ¿Buscas una plantilla Next.js con next-intl ya integrado?
Si quieres omitir la configuración y comenzar con una plantilla lista para producción que ya incluye internacionalización next-intl, revisa nuestra Plantilla Dashboard Financiero Next.js.
🎯 Esta plantilla premium incluye:
- ✅ next-intl ya configurado y listo para usar
- ✅ Cambio de tema oscuro/claro
- ✅ Gestión de estado Redux Toolkit
- ✅ Interfaz de dashboard financiero moderna
- ✅ Autenticación por email
- ✅ Diseño responsive
- ✅ Soporte TypeScript
Perfecta para aplicaciones fintech, dashboards bancarios y sistemas de gestión financiera.
1. Instalar next-intl
Primero, instala el paquete:
npm install next-intl
2. Configurar el enrutamiento
Crea una configuración de enrutamiento para definir los idiomas soportados. Por ejemplo, en src/i18n/routing.ts:
// src/i18n/routing.ts
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
locales: ['en', 'fr', 'ar', 'es'],
defaultLocale: 'en'
});
3. Añadir helpers de navegación
Configura los helpers de navegación en src/i18n/navigation.ts:
// src/i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);
4. Cargar mensajes en el servidor
Configura la carga de mensajes en src/i18n/request.ts:
// src/i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { hasLocale } from 'next-intl';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
const [seoMessages] = await Promise.all([
import(`../messages/seo/${locale}.json`).then(module => module.default).catch(() => ({})),
]);
return {
locale,
messages: {
seo: seoMessages,
}
};
});
5. Proveer mensajes a tu aplicación
Envuelve tus páginas con NextIntlClientProvider. Por ejemplo, en src/app/[locale]/about/layout.tsx:
// src/app/[locale]/about/layout.tsx
import { NextIntlClientProvider } from "next-intl";
export default async function AboutLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const [about] = await Promise.all([
import(`@/messages/about/${locale}.json`).then((mod) => mod.default),
]);
const messages = { about };
return (
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
);
}
6. Organiza tus archivos de mensajes
Guarda tus archivos de traducción en src/messages/, por ejemplo:
src/messages/about/en.json
src/messages/about/fr.json
src/messages/about/ar.json
src/messages/about/es.json
Cada archivo contiene traducciones para un namespace, por ejemplo:
{
"header_story": "Nuestra historia",
"header_title1": "Estamos construyendo"
// ...
}
7. Usar traducciones en componentes
Utiliza el hook useTranslations de next-intl:
import { useTranslations } from "next-intl";
export default function AboutHeader() {
const t = useTranslations("about");
return <h1>{t("header_title1")}</h1>;
}
8. Agregar un selector de idioma
Aquí tienes el código completo del componente LanguageSelector:
// src/components/LanguageSelector/index.tsx
"use client";
import React, { useState, useRef, useEffect } from "react";
import { useParams, useRouter } from "next/navigation";
import { cn } from "@/lib/utils";
import { IconLanguage, IconChevronDown } from "@/lib/icons";
interface Language {
code: string;
name: string;
rtl: boolean;
}
const languages: Language[] = [
{ code: "en", name: "English", rtl: false },
{ code: "fr", name: "Français", rtl: false },
{ code: "es", name: "Español", rtl: false },
{ code: "ar", name: "العربية", rtl: true },
];
export default function LanguageSelector() {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const router = useRouter();
const params = useParams();
const currentLocale = typeof params.locale === "string" ? params.locale : "en";
const currentLanguage = languages.find((lang) => lang.code === currentLocale) || languages[0];
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
const changeLanguage = (langCode: string) => {
const currentPath = window.location.pathname;
const pathSegments = currentPath.split('/');
pathSegments[1] = langCode;
const newPath = pathSegments.join('/');
router.push(newPath);
setIsOpen(false);
};
return (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center cursor-pointer px-2 py-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 text-sm"
aria-expanded={isOpen}
aria-haspopup="true"
>
<IconLanguage className="w-4 h-4 mr-1" />
<span className="font-medium text-xs mr-1">{currentLanguage.code.toUpperCase()}</span>
<IconChevronDown className={cn("w-3 h-3 transition-transform", isOpen ? "rotate-180" : "")} />
</button>
{isOpen && (
<div className="absolute z-50 mt-1 bg-white dark:bg-neutral-900 border border-gray-200 dark:border-gray-700 rounded-md shadow-lg py-1 w-40 min-w-max right-0">
<ul className="py-1">
{languages.map((language) => (
<li key={language.code}>
<button
onClick={() => changeLanguage(language.code)}
className={cn(
"w-full text-left cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center",
language.code === currentLocale && "bg-gray-100 dark:bg-gray-800"
)}
>
<span className="inline-block me-2 w-max font-medium text-xs">{language.code.toUpperCase()}</span>
{language.name}
</button>
</li>
))}
</ul>
</div>
)}
</div>
);
}
9. Prueba tu configuración
Inicia tu servidor de desarrollo y visita /en/about, /fr/about, etc. Deberías ver las traducciones correctas y poder cambiar de idioma.
Conclusión
Con next-intl, agregar traducción a Next.js 15 es sencillo y potente.
Referencias:
¿Encontró útil este artículo?
También te puede interesar

Optimización de rendimiento en Next.js: Guía práctica para acelerar tus aplicaciones
Aprende a acelerar aplicaciones Next.js con optimización de imágenes, división de código, generación estática, carga de fuentes y reducción del tamaño del bundle.

Mejora el rendimiento de Next.js con TanStack Query (React Query)
Cómo TanStack Query mejora el rendimiento de Next.js: caché, revalidación en segundo plano, mutaciones y SSR/SSG hidratado.

Publino: La plantilla Next.js definitiva para startups modernas
Descubre Publino, una plantilla Next.js de alto rendimiento y optimizada para SEO, diseñada para SaaS, startups y agencias. Crea y lanza sitios web impresionantes en minutos.