deadkash/translation-form-bundle
Installation
composer require a2lix/translation-form-bundle
Add to config/bundles.php:
A2lix\TranslationFormBundle\A2lixTranslationFormBundle::class => ['all' => true],
Enable Translations
Configure config/packages/a2lix_translation_form.yaml:
a2lix_translation_form:
default_locale: en
supported_locales: [en, fr, es]
translation_class: App\Entity\Translation
First Use Case: Translatable Entity
Annotate your entity with Translation:
use A2lix\TranslationFormBundle\Model\TranslationInterface;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Product implements TranslationInterface
{
#[ORM\Id]
private ?int $id = null;
#[ORM\Column(type: 'string')]
private string $name;
#[ORM\Column(type: 'string')]
private string $description;
// Getters/setters...
}
Form Integration
Use the TranslationType in your form:
use A2lix\TranslationFormBundle\Form\Type\TranslationType;
$builder->add('product', TranslationType::class, [
'class' => Product::class,
'translation_class' => ProductTranslation::class,
'translatable_fields' => ['name', 'description'],
]);
name_en, name_fr).translation_locale form field to switch between locales dynamically:
$builder->add('translation_locale', LocaleType::class, [
'choices' => ['en' => 'English', 'fr' => 'French'],
]);
Product → Category), configure the translation_class and translatable_fields hierarchically:
# config/packages/a2lix_translation_form.yaml
a2lix_translation_form:
translation_classes:
App\Entity\Product: App\Entity\ProductTranslation
App\Entity\Category: App\Entity\CategoryTranslation
translatable_fields to dynamically include/exclude fields:
$builder->add('product', TranslationType::class, [
'translatable_fields' => $this->getTranslatableFields($userRole),
]);
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
class ProductTranslation
{
#[ORM\Column(type: 'string')]
#[Assert\NotBlank(groups: ['fr'])]
private string $name;
}
SerializerNormalizer to expose translations in API responses:
# config/packages/serializer.yaml
framework:
serializer:
normalizers:
A2lix\TranslationFormBundle\Serializer\TranslationNormalizer: ~
$form = $this->createForm(TranslationType::class, $product, [
'translation_locale' => $request->getLocale(),
]);
use A2lix\TranslationFormBundle\Event\TranslationEvents;
use A2lix\TranslationFormBundle\Event\TranslationEvent;
$dispatcher->addListener(TranslationEvents::PRE_TRANSLATION_CREATE, function (TranslationEvent $event) {
$event->getTranslation()->setCreatedBy($this->getUser());
});
{{ product.name|trans({ locale: app.request.locale }) }}
a2lix:translation:dump command to generate translation entities:
php bin/console a2lix:translation:dump
translation_locale is missing or invalid.translation_locale is always set in the form:
$builder->add('translation_locale', HiddenType::class, [
'data' => $request->getLocale(),
]);
Product → Category → Product) cause infinite loops.translatable_fields or use ignore_circular_references: true in config.fetch: EAGER for translation associations or initialize proxies manually:
$product->getTranslation($locale)->initialize();
a2lix_translation_form.yaml:
a2lix_translation_form:
fallback_locale: en
{# templates/translation_form.html.twig #}
{% block a2lix_translation_form_row %}
{{ form_row(form) }}
{% if form.vars.data.translation_locale %}
<small>Locale: {{ form.vars.data.translation_locale }}</small>
{% endif %}
{% endblock %}
debug: true in config to log translation operations:
a2lix_translation_form:
debug: true
php bin/console doctrine:schema:validate
$data = $form->getData();
dump($data);
$errors = $validator->validate($product);
if (count($errors) > 0) {
dump($errors);
}
Translation class:
a2lix_translation_form:
translation_classes:
App\Entity\Product: App\Entity\CustomProductTranslation
use A2lix\TranslationFormBundle\Event\TranslationEvents;
use A2lix\TranslationFormBundle\Event\TranslationEvent;
class CustomTranslationSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
TranslationEvents::POST_TRANSLATION_UPDATE => 'onTranslationUpdate',
];
}
public function onTranslationUpdate(TranslationEvent $event)
{
// Custom logic here
}
}
use A2lix\TranslationFormBundle\Form\Type\TranslationType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class CustomTranslationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('custom_field', TranslationType::class, [
'translatable_fields' => ['title', 'content'],
]);
}
}
use A2lix\TranslationFormBundle\Serializer\TranslationNormalizer;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
class CustomTranslationNormalizer extends TranslationNormalizer implements ContextAwareNormalizerInterface
{
public function normalize($object, string $format = null, array $context = [])
{
// Custom logic
return parent::normalize($object, $format, $context);
}
}
use Twig\TwigFunction;
use Twig\Extension\AbstractExtension;
class TranslationTwigExtension extends AbstractExtension
{
public function getFunctions()
How can I help you explore Laravel packages today?