Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* SVN FILE: $Id: i18n.php 7945 2008-12-19 02:16:01Z gwoo $ */
3
/**
4
 * Short description for file.
5
 *
6
 * Long description for file
7
 *
8
 * PHP versions 4 and 5
9
 *
10
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
11
 * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
12
 *
13
 * Licensed under The MIT License
14
 * Redistributions of files must retain the above copyright notice.
15
 *
16
 * @filesource
17
 * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
18
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
19
 * @package       cake
20
 * @subpackage    cake.cake.libs
21
 * @since         CakePHP(tm) v 1.2.0.4116
22
 * @version       $Revision: 7945 $
23
 * @modifiedby    $LastChangedBy: gwoo $
24
 * @lastmodified  $Date: 2008-12-18 18:16:01 -0800 (Thu, 18 Dec 2008) $
25
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
26
 */
27
/**
28
 * Included libraries.
29
 */
30
App::import('Core', 'l10n');
31
/**
32
 * Short description for file.
33
 *
34
 * Long description for file
35
 *
36
 * @package       cake
37
 * @subpackage    cake.cake.libs
38
 */
39
class I18n extends Object {
40
/**
41
 * Instance of the I10n class for localization
42
 *
43
 * @var object
44
 * @access public
45
 */
46
	var $l10n = null;
47
/**
48
 * Current domain of translation
49
 *
50
 * @var string
51
 * @access public
52
 */
53
	var $domain = null;
54
/**
55
 * Current category of translation
56
 *
57
 * @var string
58
 * @access public
59
 */
60
	var $category = 'LC_MESSAGES';
61
/**
62
 * Current language used for translations
63
 *
64
 * @var string
65
 * @access private;
66
 */
67
	var $__lang = null;
68
/**
69
 * Translation strings for a specific domain read from the .mo or .po files
70
 *
71
 * @var array
72
 * @access private
73
 */
74
	var $__domains = array();
75
/**
76
 * Set to true when I18N::__bindTextDomain() is called for the first time.
77
 * If a translation file is found it is set to false again
78
 *
79
 * @var boolean
80
 * @access private
81
 */
82
	var $__noLocale = false;
83
/**
84
 * Determine if $__domains cache should be wrote
85
 *
86
 * @var boolean
87
 * @access private
88
 */
89
	var $__cache = false;
90
/**
91
 * Set to true when I18N::__bindTextDomain() is called for the first time.
92
 * If a translation file is found it is set to false again
93
 *
94
 * @var array
95
 * @access private
96
 */
97
	var $__categories = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
98
/**
99
 * Return a static instance of the I18n class
100
 *
101
 * @return object I18n
102
 * @access public
103
 */
104
	function &getInstance() {
105
		static $instance = array();
106
		if (!$instance) {
107
			$instance[0] =& new I18n();
108
			$instance[0]->l10n =& new L10n();
109
		}
110
		return $instance[0];
111
	}
112
/**
113
 * Used by the translation functions in basics.php
114
 * Can also be used like I18n::translate(); but only if the uses('i18n'); has been used to load the class.
115
 *
116
 * @param string $singular String to translate
117
 * @param string $plural Plural string (if any)
118
 * @param string $domain Domain
119
 * @param string $category Category
120
 * @param integer $count Count
121
 * @return string translated strings.
122
 * @access public
123
 */
124
	function translate($singular, $plural = null, $domain = null, $category = null, $count = null) {
125
		$_this =& I18n::getInstance();
126
 
127
		if (strpos($singular, "\r\n") !== false) {
128
			$singular = str_replace("\r\n", "\n", $singular);
129
		}
130
		if ($plural !== null && strpos($plural, "\r\n") !== false) {
131
			$plural = str_replace("\r\n", "\n", $plural);
132
		}
133
 
134
		if (is_numeric($category)) {
135
			$_this->category = $_this->__categories[$category];
136
		}
137
		$language = Configure::read('Config.language');
138
 
139
		if (!empty($_SESSION['Config']['language'])) {
140
			$language = $_SESSION['Config']['language'];
141
		}
142
 
143
		if (($_this->__lang && $_this->__lang !== $language) || !$_this->__lang) {
144
			$lang = $_this->l10n->get($language);
145
			$_this->__lang = $lang;
146
		}
147
 
148
		if (is_null($domain)) {
149
			$domain = 'default';
150
		}
151
		$_this->domain = $domain . '_' . $_this->l10n->locale;
152
 
153
		if (empty($_this->__domains)) {
154
			$_this->__domains = Cache::read($_this->domain, '_cake_core_');
155
		}
156
 
157
		if (!isset($_this->__domains[$_this->category][$_this->__lang][$domain])) {
158
			$_this->__bindTextDomain($domain);
159
			$_this->__cache = true;
160
		}
161
 
162
		if (!isset($count)) {
163
			$plurals = 0;
164
		} elseif (!empty($_this->__domains[$_this->category][$_this->__lang][$domain]["%plural-c"]) && $_this->__noLocale === false) {
165
			$header = $_this->__domains[$_this->category][$_this->__lang][$domain]["%plural-c"];
166
			$plurals = $_this->__pluralGuess($header, $count);
167
		} else {
168
			if ($count != 1) {
169
				$plurals = 1;
170
			} else {
171
				$plurals = 0;
172
			}
173
		}
174
 
175
		if (!empty($_this->__domains[$_this->category][$_this->__lang][$domain][$singular])) {
176
			if (($trans = $_this->__domains[$_this->category][$_this->__lang][$domain][$singular]) || ($plurals) && ($trans = $_this->__domains[$_this->category][$_this->__lang][$domain][$plural])) {
177
				if (is_array($trans)) {
178
					if (isset($trans[$plurals])) {
179
						$trans = $trans[$plurals];
180
					}
181
				}
182
				if (strlen($trans)) {
183
					$singular = $trans;
184
					return $singular;
185
				}
186
			}
187
		}
188
 
189
		if (!empty($plurals)) {
190
			return($plural);
191
		}
192
		return($singular);
193
	}
194
/**
195
 * Attempts to find the plural form of a string.
196
 *
197
 * @param string $header Type
198
 * @param integrer $n Number
199
 * @return integer plural match
200
 * @access private
201
 */
202
	function __pluralGuess($header, $n) {
203
		if (!is_string($header) || $header === "nplurals=1;plural=0;" || !isset($header[0])) {
204
			return 0;
205
		}
206
 
207
		if ($header === "nplurals=2;plural=n!=1;") {
208
			return $n != 1 ? 1 : 0;
209
		} elseif ($header === "nplurals=2;plural=n>1;") {
210
			return $n > 1 ? 1 : 0;
211
		}
212
 
213
		if (strpos($header, "plurals=3")) {
214
			if (strpos($header, "100!=11")) {
215
				if (strpos($header, "10<=4")) {
216
					return $n % 10 === 1 && $n % 100 !== 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
217
				} elseif (strpos($header, "100<10")) {
218
					return $n % 10 === 1 && $n % 100 !== 11 ? 0 : ($n % 10 >= 2 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
219
				}
220
				return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n != 0 ? 1 : 2);
221
			} elseif (strpos($header, "n==2")) {
222
				return $n === 1 ? 0 : ($n === 2 ? 1 : 2);
223
			} elseif (strpos($header, "n==0")) {
224
				return $n === 1 ? 0 : ($n === 0 || ($n % 100 > 0 && $n % 100 < 20) ? 1 : 2);
225
			} elseif (strpos($header, "n>=2")) {
226
				return $n === 1 ? 0 : ($n >= 2 && $n <= 4 ? 1 : 2);
227
			} elseif (strpos($header, "10>=2")) {
228
				return $n === 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
229
			}
230
			return $n % 10 === 1 ? 0 : ($n % 10 === 2 ? 1 : 2);
231
		} elseif (strpos($header, "plurals=4")) {
232
			if (strpos($header, "100==2")) {
233
				return $n % 100 === 1 ? 0 : ($n % 100 === 2 ? 1 : ($n % 100 === 3 || $n % 100 === 4 ? 2 : 3));
234
			} elseif (strpos($header, "n>=3")) {
235
				return $n === 1 ? 0 : ($n === 2 ? 1 : ($n == 0 || ($n >= 3 && $n <= 10) ? 2 : 3));
236
			} elseif (strpos($header, "100>=1")) {
237
				return $n === 1 ? 0 : ($n == 0 || ($n % 100 >= 1 && $n % 100 <= 10) ? 1 : ($n % 100 >= 11 && $n % 100 <= 20 ? 2 : 3));
238
			}
239
		} elseif (strpos($header, "plurals=5")) {
240
			return $n === 1 ? 0 : ($n === 2 ? 1 : ($n >= 3 && $n <= 6 ? 2 : ($n >= 7 && $n <= 10 ? 3 : 4)));
241
		}
242
	}
243
/**
244
 * Binds the given domain to a file in the specified directory.
245
 *
246
 * @param string $domain Domain to bind
247
 * @return string Domain binded
248
 * @access private
249
 */
250
	function __bindTextDomain($domain) {
251
		$this->__noLocale = true;
252
		$core = true;
253
		$merge = array();
254
		$searchPaths = Configure::read('localePaths');
255
		$plugins = Configure::listObjects('plugin');
256
 
257
		if (!empty($plugins)) {
258
			$pluginPaths = Configure::read('pluginPaths');
259
 
260
			foreach ($plugins as $plugin) {
261
				$plugin = Inflector::underscore($plugin);
262
				if ($plugin === $domain) {
263
					foreach ($pluginPaths as $pluginPath) {
264
						$searchPaths[] = $pluginPath . DS . $plugin . DS . 'locale';
265
					}
266
					$searchPaths = array_reverse($searchPaths);
267
					break;
268
				}
269
			}
270
		}
271
 
272
		foreach ($searchPaths as $directory) {
273
			foreach ($this->l10n->languagePath as $lang) {
274
				$file = $directory . DS . $lang . DS . $this->category . DS . $domain;
275
 
276
				if ($core) {
277
					$app = $directory . DS . $lang . DS . $this->category . DS . 'core';
278
					if (file_exists($fn = "$app.mo")) {
279
						$this->__loadMo($fn, $domain);
280
						$this->__noLocale = false;
281
						$merge[$this->category][$this->__lang][$domain] = $this->__domains[$this->category][$this->__lang][$domain];
282
						$core = null;
283
					} elseif (file_exists($fn = "$app.po") && ($f = fopen($fn, "r"))) {
284
						$this->__loadPo($f, $domain);
285
						$this->__noLocale = false;
286
						$merge[$this->category][$this->__lang][$domain] = $this->__domains[$this->category][$this->__lang][$domain];
287
						$core = null;
288
					}
289
				}
290
 
291
				if (file_exists($fn = "$file.mo")) {
292
					$this->__loadMo($fn, $domain);
293
					$this->__noLocale = false;
294
					break 2;
295
				} elseif (file_exists($fn = "$file.po") && ($f = fopen($fn, "r"))) {
296
					$this->__loadPo($f, $domain);
297
					$this->__noLocale = false;
298
					break 2;
299
				}
300
			}
301
		}
302
 
303
		if (empty($this->__domains[$this->category][$this->__lang][$domain])) {
304
			$this->__domains[$this->category][$this->__lang][$domain] = array();
305
			return($domain);
306
		}
307
 
308
		if ($head = $this->__domains[$this->category][$this->__lang][$domain][""]) {
309
			foreach (explode("\n", $head) as $line) {
310
				$header = strtok($line,":");
311
				$line = trim(strtok("\n"));
312
				$this->__domains[$this->category][$this->__lang][$domain]["%po-header"][strtolower($header)] = $line;
313
			}
314
 
315
			if (isset($this->__domains[$this->category][$this->__lang][$domain]["%po-header"]["plural-forms"])) {
316
				$switch = preg_replace("/(?:[() {}\\[\\]^\\s*\\]]+)/", "", $this->__domains[$this->category][$this->__lang][$domain]["%po-header"]["plural-forms"]);
317
				$this->__domains[$this->category][$this->__lang][$domain]["%plural-c"] = $switch;
318
				unset($this->__domains[$this->category][$this->__lang][$domain]["%po-header"]);
319
			}
320
			$this->__domains = Set::pushDiff($this->__domains, $merge);
321
 
322
			if (isset($this->__domains[$this->category][$this->__lang][$domain][null])) {
323
				unset($this->__domains[$this->category][$this->__lang][$domain][null]);
324
			}
325
		}
326
		return($domain);
327
	}
328
/**
329
 * Loads the binary .mo file for translation and sets the values for this translation in the var I18n::__domains
330
 *
331
 * @param resource $file Binary .mo file to load
332
 * @param string $domain Domain where to load file in
333
 * @access private
334
 */
335
	function __loadMo($file, $domain) {
336
		$data = file_get_contents($file);
337
 
338
		if ($data) {
339
			$header = substr($data, 0, 20);
340
			$header = unpack("L1magic/L1version/L1count/L1o_msg/L1o_trn", $header);
341
			extract($header);
342
 
343
			if ((dechex($magic) == '950412de' || dechex($magic) == 'ffffffff950412de') && $version == 0) {
344
				for ($n = 0; $n < $count; $n++) {
345
					$r = unpack("L1len/L1offs", substr($data, $o_msg + $n * 8, 8));
346
					$msgid = substr($data, $r["offs"], $r["len"]);
347
					unset($msgid_plural);
348
 
349
					if (strpos($msgid, "\000")) {
350
						list($msgid, $msgid_plural) = explode("\000", $msgid);
351
					}
352
					$r = unpack("L1len/L1offs", substr($data, $o_trn + $n * 8, 8));
353
					$msgstr = substr($data, $r["offs"], $r["len"]);
354
 
355
					if (strpos($msgstr, "\000")) {
356
						$msgstr = explode("\000", $msgstr);
357
					}
358
					$this->__domains[$this->category][$this->__lang][$domain][$msgid] = $msgstr;
359
 
360
					if (isset($msgid_plural)) {
361
						$this->__domains[$this->category][$this->__lang][$domain][$msgid_plural] =& $this->__domains[$this->category][$this->__lang][$domain][$msgid];
362
					}
363
				}
364
			}
365
		}
366
	}
367
/**
368
 * Loads the text .po file for translation and sets the values for this translation in the var I18n::__domains
369
 *
370
 * @param resource $file Text .po file to load
371
 * @param string $domain Domain to load file in
372
 * @return array Binded domain elements
373
 * @access private
374
 */
375
	function __loadPo($file, $domain) {
376
		$type = 0;
377
		$translations = array();
378
		$translationKey = "";
379
		$plural = 0;
380
		$header = "";
381
 
382
		do {
383
			$line = trim(fgets($file, 1024));
384
			if ($line == "" || $line[0] == "#") {
385
				continue;
386
			}
387
			if (preg_match("/msgid[[:space:]]+\"(.+)\"$/i", $line, $regs)) {
388
				$type = 1;
389
				$translationKey = stripcslashes($regs[1]);
390
			} elseif (preg_match("/msgid[[:space:]]+\"\"$/i", $line, $regs)) {
391
				$type = 2;
392
				$translationKey = "";
393
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && ($type == 1 || $type == 2 || $type == 3)) {
394
				$type = 3;
395
				$translationKey .= stripcslashes($regs[1]);
396
			} elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) {
397
				$translations[$translationKey] = stripcslashes($regs[1]);
398
				$type = 4;
399
			} elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) {
400
				$type = 4;
401
				$translations[$translationKey] = "";
402
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 4 && $translationKey) {
403
				$translations[$translationKey] .= stripcslashes($regs[1]);
404
			} elseif (preg_match("/msgid_plural[[:space:]]+\".*\"$/i", $line, $regs)) {
405
				$type = 6;
406
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 6 && $translationKey) {
407
				$type = 6;
408
			} elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 6 || $type == 7) && $translationKey) {
409
				$plural = $regs[1];
410
				$translations[$translationKey][$plural] = stripcslashes($regs[2]);
411
				$type = 7;
412
			} elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"\"$/i", $line, $regs) && ($type == 6 || $type == 7) && $translationKey) {
413
				$plural = $regs[1];
414
				$translations[$translationKey][$plural] = "";
415
				$type = 7;
416
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 7 && $translationKey) {
417
				$translations[$translationKey][$plural] .= stripcslashes($regs[1]);
418
			} elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && $type == 2 && !$translationKey) {
419
				$header .= stripcslashes($regs[1]);
420
				$type = 5;
421
			} elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && !$translationKey) {
422
				$header = "";
423
				$type = 5;
424
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 5) {
425
				$header .= stripcslashes($regs[1]);
426
			} else {
427
				unset($translations[$translationKey]);
428
				$type = 0;
429
				$translationKey = "";
430
				$plural = 0;
431
			}
432
		} while (!feof($file));
433
		fclose($file);
434
		$merge[""] = $header;
435
		return $this->__domains[$this->category][$this->__lang][$domain] = array_merge($merge ,$translations);
436
	}
437
/**
438
 * Object destructor
439
 *
440
 * Write cache file if changes have been made to the $__map or $__paths
441
 * @access private
442
 */
443
	function __destruct() {
444
		if ($this->__cache) {
445
			Cache::write($this->domain, array_filter($this->__domains), '_cake_core_');
446
		}
447
	}
448
}
449
?>