wp/wp-includes/l10n/class-wp-translation-file.php
changeset 21 48c4eec2b7e6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wp/wp-includes/l10n/class-wp-translation-file.php	Fri Sep 05 18:40:08 2025 +0200
@@ -0,0 +1,312 @@
+<?php
+/**
+ * I18N: WP_Translation_File class.
+ *
+ * @package WordPress
+ * @subpackage I18N
+ * @since 6.5.0
+ */
+
+/**
+ * Class WP_Translation_File.
+ *
+ * @since 6.5.0
+ */
+abstract class WP_Translation_File {
+	/**
+	 * List of headers.
+	 *
+	 * @since 6.5.0
+	 * @var array<string, string>
+	 */
+	protected $headers = array();
+
+	/**
+	 * Whether file has been parsed.
+	 *
+	 * @since 6.5.0
+	 * @var bool
+	 */
+	protected $parsed = false;
+
+	/**
+	 * Error information.
+	 *
+	 * @since 6.5.0
+	 * @var string|null Error message or null if no error.
+	 */
+	protected $error;
+
+	/**
+	 * File name.
+	 *
+	 * @since 6.5.0
+	 * @var string
+	 */
+	protected $file = '';
+
+	/**
+	 * Translation entries.
+	 *
+	 * @since 6.5.0
+	 * @var array<string, string>
+	 */
+	protected $entries = array();
+
+	/**
+	 * Plural forms function.
+	 *
+	 * @since 6.5.0
+	 * @var callable|null Plural forms.
+	 */
+	protected $plural_forms = null;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @param string $file File to load.
+	 */
+	protected function __construct( string $file ) {
+		$this->file = $file;
+	}
+
+	/**
+	 * Creates a new WP_Translation_File instance for a given file.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @param string      $file     File name.
+	 * @param string|null $filetype Optional. File type. Default inferred from file name.
+	 * @return false|WP_Translation_File
+	 */
+	public static function create( string $file, ?string $filetype = null ) {
+		if ( ! is_readable( $file ) ) {
+			return false;
+		}
+
+		if ( null === $filetype ) {
+			$pos = strrpos( $file, '.' );
+			if ( false !== $pos ) {
+				$filetype = substr( $file, $pos + 1 );
+			}
+		}
+
+		switch ( $filetype ) {
+			case 'mo':
+				return new WP_Translation_File_MO( $file );
+			case 'php':
+				return new WP_Translation_File_PHP( $file );
+			default:
+				return false;
+		}
+	}
+
+	/**
+	 * Creates a new WP_Translation_File instance for a given file.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @param string $file     Source file name.
+	 * @param string $filetype Desired target file type.
+	 * @return string|false Transformed translation file contents on success, false otherwise.
+	 */
+	public static function transform( string $file, string $filetype ) {
+		$source = self::create( $file );
+
+		if ( false === $source ) {
+			return false;
+		}
+
+		switch ( $filetype ) {
+			case 'mo':
+				$destination = new WP_Translation_File_MO( '' );
+				break;
+			case 'php':
+				$destination = new WP_Translation_File_PHP( '' );
+				break;
+			default:
+				return false;
+		}
+
+		$success = $destination->import( $source );
+
+		if ( ! $success ) {
+			return false;
+		}
+
+		return $destination->export();
+	}
+
+	/**
+	 * Returns all headers.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @return array<string, string> Headers.
+	 */
+	public function headers(): array {
+		if ( ! $this->parsed ) {
+			$this->parse_file();
+		}
+		return $this->headers;
+	}
+
+	/**
+	 * Returns all entries.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @return array<string, string[]> Entries.
+	 */
+	public function entries(): array {
+		if ( ! $this->parsed ) {
+			$this->parse_file();
+		}
+
+		return $this->entries;
+	}
+
+	/**
+	 * Returns the current error information.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @return string|null Error message or null if no error.
+	 */
+	public function error() {
+		return $this->error;
+	}
+
+	/**
+	 * Returns the file name.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @return string File name.
+	 */
+	public function get_file(): string {
+		return $this->file;
+	}
+
+	/**
+	 * Translates a given string.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @param string $text String to translate.
+	 * @return false|string Translation(s) on success, false otherwise.
+	 */
+	public function translate( string $text ) {
+		if ( ! $this->parsed ) {
+			$this->parse_file();
+		}
+
+		return $this->entries[ $text ] ?? false;
+	}
+
+	/**
+	 * Returns the plural form for a given number.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @param int $number Count.
+	 * @return int Plural form.
+	 */
+	public function get_plural_form( int $number ): int {
+		if ( ! $this->parsed ) {
+			$this->parse_file();
+		}
+
+		if ( null === $this->plural_forms && isset( $this->headers['plural-forms'] ) ) {
+			$expression         = $this->get_plural_expression_from_header( $this->headers['plural-forms'] );
+			$this->plural_forms = $this->make_plural_form_function( $expression );
+		}
+
+		if ( is_callable( $this->plural_forms ) ) {
+			/**
+			 * Plural form.
+			 *
+			 * @var int $result Plural form.
+			 */
+			$result = call_user_func( $this->plural_forms, $number );
+
+			return $result;
+		}
+
+		// Default plural form matches English, only "One" is considered singular.
+		return ( 1 === $number ? 0 : 1 );
+	}
+
+	/**
+	 * Returns the plural forms expression as a tuple.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @param string $header Plural-Forms header string.
+	 * @return string Plural forms expression.
+	 */
+	protected function get_plural_expression_from_header( string $header ): string {
+		if ( preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches ) ) {
+			return trim( $matches[2] );
+		}
+
+		return 'n != 1';
+	}
+
+	/**
+	 * Makes a function, which will return the right translation index, according to the
+	 * plural forms header.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @param string $expression Plural form expression.
+	 * @return callable(int $num): int Plural forms function.
+	 */
+	protected function make_plural_form_function( string $expression ): callable {
+		try {
+			$handler = new Plural_Forms( rtrim( $expression, ';' ) );
+			return array( $handler, 'get' );
+		} catch ( Exception $e ) {
+			// Fall back to default plural-form function.
+			return $this->make_plural_form_function( 'n != 1' );
+		}
+	}
+
+	/**
+	 * Imports translations from another file.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @param WP_Translation_File $source Source file.
+	 * @return bool True on success, false otherwise.
+	 */
+	protected function import( WP_Translation_File $source ): bool {
+		if ( null !== $source->error() ) {
+			return false;
+		}
+
+		$this->headers = $source->headers();
+		$this->entries = $source->entries();
+		$this->error   = $source->error();
+
+		return null === $this->error;
+	}
+
+	/**
+	 * Parses the file.
+	 *
+	 * @since 6.5.0
+	 */
+	abstract protected function parse_file();
+
+	/**
+	 * Exports translation contents as a string.
+	 *
+	 * @since 6.5.0
+	 *
+	 * @return string Translation file contents.
+	 */
+	abstract public function export();
+}