#44937 closed enhancement (fixed)
Add _doing_it_wrong to load_plugin_textdomain
Reported by: |
|
Owned by: |
|
---|---|---|---|
Milestone: | 6.7 | Priority: | normal |
Severity: | normal | Version: | 4.7 |
Component: | I18N | Keywords: | has-patch has-dev-note |
Focuses: | Cc: |
Description
Hello,
Came across a race condition when the load_plugin_textdomain is called (for lack of a better term) 'naked' in plugins, most recently Github Updater.
This only affects WP 4.7+ as it relates to the User Locale vs. the Site Locale within the admin area.
If you call load_plugin_textdomain in your plugin outside of a init or plugins_loaded action then your plugin will load it's strings based on the site locale rather than the admin's locale.
The Codex - https://codex.wordpress.org/Function_Reference/load_plugin_textdomain
It provides an example using plugins_loaded but doesn't indicate any issue.
I'm unsure the root of the issue but I assume the plugin loads prior to the user so their locale isn't available when the MO object is loaded into the $l10n global.
I don't know if it's possible to make the user locale available to plugins running load_plugin_textdomain prior to init and since the issue isn't present prior to 4.7 feel a _doing_it_wrong prompt will help plugin developers avoid the pitfall.
I haven't tried with other plugins or examples but to reproduce;
- Install clean WP
- Install GitHub Updater <8.3 - (8.2.1 - https://github.com/afragen/github-updater/tree/8.2.1)
- Set Site Locale to de_DE or fr_FR or ....
- Set User Locale to en_US
- Install all translations
- Visit GitHub Updater dashboard
And you'll find it's not in en_US it's in the site locale.
*If it's half en and half your site locale those are from missing strings on the site locale.
Then if you replace with 8.3+ (https://github.com/afragen/github-updater/tree/develop) you'll find the panel in user locale as 8.3 fixed this issue by wrapping load_plugin_textdomain in a plugins_loaded action function.
And I'll wrap up with a concept snippet for the load_plugin_textdomain function;
<?php if ( ! did_action( 'init' ) && ! did_action( 'plugins_loaded' ) ) { _doing_it_wrong( 'load_plugin_textdomain', sprintf( /* translators: 1: function 2: init, 3: plugins_loaded */ __( 'The %1$s should be called from the %2$s or %3$ hooks.' ), '<code>load_plugin_textdomain</code>', '<code>init</code>', '<code>plugins_loaded</code>' ), '4.7.0' ); }
And it seems I'm not alone;
https://status301.net/why-load_plugin_textdomain-would-not-work/
Cheers
Change History (65)
#2
@
7 years ago
I think this warning is a great idea. I wrote a plugin to overwrite some strings and it always fails, when the plugin just calls the load_plugin_textdomain
function outside of an action callback.
#3
in reply to:
↑ 1
@
7 years ago
Thanks @swissspidy I appreciate the 2nd-opinion
Replying to swissspidy:
This happens after
plugins_loaded
, so the first time you can be sure that everything is set up is in theinit
hook.
Just to confirm this specific case the load_*_textdomain should be called in the init
hook.
So the Codex example should update from plugins_loaded
to init
- https://codex.wordpress.org/Function_Reference/load_plugin_textdomain#Examples
And would the recommended hooks for themes be valid? - https://codex.wordpress.org/Function_Reference/load_theme_textdomain
Replying to swissspidy:
IMO it's a rule of thumb that you shouldn't run any code directly without hooking into some action. This scenario with
load_*_textdomain()
(which I know people run into from time to time) is just one example. And it's a known one. After all the blog post you linked to is over 8 years old :-)
Exposing this would be very helpful to theme/plugin developers where core functions are reliant on other actions.
Aside from localization anomalies, I'm sure others can add to the list of calling core functions too early in their plugin, theme, code.
I do see a use of the _doing_it_wrong function throughout core but wonder if it needs to become more of a convention.
Maybe an addition to the handbook on coding core functions to take into account reliance on actions.
https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/
These warnings are helpful to developers especially when adopting new (maybe just to them) functions that are reliable on action hooks. It flags edge case issues such as i18n that developers may not catch and if action reliance ever changes due to new features such as user locales the warning can be updated which devs can catch in beta/RC.
A good example in core of _doing_it_wrong in context of actions hooks can be found in _wp_scripts_maybe_doing_it_wrong
;
https://github.com/WordPress/WordPress/blob/56c162fbc9867f923862f64f1b4570d885f1ff03/wp-includes/functions.wp-scripts.php#L36-L52
#4
@
7 years ago
And a last thought;
Would it be useful to have a _doing_actions_wrong
function to streamline things and make it elegant because Code is Poetry
.
It could possibly take three parameters;
string|array - The action(s) required by this function.
semvar - The version this requirement was introduced/updated
conditional - AND/OR - control if actions are dependent
This would allow the translation to be consistent, adding a single string for i18n similar to;
https://github.com/WordPress/WordPress/blob/56c162fbc9867f923862f64f1b4570d885f1ff03/wp-includes/functions.wp-scripts.php#L36-L52
This would simply call the _doing_it_wrong
function with standardized strings and the current FUNCTION.
#5
@
3 years ago
I think this would be a good idea - to add a doing it wrong warning
Having noticed lots of plugins suffering this and spending several hours tracking through to find the issue.
#7
@
13 months ago
- Milestone changed from Awaiting Review to Future Release
The warning would also need to apply to _load_textdomain_just_in_time
, see #58546
This ticket was mentioned in PR #6462 on WordPress/wordpress-develop by @swissspidy.
11 months ago
#9
- Keywords has-patch added
In my quick local testing I already identified two plugins doing it wrong:
- RTL Tester
- Two Factor
- Already submitted a bug fix
- https://github.com/WordPress/two-factor/pull/608
Strangely, when one of these plugins and Query Monitor is active, there is some sort of infinite loop happening and the site crashes with a 500. @johnbillion Any thoughts why QM might run in circles here?
Trac ticket: https://core.trac.wordpress.org/ticket/44937
@johnbillion commented on PR #6462:
11 months ago
#10
I'll take a look at what's happening in QM. I would recommend testing this out with some multilingual plugins if you haven't already. In particular WPML (and add-on plugins for it) has been a bit of a pain in the past with regard to loading translations too early.
@johnbillion commented on PR #6462:
11 months ago
#11
I'm not seeing an infinite loop in my local testing with RTL Tester. I am concerned about calling __()
from within the load textdomain functions though. Do we do that anywhere else?
@swissspidy commented on PR #6462:
11 months ago
#12
OK here's how to reproduce the error. Even if you can't reproduce it so far, it seems logical:
- Set user profile to something else than en_US
- Activate RTL Tester
- Activate Query Monitor
- RTL Tester triggers the
_doing_it_wrong
inload_plugin_textdomain()
- This calls
QM_Collector_Doing_It_Wrong::action_doing_it_wrong_run()
which uses__()
- This in turn triggers the
_doing_it_wrong
in_load_textdomain_just_in_time()
- This calls
QM_Collector_Doing_It_Wrong::action_doing_it_wrong_run()
again...
Adding some caching to only warn about a specific text domain once would solve this. Like a _doing_it_wrong_once
function or so.
I am concerned about calling
__()
from within the load textdomain functions though. Do we do that anywhere else?
I don't think so. In theory it shouldn't cause any issues as the __()
call and the load_*_textdomain()
call use different domains. And the QM issue is only because it taps into the _doing_it_wrong
.
@swissspidy commented on PR #6462:
11 months ago
#13
Actually, it's more like QM would need to somehow unhook QM_Collector_Doing_It_Wrong
in itself because it is triggering _doing_it_wrong
itself.
@johnbillion commented on PR #6462:
11 months ago
#14
Good spot, thanks. Temporarily unhooking itself is probably best otherwise there's no way to know what else might trigger a _doing_it_wrong
call within any of the functions that are called from a doing it wrong handler.
@swissspidy commented on PR #6462:
10 months ago
#15
Looking into warnings emitted by themes, such as twentytwentyone
.
Some observations:
- Themes usually trigger just-in-time loading on
after_setup_theme
- In the admin, the current user is set up right after
setup_theme
because of theload_default_textdomain()
call inwp-settings.php
- Checking for
after_setup_theme
to emit a warning might be a better solution
I am concerned about calling
__()
from within the load textdomain functions though.
At this point determine_locale()
will already have been called, and the current user thus setup. So I don't see any other side effect happening because of this.
#18
in reply to:
↑ 17
@
6 months ago
Replying to swissspidy:
Milestone changed from 6.7 to Future Release
Hi @swissspidy I believe this means that it will no longer be included in WP 6.7? Asking because we've been preparing a test plan for the next WooCommerce version and we'd like to plan around this WP enhancement if it'll be included in 6.7. Thanks!
@swissspidy commented on PR #6462:
6 months ago
#19
@ocean90 @SergeyBiryukov @johnbillion Is this something one of you could provide feedback on? I think it's a nice safeguard that could still land before the beta, but it also might be a bit noisy.
#20
@
6 months ago
Well, it can still be included but I haven't really gotten any feedback on the current PR yet.
@johnbillion commented on PR #6462:
6 months ago
#21
This could be noisy but it might be interesting to see if any feedback about that is raised during beta.
@swissspidy commented on PR #6462:
6 months ago
#24
Committed in https://core.trac.wordpress.org/changeset/59127
#26
@
6 months ago
I have a question regarding this notice: when the function get_plugin_data()
is used in a plugin, it internally calls _get_plugin_data_markup_translate()
, which has two direct load_plugin_textdomain
calls. In this case, the PHP notice gets thrown as well. I wonder what would be the best approach here for this use case? From my research, it seems to be the only occurence in Core where load_plugin_textdomain
is used. Ping @swissspidy
#27
@
6 months ago
If you don't need the plugin data to be translated, pass false
as the third argument to get_plugin_data()
, e.g. get_plugin_data( 'foo/bar.php', true, false )
. Then WordPress won't attempt to load the translations.
First and foremost you should not be calling get_plugin_data()
too early. If you get a PHP notice because of it, it means you are calling it before after_setup_theme
or init
fires. So if you move your call to after init
, these notices should not appear.
Hope that makes sense :-) Please let me know if you encounter any issues while doing so.
There will be a dev-note post about this change as well where I'll try to provide some concrete examples too.
#28
@
6 months ago
@swissspidy [59127] looks good to me, but it seems a little confusing that the action mentioned is after_setup_theme
, while the message refers to setting up the current user. Should we perhaps check for the set_current_user
action instead?
This ticket was mentioned in PR #7480 on WordPress/wordpress-develop by @swissspidy.
6 months ago
#29
(Soft) deprecate load_plugin_textdomain()
and load_theme_textdomain()
and rely solely on the just-in-time loading and WP_Textdomain_Registry
.
This would avoid any _doing_it_wrong
messages for plugins still using load_plugin_textdomain()
, which seems to be the most common case and affects even very big plugins. So it would drastically reduce the noise.
It would still trigger a _doing_it_wrong
message if accidentally causing the just-in-time loading too early.
Trac ticket: https://core.trac.wordpress.org/ticket/44937
#30
follow-up:
↓ 33
@
6 months ago
That's not quite possible. It's a bit complicated.
If a plugin loads translations too early, a code path as follows will be executed:
load_plugin_textdomain
(or _load_textdomain_just_in_time
) -> load_textdomain
-> determine_locale
-> get_user_locale
-> wp_get_current_user
-> wp_set_current_user
That means it causes the current user to be set up too early.
So in most cases the correct user is actually loaded and the locale is known. However, other plugins (which could hook into set_current_user
or change translation paths) might not have been loaded yet.
I see how mentioning after_setup_theme
is confusing to users though. Maybe it should mention init
instead?
I am actually now thinking of a more radical approach:
(Soft) deprecate load_plugin_textdomain()
and load_theme_textdomain()
and rely solely on the just-in-time loading and WP_Textdomain_Registry
.
This would avoid any _doing_it_wrong
messages for plugins still using load_plugin_textdomain()
, which seems to be the most common case and affects even very big plugins. So it would drastically reduce the noise.
It would still trigger a _doing_it_wrong
message if accidentally causing the just-in-time loading too early.
I just opened https://github.com/WordPress/wordpress-develop/pull/7480 to discuss this idea.
@swissspidy commented on PR #7480:
6 months ago
#31
Only 1 failing test, that sounds doable :-)
1) WP_Test_REST_Themes_Controller::test_theme_tags Failed asserting that two arrays are identical. --- Expected +++ Actual @@ @@ Array &0 ( - 0 => 'holiday' + 0 => 'Holiday' 1 => 'custom-menu' ) /var/www/tests/phpunit/tests/rest-api/rest-themes-controller.php:585
@swissspidy commented on PR #7480:
6 months ago
#32
OK when I remove the now unused plugin_locale
and theme_locale
filters a couple of more tests are failing, because they hook into those filters. But nothing is actually broken.
#33
in reply to:
↑ 30
@
6 months ago
Replying to swissspidy:
I see how mentioning
after_setup_theme
is confusing to users though. Maybe it should mentioninit
instead?
Thanks! That's an option too. I would slightly adjust the wording in that case:
Translations should be loaded at the `init` action or later, to ensure that the current user is already set up.
(Soft) deprecate
load_plugin_textdomain()
andload_theme_textdomain()
and rely solely on the just-in-time loading andWP_Textdomain_Registry
.
This would avoid any
_doing_it_wrong
messages for plugins still usingload_plugin_textdomain()
, which seems to be the most common case and affects even very big plugins. So it would drastically reduce the noise.
It would still trigger a
_doing_it_wrong
message if accidentally causing the just-in-time loading too early.
I like that approach :)
#34
@
6 months ago
I just received a message that the WordPress Beta Tester plugin might also be affected:
[02-Oct-2024 13:16:35 UTC] PHP Notice: Function _load_textdomain_just_in_time was called <strong>incorrectly</strong>. Translation loading for the <code>wordpress-beta-tester</code> domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Please see <a href="https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/">Debugging in WordPress</a> for more information. (This message was added in version 6.7.0.) in /var/www/html/wp-includes/functions.php on line 6099
(ping @afragen).
#37
@
6 months ago
I’ve seen a number of these errors in many different plugins. Clearly this is something new in the beta1. I have to look into it more as I’m pretty sure I’m using load_plugin_textdomain()
correctly.
I need to review this ticket. 😉
@swissspidy commented on PR #7480:
6 months ago
#39
Committed in https://core.trac.wordpress.org/changeset/59157
#40
@
6 months ago
What about custom plugins and themes that do not use the language pack feature but rely on local translations? How should these plugins and themes load translations now?
#41
@
6 months ago
Nothing changes for custom plugins. Just make sure to call load_plugin_textdomain()
/load_theme_textdomain()
only on a later hook like init
, not before.
I actually just merged [59157] now for 6.7 Beta 2, so even if you do not change anything in your custom plugin, most likely you will not get a warning anymore because translations will be delayed.
#43
@
6 months ago
Replying to swissspidy:
In [59127],
_doing_it_wrong
warnings were added if plugins or themes load translations too early, either through a manual function call or just-in-time loading.
Because many plugins and themes still manually call
load_plugin_textdomain()
,load_theme_textdomain()
orload_muplugin_textdomain()
, even though they don't have to anymore, that caused a lot of warnings.
Could you please elaborate on the "they don't have to anymore" part? Are the textdomains autoloaded now via the plugin header or something?
#46
follow-up:
↓ 51
@
5 months ago
Looks like this is going to fill up error logs fast as it appears to affect many plugins. We are testing v6.7-beta2 on a very small site and have three plugins showing as doing it wrong ...
WooCommerce
[13-Oct-2024 08:08:08 UTC] E_USER_NOTICE: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the **woocommerce** 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. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /public_html/wp-includes/functions.php on line 6099
Payment Methods by Product & Country for WooCommerce
[13-Oct-2024 08:08:15 UTC] E_USER_NOTICE: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the **hcaptcha-for-forms-and-more** 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. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /public_html/wp-includes/functions.php on line 6099
hCaptcha for WP
[13-Oct-2024 08:08:19 UTC] E_USER_NOTICE: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the **payment-gateways-per-product-categories-for-woocommerce** 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. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /public_html/wp-includes/functions.php on line 6099
Can this be specifically highlighted in the "WordPress 6.7 is imminent!" email that will be going out to plugin developers soon in the "Highlights of the release include:" section so developers know to look out for it in their plugins?
#47
@
5 months ago
As already mentioned above, a dev-note blog post is in the works. Like all dev-notes, it will be mentioned in the field guide, which is referenced in those plugins emails as well.
#48
@
5 months ago
[59157] uncovered an issue in WooCommerce, which caused early translation loading, then called unload_textdomain()
followed by load_plugin_textdomain()
. Because load_plugin_textdomain()
doesn't actually load translations anymore, if you call it after unload_textdomain()
it won't actually do anything. Fixing the early translation loading avoids this, because then there's nothing to unload yet.
I couldn't find other plugins doing this, so probably not worth accounting for that in core, but worth noting here & keeping an eye out for.
#49
@
5 months ago
Glad WooCommerce has been picked up. I've also seen bundled themes producing the notice and couldn't find a ticket regarding it so have raised #62237.
#51
in reply to:
↑ 46
@
5 months ago
Replying to domainsupport:
Looks like this is going to fill up error logs fast as it appears to affect many plugins. We are testing v6.7-beta2 on a very small site and have three plugins showing as doing it wrong ...
WooCommerce
[13-Oct-2024 08:08:08 UTC] E_USER_NOTICE: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the **woocommerce** 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. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /public_html/wp-includes/functions.php on line 6099Payment Methods by Product & Country for WooCommerce
[13-Oct-2024 08:08:15 UTC] E_USER_NOTICE: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the **hcaptcha-for-forms-and-more** 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. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /public_html/wp-includes/functions.php on line 6099hCaptcha for WP
[13-Oct-2024 08:08:19 UTC] E_USER_NOTICE: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the **payment-gateways-per-product-categories-for-woocommerce** 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. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /public_html/wp-includes/functions.php on line 6099Can this be specifically highlighted in the "WordPress 6.7 is imminent!" email that will be going out to plugin developers soon in the "Highlights of the release include:" section so developers know to look out for it in their plugins?
Maybe also worth to mention @swissspidy that also Yoast SEO has the same above issue, even after we stop use load_plugin_textdomain
, just because we use translating functions on plugins_loaded
instead of eg. init
.
Unfortunately, fixing that is a bit more involved than moving them to a later function because they are tied to basic processes that are greatly intertwined with each other.
We're currently working towards a fix, but I figured it's worth to add in the conversation here.
#52
follow-up:
↓ 53
@
5 months ago
@LeonidasMilossis I was a big supporter of this change.
It was also mentioned in a comment 9 years ago, that translation should only be loaded after init
: https://developer.wordpress.org/reference/functions/load_plugin_textdomain/#comment-1568
Still, I also have plugin with that notice now, I help to maintain :(
#53
in reply to:
↑ 52
@
5 months ago
Replying to Kau-Boy:
It was also mentioned in a comment 9 years ago, that translation should only be loaded after
init
:
I'm afraid that our incorrect usage comes from even further back than 9 years. But yeah, we have to decouple from it at some point.
#54
@
5 months ago
- Resolution fixed deleted
- Status changed from closed to reopened
I've just been debugging this problem with my plugin oik-bwtrace.
My code was performing translations for trace records being written for an MU plugin.
I've resolved my particular issue for that plugin but have 3 messages from other plugins and themes to deal with as well - WooCommerce, WordPress SEO and a Genesis child theme.
My notes are documented starting here https://github.com/bobbingwide/bobbingwide/issues/122#issuecomment-2428741144
During the debugging I noticed that the code that issues the message checks for after_setup_theme
but the Notice refers to init
.
This seems to be inconsistent. Which hook should it be?
#55
@
5 months ago
- Keywords has-dev-note added; needs-dev-note removed
Which hook should it be?
Technically both are fine, as after_setup_theme
is mostly used by themes and init
by plugins, but they are fired right after each other. The current user is normally set up in-between.
init
is used in the error message is it's easiest to understand, but if you hook into after_setup_theme
(e.g. in a theme) that's fine too.
Hope that clarifies it :-)
See also the existing discussion about this further above:
#56
@
5 months ago
- Resolution set to fixed
- Status changed from reopened to closed
Going to close this back out because there are currently no further changes required. Discussion can always continue on closed tickets, and this can be reopened if something needs to be changed in the source code.
#57
@
5 months ago
The current user is normally set up in-between.
@swisspidy
It's still not clear to me which translations will be used when the site has one locale defined and the logged in user has an override.
I wrote a tiny plugin to see if I got different results depending upon the hook I was responding to.
//add_action( 'after_setup_theme', 'xast_after_setup_theme'); add_action( 'init', 'xast_init'); function xast_after_setup_theme() { $cheque_colour = __( 'check color', 'xast'); bw_trace2( $cheque_colour, 'check color?', false); } function xast_init() { $cheque_colour = __( 'cheque color', 'xast'); bw_trace2( $cheque_colour, 'cheque color?', false); }
In my tests I was logged in with a user who had selected a different Language to that defined for the Site Language.
Site language: bb_BB
User language: en_GB
Translations:
en_US: check color
en_GB: check colour
bb_BB: cehck cloor
en_US: cheque color
en_GB: cheque colour
bb_BB: cehuqe cloor
The output for the translation performed during init
was cehuqe color
.
I was expecting it to be cheque colour
.
I toggled the hook and for after_setup_theme
I got cehck cloor
.
In neither case did I get the output in the current user's preferred language.
In the dashboard I do get the expected results.
So now I am doubly confused
- Why don't I get my output in the user's preferred language?
- Why / how does it work in wp-admin?
#58
@
5 months ago
Why don't I get my output in the user's preferred language?
Why / how does it work in wp-admin?
Because the user's locale is only used in the admin. You can learn more about that at https://make.wordpress.org/core/2016/11/07/user-admin-languages-and-locale-switching-in-4-7/. See also `determine_locale()`.
#59
@
5 months ago
@swisspidy Thanks for the clarification. I was being misled by your comment "The current user is normally set up in-between." I've followed through the code and can see the locale being set during determine_locale()
, called from load_default_textdomain()
.
In my opinion, it's a pity that the "doing it wrong" message refers to an internal core function and not the user function that was actual the cause of the problem.
#61
@
4 months ago
- Focuses performance coding-standards added
These warnings coming from many plugins including JetPack are swamping the logfile to the extent of degrading performance. I've had to downgrade back to 6.6.2.
Could it not be remembered if a log message was emitted, so as not to repeatedly do so?
#62
@
4 months ago
- Focuses performance coding-standards removed
Please don‘t modify already fixed tickets.
You should update to Jetpack 14.0, which was released last week to fix this.
#63
@
4 months ago
Without some mechanism similar to wp_load_translations_early
this seems too agressive to always warn on this.
The use case is where a plugin does want to kill the request early. Previously, the messages could at least be translated into the locale of the site, even if the user was not loaded yet. But now, it seems plugins have no choice but to output error messages in english if they must communicate to the user before init
?
That's the reason, yes.
Plugins are loaded early in
wp-settings.php
, way before the default translations are loaded and the current user is set up.See https://github.com/WordPress/wordpress-develop/blob/e29d895ffd7388fcb6c73c1c5f6057204096dc03/src/wp-settings.php#L307 and https://github.com/WordPress/wordpress-develop/blob/e29d895ffd7388fcb6c73c1c5f6057204096dc03/src/wp-settings.php#L404-L450
This happens after
plugins_loaded
, so the first time you can be sure that everything is set up is in theinit
hook.IMO it's a rule of thumb that you shouldn't run any code directly without hooking into some action. This scenario with
load_*_textdomain()
(which I know people run into from time to time) is just one example. And it's a known one. After all the blog post you linked to is over 8 years old :-)