如何在实体中使用翻译服务


How to use the translator service inside an Entity?

假设我有一个User实体:

$user = new User(007);
echo $user->getName(); // display Bond
echo $user->getGender(); // display "Male";
echo $user->getDesignation() // display "Monsieur Bond" or "Mister Bond"

使用此功能:

public function getDesignation() {
  if ($this->getGender() == 'Male') return "Monsieur ".$this->getName();
  else return "Madame ".$this->getName();
}

如何使用实体内的翻译服务翻译"先生"answers"夫人"?

似乎翻译服务应该只在控制器中使用,但我认为在这种情况下,在这个实体中使用它是合适的。

转换器服务,就像你说的,是一个"服务",你可以在任何类中使用服务(也就是把它定义为一个服务,并使用依赖注入器容器)。所以,你几乎可以在任何你想要的地方使用翻译器。

但是像aldo所说的实体不应该有这样的责任。在最坏的情况下,如果你真的想要翻译实体内部的东西,你可以用set方法将转换器传递给实体,例如

$entity->setTranslator($translator);

但我建议你也创建一个类来处理实体之外的问题,即使用twig模板

{{ entity.property|trans }}).

你不应该这样做,一般来说这是不可能的。根据单一职责原则,实体已经有了它们的目的,即表示数据库中的数据。此外,翻译是一个表示问题,所以你不太可能想在实体层解决这样的问题(除非你想提供用不同语言翻译的实体,这完全是一个不同的问题,甚至不应该使用翻译器来解决)。

重新思考你的逻辑,尝试一些不同的东西。你确定不想在视图层上做这个转换吗?这可能是最好的选择。否则(如果你的逻辑确实需要在模型级别进行转换),你可以为实体创建一个包装器类,并创建一个工厂来生成这个"包装实体";在该工厂中,您可以注入translator服务。

我遇到了类似的问题,最后找到了这个解决方案。这不是对你问题的直接回答,因为我也意识到实体应该与服务无关,比如翻译。所以应该保持get指定函数不变。相反,在表示层(例如twig)中,您需要翻译该法语名称。

<div>{% trans %}{{ entity.designation }}{% endtrans %} {{ entity.name }}</div>

在你的messages.en.yml

Monsieur: Mr.
Madame: Mrs.

这个问题很老了,但是为了节省大家的时间,我决定加一个答案。

Symfony在5.2版中提供了很好的解决方案:可翻译对象(类似于packmat的解决方案)

https://symfony.com/blog/new-in-symfony-5-2-translatable-objects

use Symfony'Component'Translation'TranslatableMessage;
public function getDesignation(): TranslatableMessage {
    if ($this->getGender() == 'Male') {
        $translationKey = 'man_designation';
    } else {
        $translationKey = 'woman_designation';
    }
   return new TranslatableMessage($translationKey, ['%name%' => $this->getName()]);
}

然后在twig模板中你可以这样渲染:

{{ user.designation | trans }}

在过去几年中,我多次遇到这个问题,总能找到一个足够好的解决方法。这一次,我的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()调用(或Twig中的.identifyingName),并将它们替换为getIdentifyingNameTranslateObject(),并调用TranslateObjectHelpertrans()方法(或Twig中的translateObject过滤器)。