2015-08-18 00:00:26 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Translation ;
use Symfony\Component\Config\ConfigCacheFactory ;
2018-11-23 12:29:20 +00:00
use Symfony\Component\Config\ConfigCacheFactoryInterface ;
use Symfony\Component\Config\ConfigCacheInterface ;
use Symfony\Component\Translation\Exception\InvalidArgumentException ;
use Symfony\Component\Translation\Exception\LogicException ;
use Symfony\Component\Translation\Exception\NotFoundResourceException ;
use Symfony\Component\Translation\Exception\RuntimeException ;
use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface ;
use Symfony\Component\Translation\Formatter\MessageFormatter ;
use Symfony\Component\Translation\Formatter\MessageFormatterInterface ;
use Symfony\Component\Translation\Loader\LoaderInterface ;
2015-08-18 00:00:26 +00:00
/**
* @ author Fabien Potencier < fabien @ symfony . com >
*/
class Translator implements TranslatorInterface , TranslatorBagInterface
{
/**
* @ var MessageCatalogueInterface []
*/
protected $catalogues = array ();
/**
* @ var string
*/
2018-11-23 12:29:20 +00:00
private $locale ;
2015-08-18 00:00:26 +00:00
/**
* @ var array
*/
private $fallbackLocales = array ();
/**
* @ var LoaderInterface []
*/
private $loaders = array ();
/**
* @ var array
*/
private $resources = array ();
/**
2018-11-23 12:29:20 +00:00
* @ var MessageFormatterInterface
2015-08-18 00:00:26 +00:00
*/
2018-11-23 12:29:20 +00:00
private $formatter ;
2015-08-18 00:00:26 +00:00
/**
* @ var string
*/
private $cacheDir ;
/**
* @ var bool
*/
private $debug ;
/**
* @ var ConfigCacheFactoryInterface | null
*/
private $configCacheFactory ;
/**
2018-11-23 12:29:20 +00:00
* @ param string $locale The locale
* @ param MessageFormatterInterface | null $formatter The message formatter
* @ param string | null $cacheDir The directory to use for the cache
* @ param bool $debug Use cache in debug mode ?
2015-08-18 00:00:26 +00:00
*
2018-11-23 12:29:20 +00:00
* @ throws InvalidArgumentException If a locale contains invalid characters
2015-08-18 00:00:26 +00:00
*/
2018-11-23 12:29:20 +00:00
public function __construct ( $locale , $formatter = null , $cacheDir = null , $debug = false )
2015-08-18 00:00:26 +00:00
{
$this -> setLocale ( $locale );
2018-11-23 12:29:20 +00:00
if ( $formatter instanceof MessageSelector ) {
$formatter = new MessageFormatter ( $formatter );
@ trigger_error ( sprintf ( 'Passing a "%s" instance into the "%s()" method as a second argument is deprecated since Symfony 3.4 and will be removed in 4.0. Inject a "%s" implementation instead.' , MessageSelector :: class , __METHOD__ , MessageFormatterInterface :: class ), E_USER_DEPRECATED );
} elseif ( null === $formatter ) {
$formatter = new MessageFormatter ();
}
$this -> formatter = $formatter ;
2015-08-18 00:00:26 +00:00
$this -> cacheDir = $cacheDir ;
$this -> debug = $debug ;
}
public function setConfigCacheFactory ( ConfigCacheFactoryInterface $configCacheFactory )
{
$this -> configCacheFactory = $configCacheFactory ;
}
/**
* 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
*
2018-11-23 12:29:20 +00:00
* @ throws InvalidArgumentException If the locale contains invalid characters
2015-08-18 00:00:26 +00:00
*/
public function addResource ( $format , $resource , $locale , $domain = null )
{
if ( null === $domain ) {
$domain = 'messages' ;
}
$this -> assertValidLocale ( $locale );
$this -> resources [ $locale ][] = array ( $format , $resource , $domain );
2018-11-23 12:29:20 +00:00
if ( \in_array ( $locale , $this -> fallbackLocales )) {
2015-08-18 00:00:26 +00:00
$this -> catalogues = array ();
} else {
unset ( $this -> catalogues [ $locale ]);
}
}
/**
* { @ inheritdoc }
*/
public function setLocale ( $locale )
{
$this -> assertValidLocale ( $locale );
$this -> locale = $locale ;
}
/**
* { @ inheritdoc }
*/
public function getLocale ()
{
return $this -> locale ;
}
/**
* Sets the fallback locales .
*
* @ param array $locales The fallback locales
*
2018-11-23 12:29:20 +00:00
* @ throws InvalidArgumentException If a locale contains invalid characters
2015-08-18 00:00:26 +00:00
*/
public function setFallbackLocales ( array $locales )
{
// needed as the fallback locales are linked to the already loaded catalogues
$this -> catalogues = array ();
foreach ( $locales as $locale ) {
$this -> assertValidLocale ( $locale );
}
$this -> fallbackLocales = $locales ;
}
/**
* Gets the fallback locales .
*
2019-01-24 08:00:03 +00:00
* @ return array The fallback locales
2015-08-18 00:00:26 +00:00
*/
public function getFallbackLocales ()
{
return $this -> fallbackLocales ;
}
/**
* { @ inheritdoc }
*/
public function trans ( $id , array $parameters = array (), $domain = null , $locale = null )
{
if ( null === $domain ) {
$domain = 'messages' ;
}
2018-11-23 12:29:20 +00:00
return $this -> formatter -> format ( $this -> getCatalogue ( $locale ) -> get (( string ) $id , $domain ), $locale , $parameters );
2015-08-18 00:00:26 +00:00
}
/**
* { @ inheritdoc }
*/
public function transChoice ( $id , $number , array $parameters = array (), $domain = null , $locale = null )
{
2018-11-23 12:29:20 +00:00
if ( ! $this -> formatter instanceof ChoiceMessageFormatterInterface ) {
throw new LogicException ( sprintf ( 'The formatter "%s" does not support plural translations.' , \get_class ( $this -> formatter )));
}
2015-08-18 00:00:26 +00:00
if ( null === $domain ) {
$domain = 'messages' ;
}
$id = ( string ) $id ;
$catalogue = $this -> getCatalogue ( $locale );
$locale = $catalogue -> getLocale ();
while ( ! $catalogue -> defines ( $id , $domain )) {
if ( $cat = $catalogue -> getFallbackCatalogue ()) {
$catalogue = $cat ;
$locale = $catalogue -> getLocale ();
} else {
break ;
}
}
2018-11-23 12:29:20 +00:00
return $this -> formatter -> choiceFormat ( $catalogue -> get ( $id , $domain ), $number , $locale , $parameters );
2015-08-18 00:00:26 +00:00
}
/**
* { @ inheritdoc }
*/
public function getCatalogue ( $locale = null )
{
if ( null === $locale ) {
$locale = $this -> getLocale ();
} else {
$this -> assertValidLocale ( $locale );
}
if ( ! isset ( $this -> catalogues [ $locale ])) {
$this -> loadCatalogue ( $locale );
}
return $this -> catalogues [ $locale ];
}
/**
* Gets the loaders .
*
* @ return array LoaderInterface []
*/
protected function getLoaders ()
{
return $this -> loaders ;
}
/**
* @ 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 );
try {
$this -> doLoadCatalogue ( $locale );
} catch ( NotFoundResourceException $e ) {
if ( ! $this -> computeFallbackLocales ( $locale )) {
throw $e ;
}
}
$this -> loadFallbackCatalogues ( $locale );
}
/**
* @ param string $locale
*/
private function initializeCacheCatalogue ( $locale )
{
if ( isset ( $this -> catalogues [ $locale ])) {
/* Catalogue already initialized. */
return ;
}
$this -> assertValidLocale ( $locale );
$cache = $this -> getConfigCacheFactory () -> cache ( $this -> getCatalogueCachePath ( $locale ),
2018-11-23 12:29:20 +00:00
function ( ConfigCacheInterface $cache ) use ( $locale ) {
$this -> dumpCatalogue ( $locale , $cache );
2015-08-18 00:00:26 +00:00
}
);
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 ();
}
2018-11-23 12:29:20 +00:00
private function dumpCatalogue ( $locale , ConfigCacheInterface $cache )
2015-08-18 00:00:26 +00:00
{
$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 ));
2017-02-03 00:28:38 +00:00
$fallbackContent .= sprintf ( <<< 'EOF'
$catalogue % s = new MessageCatalogue ( '%s' , % s );
$catalogue % s -> addFallbackCatalogue ( $catalogue % s );
2015-08-18 00:00:26 +00:00
EOF
,
$fallbackSuffix ,
$fallback ,
var_export ( $fallbackCatalogue -> all (), true ),
$currentSuffix ,
$fallbackSuffix
);
$current = $fallbackCatalogue -> getLocale ();
$fallbackCatalogue = $fallbackCatalogue -> getFallbackCatalogue ();
}
return $fallbackContent ;
}
private function getCatalogueCachePath ( $locale )
{
2018-11-23 12:29:20 +00:00
return $this -> cacheDir . '/catalogue.' . $locale . '.' . strtr ( substr ( base64_encode ( hash ( 'sha256' , serialize ( $this -> fallbackLocales ), true )), 0 , 7 ), '/' , '_' ) . '.php' ;
2015-08-18 00:00:26 +00:00
}
private function doLoadCatalogue ( $locale )
{
$this -> catalogues [ $locale ] = new MessageCatalogue ( $locale );
if ( isset ( $this -> resources [ $locale ])) {
foreach ( $this -> resources [ $locale ] as $resource ) {
if ( ! isset ( $this -> loaders [ $resource [ 0 ]])) {
2018-11-23 12:29:20 +00:00
throw new RuntimeException ( sprintf ( 'The "%s" translation loader is not registered.' , $resource [ 0 ]));
2015-08-18 00:00:26 +00:00
}
$this -> catalogues [ $locale ] -> addCatalogue ( $this -> loaders [ $resource [ 0 ]] -> load ( $resource [ 1 ], $locale , $resource [ 2 ]));
}
}
}
private function loadFallbackCatalogues ( $locale )
{
$current = $this -> catalogues [ $locale ];
foreach ( $this -> computeFallbackLocales ( $locale ) as $fallback ) {
if ( ! isset ( $this -> catalogues [ $fallback ])) {
2017-07-03 15:47:07 +00:00
$this -> initializeCatalogue ( $fallback );
2015-08-18 00:00:26 +00:00
}
2015-10-08 18:40:12 +00:00
$fallbackCatalogue = new MessageCatalogue ( $fallback , $this -> catalogues [ $fallback ] -> all ());
2016-04-20 16:56:34 +00:00
foreach ( $this -> catalogues [ $fallback ] -> getResources () as $resource ) {
$fallbackCatalogue -> addResource ( $resource );
}
2015-10-08 18:40:12 +00:00
$current -> addFallbackCatalogue ( $fallbackCatalogue );
$current = $fallbackCatalogue ;
2015-08-18 00:00:26 +00:00
}
}
protected function computeFallbackLocales ( $locale )
{
$locales = array ();
foreach ( $this -> fallbackLocales as $fallback ) {
if ( $fallback === $locale ) {
continue ;
}
$locales [] = $fallback ;
}
2018-11-23 12:29:20 +00:00
if ( false !== strrchr ( $locale , '_' )) {
array_unshift ( $locales , substr ( $locale , 0 , - \strlen ( strrchr ( $locale , '_' ))));
2015-08-18 00:00:26 +00:00
}
return array_unique ( $locales );
}
/**
* Asserts that the locale is valid , throws an Exception if not .
*
* @ param string $locale Locale to tests
*
2018-11-23 12:29:20 +00:00
* @ throws InvalidArgumentException If the locale contains invalid characters
2015-08-18 00:00:26 +00:00
*/
protected function assertValidLocale ( $locale )
{
if ( 1 !== preg_match ( '/^[a-z0-9@_\\.\\-]*$/i' , $locale )) {
2018-11-23 12:29:20 +00:00
throw new InvalidArgumentException ( sprintf ( 'Invalid "%s" locale.' , $locale ));
2015-08-18 00:00:26 +00: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 ;
}
}