IntlPull
Technical
18 min read

¿Las traducciones i18n en Next.js no funcionan? Guía completa de solución de problemas (2026)

Soluciona problemas comunes de internacionalización en Next.js: traducciones faltantes, t() devolviendo claves, errores en archivos JSON, problemas de middleware, desajustes de hidratación y más. Guía de depuración para next-intl y react-i18next.

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

Soluciona problemas comunes de internacionalización en Next.js: traducciones faltantes, t() devolviendo claves, errores en archivos JSON, problemas de middleware, desajustes de hidratación y más. Guía de depuración para next-intl y react-i18next.

Respuesta rápida

Si tus traducciones en Next.js no funcionan, revisa esto en orden: (1) La clave de traducción existe en el archivo JSON con el anidamiento correcto, (2) El archivo JSON es válido (sin comas finales), (3) El locale se detecta correctamente en el middleware, (4) El Provider/contexto envuelve tus componentes, (5) Usas el hook correcto (Server vs Client Component). El 90% de los problemas son errores tipográficos en las claves o entradas faltantes en el JSON.


He depurado cientos de configuraciones i18n en proyectos React y Next.js. La buena noticia: los errores de traducción casi siempre son uno de unos 10 problemas predecibles. La mala noticia: pueden ser increíblemente frustrantes de diagnosticar cuando no sabes qué buscar.

Esta guía cubre todos los problemas de traducción que he encontrado en producción, organizados de más común a menos común. Me enfoco en Next.js 14/15 con App Router, pero la mayoría de estos aplican también para Pages Router.

Problema #1: Se muestra la clave en lugar de la traducción

Síntoma: Ves common.buttons.submit en pantalla en lugar de "Enviar"

Este es el problema #1, y generalmente significa una de estas cosas:

La clave no existe en tu JSON

JSON
1// ❌ Tu código usa: t('common.buttons.submit')
2// Pero tu en.json tiene:
3{
4  "common": {
5    "button": {  // Nota: "button" no "buttons"
6      "submit": "Submit"
7    }
8  }
9}

Solución: Revisa la estructura de tu JSON cuidadosamente. Usa un IDE con vista previa de rutas JSON o una herramienta como IntlPull que valida las claves.

Estás usando el namespace incorrecto

TSX
1// ❌ Incorrecto: busca en el namespace por defecto
2const t = useTranslations();
3t('checkout.title');
4
5// ✅ Correcto: especifica el namespace
6const t = useTranslations('checkout');
7t('title');

El archivo JSON no se está cargando

Revisa tu configuración de i18n:

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

Verifica que la ruta del archivo sea correcta. Un error común es poner los mensajes en /public/locales cuando tu configuración espera /messages.


Problema #2: Sintaxis JSON inválida

Síntoma: La app crashea con "Unexpected token" o las traducciones fallan silenciosamente

JSON es estricto. Esto romperá todo:

JSON
1// ❌ Coma final (lo más común)
2{
3  "welcome": "Hola",
4  "goodbye": "Adiós",  // <-- Esta coma lo rompe
5}
6
7// ❌ Comillas simples
8{
9  'welcome': 'Hola'  // Debe usar comillas dobles
10}
11
12// ❌ Comillas sin escapar en los valores
13{
14  "message": "Haz clic "aquí" para continuar"  // Necesita escapar
15}
16
17// ✅ Correcto
18{
19  "welcome": "Hola",
20  "goodbye": "Adiós",
21  "message": "Haz clic \"aquí\" para continuar"
22}

Solución: Usa un validador de JSON. VS Code resalta errores de sintaxis. Ejecuta cat en.json | python -m json.tool para validar desde CLI.


Problema #3: Traducción faltante para un locale específico

Síntoma: El inglés funciona, pero español/alemán/etc. muestra las claves

Has agregado la clave a en.json pero olvidaste los otros locales:

/messages
  en.json  ✅ Tiene "checkout.newFeature": "Try our new feature"
  es.json  ❌ Falta esta clave completamente
  de.json  ❌ Falta esta clave completamente

Solución: Usa una herramienta de gestión de traducciones como IntlPull que rastrea traducciones faltantes en todos los locales. O configura un fallback:

TypeScript
1// Configuración next-intl con fallback
2export default getRequestConfig(async ({ locale }) => ({
3  messages: {
4    ...(await import(`../messages/en.json`)).default, // Fallback
5    ...(await import(`../messages/${locale}.json`)).default
6  }
7}));

