La sección de i18n de abajo es la que debes pegar en AGENTS.md: diez reglas cortas que le indican a un coding agent que pase toda cadena visible para el usuario por la función de traducción, que la nombre por feature, que nunca concatene fragmentos, que nunca simule plurales con un ternario y que nunca toque un archivo de locale generado. globalize.now es infraestructura de localización impulsada por IA, así que leemos mucho i18n escrito por agentes, y casi todo se reduce a los mismos pocos errores. Cada regla corresponde a uno de ellos.

La segunda mitad de este post trata de dónde deja de funcionar el archivo. Dos de las superficies de código que más usan los desarrolladores nunca lo leen, y ninguna redacción lo arregla.

¿Qué debe decir la sección de i18n de AGENTS.md?

Este bloque. Ajusta las tres rutas de archivo y el comando de lint a tu proyecto y deja el resto como está.

## Internationalization

- Every user-visible string goes through the translation function. That
  includes JSX text, input hint text, `title`, `aria-label`, `alt`, toast and
  error messages, empty states and the document `<title>`.
- The source locale is `en`. The catalog is `locales/en.json`. Add the key in
  the same change that uses it.
- Keys are namespaced by feature: `checkout.summary.total`, not `total3`.
  Search the catalog for an existing key before creating one.
- Never concatenate translated fragments. Use interpolation:
  `t('cart.items', { count })`, not `t('cart.you_have') + count`.
- Plurals use the library's plural forms (`_one` / `_other` keys, or ICU
  `{count, plural, ...}`). Never `count === 1 ? 'item' : 'items'`.
- Dates, numbers and currency go through `Intl.DateTimeFormat` and
  `Intl.NumberFormat` with the active locale. Never a bare
  `toLocaleString()`, never a currency symbol glued to a number.
- Do not hardcode the locale list, language names or text direction in
  components. Read them from `i18n/config.ts`.
- Do not translate brand names, code identifiers, URLs or environment values.
- Do not edit locale files other than `en`. They are generated. If a
  translation looks wrong, fix the source key or the glossary, not the file.
- Before finishing, run `npm run lint`. The `i18next/no-literal-string`
  rule must pass.

Diez reglas es una decisión deliberada. La propia documentación de reglas de Cursor recomienda centrarlas en patrones que uses con frecuencia y usar un linter en lugar de pegar una guía de estilo, y la guía de Claude Code dice lo mismo: instrucciones lo bastante concretas como para verificarse, y un archivo corto, porque el cumplimiento baja a medida que crece. Una sección de i18n con cuarenta puntos se lee en diagonal.

¿Por qué estas diez reglas y no un simple «usa i18n»?

Porque «usa i18n» es justo lo que el agente ya cree estar haciendo cuando escribe <Button>Save changes</Button>. Las instrucciones vagas se cumplen según la idea que el propio agente tiene de cumplir. Cada regla de abajo nombra una salida concreta y la prohíbe.

Literales en atributos. Los agentes aprenden que los children de JSX hay que traducirlos y luego escriben aria-label="Close" y alt="Company logo" como cadenas planas, porque los atributos parecen configuración y no texto. Listar los atributos por nombre cierra esa brecha; un «traduce todo» genérico, no.

Concatenación. t('greeting') + ' ' + name + '!' se ve bien en inglés, pero no se puede traducir a ningún idioma que ponga el nombre primero o que lo decline. La interpolación le da al traductor una sola cadena con una sola variable que mover.

Plurales con ternarios. count === 1 ? 'item' : 'items' es lo que más vemos escribir a un agente una vez configurado el i18n, y es incorrecto para cualquier idioma con más de dos formas de plural. La regla nombra el patrón para que el agente pueda reconocerlo. Si tu catálogo usa ICU, qué es ICU MessageFormat y dónde falla en apps generadas por IA cubre la sintaxis.

