Задача
Сайт має дві мови, і з часом може зʼявитися третя. Сторінки проєктів і статті блогу пишуться окремо для кожної мови: це не дослівний переклад, а адаптований текст зі своїм заголовком, адресою й описом для пошуку.
При цьому сайт має знати, що «Переклади контенту» і «Content translations» — одна стаття. Інакше перемикач мови не знатиме, куди вести, а Google не отримає hreflang.
Чому не JSON-поля
Популярний варіант — зберігати всі мови в одному рядку: title_uk, title_en або JSON-поле. Це зручно для назв товарів, але для довгого контенту погано:
- Не можна опублікувати українську версію, поки англійська ще чернетка.
- Слаг, SEO й дата публікації в кожної мови свої — полів стає вдвічі більше.
- Додати третю мову означає міграцію всіх таблиць.
Схема даних
Кожна мова — окремий запис. Записи однієї статті повʼязані через таблицю translation_group. Пара «група + мова» унікальна, тож двох англійських версій однієї статті не буде.
#[ORM\Entity(repositoryClass: PostRepository::class)]
#[ORM\UniqueConstraint(columns: ['translation_group_id', 'locale'])]
class Post
{
#[ORM\ManyToOne(inversedBy: 'posts')]
#[ORM\JoinColumn(nullable: false)]
private TranslationGroup $translationGroup;
#[ORM\Column(length: 5)]
private string $locale;
#[ORM\Column(length: 160)]
private string $title;
#[ORM\Column(length: 160)]
private string $slug;
}Як знайти переклад
Перемикач мови й теги hreflang беруть інші записи тієї ж групи. Це один запит без жодних JOIN по мовних таблицях:
public function findTranslation(Post $post, string $locale): ?Post
{
return $this->findOneBy([
'translationGroup' => $post->getTranslationGroup(),
'locale' => $locale,
'status' => PostStatus::Published,
]);
}Що спільне для всіх мов
Частина даних не залежить від мови: обкладинка, теги, порядок на сайті, звʼязок з клієнтом. Їх я зберігаю на групі, а не на записі. Змінив тег в українській версії — він змінився і в англійській.
Самі теги теж мають назву для кожної мови, але одну адресу: /uk/blog?tag=symfony і /en/blog?tag=symfony показують ті самі статті у своїй мові.
Що вийшло
- Кожна мова публікується, коли готова, і має власні слаг та SEO.
- Третя мова — це запис у налаштуваннях, а не міграція.
- У списку адмінки одразу видно, яких перекладів бракує.
Та сама схема працює для проєктів, послуг і досвіду. Якщо робите багатомовний сайт на Symfony чи Laravel і хочете так само, напишіть — розкажу деталі.