7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
16 * @package Zend_Translate
17 * @subpackage Zend_Translate_Adapter
18 * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
19 * @license http://framework.zend.com/license/new-bsd New BSD License
20 * @version $Id: Adapter.php 17761 2009-08-22 22:23:12Z thomas $
26 require_once 'Zend/Locale.php';
29 * @see Zend_Translate_Plural
31 require_once 'Zend/Translate/Plural.php';
34 * Basic adapter class for each translation source adapter
37 * @package Zend_Translate
38 * @subpackage Zend_Translate_Adapter
39 * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
40 * @license http://framework.zend.com/license/new-bsd New BSD License
42 abstract class Zend_Translate_Adapter
{
44 * Shows if locale detection is in automatic level
47 private $_automatic = true;
50 * Internal cache for all adapters
51 * @var Zend_Cache_Core
53 protected static $_cache = null;
56 * Scans for the locale within the name of the directory
59 const LOCALE_DIRECTORY
= 'directory';
62 * Scans for the locale within the name of the file
65 const LOCALE_FILENAME
= 'filename';
68 * Array with all options, each adapter can have own additional options
69 * 'clear' => clears already loaded data when adding new files
70 * 'scan' => searches for translation files using the LOCALE constants
71 * 'locale' => the actual set locale to use
74 protected $_options = array(
76 'disableNotices' => false,
80 'logMessage' => "Untranslated message within '%locale%': %message%",
81 'logUntranslated' => false,
89 protected $_translate = array();
92 * Generates the adapter
94 * @param string|array $data Translation data or filename for this adapter
95 * @param string|Zend_Locale $locale (optional) Locale/Language to set, identical with Locale
96 * identifiers see Zend_Locale for more information
97 * @param array $options (optional) Options for the adaptor
98 * @throws Zend_Translate_Exception
101 public function __construct($data, $locale = null, array $options = array())
103 if (isset(self
::$_cache)) {
104 $id = 'Zend_Translate_' . $this->toString() . '_Options';
105 $result = self
::$_cache->load($id);
107 $this->_options
= unserialize($result);
111 if (($locale === "auto") or ($locale === null)) {
112 $this->_automatic
= true;
114 $this->_automatic
= false;
117 $this->addTranslation($data, $locale, $options);
118 if ($this->getLocale() !== (string) $locale) {
119 $this->setLocale($locale);
124 * Add translation data
126 * It may be a new language or additional data for existing language
127 * If $clear parameter is true, then translation data for specified
128 * language is replaced and added otherwise
130 * @param array|string $data Translation data
131 * @param string|Zend_Locale $locale (optional) Locale/Language to add data for, identical
132 * with locale identifier, see Zend_Locale for more information
133 * @param array $options (optional) Option for this Adapter
134 * @throws Zend_Translate_Exception
135 * @return Zend_Translate_Adapter Provides fluent interface
137 public function addTranslation($data, $locale = null, array $options = array())
140 $locale = Zend_Locale
::findLocale($locale);
141 } catch (Zend_Locale_Exception
$e) {
142 require_once 'Zend/Translate/Exception.php';
143 throw new Zend_Translate_Exception("The given Language '{$locale}' does not exist");
146 $originate = (string) $locale;
148 $this->setOptions($options);
149 if (is_string($data) and is_dir($data)) {
150 $data = realpath($data);
152 foreach (new RecursiveIteratorIterator(
153 new RecursiveDirectoryIterator($data, RecursiveDirectoryIterator
::KEY_AS_PATHNAME
),
154 RecursiveIteratorIterator
::SELF_FIRST
) as $directory => $info) {
155 $file = $info->getFilename();
156 if (strpos($directory, DIRECTORY_SEPARATOR
. $this->_options
['ignore']) !== false) {
157 // ignore files matching first characters from option 'ignore' and all files below
161 if ($info->isDir()) {
162 // pathname as locale
163 if (($this->_options
['scan'] === self
::LOCALE_DIRECTORY
) and (Zend_Locale
::isLocale($file, true, false))) {
164 if (strlen($prev) <= strlen($file)) {
166 $prev = (string) $locale;
169 } else if ($info->isFile()) {
170 // filename as locale
171 if ($this->_options
['scan'] === self
::LOCALE_FILENAME
) {
172 $filename = explode('.', $file);
173 array_pop($filename);
174 $filename = implode('.', $filename);
175 if (Zend_Locale
::isLocale((string) $filename, true, false)) {
176 $locale = (string) $filename;
178 $parts = explode('.', $file);
180 foreach($parts as $token) {
181 $parts2 +
= explode('_', $token);
183 $parts = array_merge($parts, $parts2);
185 foreach($parts as $token) {
186 $parts2 +
= explode('-', $token);
188 $parts = array_merge($parts, $parts2);
189 $parts = array_unique($parts);
191 foreach($parts as $token) {
192 if (Zend_Locale
::isLocale($token, true, false)) {
193 if (strlen($prev) <= strlen($token)) {
202 $this->_addTranslationData($info->getPathname(), (string) $locale, $this->_options
);
203 if ((isset($this->_translate
[(string) $locale]) === true) and (count($this->_translate
[(string) $locale]) > 0)) {
204 $this->setLocale($locale);
206 } catch (Zend_Translate_Exception
$e) {
207 // ignore failed sources while scanning
212 $this->_addTranslationData($data, (string) $locale, $this->_options
);
213 if ((isset($this->_translate
[(string) $locale]) === true) and (count($this->_translate
[(string) $locale]) > 0)) {
214 $this->setLocale($locale);
218 if ((isset($this->_translate
[$originate]) === true) and (count($this->_translate
[$originate]) > 0) and ($originate !== (string) $locale)) {
219 $this->setLocale($originate);
226 * Sets new adapter options
228 * @param array $options Adapter options
229 * @throws Zend_Translate_Exception
230 * @return Zend_Translate_Adapter Provides fluent interface
232 public function setOptions(array $options = array())
236 foreach ($options as $key => $option) {
237 if ($key == 'locale') {
239 } else if ((isset($this->_options
[$key]) and ($this->_options
[$key] != $option)) or
240 !isset($this->_options
[$key])) {
241 if (($key == 'log') && !($option instanceof Zend_Log
)) {
242 require_once 'Zend/Translate/Exception.php';
243 throw new Zend_Translate_Exception('Instance of Zend_Log expected for option log');
246 $this->_options
[$key] = $option;
251 if ($locale !== null) {
252 $this->setLocale($locale);
255 if (isset(self
::$_cache) and ($change == true)) {
256 $id = 'Zend_Translate_' . $this->toString() . '_Options';
257 self
::$_cache->save( serialize($this->_options
), $id, array('Zend_Translate'));
264 * Returns the adapters name and it's options
266 * @param string|null $optionKey String returns this option
267 * null returns all options
268 * @return integer|string|array|null
270 public function getOptions($optionKey = null)
272 if ($optionKey === null) {
273 return $this->_options
;
276 if (isset($this->_options
[$optionKey]) === true) {
277 return $this->_options
[$optionKey];
286 * @return Zend_Locale|string|null
288 public function getLocale()
290 return $this->_options
['locale'];
296 * @param string|Zend_Locale $locale Locale to set
297 * @throws Zend_Translate_Exception
298 * @return Zend_Translate_Adapter Provides fluent interface
300 public function setLocale($locale)
302 if (($locale === "auto") or ($locale === null)) {
303 $this->_automatic
= true;
305 $this->_automatic
= false;
309 $locale = Zend_Locale
::findLocale($locale);
310 } catch (Zend_Locale_Exception
$e) {
311 require_once 'Zend/Translate/Exception.php';
312 throw new Zend_Translate_Exception("The given Language ({$locale}) does not exist");
315 if (!isset($this->_translate
[$locale])) {
316 $temp = explode('_', $locale);
317 if (!isset($this->_translate
[$temp[0]]) and !isset($this->_translate
[$locale])) {
318 if (!$this->_options
['disableNotices']) {
319 if ($this->_options
['log']) {
320 $this->_options
['log']->notice("The language '{$locale}' has to be added before it can be used.");
322 trigger_error("The language '{$locale}' has to be added before it can be used.", E_USER_NOTICE
);
330 if (empty($this->_translate
[$locale])) {
331 if (!$this->_options
['disableNotices']) {
332 if ($this->_options
['log']) {
333 $this->_options
['log']->notice("No translation for the language '{$locale}' available.");
335 trigger_error("No translation for the language '{$locale}' available.", E_USER_NOTICE
);
340 if ($this->_options
['locale'] != $locale) {
341 $this->_options
['locale'] = $locale;
343 if (isset(self
::$_cache)) {
344 $id = 'Zend_Translate_' . $this->toString() . '_Options';
345 self
::$_cache->save( serialize($this->_options
), $id, array('Zend_Translate'));
353 * Returns the available languages from this adapter
357 public function getList()
359 $list = array_keys($this->_translate
);
361 foreach($list as $value) {
362 if (!empty($this->_translate
[$value])) {
363 $result[$value] = $value;
370 * Returns all available message ids from this adapter
371 * If no locale is given, the actual language will be used
373 * @param string|Zend_Locale $locale (optional) Language to return the message ids from
376 public function getMessageIds($locale = null)
378 if (empty($locale) or !$this->isAvailable($locale)) {
379 $locale = $this->_options
['locale'];
382 return array_keys($this->_translate
[(string) $locale]);
386 * Returns all available translations from this adapter
387 * If no locale is given, the actual language will be used
388 * If 'all' is given the complete translation dictionary will be returned
390 * @param string|Zend_Locale $locale (optional) Language to return the messages from
393 public function getMessages($locale = null)
395 if ($locale === 'all') {
396 return $this->_translate
;
399 if ((empty($locale) === true) or ($this->isAvailable($locale) === false)) {
400 $locale = $this->_options
['locale'];
403 return $this->_translate
[(string) $locale];
407 * Is the wished language available ?
410 * @param string|Zend_Locale $locale Language to search for, identical with locale identifier,
411 * @see Zend_Locale for more information
414 public function isAvailable($locale)
416 $return = isset($this->_translate
[(string) $locale]);
421 * Load translation data
424 * @param string|Zend_Locale $locale
425 * @param array $options (optional)
428 abstract protected function _loadTranslationData($data, $locale, array $options = array());
431 * Internal function for adding translation data
433 * It may be a new language or additional data for existing language
434 * If $clear parameter is true, then translation data for specified
435 * language is replaced and added otherwise
438 * @param array|string $data Translation data
439 * @param string|Zend_Locale $locale Locale/Language to add data for, identical with locale identifier,
440 * @see Zend_Locale for more information
441 * @param array $options (optional) Option for this Adapter
442 * @throws Zend_Translate_Exception
443 * @return Zend_Translate_Adapter Provides fluent interface
445 private function _addTranslationData($data, $locale, array $options = array())
448 $locale = Zend_Locale
::findLocale($locale);
449 } catch (Zend_Locale_Exception
$e) {
450 require_once 'Zend/Translate/Exception.php';
451 throw new Zend_Translate_Exception("The given Language '{$locale}' does not exist");
454 if ($options['clear'] ||
!isset($this->_translate
[$locale])) {
455 $this->_translate
[$locale] = array();
459 if (isset(self
::$_cache)) {
460 $id = 'Zend_Translate_' . md5(serialize($data)) . '_' . $this->toString();
461 $result = self
::$_cache->load($id);
463 $temp = unserialize($result);
469 $temp = $this->_loadTranslationData($data, $locale, $options);
476 $keys = array_keys($temp);
477 foreach($keys as $key) {
478 if (!isset($this->_translate
[$key])) {
479 $this->_translate
[$key] = array();
482 $this->_translate
[$key] = $temp[$key] +
$this->_translate
[$key];
485 if ($this->_automatic
=== true) {
486 $find = new Zend_Locale($locale);
487 $browser = $find->getEnvironment() +
$find->getBrowser();
489 foreach($browser as $language => $quality) {
490 if (isset($this->_translate
[$language])) {
491 $this->_options
['locale'] = $language;
497 if (($read) and (isset(self
::$_cache))) {
498 $id = 'Zend_Translate_' . md5(serialize($data)) . '_' . $this->toString();
499 self
::$_cache->save( serialize($temp), $id, array('Zend_Translate'));
506 * Translates the given string
507 * returns the translation
510 * @param string|array $messageId Translation string, or Array for plural translations
511 * @param string|Zend_Locale $locale (optional) Locale/Language to use, identical with
512 * locale identifier, @see Zend_Locale for more information
515 public function translate($messageId, $locale = null)
517 if ($locale === null) {
518 $locale = $this->_options
['locale'];
522 if (is_array($messageId)) {
523 if (count($messageId) > 2) {
524 $number = array_pop($messageId);
525 if (!is_numeric($number)) {
527 $number = array_pop($messageId);
532 $plural = $messageId;
533 $messageId = $messageId[0];
535 $messageId = $messageId[0];
539 if (!Zend_Locale
::isLocale($locale, true, false)) {
540 if (!Zend_Locale
::isLocale($locale, false, false)) {
541 // language does not exist, return original string
542 $this->_log($messageId, $locale);
543 if ($plural === null) {
547 $rule = Zend_Translate_Plural
::getPlural($number, $plocale);
548 if (!isset($plural[$rule])) {
552 return $plural[$rule];
555 $locale = new Zend_Locale($locale);
558 $locale = (string) $locale;
559 if (isset($this->_translate
[$locale][$messageId])) {
560 // return original translation
561 if ($plural === null) {
562 return $this->_translate
[$locale][$messageId];
565 $rule = Zend_Translate_Plural
::getPlural($number, $locale);
566 if (isset($this->_translate
[$locale][$plural[0]][$rule])) {
567 return $this->_translate
[$locale][$plural[0]][$rule];
569 } else if (strlen($locale) != 2) {
570 // faster than creating a new locale and separate the leading part
571 $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
573 if (isset($this->_translate
[$locale][$messageId])) {
574 // return regionless translation (en_US -> en)
575 if ($plural === null) {
576 return $this->_translate
[$locale][$messageId];
579 $rule = Zend_Translate_Plural
::getPlural($number, $locale);
580 if (isset($this->_translate
[$locale][$plural[0]][$rule])) {
581 return $this->_translate
[$locale][$plural[0]][$rule];
586 $this->_log($messageId, $locale);
587 if ($plural === null) {
591 $rule = Zend_Translate_Plural
::getPlural($number, $plocale);
592 if (!isset($plural[$rule])) {
596 return $plural[$rule];
600 * Translates the given string using plural notations
601 * Returns the translated string
604 * @param string $singular Singular translation string
605 * @param string $plural Plural translation string
606 * @param integer $number Number for detecting the correct plural
607 * @param string|Zend_Locale $locale (Optional) Locale/Language to use, identical with
608 * locale identifier, @see Zend_Locale for more information
611 public function plural($singular, $plural, $number, $locale = null)
613 return $this->translate(array($singular, $plural, $number), $locale);
617 * Logs a message when the log option is set
619 * @param string $message Message to log
620 * @param String $locale Locale to log
622 protected function _log($message, $locale) {
623 if ($this->_options
['logUntranslated']) {
624 $message = str_replace('%message%', $message, $this->_options
['logMessage']);
625 $message = str_replace('%locale%', $locale, $message);
626 if ($this->_options
['log']) {
627 $this->_options
['log']->notice($message);
629 trigger_error($message, E_USER_NOTICE
);
635 * Translates the given string
636 * returns the translation
638 * @param string $messageId Translation string
639 * @param string|Zend_Locale $locale (optional) Locale/Language to use, identical with locale
640 * identifier, @see Zend_Locale for more information
643 public function _($messageId, $locale = null)
645 return $this->translate($messageId, $locale);
649 * Checks if a string is translated within the source or not
652 * @param string $messageId Translation string
653 * @param boolean $original (optional) Allow translation only for original language
654 * when true, a translation for 'en_US' would give false when it can
655 * be translated with 'en' only
656 * @param string|Zend_Locale $locale (optional) Locale/Language to use, identical with locale identifier,
657 * see Zend_Locale for more information
660 public function isTranslated($messageId, $original = false, $locale = null)
662 if (($original !== false) and ($original !== true)) {
667 if ($locale === null) {
668 $locale = $this->_options
['locale'];
671 if (!Zend_Locale
::isLocale($locale, true, false)) {
672 if (!Zend_Locale
::isLocale($locale, false, false)) {
673 // language does not exist, return original string
674 $this->_log($messageId, $locale);
678 $locale = new Zend_Locale($locale);
681 $locale = (string) $locale;
682 if (isset($this->_translate
[$locale][$messageId]) === true) {
683 // return original translation
685 } else if ((strlen($locale) != 2) and ($original === false)) {
686 // faster than creating a new locale and separate the leading part
687 $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
689 if (isset($this->_translate
[$locale][$messageId]) === true) {
690 // return regionless translation (en_US -> en)
695 // No translation found, return original
696 $this->_log($messageId, $locale);
701 * Returns the set cache
703 * @return Zend_Cache_Core The set cache
705 public static function getCache()
707 return self
::$_cache;
711 * Sets a cache for all Zend_Translate_Adapters
713 * @param Zend_Cache_Core $cache Cache to store to
715 public static function setCache(Zend_Cache_Core
$cache)
717 self
::$_cache = $cache;
721 * Returns true when a cache is set
725 public static function hasCache()
727 if (self
::$_cache !== null) {
735 * Removes any set cache
739 public static function removeCache()
741 self
::$_cache = null;
745 * Clears all set cache data
749 public static function clearCache()
751 require_once 'Zend/Cache.php';
752 self
::$_cache->clean(Zend_Cache
::CLEANING_MODE_MATCHING_TAG
, array('Zend_Translate'));
756 * Returns the adapter name
760 abstract public function toString();