Broken translation of enums implementing TranslatableInterface
Describe the bug
Up until symfony/form v7.3.4, creating a ChoiceField from a property mapped to a backedEnum implementing TranslatableInterface was working fine, with choices being displayed as the return value from the trans() call on the enum case ; but since symfony/form v7.3.5 it displays the case name instead.
To Reproduce Steps to reproduce this error and also, the EasyAdmin version used. EasyAdmin v4.27.3 is affected, if symfony/form is >= v7.3.5 . Create an entity that has a field mapped to a backedEnum that implements TranslatableInterface, and a crud controller that includes the property in forms as a ChoiceField. With symfony/form < v7.3.5 in forms, the choices are displayed as the translated value, with symfony/form >= v7.3.5 it is the name that is displayed.
(OPTIONAL) Additional context
I investigated a little, and found the reason why this changed in symfony/form.
When using EnumType (which is used by ChoiceField when enumType is set in the doctrine metadata of the property by default), the label used to (up until v7.3.4) be computed depending on the value of the enum ; if it implemented TranslatableInterface, it was translated, otherwise its name was used.
This was considered a bug, as keys from the choices option should be used as labels, and they changed this behaviour in v7.3.5 to conform to this.
So now, when using EnumType, the labels used are the enum value (correctly translated) only if the keys of the choices array are only integers (well I think they currently test it with array_list, but I saw another PR that changed this to testing if the key was an integer, because it had other side effects).
In the ChoiceConfigurator, the choices are set up with the name of the enum as keys, not with integers, so now they are labelled with that name.
I made the changes to fix this behaviour, with some test that shows the problem, I will make a PR when I have cleaned up everything.
Can confirm I've run into this as well. When you implement the Translatable interface it just translates the display value of the field (index and detail view) while displaying the raw value in the form version.
Inversely if you set the choice_label form option as described in the docs it does the same thing but switched (raw values in display views, correct values in form views).
So atm if you want properly translated enum values you have to set both....
Couldn't we simply change ChoiceConfigurator.php:65 to use automatic numeric keys? Since the value is already used during rendering, it seems the keys are not needed (unless I'm missing something).
$processedEnumChoices = [];
foreach ($choices as $choice) {
- $processedEnumChoices[$choice->name] = $choice;
+ $processedEnumChoices[] = $choice;
}
Regardless, having the tests from @FoxCie is great :)
Couldn't we simply change ChoiceConfigurator.php:65 to use automatic numeric keys? Since the value is already used during rendering, it seems the keys are not needed (unless I'm missing something).
$processedEnumChoices = []; foreach ($choices as $choice) {
$processedEnumChoices[$choice->name] = $choice;
$processedEnumChoices[] = $choice; }Regardless, having the tests from @FoxCie is great :)
Probably, but I wasn't too sure if the use of the name as a key was impactful on some templates or elsewhere, so I tried changing it only when it seems relevant. Your proposal may be better, but since there was a processing made in the first place to change these keys, I supposed there was a hidden reason for this.
I just realized the issue with my suggestion. Currently ChoiceConfigurator currently only handles translatables when rendering, via
$processedEnumChoices[$choice->name] = $choice; (Source) + later building $selectedLabel based on $selectedValue->name (Source). This then breaks for form translation, as was described in the issue summary.
My previous suggestion thus does not work (it fixes forms but breaks rendering), I am currently trying this change instead, to restore the numeric indices for the form:
if ($areChoicesTranslatable && !$choicesSupportTranslatableInterface) {
$field->setFormTypeOptionIfNotSet('choices', array_keys($choices));
$field->setFormTypeOptionIfNotSet('choice_label', fn ($value) => $choices[$value]);
+ } elseif ($areChoicesTranslatable && $choicesSupportTranslatableInterface) {
+ $field->setFormTypeOptionIfNotSet('choices', array_values($choices));
} else {
$field->setFormTypeOptionIfNotSet('choices', $choices);
}
I just realized the issue with my suggestion. Currently ChoiceConfigurator currently only handles translatables when rendering, via
$processedEnumChoices[$choice->name] = $choice;(Source) + later building$selectedLabelbased on$selectedValue->name(Source). This then breaks for form translation, as was described in the issue summary.My previous suggestion thus does not work (it fixes forms but breaks rendering), I am currently trying this change instead, to restore the numeric indices for the form:
Are you sure this would solve the problem? If you look down a few lines there is an if-condition
if (null === $fieldValue || !$isIndexOrDetail) {
which will early return if it's not an index or not an detail page. So, in edit-forms and new-forms the translation would be still broken, probably. Did you check this already?
It is working in my project, both the form and the display rendering. The early return just stops processing when on a form page, further processing is applied for the display rendering. My added lines alter what is set for the Symfony form type choices option, without affecting the display rendering.
So basically in case of enums:
- Form rendering: Symfony expects a list of enum objects. But it currently gets a map instead (keyed by enum name). Thus translation is not applied.
- Display rendering: EasyAdmin expects a map of enums, keyed by name. It then maps the name back to the enum object, which is passed to the formattedValue and gets translated when rendered.