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.