diff --git a/wp-admin/includes/class-language-pack-upgrader.php b/wp-admin/includes/class-language-pack-upgrader.php
index 855dbe642a..6f7cf742bd 100644
--- a/wp-admin/includes/class-language-pack-upgrader.php
+++ b/wp-admin/includes/class-language-pack-upgrader.php
@@ -332,26 +332,34 @@ class Language_Pack_Upgrader extends WP_Upgrader {
// Check that the folder contains a valid language.
$files = $wp_filesystem->dirlist( $remote_source );
- // Check to see if a .po and .mo exist in the folder.
- $po = false;
- $mo = false;
+ // Check to see if the expected files exist in the folder.
+ $po = false;
+ $mo = false;
+ $php = false;
foreach ( (array) $files as $file => $filedata ) {
if ( str_ends_with( $file, '.po' ) ) {
$po = true;
} elseif ( str_ends_with( $file, '.mo' ) ) {
$mo = true;
+ } elseif ( str_ends_with( $file, '.l10n.php' ) ) {
+ $php = true;
}
}
+ if ( $php ) {
+ return $source;
+ }
+
if ( ! $mo || ! $po ) {
return new WP_Error(
'incompatible_archive_pomo',
$this->strings['incompatible_archive'],
sprintf(
- /* translators: 1: .po, 2: .mo */
- __( 'The language pack is missing either the %1$s or %2$s files.' ),
+ /* translators: 1: .po, 2: .mo, 3: .l10n.php */
+ __( 'The language pack is missing either the %1$s, %2$s, or %3$s files.' ),
'.po
',
- '.mo
'
+ '.mo
',
+ '.l10n.php
'
)
);
}
diff --git a/wp-includes/l10n.php b/wp-includes/l10n.php
index 3b99ff848b..9e2a86a759 100644
--- a/wp-includes/l10n.php
+++ b/wp-includes/l10n.php
@@ -1505,25 +1505,25 @@ function get_available_languages( $dir = null ) {
*
* @since 3.7.0
*
+ * @global WP_Textdomain_Registry $wp_textdomain_registry WordPress Textdomain Registry.
+ *
* @param string $type What to search for. Accepts 'plugins', 'themes', 'core'.
* @return array Array of language data.
*/
function wp_get_installed_translations( $type ) {
+ global $wp_textdomain_registry;
+
if ( 'themes' !== $type && 'plugins' !== $type && 'core' !== $type ) {
return array();
}
- $dir = 'core' === $type ? '' : "/$type";
+ $dir = 'core' === $type ? WP_LANG_DIR : WP_LANG_DIR . "/$type";
- if ( ! is_dir( WP_LANG_DIR ) ) {
+ if ( ! is_dir( $dir ) ) {
return array();
}
- if ( $dir && ! is_dir( WP_LANG_DIR . $dir ) ) {
- return array();
- }
-
- $files = scandir( WP_LANG_DIR . $dir );
+ $files = $wp_textdomain_registry->get_language_files_from_path( $dir );
if ( ! $files ) {
return array();
}
@@ -1531,16 +1531,7 @@ function wp_get_installed_translations( $type ) {
$language_data = array();
foreach ( $files as $file ) {
- if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "$dir/$file" ) ) {
- continue;
- }
- if ( ! str_ends_with( $file, '.po' ) ) {
- continue;
- }
- if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) {
- continue;
- }
- if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files, true ) ) {
+ if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?)\.(?:mo|l10n\.php)/', basename( $file ), $match ) ) {
continue;
}
@@ -1548,7 +1539,25 @@ function wp_get_installed_translations( $type ) {
if ( '' === $textdomain ) {
$textdomain = 'default';
}
- $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "$dir/$file" );
+
+ if ( str_ends_with( $file, '.mo' ) ) {
+ $pofile = substr_replace( $file, '.po', - strlen( '.mo' ) );
+
+ if ( ! file_exists( $pofile ) ) {
+ continue;
+ }
+
+ $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( $pofile );
+ } else {
+ $pofile = substr_replace( $file, '.po', - strlen( '.l10n.php' ) );
+
+ // If both a PO and a PHP file exist, prefer the PO file.
+ if ( file_exists( $pofile ) ) {
+ continue;
+ }
+
+ $language_data[ $textdomain ][ $language ] = wp_get_l10n_php_file_data( $file );
+ }
}
return $language_data;
}
@@ -1578,6 +1587,41 @@ function wp_get_pomo_file_data( $po_file ) {
return $headers;
}
+/**
+ * Extracts headers from a PHP translation file.
+ *
+ * @since 6.6.0
+ *
+ * @param string $php_file Path to a `.l10n.php` file.
+ * @return string[] Array of file header values keyed by header name.
+ */
+function wp_get_l10n_php_file_data( $php_file ) {
+ $data = (array) include $php_file;
+
+ unset( $data['messages'] );
+ $headers = array(
+ 'POT-Creation-Date' => 'pot-creation-date',
+ 'PO-Revision-Date' => 'po-revision-date',
+ 'Project-Id-Version' => 'project-id-version',
+ 'X-Generator' => 'x-generator',
+ );
+
+ $result = array(
+ 'POT-Creation-Date' => '',
+ 'PO-Revision-Date' => '',
+ 'Project-Id-Version' => '',
+ 'X-Generator' => '',
+ );
+
+ foreach ( $headers as $po_header => $php_header ) {
+ if ( isset( $data[ $php_header ] ) ) {
+ $result[ $po_header ] = $data[ $php_header ];
+ }
+ }
+
+ return $result;
+}
+
/**
* Displays or returns a Language selector.
*
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 1dc961be77..09fb338a07 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '6.6-alpha-58061';
+$wp_version = '6.6-alpha-58062';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.