How to use the translator service inside an Entity?
Asked Answered
A

5

6

Let's say I have a User Entity :

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

With this function :

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

How can I use the translator service inside this Entity to translate "Monsieur" and "Madame" ?

It seems the translator service should be used only inside a Controller, but I think it's appropriate in that case to use it inside this Entity.

Ashbaugh answered 23/11, 2011 at 14:53 Comment(0)
L
9

The translator service is, like you say, a "service" you can use a service inside any class (i.e. defining it as a service too and using the dependency injector container). So, you can use the translator almost wherever you want.

But the entities like aldo said shouldn't have that responsability. In the worst scenario if you really want to translate things inside the entity, you could pass the translator to the entity with a set method, i.e.

$entity->setTranslator($translator);

but I recommend you too to create a class that handles the problem outside the entity, i.e. using the twig template

{{ entity.property|trans }}).
Luby answered 23/11, 2011 at 15:50 Comment(2)
Wouah ! I didn't know about the {{ $variable | trans }} function. I believed it can work only with plain text (and not variable). Thank you very much !Ashbaugh
Yeah, by the way you don't need the $ sign. I have been working with Smarty today, in twig: {{variable|trans}} you can even translate text returned from functions, arrays, etc. like: {{app.session.getFlash('panel_alert').msg|trans}}Luby
R
4

You shouldn't and in general it is not possible. According to the Single Responsibility Principle the entity have already their purpose, which is representing data on a database. Moreover the translation is a matter of representation, so it is unlikely that you want to address such a problem in the entity layer (unless you want to provide entities translated in different languages, which totally a different problem and shouldn't even be solved using the translator).

Rethink to your logic and try something different for this. Are you sure that you don't want to do this translation on the view layer? That would be the best thing probably. Otherwise (if your logic really need to have translation at a model level) you could create a wrapper class for entities and a factory to generate this "wrapped entities"; in that factory you could inject the translator service.

Regnal answered 23/11, 2011 at 15:15 Comment(1)
In fact I did so because of this other question of mine : #8174854 But I understand what you said. Thank you :)Ashbaugh
M
3

The question is old, but I decided to add an answer to save someone's time.

Symfony has nice solution from version 5.2: Translatable Objects (similar to spackmat's solution)

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()]);
}

Then in twig template you can render it like that:

{{ user.designation | trans }}
Multiplier answered 19/7, 2023 at 18:22 Comment(0)
C
2

I ran into the similar problem and finally found this solution. This is not a direct answer to your problem because I'm also aware that an entity should have nothing to do with a service, like translator. So you should leave the getDesignation function untouched. Instead, in the presentation layer, twig for example, you translate that French designation.

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

And in your messages.en.yml

Monsieur: Mr.
Madame: Mrs.
Caffrey answered 21/4, 2014 at 7:28 Comment(0)
G
0

I ran into this problem several times over the last years and always found a good enough workaround. This time my getIdentifyingName() methods that are heavily used across the whole project (like an explicit __toString()) had to translate some keywords used in the data layer, so there was no elegant workaround.

My solution this time is a TranslateObject and a corresponding helper service. The TranslateObject is a plain object holding a translation key and an array of placeholders which also can be TranslateObjects to allow multi level translation (like a getIdentifyingNameTranslateObject() calling another related object's getIdentifyingNameTranslateObject() within one of the placeholders):

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;
    }
}

The helper looks like this and does the work:

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);
    }
}

And then within the Entity there is a getIdentifyingNameTranslateObject() method returning a 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(),
    ]);
}

When I need to return such a translated property, I need access to my injected TranslateObjectHelper service and use its trans() method like in a controller or any other service:

$this->translateObjectHelper->trans($myObject->getIdentifyingNameTranslateObject());

Then I created a twig filter as a simple helper like this:

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';
    }
}

So in Twig I can translate like this:

{{ myObject.getIdentifyingNameTranslateObject()|translateObject }}

In the end, I "just" needed to find all getIdentifyingName() calls (or .identifyingName in Twig) on that entities and replace them with getIdentifyingNameTranslateObject() with a call to the trans() method of the TranslateObjectHelper (or the translateObject filter in Twig).

Gunpaper answered 4/1, 2019 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.