web/wp-includes/pomo/translations.php
changeset 136 bde1974c263b
child 194 32102edaa81b
equal deleted inserted replaced
135:53cff4b4a802 136:bde1974c263b
       
     1 <?php
       
     2 /**
       
     3  * Class for a set of entries for translation and their associated headers
       
     4  *
       
     5  * @version $Id: translations.php 291 2009-10-21 05:46:08Z nbachiyski $
       
     6  * @package pomo
       
     7  * @subpackage translations
       
     8  */
       
     9 
       
    10 require_once dirname(__FILE__) . '/entry.php';
       
    11 
       
    12 if ( !class_exists( 'Translations' ) ):
       
    13 class Translations {
       
    14 	var $entries = array();
       
    15 	var $headers = array();
       
    16 
       
    17 	/**
       
    18 	 * Add entry to the PO structure
       
    19 	 *
       
    20 	 * @param object &$entry
       
    21 	 * @return bool true on success, false if the entry doesn't have a key
       
    22 	 */
       
    23 	function add_entry($entry) {
       
    24 		if (is_array($entry)) {
       
    25 			$entry = new Translation_Entry($entry);
       
    26 		}
       
    27 		$key = $entry->key();
       
    28 		if (false === $key) return false;
       
    29 		$this->entries[$key] = &$entry;
       
    30 		return true;
       
    31 	}
       
    32 
       
    33 	/**
       
    34 	 * Sets $header PO header to $value
       
    35 	 *
       
    36 	 * If the header already exists, it will be overwritten
       
    37 	 *
       
    38 	 * TODO: this should be out of this class, it is gettext specific
       
    39 	 *
       
    40 	 * @param string $header header name, without trailing :
       
    41 	 * @param string $value header value, without trailing \n
       
    42 	 */
       
    43 	function set_header($header, $value) {
       
    44 		$this->headers[$header] = $value;
       
    45 	}
       
    46 
       
    47 	function set_headers(&$headers) {
       
    48 		foreach($headers as $header => $value) {
       
    49 			$this->set_header($header, $value);
       
    50 		}
       
    51 	}
       
    52 
       
    53 	function get_header($header) {
       
    54 		return isset($this->headers[$header])? $this->headers[$header] : false;
       
    55 	}
       
    56 
       
    57 	function translate_entry(&$entry) {
       
    58 		$key = $entry->key();
       
    59 		return isset($this->entries[$key])? $this->entries[$key] : false;
       
    60 	}
       
    61 
       
    62 	function translate($singular, $context=null) {
       
    63 		$entry = new Translation_Entry(array('singular' => $singular, 'context' => $context));
       
    64 		$translated = $this->translate_entry($entry);
       
    65 		return ($translated && !empty($translated->translations))? $translated->translations[0] : $singular;
       
    66 	}
       
    67 
       
    68 	/**
       
    69 	 * Given the number of items, returns the 0-based index of the plural form to use
       
    70 	 *
       
    71 	 * Here, in the base Translations class, the commong logic for English is implmented:
       
    72 	 * 	0 if there is one element, 1 otherwise
       
    73 	 *
       
    74 	 * This function should be overrided by the sub-classes. For example MO/PO can derive the logic
       
    75 	 * from their headers.
       
    76 	 *
       
    77 	 * @param integer $count number of items
       
    78 	 */
       
    79 	function select_plural_form($count) {
       
    80 		return 1 == $count? 0 : 1;
       
    81 	}
       
    82 
       
    83 	function get_plural_forms_count() {
       
    84 		return 2;
       
    85 	}
       
    86 
       
    87 	function translate_plural($singular, $plural, $count, $context = null) {
       
    88 		$entry = new Translation_Entry(array('singular' => $singular, 'plural' => $plural, 'context' => $context));
       
    89 		$translated = $this->translate_entry($entry);
       
    90 		$index = $this->select_plural_form($count);
       
    91 		$total_plural_forms = $this->get_plural_forms_count();
       
    92 		if ($translated && 0 <= $index && $index < $total_plural_forms &&
       
    93 				is_array($translated->translations) &&
       
    94 				isset($translated->translations[$index]))
       
    95 			return $translated->translations[$index];
       
    96 		else
       
    97 			return 1 == $count? $singular : $plural;
       
    98 	}
       
    99 
       
   100 	/**
       
   101 	 * Merge $other in the current object.
       
   102 	 *
       
   103 	 * @param Object &$other Another Translation object, whose translations will be merged in this one
       
   104 	 * @return void
       
   105 	 **/
       
   106 	function merge_with(&$other) {
       
   107 		$this->entries = array_merge($this->entries, $other->entries);
       
   108 	}
       
   109 }
       
   110 
       
   111 class Gettext_Translations extends Translations {
       
   112 	/**
       
   113 	 * The gettext implmentation of select_plural_form.
       
   114 	 *
       
   115 	 * It lives in this class, because there are more than one descendand, which will use it and
       
   116 	 * they can't share it effectively.
       
   117 	 *
       
   118 	 */
       
   119 	function gettext_select_plural_form($count) {
       
   120 		if (!isset($this->_gettext_select_plural_form) || is_null($this->_gettext_select_plural_form)) {
       
   121 			list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header($this->get_header('Plural-Forms'));
       
   122 			$this->_nplurals = $nplurals;
       
   123 			$this->_gettext_select_plural_form = $this->make_plural_form_function($nplurals, $expression);
       
   124 		}
       
   125 		return call_user_func($this->_gettext_select_plural_form, $count);
       
   126 	}
       
   127 	
       
   128 	function nplurals_and_expression_from_header($header) {
       
   129 		if (preg_match('/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches)) {
       
   130 			$nplurals = (int)$matches[1];
       
   131 			$expression = trim($this->parenthesize_plural_exression($matches[2]));
       
   132 			return array($nplurals, $expression);
       
   133 		} else {
       
   134 			return array(2, 'n != 1');
       
   135 		}
       
   136 	}
       
   137 
       
   138 	/**
       
   139 	 * Makes a function, which will return the right translation index, according to the
       
   140 	 * plural forms header
       
   141 	 */
       
   142 	function make_plural_form_function($nplurals, $expression) {
       
   143 		$expression = str_replace('n', '$n', $expression);
       
   144 		$func_body = "
       
   145 			\$index = (int)($expression);
       
   146 			return (\$index < $nplurals)? \$index : $nplurals - 1;";
       
   147 		return create_function('$n', $func_body);
       
   148 	}
       
   149 
       
   150 	/**
       
   151 	 * Adds parantheses to the inner parts of ternary operators in
       
   152 	 * plural expressions, because PHP evaluates ternary oerators from left to right
       
   153 	 * 
       
   154 	 * @param string $expression the expression without parentheses
       
   155 	 * @return string the expression with parentheses added
       
   156 	 */
       
   157 	function parenthesize_plural_exression($expression) {
       
   158 		$expression .= ';';
       
   159 		$res = '';
       
   160 		$depth = 0;
       
   161 		for ($i = 0; $i < strlen($expression); ++$i) {
       
   162 			$char = $expression[$i];
       
   163 			switch ($char) {
       
   164 				case '?':
       
   165 					$res .= ' ? (';
       
   166 					$depth++;
       
   167 					break;
       
   168 				case ':':
       
   169 					$res .= ') : (';
       
   170 					break;
       
   171 				case ';':
       
   172 					$res .= str_repeat(')', $depth) . ';';
       
   173 					$depth= 0;
       
   174 					break;
       
   175 				default:
       
   176 					$res .= $char;
       
   177 			}
       
   178 		}
       
   179 		return rtrim($res, ';');
       
   180 	}
       
   181 	
       
   182 	function make_headers($translation) {
       
   183 		$headers = array();
       
   184 		// sometimes \ns are used instead of real new lines
       
   185 		$translation = str_replace('\n', "\n", $translation);
       
   186 		$lines = explode("\n", $translation);
       
   187 		foreach($lines as $line) {
       
   188 			$parts = explode(':', $line, 2);
       
   189 			if (!isset($parts[1])) continue;
       
   190 			$headers[trim($parts[0])] = trim($parts[1]);
       
   191 		}
       
   192 		return $headers;
       
   193 	}
       
   194 	
       
   195 	function set_header($header, $value) {
       
   196 		parent::set_header($header, $value);
       
   197 		if ('Plural-Forms' == $header) {
       
   198 			list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header($this->get_header('Plural-Forms'));
       
   199 			$this->_nplurals = $nplurals;
       
   200 			$this->_gettext_select_plural_form = $this->make_plural_form_function($nplurals, $expression);
       
   201 		}
       
   202 	}
       
   203 }
       
   204 endif;
       
   205 
       
   206 if ( !class_exists( 'NOOP_Translations' ) ):
       
   207 /**
       
   208  * Provides the same interface as Translations, but doesn't do anything
       
   209  */
       
   210 class NOOP_Translations {
       
   211 	var $entries = array();
       
   212 	var $headers = array();
       
   213 	
       
   214 	function add_entry($entry) {
       
   215 		return true;
       
   216 	}
       
   217 
       
   218 	function set_header($header, $value) {
       
   219 	}
       
   220 
       
   221 	function set_headers(&$headers) {
       
   222 	}
       
   223 
       
   224 	function get_header($header) {
       
   225 		return false;
       
   226 	}
       
   227 
       
   228 	function translate_entry(&$entry) {
       
   229 		return false;
       
   230 	}
       
   231 
       
   232 	function translate($singular, $context=null) {
       
   233 		return $singular;
       
   234 	}
       
   235 
       
   236 	function select_plural_form($count) {
       
   237 		return 1 == $count? 0 : 1;
       
   238 	}
       
   239 
       
   240 	function get_plural_forms_count() {
       
   241 		return 2;
       
   242 	}
       
   243 
       
   244 	function translate_plural($singular, $plural, $count, $context = null) {
       
   245 			return 1 == $count? $singular : $plural;
       
   246 	}
       
   247 
       
   248 	function merge_with(&$other) {
       
   249 	}
       
   250 }
       
   251 endif;