Problema #4: El middleware no detecta el locale

Síntoma: Siempre muestra el idioma por defecto, el locale en la URL se ignora

TypeScript
1// ❌ El middleware no se ejecuta en tus rutas
2export const config = {
3  matcher: ['/api/:path*']  // ¡Solo hace match con rutas de API!
4};
5
6// ✅ Matcher correcto para i18n
7export const config = {
8  matcher: ['/((?!api|_next|.*\\..*).*)']
9};

También verifica la ubicación de tu archivo de middleware - debe estar en middleware.ts en la raíz del proyecto, no dentro de /app o /src/app.

Revisa la lógica de detección del locale

TypeScript
1// middleware.ts
2import createMiddleware from 'next-intl/middleware';
3
4export default createMiddleware({
5  locales: ['en', 'es', 'de', 'fr'],
6  defaultLocale: 'en',
7  localePrefix: 'always'  // o 'as-needed'
8});

Tip de depuración: Agrega console.log al middleware para ver qué locale se está detectando:

TypeScript
1export default function middleware(request: NextRequest) {
2  console.log('Locale detectado:', request.nextUrl.pathname);
3  // ... resto del middleware
4}

Problema #5: Errores de desajuste de hidratación

Síntoma: La consola muestra "Text content does not match server-rendered HTML"

Esto sucede cuando el servidor y el cliente renderizan traducciones diferentes:

Causa 1: Usar un hook de cliente en un Server Component

TSX
1// ❌ Server Component usando hook de cliente
2// app/[locale]/page.tsx (Server Component por defecto)
3import { useTranslations } from 'next-intl';  // Esto está bien en realidad
4
5export default function Page() {
6  const t = useTranslations('home');
7  return <h1>{t('title')}</h1>;  // ¡Funciona en next-intl!
8}

Espera, esto realmente funciona en next-intl porque detecta el contexto. Pero con react-i18next:

TSX
1// ❌ Con react-i18next en Server Component
2'use server';
3import { useTranslation } from 'react-i18next';  // No funcionará
4
5// ✅ Usa función del lado del servidor
6import { getTranslations } from 'next-intl/server';
7
8export default async function Page() {
9  const t = await getTranslations('home');
10  return <h1>{t('title')}</h1>;
11}

Causa 2: Formato de fecha/hora sin zona horaria

TSX
1// ❌ El servidor puede estar en UTC, el cliente en zona horaria local
2{formatDate(new Date())}
3
4// ✅ Siempre especifica la zona horaria
5import { format } from 'date-fns-tz';
6{format(new Date(), 'PPP', { timeZone: userTimezone })}

Problema #6: El Provider no envuelve los componentes

Síntoma: "Could not find IntlProvider" u otros errores de contexto similares

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

Problema #7: Las claves dinámicas no funcionan

Síntoma: t(status.${value}) devuelve la clave, no la traducción

TSX
1// ❌ Algunas configuraciones no soportan claves dinámicas
2const key = `status.${order.status}`;
3t(key);  // Algunos bundlers no pueden optimizar esto
4
5// ✅ Usa un mapeo explícito
6const statusMessages = {
7  pending: t('status.pending'),
8  shipped: t('status.shipped'),
9  delivered: t('status.delivered')
10};
11return statusMessages[order.status];
12
13// ✅ O usa t.raw() para claves dinámicas en next-intl
14t(`status.${order.status}`);  // Esto realmente funciona en next-intl

Problema #8: La pluralización no funciona

Síntoma: Muestra "{count, plural, one {# item} other {# items}}" literalmente

Estás usando formato ICU pero la librería no lo está parseando:

JSON
1// Tu JSON
2{
3  "items": "{count, plural, one {# artículo} other {# artículos}}"
4}
TSX
1// ❌ No pasas la variable
2t('items');
3
4// ✅ Pasa la variable count
5t('items', { count: 5 });  // "5 artículos"

Verifica que tu librería soporte ICU

  • next-intl: Soporte completo de ICU ✅
  • react-i18next: Necesita el plugin i18next-icu
  • next-translate: Solo pluralización básica

Problema #9: Problemas de entorno/build

Las claves funcionan en dev, fallan en producción

TypeScript
1// ❌ Los imports dinámicos pueden fallar en tiempo de build
2const messages = await import(`@/messages/${locale}.json`);
3
4// ✅ Asegúrate de que todos los locales sean conocidos estáticamente
5import en from '@/messages/en.json';
6import es from '@/messages/es.json';
7
8const messages = { en, es };
9export const getMessages = (locale: string) => messages[locale];

