Make WordPress Core

Opened 6 weeks ago

Closed 4 days ago

#65015 closed defect (bug) (fixed)

Connectors: Several strings are not translated

Reported by: 369work's profile 369work Owned by: westonruter's profile westonruter
Milestone: 7.0 Priority: high
Severity: normal Version:
Component: I18N Keywords: has-patch has-unit-tests connectors commit needs-dev-note fixed-major dev-reviewed
Focuses: Cc:

Description (last modified by sabernhardt)

In the Connectors section of the WordPress admin panel, several strings remain in English regardless of the site language setting.

Steps to reproduce:

  • Install WordPress 7.0 RC2
  • Switch the site language to any non-English language
  • Navigate to the Connectors section in the admin panel

Strings that are not translated:

  • "Text and image generation with Gemini and Imagen."
  • "If the connector you need is not listed"
  • "All of your API keys and credentials are stored here and shared across plugins. Configure once and use everywhere."
  • "Set up" (button)

Expected behavior:

All of the above strings should be translatable, as the "Install" and "Cancel" buttons already are.

Actual behavior:

The strings listed above are not translated and remain in English regardless of the site language setting.

WordPress version: 7.0 RC2

Attachments (3)

スクリーンショット 2026-04-03 020420.png (526.8 KB) - added by 369work 6 weeks ago.
Connectors-screen-Japanese.png (124.0 KB) - added by sabernhardt 5 weeks ago.
Connectors screen in Japanese
Screenshot 2026-04-06 at 8.51.16 PM.png (17.8 KB) - added by nilambar 5 weeks ago.

Download all attachments as: .zip

Change History (59)

@sabernhardt
5 weeks ago

Connectors screen in Japanese

#1 @sabernhardt
5 weeks ago

  • Description modified (diff)
  • Keywords needs-patch removed
  • Milestone changed from Awaiting Review to 7.0
  • Summary changed from Connectors: Several strings are not translated (missing gettext) to Connectors: Several strings are not translated

I have the translation for the Gemini and Imagen string now because that is in the connectors.php file, but the JSON translations are not used yet.

All Japanese string translations for WordPress 7.0 have been completed, including those related to connectors.

The strings have the __() function, and the script requests the i18n package.

The script module includes some translations:

<script id="wp-script-module-data-options-connectors-wp-admin" type="application/json">
{"connectors":{"anthropic":{"name":"Anthropic","description":"Claude を使用したテキスト生成。","logoUrl":null,"type":"ai_provider","authentication":{"method":"api_key","settingName":"connectors_ai_anthropic_api_key","credentialsUrl":"https://platform.claude.com/settings/keys","keySource":"none","isConnected":false},"plugin":{"file":"ai-provider-for-anthropic/plugin.php","isInstalled":false,"isActivated":false}},"google":{"name":"Google","description":"Gemini と Imagen による、テキストおよび画像の生成。","logoUrl":null,"type":"ai_provider","authentication":{"method":"api_key","settingName":"connectors_ai_google_api_key","credentialsUrl":"https://aistudio.google.com/api-keys","keySource":"none","isConnected":false},"plugin":{"file":"ai-provider-for-google/plugin.php","isInstalled":false,"isActivated":false}},"openai":{"name":"OpenAI","description":"GPT と Dall-E による、テキストおよび画像の生成。","logoUrl":null,"type":"ai_provider","authentication":{"method":"api_key","settingName":"connectors_ai_openai_api_key","credentialsUrl":"https://platform.openai.com/api-keys","keySource":"none","isConnected":false},"plugin":{"file":"ai-provider-for-openai/plugin.php","isInstalled":false,"isActivated":false}}}}
</script>

I remember having a similar issue earlier with strings in JavaScript when using trunk or beta/RC, and I think it resolved itself in the general release. However, this might need to assign the translations for the script module.

#2 @jorgefilipecosta
5 weeks ago

Hi @369work, @sabernhardt. Thank you for reporting these issues, we are using the i18n functions on these strings so it may be temporary until the final release properly connects these strings Gutenberg side implementation is using. Is this issue only happening on the connectors screen or it also happens on some strings in Appearance -> Fonts, or the editor and site editor?

Than k you in advance for any additional insights.

#3 @nilambar
5 weeks ago

Some strings in Fonts page are also not translated.

#4 @369work
5 weeks ago

Hi,@jorgefilipecosta.

Thank you.
Currently, as far as I've checked,

  1. The part me pointed out in Connector
  2. The Library and Upload tabs in Appearance > Fonts

These two are untranslated.
The Site Editor seems fine in the parts I've looked at.

#5 @malayladu
5 weeks ago

  • Keywords close added
  • Resolution set to wontfix
  • Status changed from new to closed

I've found that Library and Upload strings are coming from the Gutenberg build file. It should be part of the Gutenberg repo.

Findings - gutenberg/build/routes/font-list/content.js

This is already fixed in the Gutenberg repo. So, it seems a syncing issue of Gutenberg to the core. This should be synced in upcoming release.

https://github.com/WordPress/gutenberg/blob/a473a4cbb07a650238d5ec11a0f9538db814ab30/routes/font-list/stage.tsx#L53-L60

I worked on this issue on contributer's day and reviewed by cordinator.

This ticket was mentioned in PR #11514 on WordPress/wordpress-develop by @manishxdp.


5 weeks ago
#6

  • Keywords has-patch added

I tested this issue again on the latest WordPress trunk and found that some strings in the Connectors screen are still not translated when switching to a non-English language.

issues Affected strings:

  • "Text and image generation with Gemini and Imagen."
  • "If the connector you need is not listed"
  • "All of your API keys and credentials are stored here and shared across plugins. Configure once and use everywhere."
  • "Set up"

Other buttons like "Install" and "Cancel" are translated correctly.

Issue cause:

  • Some PHP strings are not wrapped in __()
  • Some JavaScript strings are not using @wordpress/i18n

Changes done :

  • Wrapped PHP strings using __( 'string', 'text-domain' )
  • Updated JS strings using import { __ } from '@wordpress/i18n';

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

Testing:

  1. Install latest WordPress trunk
  2. Change site language to a non-English language (e.g., Japanese)
  3. Open Connectors screen
  4. Before patch: Strings are in English
  5. After patch: Strings are translated

@desrosj commented on PR #11514:


5 weeks ago
#7

I'm going to close this out since a different approach entirely is necessary.

#8 @desrosj
5 weeks ago

  • Keywords needs-patch added; close has-patch removed
  • Priority changed from normal to high

I've been working with @manishxdp today at WordCamp Asia Contributor Day, and based on what we found this seems like a legitimate issue that needs to be addressed.

As far as I can tell based on our investigation today, the normal practice for registering translations for a specific script is to call wp_set_script_translations() after the script is registered. This prints an inline, localized script with the translated strings so that wp-i18n can properly localize them.

However, there does not appear to be an equivalent function for script modules. I'm not clear on the history there and whether this is intentional or what the functional differences are between scripts and script modules, but it seems that a set_translations() method likely needs to be added to WP_Script_Modules so that strings within modules are properly localized and translated for the user.

That change belongs here in wordpress-develop with the wp_set_script_module_translations() wrapper (see wp_set_script_translations()). However, a polyfill will need to be added to gutenberg to ensure it works in the plugin for users running WP<7.0. Then the template files in the wp-build package will need to be updated to call wp_set_script_module_translations()

In talking with @westonruter, it seems this will be quite complicated because of how modules include files (directly and not typically registered as a script through WordPress). The strings for every dependency file needs to also be localized respecting the textdomain for each.

I'm unclear how this is different than the previous issues reported above, but I don't believe that this will resolve itself in the same way.

The surface area for this bug will be any registered script module that contains strings. For 7.0, this is primarily affecting the new admin pages registered through routes.

I have changed this to a high priority issue because it seems that these strings will not be localized in WordPress 7.0 without resolving this gap.

#9 @desrosj
5 weeks ago

  • Resolution wontfix deleted
  • Status changed from closed to reopened

#10 @369work
5 weeks ago

Thanks for the thorough investigation.
I'll keep watching this ticket and happy to help with testing when a patch is available.

This ticket was mentioned in PR #11543 on WordPress/wordpress-develop by @manzoorwani.jk.


4 weeks ago
#11

  • Keywords has-patch has-unit-tests added; needs-patch removed

## Summary

Adds translation support for script modules (WP_Script_Modules), mirroring the existing wp_set_script_translations() infrastructure for classic scripts.

Script modules (ES modules registered via wp_register_script_module()) currently have no way to load i18n translation data. This means any strings using __() from @wordpress/i18n inside script modules remain untranslated. This affects the new admin pages in WordPress 7.0+ that are built as script modules, including Connectors and Fonts.

## Changes

### New public API

  • wp_set_script_module_translations( $id, $domain, $path ) — registers a text domain for a script module (in script-modules.php)
  • load_script_module_textdomain( $id, $domain, $path ) — loads translation JSON files for a script module, resolving the file path from the module's source URL (in l10n.php)

### WP_Script_Modules class changes

  • Added set_translations() method — stores text domain and translation path per module
  • Added get_registered_src() method — public accessor for a module's raw source URL (used by load_script_module_textdomain())
  • Added print_script_module_translations() method — outputs inline <script> tags calling wp.i18n.setLocaleData() for each enqueued module with translations
  • Hooked print_script_module_translations() 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)

### Automatic translation setup

  • wp_default_script_modules() now calls wp_set_script_module_translations() for all @wordpress/* script modules that list wp-i18n in their dependencies

### Tests

  • 7 new unit tests covering set_translations(), get_registered_src(), wp_set_script_module_translations(), and print_script_module_translations()

## How it works

The flow mirrors classic script translations:

  1. wp_set_script_module_translations() is called after registering a script module, storing the text domain and optional path
  2. When the page renders, print_script_module_translations() iterates over all enqueued modules (and their dependencies) that have translations set
  3. For each, it calls load_script_module_textdomain() which resolves the module's source URL to a relative path, computes an MD5 hash, and looks for the corresponding JSON translation file (same naming convention as classic scripts)
  4. If found, it outputs an inline <script> calling wp.i18n.setLocaleData() with the translated strings
  5. These inline scripts execute before the deferred ES modules, ensuring translations are available when modules run

## Remaining work

  • The routes.php file in src/wp-includes/build/ is auto-generated by the Gutenberg wp-build package. The template (routes-registration.php.template) needs updating to call wp_set_script_module_translations() after registering route modules. This change belongs in the Gutenberg repository.
  • A polyfill for wp_set_script_module_translations() needs to be added to Gutenberg's lib/compat/ for sites running WP < 7.1 with the Gutenberg plugin.

## Test plan

Language packs don't include JSON translation files for script modules yet, so testing requires a small mu-plugin to provide test translations.

1. Set up the test environment:

  • Switch the site language to any non-English language (e.g., Spanish) in Settings > General
  • Drop the following mu-plugin into src/wp-content/mu-plugins/test-script-module-translations.php (create the mu-plugins directory if it doesn't exist):

<details>
<summary>test-script-module-translations.php</summary>

<?php
/**
 * Test mu-plugin: Provides mock translations for script module strings.
 * Drop this file into src/wp-content/mu-plugins/ to test script module translations.
 * DELETE after testing.
 *
 * This does two things:
 * 1. Registers translations for route modules (since routes.php is auto-generated
 *    and doesn't call wp_set_script_module_translations() yet — that change is in Gutenberg).
 * 2. Provides mock Spanish translations via the pre_load_script_translations filter
 *    so no language pack JSON files are needed.
 */

// Register translations for route modules after they are registered.
add_action( 'admin_enqueue_scripts', function () {
        add_action( 'options-connectors-wp-admin_init', function () {
                wp_set_script_module_translations( 'wp/routes/connectors-home/content', 'default' );
                wp_set_script_module_translations( 'wp/routes/connectors-home/route', 'default' );
        }, 20 );
}, 5 );

// Provide mock translations via filter.
add_filter( 'pre_load_script_translations', function ( $translations, $file, $handle, $domain ) {
        if ( 'wp/routes/connectors-home/content' !== $handle ) {
                return $translations;
        }

        return wp_json_encode( array(
                'domain'      => 'messages',
                'locale_data' => array(
                        'messages' => array(
                                ''           => array( 'domain' => 'messages', 'lang' => 'test' ),
                                'Set up'     => array( 'Configurar' ),
                                'Install'    => array( 'Instalar' ),
                                'Cancel'     => array( 'Cancelar' ),
                                'Connectors' => array( 'Conectores' ),
                                'All of your API keys and credentials are stored here and shared across plugins. Configure once and use everywhere.' =>
                                        array( 'Todas tus claves API y credenciales se almacenan aqui.' ),
                                'If the connector you need is not listed, <a>search the plugin directory</a> to see if a connector is available.' =>
                                        array( 'Si el conector que necesitas no aparece, <a>busca en el directorio de plugins</a>.' ),
                        ),
                ),
        ) );
}, 10, 4 );

</details>

2. Verify the Connectors page:

  • Navigate to Settings > Connectors (/wp-admin/options-connectors.php)
  • Expected: The heading shows "Conectores", the subtitle and footer text are in Spanish, and buttons show "Instalar" / "Configurar" / "Cancelar"
  • Before this patch: All these strings remain in English regardless of site language

3. Verify infrastructure in page source:

  • View the page source and search for js-module-translations
  • Expected: Inline <script> tags with IDs like wp/routes/connectors-home/content-js-module-translations appear after the classic scripts and before the <script type="module"> tag
  • These scripts contain wp.i18n.setLocaleData() calls with the translation data

4. Clean up:

  • Delete src/wp-content/mu-plugins/test-script-module-translations.php

@mukesh27 commented on PR #11543:


4 weeks ago
#12

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

## Use of AI Tools


@manzoorwani.jk commented on PR #11543:


4 weeks ago
#13

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

Updated. Thanks

@westonruter commented on PR #11543:


4 weeks ago
#14

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

Before:

https://github.com/user-attachments/assets/d3fdcaa4-b452-4fd3-8c58-44a168f93bc8

After:

https://github.com/user-attachments/assets/7e1cd1a0-fbd9-4754-a963-91c556affe38

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?

@manzoorwani.jk commented on PR #11543:


4 weeks ago
#15

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.
  1. 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.

#16 @JeffPaul
4 weeks ago

  • Keywords connectors added

@manzoorwani.jk commented on PR #11543:


4 weeks ago
#17

@westonruter thank for the changes. I will update the Gutenberg PR accordingly.

#18 @westonruter
4 weeks ago

  • Keywords commit dev-feedback added
  • Owner set to westonruter
  • Status changed from reopened to reviewing

I've reviewed and amended the core PR. It's approved by me for commit.

The companion Gutenberg PR will need to be adjusted based on what gets committed.

Self-assigning for review and commit. But I'd like to get another committer to review this as well since we're in RC-ish.

#19 @369work
4 weeks ago

I tested it right away, but the word "setup" isn't translated.

The same goes for "Text generation with Claude."

I'm using a Japanese environment.

I'd like to attach a screenshot, but I don't know how...

Last edited 4 weeks ago by 369work (previous) (diff)

#20 @westonruter
4 weeks ago

@369work This may be explained by @manzoorwanijk in a PR comment.

#21 @manzoorwani.jk
4 weeks ago

Yes, you can do as suggested in that comment.

#22 @manzoorwani.jk
3 weeks ago

I have simplified the PR description and updated the test instructions. It should now be much easier to test the changes.

Last edited 3 weeks ago by manzoorwani.jk (previous) (diff)

@manzoorwani.jk commented on PR #11543:


3 weeks ago
#23

@jsnajdr thank you for the feedback. I have addressed it and it is now much easier to test the PR, since translations are loaded automatically now.

@jonsurrell commented on PR #11543:


3 weeks ago
#25

I've come across this very late, so I'll just share some thoughts looking ahead. None of this is blocking or critique of this PR.

  • I think it would be great for i18n APIs to be exposed from a script module.
  • I'm very excited to see what @jsnajdr has mentioned about dynamic loading.
  • I'd love to see classic scripts change to lazy or pull style configuration like is discussed on this ticket, analogous to what script modules do. The data is made available for scripts to use on demand without imperatively evaluating inline scripts. i18n script seems like a great candidate to leverage this and it would address the "fail-safe" idea mentioned above.

Thanks for your work here @manzoorwanijk 👍

@westonruter commented on PR #11543:


3 weeks ago
#26

Shall I commit this?

#27 @westonruter
3 weeks ago

  • Keywords needs-dev-note added

The new translation support for modules seems like it warrants a dev note.

@jsnajdr commented on PR #11543:


2 weeks ago
#28

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 commented on PR #11543:


2 weeks ago
#29

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.

@manzoorwani.jk commented on PR #11543:


2 weeks ago
#30

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

It's here https://github.com/WordPress/wordpress-develop/pull/11543#discussion_r3122208067

@manzoorwani.jk commented on PR #11543:


2 weeks ago
#31

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

It's here https://github.com/WordPress/wordpress-develop/pull/11543#discussion_r3122208067

@manzoorwani.jk commented on PR #11543:


2 weeks ago
#32

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 - https://github.com/WordPress/wordpress-develop/pull/11543#pullrequestreview-4148986825

@jsnajdr commented on PR #11543:


2 weeks ago
#33

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 commented on PR #11543:


2 weeks ago
#34

@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:

https://github.com/WordPress/wordpress-develop/blob/374a234e861be65fbbc5e0b0508bd4be2f005524/src/wp-includes/class-wp-script-modules.php#L16-L23

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,
  * } 

@manzoorwani.jk commented on PR #11543:


2 weeks ago
#35

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

You can see the changes in f8bd260855 and e5b32e7625

@jsnajdr commented on PR #11543:


2 weeks ago
#36

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 https://github.com/WordPress/wordpress-develop/commit/f8bd260855aa8d193acba86454630d23018062a8 and https://github.com/WordPress/wordpress-develop/commit/e5b32e76254fe942dd7eaf561fbd29e8e1855a4b

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.

@manzoorwani.jk commented on PR #11543:


2 weeks ago
#37

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 89a25ea0a2. textdomain and translations_path now sit directly on the registered entry, matching WP_Dependency's shape. Thanks for the patient nudge 🙂

#38 @desrosj
2 weeks ago

Related #60234.

@westonruter commented on PR #11543:


2 weeks ago
#39

I'm committing now.

#40 @westonruter
2 weeks ago

  • Resolution set to fixed
  • Status changed from reviewing to closed

In 62278:

I18N: Add translation support for script modules.

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 https://github.com/WordPress/wordpress-develop/pull/11543

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

#41 @westonruter
2 weeks ago

  • Keywords fixed-major added
  • Resolution fixed deleted
  • Status changed from closed to reopened

Re-opening for 7.0 backport.

@westonruter commented on PR #11543:


2 weeks ago
#42

Committed in r62278 (93d77a2).

This ticket was mentioned in Slack in #core by audrasjb. View the logs.


12 days ago

#44 @audrasjb
12 days ago

  • Keywords dev-reviewed added; dev-feedback removed

2nd committer sign-off: let's get this backported before the next RC.

#45 @peterwilsoncc
11 days ago

  • Resolution set to fixed
  • Status changed from reopened to closed

In 62286:

I18N: Add translation support for script modules.

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 https://github.com/WordPress/wordpress-develop/pull/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.

This ticket was mentioned in PR #11690 on WordPress/wordpress-develop by @westonruter.


11 days ago
#46

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

## Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Opus 4.7
Used for: PHPStan type research

#47 @westonruter
11 days ago

I found a minor issue with [62278]. Once it landed, it started causing test failures in the Performance Lab plugin (PR):

1) Test_Audit_Enqueued_Assets::test_perflab_aea_audit_enqueued_scripts
Undefined array key "path"

…/wp-includes/l10n.php:1242
…/wp-includes/l10n.php:1185
…/wp-includes/class-wp-script-modules.php:399
…/wp-includes/class-wp-hook.php:341
…/wp-includes/class-wp-hook.php:365
…/wp-includes/plugin.php:522
…/wp-includes/general-template.php:3224
…/wordpress-phpunit/includes/utils.php:437
…/wp-content/plugins/performance/plugins/performance-lab/tests/data/class-audit-assets-mock-assets.php:94
…/wp-content/plugins/performance/plugins/performance-lab/tests/data/class-audit-assets-mock-assets.php:142
…/wp-content/plugins/performance/plugins/performance-lab/tests/includes/site-health/audit-enqueued-assets/test-audit-enqueued-assets.php:107

At issue is that the unit test in the plugin was doing this:

<?php
wp_enqueue_script_module( 'module1', 'https://module1.example.com', array(), null );

Note the lack of a path on the URL. This is causing the usage on two lines of _load_script_textdomain_from_src() to fail when it accesses $src_url['path'].

Now, it is extremely unlikely for URLs to be provided to a registered script module that lack a path. So this is why it is minor. Nevertheless, this is an opportunity for hardening.

Here's an easy way to reproduce this issue. Put the following code into a PHP file like try-script-module-index-undefined-issue.php:

<?php
wp_enqueue_script_module( 'module1', 'https://module1.example.com', array(), null );
wp_script_modules()->print_script_module_translations();

The result is:

npm run env:cli -- eval-file try-script-module-index-undefined-issue.php

> WordPress@7.1.0 env:cli
> node ./tools/local-env/scripts/docker.js exec --user wp_php cli wp eval-file try-script-module-index-undefined-issue.php

Warning: Undefined array key "path" in /var/www/src/wp-includes/l10n.php on line 1242
Deprecated: str_starts_with(): Passing null to parameter #1 ($haystack) of type string is deprecated in /var/www/src/wp-includes/l10n.php on line 1242
Warning: Undefined array key "path" in /var/www/src/wp-includes/l10n.php on line 1268
Deprecated: str_starts_with(): Passing null to parameter #1 ($haystack) of type string is deprecated in /var/www/src/wp-includes/l10n.php on line 1268

There are quite a lot of PHPStan issues being reported with _load_script_textdomain_from_src() which pre-exist 7.0 when the logic was originally located in load_script_textdomain():

Line Error
1214 Cannot call method get() on mixed.
1217 Parameter #1 $value of function untrailingslashit expects string, mixed given.
1242 Cannot access offset 'path' on mixed. (×2)
1242 Parameter #1 $haystack of str_starts_with expects string, mixed given.
1242 Parameter #2 $needle of str_starts_with expects string, mixed given.
1243 Cannot access offset 'host' on mixed. (×2)
1246 Cannot access offset 'path' on mixed.
1247 Cannot access offset 'path' on mixed.
1247 Parameter #1 $string of strlen expects string, mixed given.
1247 Parameter #1 $string of substr expects string, mixed given.
1249 Cannot access offset 'path' on mixed.
1251 Parameter #1 $str of trim expects string, mixed given.
1268 Cannot access offset 'path' on mixed. (×2)
1268 Parameter #1 $haystack of str_starts_with expects string, mixed given.
1268 Parameter #2 $needle of str_starts_with expects string, mixed given.
1269 Cannot access offset 'host' on mixed. (×2)
1272 Cannot access offset 'path' on mixed.
1273 Cannot access offset 'path' on mixed.
1273 Parameter #1 $string of strlen expects string, mixed given.
1273 Parameter #1 $string of substr expects string, mixed given.
1275 Cannot access offset 'path' on mixed.
1277 Parameter #1 $str of trim expects string, mixed given.
1284 Cannot access offset 'host' on mixed. (×2)
1285 Cannot access offset 'path' on mixed.
1286 Cannot access offset 'path' on mixed.
1286 Parameter #1 $str of trim expects string, mixed given.
1287 Cannot access offset 'path' on mixed.
1287 Parameter #1 $haystack of str_starts_with expects string, mixed given.
1287 Parameter #1 $value of trailingslashit expects string, mixed given.
1289 Cannot access offset 'path' on mixed.
1289 Parameter #1 $string of strlen expects string, mixed given.
1289 Parameter #1 $string of substr expects string, mixed given.
1312 Parameter #1 $haystack of str_ends_with expects string, mixed given.
1313 Parameter #1 $string of substr expects string, mixed given.
1316 Parameter #1 $str of md5 expects string, mixed given.

I've opened a pull request to fix the undefined index error, as well as to fix the remaining PHPStan at level 10, which primarily involves a few fixes to _load_script_textdomain_from_src() but primarily it involves adding a conditional return type for wp_parse_url().

@westonruter commented on PR #11690:


11 days ago
#48

@manzoorwanijk Please review.

@westonruter commented on PR #11690:


10 days ago
#50

The addition of PHPStan types here ensures that all of the scenarios are accounted for which could cause errors.

#51 @westonruter
9 days ago

In 62293:

I18N: Harden against undefined index in _load_script_textdomain_from_src().

This guards against an undefined index warning being raised when a script or script module is registered with a URL that lacks a path component.

This also adds full PHPStan type definitions for wp_parse_url(), ensuring that the _load_script_textdomain_from_src() function has no PHPStan errors at rule level 10.

Developed in https://github.com/WordPress/wordpress-develop/pull/11690

Follow-up to r62278.

Props westonruter, manzoorwanijk, mukesh27.
See #65015, #64238.

@westonruter commented on PR #11690:


9 days ago
#52

Committed in r62293 (0fced49)

#53 @westonruter
9 days ago

  • Keywords dev-feedback added; dev-reviewed removed
  • Resolution fixed deleted
  • Status changed from closed to reopened

Re-opening to backport follow-up [62293] to 7.0 branch.

#54 @desrosj
5 days ago

  • Keywords dev-reviewed added; dev-feedback removed

[62293] looks good to backport to the 7.0 branch.

#55 @jorbin
4 days ago

In 62325:

I18N: Harden against undefined index in _load_script_textdomain_from_src().

This guards against an undefined index warning being raised when a script or script module is registered with a URL that lacks a path component.

This also adds full PHPStan type definitions for wp_parse_url(), ensuring that the _load_script_textdomain_from_src() function has no PHPStan errors at rule level 10.

Developed in https://github.com/WordPress/wordpress-develop/pull/11690

Follow-up to r62278.

Reviewed by desrosj, jorbin.
Merges [62293] to the 7.0 branch.

Props westonruter, manzoorwanijk, mukesh27.
See #65015, #64238.

#56 @jorbin
4 days ago

  • Resolution set to fixed
  • Status changed from reopened to closed
Note: See TracTickets for help on using tickets.