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
/**
2011-03-06 11:40:06 +00:00
* @ author Fabien Potencier < fabien @ symfony . com >
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
/**
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
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
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
*/
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
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 }
*/
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 }
*/
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
*
2016-11-09 19:27:17 +00:00
* @ deprecated since version 2.3 , to be removed in 3.0 . Use setFallbackLocales () instead
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
{
2015-06-07 07:33:05 +01: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
*/
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
*/
public function getFallbackLocales ()
{
return $this -> fallbackLocales ;
2010-09-27 08:45:29 +01:00
}
/**
* { @ inheritdoc }
*/
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-05-03 07:41:16 +01:00
if ( null === $domain ) {
$domain = 'messages' ;
}
2015-04-13 19:27:34 +01:00
return strtr ( $this -> getCatalogue ( $locale ) -> get (( string ) $id , $domain ), $parameters );
2010-09-27 08:45:29 +01:00
}
/**
* { @ inheritdoc }
*/
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-05-03 07:41:16 +01:00
if ( null === $domain ) {
$domain = 'messages' ;
}
2011-09-15 07:19:52 +01:00
$id = ( string ) $id ;
2015-04-13 19:27:34 +01:00
$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 ;
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 ();
2015-04-13 19:27:34 +01:00
} 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 ;
}
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
2015-05-12 16:16:46 +01:00
*
* @ deprecated since version 2.8 , to be removed in 3.0 . Use TranslatorBagInterface :: getCatalogue () method instead .
2013-07-26 10:16:23 +01:00
*/
public function getMessages ( $locale = null )
{
2015-06-19 16:11:11 +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 15:40:06 +01:00
2015-05-04 22:22:52 +01:00
$catalogue = $this -> getCatalogue ( $locale );
$messages = $catalogue -> all ();
2013-07-26 10:16:23 +01:00
while ( $catalogue = $catalogue -> getFallbackCatalogue ()) {
2015-05-04 22:22:52 +01:00
$messages = array_replace_recursive ( $catalogue -> all (), $messages );
2013-07-26 10:16:23 +01:00
}
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
$self = $this ; // required for PHP 5.3 where "$this" cannot be use()d in anonymous functions. Change in Symfony 3.0.
2015-04-09 00:14:28 +01:00
$cache = $this -> getConfigCacheFactory () -> cache ( $this -> getCatalogueCachePath ( $locale ),
2015-04-02 14:33:59 +01:00
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. */
2015-04-09 00:14:28 +01:00
$this -> catalogues [ $locale ] = include $cache -> getPath ();
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 .
2015-04-09 00:14:28 +01:00
*
2015-04-02 14:33:59 +01:00
* @ 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 ;
\ $catalogue = new MessageCatalogue ( '%s' , % s );
% s
2015-04-09 00:14:28 +01:00
return \ $catalogue ;
2015-04-02 14:33:59 +01:00
EOF
,
$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 )
{
$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 ));
2016-10-30 09:34:06 +00:00
$fallbackContent .= sprintf ( <<< 'EOF'
$catalogue % s = new MessageCatalogue ( '%s' , % s );
$catalogue % s -> addFallbackCatalogue ( $catalogue % s );
2015-03-19 17:40:41 +00:00
EOF
,
$fallbackSuffix ,
$fallback ,
var_export ( $fallbackCatalogue -> all (), true ),
$currentSuffix ,
$fallbackSuffix
);
$current = $fallbackCatalogue -> getLocale ();
$fallbackCatalogue = $fallbackCatalogue -> getFallbackCatalogue ();
}
return $fallbackContent ;
}
2015-04-08 13:50:34 +01:00
private function getCatalogueCachePath ( $locale )
{
2015-05-20 18:31:24 +01:00
return $this -> cacheDir . '/catalogue.' . $locale . '.' . sha1 ( serialize ( $this -> fallbackLocales )) . '.php' ;
2015-04-08 13:50:34 +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 ])) {
2017-04-03 16:48:48 +01:00
$this -> initializeCatalogue ( $fallback );
2011-09-15 07:19:52 +01:00
}
2010-10-31 21:33:08 +00:00
2015-09-02 10:56:25 +01:00
$fallbackCatalogue = new MessageCatalogue ( $fallback , $this -> catalogues [ $fallback ] -> all ());
2016-01-28 22:27:35 +00:00
foreach ( $this -> catalogues [ $fallback ] -> getResources () as $resource ) {
$fallbackCatalogue -> addResource ( $resource );
}
2015-09-02 10:56:25 +01:00
$current -> addFallbackCatalogue ( $fallbackCatalogue );
$current = $fallbackCatalogue ;
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
2017-09-07 10:04:22 +01:00
if ( false !== strrchr ( $locale , '_' )) {
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
}