This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
symfony/src/Symfony/Component/Translation/Translator.php

491 lines
13 KiB
PHP
Raw Normal View History

2010-09-27 08:45:29 +01:00
<?php
/*
* This file is part of the Symfony package.
2010-09-27 08:45:29 +01:00
*
* (c) Fabien Potencier <fabien@symfony.com>
2010-09-27 08:45:29 +01:00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
2010-09-27 08:45:29 +01:00
*/
namespace Symfony\Component\Translation;
use Symfony\Component\Translation\Loader\LoaderInterface;
2012-12-13 17:37:08 +00:00
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\ConfigCacheInterface;
use Symfony\Component\Config\ConfigCacheFactoryInterface;
use Symfony\Component\Config\ConfigCacheFactory;
2010-09-27 08:45:29 +01:00
/**
* Translator.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
2010-09-27 08:45:29 +01:00
*/
2014-05-10 23:14:12 +01:00
class Translator implements TranslatorInterface, TranslatorBagInterface
2010-09-27 08:45:29 +01:00
{
/**
* @var MessageCatalogueInterface[]
*/
protected $catalogues = array();
/**
* @var string
*/
protected $locale;
/**
* @var array
*/
private $fallbackLocales = array();
/**
* @var LoaderInterface[]
*/
private $loaders = array();
/**
* @var array
*/
private $resources = array();
/**
* @var MessageSelector
*/
private $selector;
2010-09-27 08:45:29 +01:00
/**
* @var string
*/
private $cacheDir;
/**
* @var bool
*/
private $debug;
/**
* @var ConfigCacheFactoryInterface|null
*/
private $configCacheFactory;
2010-09-27 08:45:29 +01:00
/**
* Constructor.
*
2013-02-13 00:53:15 +00:00
* @param string $locale The locale
* @param MessageSelector|null $selector The message selector for pluralization
* @param string|null $cacheDir The directory to use for the cache
* @param bool $debug Use cache in debug mode ?
*
2014-07-04 18:20:43 +01:00
* @throws \InvalidArgumentException If a locale contains invalid characters
*
* @api
2010-09-27 08:45:29 +01:00
*/
public function __construct($locale, MessageSelector $selector = null, $cacheDir = null, $debug = false)
2010-09-27 08:45:29 +01:00
{
2014-07-04 18:20:43 +01:00
$this->setLocale($locale);
2013-02-13 00:53:15 +00:00
$this->selector = $selector ?: new MessageSelector();
$this->cacheDir = $cacheDir;
$this->debug = $debug;
2010-09-27 08:45:29 +01:00
}
/**
* Sets the ConfigCache factory to use.
*
* @param ConfigCacheFactoryInterface $configCacheFactory
*/
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
{
$this->configCacheFactory = $configCacheFactory;
}
2010-09-27 08:45:29 +01:00
/**
* Adds a Loader.
*
* @param string $format The name of the loader (@see addResource())
* @param LoaderInterface $loader A LoaderInterface instance
*
* @api
2010-09-27 08:45:29 +01:00
*/
public function addLoader($format, LoaderInterface $loader)
{
$this->loaders[$format] = $loader;
}
/**
* Adds a Resource.
*
* @param string $format The name of the loader (@see addLoader())
* @param mixed $resource The resource name
* @param string $locale The locale
* @param string $domain The domain
*
2014-07-04 18:20:43 +01:00
* @throws \InvalidArgumentException If the locale contains invalid characters
*
* @api
2010-09-27 08:45:29 +01:00
*/
public function addResource($format, $resource, $locale, $domain = null)
2010-09-27 08:45:29 +01:00
{
if (null === $domain) {
$domain = 'messages';
}
2014-07-04 18:20:43 +01:00
$this->assertValidLocale($locale);
2010-09-27 08:45:29 +01:00
$this->resources[$locale][] = array($format, $resource, $domain);
2013-05-10 17:49:00 +01:00
if (in_array($locale, $this->fallbackLocales)) {
$this->catalogues = array();
} else {
unset($this->catalogues[$locale]);
}
2010-09-27 08:45:29 +01:00
}
/**
* {@inheritdoc}
*
* @api
2010-09-27 08:45:29 +01:00
*/
public function setLocale($locale)
{
2014-07-04 18:20:43 +01:00
$this->assertValidLocale($locale);
2010-09-27 08:45:29 +01:00
$this->locale = $locale;
}
2010-10-06 13:18:36 +01:00
/**
* {@inheritdoc}
*
* @api
2010-10-06 13:18:36 +01:00
*/
public function getLocale()
{
return $this->locale;
}
/**
* Sets the fallback locales.
*
* @param array $locales The fallback locales
*
2014-07-04 18:20:43 +01:00
* @throws \InvalidArgumentException If a locale contains invalid characters
*
* @api
*/
public function setFallbackLocales(array $locales)
2010-09-27 08:45:29 +01:00
{
// needed as the fallback locales are linked to the already loaded catalogues
2010-10-31 21:33:08 +00:00
$this->catalogues = array();
2010-09-27 08:45:29 +01:00
2014-07-04 18:20:43 +01:00
foreach ($locales as $locale) {
$this->assertValidLocale($locale);
}
$this->fallbackLocales = $locales;
}
/**
* Gets the fallback locales.
*
* @return array $locales The fallback locales
*
* @api
*/
public function getFallbackLocales()
{
return $this->fallbackLocales;
2010-09-27 08:45:29 +01:00
}
/**
* {@inheritdoc}
*
* @api
2010-09-27 08:45:29 +01:00
*/
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
2010-09-27 08:45:29 +01:00
{
if (null === $domain) {
$domain = 'messages';
}
return strtr($this->getCatalogue($locale)->get((string) $id, $domain), $parameters);
2010-09-27 08:45:29 +01:00
}
/**
* {@inheritdoc}
*
* @api
2010-09-27 08:45:29 +01:00
*/
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
2010-09-27 08:45:29 +01:00
{
if (null === $domain) {
$domain = 'messages';
}
$id = (string) $id;
$catalogue = $this->getCatalogue($locale);
$locale = $catalogue->getLocale();
2011-09-28 15:46:33 +01:00
while (!$catalogue->defines($id, $domain)) {
if ($cat = $catalogue->getFallbackCatalogue()) {
$catalogue = $cat;
$locale = $catalogue->getLocale();
} else {
break;
}
}
return strtr($this->selector->choose($catalogue->get($id, $domain), (int) $number, $locale), $parameters);
2010-09-27 08:45:29 +01:00
}
2014-05-10 23:14:12 +01:00
/**
* {@inheritdoc}
*/
public function getCatalogue($locale = null)
{
if (null === $locale) {
$locale = $this->getLocale();
} else {
$this->assertValidLocale($locale);
2014-05-10 23:14:12 +01:00
}
if (!isset($this->catalogues[$locale])) {
$this->loadCatalogue($locale);
}
return $this->catalogues[$locale];
}
2013-09-10 18:47:19 +01:00
/**
* Gets the loaders.
*
* @return array LoaderInterface[]
*/
protected function getLoaders()
{
return $this->loaders;
}
/**
* Collects all messages for the given locale.
*
* @param string|null $locale Locale of translations, by default is current locale
*
2014-08-27 13:56:02 +01:00
* @return array[array] indexed by catalog
Merge branch '2.7' into 2.8 * 2.7: (70 commits) [travis] Use container-based infrastructure [HttpKernel] use ConfigCache::getPath() method when it exists [PropertyAccess] Fix setting public property on a class having a magic getter [Routing] Display file which contain deprecated option ContainerInterface: unused exception dropped bumped Symfony version to 2.6.8 updated VERSION for 2.6.7 updated CHANGELOG for 2.6.7 bumped Symfony version to 2.3.29 updated VERSION for 2.3.28 update CONTRIBUTORS for 2.3.28 updated CHANGELOG for 2.3.28 [Debug] Fixed ClassNotFoundFatalErrorHandlerTest [SecurityBundle] use access decision constants in config [SecurityBundle] use session auth constants in config PhpDoc fix in AbstractRememberMeServices [Filesystem] Simplified an if statement [SecurityBundle] Use Enum Nodes Instead Of Scalar [Debug 2.3] Fix test for PHP7 [HttpKernel] Check if "symfony/proxy-manager-bridge" package is installed ... Conflicts: src/Symfony/Bundle/DebugBundle/composer.json src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php src/Symfony/Component/Form/README.md src/Symfony/Component/Intl/README.md src/Symfony/Component/Security/README.md src/Symfony/Component/Translation/Loader/CsvFileLoader.php src/Symfony/Component/Translation/Loader/IniFileLoader.php src/Symfony/Component/Translation/Loader/MoFileLoader.php src/Symfony/Component/Translation/Loader/PhpFileLoader.php src/Symfony/Component/Translation/Loader/PoFileLoader.php src/Symfony/Component/Translation/Loader/YamlFileLoader.php src/Symfony/Component/Translation/README.md src/Symfony/Component/Translation/Translator.php src/Symfony/Component/Validator/README.md
2015-05-12 16:16:46 +01:00
*
* @deprecated since version 2.8, to be removed in 3.0. Use TranslatorBagInterface::getCatalogue() method instead.
*/
public function getMessages($locale = null)
{
Merge branch '2.7' into 2.8 * 2.7: (70 commits) [travis] Use container-based infrastructure [HttpKernel] use ConfigCache::getPath() method when it exists [PropertyAccess] Fix setting public property on a class having a magic getter [Routing] Display file which contain deprecated option ContainerInterface: unused exception dropped bumped Symfony version to 2.6.8 updated VERSION for 2.6.7 updated CHANGELOG for 2.6.7 bumped Symfony version to 2.3.29 updated VERSION for 2.3.28 update CONTRIBUTORS for 2.3.28 updated CHANGELOG for 2.3.28 [Debug] Fixed ClassNotFoundFatalErrorHandlerTest [SecurityBundle] use access decision constants in config [SecurityBundle] use session auth constants in config PhpDoc fix in AbstractRememberMeServices [Filesystem] Simplified an if statement [SecurityBundle] Use Enum Nodes Instead Of Scalar [Debug 2.3] Fix test for PHP7 [HttpKernel] Check if "symfony/proxy-manager-bridge" package is installed ... Conflicts: src/Symfony/Bundle/DebugBundle/composer.json src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php src/Symfony/Component/Form/README.md src/Symfony/Component/Intl/README.md src/Symfony/Component/Security/README.md src/Symfony/Component/Translation/Loader/CsvFileLoader.php src/Symfony/Component/Translation/Loader/IniFileLoader.php src/Symfony/Component/Translation/Loader/MoFileLoader.php src/Symfony/Component/Translation/Loader/PhpFileLoader.php src/Symfony/Component/Translation/Loader/PoFileLoader.php src/Symfony/Component/Translation/Loader/YamlFileLoader.php src/Symfony/Component/Translation/README.md src/Symfony/Component/Translation/Translator.php src/Symfony/Component/Validator/README.md
2015-05-12 16:16:46 +01:00
trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use TranslatorBagInterface::getCatalogue() method instead.', E_USER_DEPRECATED);
2015-05-04 22:22:52 +01:00
$catalogue = $this->getCatalogue($locale);
$messages = $catalogue->all();
while ($catalogue = $catalogue->getFallbackCatalogue()) {
2015-05-04 22:22:52 +01:00
$messages = array_replace_recursive($catalogue->all(), $messages);
}
return $messages;
}
/**
* @param string $locale
*/
protected function loadCatalogue($locale)
{
if (null === $this->cacheDir) {
$this->initializeCatalogue($locale);
} else {
$this->initializeCacheCatalogue($locale);
}
}
/**
* @param string $locale
*/
protected function initializeCatalogue($locale)
{
$this->assertValidLocale($locale);
2012-12-13 17:37:08 +00:00
try {
$this->doLoadCatalogue($locale);
} catch (NotFoundResourceException $e) {
if (!$this->computeFallbackLocales($locale)) {
throw $e;
}
}
2011-09-16 12:27:17 +01:00
$this->loadFallbackCatalogues($locale);
}
/**
* @param string $locale
*/
private function initializeCacheCatalogue($locale)
{
if (isset($this->catalogues[$locale])) {
/* Catalogue already initialized. */
return;
}
$this->assertValidLocale($locale);
$self = $this; // required for PHP 5.3 where "$this" cannot be use()d in anonymous functions. Change in Symfony 3.0.
$cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale),
function (ConfigCacheInterface $cache) use ($self, $locale) {
$self->dumpCatalogue($locale, $cache);
}
);
if (isset($this->catalogues[$locale])) {
/* Catalogue has been initialized as it was written out to cache. */
return;
}
/* Read catalogue from cache. */
$this->catalogues[$locale] = include $cache->getPath();
}
/**
* This method is public because it needs to be callable from a closure in PHP 5.3. It should be made protected (or even private, if possible) in 3.0.
*
* @internal
*/
public function dumpCatalogue($locale, ConfigCacheInterface $cache)
{
$this->initializeCatalogue($locale);
$fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
$content = sprintf(<<<EOF
<?php
use Symfony\Component\Translation\MessageCatalogue;
\$catalogue = new MessageCatalogue('%s', %s);
%s
return \$catalogue;
EOF
,
$locale,
var_export($this->catalogues[$locale]->all(), true),
$fallbackContent
);
$cache->write($content, $this->catalogues[$locale]->getResources());
}
private function getFallbackContent(MessageCatalogue $catalogue)
{
$fallbackContent = '';
$current = '';
$replacementPattern = '/[^a-z0-9_]/i';
$fallbackCatalogue = $catalogue->getFallbackCatalogue();
while ($fallbackCatalogue) {
$fallback = $fallbackCatalogue->getLocale();
$fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback));
$currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current));
$fallbackContent .= sprintf(<<<EOF
\$catalogue%s = new MessageCatalogue('%s', %s);
\$catalogue%s->addFallbackCatalogue(\$catalogue%s);
EOF
,
$fallbackSuffix,
$fallback,
var_export($fallbackCatalogue->all(), true),
$currentSuffix,
$fallbackSuffix
);
$current = $fallbackCatalogue->getLocale();
$fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
}
return $fallbackContent;
}
private function getCatalogueCachePath($locale)
{
$catalogueHash = sha1(serialize(array(
'resources' => isset($this->resources[$locale]) ? $this->resources[$locale] : array(),
'fallback_locales' => $this->fallbackLocales,
)));
return $this->cacheDir.'/catalogue.'.$locale.'.'.$catalogueHash.'.php';
}
2011-09-28 15:46:33 +01:00
private function doLoadCatalogue($locale)
2010-09-27 08:45:29 +01:00
{
$this->catalogues[$locale] = new MessageCatalogue($locale);
if (isset($this->resources[$locale])) {
foreach ($this->resources[$locale] as $resource) {
if (!isset($this->loaders[$resource[0]])) {
throw new \RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0]));
}
2011-10-23 13:08:03 +01:00
$this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2]));
2010-09-27 08:45:29 +01:00
}
}
}
2011-09-16 12:27:17 +01:00
private function loadFallbackCatalogues($locale)
2010-09-27 08:45:29 +01:00
{
$current = $this->catalogues[$locale];
2011-09-28 15:08:31 +01:00
foreach ($this->computeFallbackLocales($locale) as $fallback) {
if (!isset($this->catalogues[$fallback])) {
$this->doLoadCatalogue($fallback);
}
2010-10-31 21:33:08 +00:00
$current->addFallbackCatalogue($this->catalogues[$fallback]);
$current = $this->catalogues[$fallback];
2010-09-27 08:45:29 +01:00
}
}
2011-09-28 15:46:33 +01:00
protected function computeFallbackLocales($locale)
{
2011-09-20 06:50:47 +01:00
$locales = array();
foreach ($this->fallbackLocales as $fallback) {
if ($fallback === $locale) {
continue;
}
$locales[] = $fallback;
}
2012-02-22 17:59:56 +00:00
if (strrchr($locale, '_') !== false) {
array_unshift($locales, substr($locale, 0, -strlen(strrchr($locale, '_'))));
}
2011-09-28 15:46:33 +01:00
return array_unique($locales);
}
2014-07-04 18:20:43 +01:00
/**
* Asserts that the locale is valid, throws an Exception if not.
*
* @param string $locale Locale to tests
*
* @throws \InvalidArgumentException If the locale contains invalid characters
*/
2014-07-25 21:18:02 +01:00
protected function assertValidLocale($locale)
2014-07-04 18:20:43 +01:00
{
2014-07-22 22:54:07 +01:00
if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
throw new \InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
2014-07-04 18:20:43 +01:00
}
}
/**
* Provides the ConfigCache factory implementation, falling back to a
* default implementation if necessary.
*
* @return ConfigCacheFactoryInterface $configCacheFactory
*/
private function getConfigCacheFactory()
{
if (!$this->configCacheFactory) {
$this->configCacheFactory = new ConfigCacheFactory($this->debug);
}
return $this->configCacheFactory;
}
2010-09-27 08:45:29 +01:00
}