We can extend the approach described in the guide by making our custom model translatable. This is particularly useful for any entity whose content may vary based on locale, such as descriptions, instructions, and names.
In this example, we assume you have already created a custom Supplier model. We will now add email and makenameanddescriptionfields translatable.
Making the custom model translatable
Step 1: Create the Translation Entity
Weβll start by creating a SupplierTranslation entity that will hold the locale-specific fields.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Resource\Model\AbstractTranslation;
use Sylius\Component\Resource\Model\TranslationInterface;
use Sylius\Resource\Model\ResourceInterface;
#[ORM\Entity]
#[ORM\Table(name: 'app_supplier_translation')]
class SupplierTranslation extends AbstractTranslation implements ResourceInterface, TranslationInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
#[ORM\Column(length: 255)]
private ?string $description = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): void
{
$this->name = $name;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): void
{
$this->description = $description;
}
}
Step 2: Update the Supplier Entity to Use Translations
Extend the Supplier entity to manage the translatable fields through the translation mechanism.
<?php
namespace App\Entity;
use App\Repository\SupplierRepository;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Resource\Model\ResourceInterface;
use Sylius\Resource\Model\TranslatableInterface;
use Sylius\Resource\Model\TranslatableTrait;
use Sylius\Resource\Model\TranslationInterface;
#[ORM\Entity]
#[ORM\Table(name: 'app_supplier')]
class Supplier implements ResourceInterface, TranslatableInterface
{
use TranslatableTrait {
__construct as private initializeTranslationsCollection;
}
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
private ?string $email = null;
private ?string $name = null;
private ?string $description = null;
public function __construct()
{
$this->initializeTranslationsCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->getTranslation()->getName();
}
public function setName(?string $name): void
{
$this->getTranslation()->setName($name);
}
public function getDescription(): ?string
{
return $this->getTranslation()->getDescription();
}
public function setDescription(?string $description): void
{
$this->getTranslation()->setDescription($description);
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): void
{
$this->email = $email;
}
protected function createTranslation(): TranslationInterface
{
return new SupplierTranslation();
}
}
Step 3: Create Form Types:
To enable proper multilingual input for your Supplier entity in the Sylius Admin, you'll need to:
Create a main form type that includes both static fields (like email) and translatable fields (like name, description) via ResourceTranslationsType.
Create a separate form type for the translation entity, specifying what fields should be localized.
Register both form types as services (if you're not using autoconfiguration).
SupplierType (Main Form Type)
This form type defines the top-level fields for the Supplier entity, including:
A static email field
A dynamic translations collection using ResourceTranslationsType
π File path: src/Form/Type/SupplierType.php
<?php
namespace App\Form\Type;
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Sylius\Bundle\ResourceBundle\Form\Type\ResourceTranslationsType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormBuilderInterface;
final class SupplierType extends AbstractResourceType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class, [
'label' => 'sylius.ui.email',
])
->add('translations', ResourceTranslationsType::class, [
'entry_type' => SupplierTranslationType::class,
'label' => 'sylius.ui.translations',
])
;
}
public function getBlockPrefix(): string
{
return 'app_supplier';
}
}
SupplierTranslationType (Translation Subform)
This form defines which fields can be translated per locale. Here we include:
alias: app.supplier: Tells Sylius to use the resource alias defined in _sylius.yaml.
section: admin: Registers the resource inside the admin panel.
form.type: Connects your custom SupplierType form.
grid: Should match the grid you'll configure for listing suppliers.
redirect: update: Automatically redirects to the update form after creation.
except: ['show']: Omits the show action (optional).
Configure grid
To manage Supplier entities from the Sylius Admin Panel, you need to configure a grid. This grid defines how supplier records appear and which actions are available (create, update, delete).
These keys are used in grid labels, menu items, form titles, and other UI elements.
You can reuse them across templates and configuration files (e.g., label: app.ui.supplier).
Make sure to clear your Symfony cache after adding new translation keys:
bin/console cache:clear
Step 5: Update the Database with Migrations
Now that your entities and resource configuration are complete, you'll need to update your database schema. This is done using Doctrine Migrations.
Generate the Migration
Use the following command to detect and generate a new migration file based on the changes to your entities (specifically Supplier and SupplierTranslation):
php bin/console doctrine:migrations:diff
Tip: Make sure your database is already in sync with the current codebase before running this, or it may generate unintended changes.
Run the Migration
After the migration file is created (in migrations/), apply it to update your actual database schema:
php bin/console doctrine:migrations:migrate
You should see SQL statements executed for creating the app_supplier and app_supplier_translation tables.
π― Pimp Your Translations Section Using Twig Hooks and a Dedicated Macro
By default, Sylius renders translations as they are stored β with base styling. But you can easily align your translation UI with other admin sections using a predefined Twig macro from SyliusAdmin.
It helps unify your form sections and make translations clean, readable, and well-integrated.
β 5: Final Result: Translations Section in the Admin Panel
After completing the form and hook customization steps, your supplier form in the Sylius Admin should now display a clean and localized Translations section.
This layout includes:
A General section with static fields like email
A Translations section rendered using the with_hook macro
Accordion-style language tabs, each containing the name and description fields
This structure provides a user-friendly editing experience and ensures consistency with the rest of the Sylius admin layout.
Use to automatically apply consistent layout, translation tabs, and localization context.
The with_hook macro works hand-in-hand with Sylius's Twig hook system to render clean, localized translation forms.
But thatβs just the beginning β the helper contains more useful methods to streamline translation UIs.
π Explore all available macros and helper functions .