Make WordPress Core


Ignore:
Timestamp:
08/11/2022 12:37:05 PM (22 months ago)
Author:
swissspidy
Message:

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.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/l10n.php

    r53702 r53874  
    705705 *
    706706 * @since 1.5.0
    707  *
    708  * @global MO[] $l10n          An array of all currently loaded text domains.
    709  * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again.
     707 * @since 6.1.0 Added the `$locale` parameter.
     708 *
     709 * @global MO[]                   $l10n                   An array of all currently loaded text domains.
     710 * @global MO[]                   $l10n_unloaded          An array of all text domains that have been unloaded again.
     711 * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry.
    710712 *
    711713 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
    712714 * @param string $mofile Path to the .mo file.
     715 * @param string $locale Optional. Locale. Default is the current locale.
    713716 * @return bool True on success, false on failure.
    714717 */
    715 function load_textdomain( $domain, $mofile ) {
    716     global $l10n, $l10n_unloaded;
     718function load_textdomain( $domain, $mofile, $locale = null ) {
     719    /** @var WP_Textdomain_Registry $wp_textdomain_registry */
     720    global $l10n, $l10n_unloaded, $wp_textdomain_registry;
    717721
    718722    $l10n_unloaded = (array) $l10n_unloaded;
     
    759763    }
    760764
     765    if ( ! $locale ) {
     766        $locale = determine_locale();
     767    }
     768
    761769    $mo = new MO();
    762770    if ( ! $mo->import_from_file( $mofile ) ) {
     771        $wp_textdomain_registry->set( $domain, $locale, false );
     772
    763773        return false;
    764774    }
     
    772782    $l10n[ $domain ] = &$mo;
    773783
     784    $wp_textdomain_registry->set( $domain, $locale, dirname( $mofile ) );
     785
    774786    return true;
    775787}
     
    779791 *
    780792 * @since 3.0.0
     793 * @since 6.1.0 Added the `$reloadable` parameter.
    781794 *
    782795 * @global MO[] $l10n          An array of all currently loaded text domains.
    783796 * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again.
    784797 *
    785  * @param string $domain Text domain. Unique identifier for retrieving translated strings.
     798 * @param string $domain     Text domain. Unique identifier for retrieving translated strings.
     799 * @param bool   $reloadable Whether the text domain can be loaded just-in-time again.
    786800 * @return bool Whether textdomain was unloaded.
    787801 */
    788 function unload_textdomain( $domain ) {
     802function unload_textdomain( $domain, $reloadable = false ) {
    789803    global $l10n, $l10n_unloaded;
    790804
     
    795809     *
    796810     * @since 3.0.0
    797      *
    798      * @param bool   $override Whether to override the text domain unloading. Default false.
    799      * @param string $domain   Text domain. Unique identifier for retrieving translated strings.
    800      */
    801     $plugin_override = apply_filters( 'override_unload_textdomain', false, $domain );
     811     * @since 6.1.0 Added the `$reloadable` parameter.
     812     *
     813     * @param bool   $override   Whether to override the text domain unloading. Default false.
     814     * @param string $domain     Text domain. Unique identifier for retrieving translated strings.
     815     * @param bool   $reloadable Whether the text domain can be loaded just-in-time again.
     816     */
     817    $plugin_override = apply_filters( 'override_unload_textdomain', false, $domain, $reloadable );
    802818
    803819    if ( $plugin_override ) {
    804         $l10n_unloaded[ $domain ] = true;
     820        if ( ! $reloadable ) {
     821            $l10n_unloaded[ $domain ] = true;
     822        }
    805823
    806824        return true;
     
    811829     *
    812830     * @since 3.0.0
    813      *
    814      * @param string $domain Text domain. Unique identifier for retrieving translated strings.
    815      */
    816     do_action( 'unload_textdomain', $domain );
     831     * @since 6.1.0 Added the `$reloadable` parameter.
     832     *
     833     * @param string $domain     Text domain. Unique identifier for retrieving translated strings.
     834     * @param bool   $reloadable Whether the text domain can be loaded just-in-time again.
     835     */
     836    do_action( 'unload_textdomain', $domain, $reloadable );
    817837
    818838    if ( isset( $l10n[ $domain ] ) ) {
    819839        unset( $l10n[ $domain ] );
    820840
    821         $l10n_unloaded[ $domain ] = true;
     841        if ( ! $reloadable ) {
     842            $l10n_unloaded[ $domain ] = true;
     843        }
    822844
    823845        return true;
     
    848870    unload_textdomain( 'default' );
    849871
    850     $return = load_textdomain( 'default', WP_LANG_DIR . "/$locale.mo" );
     872    $return = load_textdomain( 'default', WP_LANG_DIR . "/$locale.mo", $locale );
    851873
    852874    if ( ( is_multisite() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) ) && ! file_exists( WP_LANG_DIR . "/admin-$locale.mo" ) ) {
    853         load_textdomain( 'default', WP_LANG_DIR . "/ms-$locale.mo" );
     875        load_textdomain( 'default', WP_LANG_DIR . "/ms-$locale.mo", $locale );
    854876        return $return;
    855877    }
    856878
    857879    if ( is_admin() || wp_installing() || ( defined( 'WP_REPAIRING' ) && WP_REPAIRING ) ) {
    858         load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo" );
     880        load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo", $locale );
    859881    }
    860882
    861883    if ( is_network_admin() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) ) {
    862         load_textdomain( 'default', WP_LANG_DIR . "/admin-network-$locale.mo" );
     884        load_textdomain( 'default', WP_LANG_DIR . "/admin-network-$locale.mo", $locale );
    863885    }
    864886
     
    884906 */
    885907function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) {
     908    /** @var WP_Textdomain_Registry $wp_textdomain_registry */
     909    global $wp_textdomain_registry;
     910
    886911    /**
    887912     * Filters a plugin's locale.
     
    897922
    898923    // Try to load from the languages directory first.
    899     if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) {
     924    if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile, $locale ) ) {
    900925        return true;
    901926    }
     
    910935    }
    911936
    912     return load_textdomain( $domain, $path . '/' . $mofile );
     937    $wp_textdomain_registry->set( $domain, $locale, $path );
     938
     939    return load_textdomain( $domain, $path . '/' . $mofile, $locale );
    913940}
    914941
     
    918945 * @since 3.0.0
    919946 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first.
     947 *
     948 * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry.
    920949 *
    921950 * @param string $domain             Text domain. Unique identifier for retrieving translated strings.
     
    925954 */
    926955function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) {
     956    /** @var WP_Textdomain_Registry $wp_textdomain_registry */
     957    global $wp_textdomain_registry;
     958
    927959    /** This filter is documented in wp-includes/l10n.php */
    928960    $locale = apply_filters( 'plugin_locale', determine_locale(), $domain );
     
    931963
    932964    // Try to load from the languages directory first.
    933     if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) {
     965    if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile, $locale ) ) {
    934966        return true;
    935967    }
     
    937969    $path = WPMU_PLUGIN_DIR . '/' . ltrim( $mu_plugin_rel_path, '/' );
    938970
    939     return load_textdomain( $domain, $path . '/' . $mofile );
     971    $wp_textdomain_registry->set( $domain, $locale, $path );
     972
     973    return load_textdomain( $domain, $path . '/' . $mofile, $locale );
    940974}
    941975
     
    950984 * @since 1.5.0
    951985 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first.
     986 *
     987 * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry.
    952988 *
    953989 * @param string       $domain Text domain. Unique identifier for retrieving translated strings.
     
    957993 */
    958994function load_theme_textdomain( $domain, $path = false ) {
     995    /** @var WP_Textdomain_Registry $wp_textdomain_registry */
     996    global $wp_textdomain_registry;
     997
    959998    /**
    960999     * Filters a theme's locale.
     
    9701009
    9711010    // Try to load from the languages directory first.
    972     if ( load_textdomain( $domain, WP_LANG_DIR . '/themes/' . $mofile ) ) {
     1011    if ( load_textdomain( $domain, WP_LANG_DIR . '/themes/' . $mofile, $locale ) ) {
    9731012        return true;
    9741013    }
     
    9781017    }
    9791018
    980     return load_textdomain( $domain, $path . '/' . $locale . '.mo' );
     1019    $wp_textdomain_registry->set( $domain, $locale, $path );
     1020
     1021    return load_textdomain( $domain, $path . '/' . $locale . '.mo', $locale );
    9811022}
    9821023
     
    11991240
    12001241/**
    1201  * Loads plugin and theme textdomains just-in-time.
     1242 * Loads plugin and theme text domains just-in-time.
    12021243 *
    12031244 * When a textdomain is encountered for the first time, we try to load
     
    12081249 * @access private
    12091250 *
    1210  * @see get_translations_for_domain()
    1211  * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again.
     1251 * @global MO[]                   $l10n_unloaded          An array of all text domains that have been unloaded again.
     1252 * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry.
    12121253 *
    12131254 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
     
    12151256 */
    12161257function _load_textdomain_just_in_time( $domain ) {
    1217     global $l10n_unloaded;
     1258    /** @var WP_Textdomain_Registry $wp_textdomain_registry */
     1259    global $l10n_unloaded, $wp_textdomain_registry;
    12181260
    12191261    $l10n_unloaded = (array) $l10n_unloaded;
     
    12241266    }
    12251267
    1226     $translation_path = _get_path_to_translation( $domain );
    1227     if ( false === $translation_path ) {
     1268    if ( $wp_textdomain_registry->has( $domain ) && ! $wp_textdomain_registry->get_current( $domain ) ) {
    12281269        return false;
    12291270    }
    12301271
    1231     return load_textdomain( $domain, $translation_path );
    1232 }
    1233 
    1234 /**
    1235  * Gets the path to a translation file for loading a textdomain just in time.
    1236  *
    1237  * Caches the retrieved results internally.
    1238  *
    1239  * @since 4.7.0
    1240  * @access private
    1241  *
    1242  * @see _load_textdomain_just_in_time()
    1243  *
    1244  * @param string $domain Text domain. Unique identifier for retrieving translated strings.
    1245  * @param bool   $reset  Whether to reset the internal cache. Used by the switch to locale functionality.
    1246  * @return string|false The path to the translation file or false if no translation file was found.
    1247  */
    1248 function _get_path_to_translation( $domain, $reset = false ) {
    1249     static $available_translations = array();
    1250 
    1251     if ( true === $reset ) {
    1252         $available_translations = array();
    1253     }
    1254 
    1255     if ( ! isset( $available_translations[ $domain ] ) ) {
    1256         $available_translations[ $domain ] = _get_path_to_translation_from_lang_dir( $domain );
    1257     }
    1258 
    1259     return $available_translations[ $domain ];
    1260 }
    1261 
    1262 /**
    1263  * Gets the path to a translation file in the languages directory for the current locale.
    1264  *
    1265  * Holds a cached list of available .mo files to improve performance.
    1266  *
    1267  * @since 4.7.0
    1268  * @access private
    1269  *
    1270  * @see _get_path_to_translation()
    1271  *
    1272  * @param string $domain Text domain. Unique identifier for retrieving translated strings.
    1273  * @return string|false The path to the translation file or false if no translation file was found.
    1274  */
    1275 function _get_path_to_translation_from_lang_dir( $domain ) {
    1276     static $cached_mofiles = null;
    1277 
    1278     if ( null === $cached_mofiles ) {
    1279         $cached_mofiles = array();
    1280 
    1281         $locations = array(
    1282             WP_LANG_DIR . '/plugins',
    1283             WP_LANG_DIR . '/themes',
    1284         );
    1285 
    1286         foreach ( $locations as $location ) {
    1287             $mofiles = glob( $location . '/*.mo' );
    1288             if ( $mofiles ) {
    1289                 $cached_mofiles = array_merge( $cached_mofiles, $mofiles );
    1290             }
    1291         }
    1292     }
    1293 
    12941272    $locale = determine_locale();
    1295     $mofile = "{$domain}-{$locale}.mo";
    1296 
    1297     $path = WP_LANG_DIR . '/plugins/' . $mofile;
    1298     if ( in_array( $path, $cached_mofiles, true ) ) {
    1299         return $path;
    1300     }
    1301 
    1302     $path = WP_LANG_DIR . '/themes/' . $mofile;
    1303     if ( in_array( $path, $cached_mofiles, true ) ) {
    1304         return $path;
    1305     }
    1306 
    1307     return false;
     1273    $path = $wp_textdomain_registry->get( $domain, $locale );
     1274    if ( ! $path ) {
     1275        return false;
     1276    }
     1277    // Themes with their language directory outside of WP_LANG_DIR have a different file name.
     1278    $template_directory   = trailingslashit( get_template_directory() );
     1279    $stylesheet_directory = trailingslashit( get_stylesheet_directory() );
     1280    if ( str_starts_with( $path, $template_directory ) || str_starts_with( $path, $stylesheet_directory ) ) {
     1281        $mofile = "{$path}{$locale}.mo";
     1282    } else {
     1283        $mofile = "{$path}{$domain}-{$locale}.mo";
     1284    }
     1285
     1286    return load_textdomain( $domain, $mofile, $locale );
    13081287}
    13091288
     
    13151294 * @since 2.8.0
    13161295 *
    1317  * @global MO[] $l10n
     1296 * @global MO[] $l10n An array of all currently loaded text domains.
    13181297 *
    13191298 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
     
    13391318 * @since 3.0.0
    13401319 *
    1341  * @global MO[] $l10n
     1320 * @global MO[] $l10n An array of all currently loaded text domains.
    13421321 *
    13431322 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
Note: See TracChangeset for help on using the changeset viewer.