Traducción JSON de WordPress: Traducción de JavaScript del Editor de Bloques

Tradujiste tu plugin. Las cadenas PHP aparecen perfectamente en la configuración de administración, las plantillas del frontend, las notificaciones por correo electrónico, todo localizado. Luego abres el editor de bloques, y cada etiqueta en tu bloque personalizado de Gutenberg está obstinadamente, burlonamente en inglés. El botón "Add Item", los encabezados del panel de inspección, el texto del marcador de posición. Tu archivo .mo está cargado. Entonces, ¿por qué estas cadenas no se traducen?
Porque desde WordPress 5.0 y la llegada de Gutenberg, las cadenas de JavaScript no provienen en absoluto de tu archivo .mo. Necesitan un archivo de traducción JSON de WordPress completamente separado y por script; y si no lo generas, tu editor de bloques permanecerá en inglés, sin importar lo completo que esté tu archivo .po. Esta es una de las brechas de localización más comunes y confusas en el desarrollo moderno de WordPress. Esta guía explica exactamente por qué sucede, cómo funciona el sistema de traducción JSON, los nombres de archivo con hash MD5 que confunden a todo el mundo y la cadena de herramientas completa para solucionarlo.
Por qué las Cadenas de tu Editor de Bloques Permanecen en Inglés
Respuesta corta: PHP y JavaScript usan dos sistemas de entrega de traducción completamente diferentes en WordPress, y tu archivo .mo solo alimenta el de PHP.
Dos Sistemas de Traducción, Un Plugin
Cuando WordPress ejecuta load_plugin_textdomain(), lee tu archivo .mo compilado en la memoria de PHP. Cada llamada a __(), _e() y _x() en tu código PHP busca su traducción allí. Esto funciona porque PHP se renderiza en el lado del servidor: los datos .mo están justo allí en el mismo proceso.
JavaScript es diferente. El código de tu bloque se ejecuta en el navegador, mucho después de que PHP haya terminado. No puede acceder a un archivo .mo del lado del servidor. En su lugar, el paquete @wordpress/i18n —el equivalente JS de Gettext, que expone __(), _x() y sprintf() a tus scripts— espera que las traducciones se entreguen como una carga útil JSON adjunta al script específico que las necesita.
Qué Sucede con una Cadena JS Sin Traducir
Así que un bloque con cadenas como esta:
import { __ } from '@wordpress/i18n';
registerBlockType( 'myplugin/feature-box', {
title: __( 'Feature Box', 'myplugin' ),
edit: () => {
return <Button>{ __( 'Add Item', 'myplugin' ) }</Button>;
},
} );
nunca encontrará "Feature Box" o "Add Item" en tu archivo .mo, porque el navegador nunca lee archivos .mo. Esas cadenas deben llegar como JSON, conectadas a este script handle exacto. Si no lo has configurado, las llamadas __() de JS simplemente devuelven el inglés original, en silencio, sin errores en la consola.
Conectando Traducciones JSON con wp_set_script_translations()
El puente entre tu script y sus traducciones JSON es una única función PHP: wp_set_script_translations(). La respuesta a "¿cómo sabe WordPress qué archivo JSON pertenece a qué script?" es: tú se lo dices, registrando el script y luego declarando su text domain y la carpeta donde reside el JSON.
Registrando el Script y su Carpeta de Traducción
add_action( 'init', function () {
wp_register_script(
'myplugin-editor',
plugins_url( 'build/index.js', __FILE__ ),
array( 'wp-blocks', 'wp-i18n', 'wp-element' ),
'1.0.0'
);
// Tell WordPress where this script's JSON translations live
wp_set_script_translations(
'myplugin-editor', // the registered script handle
'myplugin', // text domain
plugin_dir_path( __FILE__ ) . 'languages'
);
} );
Cuando el editor carga myplugin-editor, WordPress ahora sabe que debe buscar en tu carpeta languages/ un archivo JSON que coincida con este script y la configuración regional del usuario actual. Si encuentra uno, inyecta las traducciones antes de que se ejecute tu script, y las llamadas __() de JS se resuelven correctamente. El handle que pasas debe coincidir exactamente con un script registrado; un handle incorrecto o faltante es la segunda razón más común por la que las traducciones fallan silenciosamente.
El Nombre de Archivo con Hash MD5 que Nadie Espera
Aquí está el detalle que descarrila a casi todo el mundo. El archivo JSON que busca WordPress no se llama algo ordenado como myplugin-fr_FR.json. Se nombra con un hash MD5 de la ruta de origen del script:
Decodificando el Patrón de Nombres de Archivo
myplugin-fr_FR-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6.json
El patrón es {textdomain}-{locale}-{md5}.json, donde el hash es el MD5 de la ruta relativa al archivo del script (por ejemplo, build/index.js) tal como está registrado. WordPress calcula este hash en tiempo de ejecución para encontrar el JSON correcto para el script correcto. Si nombras tu archivo manualmente, WordPress no lo encontrará, y jurarás que el sistema está roto cuando en realidad solo está buscando un nombre de archivo diferente.
No calculas el hash tú mismo. El comando wp i18n de WP-CLI lo hace por ti, por eso es precisamente que debes usar las herramientas en lugar de crear estos archivos manualmente. Comprender que el hash existe, sin embargo, es lo que te ahorra horas cuando un archivo JSON está presente en languages/ pero sigue siendo ignorado; casi siempre se debe a una falta de coincidencia del hash del nombre de archivo porque la ruta del script cambió.
El Flujo de Trabajo Completo: de make-pot a make-json
La buena noticia es que mantienes tus traducciones en los mismos archivos .po que ya usas. El JSON es un artefacto derivado generado al final. La respuesta a "¿mantengo las cadenas JS por separado?" es no: viven en el mismo .pot/.po que tus cadenas PHP, y un comando extra separa las de JS en JSON.
El Flujo de Cuatro Comandos
Aquí está la canalización completa:
# 1. Extract ALL translatable strings (PHP and JS) into one template
wp i18n make-pot . languages/myplugin.pot
# 2. Translate languages/myplugin-fr_FR.po as usual (Poedit, AI, etc.)
# 3. Compile the .mo for PHP strings (server-side, as always)
wp i18n make-mo languages/
# 4. Generate the MD5-hashed JSON files for JS strings
wp i18n make-json languages/ --no-purge
El make-pot del paso 1 es lo suficientemente inteligente como para escanear tanto tus archivos .php como tu código fuente .js/.jsx, por lo que un solo .po por locale lo contiene todo. El make-json del paso 4 lee cada .po traducido, encuentra las entradas que provienen de archivos JavaScript y escribe un JSON con el hash correcto por script. El flag --no-purge también mantiene las cadenas JS en tu .po, para que un make-mo posterior no las pierda; sin él, make-json elimina las entradas JS del .po, lo que sorprende a las personas que ejecutan los comandos en el orden incorrecto.
Un archivo JSON generado se ve como un conjunto de traducción en formato Jed:
{
"translation-revision-date": "2026-06-12 10:00+0000",
"generator": "WP-CLI/2.x",
"domain": "messages",
"locale_data": {
"messages": {
"": { "domain": "messages", "lang": "fr_FR" },
"Feature Box": [ "Bloc fonctionnalité" ],
"Add Item": [ "Ajouter un élément" ]
}
}
}
WordPress lee locale_data y lo alimenta a @wordpress/i18n antes de que se ejecute tu script. Ahora __( 'Add Item', 'myplugin' ) en el navegador devuelve Ajouter un élément, y tu editor de bloques finalmente está localizado.
En qué se Diferencia del JSON de i18next
Ambos sistemas usan JSON, ambos apuntan a JavaScript, y esa similitud superficial causa una confusión real. No son intercambiables. El JSON del editor de bloques de WordPress es una carga útil derivada de Gettext, con hash MD5 y por script, consumida por @wordpress/i18n. El JSON de i18next es un archivo clave-valor plano o anidado consumido por react-i18next o next-intl, con su propia sintaxis de {{interpolation}} y convenciones de claves plurales.
Si estás trabajando en React o Next.js puro fuera de WordPress, querrás el enfoque de i18next, que cubrimos en cómo traducir JSON de i18next en React y Next.js. Dentro de WordPress, querrás el flujo de trabajo de make-json descrito anteriormente. Confundirlos —por ejemplo, escribir manualmente JSON plano al estilo i18next y esperar que wp_set_script_translations() lo cargue— simplemente no funcionará, porque WordPress busca el formato Jed con hash, no pares clave-valor arbitrarios.
Una Fuente, Cada Formato que Necesitas
La fragilidad en todo esto reside en el paso intermedio de la traducción. Tu archivo .po alimenta tanto el .mo (PHP) como el JSON (JavaScript), por lo que una única traducción fallida —un %s malformado, una etiqueta <strong> rota, una forma plural renombrada— contamina ambas salidas a la vez. Y debido a que las cadenas JS se cargan asincrónicamente en el navegador, un error estructural allí a menudo se manifiesta como una etiqueta en blanco o un fallo grave en lugar de una alternativa elegante.
Una Sola Subida, PHP y JavaScript Cubiertos
Aquí es donde una canalización de traducción que comprende la estructura de Gettext se gana su lugar. SimplePoTranslate toma un archivo .po o .pot de origen y produce una salida limpia y traducida en múltiples formatos a partir de una única subida — .po, .mo, .json, .php y .xliff — para que no tengas que unir herramientas separadas para tus capas de PHP y JavaScript. Su Bloqueo de Sintaxis mantiene %s, %1$s, {count} y el HTML en línea en su lugar, lo cual es doblemente importante para las cadenas del editor de bloques donde un marcador de posición roto puede inutilizar todo el panel del editor. Profundizamos en el modelo de una fuente, múltiples salidas en un archivo de entrada, cinco formatos de salida.
Sigues ejecutando make-json para producir los archivos con hash que WordPress espera; ese paso es específico de WordPress y permanece en tu compilación. Pero la traducción en sí, la parte con más probabilidades de romper tus cadenas JS, es manejada por un motor sensible al contexto en lugar de un script de buscar y reemplazar.
Conclusión
La razón por la que tu editor de bloques permanece en inglés es estructural, no un error: la traducción JSON de WordPress es un sistema de entrega separado del archivo .mo, construido específicamente porque los navegadores no pueden leer datos Gettext del lado del servidor. Una vez que entiendes que las cadenas de JavaScript necesitan un JSON por script, con hash MD5, generado por wp i18n make-json y conectado con wp_set_script_translations(), la solución es mecánica. Mantén tus cadenas en un .po, compila el .mo para PHP y ejecuta make-json para JS.
Haz bien el paso de traducción y ambas salidas funcionarán. Hazlo mal y depurarás paneles del editor en blanco durante una tarde.
¿Listo para traducir tus cadenas JSON y PHP de WordPress desde una única fuente limpia? Prueba SimplePoTranslate gratis — no se requiere tarjeta de crédito. El nivel gratuito traduce archivos
.poy.potreales con Bloqueo de Sintaxis seguro para marcadores de posición, para que las cadenas de tu editor de bloques se entreguen correctamente.