angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

translations.php (9600B)


      1 <?php
      2 /**
      3  * Class for a set of entries for translation and their associated headers
      4  *
      5  * @version $Id: translations.php 1157 2015-11-20 04:30:11Z dd32 $
      6  * @package pomo
      7  * @subpackage translations
      8  */
      9 
     10 require_once __DIR__ . '/plural-forms.php';
     11 require_once __DIR__ . '/entry.php';
     12 
     13 if ( ! class_exists( 'Translations', false ) ) :
     14 	class Translations {
     15 		public $entries = array();
     16 		public $headers = array();
     17 
     18 		/**
     19 		 * Add entry to the PO structure
     20 		 *
     21 		 * @param array|Translation_Entry $entry
     22 		 * @return bool true on success, false if the entry doesn't have a key
     23 		 */
     24 		function add_entry( $entry ) {
     25 			if ( is_array( $entry ) ) {
     26 				$entry = new Translation_Entry( $entry );
     27 			}
     28 			$key = $entry->key();
     29 			if ( false === $key ) {
     30 				return false;
     31 			}
     32 			$this->entries[ $key ] = &$entry;
     33 			return true;
     34 		}
     35 
     36 		/**
     37 		 * @param array|Translation_Entry $entry
     38 		 * @return bool
     39 		 */
     40 		function add_entry_or_merge( $entry ) {
     41 			if ( is_array( $entry ) ) {
     42 				$entry = new Translation_Entry( $entry );
     43 			}
     44 			$key = $entry->key();
     45 			if ( false === $key ) {
     46 				return false;
     47 			}
     48 			if ( isset( $this->entries[ $key ] ) ) {
     49 				$this->entries[ $key ]->merge_with( $entry );
     50 			} else {
     51 				$this->entries[ $key ] = &$entry;
     52 			}
     53 			return true;
     54 		}
     55 
     56 		/**
     57 		 * Sets $header PO header to $value
     58 		 *
     59 		 * If the header already exists, it will be overwritten
     60 		 *
     61 		 * TODO: this should be out of this class, it is gettext specific
     62 		 *
     63 		 * @param string $header header name, without trailing :
     64 		 * @param string $value header value, without trailing \n
     65 		 */
     66 		function set_header( $header, $value ) {
     67 			$this->headers[ $header ] = $value;
     68 		}
     69 
     70 		/**
     71 		 * @param array $headers
     72 		 */
     73 		function set_headers( $headers ) {
     74 			foreach ( $headers as $header => $value ) {
     75 				$this->set_header( $header, $value );
     76 			}
     77 		}
     78 
     79 		/**
     80 		 * @param string $header
     81 		 */
     82 		function get_header( $header ) {
     83 			return isset( $this->headers[ $header ] ) ? $this->headers[ $header ] : false;
     84 		}
     85 
     86 		/**
     87 		 * @param Translation_Entry $entry
     88 		 */
     89 		function translate_entry( &$entry ) {
     90 			$key = $entry->key();
     91 			return isset( $this->entries[ $key ] ) ? $this->entries[ $key ] : false;
     92 		}
     93 
     94 		/**
     95 		 * @param string $singular
     96 		 * @param string $context
     97 		 * @return string
     98 		 */
     99 		function translate( $singular, $context = null ) {
    100 			$entry      = new Translation_Entry(
    101 				array(
    102 					'singular' => $singular,
    103 					'context'  => $context,
    104 				)
    105 			);
    106 			$translated = $this->translate_entry( $entry );
    107 			return ( $translated && ! empty( $translated->translations ) ) ? $translated->translations[0] : $singular;
    108 		}
    109 
    110 		/**
    111 		 * Given the number of items, returns the 0-based index of the plural form to use
    112 		 *
    113 		 * Here, in the base Translations class, the common logic for English is implemented:
    114 		 *  0 if there is one element, 1 otherwise
    115 		 *
    116 		 * This function should be overridden by the subclasses. For example MO/PO can derive the logic
    117 		 * from their headers.
    118 		 *
    119 		 * @param int $count number of items
    120 		 */
    121 		function select_plural_form( $count ) {
    122 			return 1 == $count ? 0 : 1;
    123 		}
    124 
    125 		/**
    126 		 * @return int
    127 		 */
    128 		function get_plural_forms_count() {
    129 			return 2;
    130 		}
    131 
    132 		/**
    133 		 * @param string $singular
    134 		 * @param string $plural
    135 		 * @param int    $count
    136 		 * @param string $context
    137 		 */
    138 		function translate_plural( $singular, $plural, $count, $context = null ) {
    139 			$entry              = new Translation_Entry(
    140 				array(
    141 					'singular' => $singular,
    142 					'plural'   => $plural,
    143 					'context'  => $context,
    144 				)
    145 			);
    146 			$translated         = $this->translate_entry( $entry );
    147 			$index              = $this->select_plural_form( $count );
    148 			$total_plural_forms = $this->get_plural_forms_count();
    149 			if ( $translated && 0 <= $index && $index < $total_plural_forms &&
    150 				is_array( $translated->translations ) &&
    151 				isset( $translated->translations[ $index ] ) ) {
    152 				return $translated->translations[ $index ];
    153 			} else {
    154 				return 1 == $count ? $singular : $plural;
    155 			}
    156 		}
    157 
    158 		/**
    159 		 * Merge $other in the current object.
    160 		 *
    161 		 * @param Object $other Another Translation object, whose translations will be merged in this one (passed by reference).
    162 		 * @return void
    163 		 */
    164 		function merge_with( &$other ) {
    165 			foreach ( $other->entries as $entry ) {
    166 				$this->entries[ $entry->key() ] = $entry;
    167 			}
    168 		}
    169 
    170 		/**
    171 		 * @param object $other
    172 		 */
    173 		function merge_originals_with( &$other ) {
    174 			foreach ( $other->entries as $entry ) {
    175 				if ( ! isset( $this->entries[ $entry->key() ] ) ) {
    176 					$this->entries[ $entry->key() ] = $entry;
    177 				} else {
    178 					$this->entries[ $entry->key() ]->merge_with( $entry );
    179 				}
    180 			}
    181 		}
    182 	}
    183 
    184 	class Gettext_Translations extends Translations {
    185 		/**
    186 		 * The gettext implementation of select_plural_form.
    187 		 *
    188 		 * It lives in this class, because there are more than one descendand, which will use it and
    189 		 * they can't share it effectively.
    190 		 *
    191 		 * @param int $count
    192 		 */
    193 		function gettext_select_plural_form( $count ) {
    194 			if ( ! isset( $this->_gettext_select_plural_form ) || is_null( $this->_gettext_select_plural_form ) ) {
    195 				list( $nplurals, $expression )     = $this->nplurals_and_expression_from_header( $this->get_header( 'Plural-Forms' ) );
    196 				$this->_nplurals                   = $nplurals;
    197 				$this->_gettext_select_plural_form = $this->make_plural_form_function( $nplurals, $expression );
    198 			}
    199 			return call_user_func( $this->_gettext_select_plural_form, $count );
    200 		}
    201 
    202 		/**
    203 		 * @param string $header
    204 		 * @return array
    205 		 */
    206 		function nplurals_and_expression_from_header( $header ) {
    207 			if ( preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches ) ) {
    208 				$nplurals   = (int) $matches[1];
    209 				$expression = trim( $matches[2] );
    210 				return array( $nplurals, $expression );
    211 			} else {
    212 				return array( 2, 'n != 1' );
    213 			}
    214 		}
    215 
    216 		/**
    217 		 * Makes a function, which will return the right translation index, according to the
    218 		 * plural forms header
    219 		 *
    220 		 * @param int    $nplurals
    221 		 * @param string $expression
    222 		 */
    223 		function make_plural_form_function( $nplurals, $expression ) {
    224 			try {
    225 				$handler = new Plural_Forms( rtrim( $expression, ';' ) );
    226 				return array( $handler, 'get' );
    227 			} catch ( Exception $e ) {
    228 				// Fall back to default plural-form function.
    229 				return $this->make_plural_form_function( 2, 'n != 1' );
    230 			}
    231 		}
    232 
    233 		/**
    234 		 * Adds parentheses to the inner parts of ternary operators in
    235 		 * plural expressions, because PHP evaluates ternary oerators from left to right
    236 		 *
    237 		 * @param string $expression the expression without parentheses
    238 		 * @return string the expression with parentheses added
    239 		 */
    240 		function parenthesize_plural_exression( $expression ) {
    241 			$expression .= ';';
    242 			$res         = '';
    243 			$depth       = 0;
    244 			for ( $i = 0; $i < strlen( $expression ); ++$i ) {
    245 				$char = $expression[ $i ];
    246 				switch ( $char ) {
    247 					case '?':
    248 						$res .= ' ? (';
    249 						$depth++;
    250 						break;
    251 					case ':':
    252 						$res .= ') : (';
    253 						break;
    254 					case ';':
    255 						$res  .= str_repeat( ')', $depth ) . ';';
    256 						$depth = 0;
    257 						break;
    258 					default:
    259 						$res .= $char;
    260 				}
    261 			}
    262 			return rtrim( $res, ';' );
    263 		}
    264 
    265 		/**
    266 		 * @param string $translation
    267 		 * @return array
    268 		 */
    269 		function make_headers( $translation ) {
    270 			$headers = array();
    271 			// Sometimes \n's are used instead of real new lines.
    272 			$translation = str_replace( '\n', "\n", $translation );
    273 			$lines       = explode( "\n", $translation );
    274 			foreach ( $lines as $line ) {
    275 				$parts = explode( ':', $line, 2 );
    276 				if ( ! isset( $parts[1] ) ) {
    277 					continue;
    278 				}
    279 				$headers[ trim( $parts[0] ) ] = trim( $parts[1] );
    280 			}
    281 			return $headers;
    282 		}
    283 
    284 		/**
    285 		 * @param string $header
    286 		 * @param string $value
    287 		 */
    288 		function set_header( $header, $value ) {
    289 			parent::set_header( $header, $value );
    290 			if ( 'Plural-Forms' === $header ) {
    291 				list( $nplurals, $expression )     = $this->nplurals_and_expression_from_header( $this->get_header( 'Plural-Forms' ) );
    292 				$this->_nplurals                   = $nplurals;
    293 				$this->_gettext_select_plural_form = $this->make_plural_form_function( $nplurals, $expression );
    294 			}
    295 		}
    296 	}
    297 endif;
    298 
    299 if ( ! class_exists( 'NOOP_Translations', false ) ) :
    300 	/**
    301 	 * Provides the same interface as Translations, but doesn't do anything
    302 	 */
    303 	class NOOP_Translations {
    304 		public $entries = array();
    305 		public $headers = array();
    306 
    307 		function add_entry( $entry ) {
    308 			return true;
    309 		}
    310 
    311 		/**
    312 		 * @param string $header
    313 		 * @param string $value
    314 		 */
    315 		function set_header( $header, $value ) {
    316 		}
    317 
    318 		/**
    319 		 * @param array $headers
    320 		 */
    321 		function set_headers( $headers ) {
    322 		}
    323 
    324 		/**
    325 		 * @param string $header
    326 		 * @return false
    327 		 */
    328 		function get_header( $header ) {
    329 			return false;
    330 		}
    331 
    332 		/**
    333 		 * @param Translation_Entry $entry
    334 		 * @return false
    335 		 */
    336 		function translate_entry( &$entry ) {
    337 			return false;
    338 		}
    339 
    340 		/**
    341 		 * @param string $singular
    342 		 * @param string $context
    343 		 */
    344 		function translate( $singular, $context = null ) {
    345 			return $singular;
    346 		}
    347 
    348 		/**
    349 		 * @param int $count
    350 		 * @return bool
    351 		 */
    352 		function select_plural_form( $count ) {
    353 			return 1 == $count ? 0 : 1;
    354 		}
    355 
    356 		/**
    357 		 * @return int
    358 		 */
    359 		function get_plural_forms_count() {
    360 			return 2;
    361 		}
    362 
    363 		/**
    364 		 * @param string $singular
    365 		 * @param string $plural
    366 		 * @param int    $count
    367 		 * @param string $context
    368 		 */
    369 		function translate_plural( $singular, $plural, $count, $context = null ) {
    370 			return 1 == $count ? $singular : $plural;
    371 		}
    372 
    373 		/**
    374 		 * @param object $other
    375 		 */
    376 		function merge_with( &$other ) {
    377 		}
    378 	}
    379 endif;