I18N: Support loading .l10n.php translation files on their own.

Adjusts the translation file lookup in `WP_Textdomain_Registry` so that just-in-time translation loading
works even if there is only a `.l10n.php` translation file without a corresponding `.mo` file.

While language packs continue to contain both file types, this makes it easier to use translations in a project
without having to deal with `.mo` or `.po` files.

Props Chrystl.
See #59656.

git-svn-id: https://develop.svn.wordpress.org/trunk@57516 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Pascal Birchler
2024-02-01 19:03:55 +00:00
parent 67e9ffb9fd
commit 8d3e4a1eeb
7 changed files with 87 additions and 36 deletions

View File

@@ -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, '/' ) . '/';
}
}

View File

@@ -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;
}
/**

View File

@@ -0,0 +1,2 @@
<?php
return ['domain'=>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'];

View File

@@ -0,0 +1,2 @@
<?php
return ['domain'=>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'];

View File

@@ -0,0 +1,12 @@
<?php
/*
Plugin Name: Dummy Plugin 2
Plugin URI: https://wordpress.org/
Description: For testing purposes only. Only has an .l10n.php translation file.
Version: 1.0.0
Text Domain: internationalized-plugin
*/
function i18n_plugin_2_test() {
return __( 'This is a dummy plugin', 'internationalized-plugin-2' );
}

View File

@@ -49,6 +49,7 @@ class Tests_L10n_LoadTextdomainJustInTime extends WP_UnitTestCase {
$wp_textdomain_registry = new WP_Textdomain_Registry();
unload_textdomain( 'internationalized-plugin' );
unload_textdomain( 'internationalized-plugin-2' );
unload_textdomain( 'internationalized-theme' );
parent::tear_down();
@@ -86,6 +87,27 @@ class Tests_L10n_LoadTextdomainJustInTime extends WP_UnitTestCase {
$this->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
*

View File

@@ -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/',
),
);
}
}