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