From ee803c6f15ce58f3596cf4080828ad7d43ee1fdf Mon Sep 17 00:00:00 2001 From: David Stone Date: Sat, 9 May 2026 10:59:45 -0600 Subject: [PATCH] fix(i18n): defer translations until init to silence WP 6.7 textdomain notice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WordPress 6.7 added a _doing_it_wrong notice when a plugin triggers the just-in-time textdomain loader before the 'init' action fires. Two paths in Ultimate Multisite were calling __() too early: 1. WPEngine_Integration::__construct() built the description with __() inline and called set_description(). Integration_Registry instantiates every provider on plugins_loaded priority 9, before init. Fix: follow the lazy pattern already used by the other 16 integration providers — override get_description() so the translation runs only when the wizard or admin UI requests it (always after init). 2. The WP_CLI trait's enable_wp_cli() registered commands synchronously from each Manager's init() (called during plugin bootstrap, also before the init action). Each registration passed translated shortdescs through __() and sprintf(__(...)). Fix: enable_wp_cli() now schedules a single add_action('init', ...) at priority 0; the heavy registration moves into a new register_wp_cli_commands() method that runs on the init hook. WP-CLI still sees the commands because its own command dispatch happens after WordPress finishes bootstrapping. Verified by instrumenting WP's gettext filter to log any translation attempted before did_action('init'): 6+ events on origin/main, 0 events after this change. wp wu sub-commands still register and run normally. --- inc/apis/trait-wp-cli.php | 30 +++++++++++++++++-- .../wpengine/class-wpengine-integration.php | 25 ++++++++++++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/inc/apis/trait-wp-cli.php b/inc/apis/trait-wp-cli.php index 408e3f2d1..d7673008c 100644 --- a/inc/apis/trait-wp-cli.php +++ b/inc/apis/trait-wp-cli.php @@ -56,8 +56,16 @@ protected function get_wp_cli_label(): string { } /** - * Registers the routes. Should be called by the entity - * to actually enable the REST API. + * Schedules WP-CLI command registration. + * + * Called synchronously from each manager's `init()` method. The actual + * command registration — which calls `__()` for translatable shortdescs — + * is deferred to the `init` action so WordPress 6.7+ does not emit a + * `_load_textdomain_just_in_time` `_doing_it_wrong` notice. WP-CLI + * registers commands during its own bootstrap phase before `init` fires; + * hooking the registration to `init` priority 0 keeps the commands + * available for every WP-CLI invocation while satisfying WP 6.7's + * "translate after init" rule. * * @since 2.0.0 */ @@ -67,6 +75,24 @@ public function enable_wp_cli(): void { return; } + add_action('init', [$this, 'register_wp_cli_commands'], 0); + } + + /** + * Registers the WP-CLI commands for this entity. + * + * Hooked to `init` priority 0 by `enable_wp_cli()` so translations are + * loaded before any `__()` call runs (see `enable_wp_cli()` docblock). + * + * @since 2.10.2 + * @return void + */ + public function register_wp_cli_commands(): void { + + if ( ! defined('WP_CLI')) { + return; + } + $wp_cli_root = 'wu'; static $root_registered = false; diff --git a/inc/integrations/providers/wpengine/class-wpengine-integration.php b/inc/integrations/providers/wpengine/class-wpengine-integration.php index 62ac7c262..b74f6cf23 100644 --- a/inc/integrations/providers/wpengine/class-wpengine-integration.php +++ b/inc/integrations/providers/wpengine/class-wpengine-integration.php @@ -26,23 +26,38 @@ class WPEngine_Integration extends Integration { /** * Constructor. * + * Note: Translation calls (`__()`, `_e()`, etc.) are intentionally absent + * from this constructor because integration providers are instantiated on + * `plugins_loaded` priority 9 by the Integration_Registry — before the + * WordPress `init` action fires. WordPress 6.7+ emits a `_doing_it_wrong` + * notice for any translation triggered before `init`. The translatable + * description is returned lazily by `get_description()` so it only runs + * when the wizard or admin UI requests it (always after `init`). + * * @since 2.5.0 */ public function __construct() { parent::__construct('wpengine', 'WP Engine'); - $description = __('WP Engine drives your business forward faster with the first and only WordPress Digital Experience Platform. We offer the best WordPress hosting and developer experience on a proven, reliable architecture that delivers unparalleled speed, scalability, and security for your sites.', 'ultimate-multisite'); - - $description .= '

' . __('We recommend to enter in contact with WP Engine support to ask for a Wildcard domain if you are using a subdomain install.', 'ultimate-multisite') . ''; - - $this->set_description($description); $this->set_logo(function_exists('wu_get_asset') ? wu_get_asset('wpengine.svg', 'img/hosts') : ''); $this->set_tutorial_link('https://ultimatemultisite.com/docs/user-guide/host-integrations/wp-engine'); $this->set_constants([['WPE_API', 'WPE_APIKEY']]); $this->set_supports(['no-instructions', 'no-config']); } + /** + * {@inheritdoc} + */ + public function get_description(): string { + + $description = __('WP Engine drives your business forward faster with the first and only WordPress Digital Experience Platform. We offer the best WordPress hosting and developer experience on a proven, reliable architecture that delivers unparalleled speed, scalability, and security for your sites.', 'ultimate-multisite'); + + $description .= '

' . __('We recommend to enter in contact with WP Engine support to ask for a Wildcard domain if you are using a subdomain install.', 'ultimate-multisite') . ''; + + return $description; + } + /** * {@inheritdoc} */