1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Printful\GettextCms; |
4
|
|
|
|
5
|
|
|
use Gettext\Languages\Language; |
6
|
|
|
use Gettext\Translation; |
7
|
|
|
use Gettext\Translations; |
8
|
|
|
use Printful\GettextCms\Exceptions\InvalidTranslationException; |
9
|
|
|
use Printful\GettextCms\Interfaces\MessageRepositoryInterface; |
10
|
|
|
use Printful\GettextCms\Structures\MessageItem; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Class handles all saving and retrieving logic of translations using the given repository |
14
|
|
|
*/ |
15
|
|
|
class MessageStorage |
16
|
|
|
{ |
17
|
|
|
/** @var MessageRepositoryInterface */ |
18
|
|
|
private $repository; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Cache for plural form counts |
22
|
|
|
* @var array [locale => plural count, ..] |
23
|
|
|
*/ |
24
|
|
|
private $pluralFormCache = []; |
25
|
|
|
|
26
|
30 |
|
public function __construct(MessageRepositoryInterface $repository) |
27
|
|
|
{ |
28
|
30 |
|
$this->repository = $repository; |
29
|
30 |
|
} |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Save translation object for a single domain and locale |
33
|
|
|
* |
34
|
|
|
* @param Translations $translations |
35
|
|
|
* @throws InvalidTranslationException |
36
|
|
|
*/ |
37
|
7 |
|
public function saveTranslations(Translations $translations) |
38
|
|
|
{ |
39
|
7 |
|
$locale = $translations->getLanguage(); |
40
|
7 |
|
$domain = (string)$translations->getDomain(); |
41
|
|
|
|
42
|
7 |
|
if (!$locale) { |
43
|
1 |
|
throw new InvalidTranslationException('Locale is missing'); |
44
|
|
|
} |
45
|
|
|
|
46
|
6 |
|
if (!$domain) { |
47
|
1 |
|
throw new InvalidTranslationException('Domain is missing'); |
48
|
|
|
} |
49
|
|
|
|
50
|
5 |
|
foreach ($translations as $v) { |
51
|
5 |
|
$this->saveSingleTranslation($locale, $domain, $v); |
52
|
|
|
} |
53
|
5 |
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Save a translation to repository by merging it to a previously saved version. |
57
|
|
|
* |
58
|
|
|
* @param string $locale |
59
|
|
|
* @param string $domain |
60
|
|
|
* @param Translation $translation |
61
|
|
|
* @return bool |
62
|
|
|
*/ |
63
|
23 |
|
public function saveSingleTranslation(string $locale, string $domain, Translation $translation): bool |
64
|
|
|
{ |
65
|
|
|
// Make a clone so we don't modify the passed instance |
66
|
23 |
|
$translation = $translation->getClone(); |
67
|
|
|
|
68
|
23 |
|
$key = $this->getKey($locale, $domain, $translation); |
69
|
|
|
|
70
|
23 |
|
$existingItem = $this->repository->getSingle($key); |
71
|
|
|
|
72
|
23 |
|
if ($existingItem->exists()) { |
73
|
5 |
|
$existingTranslation = $this->itemToTranslation($existingItem); |
74
|
5 |
|
$translation->mergeWith($existingTranslation); |
75
|
5 |
|
$item = $this->translationToItem($locale, $domain, $translation); |
76
|
|
|
|
77
|
|
|
// Override the disabled state of the existing translation |
78
|
5 |
|
$item->isDisabled = $translation->isDisabled(); |
79
|
|
|
} else { |
80
|
23 |
|
$item = $this->translationToItem($locale, $domain, $translation); |
81
|
|
|
} |
82
|
|
|
|
83
|
23 |
|
$item->hasOriginalTranslation = $translation->hasTranslation(); |
84
|
23 |
|
$item->requiresTranslating = $this->requiresTranslating($locale, $translation); |
85
|
|
|
|
86
|
23 |
|
return $this->repository->save($item); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Function for saving only translated values. |
91
|
|
|
* This will not modify disabled state and will not create new entries in repository, only modifies existing |
92
|
|
|
* |
93
|
|
|
* @param string $locale |
94
|
|
|
* @param string $domain |
95
|
|
|
* @param Translation $translation |
96
|
|
|
* @return bool |
97
|
|
|
*/ |
98
|
3 |
|
public function saveTranslatedValue(string $locale, string $domain, Translation $translation): bool |
99
|
|
|
{ |
100
|
3 |
|
if (!$translation->hasTranslation()) { |
101
|
1 |
|
return false; |
102
|
|
|
} |
103
|
|
|
|
104
|
2 |
|
$existingItem = $this->repository->getSingle($this->getKey($locale, $domain, $translation)); |
105
|
|
|
|
106
|
2 |
|
if (!$existingItem->exists()) { |
107
|
1 |
|
return false; |
108
|
|
|
} |
109
|
|
|
|
110
|
1 |
|
$existingTranslation = $this->itemToTranslation($existingItem); |
111
|
|
|
|
112
|
1 |
|
$existingTranslation->setTranslation($translation->getTranslation()); |
113
|
|
|
|
114
|
|
|
// Make sure we do not drop previous plural translations if current one does not contain one |
115
|
1 |
|
$pluralTranslations = $translation->getPluralTranslations(); |
116
|
1 |
|
if ($pluralTranslations) { |
|
|
|
|
117
|
1 |
|
$existingTranslation->setPluralTranslations($pluralTranslations); |
118
|
|
|
} |
119
|
|
|
|
120
|
1 |
|
$item = $this->translationToItem($locale, $domain, $existingTranslation); |
121
|
|
|
|
122
|
1 |
|
$item->hasOriginalTranslation = true; |
123
|
|
|
// It's still possible that plurals are missing and this translation still needs work |
124
|
1 |
|
$item->requiresTranslating = $this->requiresTranslating($locale, $translation); |
125
|
|
|
|
126
|
1 |
|
return $this->repository->save($item); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Check if some translations are missing (original or missing plural forms) |
131
|
|
|
* |
132
|
|
|
* @param string $locale |
133
|
|
|
* @param Translation $translation |
134
|
|
|
* @return bool |
135
|
|
|
*/ |
136
|
23 |
|
private function requiresTranslating(string $locale, Translation $translation): bool |
137
|
|
|
{ |
138
|
23 |
|
if (!$translation->hasTranslation()) { |
139
|
6 |
|
return true; |
140
|
|
|
} |
141
|
|
|
|
142
|
20 |
|
if ($translation->hasPlural()) { |
143
|
6 |
|
$translatedPluralCount = count(array_filter($translation->getPluralTranslations())); |
144
|
|
|
// If there are less plural translations than language requires, this needs translating |
145
|
6 |
|
return $this->getPluralCount($locale) !== $translatedPluralCount; |
146
|
|
|
} |
147
|
|
|
|
148
|
15 |
|
return false; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Get number of plural forms for this locale |
153
|
|
|
* |
154
|
|
|
* @param string $locale |
155
|
|
|
* @return int |
156
|
|
|
* @throws \InvalidArgumentException Thrown in the locale is not correct |
157
|
|
|
*/ |
158
|
6 |
|
private function getPluralCount(string $locale): int |
159
|
|
|
{ |
160
|
6 |
|
if (!array_key_exists($locale, $this->pluralFormCache)) { |
161
|
6 |
|
$info = Language::getById($locale); |
162
|
6 |
|
$this->pluralFormCache[$locale] = count($info->categories); |
163
|
|
|
} |
164
|
|
|
|
165
|
6 |
|
return $this->pluralFormCache[$locale]; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Generate a unique key for storage (basically a primary key) |
170
|
|
|
* |
171
|
|
|
* @param string $locale |
172
|
|
|
* @param string $domain |
173
|
|
|
* @param Translation $translation |
174
|
|
|
* @return string |
175
|
|
|
*/ |
176
|
24 |
|
private function getKey(string $locale, string $domain, Translation $translation): string |
177
|
|
|
{ |
178
|
24 |
|
return md5($locale . '|' . $domain . '|' . $translation->getContext() . '|' . $translation->getOriginal()); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Convert a message item to a translation item |
183
|
|
|
* |
184
|
|
|
* @param MessageItem $item |
185
|
|
|
* @return Translation |
186
|
|
|
*/ |
187
|
19 |
|
private function itemToTranslation(MessageItem $item): Translation |
188
|
|
|
{ |
189
|
19 |
|
$translation = new Translation($item->context, $item->original, $item->originalPlural); |
190
|
|
|
|
191
|
19 |
|
$translation->setTranslation($item->translation); |
192
|
19 |
|
$translation->setPluralTranslations($item->pluralTranslations); |
193
|
19 |
|
$translation->setDisabled($item->isDisabled); |
194
|
|
|
|
195
|
19 |
|
foreach ($item->references as $v) { |
196
|
3 |
|
$translation->addReference($v[0], $v[1]); |
197
|
|
|
} |
198
|
|
|
|
199
|
19 |
|
foreach ($item->comments as $v) { |
200
|
1 |
|
$translation->addComment($v); |
201
|
|
|
} |
202
|
|
|
|
203
|
19 |
|
foreach ($item->extractedComments as $v) { |
204
|
1 |
|
$translation->addExtractedComment($v); |
205
|
|
|
} |
206
|
|
|
|
207
|
19 |
|
return $translation; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Convert a translation item to a message item |
212
|
|
|
* |
213
|
|
|
* @param string $locale |
214
|
|
|
* @param string $domain |
215
|
|
|
* @param Translation $translation |
216
|
|
|
* @return MessageItem |
217
|
|
|
*/ |
218
|
23 |
|
private function translationToItem(string $locale, string $domain, Translation $translation): MessageItem |
219
|
|
|
{ |
220
|
23 |
|
$item = new MessageItem; |
221
|
|
|
|
222
|
23 |
|
$item->key = $this->getKey($locale, $domain, $translation); |
223
|
23 |
|
$item->domain = $domain; |
224
|
23 |
|
$item->locale = $locale; |
225
|
23 |
|
$item->context = (string)$translation->getContext(); |
226
|
23 |
|
$item->original = $translation->getOriginal(); |
227
|
23 |
|
$item->translation = $translation->getTranslation(); |
228
|
23 |
|
$item->originalPlural = $translation->getPlural(); |
229
|
23 |
|
$item->pluralTranslations = $translation->getPluralTranslations(); |
230
|
23 |
|
$item->references = $translation->getReferences(); |
231
|
23 |
|
$item->comments = $translation->getComments(); |
232
|
23 |
|
$item->extractedComments = $translation->getExtractedComments(); |
233
|
23 |
|
$item->isDisabled = $translation->isDisabled(); |
234
|
|
|
|
235
|
23 |
|
foreach ($item->references as $reference) { |
236
|
3 |
|
if ($this->isJsFile($reference[0] ?? '')) { |
237
|
2 |
|
$item->isJs = true; |
238
|
3 |
|
break; |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
23 |
|
return $item; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Check if this is considered a JS file. |
247
|
|
|
* |
248
|
|
|
* @param string $filename |
249
|
|
|
* @return bool |
250
|
|
|
*/ |
251
|
3 |
|
private function isJsFile(string $filename): bool |
252
|
|
|
{ |
253
|
3 |
|
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); |
254
|
|
|
|
255
|
3 |
|
return in_array($extension, ['vue', 'js'], true); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* All translations, including disabled, enabled and untranslated |
260
|
|
|
* |
261
|
|
|
* @param string $locale |
262
|
|
|
* @param $domain |
263
|
|
|
* @return Translations |
264
|
|
|
*/ |
265
|
5 |
|
public function getAll(string $locale, $domain): Translations |
266
|
|
|
{ |
267
|
5 |
|
return $this->convertItems( |
268
|
5 |
|
$locale, |
269
|
5 |
|
(string)$domain, |
270
|
5 |
|
$this->repository->getAll($locale, (string)$domain) |
271
|
|
|
); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* All enabled translations including untranslated |
276
|
|
|
* |
277
|
|
|
* @param string $locale |
278
|
|
|
* @param $domain |
279
|
|
|
* @return Translations |
280
|
|
|
*/ |
281
|
3 |
|
public function getAllEnabled(string $locale, $domain): Translations |
282
|
|
|
{ |
283
|
3 |
|
return $this->convertItems( |
284
|
3 |
|
$locale, |
285
|
3 |
|
(string)$domain, |
286
|
3 |
|
$this->repository->getEnabled($locale, (string)$domain) |
287
|
|
|
); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Enabled and translated translations only |
292
|
|
|
* |
293
|
|
|
* @param string $locale |
294
|
|
|
* @param $domain |
295
|
|
|
* @return Translations |
296
|
|
|
*/ |
297
|
10 |
|
public function getEnabledTranslated(string $locale, $domain): Translations |
298
|
|
|
{ |
299
|
10 |
|
$items = $this->repository->getEnabledTranslated($locale, (string)$domain); |
300
|
|
|
|
301
|
10 |
|
return $this->convertItems($locale, (string)$domain, $items); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Enabled and translated JS translations |
306
|
|
|
* |
307
|
|
|
* @param string $locale |
308
|
|
|
* @param $domain |
309
|
|
|
* @return Translations |
310
|
|
|
*/ |
311
|
2 |
|
public function getEnabledTranslatedJs(string $locale, $domain): Translations |
312
|
|
|
{ |
313
|
2 |
|
$items = $this->repository->getEnabledTranslatedJs($locale, (string)$domain); |
314
|
|
|
|
315
|
2 |
|
return $this->convertItems($locale, (string)$domain, $items); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Enabled and translated translations only |
320
|
|
|
* |
321
|
|
|
* @param string $locale |
322
|
|
|
* @param $domain |
323
|
|
|
* @return Translations |
324
|
|
|
*/ |
325
|
6 |
|
public function getRequiresTranslating(string $locale, $domain): Translations |
326
|
|
|
{ |
327
|
6 |
|
return $this->convertItems( |
328
|
6 |
|
$locale, |
329
|
6 |
|
(string)$domain, |
330
|
6 |
|
$this->repository->getRequiresTranslating($locale, (string)$domain) |
331
|
|
|
); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Converts message items to a translation object |
336
|
|
|
* |
337
|
|
|
* @param string $locale |
338
|
|
|
* @param string|null $domain |
339
|
|
|
* @param MessageItem[] $items |
340
|
|
|
* @return Translations |
341
|
|
|
*/ |
342
|
23 |
|
private function convertItems(string $locale, $domain, array $items): Translations |
343
|
|
|
{ |
344
|
23 |
|
$domain = (string)$domain; |
345
|
|
|
|
346
|
23 |
|
$translations = new Translations; |
347
|
23 |
|
$translations->setDomain($domain); |
348
|
23 |
|
$translations->setLanguage($locale); |
349
|
|
|
|
350
|
23 |
|
foreach ($items as $v) { |
351
|
19 |
|
$translation = $this->itemToTranslation($v); |
352
|
19 |
|
$translations[] = $translation; |
353
|
|
|
} |
354
|
|
|
|
355
|
23 |
|
return $translations; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Mark all messages in the given domain and locale as disabled |
360
|
|
|
* |
361
|
|
|
* @param string $locale |
362
|
|
|
* @param string $domain |
363
|
|
|
*/ |
364
|
2 |
|
public function disableAll(string $locale, string $domain) |
365
|
|
|
{ |
366
|
2 |
|
$this->repository->disableAll($locale, $domain); |
367
|
|
|
} |
368
|
|
|
} |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.