2010-09-27 08:45:29 +01:00
< ? php
/*
2011-01-15 13:29:43 +00:00
* This file is part of the Symfony package .
2010-09-27 08:45:29 +01:00
*
2011-03-06 11:40:06 +00:00
* ( c ) Fabien Potencier < fabien @ symfony . com >
2010-09-27 08:45:29 +01:00
*
2011-01-15 13:29:43 +00: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
*/
2011-01-15 13:29:43 +00: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 ;
2015-04-02 14:33:59 +01:00
use Symfony\Component\Config\ConfigCacheInterface ;
use Symfony\Component\Config\ConfigCacheFactoryInterface ;
use Symfony\Component\Config\ConfigCacheFactory ;
2011-01-15 13:29:43 +00:00
2010-09-27 08:45:29 +01:00
/**
* Translator .
*
2011-03-06 11:40:06 +00:00
* @ author Fabien Potencier < fabien @ symfony . com >
2011-03-24 08:07:52 +00:00
*
* @ 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
{
2012-11-01 15:08:59 +00:00
/**
* @ var MessageCatalogueInterface []
*/
protected $catalogues = array ();
/**
* @ var string
*/
2011-03-09 09:24:16 +00:00
protected $locale ;
2012-11-01 15:08:59 +00:00
/**
* @ var array
*/
private $fallbackLocales = array ();
/**
* @ var LoaderInterface []
*/
private $loaders = array ();
/**
* @ var array
*/
private $resources = array ();
/**
* @ var MessageSelector
*/
2011-03-08 19:47:32 +00:00
private $selector ;
2010-09-27 08:45:29 +01:00
2014-07-11 12:30:03 +01:00
/**
* @ var string
*/
private $cacheDir ;
/**
* @ var bool
*/
private $debug ;
2015-04-02 14:33:59 +01:00
/**
* @ 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
2014-07-11 12:30:03 +01:00
* @ param string | null $cacheDir The directory to use for the cache
* @ param bool $debug Use cache in debug mode ?
2011-03-24 08:07:52 +00:00
*
2014-07-04 18:20:43 +01:00
* @ throws \InvalidArgumentException If a locale contains invalid characters
*
2011-03-24 08:07:52 +00:00
* @ api
2010-09-27 08:45:29 +01:00
*/
2014-07-11 12:30:03 +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 ();
2014-07-11 12:30:03 +01:00
$this -> cacheDir = $cacheDir ;
$this -> debug = $debug ;
2010-09-27 08:45:29 +01:00
}
2015-04-02 14:33:59 +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
2011-03-24 08:07:52 +00:00
*
* @ 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
2011-03-24 08:07:52 +00:00
*
2014-07-04 18:20:43 +01:00
* @ throws \InvalidArgumentException If the locale contains invalid characters
*
2011-03-24 08:07:52 +00:00
* @ api
2010-09-27 08:45:29 +01:00
*/
2013-05-03 07:41:16 +01:00
public function addResource ( $format , $resource , $locale , $domain = null )
2010-09-27 08:45:29 +01:00
{
2013-05-03 07:41:16 +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 );
2012-10-03 11:03:29 +01:00
2013-05-10 17:49:00 +01:00
if ( in_array ( $locale , $this -> fallbackLocales )) {
2013-04-23 17:01:41 +01:00
$this -> catalogues = array ();
} else {
unset ( $this -> catalogues [ $locale ]);
2013-04-23 15:03:00 +01:00
}
2010-09-27 08:45:29 +01:00
}
/**
* { @ inheritdoc }
2011-03-24 08:07:52 +00:00
*
* @ 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 }
2011-03-24 08:07:52 +00:00
*
* @ api
2010-10-06 13:18:36 +01:00
*/
public function getLocale ()
{
return $this -> locale ;
}
2010-09-27 08:45:29 +01:00
/**
2011-09-15 07:19:52 +01:00
* Sets the fallback locale ( s ) .
2010-09-27 08:45:29 +01:00
*
2012-01-11 14:52:51 +00:00
* @ param string | array $locales The fallback locale ( s )
2011-03-24 08:07:52 +00:00
*
2014-07-04 18:20:43 +01:00
* @ throws \InvalidArgumentException If a locale contains invalid characters
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.3 , to be removed in 3.0 . Use setFallbackLocales () instead .
2013-02-17 17:30:02 +00:00
*
2011-03-24 08:07:52 +00:00
* @ api
2010-09-27 08:45:29 +01:00
*/
2011-09-15 07:19:52 +01:00
public function setFallbackLocale ( $locales )
2013-02-17 17:30:02 +00:00
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.3 and will be removed in 3.0. Use the setFallbackLocales() method instead.' , E_USER_DEPRECATED );
2014-11-29 11:23:49 +00:00
2013-03-06 21:28:09 +00:00
$this -> setFallbackLocales ( is_array ( $locales ) ? $locales : array ( $locales ));
2013-02-17 17:30:02 +00:00
}
/**
* 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
*
2013-02-17 17:30:02 +00:00
* @ api
*/
public function setFallbackLocales ( array $locales )
2010-09-27 08:45:29 +01:00
{
2011-09-15 07:19:52 +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 );
}
2013-02-17 17:30:02 +00:00
$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 }
2011-03-24 08:07:52 +00:00
*
* @ api
2010-09-27 08:45:29 +01:00
*/
2013-05-03 07:41:16 +01:00
public function trans ( $id , array $parameters = array (), $domain = null , $locale = null )
2010-09-27 08:45:29 +01:00
{
2013-02-13 00:53:15 +00:00
if ( null === $locale ) {
2010-10-06 10:57:38 +01:00
$locale = $this -> getLocale ();
2014-07-04 18:20:43 +01:00
} else {
$this -> assertValidLocale ( $locale );
2010-09-27 08:45:29 +01:00
}
2013-05-03 07:41:16 +01:00
if ( null === $domain ) {
$domain = 'messages' ;
}
2010-09-27 08:45:29 +01:00
if ( ! isset ( $this -> catalogues [ $locale ])) {
$this -> loadCatalogue ( $locale );
}
2011-04-24 08:39:28 +01:00
return strtr ( $this -> catalogues [ $locale ] -> get (( string ) $id , $domain ), $parameters );
2010-09-27 08:45:29 +01:00
}
/**
* { @ inheritdoc }
2011-03-24 08:07:52 +00:00
*
* @ api
2010-09-27 08:45:29 +01:00
*/
2013-05-03 07:41:16 +01:00
public function transChoice ( $id , $number , array $parameters = array (), $domain = null , $locale = null )
2010-09-27 08:45:29 +01:00
{
2013-02-13 00:53:15 +00:00
if ( null === $locale ) {
2010-10-06 10:57:38 +01:00
$locale = $this -> getLocale ();
2014-07-04 18:20:43 +01:00
} else {
$this -> assertValidLocale ( $locale );
2010-09-27 08:45:29 +01:00
}
2013-05-03 07:41:16 +01:00
if ( null === $domain ) {
$domain = 'messages' ;
}
2010-09-27 08:45:29 +01:00
if ( ! isset ( $this -> catalogues [ $locale ])) {
$this -> loadCatalogue ( $locale );
}
2011-09-15 07:19:52 +01:00
$id = ( string ) $id ;
2011-09-28 15:46:33 +01:00
$catalogue = $this -> catalogues [ $locale ];
while ( ! $catalogue -> defines ( $id , $domain )) {
if ( $cat = $catalogue -> getFallbackCatalogue ()) {
$catalogue = $cat ;
$locale = $catalogue -> getLocale ();
} else {
break ;
2011-09-15 07:19:52 +01:00
}
2011-08-22 15:07:39 +01:00
}
2013-02-14 08:34:20 +00:00
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 ();
}
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 ;
}
2013-07-26 10:16:23 +01:00
/**
* 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
2013-07-26 10:16:23 +01:00
*/
public function getMessages ( $locale = null )
{
if ( null === $locale ) {
$locale = $this -> getLocale ();
}
if ( ! isset ( $this -> catalogues [ $locale ])) {
$this -> loadCatalogue ( $locale );
}
$catalogues = array ();
$catalogues [] = $catalogue = $this -> catalogues [ $locale ];
while ( $catalogue = $catalogue -> getFallbackCatalogue ()) {
$catalogues [] = $catalogue ;
}
$messages = array ();
for ( $i = count ( $catalogues ) - 1 ; $i >= 0 ; $i -- ) {
$localeMessages = $catalogues [ $i ] -> all ();
$messages = array_replace_recursive ( $messages , $localeMessages );
}
return $messages ;
}
2015-04-02 14:33:59 +01:00
/**
2014-06-21 22:28:07 +01:00
* @ param string $locale
*/
2011-03-09 09:24:16 +00:00
protected function loadCatalogue ( $locale )
2014-06-21 22:28:07 +01:00
{
2014-07-11 12:30:03 +01:00
if ( null === $this -> cacheDir ) {
2014-06-21 22:28:07 +01:00
$this -> initializeCatalogue ( $locale );
} else {
$this -> initializeCacheCatalogue ( $locale );
}
}
/**
* @ param string $locale
*/
protected function initializeCatalogue ( $locale )
2011-09-15 07:19:52 +01:00
{
2014-07-11 12:30:03 +01:00
$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 );
2011-09-15 07:19:52 +01:00
}
2014-06-21 22:28:07 +01:00
/**
* @ param string $locale
*/
2015-04-02 14:33:59 +01:00
private function initializeCacheCatalogue ( $locale )
2014-06-21 22:28:07 +01:00
{
if ( isset ( $this -> catalogues [ $locale ])) {
2015-04-02 14:33:59 +01:00
/* Catalogue already initialized. */
2014-06-21 22:28:07 +01:00
return ;
}
2014-07-11 12:30:03 +01:00
$this -> assertValidLocale ( $locale );
2015-04-02 14:33:59 +01:00
$cacheFile = $this -> cacheDir . '/catalogue.' . $locale . '.php' ;
$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 ( $cacheFile ,
function ( ConfigCacheInterface $cache ) use ( $self , $locale ) {
$self -> dumpCatalogue ( $locale , $cache );
}
);
2014-06-21 22:28:07 +01:00
2015-04-02 14:33:59 +01:00
if ( isset ( $this -> catalogues [ $locale ])) {
/* Catalogue has been initialized as it was written out to cache. */
2014-06-21 22:28:07 +01:00
return ;
}
2015-04-02 14:33:59 +01:00
/* Read catalogue from cache. */
$catalogue = include $cache -> getPath ();
2014-12-22 08:44:13 +00:00
2015-03-19 17:40:41 +00:00
/*
2015-04-02 14:33:59 +01:00
* Gracefully handle the case when the cached catalogue is in an " old " format , without a resourcesHash
2014-12-22 08:44:13 +00:00
*/
$resourcesHash = null ;
if ( is_array ( $catalogue )) {
list ( $catalogue , $resourcesHash ) = $catalogue ;
}
if ( $this -> debug && $resourcesHash !== $this -> getResourcesHash ( $locale )) {
2015-04-02 14:33:59 +01:00
/*
* This approach of resource checking has the disadvantage that a second
* type of freshness check happens based on content * inside * the cache , while
* the idea of ConfigCache is to make this check transparent to the client ( and keeps
* the resources in a . meta file ) .
*
* Thus , we might run into the unfortunate situation that we just thought ( a few lines above )
* that the cache is fresh -- and now that we look into it , we figure it ' s not .
*
* For now , just unlink the cache and try again . See
* https :// github . com / symfony / symfony / pull / 11862 #issuecomment-54634631 and/or
* https :// github . com / symfony / symfony / issues / 7176 for possible better approaches .
*/
unlink ( $cacheFile );
$this -> initializeCacheCatalogue ( $locale );
} else {
/* Initialize with catalogue from cache. */
$this -> catalogues [ $locale ] = $catalogue ;
2014-12-22 08:44:13 +00:00
}
2015-04-02 14:33:59 +01:00
}
/**
* 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
2014-12-22 08:44:13 +00:00
2015-04-02 14:33:59 +01:00
use Symfony\Component\Translation\MessageCatalogue ;
\ $resourcesHash = '%s' ;
\ $catalogue = new MessageCatalogue ( '%s' , % s );
% s
return array ( \ $catalogue , \ $resourcesHash );
EOF
,
$this -> getResourcesHash ( $locale ),
$locale ,
var_export ( $this -> catalogues [ $locale ] -> all (), true ),
$fallbackContent
);
$cache -> write ( $content , $this -> catalogues [ $locale ] -> getResources ());
2014-12-22 08:44:13 +00:00
}
2015-03-19 17:40:41 +00:00
private function getFallbackContent ( MessageCatalogue $catalogue )
{
if ( ! $this -> debug ) {
// merge all fallback catalogues messages into $catalogue
$fallbackCatalogue = $catalogue -> getFallbackCatalogue ();
$messages = $catalogue -> all ();
while ( $fallbackCatalogue ) {
$messages = array_replace_recursive ( $fallbackCatalogue -> all (), $messages );
$fallbackCatalogue = $fallbackCatalogue -> getFallbackCatalogue ();
}
foreach ( $messages as $domain => $domainMessages ) {
$catalogue -> add ( $domainMessages , $domain );
}
return '' ;
}
$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 ;
}
2014-12-22 08:44:13 +00:00
private function getResourcesHash ( $locale )
{
if ( ! isset ( $this -> resources [ $locale ])) {
return '' ;
}
return sha1 ( serialize ( $this -> resources [ $locale ]));
2014-06-21 22:28:07 +01:00
}
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 );
2011-02-25 12:56:27 +00:00
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
{
2011-09-15 07:19:52 +01:00
$current = $this -> catalogues [ $locale ];
2011-09-28 15:08:31 +01:00
2011-09-15 07:19:52 +01:00
foreach ( $this -> computeFallbackLocales ( $locale ) as $fallback ) {
if ( ! isset ( $this -> catalogues [ $fallback ])) {
$this -> doLoadCatalogue ( $fallback );
}
2010-10-31 21:33:08 +00:00
2011-09-15 07:19:52 +01:00
$current -> addFallbackCatalogue ( $this -> catalogues [ $fallback ]);
$current = $this -> catalogues [ $fallback ];
2010-09-27 08:45:29 +01:00
}
}
2011-08-22 15:07:39 +01:00
2011-09-28 15:46:33 +01:00
protected function computeFallbackLocales ( $locale )
2011-08-22 15:07:39 +01:00
{
2011-09-20 06:50:47 +01:00
$locales = array ();
foreach ( $this -> fallbackLocales as $fallback ) {
if ( $fallback === $locale ) {
continue ;
}
$locales [] = $fallback ;
}
2011-09-15 07:19:52 +01:00
2012-02-22 17:59:56 +00:00
if ( strrchr ( $locale , '_' ) !== false ) {
2011-09-15 07:19:52 +01:00
array_unshift ( $locales , substr ( $locale , 0 , - strlen ( strrchr ( $locale , '_' ))));
2011-08-22 15:07:39 +01:00
}
2011-09-15 07:19:52 +01:00
2011-09-28 15:46:33 +01:00
return array_unique ( $locales );
2011-08-22 15:07:39 +01:00
}
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 )) {
2014-07-24 16:02:45 +01:00
throw new \InvalidArgumentException ( sprintf ( 'Invalid "%s" locale.' , $locale ));
2014-07-04 18:20:43 +01:00
}
}
2015-04-02 14:33:59 +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
}