key(); if ( false === $key ) { return false; } $this->entries[ $key ] = &$entry; return true; } /** * @param array|Translation_Entry $entry * @return bool */ function add_entry_or_merge( $entry ) { if ( is_array( $entry ) ) { $entry = new Translation_Entry( $entry ); } $key = $entry->key(); if ( false === $key ) { return false; } if ( isset( $this->entries[ $key ] ) ) { $this->entries[ $key ]->merge_with( $entry ); } else { $this->entries[ $key ] = &$entry; } return true; } /** * Sets $header PO header to $value * * If the header already exists, it will be overwritten * * TODO: this should be out of this class, it is gettext specific * * @param string $header header name, without trailing : * @param string $value header value, without trailing \n */ function set_header( $header, $value ) { $this->headers[ $header ] = $value; } /** * @param array $headers */ function set_headers( $headers ) { foreach ( $headers as $header => $value ) { $this->set_header( $header, $value ); } } /** * @param string $header */ function get_header( $header ) { return isset( $this->headers[ $header ] ) ? $this->headers[ $header ] : false; } /** * @param Translation_Entry $entry */ function translate_entry( &$entry ) { $key = $entry->key(); return isset( $this->entries[ $key ] ) ? $this->entries[ $key ] : false; } /** * @param string $singular * @param string $context * @return string */ function translate( $singular, $context = null ) { $entry = new Translation_Entry( array( 'singular' => $singular, 'context' => $context, ) ); $translated = $this->translate_entry( $entry ); return ( $translated && ! empty( $translated->translations ) ) ? $translated->translations[0] : $singular; } /** * Given the number of items, returns the 0-based index of the plural form to use * * Here, in the base Translations class, the common logic for English is implemented: * 0 if there is one element, 1 otherwise * * This function should be overridden by the subclasses. For example MO/PO can derive the logic * from their headers. * * @param integer $count number of items */ function select_plural_form( $count ) { return 1 == $count ? 0 : 1; } /** * @return int */ function get_plural_forms_count() { return 2; } /** * @param string $singular * @param string $plural * @param int $count * @param string $context */ function translate_plural( $singular, $plural, $count, $context = null ) { $entry = new Translation_Entry( array( 'singular' => $singular, 'plural' => $plural, 'context' => $context, ) ); $translated = $this->translate_entry( $entry ); $index = $this->select_plural_form( $count ); $total_plural_forms = $this->get_plural_forms_count(); if ( $translated && 0 <= $index && $index < $total_plural_forms && is_array( $translated->translations ) && isset( $translated->translations[ $index ] ) ) { return $translated->translations[ $index ]; } else { return 1 == $count ? $singular : $plural; } } /** * Merge $other in the current object. * * @param Object $other Another Translation object, whose translations will be merged in this one (passed by reference). * @return void */ function merge_with( &$other ) { foreach ( $other->entries as $entry ) { $this->entries[ $entry->key() ] = $entry; } } /** * @param object $other */ function merge_originals_with( &$other ) { foreach ( $other->entries as $entry ) { if ( ! isset( $this->entries[ $entry->key() ] ) ) { $this->entries[ $entry->key() ] = $entry; } else { $this->entries[ $entry->key() ]->merge_with( $entry ); } } } } class Gettext_Translations extends Translations { /** * The gettext implementation of select_plural_form. * * It lives in this class, because there are more than one descendand, which will use it and * they can't share it effectively. * * @param int $count */ function gettext_select_plural_form( $count ) { if ( ! isset( $this->_gettext_select_plural_form ) || is_null( $this->_gettext_select_plural_form ) ) { list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header( $this->get_header( 'Plural-Forms' ) ); $this->_nplurals = $nplurals; $this->_gettext_select_plural_form = $this->make_plural_form_function( $nplurals, $expression ); } return call_user_func( $this->_gettext_select_plural_form, $count ); } /** * @param string $header * @return array */ function nplurals_and_expression_from_header( $header ) { if ( preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches ) ) { $nplurals = (int) $matches[1]; $expression = trim( $matches[2] ); return array( $nplurals, $expression ); } else { return array( 2, 'n != 1' ); } } /** * Makes a function, which will return the right translation index, according to the * plural forms header * * @param int $nplurals * @param string $expression */ function make_plural_form_function( $nplurals, $expression ) { 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( 2, 'n != 1' ); } } /** * Adds parentheses to the inner parts of ternary operators in * plural expressions, because PHP evaluates ternary oerators from left to right * * @param string $expression the expression without parentheses * @return string the expression with parentheses added */ function parenthesize_plural_exression( $expression ) { $expression .= ';'; $res = ''; $depth = 0; for ( $i = 0; $i < strlen( $expression ); ++$i ) { $char = $expression[ $i ]; switch ( $char ) { case '?': $res .= ' ? ('; $depth++; break; case ':': $res .= ') : ('; break; case ';': $res .= str_repeat( ')', $depth ) . ';'; $depth = 0; break; default: $res .= $char; } } return rtrim( $res, ';' ); } /** * @param string $translation * @return array */ function make_headers( $translation ) { $headers = array(); // Sometimes \n's are used instead of real new lines. $translation = str_replace( '\n', "\n", $translation ); $lines = explode( "\n", $translation ); foreach ( $lines as $line ) { $parts = explode( ':', $line, 2 ); if ( ! isset( $parts[1] ) ) { continue; } $headers[ trim( $parts[0] ) ] = trim( $parts[1] ); } return $headers; } /** * @param string $header * @param string $value */ function set_header( $header, $value ) { parent::set_header( $header, $value ); if ( 'Plural-Forms' === $header ) { list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header( $this->get_header( 'Plural-Forms' ) ); $this->_nplurals = $nplurals; $this->_gettext_select_plural_form = $this->make_plural_form_function( $nplurals, $expression ); } } } endif; if ( ! class_exists( 'NOOP_Translations', false ) ) : /** * Provides the same interface as Translations, but doesn't do anything */ class NOOP_Translations { var $entries = array(); var $headers = array(); function add_entry( $entry ) { return true; } /** * @param string $header * @param string $value */ function set_header( $header, $value ) { } /** * @param array $headers */ function set_headers( $headers ) { } /** * @param string $header * @return false */ function get_header( $header ) { return false; } /** * @param Translation_Entry $entry * @return false */ function translate_entry( &$entry ) { return false; } /** * @param string $singular * @param string $context */ function translate( $singular, $context = null ) { return $singular; } /** * @param int $count * @return bool */ function select_plural_form( $count ) { return 1 == $count ? 0 : 1; } /** * @return int */ function get_plural_forms_count() { return 2; } /** * @param string $singular * @param string $plural * @param int $count * @param string $context */ function translate_plural( $singular, $plural, $count, $context = null ) { return 1 == $count ? $singular : $plural; } /** * @param object $other */ function merge_with( &$other ) { } } endif;