Merge branch 'code/MajorAir/wp-textdomain-wiki'
This commit is contained in:
commit
a45ef55862
2 changed files with 195 additions and 1 deletions
|
|
@ -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> 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 '<domain>' "$WPROOT/wp-content/themes" "$WPROOT/wp-content/plugins" 2>/dev/null
|
||||||
|
|
||||||
|
# Which extension has the most references (i.e. owns the domain)?
|
||||||
|
grep -rl '<domain>' "$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*['\"]<domain>['\"]" \
|
||||||
|
"$WPROOT/wp-content/themes/<theme>" | 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
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Suppress the WP 6.7 "_load_textdomain_just_in_time was called incorrectly"
|
||||||
|
* notice for a theme/plugin that translates before init.
|
||||||
|
*
|
||||||
|
* Scope is intentionally narrow: only this one function is silenced, so other
|
||||||
|
* doing_it_wrong notices still surface. Translations still load via the JIT
|
||||||
|
* fallback, so nothing visible changes for visitors.
|
||||||
|
*/
|
||||||
|
add_filter( 'doing_it_wrong_trigger_error', function ( $trigger, $function_name ) {
|
||||||
|
return '_load_textdomain_just_in_time' === $function_name ? false : $trigger;
|
||||||
|
}, 10, 2 );
|
||||||
|
```
|
||||||
|
|
||||||
|
`mu-plugins/` loads automatically (no activation, can't be deactivated from the admin), and runs early enough to register the filter before the notice fires.
|
||||||
|
|
||||||
|
#### Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WPROOT=/var/www/html
|
||||||
|
|
||||||
|
# 1. Syntax-check the mu-plugin
|
||||||
|
php -l "$WPROOT/wp-content/mu-plugins/fix-textdomain.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","<domain>");' >/dev/null 2>&1
|
||||||
|
grep -c "<domain>" "$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
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
created: 2026-04-02T16:03
|
created: 2026-04-02T16:03
|
||||||
updated: 2026-06-19T10:05
|
updated: 2026-06-21T11:46
|
||||||
---
|
---
|
||||||
* [Home](index.md)
|
* [Home](index.md)
|
||||||
* [Linux & Sysadmin](01-linux/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 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)
|
* [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)
|
* [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 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)
|
* [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)
|
* [Claude Code Won't Log In (Warp & iTerm2) — Corrupt Keychain Credential](05-troubleshooting/claude-code-warp-login-corrupt-keychain-credential.md)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue