words" => "8O\x00:)\x00:(\x00:?\x00", * "small_mappings" => array( "😯", "🙂", "🙁", "😕" ) * ) * ); * * @since 6.6.0 * * @param string $indent Optional. Use this string for indentation, or rely on the default horizontal tab character. Default "\t". * @return string Value which can be pasted into a PHP source file for quick loading of table. */ public function precomputed_php_source_table( string $indent = "\t" ): string { $i1 = $indent; $i2 = $i1 . $indent; $i3 = $i2 . $indent; $class_version = self::STORAGE_VERSION; $output = self::class . "::from_precomputed_table(\n"; $output .= "{$i1}array(\n"; $output .= "{$i2}\"storage_version\" => \"{$class_version}\",\n"; $output .= "{$i2}\"key_length\" => {$this->key_length},\n"; $group_line = str_replace( "\x00", "\\x00", $this->groups ); $output .= "{$i2}\"groups\" => \"{$group_line}\",\n"; $output .= "{$i2}\"large_words\" => array(\n"; $prefixes = explode( "\x00", $this->groups ); foreach ( $prefixes as $index => $prefix ) { if ( '' === $prefix ) { break; } $group = $this->large_words[ $index ]; $group_length = strlen( $group ); $comment_line = "{$i3}//"; $data_line = "{$i3}\""; $at = 0; while ( $at < $group_length ) { $token_length = unpack( 'C', $group[ $at++ ] )[1]; $token = substr( $group, $at, $token_length ); $at += $token_length; $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; $mapping = substr( $group, $at, $mapping_length ); $at += $mapping_length; $token_digits = str_pad( dechex( $token_length ), 2, '0', STR_PAD_LEFT ); $mapping_digits = str_pad( dechex( $mapping_length ), 2, '0', STR_PAD_LEFT ); $mapping = preg_replace_callback( "~[\\x00-\\x1f\\x22\\x5c]~", static function ( $match_result ) { switch ( $match_result[0] ) { case '"': return '\\"'; case '\\': return '\\\\'; default: $hex = dechex( ord( $match_result[0] ) ); return "\\x{$hex}"; } }, $mapping ); $comment_line .= " {$prefix}{$token}[{$mapping}]"; $data_line .= "\\x{$token_digits}{$token}\\x{$mapping_digits}{$mapping}"; } $comment_line .= ".\n"; $data_line .= "\",\n"; $output .= $comment_line; $output .= $data_line; } $output .= "{$i2}),\n"; $small_words = array(); $small_length = strlen( $this->small_words ); $at = 0; while ( $at < $small_length ) { $small_words[] = substr( $this->small_words, $at, $this->key_length + 1 ); $at += $this->key_length + 1; } $small_text = str_replace( "\x00", '\x00', implode( '', $small_words ) ); $output .= "{$i2}\"small_words\" => \"{$small_text}\",\n"; $output .= "{$i2}\"small_mappings\" => array(\n"; foreach ( $this->small_mappings as $mapping ) { $output .= "{$i3}\"{$mapping}\",\n"; } $output .= "{$i2})\n"; $output .= "{$i1})\n"; $output .= ')'; return $output; } /** * Compares two strings, returning the longest, or whichever * is first alphabetically if they are the same length. * * This is an important sort when building the token map because * it should not form a match on a substring of a longer potential * match. For example, it should not detect `Cap` when matching * against the string `CapitalDifferentialD`. * * @since 6.6.0 * * @param string $a First string to compare. * @param string $b Second string to compare. * @return int -1 or lower if `$a` is less than `$b`; 1 or greater if `$a` is greater than `$b`, and 0 if they are equal. */ private static function longest_first_then_alphabetical( string $a, string $b ): int { if ( $a === $b ) { return 0; } $length_a = strlen( $a ); $length_b = strlen( $b ); // Longer strings are less-than for comparison's sake. if ( $length_a !== $length_b ) { return $length_b - $length_a; } return strcmp( $a, $b ); } } sion_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(); }