Traducir tu app Next.js es esencial para llegar a una audiencia global. Next.js proporciona excelente soporte integrado para internacionalización (i18n) a través de routing y puede mejorarse con bibliotecas como next-intl o next-i18next. Esta guía te guiará a través del proceso de traducir una app Next.js.
Entendiendo la Localización de Next.js
Next.js soporta internacionalización a través de:
- Routing i18n integrado - Detección automática de locale y routing
- next-intl - Biblioteca moderna construida para Next.js App Router
- next-i18next - Biblioteca popular para Pages Router
Cubriremos ambos enfoques en esta guía.
Método 1: Usando next-intl (App Router - Recomendado)
next-intl es la solución recomendada para Next.js 13+ con App Router.
Paso 1: Instalar Dependencias
npm install next-intl
O con yarn:
yarn add next-intl
Paso 2: Crear Archivos de Traducción
Crea un directorio messages en la raíz de tu proyecto:
messages/en.json (Inglés - por defecto):
{
"app": {
"welcome": "Welcome to our app!",
"title": "My Next.js App"
},
"button": {
"submit": "Submit",
"cancel": "Cancel",
"delete": "Delete"
},
"error": {
"network": "Network error. Please try again.",
"notFound": "Page not found"
},
"items": {
"count": "{count, plural, =0 {No items} one {# item} other {# items}}"
},
"user": {
"greeting": "Hello, {name}! You have {count, plural, =0 {no messages} one {# message} other {# messages}}."
}
}
messages/es.json (Español):
{
"app": {
"welcome": "¡Bienvenido a nuestra aplicación!",
"title": "Mi Aplicación Next.js"
},
"button": {
"submit": "Enviar",
"cancel": "Cancelar",
"delete": "Eliminar"
},
"error": {
"network": "Error de red. Por favor, inténtalo de nuevo.",
"notFound": "Página no encontrada"
},
"items": {
"count": "{count, plural, =0 {No hay elementos} one {# elemento} other {# elementos}}"
},
"user": {
"greeting": "¡Hola, {name}! Tienes {count, plural, =0 {no hay mensajes} one {# mensaje} other {# mensajes}}."
}
}
messages/fr.json (Francés):
{
"app": {
"welcome": "Bienvenue dans notre application!",
"title": "Mon Application Next.js"
},
"button": {
"submit": "Soumettre",
"cancel": "Annuler",
"delete": "Supprimer"
},
"error": {
"network": "Erreur réseau. Veuillez réessayer.",
"notFound": "Page non trouvée"
},
"items": {
"count": "{count, plural, =0 {Aucun élément} one {# élément} other {# éléments}}"
},
"user": {
"greeting": "Bonjour, {name}! Vous avez {count, plural, =0 {aucun message} one {# message} other {# messages}}."
}
}
Paso 3: Configurar next-intl
Crea i18n.ts en la raíz de tu proyecto:
i18n.ts:
import { getRequestConfig } from 'next-intl/server';
import { notFound } from 'next/navigation';
export const locales = ['en', 'es', 'fr'] as const;
export const defaultLocale = 'en' as const;
export default getRequestConfig(async ({ locale }) => {
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`./messages/${locale}.json`)).default
};
});
Paso 4: Actualizar next.config.js
next.config.js:
const createNextIntlPlugin = require('next-intl/plugin');
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = withNextIntl(nextConfig);
Paso 5: Crear Middleware
Crea middleware.ts en la raíz de tu proyecto:
middleware.ts:
import createMiddleware from 'next-intl/middleware';
import { locales, defaultLocale } from './i18n';
export default createMiddleware({
locales,
defaultLocale,
localePrefix: 'always' // o 'as-needed'
});
export const config = {
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
Paso 6: Actualizar Estructura del App Router
Reestructura tu directorio app para incluir [locale]:
app/
[locale]/
layout.tsx
page.tsx
about/
page.tsx
app/[locale]/layout.tsx:
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { locales } from '@/i18n';
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
if (!locales.includes(locale as any)) {
notFound();
}
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
app/[locale]/page.tsx:
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations();
return (
<div>
<h1>{t('app.welcome')}</h1>
<p>{t('app.title')}</p>
</div>
);
}
Paso 7: Usar Traducciones en Componentes
Componentes del Servidor:
import { useTranslations } from 'next-intl';
export default function ServerComponent() {
const t = useTranslations();
return <h1>{t('app.welcome')}</h1>;
}
Componentes del Cliente:
'use client';
import { useTranslations } from 'next-intl';
export default function ClientComponent() {
const t = useTranslations();
return <h1>{t('app.welcome')}</h1>;
}
Paso 8: Interpolación de Cadenas
import { useTranslations } from 'next-intl';
export default function UserGreeting({ name, messageCount }: { name: string; messageCount: number }) {
const t = useTranslations('user');
return (
<p>
{t('greeting', { name, count: messageCount })}
</p>
);
}
Paso 9: Pluralización
import { useTranslations } from 'next-intl';
export default function ItemCount({ count }: { count: number }) {
const t = useTranslations('items');
return <p>{t('count', { count })}</p>;
}
Paso 10: Formatear Fechas y Números
import { useTranslations, useFormatter } from 'next-intl';
export default function DateDisplay({ date }: { date: Date }) {
const format = useFormatter();
return (
<p>
{format.dateTime(date, {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
})}
</p>
);
}
export default function NumberDisplay({ number }: { number: number }) {
const format = useFormatter();
return (
<div>
<p>Número: {format.number(number)}</p>
<p>Moneda: {format.number(number, { style: 'currency', currency: 'USD' })}</p>
</div>
);
}
Paso 11: Selector de Idioma
'use client';
import { usePathname, useRouter } from 'next/navigation';
import { useLocale } from 'next-intl';
export default function LanguageSwitcher() {
const router = useRouter();
const pathname = usePathname();
const locale = useLocale();
const switchLocale = (newLocale: string) => {
const newPathname = pathname.replace(`/${locale}`, `/${newLocale}`);
router.push(newPathname);
};
return (
<div>
<button onClick={() => switchLocale('en')}>English</button>
<button onClick={() => switchLocale('es')}>Español</button>
<button onClick={() => switchLocale('fr')}>Français</button>
</div>
);
}
Método 2: Usando next-i18next (Pages Router)
Para Next.js Pages Router, usa next-i18next.
Paso 1: Instalar Dependencias
npm install next-i18next react-i18next i18next
Paso 2: Crear Archivos de Traducción
public/locales/en/common.json:
{
"welcome": "Welcome to our app!",
"button": {
"submit": "Submit",
"cancel": "Cancel"
}
}
public/locales/es/common.json:
{
"welcome": "¡Bienvenido a nuestra aplicación!",
"button": {
"submit": "Enviar",
"cancel": "Cancelar"
}
}
Paso 3: Configurar next-i18next
next-i18next.config.js:
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
localePath: './public/locales',
};
next.config.js:
const { i18n } = require('./next-i18next.config');
module.exports = {
i18n,
};
Paso 4: Crear App Personalizado
pages/_app.js:
import { appWithTranslation } from 'next-i18next';
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default appWithTranslation(MyApp);
Paso 5: Usar Traducciones en Páginas
pages/index.js:
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
export default function HomePage() {
const { t } = useTranslation('common');
return <h1>{t('welcome')}</h1>;
}
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
},
};
}
Método 3: Routing i18n Integrado de Next.js
Next.js tiene soporte i18n integrado para Pages Router.
Paso 1: Configurar next.config.js
next.config.js:
module.exports = {
i18n: {
locales: ['en', 'es', 'fr'],
defaultLocale: 'en',
localeDetection: true, // Detectar automáticamente el locale del usuario
},
};
Paso 2: Crear Archivos de Traducción
locales/en.json:
{
"welcome": "Welcome to our app!"
}
locales/es.json:
{
"welcome": "¡Bienvenido a nuestra aplicación!"
}
Paso 3: Usar en Páginas
pages/index.js:
import { useRouter } from 'next/router';
import translations from '../locales';
export default function HomePage() {
const router = useRouter();
const { locale } = router;
const t = translations[locale];
return <h1>{t.welcome}</h1>;
}
Mejores Prácticas
1. Usar Claves de Traducción Descriptivas
Malo:
{
"msg1": "Submit"
}
Bueno:
{
"button": {
"submit": "Submit"
}
}
2. Organizar por Característica
{
"auth": {
"login": "Login",
"logout": "Logout"
},
"dashboard": {
"title": "Dashboard"
}
}
3. Usar Namespaces
Para apps grandes, divide las traducciones en namespaces:
next-i18next:
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common', 'auth', 'dashboard'])),
},
};
}
next-intl:
const t = useTranslations('auth');
4. Manejar Traducciones Faltantes
const t = useTranslations();
const text = t('some.key', { defaultValue: 'Texto por defecto' });
5. Consideraciones SEO
Establece el atributo lang apropiado y etiquetas hreflang:
app/[locale]/layout.tsx:
export default function LocaleLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
return (
<html lang={locale}>
<head>
<link rel="alternate" hrefLang="en" href="/en" />
<link rel="alternate" hrefLang="es" href="/es" />
<link rel="alternate" hrefLang="fr" href="/fr" />
</head>
<body>{children}</body>
</html>
);
}
6. Generación Estática con Locales
Genera páginas estáticas para todos los locales:
next-intl:
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
next-i18next:
export async function getStaticPaths() {
return {
paths: locales.map((locale) => ({ params: { locale } })),
fallback: false,
};
}
Errores Comunes
1. No Configurar Middleware
Para App Router con next-intl, siempre crea middleware:
// middleware.ts es requerido
2. Olvidar Locale en la Estructura de URL
Asegúrate de que tus rutas incluyan locale:
/en/about
/es/about
/fr/about
3. No Manejar Componentes Cliente/Servidor
Usa 'use client' para componentes del cliente:
'use client';
import { useTranslations } from 'next-intl';
4. Codificar Cadenas Directamente
Malo:
<h1>Bienvenido</h1>
Bueno:
const t = useTranslations();
<h1>{t('app.welcome')}</h1>
Avanzado: Cambio Dinámico de Idioma
next-intl:
'use client';
import { useRouter, usePathname } from 'next/navigation';
import { useLocale } from 'next-intl';
export function LanguageSwitcher() {
const router = useRouter();
const pathname = usePathname();
const locale = useLocale();
const changeLocale = (newLocale: string) => {
const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);
router.push(newPath);
router.refresh();
};
return (
<select value={locale} onChange={(e) => changeLocale(e.target.value)}>
<option value="en">English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
</select>
);
}
Avanzado: Soporte RTL
import { useLocale } from 'next-intl';
import { useEffect } from 'react';
export default function RTLHandler() {
const locale = useLocale();
const isRTL = ['ar', 'he', 'fa'].includes(locale);
useEffect(() => {
document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
document.documentElement.lang = locale;
}, [locale, isRTL]);
return null;
}
Avanzado: Soporte TypeScript
types/next-intl.d.ts:
type Messages = typeof import('./messages/en.json');
declare global {
interface IntlMessages extends Messages {}
}
Conclusión
Localizar tu app Next.js se puede hacer a través de múltiples enfoques:
- next-intl (Recomendado para App Router) - Moderno, construido para Next.js 13+
- next-i18next (Para Pages Router) - Popular, bien documentado
- Routing i18n integrado (Pages Router) - Simple pero limitado
Elige el método que mejor se ajuste a tu versión de Next.js y requisitos. Siguiendo estas prácticas, crearás una app que proporciona una experiencia nativa para usuarios en todo el mundo, expandiendo significativamente tu base de usuarios potencial.
Optimiza tu Flujo de Trabajo de Localización Next.js
Gestionar traducciones para múltiples idiomas puede volverse complejo a medida que tu app crece. Considera usar una plataforma de gestión de traducciones para:
- Colaborar con traductores
- Mantener traducciones sincronizadas con tu código base
- Automatizar el flujo de trabajo de traducción
- Mantener consistencia en todos los idiomas
- Generar archivos de traducción automáticamente
¿Listo para llevar tu app Next.js a nivel global? Explora la plataforma de localización de AZbox y optimiza tu flujo de trabajo de traducción: