From 173f94339207cf155dca8529f94b342cd5f92afa Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 08:46:35 -0700 Subject: [PATCH 1/8] Require literal strings for gettext function arguments Translatable strings, contexts, and text domains are extracted statically from source, so passing a variable to `__()`, `_x()`, `_n()`, `_n_noop()` and friends yields a string that can never be translated. Add `@phpstan-param literal-string` narrowings for `$text`, `$single`, `$singular`, `$plural`, `$context`, and `$domain` on the gettext wrappers so PHPStan flags such misuse. Add `@phpstan-return` shapes to `_n_noop()` and `_nx_noop()` and a matching `@phpstan-param` shape for `$nooped_plural` on `translate_nooped_plural()`, so the literal types carry through the nooped plural array into `_n()`/`_nx()`. Making the shape keys required also resolves the existing `offsetAccess.notFound` errors in `translate_nooped_plural()`. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/l10n.php | 78 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 446b828721638..e00a78bc3dd49 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -332,6 +332,9 @@ function translate_with_gettext_context( $text, $context, $domain = 'default' ) * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $domain */ function __( $text, $domain = 'default' ) { return translate( $text, $domain ); @@ -348,6 +351,9 @@ function __( $text, $domain = 'default' ) { * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text on success, original text on failure. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $domain */ function esc_attr__( $text, $domain = 'default' ) { return esc_attr( translate( $text, $domain ) ); @@ -365,6 +371,9 @@ function esc_attr__( $text, $domain = 'default' ) { * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $domain */ function esc_html__( $text, $domain = 'default' ) { return esc_html( translate( $text, $domain ) ); @@ -378,6 +387,9 @@ function esc_html__( $text, $domain = 'default' ) { * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $domain */ function _e( $text, $domain = 'default' ) { echo translate( $text, $domain ); @@ -396,6 +408,9 @@ function _e( $text, $domain = 'default' ) { * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $domain */ function esc_attr_e( $text, $domain = 'default' ) { echo esc_attr( translate( $text, $domain ) ); @@ -414,6 +429,9 @@ function esc_attr_e( $text, $domain = 'default' ) { * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $domain */ function esc_html_e( $text, $domain = 'default' ) { echo esc_html( translate( $text, $domain ) ); @@ -435,6 +453,10 @@ function esc_html_e( $text, $domain = 'default' ) { * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated context string without pipe. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $context + * @phpstan-param literal-string $domain */ function _x( $text, $context, $domain = 'default' ) { return translate_with_gettext_context( $text, $context, $domain ); @@ -449,6 +471,10 @@ function _x( $text, $context, $domain = 'default' ) { * @param string $context Context information for the translators. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $context + * @phpstan-param literal-string $domain */ function _ex( $text, $context, $domain = 'default' ) { echo _x( $text, $context, $domain ); @@ -467,6 +493,10 @@ function _ex( $text, $context, $domain = 'default' ) { * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $context + * @phpstan-param literal-string $domain */ function esc_attr_x( $text, $context, $domain = 'default' ) { return esc_attr( translate_with_gettext_context( $text, $context, $domain ) ); @@ -485,6 +515,10 @@ function esc_attr_x( $text, $context, $domain = 'default' ) { * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text. + * + * @phpstan-param literal-string $text + * @phpstan-param literal-string $context + * @phpstan-param literal-string $domain */ function esc_html_x( $text, $context, $domain = 'default' ) { return esc_html( translate_with_gettext_context( $text, $context, $domain ) ); @@ -509,6 +543,10 @@ function esc_html_x( $text, $context, $domain = 'default' ) { * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string The translated singular or plural form. + * + * @phpstan-param literal-string $single + * @phpstan-param literal-string $plural + * @phpstan-param literal-string $domain */ function _n( $single, $plural, $number, $domain = 'default' ) { $translations = get_translations_for_domain( $domain ); @@ -568,6 +606,11 @@ function _n( $single, $plural, $number, $domain = 'default' ) { * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string The translated singular or plural form. + * + * @phpstan-param literal-string $single + * @phpstan-param literal-string $plural + * @phpstan-param literal-string $context + * @phpstan-param literal-string $domain */ function _nx( $single, $plural, $number, $context, $domain = 'default' ) { $translations = get_translations_for_domain( $domain ); @@ -634,6 +677,18 @@ function _nx( $single, $plural, $number, $context, $domain = 'default' ) { * @type null $context Context information for the translators. * @type string|null $domain Text domain. * } + * + * @phpstan-param literal-string $singular + * @phpstan-param literal-string $plural + * @phpstan-param literal-string|null $domain + * @phpstan-return array{ + * 0: literal-string, + * 1: literal-string, + * singular: literal-string, + * plural: literal-string, + * context: null, + * domain: literal-string|null, + * } */ function _n_noop( $singular, $plural, $domain = null ) { return array( @@ -680,6 +735,20 @@ function _n_noop( $singular, $plural, $domain = null ) { * @type string $context Context information for the translators. * @type string|null $domain Text domain. * } + * + * @phpstan-param literal-string $singular + * @phpstan-param literal-string $plural + * @phpstan-param literal-string $context + * @phpstan-param literal-string|null $domain + * @phpstan-return array{ + * 0: literal-string, + * 1: literal-string, + * 2: literal-string, + * singular: literal-string, + * plural: literal-string, + * context: literal-string, + * domain: literal-string|null, + * } */ function _nx_noop( $singular, $plural, $context, $domain = null ) { return array( @@ -719,6 +788,15 @@ function _nx_noop( $singular, $plural, $context, $domain = null ) { * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. If $nooped_plural contains * a text domain passed to _n_noop() or _nx_noop(), it will override this value. Default 'default'. * @return string Either $singular or $plural translated text. + * + * @phpstan-param array{ + * singular: literal-string, + * plural: literal-string, + * context: literal-string|null, + * domain: literal-string|null, + * ... + * } $nooped_plural + * @phpstan-param literal-string $domain */ function translate_nooped_plural( $nooped_plural, $count, $domain = 'default' ) { if ( $nooped_plural['domain'] ) { From 681fc27e750231729173792d74ebfb2b053e4e8f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 09:03:52 -0700 Subject: [PATCH 2/8] Narrow word count type return to its three valid values `WP_Locale::get_word_count_type()` validates the translated value and falls back to `words` for anything other than `characters_excluding_spaces` or `characters_including_spaces`, so the return value is always one of those three strings. Declare that union via `@phpstan-return` on both the method and `wp_get_word_count_type()`, which returns either the method's result or the `words` default. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/class-wp-locale.php | 2 ++ src/wp-includes/l10n.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/wp-includes/class-wp-locale.php b/src/wp-includes/class-wp-locale.php index e18c0c4f1d897..b1e091e5196ca 100644 --- a/src/wp-includes/class-wp-locale.php +++ b/src/wp-includes/class-wp-locale.php @@ -442,6 +442,8 @@ public function get_list_item_separator() { * * @return string Localized word count type. Possible values are `characters_excluding_spaces`, * `characters_including_spaces`, or `words`. Defaults to `words`. + * + * @phpstan-return 'characters_excluding_spaces'|'characters_including_spaces'|'words' */ public function get_word_count_type() { diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index e00a78bc3dd49..31ff5248642c1 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -2176,6 +2176,8 @@ function wp_get_list_item_separator() { * * @return string Locale-specific word count type. Possible values are `characters_excluding_spaces`, * `characters_including_spaces`, or `words`. Defaults to `words`. + * + * @phpstan-return 'characters_excluding_spaces'|'characters_including_spaces'|'words' */ function wp_get_word_count_type() { global $wp_locale; From 26bafd2d87fca970ebdaa6e802c7d21fe1c0311f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 09:13:22 -0700 Subject: [PATCH 3/8] Specify the return type of wp_get_installed_translations() The function returns language data keyed by text domain and then by locale, with each value being the header data from `wp_get_pomo_file_data()` or `wp_get_l10n_php_file_data()`. Document it as `array>` instead of a bare `array`. Add a conditional `@phpstan-return` so that an unsupported `$type` resolves to `array{}`, matching the early return for anything other than `plugins`, `themes`, or `core`. `$type` itself stays `string` since the function deliberately tolerates other values. This resolves the missing iterable value type on the function as well as the `mixed` foreach and concatenation errors in `plugin.php` and `theme.php` where the result is consumed. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/l10n.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 31ff5248642c1..977897fd37e86 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -1701,7 +1701,10 @@ function get_available_languages( $dir = null ) { * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. * * @param string $type What to search for. Accepts 'plugins', 'themes', 'core'. - * @return array Array of language data. + * @return array> Array of language data, keyed by text domain and then + * by locale, each value being the translation file headers. + * + * @phpstan-return ( $type is 'plugins'|'themes'|'core' ? array> : array{} ) */ function wp_get_installed_translations( $type ) { global $wp_textdomain_registry; From 8a5fbd55926472a8faf2beab7462917ef42f7583 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 09:17:57 -0700 Subject: [PATCH 4/8] Return a fixed header shape from the translation file data functions `wp_get_pomo_file_data()` and `wp_get_l10n_php_file_data()` always return the same four headers, so declare that shape via `@phpstan-return` and use it for the innermost type of `wp_get_installed_translations()`. To make the shape hold: * `wp_get_pomo_file_data()` now builds its result from an explicit array of the four headers rather than returning the `get_file_data()` result, whose keys PHPStan cannot infer. The `preg_replace()` that strips the closing double quote and the preceding contextual `\n` is replaced with equivalent string functions, since `preg_replace()` may return `null`. The only input the two differ on is a value ending in a real newline, which cannot occur because `get_file_data()` trims each value. * `wp_get_l10n_php_file_data()` now ignores non-string header values from the included file, keeping the empty-string default instead of passing through whatever a malformed file contains. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/l10n.php | 53 +++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 977897fd37e86..5f500b3ff4ea7 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -1704,7 +1704,16 @@ function get_available_languages( $dir = null ) { * @return array> Array of language data, keyed by text domain and then * by locale, each value being the translation file headers. * - * @phpstan-return ( $type is 'plugins'|'themes'|'core' ? array> : array{} ) + * @phpstan-return ( + * $type is 'plugins'|'themes'|'core' + * ? array> + * : array{} + * ) */ function wp_get_installed_translations( $type ) { global $wp_textdomain_registry; @@ -1765,6 +1774,13 @@ function wp_get_installed_translations( $type ) { * * @param string $po_file Path to PO file. * @return string[] Array of PO file header values keyed by header name. + * + * @phpstan-return array{ + * 'POT-Creation-Date': string, + * 'PO-Revision-Date': string, + * 'Project-Id-Version': string, + * 'X-Generator': string, + * } */ function wp_get_pomo_file_data( $po_file ) { $headers = get_file_data( @@ -1776,11 +1792,29 @@ function wp_get_pomo_file_data( $po_file ) { 'X-Generator' => '"X-Generator', ) ); - foreach ( $headers as $header => $value ) { - // Remove possible contextual '\n' and closing double quote. - $headers[ $header ] = preg_replace( '~(\\\n)?"$~', '', $value ); + + $result = array( + 'POT-Creation-Date' => '', + 'PO-Revision-Date' => '', + 'Project-Id-Version' => '', + 'X-Generator' => '', + ); + + foreach ( array_keys( $result ) as $header ) { + $value = $headers[ $header ]; + + // Remove possible closing double quote and the contextual '\n' preceding it. + if ( str_ends_with( $value, '"' ) ) { + $value = substr( $value, 0, -1 ); + if ( str_ends_with( $value, '\n' ) ) { + $value = substr( $value, 0, -2 ); + } + } + + $result[ $header ] = $value; } - return $headers; + + return $result; } /** @@ -1790,6 +1824,13 @@ function wp_get_pomo_file_data( $po_file ) { * * @param string $php_file Path to a `.l10n.php` file. * @return string[] Array of file header values keyed by header name. + * + * @phpstan-return array{ + * 'POT-Creation-Date': string, + * 'PO-Revision-Date': string, + * 'Project-Id-Version': string, + * 'X-Generator': string, + * } */ function wp_get_l10n_php_file_data( $php_file ) { $data = (array) include $php_file; @@ -1810,7 +1851,7 @@ function wp_get_l10n_php_file_data( $php_file ) { ); foreach ( $headers as $po_header => $php_header ) { - if ( isset( $data[ $php_header ] ) ) { + if ( isset( $data[ $php_header ] ) && is_string( $data[ $php_header ] ) ) { $result[ $po_header ] = $data[ $php_header ]; } } From 58a2089b9752572d39b9ecdcf1c70f28fde0f860 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 09:22:29 -0700 Subject: [PATCH 5/8] Use string-keyed array types for translation file headers The header arrays returned by `wp_get_pomo_file_data()` and `wp_get_l10n_php_file_data()` are keyed by header name, so document them as `array` rather than `string[]`, and likewise for the innermost type in the `@return` of `wp_get_installed_translations()`. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/l10n.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 5f500b3ff4ea7..ca3e04b64c389 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -1701,8 +1701,9 @@ function get_available_languages( $dir = null ) { * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. * * @param string $type What to search for. Accepts 'plugins', 'themes', 'core'. - * @return array> Array of language data, keyed by text domain and then - * by locale, each value being the translation file headers. + * @return array>> Array of language data, keyed by text domain + * and then by locale, each value being the + * translation file headers. * * @phpstan-return ( * $type is 'plugins'|'themes'|'core' @@ -1773,7 +1774,7 @@ function wp_get_installed_translations( $type ) { * @since 3.7.0 * * @param string $po_file Path to PO file. - * @return string[] Array of PO file header values keyed by header name. + * @return array Array of PO file header values keyed by header name. * * @phpstan-return array{ * 'POT-Creation-Date': string, @@ -1823,7 +1824,7 @@ function wp_get_pomo_file_data( $po_file ) { * @since 6.6.0 * * @param string $php_file Path to a `.l10n.php` file. - * @return string[] Array of file header values keyed by header name. + * @return array Array of file header values keyed by header name. * * @phpstan-return array{ * 'POT-Creation-Date': string, From 0f1c6b8918bb2dcdb7e6fdd4082f4622de0a99e7 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 09:26:53 -0700 Subject: [PATCH 6/8] Include WP_Translations in get_translations_for_domain() return type Since the performant translations library was introduced in WordPress 6.5, `load_textdomain()` stores a `WP_Translations` instance in the `$l10n` global, and `get_translations_for_domain()` returns it as is. `WP_Translations` does not extend `Translations`, so the documented `Translations|NOOP_Translations` return type has been inaccurate since then. Add `WP_Translations` to the union. `Translations` remains for `MO` instances that plugins may still place in `$l10n` directly. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/l10n.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index ca3e04b64c389..f98cb2c89a5aa 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -1586,7 +1586,7 @@ function _load_textdomain_just_in_time( $domain ) { * @global MO[] $l10n An array of all currently loaded text domains. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. - * @return Translations|NOOP_Translations A Translations instance. + * @return WP_Translations|Translations|NOOP_Translations A Translations instance. */ function get_translations_for_domain( $domain ) { global $l10n; From 85022668c3b5ba2581a68d95ae89e462086a8690 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 09:27:07 -0700 Subject: [PATCH 7/8] Correct the types of the $l10n and $l10n_unloaded globals `$l10n_unloaded` was documented as `MO[]`, but it only ever maps text domains to `true`. Document it as `array`. `$l10n` was documented as `MO[]` in some places and as `array` in others. Since WordPress 6.5, core stores `WP_Translations` instances from `load_textdomain()` and the shared `NOOP_Translations` fallback from `get_translations_for_domain()`, while plugins may still assign `MO` instances directly, which `load_textdomain()` accounts for with its `instanceof MO` check. Document it consistently as `array` in both the `@global` tags and the inline `@var` tags. The `@global` tags are significant for static analysis here, since the PHPStan `GlobalDocBlockVisitor` applies them to the `global` statements. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/l10n.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index f98cb2c89a5aa..8ffe4fc40479a 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -822,9 +822,9 @@ function translate_nooped_plural( $nooped_plural, $count, $domain = 'default' ) * @since 1.5.0 * @since 6.1.0 Added the `$locale` parameter. * - * @global MO[] $l10n An array of all currently loaded text domains. - * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. - * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + * @global array $l10n An array of all currently loaded text domains. + * @global array $l10n_unloaded An array of all text domains that have been unloaded again. + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $mofile Path to the .mo file. @@ -977,8 +977,8 @@ function load_textdomain( $domain, $mofile, $locale = null ) { * @since 3.0.0 * @since 6.1.0 Added the `$reloadable` parameter. * - * @global MO[] $l10n An array of all currently loaded text domains. - * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. + * @global array $l10n An array of all currently loaded text domains. + * @global array $l10n_unloaded An array of all text domains that have been unloaded again. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param bool $reloadable Whether the text domain can be loaded just-in-time again. @@ -1094,8 +1094,8 @@ function load_default_textdomain( $locale = null ) { * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * @since 6.7.0 Translations are no longer immediately loaded, but handed off to the just-in-time loading mechanism. * - * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. - * @global array $l10n An array of all currently loaded text domains. + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + * @global array $l10n An array of all currently loaded text domains. * * @param string $domain Unique identifier for retrieving translated strings * @param string|false $deprecated Optional. Deprecated. Use the $plugin_rel_path parameter instead. @@ -1108,7 +1108,7 @@ function load_default_textdomain( $locale = null ) { */ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) { /** @var WP_Textdomain_Registry $wp_textdomain_registry */ - /** @var array $l10n */ + /** @var array $l10n */ global $wp_textdomain_registry, $l10n; if ( ! is_string( $domain ) ) { @@ -1141,8 +1141,8 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * @since 6.7.0 Translations are no longer immediately loaded, but handed off to the just-in-time loading mechanism. * - * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. - * @global array $l10n An array of all currently loaded text domains. + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + * @global array $l10n An array of all currently loaded text domains. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $mu_plugin_rel_path Optional. Relative to `WPMU_PLUGIN_DIR` directory in which the .mo @@ -1151,7 +1151,7 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path */ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { /** @var WP_Textdomain_Registry $wp_textdomain_registry */ - /** @var array $l10n */ + /** @var array $l10n */ global $wp_textdomain_registry, $l10n; if ( ! is_string( $domain ) ) { @@ -1182,8 +1182,8 @@ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * @since 6.7.0 Translations are no longer immediately loaded, but handed off to the just-in-time loading mechanism. * - * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. - * @global array $l10n An array of all currently loaded text domains. + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + * @global array $l10n An array of all currently loaded text domains. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string|false $path Optional. Path to the directory containing the .mo file. @@ -1192,7 +1192,7 @@ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { */ function load_theme_textdomain( $domain, $path = false ) { /** @var WP_Textdomain_Registry $wp_textdomain_registry */ - /** @var array $l10n */ + /** @var array $l10n */ global $wp_textdomain_registry, $l10n; if ( ! is_string( $domain ) ) { @@ -1524,7 +1524,7 @@ function load_script_translations( $file, $handle, $domain ) { * @since 4.6.0 * @access private * - * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. + * @global array $l10n_unloaded An array of all text domains that have been unloaded again. * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. @@ -1583,7 +1583,7 @@ function _load_textdomain_just_in_time( $domain ) { * * @since 2.8.0 * - * @global MO[] $l10n An array of all currently loaded text domains. + * @global array $l10n An array of all currently loaded text domains. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return WP_Translations|Translations|NOOP_Translations A Translations instance. @@ -1609,7 +1609,7 @@ function get_translations_for_domain( $domain ) { * * @since 3.0.0 * - * @global MO[] $l10n An array of all currently loaded text domains. + * @global array $l10n An array of all currently loaded text domains. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return bool Whether there are translations. From 5577b20baa4155623444a5d10e52ea89c2007f8e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 09:28:11 -0700 Subject: [PATCH 8/8] Remove inline @var tags made redundant by @global tags Each `global` statement in `l10n.php` that carried an inline `@var` tag is in a function whose docblock already documents the same global with an `@global` tag of the same type. The PHPStan `GlobalDocBlockVisitor` applies those `@global` tags to the `global` statements, so the inline tags are redundant. PHPStan results are unchanged. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/l10n.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 8ffe4fc40479a..e90f97fd2025a 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -832,7 +832,6 @@ function translate_nooped_plural( $nooped_plural, $count, $domain = 'default' ) * @return bool True on success, false on failure. */ function load_textdomain( $domain, $mofile, $locale = null ) { - /** @var WP_Textdomain_Registry $wp_textdomain_registry */ global $l10n, $l10n_unloaded, $wp_textdomain_registry; $l10n_unloaded = (array) $l10n_unloaded; @@ -1107,8 +1106,6 @@ function load_default_textdomain( $locale = null ) { * @phpstan-param false $deprecated */ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) { - /** @var WP_Textdomain_Registry $wp_textdomain_registry */ - /** @var array $l10n */ global $wp_textdomain_registry, $l10n; if ( ! is_string( $domain ) ) { @@ -1150,8 +1147,6 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path * @return bool True when textdomain is successfully loaded, false otherwise. */ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { - /** @var WP_Textdomain_Registry $wp_textdomain_registry */ - /** @var array $l10n */ global $wp_textdomain_registry, $l10n; if ( ! is_string( $domain ) ) { @@ -1191,8 +1186,6 @@ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { * @return bool True when textdomain is successfully loaded, false otherwise. */ function load_theme_textdomain( $domain, $path = false ) { - /** @var WP_Textdomain_Registry $wp_textdomain_registry */ - /** @var array $l10n */ global $wp_textdomain_registry, $l10n; if ( ! is_string( $domain ) ) { @@ -1316,7 +1309,6 @@ function load_script_module_textdomain( string $id, string $domain = 'default', * @return string|false The JSON-encoded translated strings on success, false otherwise. */ function _load_script_textdomain_from_src( string $handle, string $src, string $domain, string $path, bool $is_module ) { - /** @var WP_Textdomain_Registry $wp_textdomain_registry */ global $wp_textdomain_registry; $locale = determine_locale(); @@ -1531,7 +1523,6 @@ function load_script_translations( $file, $handle, $domain ) { * @return bool True when the textdomain is successfully loaded, false otherwise. */ function _load_textdomain_just_in_time( $domain ) { - /** @var WP_Textdomain_Registry $wp_textdomain_registry */ global $l10n_unloaded, $wp_textdomain_registry; $l10n_unloaded = (array) $l10n_unloaded;