Porque tu configuración de i18n vive en tu cabeza y en tu config, no en el código que el agente tiene delante. Cursor regenera un componente a partir del contexto que tiene enfrente, y un texto JSX plano es lo más rápido que funciona. Nada en el repo se opone, así que el literal llega al build. globalize.now es infraestructura de localización impulsada por IA que cierra ese ciclo: extrae las cadenas nuevas, genera las claves y sincroniza las traducciones en cada push a Git.

Este es el fallo del que nadie te avisa. Configurar i18n es cosa de una tarde. El problema real es mantenerlo configurado mientras un agente reescribe tus componentes dos veces al día.

¿Por qué Cursor sigue añadiendo cadenas codificadas después de configurar i18n?

Tres cosas son ciertas al mismo tiempo, y juntas garantizan la fuga.

El agente ve un archivo, no una convención. Cuando le pides a Cursor que añada un panel de configuración, lee el componente que está editando y quizá un par de vecinos. Tu directorio de locales, tu esquema de namespaces y la regla de que todo texto orientado al usuario pasa por una función de traducción no están en esa ventana, a menos que tú los pongas ahí.

El texto sin traducir es el default estadístico. La inmensa mayoría del código React en los datos de entrenamiento de cualquier modelo escribe las cadenas en línea. Cuando el agente elige el token más probable para la etiqueta de un botón, Save changes le gana a t('settings.save') por pura frecuencia. Le estás pidiendo que elija el patrón menos común cada vez, sin ningún feedback si no lo hace.

Nada se rompe. Este es el factor decisivo. Una cadena codificada pasa el chequeo de tipos. Compila. Se renderiza. Pasa tus tests. No hay ninguna marca roja en tu pipeline que diga que un usuario en Alemania está a punto de ver un toast en inglés. El defecto es invisible hasta que alguien cambia de idioma y hace scroll.

Compáralo con un import que falta o un tipo de prop incorrecto, que el agente corrige en segundos. El texto sin traducir es la única clase de bug que tu cadena de herramientas acepta en silencio, así que es la única que se va acumulando.

¿Qué cadenas se filtran con más frecuencia?

Las que se añaden en último lugar, con menos revisión.

Recorre cualquier codebase asistido por IA que empezó siendo multilingüe y el inglés superviviente vive en sitios predecibles:

  • Toasts de error y de éxito, sobre todo los que se añaden mientras se corrige un bug sin relación
  • Estados vacíos y textos de carga, que se escriben una vez y nunca se revisan
  • Mensajes de validación de formularios, a menudo generados en línea dentro de un schema
  • Atributos de accesibilidad: aria-label, alt, title
  • Diálogos de confirmación y textos de acciones destructivas
  • Cualquier cosa dentro de un bloque catch

El patrón es consistente. Tus flujos principales se traducen porque los miras. Los bordes se quedan codificados porque nadie los abre en otro idioma.

También existe una versión de segundo orden de esto. Cuando un agente añade un literal junto a código ya traducido, a veces «se le ocurre» inventar una clave que no encaja con tu esquema de namespaces, así que terminas con errors.saveFailed en un archivo y settings_save_error en otro. Ahora tienes tanto cadenas sin traducir como un espacio de claves fragmentado. Si estás en una fase más temprana de este proceso, nuestra guía sobre cómo localizar una app generada por IA cubre la limpieza inicial.

¿Puede .cursor/rules o AGENTS.md detenerlo?

Ayudan, y deberías escribir uno, pero son orientación, no aplicación forzosa.

La capa de archivos de instrucciones se estandarizó hace poco. AGENTS.md nació como convención neutral respecto al proveedor en 2025 y ahora está gestionado por la Agentic AI Foundation, bajo la Linux Foundation. Lo leen de forma nativa Cursor, Codex, Copilot, Gemini CLI, Aider, Windsurf y Zed, lo que significa que un solo archivo en la raíz de tu repo llega a todos los agentes que use tu equipo. Cursor también admite reglas de proyecto con alcance limitado como archivos .mdc bajo .cursor/rules/, con frontmatter que controla la descripción, los globs de archivos y si la regla se aplica siempre. La configuración práctica en 2026 es un AGENTS.md en la raíz para portabilidad, más reglas de Cursor con alcance limitado donde el auto-attach realmente aporta valor.

Un bloque de i18n funcional se ve así:

## Internationalization

- Never write user-facing text as a literal in JSX or in a string passed to a UI component.
- All user-facing text goes through the translation function from `next-intl`.
- Keys follow `feature.element.state` in lowerCamelCase, e.g. `settings.save.error`.
- Add the English value to `messages/en.json` only. Never hand-edit other locale files.
- This includes toasts, empty states, validation messages, `aria-label`, `alt` and `title`.

Eso reducirá la tasa de fuga de forma medible. No la llevará a cero, porque los archivos de reglas compiten por atención con todo lo demás en la ventana de contexto, y las sesiones largas del agente se van desviando. Trata esta capa como una reducción de frecuencia, no como una garantía.

¿Cómo haces que una cadena sin traducir haga fallar el build?

Añade una regla de lint que trate un literal en JSX como un error, y ejecútala en integración continua.

La opción estándar es la regla no-literal-string de eslint-plugin-i18next. Por defecto valida el texto plano en el marcado JSX en lugar de cada literal del archivo, lo que la hace utilizable en un codebase real en vez de generar una pared de falsos positivos. Admite patrones de exclusión por expresión regular, exclusiones de callee para funciones que sabes que son seguras, y un modo solo de marcado.

Una configuración inicial:

// eslint.config.js
import i18next from 'eslint-plugin-i18next';

export default [
  {
    files: ['**/*.{jsx,tsx}'],
    plugins: { i18next },
    rules: {
      'i18next/no-literal-string': [
        'error',
        {
          markupOnly: true,
          ignoreAttribute: ['data-testid', 'className'],
          ignore: ['^[0-9]+$', '^\\s*$'],
        },
      ],
    },
  },
];

Ejecútala en tu pipeline, no solo en el editor:

npx eslint . --max-warnings=0

Ahora el defecto invisible tiene una marca roja. El agente recibe el mismo circuito de feedback para una cadena sin traducir que ya recibe para un error de tipos, lo que significa que puede corregir su propio error en la misma sesión en lugar de dejar que lo descubra un usuario en otro idioma. Si quieres la visión más amplia de cómo integrar i18n en un proyecto de Cursor desde cero, consulta nuestro tutorial de 15 minutos con Cursor y Next.js y la página de integración con Cursor.

¿Por qué no basta con una regla de lint que falle?

Porque un build en rojo te dice que existe una cadena. No la traduce.

En cuanto salta la regla, alguien todavía tiene que elegir una clave coherente con tus namespaces existentes, añadir el valor fuente en inglés y generar los demás idiomas. En un proyecto individual que se despliega varias veces al día, ese trabajo se va postergando. Y si se posterga lo suficiente, la solución honesta acaba siendo añadir el archivo a una lista de exclusión, momento en el que vuelves al punto de partida con configuración de más.

Esta es la brecha que convierte un problema resuelto en deuda recurrente. Las capas uno y dos cambian la frecuencia con la que aparece el problema y la rapidez con la que te enteras. Ninguna de las dos hace el trabajo.

¿Cómo es la configuración de tres capas?

Instrucción, aplicación y automatización. Cada capa detecta lo que se le escapó a la anterior.

Capa uno, instrucción. Escribe el bloque de i18n en el AGENTS.md de la raíz de tu repo, y añade un .cursor/rules/i18n.mdc con alcance limitado y globs para tus directorios de componentes. Coste: quince minutos, una sola vez. Efecto: se escriben menos literales desde el principio.

Capa dos, aplicación. Activa no-literal-string y haz que falle la integración continua si se dispara. Coste: una tarde ajustando patrones de exclusión en un codebase existente. Efecto: ningún literal llega a tu rama principal sin detectarse, y el agente puede autocorregirse.

Capa tres, automatización. Deja que la extracción, la generación de claves y la traducción sucedan sin ti. Esta es la capa que ocupa globalize.now. Se sitúa por encima de tu runtime de i18n en lugar de sustituirlo: next-intl o i18next siguen renderizando las cadenas. globalize.now detecta el texto codificado nuevo, genera claves coherentes con los namespaces que ya existen en tu proyecto, las traduce a todos los idiomas configurados y confirma los archivos de traducción actualizados en cada push a Git. Lo configuras una vez —conecta el repositorio en globalize.now— y ya no queda ningún paso manual que puedas saltarte. También puedes instalarlo en tu agente con:

npx skills add globalize-now/globalize-skills

Los planes empiezan en 5 € al mes; el plan Starter, de 20 €, incluye 20 € de crédito de traducción (unas 200.000 palabras) cada mes, y el uso por encima del volumen incluido se factura a la misma tarifa por carácter. Sin cargos por asiento ni por idioma, y el registro incluye 5 € de crédito de traducción, sin tarjeta. Hay más detalle para ingenieros en la página para desarrolladores, y la versión más breve para quienes lanzan apps con herramientas de IA está en la página de Vibe Coders.

El sentido de las tres capas es que ninguna depende de que tu disciplina aguante durante cien sesiones de agente. El archivo de reglas baja la tasa, la regla de lint hace que los fallos sean ruidosos, y la sincronización hace que la corrección salga gratis. Esa combinación es lo que mantiene multilingüe a una app mientras un agente no deja de reescribirla.

Tu configuración de i18n no es la parte frágil. Lo frágil es el ciclo entre que el agente escribe una cadena y esa cadena llega a un archivo de traducción. globalize.now cierra ese ciclo en cada push.

globalize.now convierte el texto codificado de tu app en archivos de traducción listos para localizar y los mantiene actualizados en cada despliegue.

Prueba globalize.now gratis