Skip to content

I18N: Add translation support for script modules#11543

Closed
manzoorwanijk wants to merge 47 commits intoWordPress:trunkfrom
manzoorwanijk:add/script-module-translations
Closed

I18N: Add translation support for script modules#11543
manzoorwanijk wants to merge 47 commits intoWordPress:trunkfrom
manzoorwanijk:add/script-module-translations

Conversation

@manzoorwanijk
Copy link
Copy Markdown
Member

@manzoorwanijk manzoorwanijk commented Apr 10, 2026

Summary

Adds translation support for script modules (WP_Script_Modules), so strings using __() from @wordpress/i18n inside script modules are translated at runtime.

Script modules (ES modules registered via wp_register_script_module()) currently have no mechanism to load i18n translation data. This affects the new admin pages in WordPress 7.0+ that are built as script modules, including Connectors and Fonts, where strings remain in English regardless of the site language.

Trac ticket: https://core.trac.wordpress.org/ticket/65015

Changes

Runtime

  • WP_Script_Modules::print_script_module_translations() — iterates every enqueued script module (and its dependencies), looks up the {locale}-{md5(relative-path)}.json chunk for each, and emits an inline <script> that calls wp.i18n.setLocaleData() with the loaded translations.
  • Hooked at priority 11 on admin_print_footer_scripts (after classic scripts load wp-i18n at priority 10) and priority 21 on wp_footer (after wp_print_footer_scripts at priority 20), so the inline data is available before deferred ES modules execute.
  • load_script_module_textdomain( $id, $domain, $path ) — new function in l10n.php, mirrors load_script_textdomain() but reads the module's source URL from WP_Script_Modules. Both functions now share a private helper to avoid duplicating the path-resolution logic.
  • WP_Script_Modules::get_registered( $id ) — public accessor for a registered module's data, used by the loader.
  • The existing load_script_textdomain_relative_path filter now receives a third argument, $is_module, so callers can distinguish script modules from classic scripts.

Public override API (optional)

  • wp_set_script_module_translations( $id, $domain, $path ) and the underlying WP_Script_Modules::set_translations() exist only as overrides, for modules whose text domain is not 'default' or whose translation files live outside the standard location (for example, plugin modules registering their own text domain). Core and most plugin modules do not need to call this — translations are loaded automatically.

Tests

New integration tests in wpScriptModules.php covering:

  • Auto-detection: print_script_module_translations() loads and prints translations for an enqueued module without any explicit registration.
  • Dependency handling: translations are also printed for static/dynamic dependencies of enqueued modules.
  • Override path: set_translations() correctly overrides the auto-detected text domain.
  • The is_module argument passed to the load_script_textdomain_relative_path filter.

Tests use the pre_load_script_translations filter to supply mock translation data, so no fixture JSON files are needed.

Related

Gutenberg companion PR: WordPress/gutenberg#77214 — adds a polyfill of the same API for the Gutenberg plugin on WP versions before this change lands.

Test plan

Automated

npm run test:php -- --group script-modules

Manual

Real language packs already ship JSON translation chunks for the Connectors script module files — for example Spanish (es_ES) has them translated. So end-to-end verification doesn't need any mocked data:

  1. Install and activate Spanish:

    Via WP-CLI:

    wp core language install es_ES
    wp site switch-language es_ES

    Or via the admin UI: switch the site language in Settings > General, then visit Dashboard > Updates (/wp-admin/update-core.php) and click "Update translations" to ensure the latest language pack is installed.

  2. Visit Settings > Connectors (/wp-admin/options-connectors.php).

  3. Expected: heading shows "Conectores", subtitle "Todas tus claves de API y credenciales…", buttons show "Instalar", connector descriptions show "Generación de texto con Claude.", etc.

  4. View page source and search for wp-script-module-translation-data- — inline <script> tags with IDs like wp-script-module-translation-data-wp/routes/connectors-home/content should appear after the classic scripts.

Some locales may not have these strings translated yet and will render in English — that's the expected fallback. Any locale whose language pack includes the chunks will render translated without any configuration.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.6
Used for: Implementation, tests, and PR description were authored with AI assistance; reviewed and tested by me.

Before After
Screenshot 2026-04-21 at 7 16 11 PM Screenshot 2026-04-21 at 7 13 15 PM

@github-actions
Copy link
Copy Markdown

Hi @manzoorwanijk! 👋

Thank you for your contribution to WordPress! 💖

It looks like this is your first pull request to wordpress-develop. Here are a few things to be aware of that may help you out!

No one monitors this repository for new pull requests. Pull requests must be attached to a Trac ticket to be considered for inclusion in WordPress Core. To attach a pull request to a Trac ticket, please include the ticket's full URL in your pull request description.

Pull requests are never merged on GitHub. The WordPress codebase continues to be managed through the SVN repository that this GitHub repository mirrors. Please feel free to open pull requests to work on any contribution you are making.

More information about how GitHub pull requests can be used to contribute to WordPress can be found in the Core Handbook.

Please include automated tests. Including tests in your pull request is one way to help your patch be considered faster. To learn about WordPress' test suites, visit the Automated Testing page in the handbook.

If you have not had a chance, please review the Contribute with Code page in the WordPress Core Handbook.

The Developer Hub also documents the various coding standards that are followed:

Thank you,
The WordPress Project

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 10, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props manzoorwanijk, westonruter, mukesh27, jsnajdr, jonsurrell, peterwilsoncc.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@mukeshpanchal27
Copy link
Copy Markdown
Member

Thanks for the PR! Can you expose which AI tool do you use in PR?

## Use of AI Tools

<!--
You are free to use artificial intelligence (AI) tooling to contribute, but you must disclose what tooling you are using and to what extent a pull request has been authored by AI. It is your responsibility to review and take responsibility for what AI generates. See the WordPress AI Guidelines: <https://make.wordpress.org/ai/handbook/ai-guidelines/>.

Example disclosure:

AI assistance: Yes
Tool(s): GitHub Copilot, ChatGPT
Model(s): GPT-5.1
Used for: Initial code skeleton and test suggestions; final implementation and tests were reviewed and edited by me.
-->

@manzoorwanijk
Copy link
Copy Markdown
Member Author

Thanks for the PR! Can you expose which AI tool do you use in PR?

Updated. Thanks

manzoorwanijk added a commit to WordPress/gutenberg that referenced this pull request Apr 10, 2026
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
@manzoorwanijk manzoorwanijk force-pushed the add/script-module-translations branch 2 times, most recently from e68fb27 to 0e73045 Compare April 11, 2026 23:58
manzoorwanijk and others added 9 commits April 12, 2026 05:45
Add `wp_set_script_module_translations()` and supporting infrastructure
to enable i18n for script modules (ES modules), mirroring the existing
`wp_set_script_translations()` for classic scripts.

Script modules registered via `wp_register_script_module()` currently
have no way to load translation data, leaving strings untranslated on
pages like Connectors and Fonts that are built as script modules.

New public API:
- `wp_set_script_module_translations()` in script-modules.php
- `load_script_module_textdomain()` in l10n.php

New methods on `WP_Script_Modules`:
- `set_translations()` — stores text domain per module
- `get_registered_src()` — public accessor for module source URL
- `print_script_module_translations()` — outputs inline setLocaleData()
  calls after classic scripts load but before modules execute

See #65015.
Add two integration tests verifying the full translation flow:

- test_print_script_module_translations_outputs_set_locale_data covers
  the happy path, asserting that the inline script contains the
  expected module ID, the wp.i18n.setLocaleData() call, and the
  translated string.
- test_print_script_module_translations_includes_dependencies covers
  translations for dependency modules (not just directly enqueued ones).

Both tests use the pre_load_script_translations filter to provide
mock translation data inline, so no fixture JSON files are needed.

See #65015.
…ules hook.

This hook registration was accidentally moved during iteration on the
translation hook timing. Restore it to its original position to keep
the diff minimal.

See #65015.
Co-authored-by: Weston Ruter <[email protected]>
Update the test and the caller in load_script_module_textdomain() to
match the ?string return type of WP_Script_Modules::get_registered_src(),
which returns null (not false) for unregistered modules.
@manzoorwanijk manzoorwanijk force-pushed the add/script-module-translations branch from 0e73045 to eb78a18 Compare April 12, 2026 00:15
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
Comment thread src/wp-includes/class-wp-script-modules.php Outdated
Comment thread src/wp-includes/l10n.php Outdated
Comment thread src/wp-includes/l10n.php
Comment thread src/wp-includes/l10n.php Outdated
Comment thread src/wp-includes/l10n.php Outdated
@westonruter
Copy link
Copy Markdown
Member

I can confirm the changes currently fix the issue at least for some of the strings.

Before:

Screenshot 2026-04-12 at 15 19 57

After:

Screenshot 2026-04-12 at 15 20 41

I suppose the other strings are coming from the textdomain of the connector plugin and aren't provided yet? Or they haven't been translated for Spanish yet period?

manzoorwanijk and others added 2 commits April 13, 2026 09:50
Refactor load_script_textdomain() and load_script_module_textdomain()
to share a private helper, _load_script_textdomain_from_src(), that
handles the path resolution and file lookup logic. Each public function
now only resolves the source URL for its respective registry and
delegates to the helper with the appropriate filter name.

Reduces duplication by ~88 lines while preserving behavior and the
existing load_script_textdomain_relative_path filter.
@manzoorwanijk
Copy link
Copy Markdown
Member Author

I can confirm the changes currently fix the issue at least for some of the strings.

Thank you for testing this.

I suppose the other strings are coming from the textdomain of the connector plugin and aren't provided yet? Or they haven't been translated for Spanish yet period?

The remaining untranslated strings come from a couple of different places:

  1. Other script modules. Strings like the connector descriptions and the "Install the AI plugin" card come from additional script modules (@wordpress/connectors and @wordpress/boot), not the wp/routes/connectors-home/content module. The test mu-plugin's filter only intercepts translation loading for that one module ID, so translations for other modules pass through and try to load from real language pack JSON files - which don't exist yet for trunk strings. Once the companion Gutenberg PR (#77214) lands, the generated code will call wp_set_script_module_translations() for those modules too, and they'll pick up translations from real language packs the same way.

  2. PHP-side strings passed via script_module_data. A few strings (e.g., the connector descriptions) are rendered PHP-side in connectors.php and passed to the module as data - those already use __() and work if translations exist in the language pack. For trunk/alpha the newer strings probably aren't in the Spanish language pack yet.

You can confirm (1) by extending the mu-plugin's filter to intercept additional module IDs like @wordpress/connectors with the same mock structure - those strings should then render in Spanish too.

manzoorwanijk added a commit to WordPress/gutenberg that referenced this pull request Apr 13, 2026
Comment thread src/wp-includes/l10n.php Outdated
Comment thread src/wp-includes/l10n.php Outdated
Co-authored-by: Weston Ruter <[email protected]>
Comment thread tests/phpunit/tests/script-modules/wpScriptModules.php Outdated
@westonruter
Copy link
Copy Markdown
Member

Shall I commit this?

@jsnajdr
Copy link
Copy Markdown
Member

jsnajdr commented Apr 24, 2026

Does this address https://core.trac.wordpress.org/ticket/60234?

Only partially: to really implement translations API for script modules, it must not depend on classic scripts. This is only an intermediate step.

@jsnajdr
Copy link
Copy Markdown
Member

jsnajdr commented Apr 24, 2026

Shall I commit this?

I'm happy with this PR except one little thing: I suggested merging the textdomain and translations_path fields directly into the ScriptModule structure, without the translations intermediate object. For symmetry with WP_Dependency object used by WP_Scripts. The same thing should have the same name.

But it's not a blocker.

Honestly I got lost in the vast number of review comments, can't find the suggestion now.

@manzoorwanijk
Copy link
Copy Markdown
Member Author

Honestly I got lost in the vast number of review comments, can't find the suggestion now.

It was in the first comment in this review - #11543 (review)

@jsnajdr
Copy link
Copy Markdown
Member

jsnajdr commented Apr 24, 2026

It was in the first comment in this review

This link doesn't work for me, but Cursor found the comment using GH tools:

My suggestion was to make this ScriptModule structure as close as possible to WP_Dependency used by WP_Script. If there are common fields, let's name them the same way.

That would mean that there are textdomain and translations_path fields directly on the ScriptModule object, not in a translations subfield.

Can't find in the web UI, but here it is. Did you resolve this somehow?

@westonruter
Copy link
Copy Markdown
Member

@jsnajdr:

My suggestion was to make this ScriptModule structure as close as possible to WP_Dependency used by WP_Script. If there are common fields, let's name them the same way.

That would mean that there are textdomain and translations_path fields directly on the ScriptModule object, not in a translations subfield.

So for this:

* @phpstan-type ScriptModule array{
* src: string,
* version: string|false|null,
* dependencies: array<int, array{ id: string, import: 'static'|'dynamic' }>,
* in_footer: bool,
* fetchpriority: 'auto'|'low'|'high',
* translations?: array{ textdomain: string, path: string },
* }

You're suggesting to get rid of translations, and to move textdomain and translations_path keys to the root?

  * @phpstan-type ScriptModule array{ 
  *     src: string, 
  *     version: string|false|null, 
  *     dependencies: array<int, array{ id: string, import: 'static'|'dynamic' }>, 
  *     in_footer: bool, 
  *     fetchpriority: 'auto'|'low'|'high', 
  *     textdomain?: string,
  *     translations_path?: string,
  * } 

@manzoorwanijk
Copy link
Copy Markdown
Member Author

Can't find in the web UI, but here it is. Did you resolve this somehow?

You can see the changes in f8bd260 and e5b32e7

@jsnajdr
Copy link
Copy Markdown
Member

jsnajdr commented Apr 27, 2026

You're suggesting to get rid of translations, and to move textdomain and translations_path keys to the root?

Yes, because that's what WP_Dependency for classic scripts also does.

You can see the changes in f8bd260 and e5b32e7

After these commits there is no longer the completely separate $this->translations registry, it's merged into $this->registered, but still as a nested object. My suggestion was to flatten it further.

It's not particularly important, just something that apparently fell through the cracks.

@manzoorwanijk
Copy link
Copy Markdown
Member Author

After these commits there is no longer the completely separate $this->translations registry, it's merged into $this->registered, but still as a nested object. My suggestion was to flatten it further.

Ah, missed that nuance - flattened them in 89a25ea. textdomain and translations_path now sit directly on the registered entry, matching WP_Dependency's shape. Thanks for the patient nudge 🙂

Copy link
Copy Markdown
Member

@jsnajdr jsnajdr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, IMO this is now ready to ship. It will be a very valuable 7.0 fix.

@westonruter
Copy link
Copy Markdown
Member

I'm committing now.

pento pushed a commit that referenced this pull request Apr 27, 2026
Add automatic translation loading for script modules (ES modules), so strings using `__()` and friends from `@wordpress/i18n` can be translated at runtime. This brings classic script i18n parity to script modules registered via `wp_register_script_module()`, which previously had no way to load translation data, leaving strings untranslated on screens like Connectors and Fonts that are built as script modules.

At the `admin_print_footer_scripts` and `wp_footer` actions, every enqueued script module and its dependencies are walked, the translation chunk is loaded for each, and an inline `<script>` calls `wp.i18n.setLocaleData()` so translations are available before deferred modules execute. Note there is currently a runtime dependency on the `wp-i18n` classic script, which is printed just-in-time if not already enqueued. This coupling is to be removed in a future release.

Public API:

* `WP_Script_Modules::set_translations()` stores the text domain (and optional path) per registered module to override the text domain and path. A global `wp_set_script_module_translations()` function is added as a wrapper around `wp_script_modules()->set_translations()`.
* `WP_Script_Modules::get_registered()` obtains a registered module's data. See #60597.
* `WP_Script_Modules::print_script_module_translations()` emits inline `wp.i18n.setLocaleData()` calls after classic scripts load but before modules execute.
* `load_script_module_textdomain()` loads the translation data for a given script module ID and text domain.
* The existing `load_script_textdomain_relative_path` filter gains a third `$is_module` parameter so callers can distinguish classic-script and script-module lookups when resolving translation paths.

PHPStan types are also added in `WP_Script_Modules`. See #64238.

Developed in #11543

Props manzoorwanijk, westonruter, jsnajdr, jonsurrell, mukesh27, peterwilsoncc, 369work, desrosj, sabernhardt, nilambar, jorgefilipecosta, malayladu.
See #64238, #60597.
Fixes #65015.


git-svn-id: https://develop.svn.wordpress.org/trunk@62278 602fd350-edb4-49c9-b593-d223f7449a82
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Apr 27, 2026
Add automatic translation loading for script modules (ES modules), so strings using `__()` and friends from `@wordpress/i18n` can be translated at runtime. This brings classic script i18n parity to script modules registered via `wp_register_script_module()`, which previously had no way to load translation data, leaving strings untranslated on screens like Connectors and Fonts that are built as script modules.

At the `admin_print_footer_scripts` and `wp_footer` actions, every enqueued script module and its dependencies are walked, the translation chunk is loaded for each, and an inline `<script>` calls `wp.i18n.setLocaleData()` so translations are available before deferred modules execute. Note there is currently a runtime dependency on the `wp-i18n` classic script, which is printed just-in-time if not already enqueued. This coupling is to be removed in a future release.

Public API:

* `WP_Script_Modules::set_translations()` stores the text domain (and optional path) per registered module to override the text domain and path. A global `wp_set_script_module_translations()` function is added as a wrapper around `wp_script_modules()->set_translations()`.
* `WP_Script_Modules::get_registered()` obtains a registered module's data. See #60597.
* `WP_Script_Modules::print_script_module_translations()` emits inline `wp.i18n.setLocaleData()` calls after classic scripts load but before modules execute.
* `load_script_module_textdomain()` loads the translation data for a given script module ID and text domain.
* The existing `load_script_textdomain_relative_path` filter gains a third `$is_module` parameter so callers can distinguish classic-script and script-module lookups when resolving translation paths.

PHPStan types are also added in `WP_Script_Modules`. See #64238.

Developed in WordPress/wordpress-develop#11543

Props manzoorwanijk, westonruter, jsnajdr, jonsurrell, mukesh27, peterwilsoncc, 369work, desrosj, sabernhardt, nilambar, jorgefilipecosta, malayladu.
See #64238, #60597.
Fixes #65015.

Built from https://develop.svn.wordpress.org/trunk@62278


git-svn-id: http://core.svn.wordpress.org/trunk@61558 1a063a9b-81f0-0310-95a4-ce76da25c4cd
@westonruter
Copy link
Copy Markdown
Member

Committed in r62278 (93d77a2).

manzoorwanijk added a commit to WordPress/gutenberg that referenced this pull request Apr 28, 2026
* I18N: Add translation support for script modules

Add polyfill for `wp_set_script_module_translations()` and update build
templates to call it for script modules that depend on `wp-i18n`.

Script modules have no mechanism to load i18n translation data, leaving
strings in ES modules untranslated regardless of site language. This
affects admin pages built as script modules like Connectors and Fonts.

Changes:
- Add polyfill in lib/compat/wordpress-7.1/script-modules.php with
  wp_set_script_module_translations(), load_script_module_textdomain(),
  and gutenberg_print_script_module_translations()
- Update routes-registration.php.template to set translations for route
  content and route modules that use wp-i18n
- Update module-registration.php.template to set translations for
  @wordpress/* script module packages that use wp-i18n

All calls guarded with function_exists() for forward compatibility with
the Core implementation.

See https://core.trac.wordpress.org/ticket/65015.

* Add backport changelog entry for script module translations

Links WordPress/wordpress-develop#11543 to this PR.

* Retarget script module translations compat to WordPress 7.0.

Move the script modules translation polyfill from the wordpress-7.1
compat directory to wordpress-7.0 so the fix targets the 7.0 release,
which is where the new admin pages (Connectors, Fonts) using script
modules were introduced.

Also moves the backport changelog entry accordingly.

See https://core.trac.wordpress.org/ticket/65015.

* Script Modules: Align null return type in translation polyfill

Update gutenberg_get_script_module_src() and its callers in the
script module translation polyfill to return/check for null instead
of false, matching the ?string return type of the Core
WP_Script_Modules::get_registered_src() method.

* Script Modules: Pass $is_module=true to load_script_textdomain_relative_path filter

Pass the new $is_module argument (introduced in WordPress 7.0) to the
load_script_textdomain_relative_path filter from the script module
translation polyfill, so callers of the filter can distinguish script
modules from classic scripts.

* Script Modules: Sync translation polyfill with upstream Core changes

Mirror two upstream changes from the Core PR:

1. Rename get_registered_src() to get_registered() — Core renamed this
   method and changed it to return the full module data array instead
   of just the src. Update gutenberg_get_script_module_src() to call
   the new method and read $module['src'] from the returned array,
   keeping the reflection fallback for older WP versions.

2. Adopt the ES6 setLocaleData JS function and sprintf-based output
   format introduced in Core for print_script_module_translations(),
   so the polyfill stays consistent with Core.

* Script Modules: Auto-detect translations for script modules

Sync with upstream Core change that removes the explicit
wp_set_script_module_translations() registration requirement:

- Drop the wp_set_script_module_translations() calls in
  routes-registration.php.template and module-registration.php.template.
  Core now iterates all enqueued modules and auto-loads translations
  from the default text domain.
- Rework gutenberg_print_script_module_translations() in the polyfill
  to do the same auto-iteration. Keep wp_set_script_module_translations()
  as an explicit override API for modules using non-default text
  domains or custom translation paths.

* Script Modules: Align polyfill override storage with Core field names

Update the script module translation polyfill to use 'textdomain' and
'translations_path' as the field names inside its override storage,
matching the names adopted on the Core side (and used by WP_Dependency
for classic scripts).

Internal change only; the wp_set_script_module_translations() signature
is unchanged.

* Script Modules: Namespace translation inline script IDs.

* Script Modules: Drop redundant translations_ prefix from polyfill path key.

* Script Modules: Hoist locale-data JS heredoc out of the translations loop.

* Script Modules: Print wp-i18n just-in-time before translation inline scripts.

* Script Modules: Simplify polyfill translation override lookup with null-coalescing.

* Script Modules: Guard polyfill setLocaleData call against missing wp.i18n.

* Script Modules: Force-print wp-i18n before translation inline scripts in polyfill.

* Script Modules: Slim polyfill to translation printing only.

Co-authored-by: manzoorwanijk <[email protected]>
Co-authored-by: westonruter <[email protected]>
Co-authored-by: jsnajdr <[email protected]>
peterwilsoncc pushed a commit to peterwilsoncc/gutenberg-build that referenced this pull request Apr 28, 2026
* I18N: Add translation support for script modules

Add polyfill for `wp_set_script_module_translations()` and update build
templates to call it for script modules that depend on `wp-i18n`.

Script modules have no mechanism to load i18n translation data, leaving
strings in ES modules untranslated regardless of site language. This
affects admin pages built as script modules like Connectors and Fonts.

Changes:
- Add polyfill in lib/compat/wordpress-7.1/script-modules.php with
  wp_set_script_module_translations(), load_script_module_textdomain(),
  and gutenberg_print_script_module_translations()
- Update routes-registration.php.template to set translations for route
  content and route modules that use wp-i18n
- Update module-registration.php.template to set translations for
  @wordpress/* script module packages that use wp-i18n

All calls guarded with function_exists() for forward compatibility with
the Core implementation.

See https://core.trac.wordpress.org/ticket/65015.

* Add backport changelog entry for script module translations

Links WordPress/wordpress-develop#11543 to this PR.

* Retarget script module translations compat to WordPress 7.0.

Move the script modules translation polyfill from the wordpress-7.1
compat directory to wordpress-7.0 so the fix targets the 7.0 release,
which is where the new admin pages (Connectors, Fonts) using script
modules were introduced.

Also moves the backport changelog entry accordingly.

See https://core.trac.wordpress.org/ticket/65015.

* Script Modules: Align null return type in translation polyfill

Update gutenberg_get_script_module_src() and its callers in the
script module translation polyfill to return/check for null instead
of false, matching the ?string return type of the Core
WP_Script_Modules::get_registered_src() method.

* Script Modules: Pass $is_module=true to load_script_textdomain_relative_path filter

Pass the new $is_module argument (introduced in WordPress 7.0) to the
load_script_textdomain_relative_path filter from the script module
translation polyfill, so callers of the filter can distinguish script
modules from classic scripts.

* Script Modules: Sync translation polyfill with upstream Core changes

Mirror two upstream changes from the Core PR:

1. Rename get_registered_src() to get_registered() — Core renamed this
   method and changed it to return the full module data array instead
   of just the src. Update gutenberg_get_script_module_src() to call
   the new method and read $module['src'] from the returned array,
   keeping the reflection fallback for older WP versions.

2. Adopt the ES6 setLocaleData JS function and sprintf-based output
   format introduced in Core for print_script_module_translations(),
   so the polyfill stays consistent with Core.

* Script Modules: Auto-detect translations for script modules

Sync with upstream Core change that removes the explicit
wp_set_script_module_translations() registration requirement:

- Drop the wp_set_script_module_translations() calls in
  routes-registration.php.template and module-registration.php.template.
  Core now iterates all enqueued modules and auto-loads translations
  from the default text domain.
- Rework gutenberg_print_script_module_translations() in the polyfill
  to do the same auto-iteration. Keep wp_set_script_module_translations()
  as an explicit override API for modules using non-default text
  domains or custom translation paths.

* Script Modules: Align polyfill override storage with Core field names

Update the script module translation polyfill to use 'textdomain' and
'translations_path' as the field names inside its override storage,
matching the names adopted on the Core side (and used by WP_Dependency
for classic scripts).

Internal change only; the wp_set_script_module_translations() signature
is unchanged.

* Script Modules: Namespace translation inline script IDs.

* Script Modules: Drop redundant translations_ prefix from polyfill path key.

* Script Modules: Hoist locale-data JS heredoc out of the translations loop.

* Script Modules: Print wp-i18n just-in-time before translation inline scripts.

* Script Modules: Simplify polyfill translation override lookup with null-coalescing.

* Script Modules: Guard polyfill setLocaleData call against missing wp.i18n.

* Script Modules: Force-print wp-i18n before translation inline scripts in polyfill.

* Script Modules: Slim polyfill to translation printing only.

Co-authored-by: manzoorwanijk <[email protected]>
Co-authored-by: westonruter <[email protected]>
Co-authored-by: jsnajdr <[email protected]>

Source: WordPress/gutenberg@2bb7f3f
t-hamano pushed a commit to WordPress/gutenberg that referenced this pull request Apr 29, 2026
* I18N: Add translation support for script modules

Add polyfill for `wp_set_script_module_translations()` and update build
templates to call it for script modules that depend on `wp-i18n`.

Script modules have no mechanism to load i18n translation data, leaving
strings in ES modules untranslated regardless of site language. This
affects admin pages built as script modules like Connectors and Fonts.

Changes:
- Add polyfill in lib/compat/wordpress-7.1/script-modules.php with
  wp_set_script_module_translations(), load_script_module_textdomain(),
  and gutenberg_print_script_module_translations()
- Update routes-registration.php.template to set translations for route
  content and route modules that use wp-i18n
- Update module-registration.php.template to set translations for
  @wordpress/* script module packages that use wp-i18n

All calls guarded with function_exists() for forward compatibility with
the Core implementation.

See https://core.trac.wordpress.org/ticket/65015.

* Add backport changelog entry for script module translations

Links WordPress/wordpress-develop#11543 to this PR.

* Retarget script module translations compat to WordPress 7.0.

Move the script modules translation polyfill from the wordpress-7.1
compat directory to wordpress-7.0 so the fix targets the 7.0 release,
which is where the new admin pages (Connectors, Fonts) using script
modules were introduced.

Also moves the backport changelog entry accordingly.

See https://core.trac.wordpress.org/ticket/65015.

* Script Modules: Align null return type in translation polyfill

Update gutenberg_get_script_module_src() and its callers in the
script module translation polyfill to return/check for null instead
of false, matching the ?string return type of the Core
WP_Script_Modules::get_registered_src() method.

* Script Modules: Pass $is_module=true to load_script_textdomain_relative_path filter

Pass the new $is_module argument (introduced in WordPress 7.0) to the
load_script_textdomain_relative_path filter from the script module
translation polyfill, so callers of the filter can distinguish script
modules from classic scripts.

* Script Modules: Sync translation polyfill with upstream Core changes

Mirror two upstream changes from the Core PR:

1. Rename get_registered_src() to get_registered() — Core renamed this
   method and changed it to return the full module data array instead
   of just the src. Update gutenberg_get_script_module_src() to call
   the new method and read $module['src'] from the returned array,
   keeping the reflection fallback for older WP versions.

2. Adopt the ES6 setLocaleData JS function and sprintf-based output
   format introduced in Core for print_script_module_translations(),
   so the polyfill stays consistent with Core.

* Script Modules: Auto-detect translations for script modules

Sync with upstream Core change that removes the explicit
wp_set_script_module_translations() registration requirement:

- Drop the wp_set_script_module_translations() calls in
  routes-registration.php.template and module-registration.php.template.
  Core now iterates all enqueued modules and auto-loads translations
  from the default text domain.
- Rework gutenberg_print_script_module_translations() in the polyfill
  to do the same auto-iteration. Keep wp_set_script_module_translations()
  as an explicit override API for modules using non-default text
  domains or custom translation paths.

* Script Modules: Align polyfill override storage with Core field names

Update the script module translation polyfill to use 'textdomain' and
'translations_path' as the field names inside its override storage,
matching the names adopted on the Core side (and used by WP_Dependency
for classic scripts).

Internal change only; the wp_set_script_module_translations() signature
is unchanged.

* Script Modules: Namespace translation inline script IDs.

* Script Modules: Drop redundant translations_ prefix from polyfill path key.

* Script Modules: Hoist locale-data JS heredoc out of the translations loop.

* Script Modules: Print wp-i18n just-in-time before translation inline scripts.

* Script Modules: Simplify polyfill translation override lookup with null-coalescing.

* Script Modules: Guard polyfill setLocaleData call against missing wp.i18n.

* Script Modules: Force-print wp-i18n before translation inline scripts in polyfill.

* Script Modules: Slim polyfill to translation printing only.

Co-authored-by: manzoorwanijk <[email protected]>
Co-authored-by: westonruter <[email protected]>
Co-authored-by: jsnajdr <[email protected]>
pento pushed a commit that referenced this pull request Apr 30, 2026
Add automatic translation loading for script modules (ES modules), so strings using `__()` and friends from `@wordpress/i18n` can be translated at runtime. This brings classic script i18n parity to script modules registered via `wp_register_script_module()`, which previously had no way to load translation data, leaving strings untranslated on screens like Connectors and Fonts that are built as script modules.

At the `admin_print_footer_scripts` and `wp_footer` actions, every enqueued script module and its dependencies are walked, the translation chunk is loaded for each, and an inline `<script>` calls `wp.i18n.setLocaleData()` so translations are available before deferred modules execute. Note there is currently a runtime dependency on the `wp-i18n` classic script, which is printed just-in-time if not already enqueued. This coupling is to be removed in a future release.

Public API:

* `WP_Script_Modules::set_translations()` stores the text domain (and optional path) per registered module to override the text domain and path. A global `wp_set_script_module_translations()` function is added as a wrapper around `wp_script_modules()->set_translations()`.
* `WP_Script_Modules::get_registered()` obtains a registered module's data. See #60597.
* `WP_Script_Modules::print_script_module_translations()` emits inline `wp.i18n.setLocaleData()` calls after classic scripts load but before modules execute.
* `load_script_module_textdomain()` loads the translation data for a given script module ID and text domain.
* The existing `load_script_textdomain_relative_path` filter gains a third `$is_module` parameter so callers can distinguish classic-script and script-module lookups when resolving translation paths.

PHPStan types are also added in `WP_Script_Modules`. See #64238.

Developed in #11543

Reviewed by audrasjb.
Merges r62278 to the 7.0 branch.

Props manzoorwanijk, westonruter, jsnajdr, jonsurrell, mukesh27, peterwilsoncc, 369work, desrosj, sabernhardt, nilambar, jorgefilipecosta, malayladu.
See #64238, #60597.
Fixes #65015.


git-svn-id: https://develop.svn.wordpress.org/branches/7.0@62286 602fd350-edb4-49c9-b593-d223f7449a82
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Apr 30, 2026
Add automatic translation loading for script modules (ES modules), so strings using `__()` and friends from `@wordpress/i18n` can be translated at runtime. This brings classic script i18n parity to script modules registered via `wp_register_script_module()`, which previously had no way to load translation data, leaving strings untranslated on screens like Connectors and Fonts that are built as script modules.

At the `admin_print_footer_scripts` and `wp_footer` actions, every enqueued script module and its dependencies are walked, the translation chunk is loaded for each, and an inline `<script>` calls `wp.i18n.setLocaleData()` so translations are available before deferred modules execute. Note there is currently a runtime dependency on the `wp-i18n` classic script, which is printed just-in-time if not already enqueued. This coupling is to be removed in a future release.

Public API:

* `WP_Script_Modules::set_translations()` stores the text domain (and optional path) per registered module to override the text domain and path. A global `wp_set_script_module_translations()` function is added as a wrapper around `wp_script_modules()->set_translations()`.
* `WP_Script_Modules::get_registered()` obtains a registered module's data. See #60597.
* `WP_Script_Modules::print_script_module_translations()` emits inline `wp.i18n.setLocaleData()` calls after classic scripts load but before modules execute.
* `load_script_module_textdomain()` loads the translation data for a given script module ID and text domain.
* The existing `load_script_textdomain_relative_path` filter gains a third `$is_module` parameter so callers can distinguish classic-script and script-module lookups when resolving translation paths.

PHPStan types are also added in `WP_Script_Modules`. See #64238.

Developed in WordPress/wordpress-develop#11543

Reviewed by audrasjb.
Merges r62278 to the 7.0 branch.

Props manzoorwanijk, westonruter, jsnajdr, jonsurrell, mukesh27, peterwilsoncc, 369work, desrosj, sabernhardt, nilambar, jorgefilipecosta, malayladu.
See #64238, #60597.
Fixes #65015.

Built from https://develop.svn.wordpress.org/branches/7.0@62286


git-svn-id: http://core.svn.wordpress.org/branches/7.0@61566 1a063a9b-81f0-0310-95a4-ce76da25c4cd
@westonruter
Copy link
Copy Markdown
Member

Follow up: #11690

See Trac comment: https://core.trac.wordpress.org/ticket/65015#comment:47

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants