Skip to content

Commit b7770bc

Browse files
aitboudadfabpot
authored andcommitted
[Translation] added LoggingTranslator.
1 parent a7f867f commit b7770bc

File tree

14 files changed

+385
-7
lines changed

14 files changed

+385
-7
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
16+
17+
/**
18+
* @author Abdellatif Ait boudad <[email protected]>
19+
*/
20+
class LoggingTranslatorPass implements CompilerPassInterface
21+
{
22+
public function process(ContainerBuilder $container)
23+
{
24+
if (!$container->hasAlias('logger')) {
25+
return;
26+
}
27+
28+
if ($container->getParameter('translator.logging')) {
29+
$translatorAlias = $container->getAlias('translator');
30+
$definition = $container->getDefinition((string) $translatorAlias);
31+
$class = $container->getParameterBag()->resolveValue($definition->getClass());
32+
33+
$refClass = new \ReflectionClass($class);
34+
if ($refClass->implementsInterface('Symfony\Component\Translation\TranslatorInterface') && $refClass->implementsInterface('Symfony\Component\Translation\TranslatorBagInterface')) {
35+
$container->getDefinition('translator.logging')->setDecoratedService('translator');
36+
}
37+
}
38+
}
39+
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222
*/
2323
class Configuration implements ConfigurationInterface
2424
{
25+
private $debug;
26+
27+
/**
28+
* @param bool $debug Whether debugging is enabled or not
29+
*/
30+
public function __construct($debug)
31+
{
32+
$this->debug = (bool) $debug;
33+
}
34+
2535
/**
2636
* Generates the configuration tree builder.
2737
*
@@ -441,6 +451,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode)
441451
->canBeEnabled()
442452
->children()
443453
->scalarNode('fallback')->defaultValue('en')->end()
454+
->booleanNode('logging')->defaultValue($this->debug)->end()
444455
->end()
445456
->end()
446457
->end()

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ public function load(array $configs, ContainerBuilder $container)
169169
));
170170
}
171171

172+
/**
173+
* {@inheritdoc}
174+
*/
175+
public function getConfiguration(array $config, ContainerBuilder $container)
176+
{
177+
return new Configuration($container->getParameter('kernel.debug'));
178+
}
179+
172180
/**
173181
* Loads Form configuration.
174182
*
@@ -627,6 +635,8 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
627635
}
628636
$translator->addMethodCall('setFallbackLocales', array($config['fallback']));
629637

638+
$container->setParameter('translator.logging', $config['logging']);
639+
630640
// Discover translation directories
631641
$dirs = array();
632642
if (class_exists('Symfony\Component\Validator\Validator')) {

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass;
2121
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
2222
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass;
23+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass;
2324
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddCacheWarmerPass;
2425
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddCacheClearerPass;
2526
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass;
@@ -77,6 +78,7 @@ public function build(ContainerBuilder $container)
7778
$container->addCompilerPass(new AddConsoleCommandPass());
7879
$container->addCompilerPass(new FormPass());
7980
$container->addCompilerPass(new TranslatorPass());
81+
$container->addCompilerPass(new LoggingTranslatorPass());
8082
$container->addCompilerPass(new AddCacheWarmerPass());
8183
$container->addCompilerPass(new AddCacheClearerPass());
8284
$container->addCompilerPass(new TranslationExtractorPass());

src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
<parameters>
88
<parameter key="translator.class">Symfony\Bundle\FrameworkBundle\Translation\Translator</parameter>
9+
<parameter key="translator.logging.class">Symfony\Component\Translation\LoggingTranslator</parameter>
910
<parameter key="translator.identity.class">Symfony\Component\Translation\IdentityTranslator</parameter>
1011
<parameter key="translator.selector.class">Symfony\Component\Translation\MessageSelector</parameter>
1112
<parameter key="translation.loader.php.class">Symfony\Component\Translation\Loader\PhpFileLoader</parameter>
@@ -46,6 +47,12 @@
4647
</argument>
4748
</service>
4849

50+
<service id="translator.logging" class="%translator.logging.class%" public="false">
51+
<argument type="service" id="translator.logging.inner" />
52+
<argument type="service" id="logger" />
53+
<tag name="monolog.logger" channel="translation" />
54+
</service>
55+
4956
<service id="translator" class="%translator.identity.class%">
5057
<argument type="service" id="translator.selector" />
5158
</service>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;
13+
14+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Definition;
17+
18+
class LoggingTranslatorPassTest extends \PHPUnit_Framework_TestCase
19+
{
20+
public function testProcess()
21+
{
22+
$definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');
23+
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder');
24+
$parameterBag = $this->getMock('Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface');
25+
26+
$container->expects($this->once())
27+
->method('hasAlias')
28+
->will($this->returnValue(true));
29+
30+
$container->expects($this->once())
31+
->method('getParameter')
32+
->will($this->returnValue(true));
33+
34+
$container->expects($this->once())
35+
->method('getAlias')
36+
->will($this->returnValue('translation.default'));
37+
38+
$container->expects($this->exactly(2))
39+
->method('getDefinition')
40+
->will($this->returnValue($definition));
41+
42+
$definition->expects($this->once())
43+
->method('getClass')
44+
->will($this->returnValue("%translator.class%"));
45+
46+
$parameterBag->expects($this->once())
47+
->method('resolveValue')
48+
->will($this->returnValue("Symfony\Bundle\FrameworkBundle\Translation\Translator"));
49+
50+
$container->expects($this->once())
51+
->method('getParameterBag')
52+
->will($this->returnValue($parameterBag));
53+
54+
$pass = new LoggingTranslatorPass();
55+
$pass->process($container);
56+
}
57+
58+
public function testThatCompilerPassIsIgnoredIfThereIsNotLoggerDefinition()
59+
{
60+
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder');
61+
$container->expects($this->once())
62+
->method('hasAlias')
63+
->will($this->returnValue(false));
64+
65+
$pass = new LoggingTranslatorPass();
66+
$pass->process($container);
67+
}
68+
}

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
1919
public function testDefaultConfig()
2020
{
2121
$processor = new Processor();
22-
$config = $processor->processConfiguration(new Configuration(), array(array('secret' => 's3cr3t')));
22+
$config = $processor->processConfiguration(new Configuration(true), array(array('secret' => 's3cr3t')));
2323

2424
$this->assertEquals(
2525
array_merge(array('secret' => 's3cr3t', 'trusted_hosts' => array()), self::getBundleDefaultConfig()),
@@ -33,7 +33,7 @@ public function testDefaultConfig()
3333
public function testValidTrustedProxies($trustedProxies, $processedProxies)
3434
{
3535
$processor = new Processor();
36-
$configuration = new Configuration();
36+
$configuration = new Configuration(true);
3737
$config = $processor->processConfiguration($configuration, array(array(
3838
'secret' => 's3cr3t',
3939
'trusted_proxies' => $trustedProxies,
@@ -62,7 +62,7 @@ public function getTestValidTrustedProxiesData()
6262
public function testInvalidTypeTrustedProxies()
6363
{
6464
$processor = new Processor();
65-
$configuration = new Configuration();
65+
$configuration = new Configuration(true);
6666
$processor->processConfiguration($configuration, array(
6767
array(
6868
'secret' => 's3cr3t',
@@ -77,7 +77,7 @@ public function testInvalidTypeTrustedProxies()
7777
public function testInvalidValueTrustedProxies()
7878
{
7979
$processor = new Processor();
80-
$configuration = new Configuration();
80+
$configuration = new Configuration(true);
8181
$processor->processConfiguration($configuration, array(
8282
array(
8383
'secret' => 's3cr3t',
@@ -123,6 +123,7 @@ protected static function getBundleDefaultConfig()
123123
'translator' => array(
124124
'enabled' => false,
125125
'fallback' => 'en',
126+
'logging' => true,
126127
),
127128
'validation' => array(
128129
'enabled' => false,

src/Symfony/Component/Translation/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CHANGELOG
55
-----
66

77
* added possibility to cache catalogues
8+
* added TranslatorBagInterface
9+
* added LoggingTranslator
810

911
2.5.0
1012
-----
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Translation;
13+
14+
use Psr\Log\LoggerInterface;
15+
16+
/**
17+
* @author Abdellatif Ait boudad <[email protected]>
18+
*/
19+
class LoggingTranslator implements TranslatorInterface
20+
{
21+
/**
22+
* @var TranslatorInterface
23+
*/
24+
private $translator;
25+
26+
/**
27+
* @var LoggerInterface
28+
*/
29+
private $logger;
30+
31+
/**
32+
* @param Translator $translator
33+
* @param LoggerInterface $logger
34+
*/
35+
public function __construct($translator, LoggerInterface $logger)
36+
{
37+
if (!($translator instanceof TranslatorInterface && $translator instanceof TranslatorBagInterface)) {
38+
throw new \InvalidArgumentException(sprintf('The Translator "%s" must implements TranslatorInterface and TranslatorBagInterface.', get_class($translator)));
39+
}
40+
41+
$this->translator = $translator;
42+
$this->logger = $logger;
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
49+
{
50+
$trans = $this->translator->trans($id, $parameters , $domain , $locale);
51+
$this->log($id, $domain, $locale);
52+
53+
return $trans;
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
60+
{
61+
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
62+
$this->log($id, $domain, $locale);
63+
64+
return $trans;
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*
70+
* @api
71+
*/
72+
public function setLocale($locale)
73+
{
74+
$this->translator->setLocale($locale);
75+
}
76+
77+
/**
78+
* {@inheritdoc}
79+
*
80+
* @api
81+
*/
82+
public function getLocale()
83+
{
84+
return $this->translator->getLocale();
85+
}
86+
87+
/**
88+
* Passes through all unknown calls onto the translator object.
89+
*/
90+
public function __call($method, $args)
91+
{
92+
return call_user_func_array(array($this->translator, $method), $args);
93+
}
94+
95+
/**
96+
* Logs for missing translations.
97+
*
98+
* @param string $id
99+
* @param string|null $domain
100+
* @param string|null $locale
101+
*/
102+
private function log($id, $domain, $locale)
103+
{
104+
if (null === $locale) {
105+
$locale = $this->getLocale();
106+
}
107+
108+
if (null === $domain) {
109+
$domain = 'messages';
110+
}
111+
112+
$id = (string) $id;
113+
$catalogue = $this->translator->getCatalogue($locale);
114+
if ($catalogue->defines($id, $domain)) {
115+
return;
116+
}
117+
118+
if ($catalogue->has($id, $domain)) {
119+
$this->logger->debug('Translation use fallback catalogue.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()));
120+
} else {
121+
$this->logger->warning('Translation not found.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()));
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)