wp/wp-includes/l10n/class-wp-translation-file.php
changeset 21 48c4eec2b7e6
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
       
     1 <?php
       
     2 /**
       
     3  * I18N: WP_Translation_File class.
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage I18N
       
     7  * @since 6.5.0
       
     8  */
       
     9 
       
    10 /**
       
    11  * Class WP_Translation_File.
       
    12  *
       
    13  * @since 6.5.0
       
    14  */
       
    15 abstract class WP_Translation_File {
       
    16 	/**
       
    17 	 * List of headers.
       
    18 	 *
       
    19 	 * @since 6.5.0
       
    20 	 * @var array<string, string>
       
    21 	 */
       
    22 	protected $headers = array();
       
    23 
       
    24 	/**
       
    25 	 * Whether file has been parsed.
       
    26 	 *
       
    27 	 * @since 6.5.0
       
    28 	 * @var bool
       
    29 	 */
       
    30 	protected $parsed = false;
       
    31 
       
    32 	/**
       
    33 	 * Error information.
       
    34 	 *
       
    35 	 * @since 6.5.0
       
    36 	 * @var string|null Error message or null if no error.
       
    37 	 */
       
    38 	protected $error;
       
    39 
       
    40 	/**
       
    41 	 * File name.
       
    42 	 *
       
    43 	 * @since 6.5.0
       
    44 	 * @var string
       
    45 	 */
       
    46 	protected $file = '';
       
    47 
       
    48 	/**
       
    49 	 * Translation entries.
       
    50 	 *
       
    51 	 * @since 6.5.0
       
    52 	 * @var array<string, string>
       
    53 	 */
       
    54 	protected $entries = array();
       
    55 
       
    56 	/**
       
    57 	 * Plural forms function.
       
    58 	 *
       
    59 	 * @since 6.5.0
       
    60 	 * @var callable|null Plural forms.
       
    61 	 */
       
    62 	protected $plural_forms = null;
       
    63 
       
    64 	/**
       
    65 	 * Constructor.
       
    66 	 *
       
    67 	 * @since 6.5.0
       
    68 	 *
       
    69 	 * @param string $file File to load.
       
    70 	 */
       
    71 	protected function __construct( string $file ) {
       
    72 		$this->file = $file;
       
    73 	}
       
    74 
       
    75 	/**
       
    76 	 * Creates a new WP_Translation_File instance for a given file.
       
    77 	 *
       
    78 	 * @since 6.5.0
       
    79 	 *
       
    80 	 * @param string      $file     File name.
       
    81 	 * @param string|null $filetype Optional. File type. Default inferred from file name.
       
    82 	 * @return false|WP_Translation_File
       
    83 	 */
       
    84 	public static function create( string $file, ?string $filetype = null ) {
       
    85 		if ( ! is_readable( $file ) ) {
       
    86 			return false;
       
    87 		}
       
    88 
       
    89 		if ( null === $filetype ) {
       
    90 			$pos = strrpos( $file, '.' );
       
    91 			if ( false !== $pos ) {
       
    92 				$filetype = substr( $file, $pos + 1 );
       
    93 			}
       
    94 		}
       
    95 
       
    96 		switch ( $filetype ) {
       
    97 			case 'mo':
       
    98 				return new WP_Translation_File_MO( $file );
       
    99 			case 'php':
       
   100 				return new WP_Translation_File_PHP( $file );
       
   101 			default:
       
   102 				return false;
       
   103 		}
       
   104 	}
       
   105 
       
   106 	/**
       
   107 	 * Creates a new WP_Translation_File instance for a given file.
       
   108 	 *
       
   109 	 * @since 6.5.0
       
   110 	 *
       
   111 	 * @param string $file     Source file name.
       
   112 	 * @param string $filetype Desired target file type.
       
   113 	 * @return string|false Transformed translation file contents on success, false otherwise.
       
   114 	 */
       
   115 	public static function transform( string $file, string $filetype ) {
       
   116 		$source = self::create( $file );
       
   117 
       
   118 		if ( false === $source ) {
       
   119 			return false;
       
   120 		}
       
   121 
       
   122 		switch ( $filetype ) {
       
   123 			case 'mo':
       
   124 				$destination = new WP_Translation_File_MO( '' );
       
   125 				break;
       
   126 			case 'php':
       
   127 				$destination = new WP_Translation_File_PHP( '' );
       
   128 				break;
       
   129 			default:
       
   130 				return false;
       
   131 		}
       
   132 
       
   133 		$success = $destination->import( $source );
       
   134 
       
   135 		if ( ! $success ) {
       
   136 			return false;
       
   137 		}
       
   138 
       
   139 		return $destination->export();
       
   140 	}
       
   141 
       
   142 	/**
       
   143 	 * Returns all headers.
       
   144 	 *
       
   145 	 * @since 6.5.0
       
   146 	 *
       
   147 	 * @return array<string, string> Headers.
       
   148 	 */
       
   149 	public function headers(): array {
       
   150 		if ( ! $this->parsed ) {
       
   151 			$this->parse_file();
       
   152 		}
       
   153 		return $this->headers;
       
   154 	}
       
   155 
       
   156 	/**
       
   157 	 * Returns all entries.
       
   158 	 *
       
   159 	 * @since 6.5.0
       
   160 	 *
       
   161 	 * @return array<string, string[]> Entries.
       
   162 	 */
       
   163 	public function entries(): array {
       
   164 		if ( ! $this->parsed ) {
       
   165 			$this->parse_file();
       
   166 		}
       
   167 
       
   168 		return $this->entries;
       
   169 	}
       
   170 
       
   171 	/**
       
   172 	 * Returns the current error information.
       
   173 	 *
       
   174 	 * @since 6.5.0
       
   175 	 *
       
   176 	 * @return string|null Error message or null if no error.
       
   177 	 */
       
   178 	public function error() {
       
   179 		return $this->error;
       
   180 	}
       
   181 
       
   182 	/**
       
   183 	 * Returns the file name.
       
   184 	 *
       
   185 	 * @since 6.5.0
       
   186 	 *
       
   187 	 * @return string File name.
       
   188 	 */
       
   189 	public function get_file(): string {
       
   190 		return $this->file;
       
   191 	}
       
   192 
       
   193 	/**
       
   194 	 * Translates a given string.
       
   195 	 *
       
   196 	 * @since 6.5.0
       
   197 	 *
       
   198 	 * @param string $text String to translate.
       
   199 	 * @return false|string Translation(s) on success, false otherwise.
       
   200 	 */
       
   201 	public function translate( string $text ) {
       
   202 		if ( ! $this->parsed ) {
       
   203 			$this->parse_file();
       
   204 		}
       
   205 
       
   206 		return $this->entries[ $text ] ?? false;
       
   207 	}
       
   208 
       
   209 	/**
       
   210 	 * Returns the plural form for a given number.
       
   211 	 *
       
   212 	 * @since 6.5.0
       
   213 	 *
       
   214 	 * @param int $number Count.
       
   215 	 * @return int Plural form.
       
   216 	 */
       
   217 	public function get_plural_form( int $number ): int {
       
   218 		if ( ! $this->parsed ) {
       
   219 			$this->parse_file();
       
   220 		}
       
   221 
       
   222 		if ( null === $this->plural_forms && isset( $this->headers['plural-forms'] ) ) {
       
   223 			$expression         = $this->get_plural_expression_from_header( $this->headers['plural-forms'] );
       
   224 			$this->plural_forms = $this->make_plural_form_function( $expression );
       
   225 		}
       
   226 
       
   227 		if ( is_callable( $this->plural_forms ) ) {
       
   228 			/**
       
   229 			 * Plural form.
       
   230 			 *
       
   231 			 * @var int $result Plural form.
       
   232 			 */
       
   233 			$result = call_user_func( $this->plural_forms, $number );
       
   234 
       
   235 			return $result;
       
   236 		}
       
   237 
       
   238 		// Default plural form matches English, only "One" is considered singular.
       
   239 		return ( 1 === $number ? 0 : 1 );
       
   240 	}
       
   241 
       
   242 	/**
       
   243 	 * Returns the plural forms expression as a tuple.
       
   244 	 *
       
   245 	 * @since 6.5.0
       
   246 	 *
       
   247 	 * @param string $header Plural-Forms header string.
       
   248 	 * @return string Plural forms expression.
       
   249 	 */
       
   250 	protected function get_plural_expression_from_header( string $header ): string {
       
   251 		if ( preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches ) ) {
       
   252 			return trim( $matches[2] );
       
   253 		}
       
   254 
       
   255 		return 'n != 1';
       
   256 	}
       
   257 
       
   258 	/**
       
   259 	 * Makes a function, which will return the right translation index, according to the
       
   260 	 * plural forms header.
       
   261 	 *
       
   262 	 * @since 6.5.0
       
   263 	 *
       
   264 	 * @param string $expression Plural form expression.
       
   265 	 * @return callable(int $num): int Plural forms function.
       
   266 	 */
       
   267 	protected function make_plural_form_function( string $expression ): callable {
       
   268 		try {
       
   269 			$handler = new Plural_Forms( rtrim( $expression, ';' ) );
       
   270 			return array( $handler, 'get' );
       
   271 		} catch ( Exception $e ) {
       
   272 			// Fall back to default plural-form function.
       
   273 			return $this->make_plural_form_function( 'n != 1' );
       
   274 		}
       
   275 	}
       
   276 
       
   277 	/**
       
   278 	 * Imports translations from another file.
       
   279 	 *
       
   280 	 * @since 6.5.0
       
   281 	 *
       
   282 	 * @param WP_Translation_File $source Source file.
       
   283 	 * @return bool True on success, false otherwise.
       
   284 	 */
       
   285 	protected function import( WP_Translation_File $source ): bool {
       
   286 		if ( null !== $source->error() ) {
       
   287 			return false;
       
   288 		}
       
   289 
       
   290 		$this->headers = $source->headers();
       
   291 		$this->entries = $source->entries();
       
   292 		$this->error   = $source->error();
       
   293 
       
   294 		return null === $this->error;
       
   295 	}
       
   296 
       
   297 	/**
       
   298 	 * Parses the file.
       
   299 	 *
       
   300 	 * @since 6.5.0
       
   301 	 */
       
   302 	abstract protected function parse_file();
       
   303 
       
   304 	/**
       
   305 	 * Exports translation contents as a string.
       
   306 	 *
       
   307 	 * @since 6.5.0
       
   308 	 *
       
   309 	 * @return string Translation file contents.
       
   310 	 */
       
   311 	abstract public function export();
       
   312 }