Skip to content

Script Modules: Fix missing translations of script modules - #84651

Draft
jorgefilipecosta wants to merge 6 commits into
trunkfrom
fix/script-module-translations
Draft

jorgefilipecosta wants to merge 6 commits into
trunkfrom
fix/script-module-translations

Conversation

@jorgefilipecosta

@jorgefilipecosta jorgefilipecosta commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Core PR: WordPress/wordpress-develop#14260
Needed by #84643 and #83368.

Right now some strings of script modules show in English even when the language packs translate them:

  • WordPress only prints the translations of the enqueued script modules and their dependencies. A script module that only a classic script imports, through its module_dependencies, loads with no translations. Actions API: Extract the core actions into the @wordpress/core-actions script modules #84643 moves the DataViews core actions into script modules that wp-editor imports this way, and Fields API #83368 does the same for the core fields. Their strings are translated today because they are bundled into wp-editor, so without a fix they would go back to English.
  • The script modules that the plugin serves look their translations up in languages/plugins/{locale}-{md5}.json, but the plugin language packs name these files gutenberg-{locale}-{md5}.json. This already affects released screens, e.g. most of Settings > Connectors is in English.
  • The full-page template of wp-build never prints the translations of script modules, so full-page screens like the extensible site editor show the strings of their script modules in English.

This PR:

  • Replaces the hooks of WP_Script_Modules::print_script_module_translations() with a function that also prints the translations of the script modules that classic scripts depend on, and of their dependencies. It only uses public methods, and prints each script module once whether or not WordPress includes the fix from the core PR.
  • Makes gutenberg_override_translation_file() use the plugin translation files for the script modules served from the plugin's build/ folder too, as it does for the plugin's scripts.
  • Makes the full-page template of wp-build fire admin_print_footer_scripts, as wp-admin/admin-footer.php does, instead of calling the footer printers one by one.
  • Adds a test plugin with an e2e test for the first change, and PHPUnit tests for the first two.

Some notes:

  • The Gutenberg function works like the core method with the fix, with one difference: WordPress skips an enqueued script module whose dependencies are not all registered, since it can't load, while the Gutenberg function still prints its translations, which go unused. I considered porting the dependency sorting of the core class, but it's private and the difference has no visible effect. So I opted for a plain walk over get_registered().
  • WordPress hooks all the footer printers to admin_print_footer_scripts since 6.7, which the template already requires for print_script_module_data(). Besides the translations, the full-page screens now also get the late styles and the live regions of @wordpress/a11y, and print the script modules before the classic scripts, like the other admin pages.
  • https://core.trac.wordpress.org/ticket/60234 discusses loading the translations of dynamically imported script modules on import, instead of printing them with the page. Both PRs follow what WordPress does today for the dynamic dependencies of enqueued script modules.
  • I checked Actions API: Extract the core actions into the @wordpress/core-actions script modules #84643 merged with this PR, in Portuguese (Portugal), with language pack files that move the strings of the core actions from build/scripts/editor/index.js to the build/modules/core-actions/ files, as GlotPress will once that PR ships. "Export as JSON" shows as "Exportar como JSON" in the pattern actions of the site editor only with the first two changes, and in the extensible site editor only with all three. Without them, the post editor prints no translations for the @wordpress/core-actions script modules either.
  • I compared the HTML of 15 admin and front-end pages before and after this PR, in English and in Portuguese, on WordPress trunk, and checked the main ones again with a classic theme and on WordPress 7.0.7. The only differences are the new translation scripts and, in the extensible site editor, the footer order and the live regions. No page logs new PHP notices or console errors.

Before

The test page of the e2e test, where one line comes from an enqueued script module and the other from a script module that only a classic script imports:

The test page on trunk: the line written by the enqueued script module ends with "(translated)", the line written by the script module that a classic script imports does not.

Settings > Connectors, in Portuguese (Portugal):

Settings > Connectors in Portuguese on trunk: the title, the description, the AI plugin card and the note at the bottom are in English.

The extensible site editor, in Portuguese (Portugal), where the back link at the top of the sidebar comes from a script module:

The extensible site editor in Portuguese on trunk: the back link at the top of the sidebar reads "Dashboard".

After

The test page with this PR: both lines end with "(translated)".

Settings > Connectors in Portuguese with this PR: all of the page is in Portuguese.

The extensible site editor in Portuguese with this PR: the back link at the top of the sidebar reads "Painel".

Testing Instructions

Go through the steps on trunk first, then on this branch after npm run build.

  1. Start the test environment with npm run wp-env-test start.

  2. Save this snippet as packages/e2e-tests/mu-plugins/module-translations-test.php, which the test environment loads as a must-use plugin. It registers a test/hello script module that only a classic script imports, gives it a translation, and makes it show __( 'Hello' ) in an alert on the Dashboard:

    <?php
    add_action( 'load-index.php', function () {
    	wp_register_script_module( 'test/hello', 'data:text/javascript,alert(wp.i18n.__("Hello"))', array(), null );
    	wp_register_script( 'test-classic', false, array( 'wp-i18n' ), false, array( 'in_footer' => true, 'module_dependencies' => array( 'test/hello' ) ) );
    	wp_add_inline_script( 'test-classic', 'addEventListener("DOMContentLoaded",()=>import("test/hello"))' );
    	wp_enqueue_script( 'test-classic' );
    } );
    add_filter( 'pre_load_script_translations', function ( $translations, $file, $handle ) {
    	return 'test/hello' === $handle ? '{"locale_data":{"messages":{"":{},"Hello":["Hello (translated)"]}}}' : $translations;
    }, 10, 3 );
    
  3. Open http://localhost:8889/wp-admin/. On trunk, the alert says "Hello". With this PR, it says "Hello (translated)".

  4. Install Portuguese (Portugal) and switch the admin user to it:

    npm run wp-env-test -- run cli wp language core install pt_PT
    npm run wp-env-test -- run cli wp language plugin install gutenberg pt_PT
    npm run wp-env-test -- run cli wp user meta update admin locale pt_PT
    
  5. Open http://localhost:8889/wp-admin/options-general.php?page=options-connectors-wp-admin. On trunk, the title, the description, the AI plugin card and the note at the bottom are in English. With this PR, all of the page is in Portuguese.

  6. Open http://localhost:8889/wp-admin/admin.php?page=site-editor-v2. On trunk, the back link at the top of the sidebar reads "Dashboard". With this PR, it reads "Painel".

  7. Delete the snippet and switch the admin user back to English with npm run wp-env-test -- run cli wp user meta delete admin locale. Then run npm run test:unit:php:base -- --filter 'Gutenberg_Script_Module_Translations_Test|Override_Translation_File_Test|Tests_Fields_API' and npm run test:e2e -- test/e2e/specs/admin/script-module-translations.spec.js.

AI usage disclosure: fix and description drafted with AI assistance and reviewed by me.

…ssic scripts depend on

`WP_Script_Modules::print_script_module_translations()` only prints the translations of the enqueued script modules and their dependencies, so a script module that only a classic script imports through its `module_dependencies` loads with no translations. Its hooks are now replaced on `after_setup_theme`, right after WordPress adds them, with `gutenberg_print_script_module_translations()`, which also prints the translations of those script modules and of their dependencies. It only uses public methods, and prints each script module once whether or not WordPress includes the fix. A hook that was removed stays removed.

The tests port the core cases, compare the output for the enqueued script modules with the one of WordPress, and cover the field modules that the Fields API adds to the `module_dependencies` of `wp-editor`.
…script modules

`gutenberg_override_translation_file()` only prepends the plugin slug to the translation files of the `wp-*` scripts. So the script modules that the plugin serves from its `build/` folder, e.g. from `build/modules/` and `build/routes/`, looked their translations up in `languages/plugins/{locale}-{md5}.json`, while the plugin language packs name these files `gutenberg-{locale}-{md5}.json`, and their strings stayed in English. These script modules now get the plugin translation file too.
…-page template

The full-page template called the footer printers one by one, so it never printed the translations of the script modules, which WordPress prints on `admin_print_footer_scripts` since 7.0, and the strings of the script modules of full-page screens, like the extensible site editor, stayed in English. The footer now fires `admin_print_footer_scripts`, as `wp-admin/admin-footer.php` does. That prints the import map, the footer scripts, the script modules and their data and translations, in the order of the other admin pages, along with the late styles and the live regions of `@wordpress/a11y`, which the template missed too. WordPress hooks all of these printers to the action since 6.7, which the template already requires for `print_script_module_data()`.
…dules that classic scripts import

The "Gutenberg Test Script Module Translations" test plugin adds a Tools page with two lines, each one written by a script module: one that the page enqueues, and one that only a classic script imports, declared in its `module_dependencies`. The plugin ships English translation files for both script modules, so each line ends with "(translated)" when its translations are printed, even on a site in English. Without the fix, only the first line is translated.
@jorgefilipecosta jorgefilipecosta added [Type] Bug An existing feature does not function as intended Internationalization (i18n) Issues or PRs related to internationalization efforts [Feature] Script Modules API Related to the Script Modules API that adds support for native ES modules and import maps [Package] wp-build /packages/wp-build labels Oct 13, 2026
@github-actions github-actions Bot added the [Package] E2E Tests /packages/e2e-tests label Oct 13, 2026
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

📦 Bundle size

Size Change: 0 B

Total Size: 8.58 MB

3603f48 Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 7d43812 trunk % Change
timeToFirstByte 52.55 ms +15.22% -3.14% 52.35 ms +14.61% -3.15% 0.38%
largestContentfulPaint 94 ms +2.13% -4.26% 88 ms +6.82% -4.55% 6.82%
lcpMinusTtfb 37.9 ms +11.48% -10.55% 33.55 ms +6.86% -3.28% 12.97%
wpBeforeTemplate 27.81 ms +19.02% -1.76% 27.89 ms +16.13% -2.37% -0.29%
wpTemplate 20.89 ms +3.88% -3.35% 21.08 ms +4.55% -6.02% -0.9%
wpTotal 49.03 ms +14.89% -3.08% 48.94 ms +13.34% -4% 0.18%
wpMemoryUsage 7.62 MB +0% -0% 7.58 MB +0% -0% 0.46%
wpDbQueries 17 +0% -0% 17 +0% -0% 0%

front-end-classic-theme

Metric 7d43812 trunk % Change
timeToFirstByte 40.2 ms +7.84% -1.37% 43.95 ms +10.69% -0.68% -8.53%
largestContentfulPaint 88 ms +2.27% -0% 96 ms +4.17% -0% -8.33%
lcpMinusTtfb 47.35 ms +1.8% -3.59% 52.35 ms +1.05% -0.57% -9.55%
wpBeforeTemplate 25.39 ms +4.92% -0.39% 25.66 ms +8.42% -2.1% -1.05%
wpTemplate 11.83 ms +3.04% -1.27% 15.63 ms +3.01% -1.54% -24.31%
wpTotal 37.41 ms +8.31% -1.31% 41.08 ms +10.78% -1.12% -8.93%
wpMemoryUsage 6.14 MB +0% -0% 6.21 MB +0% -0% -1.07%
wpDbQueries 10 +0% -0% 14 +0% -0% -28.57%

media-processing

Metric 7d43812 trunk % Change
mediaProcessingJpeg 401.92 ms +0.17% -0.36% 400.35 ms +0.7% -0.45% 0.39%
mediaProcessingAvif 6026.7 ms +0.34% -0.01% 6043.77 ms +0.09% -0.1% -0.28%
mediaProcessingJpegToAvif 4158.68 ms +0.05% -0.5% 4165.03 ms +0.29% -0.18% -0.15%

media-upload

Metric 7d43812 trunk % Change
jpegUploadProcessing 1440.6 ms +35.32% -2.64% 1415.08 ms +0.57% -0.88% 1.8%
pngUploadProcessing 200.93 ms +5.04% -17.35% 175.95 ms +18.23% -2.63% 14.2%
largeJpegUploadProcessing 1402.65 ms +0.71% -0.46% 1408.31 ms +0.39% -0.66% -0.4%
multipleImageUploadProcessing 1542.43 ms +0.58% -0.91% 1562.24 ms +8.96% -2% -1.27%

post-editor

Metric 7d43812 trunk % Change
serverResponse 514.82 ms +4.92% -1.87% 519.09 ms +3.12% -3.97% -0.82%
firstPaint 252.35 ms +35.19% -9.89% 474.12 ms +6.82% -39.5% -46.78%
domContentLoaded 1220.59 ms +1.12% -1.8% 1190.42 ms +1.44% -1.6% 2.53%
loaded 1222.09 ms +1.14% -1.79% 1191.68 ms +1.44% -1.59% 2.55%
firstContentfulPaint 526.62 ms +2.12% -1.87% 508.64 ms +1.8% -3.25% 3.53%
firstBlock 3555.51 ms +1.55% -0.8% 3540.54 ms +0.76% -1.18% 0.42%
type 18.15 ms +14.38% -3.75% 17.87 ms +2.13% -2.91% 1.57%
typeWithoutInspector 18.28 ms +4.16% -3.88% 18.35 ms +8.99% -3.38% -0.38%
typeWithTopToolbar 22.42 ms +11.02% -3.43% 24.28 ms +5.93% -3.17% -7.66%
typeContainer 8.63 ms +10.89% -5.68% 8.76 ms +3.08% -6.28% -1.48%
focus 125.72 ms +7.61% -4.84% 120.2 ms +6.86% -4.21% 4.59%
firstFocus 238.4 ms +0% -0% 220.06 ms +0% -0% 8.33%
selectAll 606.39 ms +0.43% -4.99% 578.93 ms +0.25% -3.34% 4.74%
listViewOpen 80.68 ms +6.73% -2.97% 83.74 ms +2.44% -3.34% -3.65%
inserterOpen 43.83 ms +7.03% -9.67% 41.13 ms +6.2% -12.42% 6.56%
inserterHover 12.57 ms +13.84% -6.52% 12.03 ms +9.23% -9.06% 4.49%
inserterSearch 8.57 ms +8.28% -6.88% 8.52 ms +1.88% -3.17% 0.59%
loadPatterns 648.59 ms +3.67% -3.03% 628.95 ms +3.35% -4.02% 3.12%
wpTotal 501.58 ms +5.38% -1.56% 507.75 ms +2.99% -4.06% -1.22%
wpMemoryUsage 13.35 MB +0% -0% 13.31 MB +0% -0% 0.28%
wpDbQueries 53.5 +0.93% -0.93% 54 +0% -1.85% -0.93%

site-editor

Metric 7d43812 trunk % Change
serverResponse 519.7 ms +0.97% -10.8% 527.45 ms +3.23% -0.6% -1.47%
firstPaint 285.82 ms +17.35% -3.63% 266.59 ms +23.37% -8.08% 7.21%
domContentLoaded 1273.5 ms +0.98% -1.53% 1312.17 ms +1.56% -2.62% -2.95%
loaded 1274.79 ms +0.98% -1.5% 1313.68 ms +1.55% -2.63% -2.96%
firstContentfulPaint 525.62 ms +2.95% -0.48% 544.38 ms +0.99% -2.46% -3.45%
firstBlock 4746.3 ms +1.09% -0.63% 4886.37 ms +0.67% -0.75% -2.87%
type 19.54 ms +13.2% -2.2% 20.13 ms +2.98% -3.43% -2.93%
navigate 121.78 ms +15.82% -3.19% 120.97 ms +9.15% -3.59% 0.67%
loadPatterns 1356.9 ms +10.98% -2.3% 1372.84 ms +9.57% -6.41% -1.16%
loadPages 1033.74 ms +1.43% -1.16% 1013.4 ms +1.68% -0.52% 2.01%
wpTotal 508.71 ms +0.84% -11.16% 515.92 ms +3.32% -0.63% -1.4%
wpMemoryUsage 12.38 MB +0% -0% 12.36 MB +0% -0% 0.13%
wpDbQueries 43 +2.33% -0% 43 +2.33% -0% 0%

3603f48 Run

🏁 Flaky tests

Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

refuses the drop and uploads nothing in /test/e2e/specs/editor/various/single-file-placeholder-drop.spec.js, passed after 1 failed attempt.
Error: expect(received).toHaveLength(expected)

Expected length: 0
Received length: 1
Received array:  [{"_links": {"about": [{"href": "http://localhost:8889/wp-json/wp/v2/types/attachment"}], "author": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/users/1"}], "collection": [{"href": "http://localhost:8889/wp-json/wp/v2/media"}], "curies": [{"href": "https://api.w.org/{rel}", "name": "wp", "templated": true}], "replies": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/comments?post=277"}], "self": [{"href": "http://localhost:8889/wp-json/wp/v2/media/277", "targetHints": {"allow": ["GET", "POST", "PUT", "PATCH", "DELETE"]}}], "wp:attached-to": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/posts/276", "id": 276, "post_type": "post"}]}, "alt_text": "", "author": 1, "caption": {"rendered": ""}, "class_list": ["post-277", "attachment", "type-attachment", "status-inherit", "hentry", "entry"], "comment_status": "open", "date": "2026-10-13T21:36:14", "date_gmt": "2026-10-13T21:36:14", "description": {"rendered": "<p class=\"attachment\"><a href='http://localhost:8889/wp-content/uploads/2026/10/07b41c67-c13f-4110-92c9-dd98e58cf1a7.png'><img loading=\"lazy\" decoding=\"async\" width=\"10\" height=\"10\" src=\"http://localhost:8889/wp-content/uploads/2026/10/07b41c67-c13f-4110-92c9-dd98e58cf1a7.png\" class=\"attachment-medium size-medium\" alt=\"\" style=\"width:100%;height:100%;max-width:10px;\" /></a></p>
"}, "featured_media": 0, "filename": "07b41c67-c13f-4110-92c9-dd98e58cf1a7.png", "filesize": 80, "guid": {"rendered": "http://localhost:8889/wp-content/uploads/2026/10/07b41c67-c13f-4110-92c9-dd98e58cf1a7.png"}, "id": 277, "link": "http://localhost:8889/?attachment_id=277", "media_details": {"file": "2026/10/07b41c67-c13f-4110-92c9-dd98e58cf1a7.png", "filesize": 80, "height": 10, "image_meta": {"alt": "", "aperture": "0", "camera": "", "caption": "", "copyright": "", "created_timestamp": "0", "credit": "", "focal_length": "0", "iso": "0", "keywords": [], "orientation": "0", "shutter_speed": "0", "title": ""}, "sizes": {}, "width": 10}, "media_type": "image", "meta": [], "mime_type": "image/png", "modified": "2026-10-13T21:36:14", "modified_gmt": "2026-10-13T21:36:14", "ping_status": "closed", "post": 276, "slug": "07b41c67-c13f-4110-92c9-dd98e58cf1a7", "source_url": "http://localhost:8889/wp-content/uploads/2026/10/07b41c67-c13f-4110-92c9-dd98e58cf1a7.png", "status": "inherit", "template": "", "title": {"rendered": "07b41c67-c13f-4110-92c9-dd98e58cf1a7"}, "type": "attachment"}]
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/single-file-placeholder-drop.spec.js:71:45

3603f48 Run

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Script Modules API Related to the Script Modules API that adds support for native ES modules and import maps Internationalization (i18n) Issues or PRs related to internationalization efforts [Package] E2E Tests /packages/e2e-tests [Package] wp-build /packages/wp-build [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant