IntlPull
Tutorial
12 min read

Internacionalización de Next.js: Guía completa de configuración con App Router (2026)

Tutorial paso a paso para añadir internacionalización a las aplicaciones Next.js 14+ utilizando el App Router, next-intl y las mejores prácticas para 2026.

IntlPull Team
IntlPull Team
03 Feb 2026, 11:44 AM [PST]
On this page
Summary

Tutorial paso a paso para añadir internacionalización a las aplicaciones Next.js 14+ utilizando el App Router, next-intl y las mejores prácticas para 2026.

Introducción

Next.js 14+ con el App Router aporta nuevas y potentes capacidades para la internacionalización. Esta guía le muestra cómo configurar una aplicación Next.js totalmente internacionalizada con enrutamiento, SEO y gestión de traducción adecuados.

Prerrequisitos

  • Next.js 14 o posterior
  • Conocimientos básicos de App Router
  • Node.js 18+

Paso 1: Instalar next-intl

Terminal
npm install next-intl

Paso 2: Estructura del proyecto

/app
  /[locale]
    /layout.tsx
    /page.tsx
    /about
      /page.tsx
/messages
  /en.json
  /es.json
  /fr.json
/i18n.ts
/middleware.ts
/next.config.js

Paso 3: Configurar next-intl

Cree i18n.ts en la raíz de su proyecto:

TypeScript
1import { getRequestConfig } from 'next-intl/server';
2
3export default getRequestConfig(async ({ locale }) => ({
4  messages: (await import(`./messages/${locale}.json`)).default
5}));

Paso 4: Crear middleware

TypeScript
1// middleware.ts
2import createMiddleware from 'next-intl/middleware';
3
4export default createMiddleware({
5  locales: ['en', 'es', 'fr', 'de', 'ja'],
6  defaultLocale: 'en',
7  localePrefix: 'as-needed' // or 'always' for /en/about style
8});
9
10export const config = {
11  matcher: ['/', '/(de|en|es|fr|ja)/:path*']
12};

Paso 5: Actualizar next.config.js

JavaScript
1const withNextIntl = require('next-intl/plugin')();
2
3module.exports = withNextIntl({
4  // Your existing Next.js config
5});

Paso 6: Crear Layout con Locale

TSX
1// app/[locale]/layout.tsx
2import { NextIntlClientProvider } from 'next-intl';
3import { getMessages } from 'next-intl/server';
4
5export default async function LocaleLayout({
6  children,
7  params: { locale }
8}: {
9  children: React.ReactNode;
10  params: { locale: string };
11}) {
12  const messages = await getMessages();
13
14  return (
15    <html lang={locale}>
16      <body>
17        <NextIntlClientProvider messages={messages}>
18          {children}
19        </NextIntlClientProvider>
20      </body>
21    </html>
22  );
23}

Paso 7: Crear archivos de traducción

JSON
1// messages/en.json
2{
3  "home": {
4    "title": "Welcome to our app",
5    "description": "The best solution for your needs",
6    "cta": "Get Started"
7  },
8  "navigation": {
9    "home": "Home",
10    "about": "About",
11    "contact": "Contact"
12  }
13}
JSON
1// messages/es.json
2{
3  "home": {
4    "title": "Bienvenido a nuestra aplicación",
5    "description": "La mejor solución para tus necesidades",
6    "cta": "Comenzar"
7  },
8  "navigation": {
9    "home": "Inicio",
10    "about": "Acerca de",
11    "contact": "Contacto"
12  }
13}

Paso 8: Utilizar traducciones en las páginas

Componentes del servidor

TSX
1// app/[locale]/page.tsx
2import { getTranslations } from 'next-intl/server';
3
4export default async function HomePage() {
5  const t = await getTranslations('home');
6
7  return (
8    <main>
9      <h1>{t('title')}</h1>
10      <p>{t('description')}</p>
11      <button>{t('cta')}</button>
12    </main>
13  );
14}

Componentes Cliente

TSX
1'use client';
2
3import { useTranslations } from 'next-intl';
4
5export function LanguageSwitcher() {
6  const t = useTranslations('navigation');
7  // ... language switching logic
8}

Paso 9: Añadir Cambiador de Idioma

TSX
1'use client';
2
3import { useLocale } from 'next-intl';
4import { useRouter, usePathname } from 'next/navigation';
5
6const locales = ['en', 'es', 'fr', 'de', 'ja'];
7
8export function LanguageSwitcher() {
9  const locale = useLocale();
10  const router = useRouter();
11  const pathname = usePathname();
12
13  const switchLocale = (newLocale: string) => {
14    const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);
15    router.push(newPath);
16  };
17
18  return (
19    <select value={locale} onChange={(e) => switchLocale(e.target.value)}>
20      {locales.map((loc) => (
21        <option key={loc} value={loc}>
22          {loc.toUpperCase()}
23        </option>
24      ))}
25    </select>
26  );
27}

