From cc990e234d2c2e70d41249fb6dc2ae4bb9acdf7a Mon Sep 17 00:00:00 2001 From: Abhi <19635345+ph33nx@users.noreply.github.com> Date: Wed, 5 Aug 2026 23:09:30 +0530 Subject: [PATCH] fix(i18n): bind block editor scripts to the text domain Editor panels are built in JavaScript and translated by @wordpress/i18n, which only resolves once the script handle is bound to the text domain. Declaring wp-i18n as a dependency is not enough on its own, so the editor panels stayed English while every PHP string on the same screen translated correctly. Registrar now binds every registered block's editor_script_handles. No path argument, because wordpress.org builds and serves the JSON translation files. Guarded in test-block-registration.php across all 153 blocks. --- readme.txt | 6 ++++ src/Blocks/Registrar.php | 15 +++++++++- tests/phpunit/test-block-registration.php | 34 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 17c6566..64380f4 100644 --- a/readme.txt +++ b/readme.txt @@ -208,6 +208,9 @@ Yes. All RoxyAPI shortcodes work inside any page builder that supports WordPress == Changelog == += 1.7.4 = +* The block editor now follows your site language too. Reading settings panels in the editor were left in English even when the rest of your dashboard was translated. + = 1.7.3 = * The visitor form is now translatable. Field labels, help text, section headings and form titles are all available to translators, so a site in German, Spanish, Hindi, Turkish, Portuguese, French or Russian can show the birth details form in that language instead of English. Translations are contributed at translate.wordpress.org and arrive automatically as language packs. @@ -377,6 +380,9 @@ Yes. All RoxyAPI shortcodes work inside any page builder that supports WordPress == Upgrade Notice == += 1.7.4 = +Completes the translation work in 1.7.3 by covering the block editor panels. + = 1.7.3 = Recommended for sites that are not in English. The visitor form can now be translated, so the form your visitors fill in can match the rest of your site. diff --git a/src/Blocks/Registrar.php b/src/Blocks/Registrar.php index 9091bac..de22a60 100644 --- a/src/Blocks/Registrar.php +++ b/src/Blocks/Registrar.php @@ -33,7 +33,20 @@ public static function register_blocks(): void { $block_files = glob( $blocks_dir . '/*/block.json' ); if ( $block_files ) { foreach ( $block_files as $block_json ) { - register_block_type( dirname( $block_json ) ); + $block_type = register_block_type( dirname( $block_json ) ); + /** + * Editor-side strings are translated by `@wordpress/i18n` in JavaScript, and those + * only resolve once the script HANDLE is bound to the text domain. `wp-i18n` being + * a declared dependency is not enough on its own: without this call the editor + * panel stays English even when a language pack is installed and every PHP string + * on the same screen is translated. No path argument, because wordpress.org builds + * and serves the JSON translation files for hosted plugins. + */ + if ( $block_type instanceof \WP_Block_Type ) { + foreach ( $block_type->editor_script_handles as $handle ) { + wp_set_script_translations( $handle, 'roxyapi' ); + } + } } } } diff --git a/tests/phpunit/test-block-registration.php b/tests/phpunit/test-block-registration.php index 8f38971..d515b11 100644 --- a/tests/phpunit/test-block-registration.php +++ b/tests/phpunit/test-block-registration.php @@ -64,4 +64,38 @@ public function test_generated_long_tail_blocks_carry_an_editor_script(): void { 'A generated long-tail block must ship an editorScript so its inputs are editable in the block editor, not just render read-only.' ); } + + /** + * Editor panels are built in JavaScript and translated by `@wordpress/i18n`, which only + * resolves once the script HANDLE is bound to the text domain. Declaring `wp-i18n` as a + * script dependency is not enough on its own, and the failure is silent: the editor panel + * stays English while every PHP string on the same screen translates correctly. + */ + public function test_every_block_editor_script_is_bound_to_the_text_domain(): void { + $blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered(); + $ours = array_filter( + $blocks, + static fn( $name ) => strpos( (string) $name, 'roxyapi/' ) === 0, + ARRAY_FILTER_USE_KEY + ); + // Non-vacuity: an empty set would make the loop below assert nothing. + $this->assertGreaterThan( 100, count( $ours ), 'Expected the block catalog to be registered.' ); + + $scripts = wp_scripts(); + $unbound = array(); + foreach ( $ours as $name => $block ) { + foreach ( $block->editor_script_handles as $handle ) { + $registered = isset( $scripts->registered[ $handle ] ) ? $scripts->registered[ $handle ] : null; + if ( ! $registered || empty( $registered->textdomain ) ) { + $unbound[] = $name; + } + } + } + + $this->assertSame( + array(), + array_slice( $unbound, 0, 10 ), + 'Every block editor script must be bound with wp_set_script_translations( $handle, "roxyapi" ) in Blocks\\Registrar.' + ); + } }