torrentpier-lts/library/Zend/I18n/Translator/Translator.php

773 lines
21 KiB
PHP

<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\I18n\Translator;
use Locale;
use Traversable;
use Zend\Cache;
use Zend\Cache\Storage\StorageInterface as CacheStorage;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerInterface;
use Zend\I18n\Exception;
use Zend\I18n\Translator\Loader\FileLoaderInterface;
use Zend\I18n\Translator\Loader\RemoteLoaderInterface;
use Zend\Stdlib\ArrayUtils;
/**
* Translator.
*/
class Translator implements TranslatorInterface
{
/**
* Event fired when the translation for a message is missing.
*/
const EVENT_MISSING_TRANSLATION = 'missingTranslation';
/**
* Event fired when no messages were loaded for a locale/text-domain combination.
*/
const EVENT_NO_MESSAGES_LOADED = 'noMessagesLoaded';
/**
* Messages loaded by the translator.
*
* @var array
*/
protected $messages = array();
/**
* Files used for loading messages.
*
* @var array
*/
protected $files = array();
/**
* Patterns used for loading messages.
*
* @var array
*/
protected $patterns = array();
/**
* Remote locations for loading messages.
*
* @var array
*/
protected $remote = array();
/**
* Default locale.
*
* @var string
*/
protected $locale;
/**
* Locale to use as fallback if there is no translation.
*
* @var string
*/
protected $fallbackLocale;
/**
* Translation cache.
*
* @var CacheStorage
*/
protected $cache;
/**
* Plugin manager for translation loaders.
*
* @var LoaderPluginManager
*/
protected $pluginManager;
/**
* Event manager for triggering translator events.
*
* @var EventManagerInterface
*/
protected $events;
/**
* Whether events are enabled
*
* @var bool
*/
protected $eventsEnabled = false;
/**
* Instantiate a translator
*
* @param array|Traversable $options
* @return Translator
* @throws Exception\InvalidArgumentException
*/
public static function factory($options)
{
if ($options instanceof Traversable) {
$options = ArrayUtils::iteratorToArray($options);
} elseif (!is_array($options)) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects an array or Traversable object; received "%s"',
__METHOD__,
(is_object($options) ? get_class($options) : gettype($options))
));
}
$translator = new static();
// locales
if (isset($options['locale'])) {
$locales = (array) $options['locale'];
$translator->setLocale(array_shift($locales));
if (count($locales) > 0) {
$translator->setFallbackLocale(array_shift($locales));
}
}
// file patterns
if (isset($options['translation_file_patterns'])) {
if (!is_array($options['translation_file_patterns'])) {
throw new Exception\InvalidArgumentException(
'"translation_file_patterns" should be an array'
);
}
$requiredKeys = array('type', 'base_dir', 'pattern');
foreach ($options['translation_file_patterns'] as $pattern) {
foreach ($requiredKeys as $key) {
if (!isset($pattern[$key])) {
throw new Exception\InvalidArgumentException(
"'{$key}' is missing for translation pattern options"
);
}
}
$translator->addTranslationFilePattern(
$pattern['type'],
$pattern['base_dir'],
$pattern['pattern'],
isset($pattern['text_domain']) ? $pattern['text_domain'] : 'default'
);
}
}
// files
if (isset($options['translation_files'])) {
if (!is_array($options['translation_files'])) {
throw new Exception\InvalidArgumentException(
'"translation_files" should be an array'
);
}
$requiredKeys = array('type', 'filename');
foreach ($options['translation_files'] as $file) {
foreach ($requiredKeys as $key) {
if (!isset($file[$key])) {
throw new Exception\InvalidArgumentException(
"'{$key}' is missing for translation file options"
);
}
}
$translator->addTranslationFile(
$file['type'],
$file['filename'],
isset($file['text_domain']) ? $file['text_domain'] : 'default',
isset($file['locale']) ? $file['locale'] : null
);
}
}
// remote
if (isset($options['remote_translation'])) {
if (!is_array($options['remote_translation'])) {
throw new Exception\InvalidArgumentException(
'"remote_translation" should be an array'
);
}
$requiredKeys = array('type');
foreach ($options['remote_translation'] as $remote) {
foreach ($requiredKeys as $key) {
if (!isset($remote[$key])) {
throw new Exception\InvalidArgumentException(
"'{$key}' is missing for remote translation options"
);
}
}
$translator->addRemoteTranslations(
$remote['type'],
isset($remote['text_domain']) ? $remote['text_domain'] : 'default'
);
}
}
// cache
if (isset($options['cache'])) {
if ($options['cache'] instanceof CacheStorage) {
$translator->setCache($options['cache']);
} else {
$translator->setCache(Cache\StorageFactory::factory($options['cache']));
}
}
// event manager enabled
if (isset($options['event_manager_enabled']) && $options['event_manager_enabled']) {
$translator->enableEventManager();
}
return $translator;
}
/**
* Set the default locale.
*
* @param string $locale
* @return Translator
*/
public function setLocale($locale)
{
$this->locale = $locale;
return $this;
}
/**
* Get the default locale.
*
* @return string
* @throws Exception\ExtensionNotLoadedException if ext/intl is not present and no locale set
*/
public function getLocale()
{
if ($this->locale === null) {
if (!extension_loaded('intl')) {
throw new Exception\ExtensionNotLoadedException(sprintf(
'%s component requires the intl PHP extension',
__NAMESPACE__
));
}
$this->locale = Locale::getDefault();
}
return $this->locale;
}
/**
* Set the fallback locale.
*
* @param string $locale
* @return Translator
*/
public function setFallbackLocale($locale)
{
$this->fallbackLocale = $locale;
return $this;
}
/**
* Get the fallback locale.
*
* @return string
*/
public function getFallbackLocale()
{
return $this->fallbackLocale;
}
/**
* Sets a cache
*
* @param CacheStorage $cache
* @return Translator
*/
public function setCache(CacheStorage $cache = null)
{
$this->cache = $cache;
return $this;
}
/**
* Returns the set cache
*
* @return CacheStorage The set cache
*/
public function getCache()
{
return $this->cache;
}
/**
* Set the plugin manager for translation loaders
*
* @param LoaderPluginManager $pluginManager
* @return Translator
*/
public function setPluginManager(LoaderPluginManager $pluginManager)
{
$this->pluginManager = $pluginManager;
return $this;
}
/**
* Retrieve the plugin manager for translation loaders.
*
* Lazy loads an instance if none currently set.
*
* @return LoaderPluginManager
*/
public function getPluginManager()
{
if (!$this->pluginManager instanceof LoaderPluginManager) {
$this->setPluginManager(new LoaderPluginManager());
}
return $this->pluginManager;
}
/**
* Translate a message.
*
* @param string $message
* @param string $textDomain
* @param string $locale
* @return string
*/
public function translate($message, $textDomain = 'default', $locale = null)
{
$locale = ($locale ?: $this->getLocale());
$translation = $this->getTranslatedMessage($message, $locale, $textDomain);
if ($translation !== null && $translation !== '') {
return $translation;
}
if (null !== ($fallbackLocale = $this->getFallbackLocale())
&& $locale !== $fallbackLocale
) {
return $this->translate($message, $textDomain, $fallbackLocale);
}
return $message;
}
/**
* Translate a plural message.
*
* @param string $singular
* @param string $plural
* @param int $number
* @param string $textDomain
* @param string|null $locale
* @return string
* @throws Exception\OutOfBoundsException
*/
public function translatePlural(
$singular,
$plural,
$number,
$textDomain = 'default',
$locale = null
) {
$locale = $locale ?: $this->getLocale();
$translation = $this->getTranslatedMessage($singular, $locale, $textDomain);
if ($translation === null || $translation === '') {
if (null !== ($fallbackLocale = $this->getFallbackLocale())
&& $locale !== $fallbackLocale
) {
return $this->translatePlural(
$singular,
$plural,
$number,
$textDomain,
$fallbackLocale
);
}
return ($number == 1 ? $singular : $plural);
}
$index = $this->messages[$textDomain][$locale]
->getPluralRule()
->evaluate($number);
if (!isset($translation[$index])) {
throw new Exception\OutOfBoundsException(sprintf(
'Provided index %d does not exist in plural array', $index
));
}
return $translation[$index];
}
/**
* Get a translated message.
*
* @triggers getTranslatedMessage.missing-translation
* @param string $message
* @param string $locale
* @param string $textDomain
* @return string|null
*/
protected function getTranslatedMessage(
$message,
$locale,
$textDomain = 'default'
) {
if ($message === '') {
return '';
}
if (!isset($this->messages[$textDomain][$locale])) {
$this->loadMessages($textDomain, $locale);
}
if (isset($this->messages[$textDomain][$locale][$message])) {
return $this->messages[$textDomain][$locale][$message];
}
if ($this->isEventManagerEnabled()) {
$results = $this->getEventManager()->trigger(
self::EVENT_MISSING_TRANSLATION,
$this,
array(
'message' => $message,
'locale' => $locale,
'text_domain' => $textDomain,
),
function ($r) {
return is_string($r);
}
);
$last = $results->last();
if (is_string($last)) {
return $last;
}
}
return null;
}
/**
* Add a translation file.
*
* @param string $type
* @param string $filename
* @param string $textDomain
* @param string $locale
* @return Translator
*/
public function addTranslationFile(
$type,
$filename,
$textDomain = 'default',
$locale = null
) {
$locale = $locale ?: '*';
if (!isset($this->files[$textDomain])) {
$this->files[$textDomain] = array();
}
$this->files[$textDomain][$locale][] = array(
'type' => $type,
'filename' => $filename,
);
return $this;
}
/**
* Add multiple translations with a file pattern.
*
* @param string $type
* @param string $baseDir
* @param string $pattern
* @param string $textDomain
* @return Translator
*/
public function addTranslationFilePattern(
$type,
$baseDir,
$pattern,
$textDomain = 'default'
) {
if (!isset($this->patterns[$textDomain])) {
$this->patterns[$textDomain] = array();
}
$this->patterns[$textDomain][] = array(
'type' => $type,
'baseDir' => rtrim($baseDir, '/'),
'pattern' => $pattern,
);
return $this;
}
/**
* Add remote translations.
*
* @param string $type
* @param string $textDomain
* @return Translator
*/
public function addRemoteTranslations($type, $textDomain = 'default')
{
if (!isset($this->remote[$textDomain])) {
$this->remote[$textDomain] = array();
}
$this->remote[$textDomain][] = $type;
return $this;
}
/**
* Load messages for a given language and domain.
*
* @triggers loadMessages.no-messages-loaded
* @param string $textDomain
* @param string $locale
* @throws Exception\RuntimeException
* @return void
*/
protected function loadMessages($textDomain, $locale)
{
if (!isset($this->messages[$textDomain])) {
$this->messages[$textDomain] = array();
}
if (null !== ($cache = $this->getCache())) {
$cacheId = 'Zend_I18n_Translator_Messages_' . md5($textDomain . $locale);
if (null !== ($result = $cache->getItem($cacheId))) {
$this->messages[$textDomain][$locale] = $result;
return;
}
}
$messagesLoaded = false;
$messagesLoaded |= $this->loadMessagesFromRemote($textDomain, $locale);
$messagesLoaded |= $this->loadMessagesFromPatterns($textDomain, $locale);
$messagesLoaded |= $this->loadMessagesFromFiles($textDomain, $locale);
if (!$messagesLoaded) {
$discoveredTextDomain = null;
if ($this->isEventManagerEnabled()) {
$results = $this->getEventManager()->trigger(
self::EVENT_NO_MESSAGES_LOADED,
$this,
array(
'locale' => $locale,
'text_domain' => $textDomain,
),
function ($r) {
return ($r instanceof TextDomain);
}
);
$last = $results->last();
if ($last instanceof TextDomain) {
$discoveredTextDomain = $last;
}
}
$this->messages[$textDomain][$locale] = $discoveredTextDomain;
$messagesLoaded = true;
}
if ($messagesLoaded && $cache !== null) {
$cache->setItem($cacheId, $this->messages[$textDomain][$locale]);
}
}
/**
* Load messages from remote sources.
*
* @param string $textDomain
* @param string $locale
* @return bool
* @throws Exception\RuntimeException When specified loader is not a remote loader
*/
protected function loadMessagesFromRemote($textDomain, $locale)
{
$messagesLoaded = false;
if (isset($this->remote[$textDomain])) {
foreach ($this->remote[$textDomain] as $loaderType) {
$loader = $this->getPluginManager()->get($loaderType);
if (!$loader instanceof RemoteLoaderInterface) {
throw new Exception\RuntimeException('Specified loader is not a remote loader');
}
if (isset($this->messages[$textDomain][$locale])) {
$this->messages[$textDomain][$locale]->merge($loader->load($locale, $textDomain));
} else {
$this->messages[$textDomain][$locale] = $loader->load($locale, $textDomain);
}
$messagesLoaded = true;
}
}
return $messagesLoaded;
}
/**
* Load messages from patterns.
*
* @param string $textDomain
* @param string $locale
* @return bool
* @throws Exception\RuntimeException When specified loader is not a file loader
*/
protected function loadMessagesFromPatterns($textDomain, $locale)
{
$messagesLoaded = false;
if (isset($this->patterns[$textDomain])) {
foreach ($this->patterns[$textDomain] as $pattern) {
$filename = $pattern['baseDir'] . '/' . sprintf($pattern['pattern'], $locale);
if (is_file($filename)) {
$loader = $this->getPluginManager()->get($pattern['type']);
if (!$loader instanceof FileLoaderInterface) {
throw new Exception\RuntimeException('Specified loader is not a file loader');
}
if (isset($this->messages[$textDomain][$locale])) {
$this->messages[$textDomain][$locale]->merge($loader->load($locale, $filename));
} else {
$this->messages[$textDomain][$locale] = $loader->load($locale, $filename);
}
$messagesLoaded = true;
}
}
}
return $messagesLoaded;
}
/**
* Load messages from files.
*
* @param string $textDomain
* @param string $locale
* @return bool
* @throws Exception\RuntimeException When specified loader is not a file loader
*/
protected function loadMessagesFromFiles($textDomain, $locale)
{
$messagesLoaded = false;
foreach (array($locale, '*') as $currentLocale) {
if (!isset($this->files[$textDomain][$currentLocale])) {
continue;
}
foreach ($this->files[$textDomain][$currentLocale] as $file) {
$loader = $this->getPluginManager()->get($file['type']);
if (!$loader instanceof FileLoaderInterface) {
throw new Exception\RuntimeException('Specified loader is not a file loader');
}
if (isset($this->messages[$textDomain][$locale])) {
$this->messages[$textDomain][$locale]->merge($loader->load($locale, $file['filename']));
} else {
$this->messages[$textDomain][$locale] = $loader->load($locale, $file['filename']);
}
$messagesLoaded = true;
}
unset($this->files[$textDomain][$currentLocale]);
}
return $messagesLoaded;
}
/**
* Get the event manager.
*
* @return EventManagerInterface|null
*/
public function getEventManager()
{
if (!$this->events instanceof EventManagerInterface) {
$this->setEventManager(new EventManager());
}
return $this->events;
}
/**
* Set the event manager instance used by this translator.
*
* @param EventManagerInterface $events
* @return Translator
*/
public function setEventManager(EventManagerInterface $events)
{
$events->setIdentifiers(array(
__CLASS__,
get_class($this),
'translator',
));
$this->events = $events;
return $this;
}
/**
* Check whether the event manager is enabled.
*
* @return boolean
*/
public function isEventManagerEnabled()
{
return $this->eventsEnabled;
}
/**
* Enable the event manager.
*
* @return Translator
*/
public function enableEventManager()
{
$this->eventsEnabled = true;
return $this;
}
/**
* Disable the event manager.
*
* @return Translator
*/
public function disableEventManager()
{
$this->eventsEnabled = false;
return $this;
}
}