From 9c8b1ba9ab8029bed025d6a9a748370d179733f3 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 11 Aug 2022 12:37:05 +0000 Subject: [PATCH] I18N: Introduce `WP_Textdomain_Registry` to store text domains and their language directory paths. Previously, when using `switch_to_locale()` all current loaded text domains were unloaded and added to the `$l10n_unloaded` global. This prevented the just-in-time loading for text domains after a switch. The just-in-time loading was also only possible if the translations were stored in `WP_LANG_DIR`. Both issues have been fixed. * Adds `WP_Textdomain_Registry` to keep track of the language directory paths for all plugins and themes. * Updates all `load_*_textdomain()` functions to store the path in `WP_Textdomain_Registry`. * Adds `$locale` parameter to `load_textdomain()` to specify the locale the translation file is for. * Adds `$reloadable` parameter to `unload_textdomain()` to define whether a text domain can be loaded just-in-time again. This is used by `WP_Locale_Switcher::load_translations()`. * Extends `_load_textdomain_just_in_time()` to also support text domains of plugins and themes with custom language directories. * Fixes the incorrect `test_plugin_translation_after_switching_locale_twice()` test which should have caught this issue earlier. * Adds a new test plugin and theme to test the loading of translations with a custom language directory. * Deprecates the now unused and private `_get_path_to_translation()` and `_get_path_to_translation_from_lang_dir()` functions. Previously added in [49236] and reverted in [49236] to investigate concerns which are now addressed here. Props yoavf, swissspidy, dd32, ocean90. See #26511. Fixes #39210. git-svn-id: https://develop.svn.wordpress.org/trunk@53874 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/class-wp-locale-switcher.php | 10 +- .../class-wp-textdomain-registry.php | 184 +++++++++++++++++ src/wp-includes/deprecated.php | 82 ++++++++ src/wp-includes/l10n.php | 189 ++++++++---------- src/wp-settings.php | 12 ++ .../plugins/internationalized-plugin-de_DE.mo | Bin 648 -> 640 bytes .../plugins/internationalized-plugin-de_DE.po | 6 +- .../plugins/internationalized-plugin-es_ES.mo | Bin 0 -> 639 bytes .../plugins/internationalized-plugin-es_ES.po | 23 +++ .../themes/internationalized-theme-de_DE.mo | Bin 646 -> 638 bytes .../themes/internationalized-theme-de_DE.po | 6 +- .../custom-internationalized-plugin.php | 14 ++ .../custom-internationalized-plugin-de_DE.mo | Bin 0 -> 655 bytes .../custom-internationalized-plugin-de_DE.po | 23 +++ .../custom-internationalized-plugin-es_ES.mo | Bin 0 -> 654 bytes .../custom-internationalized-plugin-es_ES.po | 23 +++ tests/phpunit/data/plugins/hello.php | 5 +- .../functions.php | 10 + .../custom-internationalized-theme/index.php | 4 + .../languages/de_DE.mo | Bin 0 -> 636 bytes .../languages/de_DE.po | 23 +++ .../languages/es_ES.mo | Bin 0 -> 634 bytes .../languages/es_ES.po | 23 +++ .../custom-internationalized-theme/style.css | 7 + tests/phpunit/includes/bootstrap.php | 3 +- tests/phpunit/tests/admin/includesPlugin.php | 2 +- tests/phpunit/tests/l10n/loadTextdomain.php | 15 ++ .../tests/l10n/loadTextdomainJustInTime.php | 23 ++- tests/phpunit/tests/l10n/wpLocaleSwitcher.php | 94 ++++++++- tests/phpunit/tests/theme/themeDir.php | 1 + 30 files changed, 648 insertions(+), 134 deletions(-) create mode 100644 src/wp-includes/class-wp-textdomain-registry.php create mode 100644 tests/phpunit/data/languages/plugins/internationalized-plugin-es_ES.mo create mode 100644 tests/phpunit/data/languages/plugins/internationalized-plugin-es_ES.po create mode 100644 tests/phpunit/data/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php create mode 100644 tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-de_DE.mo create mode 100644 tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-de_DE.po create mode 100644 tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-es_ES.mo create mode 100644 tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-es_ES.po create mode 100644 tests/phpunit/data/themedir1/custom-internationalized-theme/functions.php create mode 100644 tests/phpunit/data/themedir1/custom-internationalized-theme/index.php create mode 100644 tests/phpunit/data/themedir1/custom-internationalized-theme/languages/de_DE.mo create mode 100644 tests/phpunit/data/themedir1/custom-internationalized-theme/languages/de_DE.po create mode 100644 tests/phpunit/data/themedir1/custom-internationalized-theme/languages/es_ES.mo create mode 100644 tests/phpunit/data/themedir1/custom-internationalized-theme/languages/es_ES.po create mode 100644 tests/phpunit/data/themedir1/custom-internationalized-theme/style.css diff --git a/src/wp-includes/class-wp-locale-switcher.php b/src/wp-includes/class-wp-locale-switcher.php index 7ec7622161..3cbd613a43 100644 --- a/src/wp-includes/class-wp-locale-switcher.php +++ b/src/wp-includes/class-wp-locale-switcher.php @@ -196,11 +196,14 @@ class WP_Locale_Switcher { load_default_textdomain( $locale ); foreach ( $domains as $domain ) { + // The default text domain is handled by `load_default_textdomain()`. if ( 'default' === $domain ) { continue; } - unload_textdomain( $domain ); + // Unload current text domain but allow them to be reloaded + // after switching back or to another locale. + unload_textdomain( $domain, true ); get_translations_for_domain( $domain ); } } @@ -218,12 +221,11 @@ 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 ); + global $wp_locale; $this->load_translations( $locale ); - $GLOBALS['wp_locale'] = new WP_Locale(); + $wp_locale = new WP_Locale(); /** * Fires when the locale is switched to or restored. diff --git a/src/wp-includes/class-wp-textdomain-registry.php b/src/wp-includes/class-wp-textdomain-registry.php new file mode 100644 index 0000000000..d4d8968ac9 --- /dev/null +++ b/src/wp-includes/class-wp-textdomain-registry.php @@ -0,0 +1,184 @@ +all[ $domain ][ $locale ] ) ) { + return $this->all[ $domain ][ $locale ]; + } + + return $this->get_path_from_lang_dir( $domain, $locale ); + } + + /** + * Determines whether any MO file paths are available for the domain. + * + * @since 6.1.0 + * + * @param string $domain Text domain. + * @return bool Whether any MO file paths are available for the domain. + */ + public function has( $domain ) { + return ! empty( $this->all[ $domain ] ); + } + + /** + * Returns the current (most recent) MO file path for a specific domain. + * + * @since 6.1.0 + * + * @param string $domain Text domain. + * @return string|false Current MO file path or false if there is none available. + */ + public function get_current( $domain ) { + if ( isset( $this->current[ $domain ] ) ) { + return $this->current[ $domain ]; + } + + return false; + } + + /** + * Sets the MO file path for a specific domain and locale. + * + * Also sets the 'current' property for direct access + * to the path for the current (most recent) locale. + * + * @since 6.1.0 + * + * @param string $domain Text domain. + * @param string $locale Locale. + * @param string|false $path Language directory path or false if there is none available. + */ + public function set( $domain, $locale, $path ) { + $this->all[ $domain ][ $locale ] = $path ? trailingslashit( $path ) : false; + $this->current[ $domain ] = $this->all[ $domain ][ $locale ]; + } + + /** + * Resets the registry state. + * + * @since 6.1.0 + */ + public function reset() { + $this->cached_mo_files = null; + $this->all = array(); + $this->current = array(); + } + + /** + * Gets the path to a translation file in the languages directory for the current locale. + * + * @since 6.1.0 + * + * @param string $domain Text domain. + * @param string $locale Locale. + * @return string|false MO file path or false if there is none available. + */ + private function get_path_from_lang_dir( $domain, $locale ) { + if ( null === $this->cached_mo_files ) { + $this->set_cached_mo_files(); + } + + $mofile = "{$domain}-{$locale}.mo"; + + $path = WP_LANG_DIR . '/plugins/' . $mofile; + + if ( in_array( $path, $this->cached_mo_files, true ) ) { + $path = WP_LANG_DIR . '/plugins/'; + $this->set( $domain, $locale, $path ); + + return $path; + } + + $path = WP_LANG_DIR . '/themes/' . $mofile; + if ( in_array( $path, $this->cached_mo_files, true ) ) { + $path = WP_LANG_DIR . '/themes/'; + $this->set( $domain, $locale, $path ); + + return $path; + } + + // If no path is found for the given locale, check if an entry for the default + // en_US locale exists. This is the case when e.g. using load_plugin_textdomain + // with a custom path. + if ( 'en_US' !== $locale && isset( $this->all[ $domain ]['en_US'] ) ) { + $this->set( $domain, $locale, $this->all[ $domain ]['en_US'] ); + return $this->all[ $domain ]['en_US']; + } + + $this->set( $domain, $locale, false ); + + return false; + } + + /** + * Reads and caches all available MO files from the plugins and themes language directories. + * + * @since 6.1.0 + */ + protected function set_cached_mo_files() { + $this->cached_mo_files = array(); + + $locations = array( + WP_LANG_DIR . '/plugins', + WP_LANG_DIR . '/themes', + ); + + foreach ( $locations as $location ) { + $mo_files = glob( $location . '/*.mo' ); + + if ( $mo_files ) { + $this->cached_mo_files = array_merge( $this->cached_mo_files, $mo_files ); + } + } + } +} diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index 149f5b6e88..8841519b64 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -4360,3 +4360,85 @@ function wp_get_attachment_thumb_file( $post_id = 0 ) { return false; } + +/** + * Gets the path to a translation file for loading a textdomain just in time. + * + * Caches the retrieved results internally. + * + * @since 4.7.0 + * @deprecated 6.1.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 ) { + _deprecated_function( __FUNCTION__, '6.1.0', 'WP_Textdomain_Registry' ); + + 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 + * @deprecated 6.1.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 ) { + _deprecated_function( __FUNCTION__, '6.1.0', 'WP_Textdomain_Registry' ); + + static $cached_mofiles = null; + + if ( null === $cached_mofiles ) { + $cached_mofiles = array(); + + $locations = array( + WP_LANG_DIR . '/plugins', + WP_LANG_DIR . '/themes', + ); + + foreach ( $locations as $location ) { + $mofiles = glob( $location . '/*.mo' ); + if ( $mofiles ) { + $cached_mofiles = array_merge( $cached_mofiles, $mofiles ); + } + } + } + + $locale = determine_locale(); + $mofile = "{$domain}-{$locale}.mo"; + + $path = WP_LANG_DIR . '/plugins/' . $mofile; + if ( in_array( $path, $cached_mofiles, true ) ) { + return $path; + } + + $path = WP_LANG_DIR . '/themes/' . $mofile; + if ( in_array( $path, $cached_mofiles, true ) ) { + return $path; + } + + return false; +} diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 292355105e..f43bfe3be2 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -704,16 +704,20 @@ function translate_nooped_plural( $nooped_plural, $count, $domain = 'default' ) * and will be a MO object. * * @since 1.5.0 + * @since 6.1.0 Added the `$locale` parameter. * - * @global MO[] $l10n An array of all currently loaded text domains. - * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. + * @global MO[] $l10n An array of all currently loaded text domains. + * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $mofile Path to the .mo file. + * @param string $locale Optional. Locale. Default is the current locale. * @return bool True on success, false on failure. */ -function load_textdomain( $domain, $mofile ) { - global $l10n, $l10n_unloaded; +function load_textdomain( $domain, $mofile, $locale = null ) { + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $l10n, $l10n_unloaded, $wp_textdomain_registry; $l10n_unloaded = (array) $l10n_unloaded; @@ -758,8 +762,14 @@ function load_textdomain( $domain, $mofile ) { return false; } + if ( ! $locale ) { + $locale = determine_locale(); + } + $mo = new MO(); if ( ! $mo->import_from_file( $mofile ) ) { + $wp_textdomain_registry->set( $domain, $locale, false ); + return false; } @@ -771,6 +781,8 @@ function load_textdomain( $domain, $mofile ) { $l10n[ $domain ] = &$mo; + $wp_textdomain_registry->set( $domain, $locale, dirname( $mofile ) ); + return true; } @@ -778,14 +790,16 @@ function load_textdomain( $domain, $mofile ) { * Unloads translations for a text domain. * * @since 3.0.0 + * @since 6.1.0 Added the `$reloadable` parameter. * * @global MO[] $l10n An array of all currently loaded text domains. * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. * - * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * @param bool $reloadable Whether the text domain can be loaded just-in-time again. * @return bool Whether textdomain was unloaded. */ -function unload_textdomain( $domain ) { +function unload_textdomain( $domain, $reloadable = false ) { global $l10n, $l10n_unloaded; $l10n_unloaded = (array) $l10n_unloaded; @@ -794,14 +808,18 @@ function unload_textdomain( $domain ) { * Filters whether to override the text domain unloading. * * @since 3.0.0 + * @since 6.1.0 Added the `$reloadable` parameter. * - * @param bool $override Whether to override the text domain unloading. Default false. - * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * @param bool $override Whether to override the text domain unloading. Default false. + * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * @param bool $reloadable Whether the text domain can be loaded just-in-time again. */ - $plugin_override = apply_filters( 'override_unload_textdomain', false, $domain ); + $plugin_override = apply_filters( 'override_unload_textdomain', false, $domain, $reloadable ); if ( $plugin_override ) { - $l10n_unloaded[ $domain ] = true; + if ( ! $reloadable ) { + $l10n_unloaded[ $domain ] = true; + } return true; } @@ -810,15 +828,19 @@ function unload_textdomain( $domain ) { * Fires before the text domain is unloaded. * * @since 3.0.0 + * @since 6.1.0 Added the `$reloadable` parameter. * - * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * @param bool $reloadable Whether the text domain can be loaded just-in-time again. */ - do_action( 'unload_textdomain', $domain ); + do_action( 'unload_textdomain', $domain, $reloadable ); if ( isset( $l10n[ $domain ] ) ) { unset( $l10n[ $domain ] ); - $l10n_unloaded[ $domain ] = true; + if ( ! $reloadable ) { + $l10n_unloaded[ $domain ] = true; + } return true; } @@ -847,19 +869,19 @@ function load_default_textdomain( $locale = null ) { // Unload previously loaded strings so we can switch translations. unload_textdomain( 'default' ); - $return = load_textdomain( 'default', WP_LANG_DIR . "/$locale.mo" ); + $return = load_textdomain( 'default', WP_LANG_DIR . "/$locale.mo", $locale ); if ( ( is_multisite() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) ) && ! file_exists( WP_LANG_DIR . "/admin-$locale.mo" ) ) { - load_textdomain( 'default', WP_LANG_DIR . "/ms-$locale.mo" ); + load_textdomain( 'default', WP_LANG_DIR . "/ms-$locale.mo", $locale ); return $return; } if ( is_admin() || wp_installing() || ( defined( 'WP_REPAIRING' ) && WP_REPAIRING ) ) { - load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo" ); + load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo", $locale ); } if ( is_network_admin() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) ) { - load_textdomain( 'default', WP_LANG_DIR . "/admin-network-$locale.mo" ); + load_textdomain( 'default', WP_LANG_DIR . "/admin-network-$locale.mo", $locale ); } return $return; @@ -883,6 +905,9 @@ function load_default_textdomain( $locale = null ) { * @return bool True when textdomain is successfully loaded, false otherwise. */ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) { + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_textdomain_registry; + /** * Filters a plugin's locale. * @@ -896,7 +921,7 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path $mofile = $domain . '-' . $locale . '.mo'; // Try to load from the languages directory first. - if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) { + if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile, $locale ) ) { return true; } @@ -909,7 +934,9 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path $path = WP_PLUGIN_DIR; } - return load_textdomain( $domain, $path . '/' . $mofile ); + $wp_textdomain_registry->set( $domain, $locale, $path ); + + return load_textdomain( $domain, $path . '/' . $mofile, $locale ); } /** @@ -918,25 +945,32 @@ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path * @since 3.0.0 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + * * @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 * file resides. Default empty string. * @return bool True when textdomain is successfully loaded, false otherwise. */ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_textdomain_registry; + /** This filter is documented in wp-includes/l10n.php */ $locale = apply_filters( 'plugin_locale', determine_locale(), $domain ); $mofile = $domain . '-' . $locale . '.mo'; // Try to load from the languages directory first. - if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) { + if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile, $locale ) ) { return true; } $path = WPMU_PLUGIN_DIR . '/' . ltrim( $mu_plugin_rel_path, '/' ); - return load_textdomain( $domain, $path . '/' . $mofile ); + $wp_textdomain_registry->set( $domain, $locale, $path ); + + return load_textdomain( $domain, $path . '/' . $mofile, $locale ); } /** @@ -950,12 +984,17 @@ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { * @since 1.5.0 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string|false $path Optional. Path to the directory containing the .mo file. * Default false. * @return bool True when textdomain is successfully loaded, false otherwise. */ function load_theme_textdomain( $domain, $path = false ) { + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_textdomain_registry; + /** * Filters a theme's locale. * @@ -969,7 +1008,7 @@ function load_theme_textdomain( $domain, $path = false ) { $mofile = $domain . '-' . $locale . '.mo'; // Try to load from the languages directory first. - if ( load_textdomain( $domain, WP_LANG_DIR . '/themes/' . $mofile ) ) { + if ( load_textdomain( $domain, WP_LANG_DIR . '/themes/' . $mofile, $locale ) ) { return true; } @@ -977,7 +1016,9 @@ function load_theme_textdomain( $domain, $path = false ) { $path = get_template_directory(); } - return load_textdomain( $domain, $path . '/' . $locale . '.mo' ); + $wp_textdomain_registry->set( $domain, $locale, $path ); + + return load_textdomain( $domain, $path . '/' . $locale . '.mo', $locale ); } /** @@ -1198,7 +1239,7 @@ function load_script_translations( $file, $handle, $domain ) { } /** - * Loads plugin and theme textdomains just-in-time. + * Loads plugin and theme text domains just-in-time. * * When a textdomain is encountered for the first time, we try to load * the translation file from `wp-content/languages`, removing the need @@ -1207,14 +1248,15 @@ function load_script_translations( $file, $handle, $domain ) { * @since 4.6.0 * @access private * - * @see get_translations_for_domain() - * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. + * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return bool True when the textdomain is successfully loaded, false otherwise. */ function _load_textdomain_just_in_time( $domain ) { - global $l10n_unloaded; + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $l10n_unloaded, $wp_textdomain_registry; $l10n_unloaded = (array) $l10n_unloaded; @@ -1223,88 +1265,25 @@ function _load_textdomain_just_in_time( $domain ) { return false; } - $translation_path = _get_path_to_translation( $domain ); - if ( false === $translation_path ) { + if ( $wp_textdomain_registry->has( $domain ) && ! $wp_textdomain_registry->get_current( $domain ) ) { 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(); - - $locations = array( - WP_LANG_DIR . '/plugins', - WP_LANG_DIR . '/themes', - ); - - foreach ( $locations as $location ) { - $mofiles = glob( $location . '/*.mo' ); - if ( $mofiles ) { - $cached_mofiles = array_merge( $cached_mofiles, $mofiles ); - } - } - } - $locale = determine_locale(); - $mofile = "{$domain}-{$locale}.mo"; - - $path = WP_LANG_DIR . '/plugins/' . $mofile; - if ( in_array( $path, $cached_mofiles, true ) ) { - return $path; + $path = $wp_textdomain_registry->get( $domain, $locale ); + if ( ! $path ) { + return false; + } + // Themes with their language directory outside of WP_LANG_DIR have a different file name. + $template_directory = trailingslashit( get_template_directory() ); + $stylesheet_directory = trailingslashit( get_stylesheet_directory() ); + if ( str_starts_with( $path, $template_directory ) || str_starts_with( $path, $stylesheet_directory ) ) { + $mofile = "{$path}{$locale}.mo"; + } else { + $mofile = "{$path}{$domain}-{$locale}.mo"; } - $path = WP_LANG_DIR . '/themes/' . $mofile; - if ( in_array( $path, $cached_mofiles, true ) ) { - return $path; - } - - return false; + return load_textdomain( $domain, $mofile, $locale ); } /** @@ -1314,7 +1293,7 @@ function _get_path_to_translation_from_lang_dir( $domain ) { * * @since 2.8.0 * - * @global MO[] $l10n + * @global MO[] $l10n An array of all currently loaded text domains. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return Translations|NOOP_Translations A Translations instance. @@ -1338,7 +1317,7 @@ function get_translations_for_domain( $domain ) { * * @since 3.0.0 * - * @global MO[] $l10n + * @global MO[] $l10n An array of all currently loaded text domains. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return bool Whether there are translations. diff --git a/src/wp-settings.php b/src/wp-settings.php index ffe047b1ec..8fe16195e0 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -152,6 +152,7 @@ if ( SHORTINIT ) { // Load the L10n library. require_once ABSPATH . WPINC . '/l10n.php'; +require_once ABSPATH . WPINC . '/class-wp-textdomain-registry.php'; require_once ABSPATH . WPINC . '/class-wp-locale.php'; require_once ABSPATH . WPINC . '/class-wp-locale-switcher.php'; @@ -334,6 +335,17 @@ require ABSPATH . WPINC . '/block-supports/typography.php'; $GLOBALS['wp_embed'] = new WP_Embed(); +/** + * WordPress Textdomain Registry object. + * + * Used to support just-in-time translations for manually loaded text domains. + * + * @since 6.1.0 + * + * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry. + */ +$GLOBALS['wp_textdomain_registry'] = new WP_Textdomain_Registry(); + // Load multisite-specific files. if ( is_multisite() ) { require ABSPATH . WPINC . '/ms-functions.php'; diff --git a/tests/phpunit/data/languages/plugins/internationalized-plugin-de_DE.mo b/tests/phpunit/data/languages/plugins/internationalized-plugin-de_DE.mo index 96bf7ad89073f1322a9cd8b834e3325300c6b68f..08ee966a8ad7bde7472e9599d0a806f01d5fd757 100644 GIT binary patch delta 108 zcmeBRZD5_C!~2hsfgyu|fk6U@(vqGz}{h>@95(I+u4y)-dBRW~FxG1p3g%O|n8L^q@;F|Rl$u_V99 KN?~#clL-Lyr5+Xl delta 116 zcmZo*?O>gt!^_6Rz>vYfz#sv{`4cVOJVSJyi&7IyGV}9vT@p)DtrUz53{7k(qI_ HFOx9H>m>=5sGRNvx~(drmvpnAW_g$kjrBKw$I`oSl>vM53 z_y@c^y<7kQ literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/languages/plugins/internationalized-plugin-es_ES.po b/tests/phpunit/data/languages/plugins/internationalized-plugin-es_ES.po new file mode 100644 index 0000000000..788036ab12 --- /dev/null +++ b/tests/phpunit/data/languages/plugins/internationalized-plugin-es_ES.po @@ -0,0 +1,23 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2015-12-31 16:31+0100\n" +"PO-Revision-Date: 2020-10-20 17:12+0200\n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.4.1\n" +"X-Poedit-Basepath: .\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;" +"_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;" +"esc_html_x:1,2c\n" +"X-Textdomain-Support: yes\n" +"Language-Team: \n" +"Last-Translator: \n" +"X-Poedit-SearchPath-0: .\n" + +#: internationalized-plugin.php:11 +msgid "This is a dummy plugin" +msgstr "Este es un plugin dummy" diff --git a/tests/phpunit/data/languages/themes/internationalized-theme-de_DE.mo b/tests/phpunit/data/languages/themes/internationalized-theme-de_DE.mo index 5a95f01babf30f19e531c2ffe014891f1ccc6b20..a48788607db94f8a5b7833664ac3827baf364aba 100644 GIT binary patch delta 108 zcmZo;{l_vvhxZ>N14B9k1A{mar%kkURnQGeEz2y<%+J$xNi0dVQZO(PfVqSV_VtT4>NNQrP KmBQp;CSw3Bi5`gn delta 116 zcmeyz(#ASLhnJ0sfgzoNfk7OI^CnuldWPsa7o{eaWaj7Tx+IpQS}7PA7@F!D8tEDv zDj1qs8Cz%@7#bLG1^DX*rIuwDqo^~}H8jvQGE*=xure^3IIo4xP|rfo&|tGVBQxV< HA0{IJamXBE diff --git a/tests/phpunit/data/languages/themes/internationalized-theme-de_DE.po b/tests/phpunit/data/languages/themes/internationalized-theme-de_DE.po index 6d2391756f..03ed5de8c6 100644 --- a/tests/phpunit/data/languages/themes/internationalized-theme-de_DE.po +++ b/tests/phpunit/data/languages/themes/internationalized-theme-de_DE.po @@ -2,18 +2,20 @@ msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2015-12-31 16:38+0100\n" -"PO-Revision-Date: 2016-10-26 00:02+0200\n" +"PO-Revision-Date: 2020-10-20 17:09+0200\n" "Language: de_DE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 1.8.10\n" +"X-Generator: Poedit 2.4.1\n" "X-Poedit-Basepath: .\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;" "_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;" "esc_html_x:1,2c\n" "X-Textdomain-Support: yes\n" +"Last-Translator: \n" +"Language-Team: \n" "X-Poedit-SearchPath-0: .\n" #: functions.php:7 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 new file mode 100644 index 0000000000..41eb593556 --- /dev/null +++ b/tests/phpunit/data/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php @@ -0,0 +1,14 @@ +^7jgsAu=zTmYEO61C;gfvc55x*1h9Fm2c zFW$sy97mVRd9ksIT&ujX5^~dN(!XI{HM(szZTSdo2en19LDg~Uqn1!(nXjU&i>uXG zIhn=L2SXl-^X_^s?D59G?OUZOgQ`*1p6U30T4%a^MeuS7$?GioF3yqA9(pJY*4o3#EMfm3#Hn)FE8j?%jpj$( z{V}$7m)dy=Wto+jl{Ye(rWMm8p}u2T?8JKLdt>>UJ9J|QYgD#s3T{lqp|we_!W;2`njqgl K6P{vtCjS7~Cc@7E literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-de_DE.po b/tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-de_DE.po new file mode 100644 index 0000000000..e9e7cc28c1 --- /dev/null +++ b/tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-de_DE.po @@ -0,0 +1,23 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2015-12-31 16:31+0100\n" +"PO-Revision-Date: 2022-07-04 18:48+0200\n" +"Last-Translator: Dominik Schilling\n" +"Language-Team: \n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.1\n" +"X-Poedit-Basepath: .\n" +"X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;" +"_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;" +"esc_html_x:1,2c\n" +"X-Textdomain-Support: yes\n" +"X-Poedit-SearchPath-0: .\n" + +#: internationalized-plugin.php:11 +msgid "This is a dummy plugin" +msgstr "Das ist ein Dummy Plugin" diff --git a/tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-es_ES.mo b/tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-es_ES.mo new file mode 100644 index 0000000000000000000000000000000000000000..077ec03769b9d74b86c38b76bcda40aa5dd152e4 GIT binary patch literal 654 zcmYjP!H&}~5Df?}k+^Vx3y0wbqAp3c5|u+Q%XU{Qc1558af?hcG=^jx+0L^45PomJ zg-O$H80E?Dy-a32j_;m6`WtXOWV~R^7>^n68AD`@XN=E`C!AFC`kZmY`~k23$5124 zYwds^1y$d+JLsBzt0~B=`GutymzDU2)@ef$!t9gyf;W0l;!JszkVHu$qIV)%K)jTT z<(nvpqVP&NFLJBMHOd<+;mov}^l#XdwQd?sTTY>EueK<1R4tc2Y6<9y^(MT&yk1Y8 z<9QT*FyxWA?ylp;9&i2Iu2Gs&C~IXM`qkI`Tr8)&q5n^`Vojx~xTl2W5ADOO>8)zS zxv{O2Ks+x?w@T7sbf&9U1TR+*zfQyN;sOcnI74Qz(jHFcG5ezsr^;ccd@W%CluVC=1|uzKuV)K>MFknt9qoHDJ1J9iWud&cTx_7VZ<_<% zyEwTc-MfcQ_aBU!_nGtARi@>$6Pv#4jOA_a&`lF;P}#E1crXzSqXlc{k$(*Ibhsbm HWeNTPND0E! literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-es_ES.po b/tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-es_ES.po new file mode 100644 index 0000000000..5ee8db8df0 --- /dev/null +++ b/tests/phpunit/data/plugins/custom-internationalized-plugin/languages/custom-internationalized-plugin-es_ES.po @@ -0,0 +1,23 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2015-12-31 16:31+0100\n" +"PO-Revision-Date: 2022-07-04 18:48+0200\n" +"Last-Translator: Dominik Schilling\n" +"Language-Team: \n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.1\n" +"X-Poedit-Basepath: .\n" +"X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;" +"_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;" +"esc_html_x:1,2c\n" +"X-Textdomain-Support: yes\n" +"X-Poedit-SearchPath-0: .\n" + +#: internationalized-plugin.php:11 +msgid "This is a dummy plugin" +msgstr "Este es un plugin dummy" diff --git a/tests/phpunit/data/plugins/hello.php b/tests/phpunit/data/plugins/hello.php index 8d6287f77a..2f717ed699 100644 --- a/tests/phpunit/data/plugins/hello.php +++ b/tests/phpunit/data/plugins/hello.php @@ -4,11 +4,8 @@ Plugin Name: Hello Dolly Plugin URI: http://wordpress.org/# Description: This is not just a plugin, it symbolizes the hope and enthusiasm of an entire generation summed up in two words sung most famously by Louis Armstrong: Hello, Dolly. When activated you will randomly see a lyric from Hello, Dolly in the upper right of your admin screen on every page. Author: Matt Mullenweg -Version: 1.5.1 +Version: 1.7.2 Author URI: http://ma.tt/ Text Domain: hello-dolly */ - -// Test for -?> diff --git a/tests/phpunit/data/themedir1/custom-internationalized-theme/functions.php b/tests/phpunit/data/themedir1/custom-internationalized-theme/functions.php new file mode 100644 index 0000000000..272deed498 --- /dev/null +++ b/tests/phpunit/data/themedir1/custom-internationalized-theme/functions.php @@ -0,0 +1,10 @@ +DZjnibjZtDp_AKpwNPmwn z;lLzm8%BBZd(Roq*qT4jkM07Fr;L}38RHq_J)@6|am@J4`1>RXcD%k|RGh$a$m3;- zyw)E0Q&8>O_5f64i$Uh>FDyx1RN@;tudR^~W>?}1{?@$`Yegs_iIPM_ry^QFyp)U6 zw^0&B;Z}JPxl_hBimc5>#Pmj5m_g4Lyk!bwZTDb5irGn-qAv?YrlbZ~KUM>@Fol^zpD&3)$l tc9m`U?ZoxI>#XB859p^GT%&Sjo$dh(5G7S|;aDlR9HzHc^}0YT#ail1Tc?WRsI;b4ZfFBjLj}8NcWC1ulE0T< z(nAMuLPtFD-h(mZKimLffOF);cxBku5&3Y;n&IA9W(S(|h*|%JW@Sfha zwW@Rsd8w>JT76G1#d3PDFZUCzSnJ#rmEK8M-c{r$b!SyAE{$!R1lQ{ywIk#Y1D)o@H-{g?`v-qyyT1Sc literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/themedir1/custom-internationalized-theme/languages/es_ES.po b/tests/phpunit/data/themedir1/custom-internationalized-theme/languages/es_ES.po new file mode 100644 index 0000000000..954feb5f38 --- /dev/null +++ b/tests/phpunit/data/themedir1/custom-internationalized-theme/languages/es_ES.po @@ -0,0 +1,23 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2015-12-31 16:38+0100\n" +"PO-Revision-Date: 2022-07-04 18:47+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.1\n" +"X-Poedit-Basepath: .\n" +"X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;" +"_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;" +"esc_html_x:1,2c\n" +"X-Textdomain-Support: yes\n" +"X-Poedit-SearchPath-0: .\n" + +#: functions.php:7 +msgid "This is a dummy theme" +msgstr "Este es un tema dummy" diff --git a/tests/phpunit/data/themedir1/custom-internationalized-theme/style.css b/tests/phpunit/data/themedir1/custom-internationalized-theme/style.css new file mode 100644 index 0000000000..dec57eb4da --- /dev/null +++ b/tests/phpunit/data/themedir1/custom-internationalized-theme/style.css @@ -0,0 +1,7 @@ +/* +Theme Name: Custom Internationalized Theme +Theme URI: https://wordpress.org/ +Description: For testing purposes only. +Version: 1.0.0 +Text Domain: custom-internationalized-theme +*/ diff --git a/tests/phpunit/includes/bootstrap.php b/tests/phpunit/includes/bootstrap.php index a8a18a1ac3..3945f71002 100644 --- a/tests/phpunit/includes/bootstrap.php +++ b/tests/phpunit/includes/bootstrap.php @@ -215,7 +215,8 @@ define( 'WP_TESTS_TABLE_PREFIX', $table_prefix ); define( 'DIR_TESTDATA', __DIR__ . '/../data' ); define( 'DIR_TESTROOT', realpath( dirname( __DIR__ ) ) ); -define( 'WP_LANG_DIR', DIR_TESTDATA . '/languages' ); +define( 'WP_LANG_DIR', realpath( DIR_TESTDATA . '/languages' ) ); +define( 'WP_PLUGIN_DIR', realpath( DIR_TESTDATA . '/plugins' ) ); if ( ! defined( 'WP_TESTS_FORCE_KNOWN_BUGS' ) ) { define( 'WP_TESTS_FORCE_KNOWN_BUGS', false ); diff --git a/tests/phpunit/tests/admin/includesPlugin.php b/tests/phpunit/tests/admin/includesPlugin.php index 8dd009ddc3..0d29b81d02 100644 --- a/tests/phpunit/tests/admin/includesPlugin.php +++ b/tests/phpunit/tests/admin/includesPlugin.php @@ -22,7 +22,7 @@ class Tests_Admin_IncludesPlugin extends WP_UnitTestCase { 'Description' => 'This is not just a plugin, it symbolizes the hope and enthusiasm of an entire generation summed up in two words sung most famously by Louis Armstrong: Hello, Dolly. When activated you will randomly see a lyric from Hello, Dolly in the upper right of your admin screen on every page. By Matt Mullenweg.', 'Author' => 'Matt Mullenweg', 'AuthorURI' => 'http://ma.tt/', - 'Version' => '1.5.1', + 'Version' => '1.7.2', 'TextDomain' => 'hello-dolly', 'DomainPath' => '', ); diff --git a/tests/phpunit/tests/l10n/loadTextdomain.php b/tests/phpunit/tests/l10n/loadTextdomain.php index 93c4553a85..882cb9535f 100644 --- a/tests/phpunit/tests/l10n/loadTextdomain.php +++ b/tests/phpunit/tests/l10n/loadTextdomain.php @@ -24,6 +24,21 @@ class Tests_L10n_LoadTextdomain extends WP_UnitTestCase { add_filter( 'plugin_locale', array( $this, 'store_locale' ) ); add_filter( 'theme_locale', array( $this, 'store_locale' ) ); + + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_textdomain_registry; + + $wp_textdomain_registry->reset(); + } + + public function tear_down() { + + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_textdomain_registry; + + $wp_textdomain_registry->reset(); + + parent::tear_down(); } public function store_locale( $locale ) { diff --git a/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php b/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php index 9a8504df44..2d3c480a5e 100644 --- a/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php +++ b/tests/phpunit/tests/l10n/loadTextdomainJustInTime.php @@ -32,19 +32,23 @@ class Tests_L10n_LoadTextdomainJustInTime extends WP_UnitTestCase { add_filter( 'stylesheet_root', array( $this, 'filter_theme_root' ) ); add_filter( 'template_root', array( $this, 'filter_theme_root' ) ); wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - unset( $GLOBALS['l10n'] ); - unset( $GLOBALS['l10n_unloaded'] ); - _get_path_to_translation( null, true ); + unset( $GLOBALS['wp_themes'], $GLOBALS['l10n'], $GLOBALS['l10n_unloaded'] ); + + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_textdomain_registry; + + $wp_textdomain_registry->reset(); } public function tear_down() { $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - unset( $GLOBALS['l10n'] ); - unset( $GLOBALS['l10n_unloaded'] ); - _get_path_to_translation( null, true ); + unset( $GLOBALS['wp_themes'], $GLOBALS['l10n'], $GLOBALS['l10n_unloaded'] ); + + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_textdomain_registry; + + $wp_textdomain_registry->reset(); parent::tear_down(); } @@ -177,6 +181,7 @@ class Tests_L10n_LoadTextdomainJustInTime extends WP_UnitTestCase { /** * @ticket 37997 + * @ticket 39210 * * @covers ::switch_to_locale */ @@ -192,7 +197,7 @@ class Tests_L10n_LoadTextdomainJustInTime extends WP_UnitTestCase { restore_current_locale(); $this->assertSame( 'Das ist ein Dummy Plugin', $actual_de_de ); - $this->assertSame( 'This is a dummy plugin', $actual_es_es ); + $this->assertSame( 'Este es un plugin dummy', $actual_es_es ); } /** diff --git a/tests/phpunit/tests/l10n/wpLocaleSwitcher.php b/tests/phpunit/tests/l10n/wpLocaleSwitcher.php index a6dd2794b5..aab1bf9fc5 100644 --- a/tests/phpunit/tests/l10n/wpLocaleSwitcher.php +++ b/tests/phpunit/tests/l10n/wpLocaleSwitcher.php @@ -22,15 +22,21 @@ class Tests_L10n_wpLocaleSwitcher extends WP_UnitTestCase { $this->locale = ''; $this->previous_locale = ''; - unset( $GLOBALS['l10n'] ); - unset( $GLOBALS['l10n_unloaded'] ); - _get_path_to_translation( null, true ); + unset( $GLOBALS['l10n'], $GLOBALS['l10n_unloaded'] ); + + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_textdomain_registry; + + $wp_textdomain_registry->reset(); } public function tear_down() { - unset( $GLOBALS['l10n'] ); - unset( $GLOBALS['l10n_unloaded'] ); - _get_path_to_translation( null, true ); + unset( $GLOBALS['l10n'], $GLOBALS['l10n_unloaded'] ); + + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_textdomain_registry; + + $wp_textdomain_registry->reset(); parent::tear_down(); } @@ -458,6 +464,82 @@ class Tests_L10n_wpLocaleSwitcher extends WP_UnitTestCase { $this->assertSame( 'This is a dummy plugin', $actual ); } + /** + * @ticket 39210 + */ + public function test_switch_reloads_plugin_translations_outside_wp_lang_dir() { + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_locale_switcher, $wp_textdomain_registry; + + $locale_switcher = clone $wp_locale_switcher; + + $wp_locale_switcher = new WP_Locale_Switcher(); + $wp_locale_switcher->init(); + + require_once DIR_TESTDATA . '/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php'; + + $registry_value = $wp_textdomain_registry->get( 'custom-internationalized-plugin', determine_locale() ); + + $actual = custom_i18n_plugin_test(); + + switch_to_locale( 'es_ES' ); + switch_to_locale( 'de_DE' ); + + $actual_de_de = custom_i18n_plugin_test(); + + restore_previous_locale(); + + $actual_es_es = custom_i18n_plugin_test(); + + restore_current_locale(); + + $wp_locale_switcher = $locale_switcher; + + $this->assertSame( 'This is a dummy plugin', $actual ); + $this->assertSame( WP_PLUGIN_DIR . '/custom-internationalized-plugin/languages/', $registry_value ); + $this->assertSame( 'Das ist ein Dummy Plugin', $actual_de_de ); + $this->assertSame( 'Este es un plugin dummy', $actual_es_es ); + } + + /** + * @ticket 39210 + */ + public function test_switch_reloads_theme_translations_outside_wp_lang_dir() { + /** @var WP_Textdomain_Registry $wp_textdomain_registry */ + global $wp_locale_switcher, $wp_textdomain_registry; + + $locale_switcher = clone $wp_locale_switcher; + + $wp_locale_switcher = new WP_Locale_Switcher(); + $wp_locale_switcher->init(); + + switch_theme( 'custom-internationalized-theme' ); + + require_once get_stylesheet_directory() . '/functions.php'; + + $registry_value = $wp_textdomain_registry->get( 'custom-internationalized-theme', determine_locale() ); + + $actual = custom_i18n_theme_test(); + + switch_to_locale( 'es_ES' ); + switch_to_locale( 'de_DE' ); + + $actual_de_de = custom_i18n_theme_test(); + + restore_previous_locale(); + + $actual_es_es = custom_i18n_theme_test(); + + restore_current_locale(); + + $wp_locale_switcher = $locale_switcher; + + $this->assertSame( get_template_directory() . '/languages/', $registry_value ); + $this->assertSame( 'This is a dummy theme', $actual ); + $this->assertSame( 'Das ist ein Dummy Theme', $actual_de_de ); + $this->assertSame( 'Este es un tema dummy', $actual_es_es ); + } + public function filter_locale() { return 'es_ES'; } diff --git a/tests/phpunit/tests/theme/themeDir.php b/tests/phpunit/tests/theme/themeDir.php index edf7563815..365d225f11 100644 --- a/tests/phpunit/tests/theme/themeDir.php +++ b/tests/phpunit/tests/theme/themeDir.php @@ -159,6 +159,7 @@ class Tests_Theme_ThemeDir extends WP_UnitTestCase { 'Page Template Theme', // Theme with page templates for other test code. 'Theme with Spaces in the Directory', 'Internationalized Theme', + 'Custom Internationalized Theme', 'camelCase', 'REST Theme', 'Block Theme',