Implementar i18n con Claude Code: guía práctica para Next.js
Flujo práctico para implementar i18n en Next.js con Claude Code, next-intl, validación de traducciones y errores comunes.
Antes de traducir, define la arquitectura
i18n significa internacionalización: preparar una aplicación para funcionar en varios idiomas y regiones. En producción no basta con crear archivos JSON traducidos. También hay que decidir la estructura de URL, cómo se detecta el idioma, qué ocurre con el SEO, cómo se formatean fechas y monedas, cómo se revisan los enlaces internos y qué comando falla cuando falta una traducción. Claude Code ayuda mucho porque puede leer el proyecto, editar varios archivos, ejecutar pruebas y entregar un resumen de riesgos. Pero necesita una instrucción concreta.
El error que Masa ha visto en sitios de contenido y páginas SaaS es pedir “traduce todo” demasiado pronto. El resultado parece avanzado, pero semanas después aparecen claves sin usar, metadata sin traducir y URLs imposibles de revisar. La forma más robusta es pedir a Claude Code una base completa: routing, mensajes, migración de páginas, script de verificación y checklist de revisión. Esa base convierte el i18n en un flujo repetible, no en una colección de textos sueltos.
Este artículo usa Next.js App Router con next-intl. Para revisar el código, conviene tener abiertas las referencias oficiales: documentación de Claude Code, setup de routing en next-intl, guía de internacionalización de Next.js y referencia Intl de MDN. Son importantes porque las versiones recientes de Next.js usan proxy.ts, mientras que proyectos anteriores pueden conservar middleware.ts.
flowchart LR
A[Requisitos] --> B[Routing]
B --> C[Mensajes JSON]
C --> D[Páginas y metadata]
D --> E[Verificación]
E --> F[SEO y publicación]
Configuración mínima con Next.js y next-intl
El ejemplo usa ja como idioma predeterminado y añade en y de. La estructura mantiene el routing, la navegación y los mensajes separados para que la revisión sea clara.
src/
app/
[locale]/
layout.tsx
page.tsx
i18n/
navigation.ts
request.ts
routing.ts
messages/
ja.json
en.json
de.json
proxy.ts
routing.ts concentra los idiomas soportados y las rutas localizadas. Esta decisión evita reemplazos de cadenas dispersos por componentes.
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['ja', 'en', 'de'],
defaultLocale: 'ja',
pathnames: {
'/': '/',
'/pricing': {
ja: '/pricing',
en: '/pricing',
de: '/preise',
},
'/docs': {
ja: '/docs',
en: '/docs',
de: '/dokumentation',
},
},
});
export type Locale = (typeof routing.locales)[number];
La navegación debe pasar por los helpers de next-intl. Así los enlaces, redirects y cambios de idioma respetan la misma configuración.
// src/i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);
request.ts lee el idioma solicitado y carga el JSON correspondiente. Si llega un locale no soportado, cae al idioma predeterminado.
// src/i18n/request.ts
import { hasLocale } from 'next-intl';
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});
En Next.js 16 el archivo esperado es proxy.ts. Si el proyecto sigue en una versión anterior, pide a Claude Code que confirme si debe usar middleware.ts.
// src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};
Usar las claves en páginas reales
No conviene poner todos los textos en common. Ese archivo debe contener navegación, selector de idioma y botones realmente compartidos. Los textos de cada pantalla deberían vivir en espacios como HomePage, PricingPage o DocsPage. La palabra “namespace” se refiere a ese grupo de claves relacionadas.
{
"common": {
"language": {
"label": "Idioma de visualización",
"ja": "日本語",
"en": "English",
"de": "Deutsch"
},
"nav": {
"docs": "Documentación",
"pricing": "Precios"
}
},
"HomePage": {
"title": "Entrega conocimiento del equipo en varios idiomas",
"lead": "Muestra {count} artículos en el idioma del lector.",
"cta": "Reservar una consulta"
}
}
En una página del App Router, getTranslations funciona bien en Server Components y también sirve para metadata traducida.
// src/app/[locale]/page.tsx
import { getTranslations, setRequestLocale } from 'next-intl/server';
type Props = {
params: Promise<{ locale: string }>;
};
export default async function HomePage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations({ locale, namespace: 'HomePage' });
return (
<main>
<h1>{t('title')}</h1>
<p>{t('lead', { count: 42 })}</p>
<a href={`/${locale}/pricing`}>{t('cta')}</a>
</main>
);
}
Para un selector de idioma, conserva la ruta actual y cambia solo el locale. No hagas reemplazos manuales de texto como cambiar /en por /ja, porque una ruta como /enquiry podría romperse.
Evitar huecos de traducción con CI
Cuando Claude Code genera mensajes, los revisores suelen detectar una frase rara, pero no siempre detectan que falta una clave. El siguiente script compara el idioma base con todos los demás.
// scripts/check-translations.mjs
import { readdir, readFile } from 'node:fs/promises';
const messagesDir = new URL('../src/messages/', import.meta.url);
const baseLocale = 'ja';
function flattenKeys(value, prefix = '') {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return [prefix];
}
return Object.entries(value).flatMap(([key, child]) => {
const nextPrefix = prefix ? `${prefix}.${key}` : key;
return flattenKeys(child, nextPrefix);
});
}
async function readMessages(locale) {
const file = new URL(`${locale}.json`, messagesDir);
return JSON.parse(await readFile(file, 'utf8'));
}
const files = await readdir(messagesDir);
const locales = files.filter((file) => file.endsWith('.json')).map((file) => file.replace(/\.json$/, ''));
const baseKeys = new Set(flattenKeys(await readMessages(baseLocale)));
let hasError = false;
for (const locale of locales.filter((item) => item !== baseLocale)) {
const targetKeys = new Set(flattenKeys(await readMessages(locale)));
const missing = [...baseKeys].filter((key) => !targetKeys.has(key));
const extra = [...targetKeys].filter((key) => !baseKeys.has(key));
if (missing.length || extra.length) {
hasError = true;
console.error(`\n${locale}.json has translation key drift`);
if (missing.length) console.error('Missing:', missing.join(', '));
if (extra.length) console.error('Extra:', extra.join(', '));
}
}
if (hasError) process.exit(1);
console.log(`Translation keys are aligned for ${locales.length} locales.`);
Este script no evalúa si una traducción suena natural. Su valor es otro: impedir que una página llegue a producción con botones o metadata incompletos. Después, la revisión humana puede concentrarse en tono, claridad, precio, legal y adaptación cultural.
Tres casos de uso reales
El primer caso es una página de precios SaaS. Allí se mezclan moneda, impuestos, periodo de facturación y nombres de planes. Pide a Claude Code que use Intl.NumberFormat o formatters de next-intl, no concatenación manual de strings. La frase final debe revisarse porque el orden natural cambia entre español, japonés, inglés y alemán.
El segundo caso es una documentación técnica. Términos como middleware, proxy, locale o namespace no siempre se traducen. Lo mejor es explicarlos la primera vez con español claro y conservar el término original para que el lector pueda buscarlo en la documentación oficial.
El tercer caso es un panel de administración. Aquí el riesgo principal no es el SEO, sino una operación equivocada. “Eliminar”, “desactivar” y “revocar invitación” deben ser precisos. Los botones, modales de confirmación, notificaciones y registros de auditoría deben estar en el mismo sistema de claves.
Un cuarto caso habitual es un blog multilingüe. No basta traducir el cuerpo: title, description, canonical, enlaces alternativos, OGP e internos también deben moverse juntos. Puedes documentar esa regla en CLAUDE.md best practices para que Claude Code la respete en cada actualización.
Errores comunes y resultado de verificación
| Error | Qué rompe | Solución práctica |
|---|---|---|
| Decidir las URLs al final | Redirecciones y páginas indexadas confusas | Elegir prefix, subdominio o dominio desde el inicio |
| Claves genéricas | title2 y text3 no se pueden revisar | Nombrar por pantalla y significado |
| Depender demasiado del fallback | Las traducciones faltantes se ocultan | Fallar CI si falta una clave |
| Concatenar fechas y monedas | Separadores y orden incorrectos | Usar Intl o formatters |
| Olvidar metadata | El resultado de búsqueda queda en otro idioma | Traducir generateMetadata |
Probé el script con un conjunto pequeño de mensajes y eliminé HomePage.cta del archivo inglés. La verificación falló y mostró la clave faltante. Lo que no puede decidir es si “Reservar una consulta” es el CTA adecuado para todos los mercados. Esa es la división correcta: Claude Code y CI detectan errores mecánicos; las personas revisan tono, precios, legal y cultura. Para cerrar el flujo, combínalo con el checklist de revisión de Claude Code o empieza por la consulta de ClaudeCodeLab.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.