Guía completa de internacionalización en Next.js: mejores prácticas con next-intl

El año pasado me tocó un proyecto Next.js que debía soportar varios idiomas. Al ver la configuración de i18n, me quedé un poco perdido. Tras revisar la documentación un buen rato, descubrí que App Router y Pages Router son enfoques totalmente distintos en internacionalización. Me llevó una semana configurar next-intl y, de paso, caí en más de un agujero.
Hoy repasamos las opciones de internacionalización en Next.js y, sobre todo, cómo implementar varios idiomas con next-intl de forma limpia bajo App Router.
¿Por qué elegir next-intl?
Puede que te preguntes: ¿Next.js no trae internacionalización de fábrica? Sí, en la era de Pages Router había soporte integrado de rutas i18n. En App Router, esa función desapareció.
La recomendación oficial es usar una librería de terceros. next-intl es una de las más populares.
Ventajas de next-intl:
- Soporte nativo para App Router — diseñado específicamente para App Router; encaja muy bien
- Seguridad de tipos — con TypeScript puedes comprobar los textos traducidos
- Esquemas de rutas flexibles — subrutas, dominios, cookies y más formas de cambiar idioma
- Funciones potentes — plurales, fechas, números, texto enriquecido, etc.
- Buen rendimiento — compatible con Server Components y renderizado estático
Frente a otras opciones, la documentación de next-intl también está bastante clara; no cuesta tanto empezar.
Configuración básica: desde cero
1. Instalar dependencias
Lo primero es instalar next-intl:
npm install next-intl
# o
pnpm add next-intl
# o
yarn add next-intl
2. Crear archivos de traducción
En la raíz del proyecto crea la carpeta messages (también puede llamarse locales u otro nombre) y añade un JSON por idioma:
messages/
├── en.json
├── zh.json
└── ja.json
messages/zh.json:
{
"HomePage": {
"title": "欢迎来到我的网站",
"description": "这是一个支持多语言的 Next.js 应用"
},
"Navigation": {
"home": "首页",
"about": "关于",
"contact": "联系我们"
}
}
messages/en.json:
{
"HomePage": {
"title": "Welcome to My Website",
"description": "This is a multilingual Next.js application"
},
"Navigation": {
"home": "Home",
"about": "About",
"contact": "Contact Us"
}
}
La estructura anidada no es obligatoria, pero agrupar por página o componente facilita mucho el mantenimiento.
3. Configurar i18n.ts
Crea i18n.ts (o i18n/config.ts) con los idiomas soportados:
import { getRequestConfig } from 'next-intl/server';
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`./messages/${locale}.json`)).default
}));
Esta configuración indica a next-intl dónde están las traducciones. El parámetro locale se extrae automáticamente de la URL.
4. Crear el middleware
En la raíz del proyecto crea middleware.ts; es la pieza clave para las rutas multilingües:
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// Lista de idiomas soportados
locales: ['en', 'zh', 'ja'],
// Idioma por defecto
defaultLocale: 'zh',
// Si el idioma por defecto lleva prefijo en la URL
localePrefix: 'as-needed'
});
export const config = {
// Coincide con todas las rutas excepto api, _next/static, _next/image, favicon.ico
matcher: ['/', '/(zh|en|ja)/:path*', '/((?!api|_next|_next/static|_next/image|favicon.ico).*)']
};
Opciones de localePrefix:
'always'— todos los idiomas llevan prefijo, incluido el por defecto (/zh/about,/en/about)'as-needed'— el idioma por defecto no lleva prefijo (/about,/en/about)'never'— ningún idioma lleva prefijo (hace falta otro modo de identificar el idioma, por ejemplo el dominio)
Yo suelo usar 'as-needed': para usuarios de chino la URL queda más limpia.
5. Reorganizar el directorio app
Es el paso más importante: mueve todas las rutas bajo el segmento dinámico [locale]:
Estructura anterior:
app/
├── page.tsx
├── about/
│ └── page.tsx
└── layout.tsx
Después del cambio:
app/
├── [locale]/
│ ├── page.tsx
│ ├── about/
│ │ └── page.tsx
│ └── layout.tsx
└── layout.tsx (opcional, para configuración global)
6. Configurar el layout raíz
En app/[locale]/layout.tsx configura el idioma:
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
const locales = ['en', 'zh', 'ja'];
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
// Comprobar que el idioma está soportado
if (!locales.includes(locale)) {
notFound();
}
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
Fíjate en generateStaticParams: si usas generación estática, le dice a Next.js para qué idiomas debe generar páginas.
Usar traducciones en componentes
Con la configuración lista, ya puedes traducir en los componentes.
En Server Components
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
El argumento de useTranslations es el namespace del JSON (la clave de nivel superior). Sin argumento puedes usar la ruta completa, por ejemplo t('HomePage.title').
En Client Components
En Client Components se usa igual:
'use client';
import { useTranslations } from 'next-intl';
export default function Navigation() {
const t = useTranslations('Navigation');
return (
<nav>
<a href="/">{t('home')}</a>
<a href="/about">{t('about')}</a>
<a href="/contact">{t('contact')}</a>
</nav>
);
}
Ahí está la gracia de next-intl: misma API en Server y Client Components.
Manejo de rutas multilingües
Obtener el idioma actual
import { useLocale } from 'next-intl';
export default function LanguageSwitcher() {
const locale = useLocale();
return <div>Current language: {locale}</div>;
}
Crear un selector de idioma
Algo que toda web internacionalizada necesita:
'use client';
import { useLocale } from 'next-intl';
import { usePathname, useRouter } from 'next/navigation';
export default function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const switchLanguage = (newLocale: string) => {
// Sustituir el segmento de idioma en la ruta
const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);
router.push(newPath);
};
return (
<select value={locale} onChange={(e) => switchLanguage(e.target.value)}>
<option value="zh">中文</option>
<option value="en">English</option>
<option value="ja">日本語</option>
</select>
);
}
Este enfoque tiene un detalle: si el usuario está en el idioma por defecto (por ejemplo chino) y la URL es /about, al pasar a inglés debería ser /en/about. Hay que afinarlo:
const switchLanguage = (newLocale: string) => {
// Quitar el prefijo de idioma actual
let path = pathname;
if (pathname.startsWith(`/${locale}`)) {
path = pathname.substring(locale.length + 1);
}
// Añadir el nuevo prefijo (salvo idioma por defecto con as-needed)
const newPath = newLocale === 'zh' ? path : `/${newLocale}${path}`;
router.push(newPath);
};
Usar el componente Link
next-intl ofrece un Link mejorado que gestiona el prefijo de idioma:
import { Link } from '@/navigation'; // hay que configurarlo antes
<Link href="/about">
{t('about')}
</Link>
Configura navigation.ts:
import { createSharedPathnamesNavigation } from 'next-intl/navigation';
export const locales = ['en', 'zh', 'ja'] as const;
export const localePrefix = 'as-needed';
export const { Link, redirect, usePathname, useRouter } =
createSharedPathnamesNavigation({ locales, localePrefix });
Así, Link, useRouter, etc. respetan automáticamente el idioma en la ruta.
Funciones avanzadas
1. Traducciones con parámetros
A menudo hay que insertar variables; next-intl lo soporta así:
messages/zh.json:
{
"welcome": "欢迎回来,{username}!",
"items": "你有 {count} 个新消息"
}
Uso:
const t = useTranslations();
<p>{t('welcome', { username: 'John' })}</p>
<p>{t('items', { count: 5 })}</p>
2. Plurales
Las reglas de plural varían por idioma; next-intl las cubre con claves como one / other:
messages/en.json:
{
"messages": {
"one": "You have {count} message",
"other": "You have {count} messages"
}
}
Uso:
t('messages', { count: 1 }) // "You have 1 message"
t('messages', { count: 5 }) // "You have 5 messages"
En chino no hay plural gramatical; puedes simplificar:
messages/zh.json:
{
"messages": "你有 {count} 条消息"
}
3. Formato de fechas y números
next-intl incluye formateadores dedicados:
import { useFormatter } from 'next-intl';
export default function DateExample() {
const format = useFormatter();
const now = new Date();
return (
<div>
<p>{format.dateTime(now, { dateStyle: 'full' })}</p>
{/* 中文:2025年12月25日星期三 */}
{/* 英文:Wednesday, December 25, 2025 */}
<p>{format.number(1234567.89, { style: 'currency', currency: 'CNY' })}</p>
{/* 中文:¥1,234,567.89 */}
{/* 英文:CN¥1,234,567.89 */}
</div>
);
}
4. Texto enriquecido
A veces la traducción incluye etiquetas HTML o componentes React:
messages/zh.json:
{
"richText": "我同意<terms>服务条款</terms>和<privacy>隐私政策</privacy>"
}
Uso:
import { useTranslations } from 'next-intl';
export default function Agreement() {
const t = useTranslations();
return (
<p>
{t.rich('richText', {
terms: (chunks) => <a href="/terms">{chunks}</a>,
privacy: (chunks) => <a href="/privacy">{chunks}</a>
})}
</p>
);
}
Mejores prácticas para gestionar traducciones
Cuando el proyecto crece, los JSON se vuelven difíciles de mantener. Algunos trucos que funcionan:
1. Dividir por módulos funcionales
No metas todo en un solo JSON; separa por página o función:
messages/
├── zh/
│ ├── common.json # Textos comunes (botones, errores, etc.)
│ ├── home.json # Inicio
│ ├── about.json # Acerca de
│ └── auth.json # Autenticación
├── en/
│ ├── common.json
│ ├── home.json
│ ├── about.json
│ └── auth.json
Y en i18n.ts combínalos:
import { getRequestConfig } from 'next-intl/server';
export default getRequestConfig(async ({ locale }) => {
const common = (await import(`./messages/${locale}/common.json`)).default;
const home = (await import(`./messages/${locale}/home.json`)).default;
const about = (await import(`./messages/${locale}/about.json`)).default;
const auth = (await import(`./messages/${locale}/auth.json`)).default;
return {
messages: {
common,
home,
about,
auth
}
};
});
2. Comprobación de tipos con TypeScript
Muy útil en la práctica: tipar los JSON y evitar typos en las claves:
types/i18n.ts:
import zh from '@/messages/zh.json';
type Messages = typeof zh;
declare global {
interface IntlMessages extends Messages {}
}
En tsconfig.json:
{
"compilerOptions": {
"types": ["./types/i18n"]
}
}
Si usas t('xxx') con una clave inexistente, TypeScript avisa. Muy práctico.
3. Extraer textos compartidos
Textos como «Guardar», «Cancelar», «Confirmar» se repiten; conviene un módulo común:
messages/zh/common.json:
{
"actions": {
"save": "保存",
"cancel": "取消",
"delete": "删除",
"confirm": "确认",
"edit": "编辑"
},
"status": {
"success": "操作成功",
"error": "操作失败",
"loading": "加载中..."
}
}
Uso:
const t = useTranslations('common.actions');
<button>{t('save')}</button>
4. Herramientas de gestión de traducciones
En proyectos grandes, mantener JSON a mano duele. Opciones:
- Tolgee — plataforma open source con edición en tiempo real
- Localazy — flujos automatizados de traducción
- i18n Ally (plugin de VSCode) — gestionar traducciones desde el editor
Yo uso sobre todo i18n Ally: ves el texto mientras codificas y lo cambias al momento.
5. Traducciones faltantes
En desarrollo a veces falta un idioma. Puedes configurar fallback:
// i18n.ts
export default getRequestConfig(async ({ locale }) => {
const messages = (await import(`./messages/${locale}.json`)).default;
const fallback = locale !== 'zh'
? (await import(`./messages/zh.json`)).default
: {};
return {
messages: {
...fallback,
...messages
}
};
});
Si falta una clave en inglés, cae al chino.
Optimización del rendimiento
1. División de código
Con JSON muy grandes, carga bajo demanda:
// Cargar solo cuando haga falta
export default function AdminPage() {
const t = useTranslations('admin'); // solo el namespace admin
// ...
}
2. Generación estática
Para páginas que cambian poco, la generación estática ayuda mucho:
// app/[locale]/about/page.tsx
export const dynamic = 'force-static';
export function generateStaticParams() {
return [
{ locale: 'zh' },
{ locale: 'en' },
{ locale: 'ja' }
];
}
3. Precarga de traducciones
Para el contenido above the fold, precarga en servidor:
import { getTranslations } from 'next-intl/server';
// Precarga en el servidor
export default async function Home() {
const t = await getTranslations('HomePage');
return <h1>{t('title')}</h1>;
}
Problemas frecuentes y soluciones
1. Cambio de idioma en rutas dinámicas
En rutas como /blog/[slug] conviene conservar el slug al cambiar idioma:
const switchLanguage = (newLocale: string) => {
const segments = pathname.split('/').filter(Boolean);
// Quitar prefijo de idioma anterior
if (['zh', 'en', 'ja'].includes(segments[0])) {
segments.shift();
}
// Añadir nuevo prefijo si hace falta
if (newLocale !== 'zh' || localePrefix === 'always') {
segments.unshift(newLocale);
}
router.push('/' + segments.join('/'));
};
2. SEO
En sitios multilingües el SEO requiere cuidado:
// app/[locale]/layout.tsx
export async function generateMetadata({ params: { locale } }) {
const t = await getTranslations({ locale, namespace: 'metadata' });
return {
title: t('title'),
description: t('description'),
alternates: {
canonical: `https://example.com/${locale}`,
languages: {
'zh-CN': 'https://example.com/zh',
'en-US': 'https://example.com/en',
'ja-JP': 'https://example.com/ja'
}
}
};
}
3. Detección de idioma
En la primera visita puedes detectar el idioma del usuario:
// middleware.ts
import createMiddleware from 'next-intl/middleware';
import { NextRequest } from 'next/server';
const intlMiddleware = createMiddleware({
locales: ['en', 'zh', 'ja'],
defaultLocale: 'zh',
localeDetection: true // activar detección automática
});
export default function middleware(request: NextRequest) {
return intlMiddleware(request);
}
next-intl usa la cabecera Accept-Language para elegir idioma.
4. Recordar la preferencia de idioma
Tras elegir idioma, conviene persistirlo:
// Guardar en cookie
const switchLanguage = (newLocale: string) => {
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000`;
router.push(newPath);
};
El middleware de next-intl lee esa cookie automáticamente.
Caso práctico: proyecto internacionalizado completo
Por último, un resumen del código clave de un proyecto pequeño que hice antes.
Estructura del proyecto:
├── app/
│ ├── [locale]/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── blog/
│ │ └── [slug]/
│ │ └── page.tsx
├── components/
│ ├── LanguageSwitcher.tsx
│ └── Navigation.tsx
├── messages/
│ ├── zh/
│ │ ├── common.json
│ │ └── blog.json
│ ├── en/
│ │ ├── common.json
│ │ └── blog.json
├── i18n.ts
├── middleware.ts
└── navigation.ts
navigation.ts (configuración de rutas):
import { createSharedPathnamesNavigation } from 'next-intl/navigation';
export const locales = ['zh', 'en'] as const;
export const localePrefix = 'as-needed';
export const { Link, redirect, usePathname, useRouter } =
createSharedPathnamesNavigation({ locales, localePrefix });
components/Navigation.tsx:
'use client';
import { Link } from '@/navigation';
import { useTranslations } from 'next-intl';
import LanguageSwitcher from './LanguageSwitcher';
export default function Navigation() {
const t = useTranslations('common.navigation');
return (
<nav className="flex items-center justify-between p-4">
<div className="flex gap-4">
<Link href="/">{t('home')}</Link>
<Link href="/blog">{t('blog')}</Link>
<Link href="/about">{t('about')}</Link>
</div>
<LanguageSwitcher />
</nav>
);
}
Tras el despliegue, el cambio de idioma fue fluido; no hubo sorpresas.
Resumen
La internacionalización en Next.js con App Router parece compleja al principio, pero con next-intl el camino se aclara:
- Configuración central: middleware + i18n.ts + carpeta
[locale] - Uso de traducciones: hook
useTranslationsen Server y Client Components - Rutas: Link y Router de next-intl
- Archivos: división por módulos + tipos en TypeScript
- Rendimiento: generación estática + carga bajo demanda
Al principio el middleware y las rutas dinámicas marean un poco. Tras repetirlo unas veces, se vuelve rutina; casi todos mis proyectos i18n siguen este esquema.
Si estás montando o planeas un proyecto multilingüe, merece la pena probar next-intl. Tiene curva de aprendizaje, pero a largo plazo compensa.
Recursos
- Documentación oficial de next-intl
- Guía de internacionalización de Next.js
- Plugin i18n Ally para VSCode
- Plataforma Tolgee
¡Espero que esto te ayude si estás peleándote con la internacionalización en Next.js!
Flujo completo de configuración i18n en Next.js
Pasos completos desde instalar next-intl hasta configurar rutas multilingües y gestionar archivos de traducción
⏱️ Estimated time: 2 hr
- 1
Step 1: Instalar next-intl y configuración básica
Instalación:
```bash
npm install next-intl
```
Crear archivos de traducción:
```
messages/
zh.json
en.json
```
Configurar middleware.ts:
```ts
import createMiddleware from 'next-intl/middleware'
import { routing } from './i18n/routing'
export default createMiddleware(routing)
export const config = {
matcher: ['/', '/(zh|en)/:path*']
}
```
Configurar app/[locale]/layout.tsx:
```tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
export default async function LocaleLayout({
children,
params: { locale }
}) {
const messages = await getMessages()
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
)
}
```
Puntos clave:
• Usar ruta dinámica [locale]
• Proveer traducciones en el layout
• Configurar middleware para el cambio de idioma - 2
Step 2: Configurar esquema de rutas multilingües
Opción 1: subruta (recomendada)
• Formato URL: /zh/about, /en/about
• Configuración sencilla
• SEO amigable
Opción 2: dominio
• Formato URL: zh.example.com, en.example.com
• Requiere varios dominios
• Más profesional
Opción 3: cookie
• Cambio de idioma vía cookie
• URL sin prefijo de idioma
• Adecuado si la audiencia usa un solo idioma
Configurar subruta:
```ts
// i18n/routing.ts
export const routing = {
locales: ['zh', 'en'],
defaultLocale: 'zh'
}
```
Uso:
```tsx
import { useTranslations } from 'next-intl'
export function Page() {
const t = useTranslations('common')
return <h1>{t('title')}</h1>
}
```
Punto clave: elige el esquema que encaje con tu proyecto; en la mayoría basta con subrutas - 3
Step 3: Gestionar archivos de traducción
Crear archivos de traducción:
```json
// messages/zh.json
{
"common": {
"title": "欢迎",
"description": "这是一个多语言网站"
},
"nav": {
"home": "首页",
"about": "关于"
}
}
```
Usar traducciones:
```tsx
import { useTranslations } from 'next-intl'
export function Page() {
const t = useTranslations('common')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
)
}
```
Seguridad de tipos:
```ts
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default
}))
```
Puntos clave:
• Estructura anidada para organizar traducciones
• TypeScript para tipos seguros
• Plugin i18n Ally en VSCode para mejorar la experiencia de desarrollo
FAQ
¿Por qué App Router necesita next-intl?
Pages Router:
• Incluía soporte de rutas i18n
• Se configuraba el campo i18n en next.config.js
• Gestionaba el cambio de idioma automáticamente
App Router:
• Eliminó la i18n integrada
• Hay que usar una librería de terceros
• next-intl es una de las opciones más populares
Ventajas de next-intl:
• Soporte nativo para App Router
• Seguridad de tipos
• Esquemas de rutas flexibles
• Funciones avanzadas (plurales, fechas, etc.)
• Buen rendimiento
Recomendación: si usas App Router, next-intl suele ser la mejor opción.
¿Cuántos esquemas de rutas ofrece next-intl?
Opción 1: subruta (recomendada)
• URL: /zh/about, /en/about
• Configuración sencilla
• SEO amigable
• Adecuado para la mayoría de proyectos
Opción 2: dominio
• URL: zh.example.com, en.example.com
• Requiere varios dominios
• Más profesional
• Proyectos grandes
Opción 3: cookie
• Cambio de idioma vía cookie
• URL sin prefijo de idioma
• Audiencia monolingüe
• Configuración más compleja
Criterio:
• Mayoría de proyectos → subruta
• Proyectos grandes → dominio
• Casos especiales → cookie
Punto clave: elige según tu proyecto; en la mayoría basta con subrutas.
¿Cómo configurar next-intl?
```bash
npm install next-intl
```
Crear archivos de traducción:
```
messages/
zh.json
en.json
```
Configurar middleware.ts:
```ts
import createMiddleware from 'next-intl/middleware'
import { routing } from './i18n/routing'
export default createMiddleware(routing)
export const config = {
matcher: ['/', '/(zh|en)/:path*']
}
```
Configurar app/[locale]/layout.tsx:
```tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
export default async function LocaleLayout({
children,
params: { locale }
}) {
const messages = await getMessages()
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
)
}
```
Puntos clave:
• Ruta dinámica [locale]
• Proveer traducciones en el layout
• Middleware para el cambio de idioma
¿Cómo gestionar archivos de traducción?
```json
// messages/zh.json
{
"common": {
"title": "欢迎",
"description": "这是一个多语言网站"
},
"nav": {
"home": "首页",
"about": "关于"
}
}
```
Usar traducciones:
```tsx
import { useTranslations } from 'next-intl'
export function Page() {
const t = useTranslations('common')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
)
}
```
Seguridad de tipos:
```ts
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default
}))
```
Puntos clave:
• Estructura anidada
• TypeScript para tipos seguros
• Plugin i18n Ally en VSCode
Recomendación: organiza por módulos funcionales y evita un solo archivo enorme.
¿Qué funciones soporta next-intl?
• Texto traducido (función t)
• Plurales
• Formato de fechas
• Formato de números
• Texto enriquecido
Ejemplo:
```tsx
import { useTranslations, useFormatter } from 'next-intl'
export function Page() {
const t = useTranslations('common')
const format = useFormatter()
return (
<div>
<h1>{t('title')}</h1>
<p>{format.dateTime(new Date(), { dateStyle: 'long' })}</p>
<p>{format.number(1234.56, { style: 'currency', currency: 'USD' })}</p>
</div>
)
}
```
Ventajas:
• Funciones completas
• Seguridad de tipos
• Buen rendimiento
• Compatible con Server Components
Recomendación: aprovecha estas funciones para mejorar la experiencia de usuario.
¿Cómo implementar el cambio de idioma?
```tsx
import { Link } from '@/i18n/navigation'
<Link href="/about" locale="en">
English
</Link>
<Link href="/about" locale="zh">
中文
</Link>
```
Con useRouter:
```tsx
'use client'
import { useRouter, usePathname } from '@/i18n/navigation'
export function LanguageSwitcher() {
const router = useRouter()
const pathname = usePathname()
const switchLanguage = (locale: string) => {
router.replace(pathname, { locale })
}
return (
<button onClick={() => switchLanguage('en')}>
English
</button>
)
}
```
Puntos clave:
• Usar Link y useRouter de next-intl
• Mantener la ruta actual y solo cambiar idioma
• Buena experiencia de usuario
Recomendación: añade el selector en la barra de navegación o el pie de página.
12 min de lectura · Publicado el: 25 dic 2025 · Actualizado el: 21 ago 2026
Guía completa de Next.js
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Inicio de sesión OAuth en Next.js: guía práctica para Google, GitHub y WeChat
Del principio de OAuth a la configuración práctica: entiende el flujo de autorización con la analogía de recoger un paquete e implementa paso a paso Google, GitHub y WeChat con NextAuth.js, con guía completa de resolución de errores.
Parte 14 de 51
Siguiente
Internacionalización de Next.js y generación estática: práctica de sitios web multilingües de SSG
Desde el informe de errores de compilación hasta la optimización del rendimiento, te enseñamos paso a paso cómo utilizar App Router para lograr una generación estática en varios idiomas sin agujeros. Contiene ejemplos de código completos, explicación detallada de la configuración de generateStaticParams y consejos para optimizar el tiempo de compilación.
Parte 16 de 51



Comentarios
Inicia sesión con GitHub para dejar un comentario