A tumblelog CMS built on AJAX, PHP and MySQL.

gettext.class.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <?php
  2. if(!defined('entry') || !entry) die('Not a valid page');
  3. /*
  4. Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
  5. Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
  6. This file is part of PHP-gettext.
  7. PHP-gettext is free software; you can redistribute it and/or modify
  8. it under the terms of the GNU General Public License as published by
  9. the Free Software Foundation; either version 2 of the License, or
  10. (at your option) any later version.
  11. PHP-gettext is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. GNU General Public License for more details.
  15. You should have received a copy of the GNU General Public License
  16. along with PHP-gettext; if not, write to the Free Software
  17. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  18. */
  19. /**
  20. * Provides a simple gettext replacement that works independently from
  21. * the system's gettext abilities.
  22. * It can read MO files and use them for translating strings.
  23. * The files are passed to gettext_reader as a Stream (see streams.php)
  24. *
  25. * This version has the ability to cache all strings and translations to
  26. * speed up the string lookup.
  27. * While the cache is enabled by default, it can be switched off with the
  28. * second parameter in the constructor (e.g. whenusing very large MO files
  29. * that you don't want to keep in memory)
  30. */
  31. class gettext_reader {
  32. //public:
  33. var $error = 0; // public variable that holds error code (0 if no error)
  34. //private:
  35. var $BYTEORDER = 0; // 0: low endian, 1: big endian
  36. var $STREAM = NULL;
  37. var $short_circuit = false;
  38. var $enable_cache = false;
  39. var $originals = NULL; // offset of original table
  40. var $translations = NULL; // offset of translation table
  41. var $pluralheader = NULL; // cache header field for plural forms
  42. var $total = 0; // total string count
  43. var $table_originals = NULL; // table for original strings (offsets)
  44. var $table_translations = NULL; // table for translated strings (offsets)
  45. var $cache_translations = NULL; // original -> translation mapping
  46. /* Methods */
  47. /**
  48. * Reads a 32bit Integer from the Stream
  49. *
  50. * @access private
  51. * @return Integer from the Stream
  52. */
  53. function readint() {
  54. if ($this->BYTEORDER == 0) {
  55. // low endian
  56. return array_shift(unpack('V', $this->STREAM->read(4)));
  57. } else {
  58. // big endian
  59. return array_shift(unpack('N', $this->STREAM->read(4)));
  60. }
  61. }
  62. /**
  63. * Reads an array of Integers from the Stream
  64. *
  65. * @param int count How many elements should be read
  66. * @return Array of Integers
  67. */
  68. function readintarray($count) {
  69. if ($this->BYTEORDER == 0) {
  70. // low endian
  71. return unpack('V'.$count, $this->STREAM->read(4 * $count));
  72. } else {
  73. // big endian
  74. return unpack('N'.$count, $this->STREAM->read(4 * $count));
  75. }
  76. }
  77. /**
  78. * Constructor
  79. *
  80. * @param object Reader the StreamReader object
  81. * @param boolean enable_cache Enable or disable caching of strings (default on)
  82. */
  83. function gettext_reader($Reader, $enable_cache = true) {
  84. // If there isn't a StreamReader, turn on short circuit mode.
  85. if (! $Reader || isset($Reader->error) ) {
  86. $this->short_circuit = true;
  87. return;
  88. }
  89. // Caching can be turned off
  90. $this->enable_cache = $enable_cache;
  91. // $MAGIC1 = (int)0x950412de; //bug in PHP 5
  92. $MAGIC1 = (int) - 1794895138;
  93. // $MAGIC2 = (int)0xde120495; //bug
  94. $MAGIC2 = (int) - 569244523;
  95. $this->STREAM = $Reader;
  96. $magic = $this->readint();
  97. if ($magic == $MAGIC1) {
  98. $this->BYTEORDER = 0;
  99. } elseif ($magic == $MAGIC2) {
  100. $this->BYTEORDER = 1;
  101. } else {
  102. $this->error = 1; // not MO file
  103. return false;
  104. }
  105. // FIXME: Do we care about revision? We should.
  106. $revision = $this->readint();
  107. $this->total = $this->readint();
  108. $this->originals = $this->readint();
  109. $this->translations = $this->readint();
  110. }
  111. /**
  112. * Loads the translation tables from the MO file into the cache
  113. * If caching is enabled, also loads all strings into a cache
  114. * to speed up translation lookups
  115. *
  116. * @access private
  117. */
  118. function load_tables() {
  119. if (is_array($this->cache_translations) &&
  120. is_array($this->table_originals) &&
  121. is_array($this->table_translations))
  122. return;
  123. /* get original and translations tables */
  124. $this->STREAM->seekto($this->originals);
  125. $this->table_originals = $this->readintarray($this->total * 2);
  126. $this->STREAM->seekto($this->translations);
  127. $this->table_translations = $this->readintarray($this->total * 2);
  128. if ($this->enable_cache) {
  129. $this->cache_translations = array ();
  130. /* read all strings in the cache */
  131. for ($i = 0; $i < $this->total; $i++) {
  132. $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
  133. $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
  134. $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
  135. $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
  136. $this->cache_translations[$original] = $translation;
  137. }
  138. }
  139. }
  140. /**
  141. * Returns a string from the "originals" table
  142. *
  143. * @access private
  144. * @param int num Offset number of original string
  145. * @return string Requested string if found, otherwise ''
  146. */
  147. function get_original_string($num) {
  148. $length = $this->table_originals[$num * 2 + 1];
  149. $offset = $this->table_originals[$num * 2 + 2];
  150. if (! $length)
  151. return '';
  152. $this->STREAM->seekto($offset);
  153. $data = $this->STREAM->read($length);
  154. return (string)$data;
  155. }
  156. /**
  157. * Returns a string from the "translations" table
  158. *
  159. * @access private
  160. * @param int num Offset number of original string
  161. * @return string Requested string if found, otherwise ''
  162. */
  163. function get_translation_string($num) {
  164. $length = $this->table_translations[$num * 2 + 1];
  165. $offset = $this->table_translations[$num * 2 + 2];
  166. if (! $length)
  167. return '';
  168. $this->STREAM->seekto($offset);
  169. $data = $this->STREAM->read($length);
  170. return (string)$data;
  171. }
  172. /**
  173. * Binary search for string
  174. *
  175. * @access private
  176. * @param string string
  177. * @param int start (internally used in recursive function)
  178. * @param int end (internally used in recursive function)
  179. * @return int string number (offset in originals table)
  180. */
  181. function find_string($string, $start = -1, $end = -1) {
  182. if (($start == -1) or ($end == -1)) {
  183. // find_string is called with only one parameter, set start end end
  184. $start = 0;
  185. $end = $this->total;
  186. }
  187. if (abs($start - $end) <= 1) {
  188. // We're done, now we either found the string, or it doesn't exist
  189. $txt = $this->get_original_string($start);
  190. if ($string == $txt)
  191. return $start;
  192. else
  193. return -1;
  194. } else if ($start > $end) {
  195. // start > end -> turn around and start over
  196. return $this->find_string($string, $end, $start);
  197. } else {
  198. // Divide table in two parts
  199. $half = (int)(($start + $end) / 2);
  200. $cmp = strcmp($string, $this->get_original_string($half));
  201. if ($cmp == 0)
  202. // string is exactly in the middle => return it
  203. return $half;
  204. else if ($cmp < 0)
  205. // The string is in the upper half
  206. return $this->find_string($string, $start, $half);
  207. else
  208. // The string is in the lower half
  209. return $this->find_string($string, $half, $end);
  210. }
  211. }
  212. /**
  213. * Translates a string
  214. *
  215. * @access public
  216. * @param string string to be translated
  217. * @return string translated string (or original, if not found)
  218. */
  219. function translate($string) {
  220. if ($this->short_circuit)
  221. return $string;
  222. $this->load_tables();
  223. if ($this->enable_cache) {
  224. // Caching enabled, get translated string from cache
  225. if (array_key_exists($string, $this->cache_translations))
  226. return $this->cache_translations[$string];
  227. else
  228. return $string;
  229. } else {
  230. // Caching not enabled, try to find string
  231. $num = $this->find_string($string);
  232. if ($num == -1)
  233. return $string;
  234. else
  235. return $this->get_translation_string($num);
  236. }
  237. }
  238. /**
  239. * Get possible plural forms from MO header
  240. *
  241. * @access private
  242. * @return string plural form header
  243. */
  244. function get_plural_forms() {
  245. // lets assume message number 0 is header
  246. // this is true, right?
  247. $this->load_tables();
  248. // cache header field for plural forms
  249. if (! is_string($this->pluralheader)) {
  250. if ($this->enable_cache) {
  251. $header = $this->cache_translations[""];
  252. } else {
  253. $header = $this->get_translation_string(0);
  254. }
  255. if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
  256. $expr = $regs[1];
  257. else
  258. $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
  259. $this->pluralheader = $expr;
  260. }
  261. return $this->pluralheader;
  262. }
  263. /**
  264. * Detects which plural form to take
  265. *
  266. * @access private
  267. * @param n count
  268. * @return int array index of the right plural form
  269. */
  270. function select_string($n) {
  271. $string = $this->get_plural_forms();
  272. $string = str_replace('nplurals',"\$total",$string);
  273. $string = str_replace("n",$n,$string);
  274. $string = str_replace('plural',"\$plural",$string);
  275. $total = 0;
  276. $plural = 0;
  277. eval("$string");
  278. if ($plural >= $total) $plural = $total - 1;
  279. return $plural;
  280. }
  281. /**
  282. * Plural version of gettext
  283. *
  284. * @access public
  285. * @param string single
  286. * @param string plural
  287. * @param string number
  288. * @return translated plural form
  289. */
  290. function ngettext($single, $plural, $number) {
  291. if ($this->short_circuit) {
  292. if ($number != 1)
  293. return $plural;
  294. else
  295. return $single;
  296. }
  297. // find out the appropriate form
  298. $select = $this->select_string($number);
  299. // this should contains all strings separated by NULLs
  300. $key = $single.chr(0).$plural;
  301. if ($this->enable_cache) {
  302. if (! array_key_exists($key, $this->cache_translations)) {
  303. return ($number != 1) ? $plural : $single;
  304. } else {
  305. $result = $this->cache_translations[$key];
  306. $list = explode(chr(0), $result);
  307. return $list[$select];
  308. }
  309. } else {
  310. $num = $this->find_string($key);
  311. if ($num == -1) {
  312. return ($number != 1) ? $plural : $single;
  313. } else {
  314. $result = $this->get_translation_string($num);
  315. $list = explode(chr(0), $result);
  316. return $list[$select];
  317. }
  318. }
  319. }
  320. }
  321. ?>