Locastic / ApiPlatformTranslationBundle

Translation bundle for ApiPlatform based on Sylius translation
MIT License
85 stars 28 forks source link

Missing docs for a programmatic example #33

Closed nurtext closed 3 years ago

nurtext commented 3 years ago

I'm looking for a programmatic example in order to add new translations e.g. using a command, but I couldn't figure out how to get started. I'd be glad if you could give me a hint on how to start or extend the documentation with an example. Thanks!

paullla commented 3 years ago

Hi @nurtext ,

It should be quite simple. Since this bundle is using the same concept as Sylius, you can take a look at Sylius example for adding translations programatically: https://docs.sylius.com/en/1.2/book/architecture/translations.html#how-to-add-a-new-translation-programmatically. It should work here too :) GL

nurtext commented 3 years ago

Hi @paullla,

thanks for your help. Unfortunately this won't work, I tried the following:

/** @var Salutation $salutationEntity */
$salutation = new Salutation();
$salutation->setTitle('Mr.');

/** @var SalutationTranslation $salutationTranslation */
$salutationTranslation = new SalutationTranslation();
$salutationTranslation->setLocale('de');
$salutationTranslation->setTitle('Herr');

$salutation->addTranslation($salutationTranslation);
$this->entityManager->flush($salutation);

Which resulted in: In TranslatableTrait.php line 65: No locale has been set and current locale is undefined.

This are my two classes:

<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use App\Entity\SalutationTranslation;
use App\Repository\SalutationRepository;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;

/**
 * @ApiResource()
 * @ORM\Entity(repositoryClass=SalutationRepository::class)
 */
class Salutation extends AbstractTranslatable
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $title;

    /**
     * @ORM\OneToMany(targetEntity="SalutationTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"PERSIST"}, orphanRemoval=true)
     *
     * @Groups({"post_write", "translations"})
     */
    protected $translations;

    /**
     * @return int|null
     */
    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * @return string|null
     */
    public function getTitle(): ?string
    {
        return $this->getTranslation()->getTitle();
    }

    /**
     * @param string $title
     *
     * @return self
     */
    public function setTitle(string $title): self
    {
        $this->getTranslation()->setTitle($title);

        return $this;
    }

    /**
     * @return TranslationInterface
     */
    protected function createTranslation(): TranslationInterface
    {
        return new SalutationTranslation();
    }
}
<?php

namespace App\Entity;

use App\Entity\Salutation;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\SalutationTranslationRepository;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;

/**
 * @ORM\Entity(repositoryClass=SalutationTranslationRepository::class)
 */
class SalutationTranslation extends AbstractTranslation
{
    /**
     * @ORM\ManyToOne(targetEntity="Salutation", inversedBy="translations")
     */
    protected $translatable;

    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"post_read", "post_write", "translations"})
     */
    private $title;

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"post_write", "translations"})
     */
    protected $locale;

    /**
     * @return int|null
     */
    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * @param string $title
     *
     * @return void
     */
    public function setTitle(string $title): void
    {
        $this->title = $title;
    }

    /**
     * @return string|null
     */
    public function getTitle(): ?string
    {
        return $this->title;
    }
}

Any ideas what I'm doing wrong here?

Thanks!

paullla commented 3 years ago

Current locale isn't set. When creating translation using API, it happens behind the hood. So I think the solution would be to also set current locale in your code: $salutationTranslation->setCurrentLocale('de');

nurtext commented 3 years ago

I tried both setLocale() and setCurrentLocale() on the $salutationTranslation object and both didn't work for me.

paullla commented 3 years ago

I don't have any more ideas :D I'll need to reproduce it and debug the problem. I'll try to do it tomorrow and let you know the results.

nurtext commented 3 years ago

Sure, thanks a lot for now :)

nurtext commented 3 years ago

Hey @paullla, any news on this one? I tried a few different things on my own, basically creating objects one after another, trying to set different locales using different methods, but none made it work.

paullla commented 3 years ago

Hi @nurtext, I found the problem. You mapped $title in Salutation to database, and also called the setter. Fields that are translated need to be mapped to the database in SalutationTranslation only.

First step is to remove @ORM\Column(type="string") from $title in Salutation Next step is to remove $salutation->setTitle('Mr.');

So the result would be:

<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use App\Entity\SalutationTranslation;

use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;

/**
 * @ApiResource()
 * @ORM\Entity()
 */
class Salutation extends AbstractTranslatable
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    private $title;

    /**
     * @ORM\OneToMany(targetEntity="SalutationTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"PERSIST"}, orphanRemoval=true)
     * @Groups({"post_write", "translations"})
     */
    protected $translations;

    /**
     * @return int|null
     */
    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * @return string|null
     */
    public function getTitle(): ?string
    {
        return $this->getTranslation()->getTitle();
    }

    /**
     * @param string $title
     *
     * @return self
     */
    public function setTitle(string $title): self
    {
        $this->getTranslation()->setTitle($title);

        return $this;
    }

    /**
     * @return TranslationInterface
     */
    protected function createTranslation(): TranslationInterface
    {
        return new SalutationTranslation();
    }
}

and creating objects:

$salutation = new Salutation();

/** @var SalutationTranslation $salutationTranslation */
$salutationTranslation = new SalutationTranslation();
$salutationTranslation->setLocale('de');
$salutationTranslation->setTitle('Herr');

$salutation->addTranslation($salutationTranslation);
$this->entityManager->persist($salutation);
$this->entityManager->flush();
nurtext commented 3 years ago

@paullla Awesome, now it works! 👍 For some reason I though the concept was to have the translatable entity to hold the original text in the given base locale and to have the translation entity solely for translations.