The bundle provides a trait for admin classes and a trait for entities. Together, they add the fields and actions necessary for managing multiple language variants.
The locales come from the Sonata Page sites. The administration then displays the available variants and allows linking the records of the same translation group.
Screens
- Editing a translation on the selected site

- List of contents in the selected locale

- Creating a translation from the current variant

Example of implementation for a simple admin class
Entity file
<?php
namespace App\Entity;
use App\Repository\SimpleTestRepository;
use Doctrine\ORM\Mapping as ORM;
use Partitech\SonataExtra\Traits\EntityTranslationTrait;
#[ORM\Entity(repositoryClass: SimpleTestRepository::class)]
class SimpleTest
{
use EntityTranslationTrait;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
// ...
}
Admin file
<?php
declare(strict_types=1);
namespace App\Admin;
use Partitech\SonataExtra\Attribute\AsAdmin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Partitech\SonataExtra\Controller\Admin\TranslationController;
use Partitech\SonataExtra\Traits\AdminTranslationTrait;
#[AsAdmin(
manager_type: 'orm',
label: 'Simple Entity',
model_class: \App\Entity\SimpleTest::class,
)]
final class SimpleTestAdmin extends AbstractAdmin
{
use AdminTranslationTrait;
// ...
}
You just need to add the traits to the admin and entity classes. Then, run the following commands:
bin/console doctrine:schema:update --force
bin/console cache:clear
If you already have a controller, simply add the controller trait
class MenuController extends Controller
{
use \Partitech\SonataExtra\Traits\ControllerTranslationTrait;
}
You can also call the translation creation action using the extended controller as a service
use \Partitech\SonataExtra\Controller\Admin\TranslationController;
class MenuController extends Controller
{
use \Partitech\SonataExtra\Traits\ControllerTranslationTrait;
private $TranslationController;
#[Required]
public function autowireDependencies(
TranslationController $TranslationController
): void {
$this->TranslationController = $TranslationController;
}
public function createTranslationAction($id, $from_site, $to_site, $fqcn): Response
{
return $this->TranslationController->createTranslationAction($id, $from_site, $to_site, $fqcn);
}
}
Administrator Configuration
The trait has its own configuration. If you have a configuration method in your administrator, simply make sure to call the trait's configuration as follows:
#[AsAdmin(
manager_type: 'orm',
group: 'Admin',
label: 'Test',
model_class: \App\Entity\Test::class
)]
final class TestAdmin extends AbstractAdmin
{
use AdminTranslationTrait;
protected function configure(): void
{
$this->configureTrait();
$this->setTemplate('edit', '@PartitechSonataMenu/CRUD/edit.html.twig');
}
}
Administrator route configuration
The trait adds its own route to create translations. If users also add routes, they must configure their routes as follows:
#[AsAdmin(
manager_type: 'orm',
group: 'Admin',
label: 'Test',
model_class: \App\Entity\Test::class
)]
final class TestAdmin extends AbstractAdmin
{
use AdminTranslationTrait;
protected function configureRoutes(RouteCollectionInterface $collection): void
{
$this->configureTraitRoutes($collection);
$collection->remove('show');
}
// ...
}
The call to configureTraitRoutes() add the necessary routes before configuring your own routes. This is only necessary if configureRoutes is implemented in the admin class.
Automatic translation compatibility
The multilingual feature is compatible with our automatic translation tool via the "Translation API" (see service_intelligent.md).
Add the attribute #[Translatable] to make a field eligible for automatic translation via the API, using the application's provider (see service_intelligent.md).
use Partitech\SonataExtra\Attribute\Translatable;
#[ORM\Column(type: 'text', nullable: true)]
#[Translatable]
private ?string $description=null;
// ...
This feature is compatible with relationships OneToMany. In this case, add the annotation both on the entity relationship and on the fields of the related entity.
During a translation, the object is first cloned, then translated. Automatic cloning is suitable for a simple object; for a complex entity, explicitly implement the method. __clone()The menu tree cannot be cloned without an internal cloning function.
About slug fields
Since the slug must be unique, during the cloning process, the process searches for a method $object->getSlug(). If the method exists, the process checks whether the term has been translated or not. If the term is the same, it will prefix the value with its locale.
if (method_exists($clonedObject, 'setSlug') && $clonedObject->getSlug()==$object->getSlug()) {
$slugger = new AsciiSlugger();
$slug=$slugger->slug($this->site->getLocale().'-'.$clonedObject->getSlug())->lower();
$clonedObject->setSlug($slug);
}
Multilingual integration of Sonata classification
To retain translation information of SonataClassificationTag and SonataClassificationCategory, use the admin classes provided by Sonata Extra.
In the sonata_classification.yaml configuration, keep your configuration as follows:
sonata_classification:
class:
category: App\Entity\SonataClassificationCategory
collection: App\Entity\SonataClassificationCollection
context: App\Entity\SonataClassificationContext
tag: App\Entity\SonataClassificationTag
In your entity, be sure to add the trait:
- SonataClassificationTag.php
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\DBAL\Types\Types;
use App\Repository\SonataClassificationTagRepository;
use Sonata\ClassificationBundle\Entity\BaseTag;
use Partitech\SonataExtra\Traits\EntityTranslationTrait;
#[ORM\Entity(repositoryClass: SonataClassificationTagRepository::class)]
#[ORM\Table(name: 'classification__tag')]
#[ORM\HasLifecycleCallbacks]
class SonataClassificationTag extends BaseTag
{
use EntityTranslationTrait;
#[ORM\Id]
#[ORM\Column(type: Types::INTEGER)]
#[ORM\GeneratedValue]
protected ?int $id = null;
public function getId(): ?int
{
return $this->id;
}
}
- SonataClassificationCategory.php
<?php
namespace App\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Sonata\ClassificationBundle\Entity\BaseCategory;
use Partitech\SonataExtra\Traits\EntityTranslationTrait;
#[ORM\Entity]
#[ORM\Table(name: 'classification__category')]
class SonataClassificationCategory extends BaseCategory
{
use EntityTranslationTrait;
#[ORM\Id]
#[ORM\Column(type: Types::INTEGER)]
#[ORM\GeneratedValue]
protected ?int $id = null;
public function getId(): ?int
{
return $this->id;
}
}
Use the admin classes for tags and categories provided by Sonata Extra. DO NOT USE the tag and category admin from the sonataClassification package. They will not have the tracking fields needed for multilingual support.
- sonata_admin.yaml
cms:
icon: fa fa-pencil
label: CMS
keep_open: true
items:
- Partitech\SonataExtra\Admin\ArticleAdmin
- sonata.page.admin.page
- sonata.page.admin.shared_block
- Partitech\SonataExtra\Admin\SliderAdmin
- Partitech\SonataExtra\Admin\FaqCategoryAdmin
- Partitech\SonataExtra\Admin\TagAdmin
- Partitech\SonataExtra\Admin\CategoryAdmin
Checkpoint
Before adding a translation, check that the Sonata Page sites have the expected locales and that only one entry in the group is set as the default version. Then test the creation, editing, and navigation between variants with a user with limited rights.