Make WordPress Core

Changeset 55010


Ignore:
Timestamp:
12/20/2022 03:10:35 PM (4 years ago)
Author:
swissspidy
Message:

I18N: Change how WP_Textdomain_Registry caches translation information.

WP_Textdomain_Registry was introduced in [53874] and later adjusted in [54682] to store text domains and their language directory paths, addressing issues with just-in-time loading of textdomains when using locale switching and load_*_textdomain() functions.

This change improves how the class stores information about all existing MO files on the site, addressing an issue where translations are not loaded after calling switch_to_locale().

Props johnbillion, ocean90, SergeyBiryukov.
Fixes #57116.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-locale-switcher.php

    r54318 r55010  
    3737         * @var string[] An array of language codes (file names without the .mo extension).
    3838         */
    39         private $available_languages = array();
     39        private $available_languages;
    4040
    4141        /**
  • trunk/src/wp-includes/class-wp-textdomain-registry.php

    r54669 r55010  
    5252         * @var array
    5353         */
    54         protected $cached_mo_files;
     54        protected $cached_mo_files = array();
     55
     56        /**
     57         * Holds a cached list of domains with translations to improve performance.
     58         *
     59         * @since 6.1.2
     60         *
     61         * @var string[]
     62         */
     63        protected $domains_with_translations = array();
    5564
    5665        /**
     
    8594         */
    8695        public function has( $domain ) {
    87                 return ! empty( $this->current[ $domain ] ) || empty( $this->all[ $domain ] );
     96                return (
     97                        ! empty( $this->current[ $domain ] ) ||
     98                        empty( $this->all[ $domain ] ) ||
     99                        in_array( $domain, $this->domains_with_translations, true )
     100                );
    88101        }
    89102
     
    110123         * Used by {@see load_plugin_textdomain()} and {@see load_theme_textdomain()}.
    111124         *
     125         * @since 6.1.0
     126         *
    112127         * @param string $domain Text domain.
    113128         * @param string $path   Language directory path.
     
    118133
    119134        /**
    120          * Gets the path to the language directory for the current locale.
    121          *
    122          * Checks the plugins and themes language directories as well as any
    123          * custom directory set via {@see load_plugin_textdomain()} or {@see load_theme_textdomain()}.
    124          *
    125          * @since 6.1.0
    126          *
    127          * @see _get_path_to_translation_from_lang_dir()
    128          *
    129          * @param string $domain Text domain.
    130          * @param string $locale Locale.
    131          * @return string|false Language directory path or false if there is none available.
    132          */
    133         private function get_path_from_lang_dir( $domain, $locale ) {
     135         * Returns possible language directory paths for a given text domain.
     136         *
     137         * @since 6.1.2
     138         *
     139         * @param string $domain Text domain.
     140         * @return string[] Array of language directory paths.
     141         */
     142        private function get_paths_for_domain( $domain ) {
    134143                $locations = array(
    135144                        WP_LANG_DIR . '/plugins',
     
    141150                }
    142151
    143                 $mofile = "$domain-$locale.mo";
     152                return $locations;
     153        }
     154
     155        /**
     156         * Gets the path to the language directory for the current locale.
     157         *
     158         * Checks the plugins and themes language directories as well as any
     159         * custom directory set via {@see load_plugin_textdomain()} or {@see load_theme_textdomain()}.
     160         *
     161         * @since 6.1.0
     162         *
     163         * @see _get_path_to_translation_from_lang_dir()
     164         *
     165         * @param string $domain Text domain.
     166         * @param string $locale Locale.
     167         * @return string|false Language directory path or false if there is none available.
     168         */
     169        private function get_path_from_lang_dir( $domain, $locale ) {
     170                $locations = $this->get_paths_for_domain( $domain );
     171
     172                $found_location = false;
    144173
    145174                foreach ( $locations as $location ) {
     
    148177                        }
    149178
    150                         $path = $location . '/' . $mofile;
    151 
    152                         if ( in_array( $path, $this->cached_mo_files[ $location ], true ) ) {
    153                                 $this->set( $domain, $locale, $location );
    154 
    155                                 return trailingslashit( $location );
     179                        $path = "$location/$domain-$locale.mo";
     180
     181                        foreach ( $this->cached_mo_files[ $location ] as $mo_path ) {
     182                                if (
     183                                        ! in_array( $domain, $this->domains_with_translations, true ) &&
     184                                        str_starts_with( str_replace( "$location/", '', $mo_path ), "$domain-" )
     185                                ) {
     186                                        $this->domains_with_translations[] = $domain;
     187                                }
     188
     189                                if ( $mo_path === $path ) {
     190                                        $found_location = trailingslashit( $location );
     191                                }
    156192                        }
     193                }
     194
     195                if ( $found_location ) {
     196                        $this->set( $domain, $locale, $found_location );
     197
     198                        return $found_location;
    157199                }
    158200
     
    160202                // using load_plugin_textdomain/load_theme_textdomain, use that one.
    161203                if ( 'en_US' !== $locale && isset( $this->custom_paths[ $domain ] ) ) {
    162                         $path = trailingslashit( $this->custom_paths[ $domain ] );
    163                         $this->set( $domain, $locale, $path );
    164                         return $path;
     204                        $fallback_location = trailingslashit( $this->custom_paths[ $domain ] );
     205                        $this->set( $domain, $locale, $fallback_location );
     206                        return $fallback_location;
    165207                }
    166208
  • trunk/tests/phpunit/tests/l10n/wpLocaleSwitcher.php

    r54669 r55010  
    2525                unset( $GLOBALS['l10n'], $GLOBALS['l10n_unloaded'] );
    2626
    27                 /** @var WP_Textdomain_Registry $wp_textdomain_registry */
    28                 global $wp_textdomain_registry;
     27                global $wp_textdomain_registry, $wp_locale_switcher;
    2928
    3029                $wp_textdomain_registry = new WP_Textdomain_Registry();
     30
     31                remove_filter( 'locale', array( $wp_locale_switcher, 'filter_locale' ) );
     32                $wp_locale_switcher = new WP_Locale_Switcher();
     33                $wp_locale_switcher->init();
    3134        }
    3235
     
    3437                unset( $GLOBALS['l10n'], $GLOBALS['l10n_unloaded'] );
    3538
    36                 /** @var WP_Textdomain_Registry $wp_textdomain_registry */
    37                 global $wp_textdomain_registry;
     39                global $wp_textdomain_registry, $wp_locale_switcher;
    3840
    3941                $wp_textdomain_registry = new WP_Textdomain_Registry();
     42
     43                remove_filter( 'locale', array( $wp_locale_switcher, 'filter_locale' ) );
     44                $wp_locale_switcher = new WP_Locale_Switcher();
     45                $wp_locale_switcher->init();
    4046
    4147                parent::tear_down();
     
    337343                set_current_screen( 'dashboard' );
    338344
    339                 $locale_switcher = clone $wp_locale_switcher;
    340 
     345                // Reset $wp_locale_switcher so it thinks es_ES is the original locale.
     346                remove_filter( 'locale', array( $wp_locale_switcher, 'filter_locale' ) );
    341347                $wp_locale_switcher = new WP_Locale_Switcher();
    342348                $wp_locale_switcher->init();
     
    357363
    358364                $language_header_after_restore = $l10n['default']->headers['Language']; // de_DE
    359 
    360                 $wp_locale_switcher = $locale_switcher;
    361365
    362366                $this->assertFalse( $locale_switched_user_locale );
     
    389393                set_current_screen( 'dashboard' );
    390394
    391                 $locale_switcher = clone $wp_locale_switcher;
    392 
     395                // Reset $wp_locale_switcher so it thinks es_ES is the original locale.
     396                remove_filter( 'locale', array( $wp_locale_switcher, 'filter_locale' ) );
    393397                $wp_locale_switcher = new WP_Locale_Switcher();
    394398                $wp_locale_switcher->init();
     
    409413
    410414                $language_header_after_restore = $l10n['default']->headers['Language']; // de_DE
    411 
    412                 $wp_locale_switcher = $locale_switcher;
    413415
    414416                remove_filter( 'locale', array( $this, 'filter_locale' ) );
     
    427429         */
    428430        public function test_multiple_switches_to_site_locale_and_user_locale() {
    429                 global $wp_locale_switcher;
    430 
    431431                $site_locale = get_locale();
    432432
     
    441441                set_current_screen( 'dashboard' );
    442442
    443                 $locale_switcher = clone $wp_locale_switcher;
    444 
    445                 $wp_locale_switcher = new WP_Locale_Switcher();
    446                 $wp_locale_switcher->init();
    447 
    448443                $user_locale = get_user_locale();
    449444
     
    458453
    459454                restore_current_locale();
    460 
    461                 $wp_locale_switcher = $locale_switcher;
    462455
    463456                $this->assertSame( 'en_US', get_locale() );
     
    470463        public function test_switch_reloads_plugin_translations_outside_wp_lang_dir() {
    471464                /** @var WP_Textdomain_Registry $wp_textdomain_registry */
    472                 global $wp_locale_switcher, $wp_textdomain_registry;
    473 
    474                 $locale_switcher = clone $wp_locale_switcher;
    475 
    476                 $wp_locale_switcher = new WP_Locale_Switcher();
    477                 $wp_locale_switcher->init();
     465                global $wp_textdomain_registry;
    478466
    479467                require_once DIR_TESTDATA . '/plugins/custom-internationalized-plugin/custom-internationalized-plugin.php';
     
    494482
    495483                restore_current_locale();
    496 
    497                 $wp_locale_switcher = $locale_switcher;
    498484
    499485                $this->assertSame( 'This is a dummy plugin', $actual );
     
    504490
    505491        /**
     492         * @ticket 57116
     493         */
     494        public function test_switch_reloads_plugin_translations() {
     495                /** @var WP_Textdomain_Registry $wp_textdomain_registry */
     496                global $wp_textdomain_registry;
     497
     498                $has_translations_1 = $wp_textdomain_registry->has( 'internationalized-plugin' );
     499
     500                require_once DIR_TESTDATA . '/plugins/internationalized-plugin.php';
     501
     502                $actual = i18n_plugin_test();
     503
     504                switch_to_locale( 'es_ES' );
     505
     506                $lang_path_es_es = $wp_textdomain_registry->get( 'internationalized-plugin', determine_locale() );
     507
     508                switch_to_locale( 'de_DE' );
     509
     510                $actual_de_de = i18n_plugin_test();
     511
     512                $has_translations_3 = $wp_textdomain_registry->has( 'internationalized-plugin' );
     513
     514                restore_previous_locale();
     515
     516                $actual_es_es = i18n_plugin_test();
     517
     518                restore_current_locale();
     519
     520                $lang_path_en_us = $wp_textdomain_registry->get( 'internationalized-plugin', determine_locale() );
     521
     522                $this->assertSame( 'This is a dummy plugin', $actual );
     523                $this->assertSame( 'Das ist ein Dummy Plugin', $actual_de_de );
     524                $this->assertSame( 'Este es un plugin dummy', $actual_es_es );
     525                $this->assertTrue( $has_translations_1 );
     526                $this->assertTrue( $has_translations_3 );
     527                $this->assertSame( WP_LANG_DIR . '/plugins/', $lang_path_es_es );
     528                $this->assertFalse( $lang_path_en_us );
     529        }
     530
     531        /**
    506532         * @ticket 39210
    507533         */
    508534        public function test_switch_reloads_theme_translations_outside_wp_lang_dir() {
    509535                /** @var WP_Textdomain_Registry $wp_textdomain_registry */
    510                 global $wp_locale_switcher, $wp_textdomain_registry;
    511 
    512                 $locale_switcher = clone $wp_locale_switcher;
    513 
    514                 $wp_locale_switcher = new WP_Locale_Switcher();
    515                 $wp_locale_switcher->init();
     536                global $wp_textdomain_registry;
    516537
    517538                switch_theme( 'custom-internationalized-theme' );
     
    534555
    535556                restore_current_locale();
    536 
    537                 $wp_locale_switcher = $locale_switcher;
    538557
    539558                $this->assertSame( get_template_directory() . '/languages/', $registry_value );
     
    543562        }
    544563
     564        /**
     565         * @ticket 57116
     566         */
     567        public function test_switch_to_locale_should_work() {
     568                global $wp_textdomain_registry;
     569                require_once DIR_TESTDATA . '/plugins/internationalized-plugin.php';
     570
     571                $has_translations = $wp_textdomain_registry->has( 'internationalized-plugin' );
     572                $path             = $wp_textdomain_registry->get( 'internationalized-plugin', 'es_ES' );
     573
     574                $actual = i18n_plugin_test();
     575
     576                switch_to_locale( 'es_ES' );
     577
     578                $actual_es_es = i18n_plugin_test();
     579
     580                $this->assertTrue( $has_translations );
     581                $this->assertNotEmpty( $path );
     582                $this->assertSame( 'This is a dummy plugin', $actual );
     583                $this->assertSame( 'Este es un plugin dummy', $actual_es_es );
     584        }
     585
    545586        public function filter_locale() {
    546587                return 'es_ES';
  • trunk/tests/phpunit/tests/l10n/wpTextdomainRegistry.php

    r54669 r55010  
    2929                $reflection_property->setAccessible( true );
    3030
    31                 $this->assertNull(
     31                $this->assertEmpty(
    3232                        $reflection_property->getValue( $this->instance ),
    3333                        'Cache not empty by default'
     
    7676                        $reflection_property->getValue( $this->instance ),
    7777                        'Default plugins path missing from cache'
    78                 );
    79         }
    80 
    81         /**
    82          * @covers ::get_path_from_lang_dir
    83          */
    84         public function test_get_does_not_check_themes_directory_for_plugin() {
    85                 $reflection          = new ReflectionClass( $this->instance );
    86                 $reflection_property = $reflection->getProperty( 'cached_mo_files' );
    87                 $reflection_property->setAccessible( true );
    88 
    89                 $this->instance->get( 'internationalized-plugin', 'de_DE' );
    90 
    91                 $this->assertArrayHasKey(
    92                         WP_LANG_DIR . '/plugins',
    93                         $reflection_property->getValue( $this->instance ),
    94                         'Default plugins path missing from cache'
    95                 );
    96                 $this->assertArrayNotHasKey(
    97                         WP_LANG_DIR . '/themes',
    98                         $reflection_property->getValue( $this->instance ),
    99                         'Default themes path should not be in cache'
    10078                );
    10179        }
Note: See TracChangeset for help on using the changeset viewer.