El copy de la UI se mantiene coherente cuando cada cadena visible para el usuario vive en un único catálogo del repo, se referencia por clave y solo cambia mediante un diff revisado. Es la misma disciplina que ya aplicas a la lógica, aplicada a la redacción. Todo lo demás en esta página es la práctica que lo hace funcionar.
Esta es la guía operativa. La arquitectura que hay detrás, y el argumento de por qué el codebase debería ser la fuente de verdad del copy del producto, están en copy de producto nativo del código. Lee eso si quieres el porqué. Sigue aquí si quieres el cómo.
¿Dónde debe vivir el copy de la UI?
En un archivo de catálogo por idioma de origen, dentro del repositorio, junto al código que lo renderiza. No en una hoja de cálculo, ni en un archivo de diseño, ni en una página de wiki que era correcta en marzo. El catálogo es el sitio al que miran quien desarrolla, quien revisa, quien traduce y el agente de programación cuando quieren saber qué dice una pantalla.
Las reglas prácticas que se derivan de ahí:
- Un catálogo, un locale de origen. Un único
en.json(o.po,.ftl,.strings, lo que lea tu biblioteca de i18n) es el copy de referencia. Si tienes varias apps en un monorepo, cada app tiene el suyo, y las cadenas compartidas van a un catálogo compartido que ambas importan. Lo que evitas son dos archivos que puedan contener el mismo mensaje. - Agrupa por pantalla o funcionalidad, no por componente. Claves como
checkout.summary.totalenvejecen mejor queSummaryCard.totalLabel, porque los componentes se renombran y se dividen mientras que la pantalla y el mensaje permanecen. - Nombra la clave según el mensaje, no según la redacción.
checkout.submitsobrevive a un cambio de «Realizar pedido» a «Comprar ahora».checkout.placeOrderButtonno, y el desajuste entre clave y texto es lo que lleva a quien revisa después a suponer que son dos cadenas distintas. - Mantén el catálogo en la misma pull request que el código que lo usa. Una cadena añadida en una PR y su componente en otra es una cadena que se perderá, se olvidará o se duplicará.
Si tus cadenas siguen siendo literales dentro de los componentes, lo primero es crear el catálogo. Hacerlo a mano es un proyecto en sí mismo, y de ahí el argumento para conectar el repositorio y dejar que la conversión lo haga, que se explica más abajo.
¿Por qué referenciar por clave es mejor que buscar el literal?
Porque una clave solo puede resolverse a una cadena, y un literal se puede escribir de tantas formas como desarrolladores hay. «Iniciar sesión», «Iniciar Sesión», «Inicia sesión» y «Iniciar sesion» son cuatro cadenas para una herramienta de búsqueda y una sola intención para el usuario. Cuando cada componente llama a t("auth.signIn"), la pregunta de qué redacción es la correcta se responde una vez, en un solo sitio, y la respuesta se aplica en todas partes donde se use la clave.
Dos hábitos mantienen esto en marcha:
- Reutiliza antes de añadir. Antes de crear una clave, busca el mensaje en el catálogo. Si ya existe
common.cancel, undialog.cancelButtonnuevo con el mismo texto es el comienzo de la deriva, no un duplicado inofensivo. Los dos se editarán por separado en cuanto alguien cambie uno de ellos. - No concatenes.
t("cart.items") + " " + countproduce un texto que ninguna línea del catálogo describe. Usa la interpolación y las formas de plural de tu biblioteca para que la frase completa sea una única unidad revisable.
La excepción a la que recurre la gente es «esta cadena solo aparece una vez, así que un literal vale». Hoy aparece una vez. El componente se copiará, la pantalla tendrá una hermana, y el literal viajará con ella y luego divergirá.
¿Cómo se revisan los cambios de copy en una pull request?
Trata una línea de catálogo modificada igual que una firma de función modificada: algo que quien revisa lee, cuestiona y aprueba. Esta es la práctica que convierte la coherencia de un problema de memoria en un problema de revisión, y la revisión es el único momento en el que alguien presta atención de forma fiable.
Qué hace que la revisión del catálogo funcione:
- Un cambio de redacción es su propia línea en el diff. Eso solo se cumple si la cadena está en el catálogo. Un cambio de redacción dentro de un componente también es una línea del diff, pero se esconde entre cambios de lógica y se pasa por alto como «solo texto».
- Dirige los cambios del catálogo a un responsable del copy. Una entrada
CODEOWNERSsobre la ruta del catálogo hace que quien se encarga de la redacción, ya sea una persona de producto, un content designer o el desarrollador al que más le importa, sea solicitado en cada cambio. Todo lo demás de la PR sigue la revisión normal. - Haz tres preguntas por cada línea modificada. ¿Es este el término aprobado para esto? ¿Encaja en tono y mayúsculas con las cadenas que tiene alrededor? ¿La clave sigue describiendo el mensaje? Quien revisa y se hace esas tres preguntas atrapa la mayor parte de lo que una guía de estilo existe para evitar.
- Rechaza los renombrados silenciosos. Una PR que cambia
auth.signInde «Iniciar sesión» a «Acceder» sin ninguna explicación es una decisión de copy tomada por quien estuviera editando en ese momento. Necesita una frase en la descripción de la PR que explique por qué, o hay que revertirla.
El fallo que esto previene tiene nombre. La deriva del copy (copy drift) es el texto visible para el usuario que se aleja de su fuente aprobada entre superficies y con el paso del tiempo, sin una única fuente de verdad de la que alejarse. Qué es la deriva del copy explica las causas; la revisión del catálogo en el diff es la práctica que detiene la mayoría.
¿Cómo evitas que se cuelen cadenas codificadas nuevas?
Haz que un literal nuevo rompa el build. Las reglas y las revisiones atrapan lo que la gente se acuerda de mirar. Una regla de lint atrapa el resto, en cada pull request, sin que nadie tenga que acordarse.
La configuración es pequeña:
- Activa una regla no-literal-string.
eslint-plugin-i18nextincluye una para proyectos de JavaScript y TypeScript. Limítala al texto JSX y a los atributos visibles para el usuario, como los hints de los inputs,title,aria-labelyalt, y marca como exentos los archivos de test y las stories de Storybook para que la regla mantenga su credibilidad. - Ejecútala donde se hacen los merges. Un hook de pre-commit es cómodo; un paso de CI es el que cuenta, porque se ejecuta en la pull request aunque el editor de quien la escribe no estuviera configurado.
- Añade una comprobación de claves sin usar y claves que faltan. La mayoría de las bibliotecas de i18n tienen una herramienta complementaria que lista las claves referenciadas en el código pero ausentes del catálogo, y las claves del catálogo que no se referencian en ningún sitio. Ejecútala también en CI. Las claves sin usar son el sitio donde se esconde la redacción obsoleta.
- Empieza siendo estricto y permite excepciones mediante comentarios. Una regla desactivada en toda la carpeta
components/no es una regla. Una regla desactivada en una línea con su motivo es documentación.
Los equipos que desarrollan con herramientas de programación con IA lo sufren más, porque esas herramientas regeneran componentes a partir del contexto local y traen de vuelta los literales. Por qué Cursor AI sigue añadiendo cadenas codificadas repasa la regla de lint en detalle, incluida la configuración en tres capas de archivo de reglas, linter y CI.
¿Qué debe incluir una guía de estilo del copy para desarrolladores?
Un archivo corto en el repositorio que quepa en una pantalla, defina la voz, fije los términos e indique dónde van las cadenas. Las guías de estilo largas viven en una wiki y se leen una vez. Una corta vive junto al código y la leen personas y herramientas cada vez que se añade una cadena.
Un COPY.md funcional (o una sección en tu guía de contribución actual) cubre:
- La voz, en tres frases. A quién se parece el producto al hablar, cuán formal es, si dice «tú» o «el usuario». Lo suficiente para zanjar la mayoría de las discusiones.
- Los términos que nunca varían. El nombre que el producto da a cada funcionalidad, los verbos de las acciones principales (¿«Guardar» o «Actualizar»? ¿«Eliminar» o «Quitar»?) y las palabras que has decidido no usar.
- Reglas de mayúsculas y puntuación. Sentence case o title case en botones y titulares, punto final en los tooltips o no, cómo se escriben los números y las fechas.
- Dónde van las cadenas y cómo se nombran las claves. La ruta del catálogo, la regla de agrupación, la regla de nombres de claves y «busca antes de añadir».
- Qué hacer cuando hay dudas. A quién preguntar, o qué clave reutilizar mientras tanto.
Mantenlo en el repositorio para que un archivo de reglas pueda apuntar a él, quien revisa pueda enlazarlo en un comentario y un coding agent pueda leerlo antes de escribir una etiqueta.
¿Cómo evitas que los agentes de programación con IA rompan la coherencia?
Dale al agente una fuente de verdad que leer y una barrera que lo detenga cuando no la lea. Un agente que genera un formulario escribirá sin problema «Enviar» en una pantalla y «Mandar» en la siguiente, porque cada una era la palabra localmente más probable. No es descuido; no tiene nada con lo que ser coherente.
Dos movimientos arreglan casi todo. Primero, un archivo de reglas (AGENTS.md, .cursor/rules, CLAUDE.md, el que lean tus herramientas) que diga: el texto visible para el usuario va en el catálogo, reutiliza las claves existentes y lee COPY.md antes de escribir una etiqueta. Segundo, la regla de lint de la sección anterior, porque un archivo de reglas reduce las suposiciones y una regla de lint atrapa las que se colaron.
Eso es todo lo que esta página tiene que decir sobre los agentes. El problema concreto de un agente que reformula cadenas que ya existían, y cómo hacer que esa edición sea visible en lugar de silenciosa, es un tema aparte: cómo evitar que los agentes de programación con IA reescriban el copy de tu UI.
¿Dónde encaja la localización?
Coherencia y localización dependen de lo mismo, una fuente acordada, así que el catálogo que te da una te da la otra. Un equipo que no sabe decir cuál de tres formulaciones es la aprobada tampoco puede traducir ninguna correctamente. Arregla la fuente y la traducción pasa a ser un artefacto derivado del catálogo, no un proyecto aparte con su propia copia de la verdad.
En la práctica, eso significa que el catálogo que creaste para lograr coherencia ya es el archivo con el que trabaja quien traduce o una herramienta de traducción. Cada clave recibe sus equivalentes en cada idioma de destino, se aplican los mismos nombres de clave y un cambio de redacción en el catálogo de origen se ve como un cambio que las traducciones deben seguir. Cuando esas traducciones se quedan rezagadas respecto a la fuente, aparece el tipo de problema que describe por qué los archivos de traducción se desincronizan, y la solución es la misma disciplina otra vez: un catálogo, claves, cambios en el diff.
Dónde encaja globalize.now
globalize.now es un sistema de gestión de copy nativo del código con localización integrada, y cada práctica de esta página es lo que da por supuesto sobre tu repositorio. Conectas un repositorio de GitHub o GitLab en la app. Un escaneo averigua qué hay. Si las cadenas son literales dentro de los componentes, una conversión única las mueve detrás de claves a un catálogo y añade la configuración de i18n si no hay ninguna. Si ya usas next-intl, react-i18next, i18next o Lingui, trabaja con lo que tienes. El resultado llega como una pull request en su propia branch, de modo que el primer catálogo pasa exactamente por la revisión descrita arriba. Después, las cadenas de origen nuevas que añadas se traducen mediante jobs de push que abren una pull request con el resultado. El repositorio sigue siendo la única fuente de verdad.
Estamos trabajando para que la misma disciplina se mantenga en el sitio web de marketing, la página de destino y el producto a partir de una sola fuente, de modo que una funcionalidad se describa igual en todos los lugares donde el usuario la encuentra. Ahí es hacia donde vamos, no es algo que se pueda contratar hoy. Los precios están en la página de precios.
Una checklist que puedes adoptar esta semana
- Crea un catálogo por idioma de origen, en el repositorio, y decide la regla de nombres de claves.
- Mueve a él las cadenas de las pantallas que más modificas; haz referencia a ellas por clave.
- Activa una regla de lint llamada no-literal-string, limitada al texto visible para el usuario, y ejecútala en CI.
- Añade una línea
CODEOWNERSsobre la ruta del catálogo. - Escribe
COPY.md: voz, términos fijos, reglas de mayúsculas, dónde van las cadenas. Una pantalla. - Apunta el archivo de reglas de tu agente al catálogo y a
COPY.md. - Rechaza en la revisión cualquier cambio de redacción que no tenga un motivo en la descripción de la PR.
Si el segundo paso es el que parece un mes de trabajo, conecta un repo, lee el plan que propone y revisa la pull request que abre.
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