PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-includes/pomo/translations.php

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