1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\Cms\Support; |
4
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
6
|
|
|
|
7
|
|
|
// From 'psr/http-message' |
8
|
|
|
use Psr\Http\Message\UriInterface; |
9
|
|
|
|
10
|
|
|
// From 'charcoal-object' |
11
|
|
|
use Charcoal\Object\RoutableInterface; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Provides awareness of locales. |
15
|
|
|
* |
16
|
|
|
* ## Requirements |
17
|
|
|
* |
18
|
|
|
* - Translator (e.g., {@see \Charcoal\Translator\TranslatorAwareTrait}) |
19
|
|
|
*/ |
20
|
|
|
trait LocaleAwareTrait |
21
|
|
|
{ |
22
|
|
|
/** |
23
|
|
|
* Available languages as defined by the application configset. |
24
|
|
|
* |
25
|
|
|
* @var array |
26
|
|
|
*/ |
27
|
|
|
protected $locales = []; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Store the processed link structures to translations |
31
|
|
|
* for the current route, if any. |
32
|
|
|
* |
33
|
|
|
* @var array |
34
|
|
|
*/ |
35
|
|
|
protected $alternateTranslations; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Retrieve the available locales. |
39
|
|
|
* |
40
|
|
|
* @return array |
41
|
|
|
*/ |
42
|
|
|
protected function locales() |
43
|
|
|
{ |
44
|
|
|
return $this->locales; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Set the available locales. |
49
|
|
|
* |
50
|
|
|
* @param array $locales The list of language structures. |
51
|
|
|
* @return self |
52
|
|
|
*/ |
53
|
|
|
protected function setLocales(array $locales) |
54
|
|
|
{ |
55
|
|
|
$this->locales = []; |
56
|
|
|
foreach ($locales as $langCode => $localeStruct) { |
57
|
|
|
$this->locales[$langCode] = $this->parseLocale($localeStruct, $langCode); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
return $this; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Parse the given locale. |
65
|
|
|
* |
66
|
|
|
* @see \Charcoal\Admin\Widget\FormSidebarWidget::languages() |
67
|
|
|
* @see \Charcoal\Admin\Widget\FormGroupWidget::languages() |
68
|
|
|
* @param array $localeStruct The language structure. |
69
|
|
|
* @param string $langCode The language code. |
70
|
|
|
* @throws InvalidArgumentException If the locale does not have a language code. |
71
|
|
|
* @return array |
72
|
|
|
*/ |
73
|
|
|
private function parseLocale(array $localeStruct, $langCode) |
74
|
|
|
{ |
75
|
|
|
$trans = 'locale.' . $langCode; |
76
|
|
|
|
77
|
|
|
/** Setup the name of the language in the current locale */ |
78
|
|
|
if (isset($localeStruct['name'])) { |
79
|
|
|
$name = $this->translator()->translate($localeStruct['name']); |
80
|
|
View Code Duplication |
} else { |
|
|
|
|
81
|
|
|
$name = $this->translator()->translate($trans); |
82
|
|
|
if ($trans === $name) { |
83
|
|
|
$name = strtoupper($langCode); |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** Setup the native name of the language */ |
88
|
|
|
if (isset($localeStruct['native'])) { |
89
|
|
|
$native = $this->translator()->translate($localeStruct['native'], [], null, $langCode); |
90
|
|
View Code Duplication |
} else { |
|
|
|
|
91
|
|
|
$native = $this->translator()->translate($trans, [], null, $langCode); |
92
|
|
|
if ($trans === $native) { |
93
|
|
|
$native = strtoupper($langCode); |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
if (!isset($localeStruct['locale'])) { |
98
|
|
|
$localeStruct['locale'] = $langCode; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
$localeStruct['name'] = $name; |
102
|
|
|
$localeStruct['native'] = $native; |
103
|
|
|
$localeStruct['code'] = $langCode; |
104
|
|
|
|
105
|
|
|
return $localeStruct; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Retrieve the translator service. |
110
|
|
|
* |
111
|
|
|
* @return array |
112
|
|
|
*/ |
113
|
|
|
protected function availableLanguages() |
114
|
|
|
{ |
115
|
|
|
return array_keys($this->availableLocales); |
|
|
|
|
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Build the alternate translations associated with the current route. |
120
|
|
|
* |
121
|
|
|
* This method _excludes_ the current route's canonical URI. |
122
|
|
|
* |
123
|
|
|
* @return array |
124
|
|
|
*/ |
125
|
|
|
protected function buildAlternateTranslations() |
126
|
|
|
{ |
127
|
|
|
$translations = []; |
128
|
|
|
|
129
|
|
|
$context = $this->contextObject(); |
130
|
|
|
$origLang = $this->currentLanguage(); |
|
|
|
|
131
|
|
|
|
132
|
|
|
$this->translator()->isIteratingLocales = true; |
|
|
|
|
133
|
|
|
foreach ($this->locales() as $langCode => $localeStruct) { |
134
|
|
|
if ($langCode === $origLang) { |
135
|
|
|
continue; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
$this->translator()->setLocale($langCode); |
139
|
|
|
|
140
|
|
|
$translations[$langCode] = $this->formatAlternateTranslation($context, $localeStruct); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
$this->translator()->setLocale($origLang); |
144
|
|
|
unset($this->translator()->isIteratingLocales); |
145
|
|
|
|
146
|
|
|
return $translations; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Retrieve the alternate translations associated with the current route. |
151
|
|
|
* |
152
|
|
|
* This method _excludes_ the current route's canonical URI. |
153
|
|
|
* |
154
|
|
|
* @return array |
155
|
|
|
*/ |
156
|
|
|
protected function getAlternateTranslations() |
157
|
|
|
{ |
158
|
|
|
if ($this->alternateTranslations === null) { |
159
|
|
|
$this->alternateTranslations = $this->buildAlternateTranslations(); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
return $this->alternateTranslations; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Format an alternate translation for the given translatable model. |
167
|
|
|
* |
168
|
|
|
* Note: The application's locale is already modified and will be reset |
169
|
|
|
* after processing all available languages. |
170
|
|
|
* |
171
|
|
|
* @param mixed $context The translated {@see \Charcoal\Model\ModelInterface model} |
172
|
|
|
* or array-accessible structure. |
173
|
|
|
* @param array $localeStruct The currently iterated language. |
174
|
|
|
* @return array Returns a link structure. |
175
|
|
|
*/ |
176
|
|
|
protected function formatAlternateTranslation($context, array $localeStruct) |
177
|
|
|
{ |
178
|
|
|
return [ |
179
|
|
|
'id' => ($context['id']) ? : $this->templateName(), |
180
|
|
|
'title' => ((string)$context['title']) ? : $this->title(), |
181
|
|
|
'url' => $this->formatAlternateTranslationUrl($context, $localeStruct), |
182
|
|
|
'hreflang' => $localeStruct['code'], |
183
|
|
|
'locale' => $localeStruct['locale'], |
184
|
|
|
'name' => $localeStruct['name'], |
185
|
|
|
'native' => $localeStruct['native'], |
186
|
|
|
]; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Format an alternate translation URL for the given translatable model. |
191
|
|
|
* |
192
|
|
|
* Note: The application's locale is already modified and will be reset |
193
|
|
|
* after processing all available languages. |
194
|
|
|
* |
195
|
|
|
* @param mixed $context The translated {@see \Charcoal\Model\ModelInterface model} |
196
|
|
|
* or array-accessible structure. |
197
|
|
|
* @param array $localeStruct The currently iterated language. |
198
|
|
|
* @return string Returns a link. |
199
|
|
|
*/ |
200
|
|
|
protected function formatAlternateTranslationUrl($context, array $localeStruct) |
201
|
|
|
{ |
202
|
|
|
$isRoutable = ($context instanceof RoutableInterface && $context->isActiveRoute()); |
203
|
|
|
$langCode = $localeStruct['code']; |
204
|
|
|
$path = ($isRoutable ? $context->url($langCode) : ($this->currentUrl() ? : $langCode)); |
|
|
|
|
205
|
|
|
|
206
|
|
|
if ($path instanceof UriInterface) { |
207
|
|
|
$path = $path->getPath(); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
return $this->baseUrl()->withPath($path); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Yield the alternate translations associated with the current route. |
215
|
|
|
* |
216
|
|
|
* @return Generator|null |
217
|
|
|
*/ |
218
|
|
|
public function alternateTranslations() |
219
|
|
|
{ |
220
|
|
|
foreach ($this->getAlternateTranslations() as $langCode => $transStruct) { |
221
|
|
|
yield $langCode => $transStruct; |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Determine if there exists alternate translations associated with the current route. |
227
|
|
|
* |
228
|
|
|
* @return boolean |
229
|
|
|
*/ |
230
|
|
|
public function hasAlternateTranslations() |
231
|
|
|
{ |
232
|
|
|
return !empty($this->getAlternateTranslations()); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Retrieve the translator service. |
237
|
|
|
* |
238
|
|
|
* @see \Charcoal\Translator\TranslatorAwareTrait |
239
|
|
|
* @return \Charcoal\Translator\Translator |
240
|
|
|
*/ |
241
|
|
|
abstract protected function translator(); |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Retrieve the template's identifier. |
245
|
|
|
* |
246
|
|
|
* @return string |
247
|
|
|
*/ |
248
|
|
|
abstract public function templateName(); |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Retrieve the title of the page (from the context). |
252
|
|
|
* |
253
|
|
|
* @return string |
254
|
|
|
*/ |
255
|
|
|
abstract public function title(); |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Retrieve the current URI of the context. |
259
|
|
|
* |
260
|
|
|
* @return \Psr\Http\Message\UriInterface|string |
261
|
|
|
*/ |
262
|
|
|
abstract public function currentUrl(); |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Retrieve the current object relative to the context. |
266
|
|
|
* |
267
|
|
|
* @return \Charcoal\Model\ModelInterface|null |
268
|
|
|
*/ |
269
|
|
|
abstract public function contextObject(); |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Retrieve the base URI of the project. |
273
|
|
|
* |
274
|
|
|
* @throws RuntimeException If the base URI is missing. |
275
|
|
|
* @return UriInterface|null |
276
|
|
|
*/ |
277
|
|
|
abstract public function baseUrl(); |
278
|
|
|
} |
279
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.