Step 10: SEO Optimization

Generate Static Params

TSX
1// app/[locale]/page.tsx
2export function generateStaticParams() {
3  return [
4    { locale: 'en' },
5    { locale: 'es' },
6    { locale: 'fr' },
7    { locale: 'de' },
8    { locale: 'ja' },
9  ];
10}

Metadata with Translations

TSX
1import { getTranslations } from 'next-intl/server';
2
3export async function generateMetadata({ params: { locale } }) {
4  const t = await getTranslations({ locale, namespace: 'home' });
5
6  return {
7    title: t('meta.title'),
8    description: t('meta.description'),
9    alternates: {
10      canonical: `https://example.com/${locale}`,
11      languages: {
12        'en': 'https://example.com/en',
13        'es': 'https://example.com/es',
14        'fr': 'https://example.com/fr',
15      },
16    },
17  };
18}

Hreflang Tags

TSX
1// app/[locale]/layout.tsx
2export default function Layout({ children, params: { locale } }) {
3  return (
4    <html lang={locale}>
5      <head>
6        <link rel="alternate" hrefLang="en" href="https://example.com/en" />
7        <link rel="alternate" hrefLang="es" href="https://example.com/es" />
8        <link rel="alternate" hrefLang="x-default" href="https://example.com" />
9      </head>
10      <body>{children}</body>
11    </html>
12  );
13}

Advanced: Pluralization and Formatting

Plurals

JSON
1{
2  "cart": {
3    "items": "{count, plural, =0 {No items} =1 {1 item} other {# items}}"
4  }
5}
TSX
t('cart.items', { count: 5 }) // "5 items"

Rich Text

JSON
{
  "welcome": "Hello, <bold>{name}</bold>!"
}
TSX
1t.rich('welcome', {
2  name: 'John',
3  bold: (chunks) => <strong>{chunks}</strong>
4})

Dates and Numbers

TSX
1import { useFormatter } from 'next-intl';
2
3function PriceDisplay({ amount, date }) {
4  const format = useFormatter();
5
6  return (
7    <div>
8      <p>{format.number(amount, { style: 'currency', currency: 'USD' })}</p>
9      <p>{format.dateTime(date, { dateStyle: 'full' })}</p>
10    </div>
11  );
12}

Managing Translations at Scale

Manually managing JSON files gets painful fast. Here's how IntlPull helps:

Bidirectional Sync

Terminal
# Watch for changes and sync automatically
npx @intlpullhq/cli sync --watch

This keeps your local files in sync with IntlPull in real-time.

Upload & Download

Terminal
1# Pull latest from IntlPull
2npx @intlpullhq/cli download --output ./messages
3
4# Push new strings
5npx @intlpullhq/cli upload --source ./messages/en.json

AI Translation

When you add a new string, IntlPull automatically translates it to all your configured languages using context-aware AI.

Common Issues and Solutions

Issue: Hydration Mismatch

Solution: Ensure your middleware and locale detection are consistent.

TypeScript
1// middleware.ts
2export default createMiddleware({
3  localePrefix: 'always', // Prevents mismatches
4});

Issue: Flash of Wrong Language

Solution: Use cookies for persistence:

TypeScript
1export default createMiddleware({
2  localeDetection: true,
3  // Stores preference in cookie
4});

Issue: Missing Translations in Production

Solution: Set up fallback handling:

TypeScript
1getRequestConfig(async ({ locale }) => ({
2  messages: {
3    ...(await import(`./messages/en.json`)).default, // Fallback
4    ...(await import(`./messages/${locale}.json`)).default,
5  },
6}));

Performance Tips

  1. Use static generation where possible
  2. Code-split large translation namespaces
  3. Cache translations with ISR
  4. Lazy load non-critical translations

Conclusion

Next.js with next-intl provides a powerful, type-safe way to build internationalized applications. Combined with a translation management system like IntlPull, you can ship localized features faster than ever.

Ready to simplify your Next.js i18n? Try IntlPull free and automate your translation workflow.

Tags
nextjs
i18n
next-intl
app-router
tutorial
react
2026
IntlPull Team
IntlPull Team
Engineering

Building tools to help teams ship products globally. Follow us for more insights on localization and i18n.