"Module not found" después de agregar un nuevo locale

Después de agregar un nuevo archivo de locale, reinicia tu servidor de desarrollo. Next.js cachea la resolución de módulos.

Terminal
rm -rf .next && npm run dev

Problema #10: Los enlaces/navegación pierden el locale

Síntoma: Al hacer clic en enlaces internos se resetea al idioma por defecto

TSX
1// ❌ El Link normal pierde el locale
2import Link from 'next/link';
3<Link href="/about">Acerca de</Link>
4
5// ✅ Usa la navegación de next-intl
6import { Link } from '@/i18n/navigation';  // Tu navegación configurada
7<Link href="/about">Acerca de</Link>  // Preserva el locale automáticamente
8
9// ✅ O incluye el locale manualmente
10import { useLocale } from 'next-intl';
11const locale = useLocale();
12<Link href={`/${locale}/about`}>Acerca de</Link>

Lista de verificación para depuración

Cuando las traducciones fallen, pasa por esta lista:

VerificaciónComando/Acción
Sintaxis JSON válida`cat messages/en.json
La clave existeBusca la clave exacta en el archivo JSON
El archivo del locale existels messages/
El middleware se ejecutaAgrega console.log, revisa la salida del servidor
El Provider está en su lugarRevisa layout.tsx buscando IntlProvider
Import correctoServer: getTranslations, Client: useTranslations
Caché limpiorm -rf .next && npm run dev

Mensajes de error comunes decodificados

ErrorSignificadoSolución
Missing message: "key"La clave no está en el JSONAgrega la clave a todos los archivos de locale
Unable to find next-intl localeEl middleware no está configurando el localeRevisa la ubicación y configuración de middleware.ts
Hydration failedDesajuste servidor/clienteUsa el hook correcto para el tipo de componente
Cannot read property 't' of undefinedFalta el providerEnvuelve con IntlClientProvider
ENOENT: no such fileRuta del archivo incorrectaRevisa la ruta de la carpeta messages en la configuración

Prevención: Evita estos problemas por completo

1. Usa TypeScript para claves type-safe

TypeScript
1// Genera tipos desde tu JSON
2// Con next-intl, crea un archivo de tipos:
3type Messages = typeof import('./messages/en.json');
4declare global {
5  interface IntlMessages extends Messages {}
6}

Ahora TypeScript mostrará errores en claves inválidas.

2. Usa un sistema de gestión de traducciones

Herramientas como IntlPull automáticamente:

  • Validan la sintaxis JSON
  • Rastrean traducciones faltantes por locale
  • Previenen errores tipográficos con autocompletado
  • Sincronizan claves en todos los locales

3. Agrega verificaciones en CI

YAML
1# .github/workflows/i18n-check.yml
2- name: Validar archivos JSON
3  run: |
4    for f in messages/*.json; do
5      python -m json.tool "$f" > /dev/null || exit 1
6    done
7
8- name: Verificar claves faltantes
9  run: npx intlpull check --config intlpull.config.json

4. Tests de integración para rutas críticas

TypeScript
1// e2e/i18n.spec.ts
2test('la página de checkout renderiza en todos los locales', async ({ page }) => {
3  for (const locale of ['en', 'es', 'de']) {
4    await page.goto(`/${locale}/checkout`);
5    // No debería contener claves de traducción en crudo
6    await expect(page.locator('body')).not.toContainText('checkout.');
7  }
8});

Cuándo usar gestión de traducciones

Si experimentas estos problemas repetidamente, considera un TMS:

EscenarioArchivos JSON DIYGestión de traducciones
< 50 claves, 1-2 devs✅ Funciona bienExcesivo
> 200 claves, múltiples devs❌ Conflictos de merge✅ Única fuente de verdad
> 3 idiomas❌ Difícil sincronizar✅ Detección de claves faltantes
Traductores externos❌ Pesadilla de handoff de JSON✅ Flujo de trabajo integrado

IntlPull (plug descarado) está construido específicamente para esto. Se integra con tu flujo de trabajo de Git, detecta traducciones faltantes en CI, y soporta actualizaciones OTA para que no necesites redesplegar para corregir traducciones.


Preguntas frecuentes

¿Por qué mi función t() devuelve la clave en lugar de la traducción?

Tu clave de traducción no existe en el archivo JSON o namespace. Revisa errores tipográficos en la clave, verifica que la estructura JSON coincida con la ruta esperada en tu código, asegúrate de usar el namespace correcto, y valida que el archivo JSON se esté cargando. Este es el problema de i18n más común.

¿Por qué mis traducciones funcionan en inglés pero no en otros idiomas?

La clave de traducción falta en los otros archivos de locale. Cuando agregas una nueva clave a en.json, también debes agregarla a es.json, de.json, etc. Usa un TMS como IntlPull para detectar automáticamente traducciones faltantes, o configura locales de fallback en tu configuración.

¿Cómo soluciono los errores de desajuste de hidratación en Next.js i18n?

El servidor y el cliente están renderizando contenido diferente. Esto generalmente ocurre con formato de fecha/hora (el servidor está en UTC, el cliente en hora local), usando hooks de cliente en Server Components, o desajuste de locale entre servidor y cliente. Especifica zonas horarias explícitamente, usa los hooks correctos para el tipo de componente, y asegúrate de que el locale se pase correctamente.

¿Por qué mi middleware de Next.js no detecta el locale?

Tu patrón matcher del middleware no coincide con tus rutas. Asegúrate de que middleware.ts esté en la raíz del proyecto (no en /app), tu matcher incluya las rutas que necesitas, y el array de locales coincida con tus idiomas soportados. Agrega console.log al middleware para depurar qué se está detectando.

¿Cómo depuro errores de "Could not find IntlProvider"?

Tu componente no está envuelto con el provider de i18n. Verifica que NextIntlClientProvider (o el provider de tu librería) esté en tu app/[locale]/layout.tsx y envuelva todos los componentes hijos. El provider debe recibir los messages y el locale actual.

¿Por qué mi archivo JSON causa errores de "Unexpected token"?

Tu sintaxis JSON es inválida. Problemas comunes: comas finales, comillas simples en lugar de dobles, comillas sin escapar en los valores, o comas faltantes. Pasa tu JSON por un validador como python -m json.tool para encontrar la ubicación exacta del error.

¿Cómo hago que las traducciones funcionen en Server Components de Next.js?

Usa funciones de traducción del lado del servidor. En next-intl, usa getTranslations() de next-intl/server en Server Components. El hook regular useTranslations() funciona en next-intl gracias a la detección de contexto, pero para react-i18next necesitas funciones explícitas del lado del servidor.

¿Por qué mis traducciones funcionan en desarrollo pero fallan en producción?

Los imports dinámicos pueden fallar en tiempo de build. Asegúrate de que todos los archivos de locale existan antes del build, usa imports estáticos o imports dinámicos verificados, y limpia el caché .next antes de construir. Los builds de producción optimizan los imports de manera diferente que el modo dev.

¿Cómo evito que se pierda el locale al navegar?

Usa el componente Link de tu librería i18n en lugar del Link de Next.js. En next-intl, configura las exportaciones de navegación en i18n/navigation.ts e importa Link desde ahí. Alternativamente, incluye manualmente el locale en href: /${locale}/about.

¿Cómo pruebo que todas las traducciones existan?

Agrega verificaciones de CI que validen el JSON y detecten claves faltantes. Valida la sintaxis JSON con un linter, compara claves entre archivos de locale, y ejecuta tests de integración que verifiquen que las páginas no muestren claves de traducción en crudo. El CLI de IntlPull proporciona detección automatizada de traducciones faltantes.


Resumen

La mayoría de los problemas de i18n en Next.js caen en categorías predecibles:

  1. Errores tipográficos en claves → Usa tipos TypeScript y autocompletado
  2. JSON inválido → Valida en CI
  3. Datos de locale faltantes → Usa un TMS con verificación de sincronización
  4. Hook/contexto incorrecto → Sigue las reglas de Server vs Client Component
  5. Problemas de middleware → Revisa la ubicación del archivo y configuración del matcher

El ecosistema ha mejorado mucho. Con App Router y librerías modernas como next-intl, i18n es significativamente más confiable que con Pages Router. Pero sigue siendo código, y el código tiene bugs.

Construye defensivamente: claves type-safe, validación en CI, y buenas herramientas te ahorrarán horas de depuración.


¿Necesitas ayuda gestionando traducciones a escala? Comienza gratis con IntlPull — detección automática de traducciones faltantes, traducción con IA e integración perfecta con next-intl.

Tags
nextjs
i18n
troubleshooting
debugging
next-intl
react-i18next
localization
2026
IntlPull Team
IntlPull Team
Engineering

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