Symfony UX Autocomplete - choice_label with KnpLabs Translatable

265 views Asked by At

Autocomplete choice labels don't translate

Platform is Symfony 6.3, with UX Autocomplete 2.9

My issue

My issue: I have a Symfony UX Autocomplete form field, the choice_labels are stored in translated form in the database. I cannot get the choice_label translation to work. It always defaults to English (my default locale).

I have KnpLabs Translatable installed & working.

My current setup:

  • FunctionProfile Entity
  • FunctionProfileTranslation Entity -> contains a translated name field.
  • TrainingPickerType Form -> includes FunctionProfileAutocompleteField
  • FunctionProfileAutocompleteField -> included in TrainingPickerType

Just to make sure, the entities and the forms (removed unneccessary parts of the code):

<?php

//App\Entity\FunctionProfile.php

namespace App\Entity;

use App\Repository\FunctionProfileRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\TranslatableInterface;
use Knp\DoctrineBehaviors\Model\Translatable\TranslatableTrait;

#[ORM\Entity(repositoryClass: FunctionProfileRepository::class)]
class FunctionProfile implements TranslatableInterface
{
    use TranslatableTrait;

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

//etc.

    public function __toString(): string
    {
        return $this->translate(null, false)->getName();
    }

//etc.

And it's friend:

<?php

namespace App\Entity;

//App\Entity\FunctionProfileTranslation.php

use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\TranslationInterface;
use Knp\DoctrineBehaviors\Model\Translatable\TranslationTrait;

#[ORM\Entity]
class FunctionProfileTranslation implements TranslationInterface
{
    use TranslationTrait;

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $id;

    #[ORM\Column(length: 255)]
    private ?string $name = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }
}

Now for the form part, this main form includes the child form field that's not doing what I want it to do:

<?php

//App\Form\TrainingPickerType.php

namespace App\Form;

use App\Form\Field\FunctionProfileAutocompleteField;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

class TrainingPickerType extends AbstractType
{
    private TranslatorInterface $translator;
    private UrlGeneratorInterface $router;

    public function __construct(TranslatorInterface $translator, UrlGeneratorInterface $router)
    {
        $this->translator = $translator;
        $this->router = $router;
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $locale = $options['locale'];
        $builder
            ->setAction($this->router->generate('app_api_get_training_pick_dialog'))
            ->add('functionProfile', FunctionProfileAutocompleteField::class, [
                'label' => $this->translator->trans('Function Profiles', locale: $locale),
                'attr' => ['class' => 'none', 'placeholder' => $this->translator->trans('Start typing or select an option...', locale: $locale),],
                'label_attr' => ['class' => 'block text-sm font-medium leading-6 text-white'],
            ])
        ;
    }


    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'locale' => null
        ]);
    }
}

The form hereabove behaves as it should, the locale is set to whatever it needs to be, labels are translated nicely, etc.

This included form field is the issue:

<?php

namespace App\Form\Field;

use App\Entity\FunctionProfile;
use App\Repository\FunctionProfileRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;

#[AsEntityAutocompleteField]
class FunctionProfileAutocompleteField extends AbstractType
{
    private RequestStack $requestStack;

    public function __construct(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $request = $this->requestStack->getCurrentRequest();
        $query = $request?->query->get('query');
        $locale = $request ? $request->getLocale() : 'nl';
        
        $resolver->setDefaults([
            'class' => FunctionProfile::class,
            'multiple' => true,
            'choice_translation_domain' => true,
            'choice_label' => function (FunctionProfile $functionProfile) use ($locale) {
                $functionProfile->getTranslations();
                return $functionProfile->translate($locale)->getName();
            },
            'query_builder' => function(FunctionProfileRepository $functionProfileRepository) use ($locale, $query) {
                $qb = $functionProfileRepository->createQueryBuilder('functionProfile')
                    ->join('functionProfile.translations', 't', 'WITH', 't.locale = :locale')
                    ->setParameter('locale', $locale);

                if (!empty($query)) {
                    $qb->andWhere('t.name LIKE :search')
                        ->setParameter('search', '%' . $query . '%');
                }

                return $qb;
            },
        ]);
    }

    public function buildView(FormView|\Symfony\Component\Form\FormView $view, FormInterface|\Symfony\Component\Form\FormInterface $form, array $options): void
    {
        $view->vars['attr']['class'] = ''; // Remove any existing classes
    }

    public function getParent(): string
    {
        return ParentEntityAutocompleteType::class;
    }
}

When I enter a query, the search happens. The autocomplete options are presented. But they're always in English, even if I manually set a different locale like "nl", the outcome remains the same: all choice_labels are in English.

Any guidance or help you may be able to provide would be super welcome!

What I have tried

I have searched the KnpLabs translatable docs for solutions, but couldn't find any solutions.

I have verified that (outside of the form field), the translated versions of the entity appear; as an example;

The translation I tested

Neatly returns Banana when I run:

$test = $functionProfileRepository->findOneBy(["id" => 187]);
dd($test->translate('en')->getName());

And Banaan when I run:

$test = $functionProfileRepository->findOneBy(["id" => 187]);
dd($test->translate('nl')->getName());

I tried retrieving the choice_labels in a separate function and then add those to the choice_label option, but to no avail.

    public function getTranslatedLabel(FunctionProfile $functionProfile)
    {
        $request = $this->requestStack->getCurrentRequest();
        $locale = $request ? $request->getLocale() : 'nl';
        return $functionProfile->translate($locale)->getName();
    }

I'm sorta out of idea's now.

I know about https://github.com/a2lix/TranslationFormBundle but my colleagues indicated not wanting to use it unless absolutely needed, citing bad experiences with earlier versions of the bundle. But if we must, we will use it.

1

There are 1 answers

0
Julian Koster On BEST ANSWER

Okay, so I was a bit stupid. My $request->getLocale() wasn't sticky yet. Everything works as it should implementing this EventSubscriber: https://symfony.com/doc/current/session.html#creating-a-localesubscriber