These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace Yiisoft\I18n; |
||
3 | |||
4 | /** |
||
5 | * Locale stores locale information created from BCP 47 formatted string |
||
6 | * https://tools.ietf.org/html/bcp47 |
||
7 | */ |
||
8 | final class Locale |
||
9 | { |
||
10 | /** |
||
11 | * @var string|null Two-letter ISO-639-2 language code |
||
12 | * @see http://www.loc.gov/standards/iso639-2/ |
||
13 | */ |
||
14 | private $language; |
||
15 | |||
16 | /** |
||
17 | * @var string|null extended language subtags |
||
18 | */ |
||
19 | private $extendedLanguage; |
||
20 | |||
21 | /** |
||
22 | * @var string|null |
||
23 | */ |
||
24 | private $extension; |
||
25 | |||
26 | /** |
||
27 | * @var string|null Four-letter ISO 15924 script code |
||
28 | * @see http://www.unicode.org/iso15924/iso15924-codes.html |
||
29 | */ |
||
30 | private $script; |
||
31 | |||
32 | /** |
||
33 | * @var string|null Two-letter ISO 3166-1 country code |
||
34 | * @see https://www.iso.org/iso-3166-country-codes.html |
||
35 | */ |
||
36 | private $region; |
||
37 | |||
38 | /** |
||
39 | * @var string|null variant of language conventions to use |
||
40 | */ |
||
41 | private $variant; |
||
42 | |||
43 | /** |
||
44 | * @var string|null ICU currency |
||
45 | */ |
||
46 | private $currency; |
||
47 | |||
48 | /** |
||
49 | * @var string|null ICU calendar |
||
50 | */ |
||
51 | private $calendar; |
||
52 | |||
53 | /** |
||
54 | * @var string ICU collation |
||
55 | */ |
||
56 | private $collation; |
||
57 | |||
58 | /** |
||
59 | * @var string|null ICU numbers |
||
60 | */ |
||
61 | private $numbers; |
||
62 | |||
63 | /** |
||
64 | * @var string|null |
||
65 | */ |
||
66 | private $grandfathered; |
||
67 | |||
68 | /** |
||
69 | * @var string|null |
||
70 | */ |
||
71 | private $private; |
||
72 | |||
73 | /** |
||
74 | * Locale constructor. |
||
75 | * @param string $localeString BCP 47 formatted locale string |
||
76 | * @see https://tools.ietf.org/html/bcp47 |
||
77 | * @throws \InvalidArgumentException |
||
78 | */ |
||
79 | public function __construct(string $localeString) |
||
80 | { |
||
81 | if (!preg_match(static::getBCP47Regex(), $localeString, $matches)) { |
||
0 ignored issues
–
show
|
|||
82 | throw new \InvalidArgumentException($localeString . ' is not valid BCP 47 formatted locale string'); |
||
83 | } |
||
84 | |||
85 | if (!empty($matches['language'])) { |
||
86 | $this->language = strtolower($matches['language']); |
||
87 | } |
||
88 | |||
89 | if (!empty($matches['region'])) { |
||
90 | $this->region = strtoupper($matches['region']); |
||
91 | } |
||
92 | |||
93 | if (!empty($matches['variant'])) { |
||
94 | $this->variant = $matches['variant']; |
||
95 | } |
||
96 | |||
97 | if (!empty($matches['extendedLanguage'])) { |
||
98 | $this->extendedLanguage = $matches['extendedLanguage']; |
||
99 | } |
||
100 | |||
101 | if (!empty($matches['extension'])) { |
||
102 | $this->extension = $matches['extension']; |
||
103 | } |
||
104 | |||
105 | if (!empty($matches['script'])) { |
||
106 | $this->script = ucfirst(strtolower($matches['script'])); |
||
107 | } |
||
108 | |||
109 | if (!empty($matches['grandfathered'])) { |
||
110 | $this->grandfathered = $matches['grandfathered']; |
||
111 | } |
||
112 | |||
113 | if (!empty($matches['private'])) { |
||
114 | $this->private = preg_replace('~^x-~', '', $matches['private']); |
||
115 | } |
||
116 | |||
117 | if (!empty($matches['keywords'])) { |
||
118 | foreach (explode(';', $matches['keywords']) as $pair) { |
||
119 | [$key, $value] = explode('=', $pair); |
||
120 | |||
121 | if ($key === 'calendar') { |
||
122 | $this->calendar = $value; |
||
123 | } |
||
124 | |||
125 | if ($key === 'collation') { |
||
126 | $this->collation = $value; |
||
127 | } |
||
128 | |||
129 | if ($key === 'currency') { |
||
130 | $this->currency = $value; |
||
131 | } |
||
132 | |||
133 | if ($key === 'numbers') { |
||
134 | $this->numbers = $value; |
||
135 | } |
||
136 | } |
||
137 | } |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * @return string Four-letter ISO 15924 script code |
||
142 | * @see http://www.unicode.org/iso15924/iso15924-codes.html |
||
143 | */ |
||
144 | public function script(): ?string |
||
145 | { |
||
146 | return $this->script; |
||
147 | } |
||
148 | |||
149 | /** |
||
150 | * @param null|string $script Four-letter ISO 15924 script code |
||
151 | * @see http://www.unicode.org/iso15924/iso15924-codes.html |
||
152 | * @return self |
||
153 | */ |
||
154 | public function withScript(?string $script): self |
||
155 | { |
||
156 | $new = clone $this; |
||
157 | $new->script = $script; |
||
158 | return $new; |
||
159 | } |
||
160 | |||
161 | |||
162 | /** |
||
163 | * @return string variant of language conventions to use |
||
164 | */ |
||
165 | public function variant(): ?string |
||
166 | { |
||
167 | return $this->variant; |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * @param null|string $variant variant of language conventions to use |
||
172 | * @return self |
||
173 | */ |
||
174 | public function withVariant(?string $variant): self |
||
175 | { |
||
176 | $new = clone $this; |
||
177 | $new->variant = $variant; |
||
178 | return $new; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * @return string|null Two-letter ISO-639-2 language code |
||
183 | * @see http://www.loc.gov/standards/iso639-2/ |
||
184 | */ |
||
185 | public function language(): string |
||
186 | { |
||
187 | return $this->language; |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * @param null|string $language Two-letter ISO-639-2 language code |
||
192 | * @see http://www.loc.gov/standards/iso639-2/ |
||
193 | * @return self |
||
194 | */ |
||
195 | public function withLanguage(?string $language): self |
||
196 | { |
||
197 | $new = clone $this; |
||
198 | $new->language = $language; |
||
199 | return $new; |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * @return null|string ICU calendar |
||
204 | */ |
||
205 | public function calendar(): ?string |
||
206 | { |
||
207 | return $this->calendar; |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * @param null|string $calendar ICU calendar |
||
212 | * @return self |
||
213 | */ |
||
214 | public function withCalendar(?string $calendar): self |
||
215 | { |
||
216 | $new = clone $this; |
||
217 | $new->calendar = $calendar; |
||
218 | return $new; |
||
219 | } |
||
220 | |||
221 | |||
222 | /** |
||
223 | * @return null|string ICU collation |
||
224 | */ |
||
225 | public function collation(): ?string |
||
226 | { |
||
227 | return $this->collation; |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * @param null|string $collation ICU collation |
||
232 | * @return self |
||
233 | */ |
||
234 | public function withCollation(?string $collation): self |
||
235 | { |
||
236 | $new = clone $this; |
||
237 | $new->collation = $collation; |
||
238 | return $new; |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * @return null|string ICU numbers |
||
243 | */ |
||
244 | public function numbers(): ?string |
||
245 | { |
||
246 | return $this->numbers; |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * @param null|string $numbers ICU numbers |
||
251 | * @return self |
||
252 | */ |
||
253 | public function withNumbers(?string $numbers): self |
||
254 | { |
||
255 | $new = clone $this; |
||
256 | $new->numbers = $numbers; |
||
257 | return $new; |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * @return string Two-letter ISO 3166-1 country code |
||
262 | * @see https://www.iso.org/iso-3166-country-codes.html |
||
263 | */ |
||
264 | public function region(): ?string |
||
265 | { |
||
266 | return $this->region; |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * @param null|string $region Two-letter ISO 3166-1 country code |
||
271 | * @see https://www.iso.org/iso-3166-country-codes.html |
||
272 | * @return self |
||
273 | */ |
||
274 | public function withRegion(?string $region): self |
||
275 | { |
||
276 | $new = clone $this; |
||
277 | $new->region = $region; |
||
278 | return $new; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * @return string ICU currency |
||
283 | */ |
||
284 | public function currency(): ?string |
||
285 | { |
||
286 | return $this->currency; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * @param null|string $currency ICU currency |
||
291 | * @return self |
||
292 | */ |
||
293 | public function withCurrency(?string $currency): self |
||
294 | { |
||
295 | $new = clone $this; |
||
296 | $new->currency = $currency; |
||
297 | |||
298 | return $new; |
||
299 | } |
||
300 | |||
301 | /** |
||
302 | * @return null|string extended language subtags |
||
303 | */ |
||
304 | public function extendedLanguage(): ?string |
||
305 | { |
||
306 | return $this->extendedLanguage; |
||
307 | } |
||
308 | |||
309 | /** |
||
310 | * @param null|string $extendedLanguage extended language subtags |
||
311 | * @return self |
||
312 | */ |
||
313 | public function withExtendedLanguage(?string $extendedLanguage): self |
||
314 | { |
||
315 | $new = clone $this; |
||
316 | $new->extendedLanguage = $extendedLanguage; |
||
317 | |||
318 | return $new; |
||
319 | } |
||
320 | |||
321 | |||
322 | /** |
||
323 | * @return null|string |
||
324 | */ |
||
325 | public function private(): ?string |
||
326 | { |
||
327 | return $this->private; |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * @param null|string $private |
||
332 | * @return self |
||
333 | */ |
||
334 | public function withPrivate(?string $private): self |
||
335 | { |
||
336 | $new = clone $this; |
||
337 | $new->private = $private; |
||
338 | |||
339 | return $new; |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * @return string regular expression for parsing BCP 47 |
||
344 | * @see https://tools.ietf.org/html/bcp47 |
||
345 | */ |
||
346 | private static function getBCP47Regex(): string |
||
347 | { |
||
348 | $regular = '(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)'; |
||
349 | $irregular = '(?:en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)'; |
||
350 | $grandfathered = '(?<grandfathered>' . $irregular . '|' . $regular . ')'; |
||
351 | $private = '(?<private>x(?:-[A-Za-z0-9]{1,8})+)'; |
||
352 | $singleton = '[0-9A-WY-Za-wy-z]'; |
||
353 | $extension = '(?<extension>' . $singleton . '(?:-[A-Za-z0-9]{2,8})+)'; |
||
354 | $variant = '(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})'; |
||
355 | $region = '(?<region>[A-Za-z]{2}|[0-9]{3})'; |
||
356 | $script = '(?<script>[A-Za-z]{4})'; |
||
357 | $extendedLanguage = '(?<extendedLanguage>[A-Za-z]{3}(?:-[A-Za-z]{3}){0,2})'; |
||
358 | $language = '(?<language>[A-Za-z]{4,8})|(?<language>[A-Za-z]{2,3})(?:-' . $extendedLanguage . ')?'; |
||
359 | $icuKeywords = '(?:@(?<keywords>.*?))?'; |
||
360 | $languageTag = '(?:' . $language . '(?:-' . $script . ')?' . '(?:-' . $region . ')?' . '(?:-' . $variant . ')*' . '(?:-' . $extension . ')*' . '(?:-' . $private . ')?' . ')'; |
||
361 | return '/^(?J:' . $grandfathered . '|' . $languageTag . '|' . $private . ')' . $icuKeywords . '$/'; |
||
362 | } |
||
363 | |||
364 | public function __toString(): string |
||
365 | { |
||
366 | return $this->asString(); |
||
367 | } |
||
368 | |||
369 | /** |
||
370 | * @return string |
||
371 | */ |
||
372 | public function asString(): string |
||
373 | { |
||
374 | if ($this->grandfathered !== null) { |
||
375 | return $this->grandfathered; |
||
376 | } |
||
377 | |||
378 | $result = []; |
||
379 | if ($this->language !== null) { |
||
380 | $result[] = $this->language; |
||
381 | |||
382 | if ($this->extendedLanguage !== null) { |
||
383 | $result[] = $this->extendedLanguage; |
||
384 | } |
||
385 | |||
386 | if ($this->script !== null) { |
||
387 | $result[] = $this->script; |
||
388 | } |
||
389 | |||
390 | if ($this->region !== null) { |
||
391 | $result[] = $this->region; |
||
392 | } |
||
393 | |||
394 | if ($this->variant !== null) { |
||
395 | $result[] = $this->variant; |
||
396 | } |
||
397 | |||
398 | if ($this->extension !== null) { |
||
399 | $result[] = $this->extension; |
||
400 | } |
||
401 | } |
||
402 | |||
403 | if ($this->private !== null) { |
||
404 | $result[] = 'x-' . $this->private; |
||
405 | } |
||
406 | |||
407 | $keywords = []; |
||
408 | if ($this->currency !== null) { |
||
409 | $keywords[] = 'currency=' . $this->currency; |
||
410 | } |
||
411 | if ($this->collation !== null) { |
||
412 | $keywords[] = 'collation=' . $this->collation; |
||
413 | } |
||
414 | if ($this->calendar !== null) { |
||
415 | $keywords[] = 'calendar=' . $this->calendar; |
||
416 | } |
||
417 | if ($this->numbers !== null) { |
||
418 | $keywords[] = 'numbers=' . $this->numbers; |
||
419 | } |
||
420 | |||
421 | $string = implode('-', $result); |
||
422 | |||
423 | if ($keywords !== []) { |
||
424 | $string .= '@' . implode(';', $keywords); |
||
425 | } |
||
426 | |||
427 | return $string; |
||
428 | } |
||
429 | |||
430 | /** |
||
431 | * Returns fallback locale |
||
432 | * |
||
433 | * @return self fallback locale |
||
434 | */ |
||
435 | public function fallbackLocale(): self |
||
436 | { |
||
437 | $fallback = $this |
||
438 | ->withCalendar(null) |
||
439 | ->withCollation(null) |
||
440 | ->withCurrency(null) |
||
441 | ->withExtendedLanguage(null) |
||
442 | ->withNumbers(null) |
||
443 | ->withPrivate(null); |
||
444 | |||
445 | if ($fallback->variant() !== null) { |
||
446 | return $fallback->withVariant(null); |
||
447 | } |
||
448 | |||
449 | if ($fallback->region() !== null) { |
||
450 | return $fallback->withRegion(null); |
||
451 | } |
||
452 | |||
453 | if ($fallback->script() !== null) { |
||
454 | return $fallback->withScript(null); |
||
455 | } |
||
456 | |||
457 | return $fallback; |
||
458 | } |
||
459 | } |
||
460 |
Late static binding only has effect in subclasses. A
final
class cannot be extended anymore so late static binding cannot occurr. Consider replacingstatic::
withself::
.To learn more about late static binding, please refer to the PHP core documentation.