Skip to content

Commit 193f6eb

Browse files
committed
i18n: Account for load_*_textdomain() after JIT loading.
When `load_*_textdomain()` functions are called after WordPress has already attempted just-in-time loading of translations, nothing happens. This updates the related logic to retry translation loading when a custom path is set to ensure all translations are available. Additionally, this also fixes cases where an `en_US.mo` file is provided with non-English strings to override the default language. Follow up to [59157]. Props swissspidy, peterwilsoncc, desrosj, apermo, sergeybiryukov, wildworks, tigriweb, twvania, looswebstudio, stimul, audrasjb, finntown, bluantinoo, timwhitlock, albigdd. See #62337. git-svn-id: https://develop.svn.wordpress.org/trunk@59430 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 9b62294 commit 193f6eb

6 files changed

Lines changed: 77 additions & 7 deletions

File tree

src/wp-includes/class-wp-textdomain-registry.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,16 @@ public function set( $domain, $locale, $path ) {
153153
* @param string $path Language directory path.
154154
*/
155155
public function set_custom_path( $domain, $path ) {
156+
// If just-in-time loading was triggered before, reset the entry so it can be tried again.
157+
158+
if ( isset( $this->all[ $domain ] ) ) {
159+
$this->all[ $domain ] = array_filter( $this->all[ $domain ] );
160+
}
161+
162+
if ( empty( $this->current[ $domain ] ) ) {
163+
unset( $this->current[ $domain ] );
164+
}
165+
156166
$this->custom_paths[ $domain ] = rtrim( $path, '/' );
157167
}
158168

@@ -336,7 +346,7 @@ private function get_path_from_lang_dir( $domain, $locale ) {
336346
* If no path is found for the given locale and a custom path has been set
337347
* using load_plugin_textdomain/load_theme_textdomain, use that one.
338348
*/
339-
if ( 'en_US' !== $locale && isset( $this->custom_paths[ $domain ] ) ) {
349+
if ( isset( $this->custom_paths[ $domain ] ) ) {
340350
$fallback_location = rtrim( $this->custom_paths[ $domain ], '/' ) . '/';
341351
$this->set( $domain, $locale, $fallback_location );
342352
return $fallback_location;

src/wp-includes/l10n.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,9 @@ function load_default_textdomain( $locale = null ) {
985985
* @since 4.6.0 The function now tries to load the .mo file from the languages directory first.
986986
* @since 6.7.0 Translations are no longer immediately loaded, but handed off to the just-in-time loading mechanism.
987987
*
988+
* @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry.
989+
* @global array<string, WP_Translations|NOOP_Translations> $l10n An array of all currently loaded text domains.
990+
*
988991
* @param string $domain Unique identifier for retrieving translated strings
989992
* @param string|false $deprecated Optional. Deprecated. Use the $plugin_rel_path parameter instead.
990993
* Default false.
@@ -994,7 +997,8 @@ function load_default_textdomain( $locale = null ) {
994997
*/
995998
function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) {
996999
/** @var WP_Textdomain_Registry $wp_textdomain_registry */
997-
global $wp_textdomain_registry;
1000+
/** @var array<string, WP_Translations|NOOP_Translations> $l10n */
1001+
global $wp_textdomain_registry, $l10n;
9981002

9991003
if ( ! is_string( $domain ) ) {
10001004
return false;
@@ -1011,6 +1015,11 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path
10111015

10121016
$wp_textdomain_registry->set_custom_path( $domain, $path );
10131017

1018+
// If just-in-time loading was triggered before, reset the entry so it can be tried again.
1019+
if ( isset( $l10n[ $domain ] ) && $l10n[ $domain ] instanceof NOOP_Translations ) {
1020+
unset( $l10n[ $domain ] );
1021+
}
1022+
10141023
return true;
10151024
}
10161025

@@ -1022,6 +1031,7 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path
10221031
* @since 6.7.0 Translations are no longer immediately loaded, but handed off to the just-in-time loading mechanism.
10231032
*
10241033
* @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry.
1034+
* @global array<string, WP_Translations|NOOP_Translations> $l10n An array of all currently loaded text domains.
10251035
*
10261036
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
10271037
* @param string $mu_plugin_rel_path Optional. Relative to `WPMU_PLUGIN_DIR` directory in which the .mo
@@ -1030,7 +1040,8 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path
10301040
*/
10311041
function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) {
10321042
/** @var WP_Textdomain_Registry $wp_textdomain_registry */
1033-
global $wp_textdomain_registry;
1043+
/** @var array<string, WP_Translations|NOOP_Translations> $l10n */
1044+
global $wp_textdomain_registry, $l10n;
10341045

10351046
if ( ! is_string( $domain ) ) {
10361047
return false;
@@ -1040,6 +1051,11 @@ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) {
10401051

10411052
$wp_textdomain_registry->set_custom_path( $domain, $path );
10421053

1054+
// If just-in-time loading was triggered before, reset the entry so it can be tried again.
1055+
if ( isset( $l10n[ $domain ] ) && $l10n[ $domain ] instanceof NOOP_Translations ) {
1056+
unset( $l10n[ $domain ] );
1057+
}
1058+
10431059
return true;
10441060
}
10451061

@@ -1056,6 +1072,7 @@ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) {
10561072
* @since 6.7.0 Translations are no longer immediately loaded, but handed off to the just-in-time loading mechanism.
10571073
*
10581074
* @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry.
1075+
* @global array<string, WP_Translations|NOOP_Translations> $l10n An array of all currently loaded text domains.
10591076
*
10601077
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
10611078
* @param string|false $path Optional. Path to the directory containing the .mo file.
@@ -1064,7 +1081,8 @@ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) {
10641081
*/
10651082
function load_theme_textdomain( $domain, $path = false ) {
10661083
/** @var WP_Textdomain_Registry $wp_textdomain_registry */
1067-
global $wp_textdomain_registry;
1084+
/** @var array<string, WP_Translations|NOOP_Translations> $l10n */
1085+
global $wp_textdomain_registry, $l10n;
10681086

10691087
if ( ! is_string( $domain ) ) {
10701088
return false;
@@ -1076,6 +1094,11 @@ function load_theme_textdomain( $domain, $path = false ) {
10761094

10771095
$wp_textdomain_registry->set_custom_path( $domain, $path );
10781096

1097+
// If just-in-time loading was triggered before, reset the entry so it can be tried again.
1098+
if ( isset( $l10n[ $domain ] ) && $l10n[ $domain ] instanceof NOOP_Translations ) {
1099+
unset( $l10n[ $domain ] );
1100+
}
1101+
10791102
return true;
10801103
}
10811104

tests/phpunit/data/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
Text Domain: custom-internationalized-plugin
88
*/
99

10-
load_plugin_textdomain( 'custom-internationalized-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
10+
function custom_i18n_load_textdomain() {
11+
load_plugin_textdomain( 'custom-internationalized-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
12+
}
13+
14+
add_action( 'init', 'custom_i18n_load_textdomain' );
1115

1216
function custom_i18n_plugin_test() {
1317
return __( 'This is a dummy plugin', 'custom-internationalized-plugin' );

tests/phpunit/tests/l10n/loadTextdomainJustInTime.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,4 +342,34 @@ public function test_get_locale_is_called_only_once_per_textdomain_with_custom_l
342342
$this->assertFalse( is_textdomain_loaded( $textdomain ) );
343343
$this->assertSame( 1, $filter->get_call_count() );
344344
}
345+
346+
/**
347+
* @ticket 44937
348+
* @ticket 62337
349+
*
350+
* @covers ::load_plugin_textdomain
351+
* @covers ::is_textdomain_loaded
352+
* @covers WP_Textdomain_Registry::set_custom_path
353+
*/
354+
public function test_plugin_translation_should_be_translated_when_calling_load_plugin_textdomain_too_late() {
355+
require_once DIR_TESTDATA . '/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php';
356+
357+
add_filter( 'locale', array( $this, 'filter_set_locale_to_german' ) );
358+
359+
$is_textdomain_loaded_before = is_textdomain_loaded( 'custom-internationalized-plugin' );
360+
$output_before = custom_i18n_plugin_test();
361+
362+
$is_textdomain_loaded_middle = is_textdomain_loaded( 'custom-internationalized-plugin' );
363+
364+
custom_i18n_load_textdomain();
365+
366+
$output_after = custom_i18n_plugin_test();
367+
$is_textdomain_loaded_after = is_textdomain_loaded( 'custom-internationalized-plugin' );
368+
369+
$this->assertFalse( $is_textdomain_loaded_before );
370+
$this->assertFalse( $is_textdomain_loaded_middle );
371+
$this->assertSame( 'This is a dummy plugin', $output_before );
372+
$this->assertSame( 'Das ist ein Dummy Plugin', $output_after );
373+
$this->assertTrue( $is_textdomain_loaded_after );
374+
}
345375
}

tests/phpunit/tests/l10n/wpLocaleSwitcher.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,8 @@ public function test_switch_reloads_plugin_translations_outside_wp_lang_dir() {
493493

494494
require_once DIR_TESTDATA . '/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php';
495495

496+
custom_i18n_load_textdomain();
497+
496498
$actual = custom_i18n_plugin_test();
497499

498500
switch_to_locale( 'es_ES' );

tests/phpunit/tests/l10n/wpTextdomainRegistry.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ public function test_set_custom_path() {
3939
$this->instance->has( 'foo' ),
4040
'Incorrect availability status for textdomain with custom path'
4141
);
42-
$this->assertFalse(
42+
$this->assertSame(
43+
WP_LANG_DIR . '/bar/',
4344
$this->instance->get( 'foo', 'en_US' ),
44-
'Should not return custom path for textdomain and en_US locale'
45+
'Should return custom path for textdomain and en_US locale'
4546
);
4647
$this->assertSame(
4748
WP_LANG_DIR . '/bar/',

0 commit comments

Comments
 (0)