Installation:
composer require braunstetter/translated-forms
Ensure you also have knplabs/doctrine-behaviors installed (for translatable entities).
Enable the Bundle:
Add to config/bundles.php:
Braunstetter\TranslatedFormsBundle\BraunstetterTranslatedFormsBundle::class => ['all' => true],
First Use Case:
Create a translatable entity (e.g., Post) with Translatable behavior, then mark a form as translated:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class PostType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('content');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'translated' => true, // Enable translation
'data_class' => Post::class,
]);
}
}
Locale Handling:
Ensure your app uses a locale (e.g., via RequestStack or middleware). The bundle reads the locale from the request.
Form Submission:
The bundle automatically detects the current locale (e.g., fr, en) from the request and binds form data to the correct translation of the entity.
Entity Structure:
Translatable behavior for your entity (e.g., Post with title and content fields).use Knp\DoctrineBehaviors\Model\Translatable\Translatable;
class Post
{
use Translatable;
// ...
}
Dynamic Field Handling:
createdAt) remain unchanged.Integration with Symfony Forms:
FormBuilder, FormFactory, and FormEvents.$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$post = $event->getData();
if ($post) {
$form->add('title', TextType::class, [
'label' => $post->getTranslations()->getCurrentLocale('title'),
]);
}
});
Locale Switching:
<a href="{{ path('app_switch_locale', {'_locale': 'fr'}) }}">Français</a>
Custom Field Mapping: Override the default field-to-translation mapping in the form type:
$resolver->setDefaults([
'translated' => [
'fields' => ['custom_field_name' => 'title'], // Map form field to entity translation
],
]);
Fallback Locales:
Configure fallback locales in config/packages/braunstetter_translated_forms.yaml:
braunstetter_translated_forms:
fallback_locales: ['en', 'fr']
Validation: Validate translations per locale using constraints:
use Symfony\Component\Validator\Constraints as Assert;
$builder->add('title', TextType::class, [
'constraints' => [
new Assert\NotBlank(),
new Assert\Length(['min' => 3]),
],
]);
API/JSON Forms: For API responses, ensure the correct locale is set in the request before processing:
$request->setLocale('fr');
$form = $this->createForm(PostType::class, $post, ['translated' => true]);
Locale Not Set:
public function __construct(private RequestStack $requestStack) {}
public function setLocale(): void
{
$request = $this->requestStack->getCurrentRequest();
$request->setLocale($request->getPreferredLanguage(['en', 'fr']));
}
Proxy Translations Missing:
Translatable behavior isn’t configured with Proxy translations, the bundle won’t work.Proxy to your entity:
use Knp\DoctrineBehaviors\Model\Translatable\Translation;
class PostTranslation
{
use Translation;
}
Caching Conflicts:
$this->get('cache')->clear();
Nested Forms:
$builder->add('author', AuthorType::class, ['translated' => true]);
Database Mismatch:
translated fields don’t match the form fields.Translatable fields.Check Locale: Dump the current locale in a controller to verify it’s set:
dd($request->getLocale()); // Should output 'fr', 'en', etc.
Form Data Binding: Debug form binding by inspecting the entity after submission:
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dd($post->getTranslations()->getCurrentLocale('title')); // Check if data is bound
}
Event Listeners:
Use FormEvents::PRE_SUBMIT to log form data before submission:
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
error_log(print_r($event->getData(), true));
});
Custom Translator: Override the default translator service to handle custom logic:
# config/services.yaml
Braunstetter\TranslatedFormsBundle\Translator\TranslatorInterface: '@app.custom_translator'
Dynamic Field Labels:
Use FormEvents::PRE_SET_DATA to dynamically set labels based on locale:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$form->get('title')->setLabel(__('Title', [], $event->getRequest()->getLocale()));
});
Locale-Aware Validation: Add locale-specific validation rules:
$builder->add('title', TextType::class, [
'constraints' => [
new Assert\Length([
'min' => $locale === 'fr' ? 5 : 3,
'minMessage' => '{{ locale }}: Minimum length is {{ limit }} characters.',
]),
],
]);
Fallback Logic: Implement custom fallback logic for missing translations:
$resolver->setDefaults([
'translated' => [
'fallback_locale' => 'en',
'fallback_on_missing' => true,
],
]);
How can I help you explore Laravel packages today?