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-2010 Zend Technologies USA Inc. (http://www.zend.com)
19 * @license http://framework.zend.com/license/new-bsd New BSD License
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-2010 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 value to see already routed languages
53 private $_routed = array();
56 * Internal cache for all adapters
57 * @var Zend_Cache_Core
59 protected static $_cache = null;
62 * Scans for the locale within the name of the directory
65 const LOCALE_DIRECTORY
= 'directory';
68 * Scans for the locale within the name of the file
71 const LOCALE_FILENAME
= 'filename';
74 * Array with all options, each adapter can have own additional options
75 * 'clear' => when true, clears already loaded translations when adding new files
76 * 'content' => content to translate or file or directory with content
77 * 'disableNotices' => when true, omits notices from being displayed
78 * 'ignore' => a prefix for files and directories which are not being added
79 * 'locale' => the actual set locale to use
80 * 'log' => a instance of Zend_Log where logs are written to
81 * 'logMessage' => message to be logged
82 * 'logUntranslated' => when true, untranslated messages are not logged
83 * 'reload' => reloads the cache by reading the content again
84 * 'scan' => searches for translation files using the LOCALE constants
88 protected $_options = array(
91 'disableNotices' => false,
95 'logMessage' => "Untranslated message within '%locale%': %message%",
96 'logUntranslated' => false,
106 protected $_translate = array();
109 * Generates the adapter
111 * @param array|Zend_Config $options Translation options for this adapter
112 * @throws Zend_Translate_Exception
115 public function __construct($options = array())
117 if ($options instanceof Zend_Config
) {
118 $options = $options->toArray();
119 } else if (func_num_args() > 1) {
120 $args = func_get_args();
122 $options['content'] = array_shift($args);
125 $options['locale'] = array_shift($args);
129 $opt = array_shift($args);
130 $options = array_merge($opt, $options);
132 } else if (!is_array($options)) {
133 $options = array('content' => $options);
136 if (isset(self
::$_cache)) {
137 $id = 'Zend_Translate_' . $this->toString() . '_Options';
138 $result = self
::$_cache->load($id);
140 $this->_options
= $result;
144 if (empty($options['locale']) ||
($options['locale'] === "auto")) {
145 $this->_automatic
= true;
147 $this->_automatic
= false;
151 if (!empty($options['locale'])) {
152 $locale = $options['locale'];
153 unset($options['locale']);
156 $this->setOptions($options);
157 $options['locale'] = $locale;
159 if (!empty($options['content'])) {
160 $this->addTranslation($options);
163 if ($this->getLocale() !== (string) $options['locale']) {
164 $this->setLocale($options['locale']);
171 * This may be a new language or additional content for an existing language
172 * If the key 'clear' is true, then translations for the specified
173 * language will be replaced and added otherwise
175 * @param array|Zend_Config $options Options and translations to be added
176 * @throws Zend_Translate_Exception
177 * @return Zend_Translate_Adapter Provides fluent interface
179 public function addTranslation($options = array())
181 if ($options instanceof Zend_Config
) {
182 $options = $options->toArray();
183 } else if (func_num_args() > 1) {
184 $args = func_get_args();
186 $options['content'] = array_shift($args);
189 $options['locale'] = array_shift($args);
193 $opt = array_shift($args);
194 $options = array_merge($opt, $options);
196 } else if (!is_array($options)) {
197 $options = array('content' => $options);
201 if (!empty($options['locale'])) {
202 $originate = (string) $options['locale'];
205 if ((array_key_exists('log', $options)) && !($options['log'] instanceof Zend_Log
)) {
206 require_once 'Zend/Translate/Exception.php';
207 throw new Zend_Translate_Exception('Instance of Zend_Log expected for option log');
211 if (!($options['content'] instanceof Zend_Translate
) && !($options['content'] instanceof Zend_Translate_Adapter
)) {
212 if (empty($options['locale'])) {
213 $options['locale'] = null;
216 $options['locale'] = Zend_Locale
::findLocale($options['locale']);
218 } catch (Zend_Locale_Exception
$e) {
219 require_once 'Zend/Translate/Exception.php';
220 throw new Zend_Translate_Exception("The given Language '{$options['locale']}' does not exist", 0, $e);
223 $options = $options +
$this->_options
;
224 if (is_string($options['content']) and is_dir($options['content'])) {
225 $options['content'] = realpath($options['content']);
227 foreach (new RecursiveIteratorIterator(
228 new RecursiveDirectoryIterator($options['content'], RecursiveDirectoryIterator
::KEY_AS_PATHNAME
),
229 RecursiveIteratorIterator
::SELF_FIRST
) as $directory => $info) {
230 $file = $info->getFilename();
231 if (is_array($options['ignore'])) {
232 foreach ($options['ignore'] as $key => $ignore) {
233 if (strpos($key, 'regex') !== false) {
234 if (preg_match($ignore, $directory)) {
235 // ignore files matching the given regex from option 'ignore' and all files below
238 } else if (strpos($directory, DIRECTORY_SEPARATOR
. $ignore) !== false) {
239 // ignore files matching first characters from option 'ignore' and all files below
244 if (strpos($directory, DIRECTORY_SEPARATOR
. $options['ignore']) !== false) {
245 // ignore files matching first characters from option 'ignore' and all files below
250 if ($info->isDir()) {
251 // pathname as locale
252 if (($options['scan'] === self
::LOCALE_DIRECTORY
) and (Zend_Locale
::isLocale($file, true, false))) {
253 $options['locale'] = $file;
254 $prev = (string) $options['locale'];
256 } else if ($info->isFile()) {
257 // filename as locale
258 if ($options['scan'] === self
::LOCALE_FILENAME
) {
259 $filename = explode('.', $file);
260 array_pop($filename);
261 $filename = implode('.', $filename);
262 if (Zend_Locale
::isLocale((string) $filename, true, false)) {
263 $options['locale'] = (string) $filename;
265 $parts = explode('.', $file);
267 foreach($parts as $token) {
268 $parts2 +
= explode('_', $token);
270 $parts = array_merge($parts, $parts2);
272 foreach($parts as $token) {
273 $parts2 +
= explode('-', $token);
275 $parts = array_merge($parts, $parts2);
276 $parts = array_unique($parts);
278 foreach($parts as $token) {
279 if (Zend_Locale
::isLocale($token, true, false)) {
280 if (strlen($prev) <= strlen($token)) {
281 $options['locale'] = $token;
290 $options['content'] = $info->getPathname();
291 $this->_addTranslationData($options);
292 } catch (Zend_Translate_Exception
$e) {
293 // ignore failed sources while scanning
298 $this->_addTranslationData($options);
301 if ((isset($this->_translate
[$originate]) === true) and (count($this->_translate
[$originate]) > 0)) {
302 $this->setLocale($originate);
309 * Sets new adapter options
311 * @param array $options Adapter options
312 * @throws Zend_Translate_Exception
313 * @return Zend_Translate_Adapter Provides fluent interface
315 public function setOptions(array $options = array())
319 foreach ($options as $key => $option) {
320 if ($key == 'locale') {
322 } else if ((isset($this->_options
[$key]) and ($this->_options
[$key] != $option)) or
323 !isset($this->_options
[$key])) {
324 if (($key == 'log') && !($option instanceof Zend_Log
)) {
325 require_once 'Zend/Translate/Exception.php';
326 throw new Zend_Translate_Exception('Instance of Zend_Log expected for option log');
329 $this->_options
[$key] = $option;
334 if ($locale !== null) {
335 $this->setLocale($locale);
338 if (isset(self
::$_cache) and ($change == true)) {
339 $id = 'Zend_Translate_' . $this->toString() . '_Options';
340 self
::$_cache->save($this->_options
, $id, array('Zend_Translate'));
347 * Returns the adapters name and it's options
349 * @param string|null $optionKey String returns this option
350 * null returns all options
351 * @return integer|string|array|null
353 public function getOptions($optionKey = null)
355 if ($optionKey === null) {
356 return $this->_options
;
359 if (isset($this->_options
[$optionKey]) === true) {
360 return $this->_options
[$optionKey];
369 * @return Zend_Locale|string|null
371 public function getLocale()
373 return $this->_options
['locale'];
379 * @param string|Zend_Locale $locale Locale to set
380 * @throws Zend_Translate_Exception
381 * @return Zend_Translate_Adapter Provides fluent interface
383 public function setLocale($locale)
385 if (($locale === "auto") or ($locale === null)) {
386 $this->_automatic
= true;
388 $this->_automatic
= false;
392 $locale = Zend_Locale
::findLocale($locale);
393 } catch (Zend_Locale_Exception
$e) {
394 require_once 'Zend/Translate/Exception.php';
395 throw new Zend_Translate_Exception("The given Language ({$locale}) does not exist", 0, $e);
398 if (!isset($this->_translate
[$locale])) {
399 $temp = explode('_', $locale);
400 if (!isset($this->_translate
[$temp[0]]) and !isset($this->_translate
[$locale])) {
401 if (!$this->_options
['disableNotices']) {
402 if ($this->_options
['log']) {
403 $this->_options
['log']->notice("The language '{$locale}' has to be added before it can be used.");
405 trigger_error("The language '{$locale}' has to be added before it can be used.", E_USER_NOTICE
);
413 if (empty($this->_translate
[$locale])) {
414 if (!$this->_options
['disableNotices']) {
415 if ($this->_options
['log']) {
416 $this->_options
['log']->notice("No translation for the language '{$locale}' available.");
418 trigger_error("No translation for the language '{$locale}' available.", E_USER_NOTICE
);
423 if ($this->_options
['locale'] != $locale) {
424 $this->_options
['locale'] = $locale;
426 if (isset(self
::$_cache)) {
427 $id = 'Zend_Translate_' . $this->toString() . '_Options';
428 self
::$_cache->save($this->_options
, $id, array('Zend_Translate'));
436 * Returns the available languages from this adapter
440 public function getList()
442 $list = array_keys($this->_translate
);
444 foreach($list as $value) {
445 if (!empty($this->_translate
[$value])) {
446 $result[$value] = $value;
453 * Returns the message id for a given translation
454 * If no locale is given, the actual language will be used
456 * @param string $message Message to get the key for
457 * @param string|Zend_Locale $locale (optional) Language to return the message ids from
458 * @return string|array|false
460 public function getMessageId($message, $locale = null)
462 if (empty($locale) or !$this->isAvailable($locale)) {
463 $locale = $this->_options
['locale'];
466 return array_search($message, $this->_translate
[(string) $locale]);
470 * Returns all available message ids from this adapter
471 * If no locale is given, the actual language will be used
473 * @param string|Zend_Locale $locale (optional) Language to return the message ids from
476 public function getMessageIds($locale = null)
478 if (empty($locale) or !$this->isAvailable($locale)) {
479 $locale = $this->_options
['locale'];
482 return array_keys($this->_translate
[(string) $locale]);
486 * Returns all available translations from this adapter
487 * If no locale is given, the actual language will be used
488 * If 'all' is given the complete translation dictionary will be returned
490 * @param string|Zend_Locale $locale (optional) Language to return the messages from
493 public function getMessages($locale = null)
495 if ($locale === 'all') {
496 return $this->_translate
;
499 if ((empty($locale) === true) or ($this->isAvailable($locale) === false)) {
500 $locale = $this->_options
['locale'];
503 return $this->_translate
[(string) $locale];
507 * Is the wished language available ?
510 * @param string|Zend_Locale $locale Language to search for, identical with locale identifier,
511 * @see Zend_Locale for more information
514 public function isAvailable($locale)
516 $return = isset($this->_translate
[(string) $locale]);
521 * Load translation data
524 * @param string|Zend_Locale $locale
525 * @param array $options (optional)
528 abstract protected function _loadTranslationData($data, $locale, array $options = array());
531 * Internal function for adding translation data
533 * This may be a new language or additional data for an existing language
534 * If the options 'clear' is true, then the translation data for the specified
535 * language is replaced and added otherwise
538 * @param array|Zend_Config $content Translation data to add
539 * @throws Zend_Translate_Exception
540 * @return Zend_Translate_Adapter Provides fluent interface
542 private function _addTranslationData($options = array())
544 if ($options instanceof Zend_Config
) {
545 $options = $options->toArray();
546 } else if (func_num_args() > 1) {
547 $args = func_get_args();
548 $options['content'] = array_shift($args);
551 $options['locale'] = array_shift($args);
555 $options +
= array_shift($args);
559 if (($options['content'] instanceof Zend_Translate
) ||
($options['content'] instanceof Zend_Translate_Adapter
)) {
560 $options['usetranslateadapter'] = true;
561 if (!empty($options['locale'])) {
562 $options['content'] = $options['content']->getMessages($options['locale']);
564 $locales = $options['content']->getList();
565 foreach ($locales as $locale) {
566 $options['locale'] = $locale;
567 $options['content'] = $options['content']->getMessages($locale);
568 $this->_addTranslationData($options);
576 $options['locale'] = Zend_Locale
::findLocale($options['locale']);
577 } catch (Zend_Locale_Exception
$e) {
578 require_once 'Zend/Translate/Exception.php';
579 throw new Zend_Translate_Exception("The given Language '{$options['locale']}' does not exist", 0, $e);
582 if ($options['clear'] ||
!isset($this->_translate
[$options['locale']])) {
583 $this->_translate
[$options['locale']] = array();
587 if (isset(self
::$_cache)) {
588 $id = 'Zend_Translate_' . md5(serialize($options['content'])) . '_' . $this->toString();
589 $temp = self
::$_cache->load($id);
595 if ($options['reload']) {
600 if (!empty($options['usetranslateadapter'])) {
601 $temp = array($options['locale'] => $options['content']);
603 $temp = $this->_loadTranslationData($options['content'], $options['locale'], $options);
611 $keys = array_keys($temp);
612 foreach($keys as $key) {
613 if (!isset($this->_translate
[$key])) {
614 $this->_translate
[$key] = array();
617 if (array_key_exists($key, $temp) && is_array($temp[$key])) {
618 $this->_translate
[$key] = $temp[$key] +
$this->_translate
[$key];
622 if ($this->_automatic
=== true) {
623 $find = new Zend_Locale($options['locale']);
624 $browser = $find->getEnvironment() +
$find->getBrowser();
626 foreach($browser as $language => $quality) {
627 if (isset($this->_translate
[$language])) {
628 $this->_options
['locale'] = $language;
634 if (($read) and (isset(self
::$_cache))) {
635 $id = 'Zend_Translate_' . md5(serialize($options['content'])) . '_' . $this->toString();
636 self
::$_cache->save($temp, $id, array('Zend_Translate'));
643 * Translates the given string
644 * returns the translation
647 * @param string|array $messageId Translation string, or Array for plural translations
648 * @param string|Zend_Locale $locale (optional) Locale/Language to use, identical with
649 * locale identifier, @see Zend_Locale for more information
652 public function translate($messageId, $locale = null)
654 if ($locale === null) {
655 $locale = $this->_options
['locale'];
659 if (is_array($messageId)) {
660 if (count($messageId) > 2) {
661 $number = array_pop($messageId);
662 if (!is_numeric($number)) {
664 $number = array_pop($messageId);
669 $plural = $messageId;
670 $messageId = $messageId[0];
672 $messageId = $messageId[0];
676 if (!Zend_Locale
::isLocale($locale, true, false)) {
677 if (!Zend_Locale
::isLocale($locale, false, false)) {
678 // language does not exist, return original string
679 $this->_log($messageId, $locale);
680 // use rerouting when enabled
681 if (!empty($this->_options
['route'])) {
682 if (array_key_exists($locale, $this->_options
['route']) &&
683 !array_key_exists($locale, $this->_routed
)) {
684 $this->_routed
[$locale] = true;
685 return $this->translate($messageId, $this->_options
['route'][$locale]);
688 $this->_routed
= array();
691 if ($plural === null) {
695 $rule = Zend_Translate_Plural
::getPlural($number, $plocale);
696 if (!isset($plural[$rule])) {
700 return $plural[$rule];
703 $locale = new Zend_Locale($locale);
706 $locale = (string) $locale;
707 if ((is_string($messageId) ||
is_int($messageId)) && isset($this->_translate
[$locale][$messageId])) {
708 // return original translation
709 if ($plural === null) {
710 return $this->_translate
[$locale][$messageId];
713 $rule = Zend_Translate_Plural
::getPlural($number, $locale);
714 if (isset($this->_translate
[$locale][$plural[0]][$rule])) {
715 return $this->_translate
[$locale][$plural[0]][$rule];
717 } else if (strlen($locale) != 2) {
718 // faster than creating a new locale and separate the leading part
719 $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
721 if ((is_string($messageId) ||
is_int($messageId)) && isset($this->_translate
[$locale][$messageId])) {
722 // return regionless translation (en_US -> en)
723 if ($plural === null) {
724 return $this->_translate
[$locale][$messageId];
727 $rule = Zend_Translate_Plural
::getPlural($number, $locale);
728 if (isset($this->_translate
[$locale][$plural[0]][$rule])) {
729 return $this->_translate
[$locale][$plural[0]][$rule];
734 $this->_log($messageId, $locale);
735 // use rerouting when enabled
736 if (!empty($this->_options
['route'])) {
737 if (array_key_exists($locale, $this->_options
['route']) &&
738 !array_key_exists($locale, $this->_routed
)) {
739 $this->_routed
[$locale] = true;
740 return $this->translate($messageId, $this->_options
['route'][$locale]);
743 $this->_routed
= array();
746 if ($plural === null) {
750 $rule = Zend_Translate_Plural
::getPlural($number, $plocale);
751 if (!isset($plural[$rule])) {
755 return $plural[$rule];
759 * Translates the given string using plural notations
760 * Returns the translated string
763 * @param string $singular Singular translation string
764 * @param string $plural Plural translation string
765 * @param integer $number Number for detecting the correct plural
766 * @param string|Zend_Locale $locale (Optional) Locale/Language to use, identical with
767 * locale identifier, @see Zend_Locale for more information
770 public function plural($singular, $plural, $number, $locale = null)
772 return $this->translate(array($singular, $plural, $number), $locale);
776 * Logs a message when the log option is set
778 * @param string $message Message to log
779 * @param String $locale Locale to log
781 protected function _log($message, $locale) {
782 if ($this->_options
['logUntranslated']) {
783 $message = str_replace('%message%', $message, $this->_options
['logMessage']);
784 $message = str_replace('%locale%', $locale, $message);
785 if ($this->_options
['log']) {
786 $this->_options
['log']->notice($message);
788 trigger_error($message, E_USER_NOTICE
);
794 * Translates the given string
795 * returns the translation
797 * @param string $messageId Translation string
798 * @param string|Zend_Locale $locale (optional) Locale/Language to use, identical with locale
799 * identifier, @see Zend_Locale for more information
802 public function _($messageId, $locale = null)
804 return $this->translate($messageId, $locale);
808 * Checks if a string is translated within the source or not
811 * @param string $messageId Translation string
812 * @param boolean $original (optional) Allow translation only for original language
813 * when true, a translation for 'en_US' would give false when it can
814 * be translated with 'en' only
815 * @param string|Zend_Locale $locale (optional) Locale/Language to use, identical with locale identifier,
816 * see Zend_Locale for more information
819 public function isTranslated($messageId, $original = false, $locale = null)
821 if (($original !== false) and ($original !== true)) {
826 if ($locale === null) {
827 $locale = $this->_options
['locale'];
830 if (!Zend_Locale
::isLocale($locale, true, false)) {
831 if (!Zend_Locale
::isLocale($locale, false, false)) {
832 // language does not exist, return original string
836 $locale = new Zend_Locale($locale);
839 $locale = (string) $locale;
840 if ((is_string($messageId) ||
is_int($messageId)) && isset($this->_translate
[$locale][$messageId])) {
841 // return original translation
843 } else if ((strlen($locale) != 2) and ($original === false)) {
844 // faster than creating a new locale and separate the leading part
845 $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
847 if ((is_string($messageId) ||
is_int($messageId)) && isset($this->_translate
[$locale][$messageId])) {
848 // return regionless translation (en_US -> en)
853 // No translation found, return original
858 * Returns the set cache
860 * @return Zend_Cache_Core The set cache
862 public static function getCache()
864 return self
::$_cache;
868 * Sets a cache for all Zend_Translate_Adapters
870 * @param Zend_Cache_Core $cache Cache to store to
872 public static function setCache(Zend_Cache_Core
$cache)
874 self
::$_cache = $cache;
878 * Returns true when a cache is set
882 public static function hasCache()
884 if (self
::$_cache !== null) {
892 * Removes any set cache
896 public static function removeCache()
898 self
::$_cache = null;
902 * Clears all set cache data
906 public static function clearCache()
908 require_once 'Zend/Cache.php';
909 self
::$_cache->clean(Zend_Cache
::CLEANING_MODE_MATCHING_TAG
, array('Zend_Translate'));
913 * Returns the adapter name
917 abstract public function toString();