Cambiar tema

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

Easton editorial illustration: build pipeline conveyor

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);
};

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:

  1. Configuración central: middleware + i18n.ts + carpeta [locale]
  2. Uso de traducciones: hook useTranslations en Server y Client Components
  3. Rutas: Link y Router de next-intl
  4. Archivos: división por módulos + tipos en TypeScript
  5. 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

¡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. 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. 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. 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?
Motivo: App Router eliminó la i18n integrada de Pages Router.

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?
Tres esquemas principales:

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?
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:
• Ruta dinámica [locale]
• Proveer traducciones en el layout
• Middleware para el cambio de idioma
¿Cómo 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
• 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?
Funciones principales:
• 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?
Con el componente Link:
```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

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog