backend Sep 25

Having troubles with implementing translations in ApiPlatform?

3 min read –

So here you are, trying to implement translations for entities on your ApiPlatform app — but you can’t decide on the best way to do it.

Luckily for you, there’s a bundle that could probably help you. I named it LocasticApiTranslationBundle and it’s inspired by Sylius translation which uses the approach of personal translations.

Sylius is an Open Source eCommerce Framework based on Symfony full stack. The technology is constructed from fully decoupled components (bundles in Symfony glossary), which means that every feature (products catalog, shipping engine, promotions system…) can be used in any other application.

symfony.com

What’s the idea?

First of all, each entity has a related translation entity which holds all translatable fields. Using serialization groups you can either return the object translated to a rather single language or use another way – return an embedded collection with all available translations.

Let’s get down to coding

  1. Create translatable and translation classes

Let’s assume that we would like to have Event entity with translatable fields, then we need to make Event and EventTranslation classes. As a result, let’s take a look at Event class:

<?php
namespace App\Entity;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;
use Symfony\Component\Serializer\Annotation\Groups;
class Event extends AbstractTranslatable
{
    private $id;
    /**
     * @Groups({"event_read", "event_write"})
     */
    private $startAt;
    /**
     * @Groups({"event_read"})
     */
    private $title;
    /**
     * @Groups({"event_read"})
     */
    private $description;
    /**
     * @Groups({"event_write", "translations"})
     */
    protected $translations;
    public function getId()
    {
        return $this->id;
    }
    public function getStartAt(): \DateTime
    {
        return $this->startAt;
    }
    public function setStartAt(\DateTime $startAt)
    {
        $this->startAt = $startAt;
    }
    public function setTitle($title)
    {
        $this->getTranslation()->setTitle($title);
    }
    public function getTitle(): string
    {
        return $this->getTranslation()->getTitle();
    }
    public function setDescription($description)
    {
        $this->getTranslation()->setDescription($description);
    }
    public function getDescription(): string
    {
        return $this->getTranslation()->getDescription();
    }
    protected function createTranslation():TranslationInterface
    {
        return new EventTranslation();
    }
}

Event needs to extend AbstractTranslatable class and implement method for creating new EventTranslation object. AbstractTranslatable class provides getTranslation() method which gives us access to our translation class.

Because of this, we are able to make virtual fields for title and description and set their translated values. $translations variable is overwritten just to add group for writing Event object.

Translation class is somewhat simpler, due to only important thing is to extend AbstractTranslation and add corresponding serialization groups:

<?php
namespace App\Entity;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;
use Symfony\Component\Serializer\Annotation\Groups;
class EventTranslation extends AbstractTranslation
{
    /**
     * @var int
     */
    private $id;
    /**
     * @var string
     *
     * @Groups({"event_read", "event_write", "translations"})
     */
    private $title;
    /**
     * @var string
     *
     * @Groups({"event_read", "event_write", "translations"})
     */
    private $description;
    /**
     * @var string
     * @Groups({"event_write", "translations"})
     */
    protected $locale;
    
//  ... getters and setters
  • When it comes to doctrine relation between Event and EventTranslation make sure that you set fetch to “EXTRA_LAZY” and index-by to “locale”.

2. Create API resource

Therefore, the next step is to create resource for Event and EventTranslation classes:

App\Event:
    itemOperations:
        get:
            method: GET
        put:
            method: PUT
            normalization_context:
                groups: ['translations']
    collectionOperations:
        get:
            method: GET
        post:
            method: POST
            normalization_context:
                groups: ['translations']
    attributes:
        filters: ['translation.groups']
        normalization_context:
            groups: ['event_read']
        denormalization_context:
groups: ['event_write']

You should note two things here:

  • PUT and POST operations have translations normalization group, because we want to return full collection of translations for response.
  • We added translation.groups filter, so that client is able to request full translations response for any operation if necessary.

All ready!

Finally, now that you managed to reach towards the end of the equation, our translations are definitely ready for use and here is how:

POST Event with translations example

{
    "startAt":"2018-12-06",
    "translations": { 
        "en":{
            "title":"SymfonyCon Lisbon",
            "description":"International Symfony conference",
            "locale":"en"
        },
        "it":{
            "title":"SymfonyCon Lisbona",
            "content":"Conferenza internazionale di Symfony",
            "locale":"it"
        }
    }
}

Get response by locale

?locale=en

If you don’t specify a locale, don’t worry! The fallback locale from Symfony framework config will be used.

Get response with all available translations embedded:

?groups[]=translations

That’s all folks!

There we go, that sums up one of the easiest ways to implement translations into API Platform. All in all, if you have any questions or suggestions on how to improve translation implementation, feel free to let us know!

Also, it’s worth to note that if you need help with your API Platform, Sylius or Symfony project, get in touch with us! We might be able to help with some of the problems, hopefully.