Skip to content

[Translation] Remove TranslatorBagInterface to allow for optimized caching in 3.0 #14530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
68 changes: 47 additions & 21 deletions src/Symfony/Component/Translation/DataCollectorTranslator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface
class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, FallbackLocaleAwareInterface
{
const MESSAGE_DEFINED = 0;
const MESSAGE_MISSING = 1;
Expand All @@ -30,13 +30,23 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
*/
private $messages = array();

/**
* @var FallbackLocaleAwareInterface
*/
private $fallbackLocaleAware;

/**
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
*/
public function __construct(TranslatorInterface $translator)
{
if (!$translator instanceof TranslatorBagInterface) {
throw new \InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', get_class($translator)));
if ($translator instanceof FallbackLocaleAwareInterface) {
$this->fallbackLocaleAware = $translator;
} else if ($translator instanceof TranslatorBagInterface) {
@trigger_error(sprintf('The Translator "%" should implement \Symfony\Component\Translation\FallbackLocaleAwareInterface instead of (or in addition to) \Symfony\Component\Translation\TranslatorBagInterface.', get_class($translator)), E_USER_DEPRECATED);
$this->fallbackLocaleAware = new TranslatorBagToFallbackLocaleAwareAdapter($translator);
} else {
throw new \InvalidArgumentException(sprintf('The Translator "%s" implements neither \Symfony\Component\Translation\FallbackLocaleAwareInterface nor the deprecated \Symfony\Component\Translation\TranslatorBagInterface.', get_class($translator)));
}

$this->translator = $translator;
Expand Down Expand Up @@ -67,6 +77,16 @@ public function transChoice($id, $number, array $parameters = array(), $domain =
/**
* {@inheritdoc}
*/
public function resolveLocale($id, $domain = null, $locale = null)
{
return $this->fallbackLocaleAware->resolveLocale($id, $domain, $locale);
}

/**
* {@inheritdoc}
*
* @api
*/
public function setLocale($locale)
{
$this->translator->setLocale($locale);
Expand All @@ -82,9 +102,15 @@ public function getLocale()

/**
* {@inheritdoc}
*
* @deprecated TranslatorBagInterface implementation will be removed in 3.0.
*/
public function getCatalogue($locale = null)
{
if (!$this->translator instanceof TranslatorBagInterface) {
throw new \RuntimeException(sprintf('You called the deprecated \Symfony\Component\Translation\DataCollectorTranslator::getCatalogue() method, but the Translator provided to the \Symfony\Component\Translation\DataCollectorTranslator constructor does not implement TranslatorBagInterface. (Hint: The class is %s.)', get_class($this->translator)));
}

return $this->translator->getCatalogue($locale);
}

Expand Down Expand Up @@ -118,25 +144,25 @@ private function collectMessage($locale, $domain, $id, $translation, $parameters
$domain = 'messages';
}

if (null === $locale) {
$locale = $this->translator->getLocale();
}

$id = (string) $id;
$catalogue = $this->translator->getCatalogue($locale);
$locale = $catalogue->getLocale();
if ($catalogue->defines($id, $domain)) {
$state = self::MESSAGE_DEFINED;
} elseif ($catalogue->has($id, $domain)) {
$state = self::MESSAGE_EQUALS_FALLBACK;

$fallbackCatalogue = $catalogue->getFallBackCatalogue();
while ($fallbackCatalogue) {
if ($fallbackCatalogue->defines($id, $domain)) {
$locale = $fallbackCatalogue->getLocale();
break;
}

$fallbackCatalogue = $fallbackCatalogue->getFallBackCatalogue();
}
} else {
$state = self::MESSAGE_MISSING;
$resolvedLocale = $this->translator->resolveLocale($id, $domain, $locale);

switch (true) {
case $resolvedLocale === $locale:
$state = self::MESSAGE_DEFINED;
break;

case $resolvedLocale === null:
$state = self::MESSAGE_MISSING;
break;

default:
$state = self::MESSAGE_EQUALS_FALLBACK;
$locale = $resolvedLocale;
}

$this->messages[] = array(
Expand Down
35 changes: 35 additions & 0 deletions src/Symfony/Component/Translation/FallbackLocaleAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?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;

/**
* An implementing class handles multiple locales and possible MessageCatalogues
* to find appropriate translations with some kind of fallback strategy.
*
* Through this interface it is possible to query which locale the translation
* for a given message id and domain will be taken from.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface FallbackLocaleAwareInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TranslatorLocaleAwareInterface ?

{
/**
* Determines the locale that the translation will ultimately be taken from.
*
* @param string $id The message id (may also be an object that can be cast to string)
* @param string|null $domain The domain for the message or null to use the default
* @param string|null $locale The locale or null to use the default
*
* @return string|null The locale of the best available translation or null for unknown messages.
*/
public function resolveLocale($id, $domain = null, $locale = null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDefinesLocale ?

}
48 changes: 40 additions & 8 deletions src/Symfony/Component/Translation/LoggingTranslator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface
class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, FallbackLocaleAwareInterface
{
/**
* @var TranslatorInterface|TranslatorBagInterface
Expand All @@ -28,14 +28,24 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface
*/
private $logger;

/**
* @var FallbackLocaleAwareInterface
*/
private $fallbackLocaleAware;

/**
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
* @param LoggerInterface $logger
*/
public function __construct(TranslatorInterface $translator, LoggerInterface $logger)
{
if (!$translator instanceof TranslatorBagInterface) {
throw new \InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', get_class($translator)));
if ($translator instanceof FallbackLocaleAwareInterface) {
$this->fallbackLocaleAware = $translator;
} else if ($translator instanceof TranslatorBagInterface) {
@trigger_error(sprintf('The Translator "%" should implement \Symfony\Component\Translation\FallbackLocaleAwareInterface instead of (or in addition to) \Symfony\Component\Translation\TranslatorBagInterface.', get_class($translator)), E_USER_DEPRECATED);
$this->fallbackLocaleAware = new TranslatorBagToFallbackLocaleAwareAdapter($translator);
} else {
throw new \InvalidArgumentException(sprintf('The Translator "%s" implements neither \Symfony\Component\Translation\FallbackLocaleAwareInterface nor the deprecated \Symfony\Component\Translation\TranslatorBagInterface.', get_class($translator)));
}

$this->translator = $translator;
Expand Down Expand Up @@ -67,6 +77,16 @@ public function transChoice($id, $number, array $parameters = array(), $domain =
/**
* {@inheritdoc}
*/
public function resolveLocale($id, $domain = null, $locale = null)
{
return $this->fallbackLocaleAware->resolveLocale($id, $domain, $locale);
}

/**
* {@inheritdoc}
*
* @api
*/
public function setLocale($locale)
{
$this->translator->setLocale($locale);
Expand All @@ -82,9 +102,15 @@ public function getLocale()

/**
* {@inheritdoc}
*
* @deprecated TranslatorBagInterface implementation will be removed in 3.0.
*/
public function getCatalogue($locale = null)
{
if (!$this->translator instanceof TranslatorBagInterface) {
throw new \RuntimeException(sprintf('You called the deprecated \Symfony\Component\Translation\LoggingTranslator::getCatalogue() method, but the Translator provided to the \Symfony\Component\Translation\LoggingTranslator constructor does not implement TranslatorBagInterface. (Hint: The class is %s.)', get_class($this->translator)));
}

return $this->translator->getCatalogue($locale);
}

Expand All @@ -109,16 +135,22 @@ private function log($id, $domain, $locale)
$domain = 'messages';
}

if (null === $locale) {
$locale = $this->translator->getLocale();
}

$id = (string) $id;
$catalogue = $this->translator->getCatalogue($locale);
if ($catalogue->defines($id, $domain)) {

$resolvedLocale = $this->translator->resolveLocale($id, $domain, $locale);

if ($locale === $resolvedLocale) {
return;
}

if ($catalogue->has($id, $domain)) {
$this->logger->debug('Translation use fallback catalogue.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()));
if ($resolvedLocale === null) {
$this->logger->warning('Translation not found.', array('id' => $id, 'domain' => $domain, 'locale' => $locale));
} else {
$this->logger->warning('Translation not found.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()));
$this->logger->debug('Translation use fallback catalogue.', array('id' => $id, 'domain' => $domain, 'locale' => $resolvedLocale));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,10 @@ public function testDifferentCacheFilesAreUsedForDifferentSetsOfFallbackLocales(
$this->assertEquals('bar', $translator->trans('bar'));
}

public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardlessOfCaching()
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it shoud be removed in 3.0 rename it into testLegacyPrimaryA.. instead and add

$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);

* @group legacy
*/
public function testLegacyPrimaryAndFallbackCataloguesContainTheSameMessagesRegardlessOfCaching()
{
/*
* As a safeguard against potential BC breaks, make sure that primary and fallback
Expand Down
10 changes: 8 additions & 2 deletions src/Symfony/Component/Translation/Tests/TranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ public function testSetValidLocale($locale)
$this->assertEquals($locale, $translator->getLocale());
}

public function testGetCatalogue()
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

* @group legacy
*/
public function testLegacyGetCatalogue()
{
$translator = new Translator('en');

Expand All @@ -85,7 +88,10 @@ public function testGetCatalogue()
$this->assertEquals(new MessageCatalogue('fr'), $translator->getCatalogue('fr'));
}

public function testGetCatalogueReturnsConsolidatedCatalogue()
/**
* @deprecated
*/
public function testLegacyGetCatalogueReturnsConsolidatedCatalogue()
{
/*
* This will be useful once we refactor so that different domains will be loaded lazily (on-demand).
Expand Down
Loading