<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2013 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\I18n\Exception
;
use Zend\I18n\Translator\Loader\FileLoaderInterface
;
use Zend\I18n\Translator\Loader\RemoteLoaderInterface
;
use Zend\Stdlib\ArrayUtils
;
/**
* Translator.
*/
class Translator
{
/**
* 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;
/**
* 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']));
}
}
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
*/
public function getLocale
()
{
if ($this->locale === null) {
$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.
*
* @param string $message
* @param string $locale
* @param string $textDomain
* @return string|null
*/
protected function getTranslatedMessage
(
$message,
$locale = null,
$textDomain = 'default'
) {
if ($message === '') {
return '';
}
if (!isset($this->messages[$textDomain][$locale])) {
$this->loadMessages($textDomain, $locale);
}
if (!isset($this->messages[$textDomain][$locale][$message])) {
return null;
}
return $this->messages[$textDomain][$locale][$message];
}
/**
* 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.
*
* @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;
}
}
$hasToCache = false;
// Try to load from remote sources
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]->exchangeArray(array_merge(
(array) $this->messages[$textDomain][$locale],
(array) $loader->load($locale, $textDomain)
));
} else {
$this->messages[$textDomain][$locale] = $loader->load($locale, $textDomain);
}
$hasToCache = true;
}
}
// Try to load from pattern
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]->exchangeArray(array_merge(
(array) $this->messages[$textDomain][$locale],
(array) $loader->load($locale, $filename)
));
} else {
$this->messages[$textDomain][$locale] = $loader->load($locale, $filename);
}
$hasToCache = true;
}
}
}
// Try to load from concrete files
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]->exchangeArray(array_merge(
(array) $this->messages[$textDomain][$locale],
(array) $loader->load($locale, $file['filename'])
));
} else {
$this->messages[$textDomain][$locale] = $loader->load($locale, $file['filename']);
}
$hasToCache = true;
}
unset($this->files[$textDomain][$currentLocale]);
}
// Cache the loaded text domain
if ($hasToCache && $cache !== null) {
$cache->setItem($cacheId, $this->messages[$textDomain][$locale]);
}
}
}