From e767ebffcbb2ada415702a6e2f387a641dd57254 Mon Sep 17 00:00:00 2001 From: majorlinux Date: Sun, 21 Jun 2026 11:44:48 -0400 Subject: [PATCH] Add runbook: WordPress 6.7 _load_textdomain_just_in_time notice Covers the WP 6.7 doing_it_wrong notice fired when a theme/plugin translates before init (e.g. nav-menu labels on after_setup_theme). Documents source fix (defer to init) and the update-safe mu-plugin suppression via doing_it_wrong_trigger_error, plus the renamed-theme domain gotcha. Derived from the majorlinux.com kappa/marstheme triage. --- ...press-67-textdomain-just-in-time-notice.md | 193 ++++++++++++++++++ SUMMARY.md | 3 +- 2 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 05-troubleshooting/wordpress-67-textdomain-just-in-time-notice.md diff --git a/05-troubleshooting/wordpress-67-textdomain-just-in-time-notice.md b/05-troubleshooting/wordpress-67-textdomain-just-in-time-notice.md new file mode 100644 index 0000000..6e84f09 --- /dev/null +++ b/05-troubleshooting/wordpress-67-textdomain-just-in-time-notice.md @@ -0,0 +1,193 @@ +--- +title: "WordPress 6.7 _load_textdomain_just_in_time Notice (Theme/Plugin Loads Translations Too Early)" +domain: troubleshooting +category: troubleshooting +tags: + - wordpress + - wordpress-6.7 + - php + - i18n + - textdomain + - theme + - mu-plugin + - deprecation + - troubleshooting +status: published +created: 2026-06-21 +updated: 2026-06-21 +--- + +# WordPress 6.7 `_load_textdomain_just_in_time` Notice + +> **TL;DR** — WordPress 6.7 added a `doing_it_wrong` notice that fires when a translation function (`__()`, `_e()`, `esc_html__()`, …) is called for a text domain **before the `init` action**. It's almost always a theme or plugin registering nav menus / sidebars / labels on `after_setup_theme` (which runs before `init`). The notice is **debug-only and harmless** — translations still load via the just-in-time fallback. If the offending code is in your own (or an updatable) theme/plugin, fix it at the source by deferring to `init`. If it's a **non-updating or third-party** theme you don't want to hand-edit, suppress *only this one notice* with a `doing_it_wrong_trigger_error` filter in a tiny mu-plugin. + +--- + +## Symptom + +With `WP_DEBUG` on (or in Query Monitor's PHP panel), you see: + +``` +Function _load_textdomain_just_in_time was called incorrectly. +Translation loading for the domain was triggered too early. +This is usually an indicator for some code in the plugin or theme running too early. +Translations should be loaded at the init action or later. +(This message was added in version 6.7.0.) + +_load_textdomain_just_in_time() wp-includes/l10n.php +get_translations_for_domain() wp-includes/l10n.php +translate() wp-includes/l10n.php +__() wp-includes/l10n.php +WordPress Core +``` + +The key fields are **the domain name** (e.g. `marstheme`, `woocommerce`, `astra`) and the fact that the stack bottoms out in **WordPress Core** via `__()` — that tells you *some* extension called a translation function, not that core is broken. + +## Why it happens (the WP 6.7 change) + +Before 6.7, WordPress silently "just-in-time" loaded a text domain the first time you translated a string in it. 6.7 kept the JIT loading but started **warning** when it's triggered before `init`, because: + +- Translations loaded before `init` can't be filtered/overridden by other plugins that hook `init`. +- It signals the extension is doing setup work earlier than the WordPress lifecycle intends. + +The usual culprit is code on **`after_setup_theme`** (which fires *before* `init`) that translates a label inline, e.g.: + +```php +function mytheme_setup() { + register_nav_menus( array( + 'primary' => __( 'Primary Menu', 'mytheme' ), // <-- translate call before init + ) ); +} +add_action( 'after_setup_theme', 'mytheme_setup' ); +``` + +> **Important:** explicitly calling `load_theme_textdomain()` / `load_plugin_textdomain()` early does **not** fix the notice, and as of WP 4.6+ themes on wordpress.org don't even need to call it. The notice is about the *translate call*, not about whether the domain was loaded. Moving only the `load_*_textdomain()` call around is a common dead-end (see the gotcha below). + +## Diagnostic chain + +### 1. Identify the domain and what owns it + +The notice names the domain. Find which theme/plugin uses it: + +```bash +WPROOT=/var/www/html +grep -rlw '' "$WPROOT/wp-content/themes" "$WPROOT/wp-content/plugins" 2>/dev/null + +# Which extension has the most references (i.e. owns the domain)? +grep -rl '' "$WPROOT/wp-content/" 2>/dev/null \ + | sed -E "s#$WPROOT/wp-content/(themes|plugins|mu-plugins)/([^/]+)/.*#\1/\2#" \ + | sort | uniq -c | sort -rn | head +``` + +> **Watch for renamed/forked themes.** The domain often does **not** match the theme's folder name. A theme bought as "Mars" and re-slugged to `kappa` keeps `marstheme` as its text domain in all 40+ template files. So `wp theme list` shows `kappa` active while the notice says `marstheme` — they're the same thing. + +### 2. Confirm it's active and whether it can be updated + +```bash +sudo -u www-data wp --path=$WPROOT theme list --fields=name,status,version,update +sudo -u www-data wp --path=$WPROOT plugin list --fields=name,status,version,update +``` + +- `update available` → **update it first** (newest releases of most themes/plugins fixed this in late 2024/2025). That's the proper fix; the rest of this article is for when you can't. +- `update none` on a **renamed/custom fork** → no upstream exists, so updating is impossible. Go to the suppression fix. + +### 3. Pin down the early call (optional) + +```bash +grep -rn "__(\s*['\"].*['\"]\s*,\s*['\"]['\"]" \ + "$WPROOT/wp-content/themes/" | head +``` + +Look for translate calls inside functions hooked to `after_setup_theme`, `setup_theme`, `plugins_loaded`, or run at file scope in `functions.php`. + +## The fix + +### Option A — fix it at the source (own / updatable code) + +Defer the translation. Either register the raw string and translate at render time, or move the registration to `init`: + +```php +// Before: translated on after_setup_theme (too early) +add_action( 'after_setup_theme', function () { + register_nav_menus( array( 'primary' => __( 'Primary Menu', 'mytheme' ) ) ); +} ); + +// After: register the menu location on init, where translation is allowed +add_action( 'init', function () { + register_nav_menus( array( 'primary' => __( 'Primary Menu', 'mytheme' ) ) ); +} ); +``` + +Don't do this by editing a theme/plugin that receives updates — your change is wiped on the next update. Use Option B for those. + +### Option B — suppress just this notice (third-party / non-updating code) + +When the early call lives in a theme you don't control and can't update (a renamed commercial fork, an abandoned plugin), the clean, update-safe move is to silence **only** the `_load_textdomain_just_in_time` notice — not all `doing_it_wrong` output — via a must-use plugin. + +Create `wp-content/mu-plugins/fix-textdomain.php`: + +```php + No syntax errors detected + +# 2. Confirm WP still boots and the filter is registered +sudo -u www-data wp --path=$WPROOT eval \ + 'echo has_filter("doing_it_wrong_trigger_error") ? "filter set\n" : "MISSING\n";' + +# 3. Clear the debug log, trigger an early translate, confirm 0 new notices +DBG="$WPROOT/wp-content/debug.log" +[ -f "$DBG" ] && : > "$DBG" +sudo -u www-data wp --path=$WPROOT eval '__("Primary Menu","");' >/dev/null 2>&1 +grep -c "" "$DBG" 2>/dev/null || echo 0 +# -> 0 +``` + +## Gotchas + +### The "load the textdomain earlier/later" dead-end + +A very common (wrong) first attempt is an mu-plugin that just calls `load_theme_textdomain()` on `plugins_loaded` or `after_setup_theme`: + +```php +// DOES NOT FIX THE NOTICE +add_action( 'plugins_loaded', function () { + load_theme_textdomain( 'mytheme', get_template_directory() . '/languages' ); +}, 0 ); +``` + +`plugins_loaded` still runs **before `init`**, and — more importantly — the notice is triggered by the theme's own early `__()` call, not by whether you've loaded the domain. This code is dead weight. If you find one in place, replace it with the Option B filter rather than tweaking its hook/priority. + +### Don't blanket-suppress all deprecations + +Resist `error_reporting(E_ALL & ~E_DEPRECATED)` or returning `false` from `doing_it_wrong_trigger_error` unconditionally — that also hides genuinely useful warnings (a plugin breaking on a future PHP/WP bump). Scope the filter to the one `function_name`. + +### Renamed theme ⇒ domain ≠ folder + +Re-stating because it costs the most time: the domain in the notice can be the theme's *original* slug, not its current folder. Always `grep` for the domain to find the real owner before concluding "I don't even have that theme installed." + +## See also + +- [Patching PHP 8.4 Implicit-Nullable Deprecations in Vendor Packages](php-84-vendor-implicit-nullable-patch.md) — the other "harmless deprecation that floods logs" pattern on the WordPress fleet +- [WordPress developer note: i18n improvements in 6.7](https://make.wordpress.org/core/2024/10/21/i18n-improvements-in-6-7/) — the canonical reference for this change diff --git a/SUMMARY.md b/SUMMARY.md index 3178901..689c50a 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,6 +1,6 @@ --- created: 2026-04-02T16:03 -updated: 2026-06-19T10:05 +updated: 2026-06-21T11:46 --- * [Home](index.md) * [Linux & Sysadmin](01-linux/index.md) @@ -118,6 +118,7 @@ updated: 2026-06-19T10:05 * [Claude Desktop MCP Server Started via wsl.exe Sees Empty Environment (WSLENV)](05-troubleshooting/wsl-env-claude-desktop-mcp.md) * [Claude Desktop MCP Mass-Disconnect After Blocking SSH Reboot](05-troubleshooting/claude-desktop-mcp-mass-disconnect-blocking-reboot.md) * [Patching PHP 8.4 Implicit-Nullable Deprecations in Vendor Packages](05-troubleshooting/php-84-vendor-implicit-nullable-patch.md) + * [WordPress 6.7 `_load_textdomain_just_in_time` Notice (Translations Loaded Too Early)](05-troubleshooting/wordpress-67-textdomain-just-in-time-notice.md) * [Ollama Drops Off Tailscale When Mac Sleeps](05-troubleshooting/ollama-macos-sleep-tailscale-disconnect.md) * [Ollama: `ollama run` with Piped Stdin Bypasses Chat Template + SYSTEM Prompt](05-troubleshooting/ollama-chat-template-pipe-stdin-bypass.md) * [Claude Code Won't Log In (Warp & iTerm2) — Corrupt Keychain Credential](05-troubleshooting/claude-code-warp-login-corrupt-keychain-credential.md)