| 23 | | |
|---|
| 24 | | |
|---|
| 25 | | // For start, we only want to read the MO files |
|---|
| 26 | | |
|---|
| | 24 | /** |
|---|
| | 25 | * Provides a simple gettext replacement that works independently from |
|---|
| | 26 | * the system's gettext abilities. |
|---|
| | 27 | * It can read MO files and use them for translating strings. |
|---|
| | 28 | * The files are passed to gettext_reader as a Stream (see streams.php) |
|---|
| | 29 | * |
|---|
| | 30 | * This version has the ability to cache all strings and translations to |
|---|
| | 31 | * speed up the string lookup. |
|---|
| | 32 | * While the cache is enabled by default, it can be switched off with the |
|---|
| | 33 | * second parameter in the constructor (e.g. whenusing very large MO files |
|---|
| | 34 | * that you don't want to keep in memory) |
|---|
| | 35 | */ |
|---|
| 36 | | // Reads 4 byte value from $FD and puts it in int |
|---|
| 37 | | // $BYTEORDER specifies the byte order: 0 low endian, 1 big endian |
|---|
| 38 | | for ($i=0; $i<4; $i++) { |
|---|
| 39 | | $byte[$i]=ord($this->STREAM->read(1)); |
|---|
| 40 | | } |
|---|
| 41 | | //print sprintf("pos: %d\n",$this->STREAM->currentpos()); |
|---|
| 42 | | if ($this->BYTEORDER == 0) |
|---|
| 43 | | return (int)(($byte[0]) | ($byte[1]<<8) | ($byte[2]<<16) | ($byte[3]<<24)); |
|---|
| 44 | | else |
|---|
| 45 | | return (int)(($byte[3]) | ($byte[2]<<8) | ($byte[1]<<16) | ($byte[0]<<24)); |
|---|
| 46 | | } |
|---|
| 47 | | |
|---|
| 48 | | // constructor that requires StreamReader object |
|---|
| 49 | | function gettext_reader($Reader) { |
|---|
| | 64 | if ($this->BYTEORDER == 0) { |
|---|
| | 65 | // low endian |
|---|
| | 66 | return array_shift(unpack('V', $this->STREAM->read(4))); |
|---|
| | 67 | } else { |
|---|
| | 68 | // big endian |
|---|
| | 69 | return array_shift(unpack('N', $this->STREAM->read(4))); |
|---|
| | 70 | } |
|---|
| | 71 | } |
|---|
| | 72 | |
|---|
| | 73 | /** |
|---|
| | 74 | * Reads an array of Integers from the Stream |
|---|
| | 75 | * |
|---|
| | 76 | * @param int count How many elements should be read |
|---|
| | 77 | * @return Array of Integers |
|---|
| | 78 | */ |
|---|
| | 79 | function readintarray($count) { |
|---|
| | 80 | if ($this->BYTEORDER == 0) { |
|---|
| | 81 | // low endian |
|---|
| | 82 | return unpack('V'.$count, $this->STREAM->read(4 * $count)); |
|---|
| | 83 | } else { |
|---|
| | 84 | // big endian |
|---|
| | 85 | return unpack('N'.$count, $this->STREAM->read(4 * $count)); |
|---|
| | 86 | } |
|---|
| | 87 | } |
|---|
| | 88 | |
|---|
| | 89 | /** |
|---|
| | 90 | * Constructor |
|---|
| | 91 | * |
|---|
| | 92 | * @param object Reader the StreamReader object |
|---|
| | 93 | * @param boolean enable_cache Enable or disable caching of strings (default on) |
|---|
| | 94 | */ |
|---|
| | 95 | function gettext_reader($Reader, $enable_cache = true) { |
|---|
| 52 | | $this->short_circuit = true; |
|---|
| 53 | | return; |
|---|
| 54 | | } |
|---|
| 55 | | |
|---|
| 56 | | // $MAGIC1 = (int)0x950412de; //bug in PHP 5 |
|---|
| 57 | | $MAGIC1 = (int) - 1794895138; |
|---|
| 58 | | // $MAGIC2 = (int)0xde120495; //bug |
|---|
| 59 | | $MAGIC2 = (int) - 569244523; |
|---|
| 60 | | |
|---|
| | 98 | $this->short_circuit = true; |
|---|
| | 99 | return; |
|---|
| | 100 | } |
|---|
| | 101 | |
|---|
| | 102 | // Caching can be turned off |
|---|
| | 103 | $this->enable_cache = $enable_cache; |
|---|
| | 104 | |
|---|
| | 105 | // $MAGIC1 = (int)0x950412de; //bug in PHP 5 |
|---|
| | 106 | $MAGIC1 = (int) - 1794895138; |
|---|
| | 107 | // $MAGIC2 = (int)0xde120495; //bug |
|---|
| | 108 | $MAGIC2 = (int) - 569244523; |
|---|
| 75 | | |
|---|
| 76 | | $total = $this->readint(); |
|---|
| 77 | | $originals = $this->readint(); |
|---|
| 78 | | $translations = $this->readint(); |
|---|
| 79 | | |
|---|
| 80 | | $this->total = $total; |
|---|
| 81 | | $this->originals = $originals; |
|---|
| 82 | | $this->translations = $translations; |
|---|
| 83 | | |
|---|
| 84 | | } |
|---|
| 85 | | |
|---|
| 86 | | function load_tables($translations=false) { |
|---|
| 87 | | // if tables are loaded do not load them again |
|---|
| 88 | | if (!is_array($this->ORIGINALS)) { |
|---|
| 89 | | $this->ORIGINALS = array(); |
|---|
| 90 | | $this->STREAM->seekto($this->originals); |
|---|
| 91 | | for ($i=0; $i<$this->total; $i++) { |
|---|
| 92 | | $len = $this->readint(); |
|---|
| 93 | | $ofs = $this->readint(); |
|---|
| 94 | | $this->ORIGINALS[] = array($len,$ofs); |
|---|
| 95 | | } |
|---|
| 96 | | } |
|---|
| 97 | | |
|---|
| 98 | | // similar for translations |
|---|
| 99 | | if ($translations and !is_array($this->TRANSLATIONS)) { |
|---|
| 100 | | $this->TRANSLATIONS = array(); |
|---|
| 101 | | $this->STREAM->seekto($this->translations); |
|---|
| 102 | | for ($i=0; $i<$this->total; $i++) { |
|---|
| 103 | | $len = $this->readint(); |
|---|
| 104 | | $ofs = $this->readint(); |
|---|
| 105 | | $this->TRANSLATIONS[] = array($len,$ofs); |
|---|
| 106 | | } |
|---|
| 107 | | } |
|---|
| 108 | | |
|---|
| 109 | | } |
|---|
| 110 | | |
|---|
| 111 | | function get_string_number($num) { |
|---|
| 112 | | // get a string with particular number |
|---|
| 113 | | // TODO: Add simple hashing [check array, add if not already there] |
|---|
| 114 | | $this->load_tables(); |
|---|
| 115 | | $meta = $this->ORIGINALS[$num]; |
|---|
| 116 | | $length = $meta[0]; |
|---|
| 117 | | $offset = $meta[1]; |
|---|
| 118 | | if (! $length) { |
|---|
| 119 | | return ''; |
|---|
| 120 | | } |
|---|
| | 123 | |
|---|
| | 124 | $this->total = $this->readint(); |
|---|
| | 125 | $this->originals = $this->readint(); |
|---|
| | 126 | $this->translations = $this->readint(); |
|---|
| | 127 | } |
|---|
| | 128 | |
|---|
| | 129 | /** |
|---|
| | 130 | * Loads the translation tables from the MO file into the cache |
|---|
| | 131 | * If caching is enabled, also loads all strings into a cache |
|---|
| | 132 | * to speed up translation lookups |
|---|
| | 133 | * |
|---|
| | 134 | * @access private |
|---|
| | 135 | */ |
|---|
| | 136 | function load_tables() { |
|---|
| | 137 | if (is_array($this->cache_translations) && |
|---|
| | 138 | is_array($this->table_originals) && |
|---|
| | 139 | is_array($this->table_translations)) |
|---|
| | 140 | return; |
|---|
| | 141 | |
|---|
| | 142 | /* get original and translations tables */ |
|---|
| | 143 | $this->STREAM->seekto($this->originals); |
|---|
| | 144 | $this->table_originals = $this->readintarray($this->total * 2); |
|---|
| | 145 | $this->STREAM->seekto($this->translations); |
|---|
| | 146 | $this->table_translations = $this->readintarray($this->total * 2); |
|---|
| | 147 | |
|---|
| | 148 | if ($this->enable_cache) { |
|---|
| | 149 | $this->cache_translations = array (); |
|---|
| | 150 | /* read all strings in the cache */ |
|---|
| | 151 | for ($i = 0; $i < $this->total; $i++) { |
|---|
| | 152 | $this->STREAM->seekto($this->table_originals[$i * 2 + 2]); |
|---|
| | 153 | $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]); |
|---|
| | 154 | $this->STREAM->seekto($this->table_translations[$i * 2 + 2]); |
|---|
| | 155 | $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]); |
|---|
| | 156 | $this->cache_translations[$original] = $translation; |
|---|
| | 157 | } |
|---|
| | 158 | } |
|---|
| | 159 | } |
|---|
| | 160 | |
|---|
| | 161 | /** |
|---|
| | 162 | * Returns a string from the "originals" table |
|---|
| | 163 | * |
|---|
| | 164 | * @access private |
|---|
| | 165 | * @param int num Offset number of original string |
|---|
| | 166 | * @return string Requested string if found, otherwise '' |
|---|
| | 167 | */ |
|---|
| | 168 | function get_original_string($num) { |
|---|
| | 169 | $length = $this->table_originals[$num * 2 + 1]; |
|---|
| | 170 | $offset = $this->table_originals[$num * 2 + 2]; |
|---|
| | 171 | if (! $length) |
|---|
| | 172 | return ''; |
|---|
| 125 | | |
|---|
| 126 | | function get_translation_number($num) { |
|---|
| 127 | | // get a string with particular number |
|---|
| 128 | | // TODO: Add simple hashing [check array, add if not already there] |
|---|
| 129 | | $this->load_tables(true); |
|---|
| 130 | | $meta = $this->TRANSLATIONS[$num]; |
|---|
| 131 | | $length = $meta[0]; |
|---|
| 132 | | $offset = $meta[1]; |
|---|
| | 177 | |
|---|
| | 178 | /** |
|---|
| | 179 | * Returns a string from the "translations" table |
|---|
| | 180 | * |
|---|
| | 181 | * @access private |
|---|
| | 182 | * @param int num Offset number of original string |
|---|
| | 183 | * @return string Requested string if found, otherwise '' |
|---|
| | 184 | */ |
|---|
| | 185 | function get_translation_string($num) { |
|---|
| | 186 | $length = $this->table_translations[$num * 2 + 1]; |
|---|
| | 187 | $offset = $this->table_translations[$num * 2 + 2]; |
|---|
| | 188 | if (! $length) |
|---|
| | 189 | return ''; |
|---|
| 138 | | // binary search for string |
|---|
| 139 | | function find_string($string, $start,$end) { |
|---|
| 140 | | //print "start: $start, end: $end\n"; |
|---|
| 141 | | if (abs($start-$end)<=1) { |
|---|
| 142 | | // we're done, if it's not it, bye bye |
|---|
| 143 | | $txt = $this->get_string_number($start); |
|---|
| | 195 | /** |
|---|
| | 196 | * Binary search for string |
|---|
| | 197 | * |
|---|
| | 198 | * @access private |
|---|
| | 199 | * @param string string |
|---|
| | 200 | * @param int start (internally used in recursive function) |
|---|
| | 201 | * @param int end (internally used in recursive function) |
|---|
| | 202 | * @return int string number (offset in originals table) |
|---|
| | 203 | */ |
|---|
| | 204 | function find_string($string, $start = -1, $end = -1) { |
|---|
| | 205 | if (($start == -1) or ($end == -1)) { |
|---|
| | 206 | // find_string is called with only one parameter, set start end end |
|---|
| | 207 | $start = 0; |
|---|
| | 208 | $end = $this->total; |
|---|
| | 209 | } |
|---|
| | 210 | if (abs($start - $end) <= 1) { |
|---|
| | 211 | // We're done, now we either found the string, or it doesn't exist |
|---|
| | 212 | $txt = $this->get_original_string($start); |
|---|
| 145 | | return $start; |
|---|
| 146 | | else |
|---|
| 147 | | return -1; |
|---|
| 148 | | } elseif ($start>$end) { |
|---|
| 149 | | return $this->find_string($string,$end,$start); |
|---|
| 150 | | } else { |
|---|
| 151 | | $half = (int)(($start+$end)/2); |
|---|
| 152 | | $tst = $this->get_string_number($half); |
|---|
| 153 | | $cmp = strcmp($string,$tst); |
|---|
| 154 | | if ($cmp == 0) |
|---|
| 155 | | return $half; |
|---|
| 156 | | elseif ($cmp<0) |
|---|
| 157 | | return $this->find_string($string,$start,$half); |
|---|
| 158 | | else |
|---|
| 159 | | return $this->find_string($string,$half,$end); |
|---|
| 160 | | } |
|---|
| 161 | | } |
|---|
| 162 | | |
|---|
| | 214 | return $start; |
|---|
| | 215 | else |
|---|
| | 216 | return -1; |
|---|
| | 217 | } else if ($start > $end) { |
|---|
| | 218 | // start > end -> turn around and start over |
|---|
| | 219 | return $this->find_string($string, $end, $start); |
|---|
| | 220 | } else { |
|---|
| | 221 | // Divide table in two parts |
|---|
| | 222 | $half = (int)(($start + $end) / 2); |
|---|
| | 223 | $cmp = strcmp($string, $this->get_original_string($half)); |
|---|
| | 224 | if ($cmp == 0) |
|---|
| | 225 | // string is exactly in the middle => return it |
|---|
| | 226 | return $half; |
|---|
| | 227 | else if ($cmp < 0) |
|---|
| | 228 | // The string is in the upper half |
|---|
| | 229 | return $this->find_string($string, $start, $half); |
|---|
| | 230 | else |
|---|
| | 231 | // The string is in the lower half |
|---|
| | 232 | return $this->find_string($string, $half, $end); |
|---|
| | 233 | } |
|---|
| | 234 | } |
|---|
| | 235 | |
|---|
| | 236 | /** |
|---|
| | 237 | * Translates a string |
|---|
| | 238 | * |
|---|
| | 239 | * @access public |
|---|
| | 240 | * @param string string to be translated |
|---|
| | 241 | * @return string translated string (or original, if not found) |
|---|
| | 242 | */ |
|---|
| 180 | | if (is_string($this->pluralheader)) |
|---|
| 181 | | return $this->pluralheader; |
|---|
| 182 | | else { |
|---|
| 183 | | $header = $this->get_translation_number(0); |
|---|
| 184 | | |
|---|
| 185 | | if (eregi("plural-forms: (.*)\n",$header,$regs)) { |
|---|
| 186 | | $expr = $regs[1]; |
|---|
| 187 | | } else { |
|---|
| 188 | | $expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; |
|---|
| 189 | | } |
|---|
| | 276 | if (! is_string($this->pluralheader)) { |
|---|
| | 277 | if ($this->enable_cache) { |
|---|
| | 278 | $header = $this->cache_translations[""]; |
|---|
| | 279 | } else { |
|---|
| | 280 | $header = $this->get_translation_string(0); |
|---|
| | 281 | } |
|---|
| | 282 | if (eregi("plural-forms: (.*)\n", $header, $regs)) |
|---|
| | 283 | $expr = $regs[1]; |
|---|
| | 284 | else |
|---|
| | 285 | $expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; |
|---|