Login to bookmark this video
Buy Access to Course
16.

Translatable Mapping

|

Share this awesome video!

|

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

This Chapter isn't quite ready...

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com

It's time to dive into how users will mark their app's entities for translation. For our app, we have four entities: Article, Category, Tag, and Translation. The Translation entity of course stores our translations, but the other three need translations.

For instance, check out Article. We need to mark this class as translatable. And down here, we want the title and content properties to be translatable fields on this entity.

Choosing a Translation Approach

We've got a couple ways to achieve this. One method could be to create some sort of interface in our bundle. User's would then implement this on entities they want translatable. But a more modern and slick approach is to use PHP attributes.

I'm all for slick and modern, so let's go with attributes.

These will be similar to these Doctrine ORM mapping attributes. Ours will map translatable entities and properties.

Creating the Translatable Attribute

In our object-translation-bundle/src directory, create a new directory, Mapping. This is where we'll store our attributes.

Inside, create a new PHP class called Translatable. This attribute will be used to mark an entity as translatable. Make it final. To let PHP know that this is an attribute, we need to use an attribute! Above the class, write #[\Attribute()]. The first argument is what type of element this attribute can be applied to. In our case, we want to apply this to classes, so write \Attribute::TARGET_CLASS.

We need to pass in one argument to this attribute: the type. This is that string alias we'll store in the database instead of the class name. Create a constructor with public function __construct(). Now add a single, mandatory argument (that's also a property): public string $type.

Creating the TranslatableProperty Attribute

We also need to let our bundle know what properties should be translatable. Now, we could use an array argument in the Translatable attribute to list the properties... but I think it's cleaner to use a separate attribute for this.

Also in the Mapping directory, create another PHP class called TranslatableProperty. This class will be empty, no arguments - it's just used as a property marker.

Mark the class as an attribute with #[\Attribute()]. This time, we want to apply this attribute to properties, so use \Attribute::TARGET_PROPERTY.

Marking Entities as Translatable

Now that our attributes are ready, let's go to our app's entities and mark them. Start with Article. Above the class, add the attribute #[Translatable('article')]. Now, identify the properties that are going to be translatable. Above $title, add #[TranslatableProperty] and do the same for $content.

Next, mark Category as translatable with #[Translatable('category')]. The $name property will be a TranslatableProperty.

Finally, mark Tag as #[Translatable('tag')] and again, the $name property will be the only TranslatableProperty.

Both our Tag and Category have a single translatable property. Article has two: $content and $title. Nice!

Creating Translation Fixtures

There's one last thing I want to do. If we look in our app's Story directory, we'll find AppStory where we're loading the articles for our fixture data. To test this more effectively, we'll create some translation fixtures. This way, when we switch to French on our site, we'll display some mock French data, just like we currently have mock English data.

We need to create a factory using Foundry. So, run:

symfony console make:factory

Create a factory for the Translation entity: 0 in this list.

If we navigate back to our Factory directory, here it is: TranslationFactory. In defaults(), it's detected all fields and is generating fake data for them. We're going to override all of these when we create it, so no need to edit anything here - we just need this factory to exist.

Now, in AppStory, find the first article "Why asteroids taste like bacon". Assign this created article to a variable with $article1 = . We do this because we need to get its ID to link our translations to it.

Below this fixture, create our first translation with TranslationFactory::createOne(). Inside, an array: 'locale' => 'fr', 'objectId' => $article1->getId(), 'objectType' => 'article'. Now, the first field we want to translate is the title, so 'field' => 'title'. For the value, 'value' => 'French title...'. Not super creative but it gets the job done.

Now for the content translation. Copy this whole TranslationFactory::createOne() and paste below. Change the field to 'content' and the value to 'French content...'. Leave everything else the same - as it's for the same object and locale.

Good enough to get started!

At your terminal, reload the fixtures with:

symfony console foundry:load-fixtures

Choose y to confirm recreating the database.

Let's do a quick sanity check to ensure these translations were indeed loaded:

symfony console doctrine:query:sql 'SELECT * FROM translation'

(translation is the table name for our Translation entity.)

Perfect! Our two translation fixtures are in the database.

Next, we'll write the logic that loads translations from the database and translates our Translatable entities.