diff --git a/src/wp-includes/class-wp-textdomain-registry.php b/src/wp-includes/class-wp-textdomain-registry.php index e5aeb82e5c..bb2135e365 100644 --- a/src/wp-includes/class-wp-textdomain-registry.php +++ b/src/wp-includes/class-wp-textdomain-registry.php @@ -153,6 +153,16 @@ class WP_Textdomain_Registry { * @param string $path Language directory path. */ public function set_custom_path( $domain, $path ) { + // If just-in-time loading was triggered before, reset the entry so it can be tried again. + + if ( isset( $this->all[ $domain ] ) ) { + $this->all[ $domain ] = array_filter( $this->all[ $domain ] ); + } + + if ( empty( $this->current[ $domain ] ) ) { + unset( $this->current[ $domain ] ); + } + $this->custom_paths[ $domain ] = rtrim( $path, '/' ); } @@ -336,7 +346,7 @@ class WP_Textdomain_Registry { * If no path is found for the given locale and a custom path has been set * using load_plugin_textdomain/load_theme_textdomain, use that one. */ - if ( 'en_US' !== $locale && isset( $this->custom_paths[ $domain ] ) ) { + if ( isset( $this->custom_paths[ $domain ] ) ) { $fallback_location = rtrim( $this->custom_paths[ $domain ], '/' ) . '/'; $this->set( $domain, $locale, $fallback_location ); return $fallback_location; diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index bee3581618..f9239f84b9 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -985,6 +985,9 @@ function load_default_textdomain( $locale = null ) { * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * @since 6.7.0 Translations are no longer immediately loaded, but handed off to the just-in-time loading mechanism. * + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + * @global array $l10n An array of all currently loaded text domains. + * * @param string $domain Unique identifier for retrieving translated strings * @param string|false $deprecated Optional. Deprecated. Use the $plugin_rel_path parameter instead. * Default false. @@ -994,7 +997,8 @@ function load_default_textdomain( $locale = null ) { */ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) { /** @var WP_Textdomain_Registry $wp_textdomain_registry */ - global $wp_textdomain_registry; + /** @var array $l10n */ + global $wp_textdomain_registry, $l10n; if ( ! is_string( $domain ) ) { return false; @@ -1011,6 +1015,11 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path $wp_textdomain_registry->set_custom_path( $domain, $path ); + // If just-in-time loading was triggered before, reset the entry so it can be tried again. + if ( isset( $l10n[ $domain ] ) && $l10n[ $domain ] instanceof NOOP_Translations ) { + unset( $l10n[ $domain ] ); + } + return true; } @@ -1022,6 +1031,7 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path * @since 6.7.0 Translations are no longer immediately loaded, but handed off to the just-in-time loading mechanism. * * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + * @global array $l10n An array of all currently loaded text domains. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @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 */ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { /** @var WP_Textdomain_Registry $wp_textdomain_registry */ - global $wp_textdomain_registry; + /** @var array $l10n */ + global $wp_textdomain_registry, $l10n; if ( ! is_string( $domain ) ) { return false; @@ -1040,6 +1051,11 @@ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { $wp_textdomain_registry->set_custom_path( $domain, $path ); + // If just-in-time loading was triggered before, reset the entry so it can be tried again. + if ( isset( $l10n[ $domain ] ) && $l10n[ $domain ] instanceof NOOP_Translations ) { + unset( $l10n[ $domain ] ); + } + return true; } @@ -1056,6 +1072,7 @@ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { * @since 6.7.0 Translations are no longer immediately loaded, but handed off to the just-in-time loading mechanism. * * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + * @global array $l10n An array of all currently loaded text domains. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @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 = '' ) { */ function load_theme_textdomain( $domain, $path = false ) { /** @var WP_Textdomain_Registry $wp_textdomain_registry */ - global $wp_textdomain_registry; + /** @var array $l10n */ + global $wp_textdomain_registry, $l10n; if ( ! is_string( $domain ) ) { return false; @@ -1076,6 +1094,11 @@ function load_theme_textdomain( $domain, $path = false ) { $wp_textdomain_registry->set_custom_path( $domain, $path ); + // If just-in-time loading was triggered before, reset the entry so it can be tried again. + if ( isset( $l10n[ $domain ] ) && $l10n[ $domain ] instanceof NOOP_Translations ) { + unset( $l10n[ $domain ] ); + } + return true; } diff --git a/tests/phpunit/data/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php b/tests/phpunit/data/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php index 41eb593556..c7d1c0d5e4 100644 --- a/tests/phpunit/data/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php +++ b/tests/phpunit/data/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php @@ -7,7 +7,11 @@ Version: 1.0.0 Text Domain: custom-internationalized-plugin */ -load_plugin_textdomain( 'custom-internationalized-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); +function custom_i18n_load_textdomain() { + load_plugin_textdomain( 'custom-internationalized-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); +} + +add_action( 'init', 'custom_i18n_load_textdomain' ); function custom_i18n_plugin_test() { return __( 'This is a dummy plugin', 'custom-internationalized-plugin' ); diff --git a/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php b/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php index ea6de4017b..af8bb8825a 100644 --- a/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php +++ b/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php @@ -342,4 +342,34 @@ class Tests_L10n_LoadTextdomainJustInTime extends WP_UnitTestCase { $this->assertFalse( is_textdomain_loaded( $textdomain ) ); $this->assertSame( 1, $filter->get_call_count() ); } + + /** + * @ticket 44937 + * @ticket 62337 + * + * @covers ::load_plugin_textdomain + * @covers ::is_textdomain_loaded + * @covers WP_Textdomain_Registry::set_custom_path + */ + public function test_plugin_translation_should_be_translated_when_calling_load_plugin_textdomain_too_late() { + require_once DIR_TESTDATA . '/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php'; + + add_filter( 'locale', array( $this, 'filter_set_locale_to_german' ) ); + + $is_textdomain_loaded_before = is_textdomain_loaded( 'custom-internationalized-plugin' ); + $output_before = custom_i18n_plugin_test(); + + $is_textdomain_loaded_middle = is_textdomain_loaded( 'custom-internationalized-plugin' ); + + custom_i18n_load_textdomain(); + + $output_after = custom_i18n_plugin_test(); + $is_textdomain_loaded_after = is_textdomain_loaded( 'custom-internationalized-plugin' ); + + $this->assertFalse( $is_textdomain_loaded_before ); + $this->assertFalse( $is_textdomain_loaded_middle ); + $this->assertSame( 'This is a dummy plugin', $output_before ); + $this->assertSame( 'Das ist ein Dummy Plugin', $output_after ); + $this->assertTrue( $is_textdomain_loaded_after ); + } } diff --git a/tests/phpunit/tests/l10n/wpLocaleSwitcher.php b/tests/phpunit/tests/l10n/wpLocaleSwitcher.php index ba12a432e4..f24ca7695c 100644 --- a/tests/phpunit/tests/l10n/wpLocaleSwitcher.php +++ b/tests/phpunit/tests/l10n/wpLocaleSwitcher.php @@ -493,6 +493,8 @@ class Tests_L10n_wpLocaleSwitcher extends WP_UnitTestCase { require_once DIR_TESTDATA . '/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php'; + custom_i18n_load_textdomain(); + $actual = custom_i18n_plugin_test(); switch_to_locale( 'es_ES' ); diff --git a/tests/phpunit/tests/l10n/wpTextdomainRegistry.php b/tests/phpunit/tests/l10n/wpTextdomainRegistry.php index 0344fe6caf..f5a12ff779 100644 --- a/tests/phpunit/tests/l10n/wpTextdomainRegistry.php +++ b/tests/phpunit/tests/l10n/wpTextdomainRegistry.php @@ -39,9 +39,10 @@ class Tests_L10n_wpTextdomainRegistry extends WP_UnitTestCase { $this->instance->has( 'foo' ), 'Incorrect availability status for textdomain with custom path' ); - $this->assertFalse( + $this->assertSame( + WP_LANG_DIR . '/bar/', $this->instance->get( 'foo', 'en_US' ), - 'Should not return custom path for textdomain and en_US locale' + 'Should return custom path for textdomain and en_US locale' ); $this->assertSame( WP_LANG_DIR . '/bar/',