Archivos de locale generados. Esta regla existe porque los agentes son serviciales. Si le pides que corrija una errata en alemán, el agente abrirá locales/de.json y la editará, y funciona hasta que el siguiente trabajo de traducción regenera el archivo desde el origen. Decirle que los archivos que no son de origen están generados, y dónde va la corrección real, evita toda una clase de regresiones silenciosas.

Editar en en el mismo cambio. Sin esto, el agente añade t('checkout.summary.total') y sigue adelante, y la clave se renderiza con su propio nombre hasta que alguien se da cuenta. La entrada del catálogo y su primer uso van en el mismo diff.

¿Dónde lee realmente el archivo cada herramienta?

El mismo contenido va en sitios distintos según el agente, y la ubicación decide si la regla está siempre en contexto o si se carga solo cuando hace falta.

AGENTS.md en la raíz del repositorio es la ubicación compartida. El formato es Markdown plano sin campos obligatorios, lo gestiona la Agentic AI Foundation bajo la Linux Foundation y lo leen Cursor, Codex, el coding agent de Copilot y muchos más. La especificación admite archivos anidados, y gana el más cercano al archivo que se edita.

Cursor lee AGENTS.md directamente, incluidos los anidados, y además tiene su propio formato con scope. Una regla limitada a archivos de UI se queda fuera del contexto hasta que se abre un archivo que coincida:

---
description: i18n rules for user-visible strings
globs: src/**/*.tsx, src/**/*.jsx
alwaysApply: false
---
(paste the Internationalization section here)

El archivo debe terminar en .mdc y estar en .cursor/rules/; un .md plano en esa carpeta se ignora porque no lleva frontmatter.

GitHub Copilot en VS Code tiene tres opciones. .github/copilot-instructions.md se aplica a cada solicitud de chat. Un archivo .github/instructions/i18n.instructions.md con applyTo: "**/*.tsx,**/*.jsx" en el frontmatter se aplica solo cuando se crean o modifican esos archivos. Y AGENTS.md en la raíz del repositorio lo lee el Local agent cuando la opción chat.useAgentsMdFile está activada; los archivos AGENTS.md anidados en subcarpetas dependen de otra opción experimental, chat.useNestedAgentsMdFiles, que está desactivada por defecto. El comportamiento específico de Copilot se explica en cómo añadir i18n a una app creada con GitHub Copilot.

Claude Code lee CLAUDE.md y, desde la v2.1.277, lee por su cuenta un AGENTS.md en la raíz cuando el proyecto no tiene ningún CLAUDE.md por encima del directorio de trabajo. Si mantienes ambos, el patrón documentado es un import @AGENTS.md al principio de CLAUDE.md para que el archivo se cargue una sola vez. Para el scope, .claude/rules/i18n.md con una lista paths: en el frontmatter se carga solo cuando Claude lee un archivo que coincide. El flujo completo de Claude Code está en cómo localizar una app generada con Claude Code.

El arreglo práctico para un repo en el que tocan varios agentes: las diez reglas en un archivo con scope para el agente que más use tu equipo, y un puntero de dos líneas en AGENTS.md («Cadenas de UI: consulta las reglas de i18n en .cursor/rules/i18n.mdc y síguelas») para que cualquier agente sin soporte de scope reciba igualmente la instrucción.

¿Qué superficies de código nunca leen AGENTS.md?

Los autocompletados en línea. Ambos proveedores lo documentan, y es la razón por la que el archivo de reglas no puede ser toda la respuesta.

El FAQ de reglas de Cursor responde con un no rotundo a «¿Las reglas afectan a Cursor Tab u otras funciones de IA?». Las reglas alimentan a Agent; Tab, el autocompletado que termina la línea que estás escribiendo, no las ve. La página de instrucciones personalizadas de VS Code incluye la misma nota para Copilot: las instrucciones no se tienen en cuenta en las sugerencias en línea mientras escribes en el editor.

Es decir, la superficie en la que un desarrollador escribe <p>No results found</p> y acepta el texto fantasma es justo la que las reglas nunca alcanzan. El chat y el modo agente sí asignarán una clave a la cadena; el autocompletado que terminó la línea mientras pensabas en otra cosa, no. A lo largo de una semana normal de trabajo, la base de código acumula literales de la única vía que el archivo de instrucciones no cubre, y el desarrollador, que escribió un archivo de reglas cuidadoso, asume que el agente lo está ignorando.

Claude Code no tiene superficie de autocompletado en línea, pero su documentación plantea el mismo punto desde el otro lado: el contenido de CLAUDE.md es contexto, no configuración que se haga cumplir, y para bloquear una acción decida lo que decida el modelo se usa un hook. Ese es el modelo mental correcto para todas las herramientas de aquí. Los archivos de instrucciones desplazan probabilidades. No hacen cumplir nada.

¿Qué hace cumplir la regla de verdad?

Una regla de lint en CI, y es un añadido de una línea una vez que el archivo de reglas ya le dice al agente que la ejecute. eslint-plugin-i18next incluye no-literal-string; actívala en los directorios de tus componentes y un literal en JSX hará fallar el build en lugar de recibir un recordatorio amable. La configuración, las opciones de la regla y por qué instrucción y cumplimiento son dos capas distintas se explican en por qué Cursor sigue añadiendo cadenas hardcodeadas después de configurar i18n, así que aquí no las repetimos.

El lint te da dos cosas que el archivo de reglas no puede. Atrapa la salida del autocompletado en línea, porque se ejecuta sobre el archivo, no sobre la conversación. Y convierte el propio bucle del agente en la solución: la última regla del bloque de arriba le indica al agente que ejecute lint antes de terminar, y un agente que ve fallar no-literal-string asignará la clave a la cadena él mismo en esa misma sesión.

En el caso concreto de Claude Code, un hook PreToolUse o posterior a la edición que ejecute el linter sobre el archivo recién escrito es el mecanismo de cumplimiento al que apunta su documentación. Los hooks se ejecutan como comandos de shell en puntos fijos y se aplican tanto si el modelo decidió seguir la regla como si no.

¿Qué pasa con las cadenas que aun así se cuelan?

Hay que extraerlas, asignarles una clave según los namespaces existentes y traducirlas, y eso es lo que merece la pena automatizar en lugar de repetir el prompt. Un build en rojo te dice que existe un literal. Alguien tiene que convertirlo en una clave, añadirla a en y llevarla a todos los demás idiomas.

En esa capa se sitúa globalize.now. La conversión se hace una sola vez y dentro de la app: conectas el repositorio, el codebase se convierte una vez y el catálogo te llega como una pull request que revisas. A partir de ahí, los jobs de push traducen las nuevas unidades del catálogo conforme llegan, así que una clave añadida en la PR del martes tiene sus traducciones en la del miércoles. La biblioteca de runtime, el formato del catálogo y el archivo de reglas de arriba son todos tuyos; la referencia para desarrolladores explica cómo los agentes de Cursor, Claude Code, Codex y Copilot asumen su papel tras la configuración, y la integración con Cursor es la guía más corta.

Dos modos de fallo relacionados tienen su propio post. Si el problema es que el agente reescribe texto que ya tenía clave, eso es cómo evitar que los agentes de IA reescriban el copy de tu UI, y la solución es la misma disciplina de source locale que aplica la regla nueve de arriba. Si los locales existen pero siguen divergiendo, por qué los archivos de traducción se desincronizan explica el fallo de enrutamiento que hay detrás.

¿Por dónde empiezas?

Pega el bloque, ponle scope a tus directorios de UI y activa no-literal-string en CI esa misma tarde. Después observa lo que atrapa la regla de lint durante una semana; esa es la medida de lo que el archivo de reglas iba a lograr por sí solo. Si estás lanzando una app creada con IA y quieres que la extracción y la traducción de todo lo que aún se cuela queden resueltas en lugar de mantenerlas tú, la página de Vibe Coders es el resumen y los precios tienen su propia página.

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