From b605d0c29942e16fa7509e077ebcbfb325f27526 Mon Sep 17 00:00:00 2001 From: magdev Date: Sat, 7 Feb 2026 11:39:25 +0100 Subject: [PATCH] fix: Defer textdomain loading to init action for WordPress 6.7+ compatibility (v0.4.8) Fixes _load_textdomain_just_in_time notice and headers already sent warnings on admin pages by deferring load_plugin_textdomain() and Settings tab label initialization to the init action instead of plugins_loaded. Co-Authored-By: Claude --- CHANGELOG.md | 8 ++++++++ CLAUDE.md | 20 +++++++++++++++++++- src/Admin/Settings.php | 38 ++++++++++++++++++++++++++------------ src/Plugin.php | 8 ++++++-- wp-prometheus.php | 4 ++-- 5 files changed, 61 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd50902..3f7eaa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.8] - 2026-02-07 + +### Fixed + +- Fixed `_load_textdomain_just_in_time` notice on admin pages (WordPress 6.7+ compatibility) +- Deferred `load_plugin_textdomain()` to `init` action instead of `plugins_loaded` +- Deferred Settings tab label initialization to avoid early translation loading + ## [0.4.7] - 2026-02-03 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index bcb0784..ad05f44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,9 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w **Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file. -*No pending roadmap items.* +### Known Bugs + +*No known bugs at this time.* ## Technical Stack @@ -288,6 +290,22 @@ add_action( 'wp_prometheus_collect_metrics', function( $collector ) { ## Session History +### 2026-02-07 - Fix Early Textdomain Loading (v0.4.8) + +- Fixed `_load_textdomain_just_in_time` warning on admin pages (WordPress 6.7+ compatibility) +- Root cause: `load_plugin_textdomain()` was called during `plugins_loaded` in `Plugin::__construct()` +- WordPress 6.7+ requires textdomain loading at the `init` action or later +- Two changes made: + - `Plugin.php`: Deferred `load_textdomain()` to `init` action hook, changed method visibility to public + - `Settings.php`: Deferred tab label initialization (which uses `__()`) to a lazy `get_tabs()` method +- Cleared Known Bugs section — no remaining known issues +- **Key Learning**: WordPress 6.7 textdomain loading requirements + - `load_plugin_textdomain()` must be called at `init` or later + - WordPress's JIT textdomain loader (`_load_textdomain_just_in_time`) also triggers too-early warnings + - Any `__()` / `_e()` calls before `init` for a plugin textdomain will trigger the notice + - The warning causes "headers already sent" errors because the notice output breaks header modifications + - Solution: Defer both explicit `load_plugin_textdomain()` and any `__()` calls to `init` or later hooks + ### 2026-02-03 - Database Query Timing Documentation (v0.4.7) - Added database query duration distribution panel to Grafana Runtime dashboard diff --git a/src/Admin/Settings.php b/src/Admin/Settings.php index adf38de..5bf045c 100644 --- a/src/Admin/Settings.php +++ b/src/Admin/Settings.php @@ -49,15 +49,6 @@ class Settings { * Constructor. */ public function __construct() { - $this->tabs = array( - 'license' => __( 'License', 'wp-prometheus' ), - 'metrics' => __( 'Metrics', 'wp-prometheus' ), - 'storage' => __( 'Storage', 'wp-prometheus' ), - 'custom' => __( 'Custom Metrics', 'wp-prometheus' ), - 'dashboards' => __( 'Dashboards', 'wp-prometheus' ), - 'help' => __( 'Help', 'wp-prometheus' ), - ); - $this->metric_builder = new CustomMetricBuilder(); $this->dashboard_provider = new DashboardProvider(); @@ -76,14 +67,37 @@ class Settings { add_action( 'wp_ajax_wp_prometheus_test_storage', array( $this, 'ajax_test_storage' ) ); } + /** + * Get available tabs. + * + * Lazily initializes tab labels to avoid triggering textdomain loading + * before the 'init' action (required since WordPress 6.7). + * + * @return array + */ + private function get_tabs(): array { + if ( empty( $this->tabs ) ) { + $this->tabs = array( + 'license' => __( 'License', 'wp-prometheus' ), + 'metrics' => __( 'Metrics', 'wp-prometheus' ), + 'storage' => __( 'Storage', 'wp-prometheus' ), + 'custom' => __( 'Custom Metrics', 'wp-prometheus' ), + 'dashboards' => __( 'Dashboards', 'wp-prometheus' ), + 'help' => __( 'Help', 'wp-prometheus' ), + ); + } + return $this->tabs; + } + /** * Get current tab. * * @return string */ private function get_current_tab(): string { - $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'license'; - return array_key_exists( $tab, $this->tabs ) ? $tab : 'license'; + $tabs = $this->get_tabs(); + $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'license'; + return array_key_exists( $tab, $tabs ) ? $tab : 'license'; } /** @@ -268,7 +282,7 @@ class Settings { ?>