Translatable

← Translatable

Ashterix

docs: Doctrine section with Doctrine Behaviors and Doctrine Helper (uk + en)

uk/docs/doctrine/doctrine-behaviors/translatable.md новий файл +150 −0

1 + # Translatable
2 +
3 + Переклади полів сутності в окремій сутності перекладу — по одному рядку на локаль.
4 +
5 + ## Дві сутності
6 +
7 + Для сутності `<X>` поведінка очікує сутність `<X>Translation` у тому самому просторі імен: `App\Entity\Category` і `App\Entity\CategoryTranslation`.
8 +
9 + Сутність перекладу містить поля, що перекладаються, і власний ідентифікатор:
10 +
11 + ```php
12 + use Doctrine\ORM\Mapping as ORM;
13 + use Ufo\DoctrineBehaviors\Contract\Entity\TranslationInterface;
14 + use Ufo\DoctrineBehaviors\Model\Translatable\TranslationTrait;
15 +
16 + #[ORM\Entity]
17 + class CategoryTranslation implements TranslationInterface
18 + {
19 + use TranslationTrait;
20 +
21 + #[ORM\Id, ORM\GeneratedValue, ORM\Column]
22 + private ?int $id = null;
23 +
24 + #[ORM\Column(length: 255, nullable: true)]
25 + private ?string $name = null;
26 +
27 + public function getName(): ?string
28 + {
29 + return $this->name;
30 + }
31 +
32 + public function setName(string $name): void
33 + {
34 + $this->name = $name;
35 + }
36 + }
37 + ```
38 +
39 + Сутність, що перекладається, містить лише поля без перекладу:
40 +
41 + ```php
42 + use Doctrine\ORM\Mapping as ORM;
43 + use Ufo\DoctrineBehaviors\Contract\Entity\TranslatableInterface;
44 + use Ufo\DoctrineBehaviors\Model\Translatable\TranslatableTrait;
45 +
46 + #[ORM\Entity]
47 + class Category implements TranslatableInterface
48 + {
49 + use TranslatableTrait;
50 +
51 + #[ORM\Id, ORM\GeneratedValue, ORM\Column]
52 + private ?int $id = null;
53 + }
54 + ```
55 +
56 + ## Мапінг
57 +
58 + | Сутність | Що додає бандл |
59 + | --- | --- |
60 + | `Category` | `OneToMany` `translations` → `CategoryTranslation`, індекс за `locale`, `cascade: persist, remove`, `orphanRemoval` |
61 + | `CategoryTranslation` | `ManyToOne` `translatable` → `Category` (колонка `translatable_id`, `ON DELETE CASCADE`), рядок `locale` довжиною 5, унікальний індекс (`translatable_id`, `locale`) |
62 +
63 + Унікальний індекс називається `<таблиця>_unique_translation`; якщо назва довша за 63 символи, з 2.0.0 замість неї використовується `t_<хеш>_unique_translation`. `#[ORM\MappedSuperclass]` пропускається; в ієрархії наслідування індекс отримує лише коренева сутність перекладу.
64 +
65 + ## Використання
66 +
67 + ```php
68 + $category = new Category();
69 + $category->translate('uk')->setName('Взуття');
70 + $category->translate('en')->setName('Shoes');
71 +
72 + $entityManager->persist($category);
73 + $category->mergeNewTranslations(); // перед flush()
74 + $entityManager->flush();
75 +
76 + $category->translate('en')->getName(); // Shoes
77 + $category->translate()->getName(); // поточна локаль
78 + ```
79 +
80 + `translate($locale = null, $fallbackToDefault = true)`:
81 +
82 + 1. шукає непорожній переклад для `$locale` (без аргументу — поточна локаль);
83 + 2. якщо його немає і `$fallbackToDefault` — пробує локаль без регіону (`uk_UA` → `uk`), потім локаль за замовчуванням;
84 + 3. якщо нічого не знайдено — створює новий переклад і кладе його в `getNewTranslations()`.
85 +
86 + Нові переклади потрапляють у `translations` (і в базу) лише після `mergeNewTranslations()`. Той самий метод прибирає порожні переклади — `isEmpty()` вважає порожнім переклад, у якого всі властивості, крім `id`, `translatable` і `locale`, порожні.
87 +
88 + Інші методи: `getTranslations()`, `setTranslations(iterable)`, `addTranslation()`, `removeTranslation()`, `getCurrentLocale()` / `setCurrentLocale()`, `getDefaultLocale()` / `setDefaultLocale()`.
89 +
90 + ## Локалі
91 +
92 + Під час `postLoad` і `prePersist` бандл ставить сутності поточну локаль і локаль за замовчуванням з `LocaleProviderInterface` (див. [Налаштування](/docs/doctrine/doctrine-behaviors/config)). Поза HTTP-запитом поточна локаль не задається і дорівнює локалі за замовчуванням; початкове значення локалі за замовчуванням у трейті — `en`.
93 +
94 + :::warning
95 + Колонка `locale` має довжину 5 — локалі на кшталт `sr_Latn` чи `zh_Hant` у неї не вміщаються. Див. [Відомі помилки](/docs/doctrine/doctrine-behaviors/known_issues/open).
96 + :::
97 +
98 + ## Проксі до перекладу
99 +
100 + `proxyCurrentLocaleTranslation()` викликає метод перекладу поточної локалі; зручно в `__call()`, щоб `$category->getName()` (або `category.name` у Twig) повертав перекладене значення:
101 +
102 + ```php
103 + public function __call(string $method, array $arguments): mixed
104 + {
105 + return $this->proxyCurrentLocaleTranslation($method, $arguments);
106 + }
107 + ```
108 +
109 + ## Інші назви класів
110 +
111 + Щоб назвати сутність перекладу інакше або винести в інший простір імен, перевизначте обидва статичні методи — кожен має повертати клас іншої сторони:
112 +
113 + ```php
114 + class Category implements TranslatableInterface
115 + {
116 + use TranslatableTrait;
117 +
118 + public static function getTranslationEntityClass(): string
119 + {
120 + return \App\Entity\Translation\CategoryTranslation::class;
121 + }
122 + }
123 +
124 + class CategoryTranslation implements TranslationInterface
125 + {
126 + use TranslationTrait;
127 +
128 + public static function getTranslatableEntityClass(): string
129 + {
130 + return \App\Entity\Category::class;
131 + }
132 + }
133 + ```
134 +
135 + ## Стратегія завантаження
136 +
137 + ```yaml
138 + # config/services.yaml
139 + parameters:
140 + doctrine_behaviors_translatable_fetch_mode: EXTRA_LAZY # колекція translations
141 + doctrine_behaviors_translation_fetch_mode: LAZY # зв'язок translatable
142 + ```
143 +
144 + | Значення | Поведінка Doctrine |
145 + | --- | --- |
146 + | `LAZY` | Завантажує переклади при першому зверненні (за замовчуванням). |
147 + | `EAGER` | Завантажує разом із сутністю. |
148 + | `EXTRA_LAZY` | `count()`, `contains()` і подібні виклики не завантажують усю колекцію. |
149 +
150 + JOIN бандл не додає — змінюється лише стратегія завантаження.