Я сталкивался с этой проблемой несколько раз за последние годы и всегда находил достаточно хороший обходной путь. На этот раз мои getIdentifyingName()
методы, которые интенсивно используются во всем проекте (например, явный __toString()
), должны были перевести некоторые ключевые слова, используемые на уровне данных, поэтому элегантного обходного пути не было.
Мое решение на этот раз - TranslateObject
и соответствующая вспомогательная служба. TranslateObject
- это простой объект, содержащий ключ перевода и массив заполнителей, которые также могут быть объектами TranslateObjects для обеспечения многоуровневой трансляции (например, getIdentifyingNameTranslateObject()
вызывает другой связанный объект getIdentifyingNameTranslateObject()
в одном из заполнителей):
namespace App\Utils;
class TranslateObject
{
/** @var string */
protected $transKey;
/** @var array */
protected $placeholders;
public function __construct(string $transKey, array $placeholders = [])
{
$this->transKey = $transKey;
$this->placeholders = self::normalizePlaceholders($placeholders);
}
public static function normalizePlaceholders(array $placeholders): array
{
foreach ($placeholders as $key => &$placeholder) {
if (substr($key, 0, 1) !== '%' || substr($key, -1, 1) !== '%') {
throw new \InvalidArgumentException('The $placeholder attribute must only contain keys in format "%placeholder%".');
}
if ($placeholder instanceof TranslateObject) {
continue;
}
if (is_scalar($placeholder)) {
$placeholder = ['value' => $placeholder];
}
if (!isset($placeholder['value']) || !is_scalar($placeholder['value'])) {
throw new \InvalidArgumentException('$placeholders[\'%placeholder%\'][\'value\'] must be present and a scalar value.');
}
if (!isset($placeholder['translate'])) {
$placeholder['translate'] = false;
}
if (!is_bool($placeholder['translate'])) {
throw new \InvalidArgumentException('$placeholders[\'%placeholder%\'][\'translate\'] must be a boolean.');
}
}
return $placeholders;
}
public function getTransKey(): string
{
return $this->transKey;
}
public function getPlaceholders(): array
{
return $this->placeholders;
}
}
Помощник выглядит следующим образом и выполняет работу:
namespace App\Services;
use App\Utils\TranslateObject;
use Symfony\Contracts\Translation\TranslatorInterface;
class TranslateObjectHelper
{
/** @var TranslatorInterface */
protected $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function trans(TranslateObject $translateObject): string
{
$placeholders = $translateObject->getPlaceholders();
foreach ($placeholders as $key => &$placeholder) {
if ($placeholder instanceof TranslateObject) {
$placeholder = $this->trans($placeholder);
}
elseif (true === $placeholder['translate']) {
$placeholder = $this->translator->trans($placeholder['value']);
}
else {
$placeholder = $placeholder['value'];
}
}
return $this->translator->trans($translateObject->getTransKey(), $placeholders);
}
}
А затем внутри сущности есть метод getIdentifyingNameTranslateObject()
, возвращающий TranslateObject
.
/**
* Get an identifying name as a TranslateObject (for use with TranslateObjectHelper)
*/
public function getIdentifyingNameTranslateObject(): TranslateObject
{
return new TranslateObject('my.whatever.translation.key', [
'%placeholder1%' => $this->myEntityProperty1,
'%placeholderWithANeedOfTranslation%' => [
'value' => 'my.whatever.translation.values.' . $this->myPropertyWithANeedOfTranslation,
'translate' => true,
],
'%placeholderWithCascadingTranslationNeeds%' => $this->getRelatedEntity()->getIdentifyingNameTranslateObject(),
]);
}
Когда мне нужно вернуть такое переведенное свойство, мне нужен доступ к моей внедренной службе TranslateObjectHelper
и использование ее метода trans()
, как в контроллере или любой другой службе:
$this->translateObjectHelper->trans($myObject->getIdentifyingNameTranslateObject());
Затем я создал фильтр веточек в виде простого помощника, подобного этому:
namespace App\Twig;
use App\Services\TranslateObjectHelper;
use App\Utils\TranslateObject;
class TranslateObjectExtension extends \Twig_Extension
{
/** @var TranslateObjectHelper */
protected $translateObjectHelper;
public function __construct(TranslateObjectHelper $translateObjectHelper)
{
$this->translateObjectHelper = $translateObjectHelper;
}
public function getFilters()
{
return array(
new \Twig_SimpleFilter('translateObject', [$this, 'translateObject']),
);
}
/**
* sends a TranslateObject through a the translateObjectHelper->trans() method
*/
public function translateObject(TranslateObject $translateObject): string
{
return $this->translateObjectHelper->trans($translateObject);
}
public function getName(): string
{
return 'translate_object_twig_extension';
}
}
Так что в Twig я могу перевести так:
{{ myObject.getIdentifyingNameTranslateObject()|translateObject }}
В конце концов, мне «просто» нужно было найти все getIdentifyingName()
вызовы (или .identifyingName
в Twig) для этих объектов и заменить их на getIdentifyingNameTranslateObject()
вызовом trans()
метода * 1035. * (или фильтр translateObject
в Twig).