diff --git a/src/wp-includes/class-wp-locale-switcher.php b/src/wp-includes/class-wp-locale-switcher.php index 3d288c8c35..2b0638d291 100644 --- a/src/wp-includes/class-wp-locale-switcher.php +++ b/src/wp-includes/class-wp-locale-switcher.php @@ -202,14 +202,7 @@ class WP_Locale_Switcher { continue; } - $mofile = $l10n[ $domain ]->get_filename(); - unload_textdomain( $domain ); - - if ( $mofile ) { - load_textdomain( $domain, $mofile ); - } - get_translations_for_domain( $domain ); } } @@ -228,6 +221,9 @@ class WP_Locale_Switcher { * @param string $locale The locale to change to. */ private function change_locale( $locale ) { + // Reset translation availability information. + _get_path_to_translation( null, true ); + $this->load_translations( $locale ); $GLOBALS['wp_locale'] = new WP_Locale(); diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 35f3582f66..524beb49d9 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -827,8 +827,6 @@ function load_child_theme_textdomain( $domain, $path = false ) { * the translation file from `wp-content/languages`, removing the need * to call load_plugin_texdomain() or load_theme_texdomain(). * - * Holds a cached list of available .mo files to improve performance. - * * @since 4.6.0 * @access private * @@ -843,13 +841,63 @@ function _load_textdomain_just_in_time( $domain ) { $l10n_unloaded = (array) $l10n_unloaded; - static $cached_mofiles = null; - // Short-circuit if domain is 'default' which is reserved for core. if ( 'default' === $domain || isset( $l10n_unloaded[ $domain ] ) ) { return false; } + $translation_path = _get_path_to_translation( $domain ); + if ( false === $translation_path ) { + return false; + } + + return load_textdomain( $domain, $translation_path ); +} + +/** + * Gets the path to a translation file for loading a textdomain just in time. + * + * Caches the retrieved results internally. + * + * @since 4.7.0 + * @access private + * + * @see _load_textdomain_just_in_time() + * + * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * @param bool $reset Whether to reset the internal cache. Used by the switch to locale functionality. + * @return string|false The path to the translation file or false if no translation file was found. + */ +function _get_path_to_translation( $domain, $reset = false ) { + static $available_translations = array(); + + if ( true === $reset ) { + $available_translations = array(); + } + + if ( ! isset( $available_translations[ $domain ] ) ) { + $available_translations[ $domain ] = _get_path_to_translation_from_lang_dir( $domain ); + } + + return $available_translations[ $domain ]; +} + +/** + * Gets the path to a translation file in the languages directory for the current locale. + * + * Holds a cached list of available .mo files to improve performance. + * + * @since 4.7.0 + * @access private + * + * @see _get_path_to_translation() + * + * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * @return string|false The path to the translation file or false if no translation file was found. + */ +function _get_path_to_translation_from_lang_dir( $domain ) { + static $cached_mofiles = null; + if ( null === $cached_mofiles ) { $cached_mofiles = array(); @@ -869,12 +917,14 @@ function _load_textdomain_just_in_time( $domain ) { $locale = is_admin() ? get_user_locale() : get_locale(); $mofile = "{$domain}-{$locale}.mo"; - if ( in_array( WP_LANG_DIR . '/plugins/' . $mofile, $cached_mofiles ) ) { - return load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ); + $path = WP_LANG_DIR . '/plugins/' . $mofile; + if ( in_array( $path, $cached_mofiles ) ) { + return $path; } - if ( in_array( WP_LANG_DIR . '/themes/' . $mofile, $cached_mofiles ) ) { - return load_textdomain( $domain, WP_LANG_DIR . '/themes/' . $mofile ); + $path = WP_LANG_DIR . '/themes/' . $mofile; + if ( in_array( $path, $cached_mofiles ) ) { + return $path; } return false; diff --git a/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php b/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php index f0d7b5528f..fc2b84aa3a 100644 --- a/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php +++ b/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php @@ -8,6 +8,7 @@ class Tests_L10n_loadTextdomainJustInTime extends WP_UnitTestCase { protected $orig_theme_dir; protected $theme_root; protected static $user_id; + private $locale_count; public static function wpSetUpBeforeClass( $factory ) { self::$user_id = $factory->user->create( array( @@ -21,6 +22,7 @@ class Tests_L10n_loadTextdomainJustInTime extends WP_UnitTestCase { $this->theme_root = DIR_TESTDATA . '/themedir1'; $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + $this->locale_count = 0; // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); @@ -31,6 +33,7 @@ class Tests_L10n_loadTextdomainJustInTime extends WP_UnitTestCase { unset( $GLOBALS['wp_themes'] ); unset( $GLOBALS['l10n'] ); unset( $GLOBALS['l10n_unloaded'] ); + _get_path_to_translation( null, true ); } public function tearDown() { @@ -42,6 +45,7 @@ class Tests_L10n_loadTextdomainJustInTime extends WP_UnitTestCase { unset( $GLOBALS['wp_themes'] ); unset( $GLOBALS['l10n'] ); unset( $GLOBALS['l10n_unloaded'] ); + _get_path_to_translation( null, true ); parent::tearDown(); } @@ -162,6 +166,24 @@ class Tests_L10n_loadTextdomainJustInTime extends WP_UnitTestCase { $this->assertSame( 'Das ist ein Dummy Plugin', $expected ); } + /** + * @ticket 37997 + */ + public function test_plugin_translation_after_switching_locale_twice() { + require_once DIR_TESTDATA . '/plugins/internationalized-plugin.php'; + + switch_to_locale( 'de_DE' ); + $expected_de_DE = i18n_plugin_test(); + + switch_to_locale( 'es_ES' ); + $expected_es_ES = i18n_plugin_test(); + + restore_current_locale(); + + $this->assertSame( 'Das ist ein Dummy Plugin', $expected_de_DE ); + $this->assertSame( 'This is a dummy plugin', $expected_es_ES ); + } + /** * @ticket 26511 */ @@ -212,4 +234,30 @@ class Tests_L10n_loadTextdomainJustInTime extends WP_UnitTestCase { $this->assertSame( 'Das ist ein Dummy Theme', $expected ); } + + /** + * @ticket 37997 + */ + public function test_get_locale_is_called_only_once_per_textdomain() { + $textdomain = 'foo-bar-baz'; + + add_filter( 'locale', array( $this, '_filter_locale_count' ) ); + + __( 'Foo', $textdomain ); + __( 'Bar', $textdomain ); + __( 'Baz', $textdomain ); + __( 'Foo Bar', $textdomain ); + __( 'Foo Bar Baz', $textdomain ); + + remove_filter( 'locale', array( $this, '_filter_locale_count' ) ); + + $this->assertFalse( is_textdomain_loaded( $textdomain ) ); + $this->assertSame( 1, $this->locale_count ); + } + + public function _filter_locale_count( $locale ) { + ++$this->locale_count; + + return $locale; + } } diff --git a/tests/phpunit/tests/l10n/localeSwitcher.php b/tests/phpunit/tests/l10n/localeSwitcher.php index 262f8b1810..41492bdb45 100644 --- a/tests/phpunit/tests/l10n/localeSwitcher.php +++ b/tests/phpunit/tests/l10n/localeSwitcher.php @@ -24,11 +24,13 @@ class Tests_Locale_Switcher extends WP_UnitTestCase { unset( $GLOBALS['l10n'] ); unset( $GLOBALS['l10n_unloaded'] ); + _get_path_to_translation( null, true ); } public function tearDown() { unset( $GLOBALS['l10n'] ); unset( $GLOBALS['l10n_unloaded'] ); + _get_path_to_translation( null, true ); parent::tearDown(); }