Discuss your project
Partitech Sonata Extra Bundle

Sonata Extra Bundle: managing multilingual administrations

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...

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 Multilanguage_edit.png
  • List of contents in the selected locale Multilingual_list.png
  • Creating a translation from the current variant Multilanguage_create_translation.png

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.

Share this article