diff --git a/src/wp-includes/class-wp-textdomain-registry.php b/src/wp-includes/class-wp-textdomain-registry.php index b66b5b18a6..b2cbd72fa5 100644 --- a/src/wp-includes/class-wp-textdomain-registry.php +++ b/src/wp-includes/class-wp-textdomain-registry.php @@ -150,21 +150,21 @@ class WP_Textdomain_Registry { } /** - * Retrieves .mo files from the specified path. + * Retrieves translation files from the specified path. * * Allows early retrieval through the {@see 'pre_get_mo_files_from_path'} filter to optimize * performance, especially in directories with many files. * * @since 6.5.0 * - * @param string $path The directory path to search for .mo files. - * @return array Array of .mo file paths. + * @param string $path The directory path to search for translation files. + * @return array Array of translation file paths. Can contain .mo and .l10n.php files. */ public function get_language_files_from_path( $path ) { $path = rtrim( $path, '/' ) . '/'; /** - * Filters the .mo files retrieved from a specified path before the actual lookup. + * Filters the translation files retrieved from a specified path before the actual lookup. * * Returning a non-null value from the filter will effectively short-circuit * the MO files lookup, returning that value instead. @@ -174,27 +174,33 @@ class WP_Textdomain_Registry { * * @since 6.5.0 * - * @param null|array $mo_files List of .mo files. Default null. - * @param string $path The path from which .mo files are being fetched. + * @param null|array $files List of translation files. Default null. + * @param string $path The path from which translation files are being fetched. **/ - $mo_files = apply_filters( 'pre_get_language_files_from_path', null, $path ); + $files = apply_filters( 'pre_get_language_files_from_path', null, $path ); - if ( null !== $mo_files ) { - return $mo_files; + if ( null !== $files ) { + return $files; } $cache_key = 'cached_mo_files_' . md5( $path ); - $mo_files = wp_cache_get( $cache_key, 'translations' ); + $files = wp_cache_get( $cache_key, 'translations' ); - if ( false === $mo_files ) { - $mo_files = glob( $path . '*.mo' ); - if ( false === $mo_files ) { - $mo_files = array(); + if ( false === $files ) { + $files = glob( $path . '*.mo' ); + if ( false === $files ) { + $files = array(); } - wp_cache_set( $cache_key, $mo_files, 'translations' ); + + $php_files = glob( $path . '*.l10n.php' ); + if ( is_array( $php_files ) ) { + $files = array_merge( $files, $php_files ); + } + + wp_cache_set( $cache_key, $files, 'translations' ); } - return $mo_files; + return $files; } /** @@ -295,17 +301,18 @@ class WP_Textdomain_Registry { foreach ( $locations as $location ) { $files = $this->get_language_files_from_path( $location ); - $path = "$location/$domain-$locale.mo"; + $mo_path = "$location/$domain-$locale.mo"; + $php_path = "$location/$domain-$locale.l10n.php"; - foreach ( $files as $mo_path ) { + foreach ( $files as $file_path ) { if ( ! in_array( $domain, $this->domains_with_translations, true ) && - str_starts_with( str_replace( "$location/", '', $mo_path ), "$domain-" ) + str_starts_with( str_replace( "$location/", '', $file_path ), "$domain-" ) ) { $this->domains_with_translations[] = $domain; } - if ( $mo_path === $path ) { + if ( $file_path === $mo_path || $file_path === $php_path ) { $found_location = rtrim( $location, '/' ) . '/'; } } diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 228c40757c..8191b88cee 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -789,10 +789,6 @@ function load_textdomain( $domain, $mofile, $locale = null ) { */ $mofile = apply_filters( 'load_textdomain_mofile', $mofile, $domain ); - if ( ! is_readable( $mofile ) ) { - return false; - } - if ( ! $locale ) { $locale = determine_locale(); } @@ -817,14 +813,14 @@ function load_textdomain( $domain, $mofile, $locale = null ) { $preferred_format = 'php'; } - $translation_files = array( $mofile ); + $translation_files = array(); + if ( 'mo' !== $preferred_format ) { - array_unshift( - $translation_files, - substr_replace( $mofile, ".l10n.$preferred_format", - strlen( '.mo' ) ) - ); + $translation_files[] = substr_replace( $mofile, ".l10n.$preferred_format", - strlen( '.mo' ) ); } + $translation_files[] = $mofile; + foreach ( $translation_files as $file ) { /** * Filters the file path for loading translations for the given text domain. @@ -857,7 +853,7 @@ function load_textdomain( $domain, $mofile, $locale = null ) { } } - return true; + return false; } /** diff --git a/tests/phpunit/data/languages/plugins/internationalized-plugin-2-de_DE.l10n.php b/tests/phpunit/data/languages/plugins/internationalized-plugin-2-de_DE.l10n.php new file mode 100644 index 0000000000..4002587e9e --- /dev/null +++ b/tests/phpunit/data/languages/plugins/internationalized-plugin-2-de_DE.l10n.php @@ -0,0 +1,2 @@ +NULL,'plural-forms'=>'nplurals=2; plural=(n != 1);','messages'=>['This is a dummy plugin'=>'Das ist ein Dummy Plugin'],'language'=>'de_DE','x-generator'=>'Poedit 2.4.1']; \ No newline at end of file diff --git a/tests/phpunit/data/languages/plugins/internationalized-plugin-2-es_ES.l10n.php b/tests/phpunit/data/languages/plugins/internationalized-plugin-2-es_ES.l10n.php new file mode 100644 index 0000000000..c38b5058f6 --- /dev/null +++ b/tests/phpunit/data/languages/plugins/internationalized-plugin-2-es_ES.l10n.php @@ -0,0 +1,2 @@ +NULL,'plural-forms'=>'nplurals=2; plural=(n != 1);','messages'=>['This is a dummy plugin'=>'Este es un plugin dummy'],'language'=>'de_DE','x-generator'=>'Poedit 2.4.1']; \ No newline at end of file diff --git a/tests/phpunit/data/plugins/internationalized-plugin-2.php b/tests/phpunit/data/plugins/internationalized-plugin-2.php new file mode 100644 index 0000000000..5e75cf6b7f --- /dev/null +++ b/tests/phpunit/data/plugins/internationalized-plugin-2.php @@ -0,0 +1,12 @@ +assertTrue( $is_textdomain_loaded_after ); } + /** + * @ticket 59656 + * + * @covers ::is_textdomain_loaded + */ + public function test_plugin_translation_should_be_translated_with_only_an_l10n_php_file() { + add_filter( 'locale', array( $this, 'filter_set_locale_to_german' ) ); + + require_once DIR_TESTDATA . '/plugins/internationalized-plugin-2.php'; + + $is_textdomain_loaded_before = is_textdomain_loaded( 'internationalized-plugin-2' ); + $actual_output = i18n_plugin_2_test(); + $is_textdomain_loaded_after = is_textdomain_loaded( 'internationalized-plugin-2' ); + + remove_filter( 'locale', array( $this, 'filter_set_locale_to_german' ) ); + + $this->assertFalse( $is_textdomain_loaded_before ); + $this->assertSame( 'Das ist ein Dummy Plugin', $actual_output ); + $this->assertTrue( $is_textdomain_loaded_after ); + } + /** * @ticket 34114 * diff --git a/tests/phpunit/tests/l10n/wpTextdomainRegistry.php b/tests/phpunit/tests/l10n/wpTextdomainRegistry.php index 19a239428d..dd9688e947 100644 --- a/tests/phpunit/tests/l10n/wpTextdomainRegistry.php +++ b/tests/phpunit/tests/l10n/wpTextdomainRegistry.php @@ -151,36 +151,46 @@ class Tests_L10n_wpTextdomainRegistry extends WP_UnitTestCase { public function data_domains_locales() { return array( - 'Non-existent plugin' => array( + 'Non-existent plugin' => array( 'unknown-plugin', 'en_US', false, ), - 'Non-existent plugin with de_DE' => array( + 'Non-existent plugin with de_DE' => array( 'unknown-plugin', 'de_DE', false, ), - 'Available de_DE translations' => array( + 'Available de_DE translations' => array( 'internationalized-plugin', 'de_DE', WP_LANG_DIR . '/plugins/', ), - 'Available es_ES translations' => array( + 'Available es_ES translations' => array( 'internationalized-plugin', 'es_ES', WP_LANG_DIR . '/plugins/', ), - 'Unavailable fr_FR translations' => array( + 'Unavailable fr_FR translations' => array( 'internationalized-plugin', 'fr_FR', false, ), - 'Unavailable en_US translations' => array( + 'Unavailable en_US translations' => array( 'internationalized-plugin', 'en_US', false, ), + 'Available de_DE translations (.l10n.php)' => array( + 'internationalized-plugin-2', + 'de_DE', + WP_LANG_DIR . '/plugins/', + ), + 'Available es_ES translations (.l10n.php)' => array( + 'internationalized-plugin-2', + 'es_ES', + WP_LANG_DIR . '/plugins/', + ), ); } }