Completed
Push — master ( 9315ce...ccf896 )
by Alexander
12:20
created

Locale::getExtendedLanguage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
namespace Yii\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
Comprehensibility introduced by
Since Yii\I18n\Locale is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
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 = $matches['private'];
115
        }
116
117
        if (!empty($matches['keywords'])) {
118
            foreach (explode(';', $matches['keywords']) as $pair) {
119
                [$key, $value] = explode('=', $pair);
0 ignored issues
show
Bug introduced by
The variable $key does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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
     * Returns Locale.
142
     * @param Locale|string $locale
143
     * @return self
144
     * @throws \InvalidArgumentException
145
     */
146
    public static function create($locale): self
147
    {
148
        if ($locale instanceof self) {
149
            return $locale;
150
        }
151
152
        return new self((string)$locale);
153
    }
154
155
    /**
156
     * @return string Four-letter ISO 15924 script code
157
     * @see http://www.unicode.org/iso15924/iso15924-codes.html
158
     */
159
    public function getScript(): ?string
160
    {
161
        return $this->script;
162
    }
163
164
    /**
165
     * @param null|string $script Four-letter ISO 15924 script code
166
     * @see http://www.unicode.org/iso15924/iso15924-codes.html
167
     * @return self
168
     */
169
    public function withScript(?string $script): self
170
    {
171
        $clone = clone $this;
172
        $clone->script = $script;
173
        return $clone;
174
    }
175
176
177
    /**
178
     * @return string variant of language conventions to use
179
     */
180
    public function getVariant(): ?string
181
    {
182
        return $this->variant;
183
    }
184
185
    /**
186
     * @param null|string $variant variant of language conventions to use
187
     * @return self
188
     */
189
    public function withVariant(?string $variant): self
190
    {
191
        $clone = clone $this;
192
        $clone->variant = $variant;
193
        return $clone;
194
    }
195
196
    /**
197
     * @return string|null Two-letter ISO-639-2 language code
198
     * @see http://www.loc.gov/standards/iso639-2/
199
     */
200
    public function getLanguage(): string
201
    {
202
        return $this->language;
203
    }
204
205
    /**
206
     * @param null|string $language Two-letter ISO-639-2 language code
207
     * @see http://www.loc.gov/standards/iso639-2/
208
     * @return self
209
     */
210
    public function withLanguage(?string $language): self
211
    {
212
        $clone = clone $this;
213
        $clone->language = $language;
214
        return $clone;
215
    }
216
217
    /**
218
     * @return null|string ICU calendar
219
     */
220
    public function getCalendar(): ?string
221
    {
222
        return $this->calendar;
223
    }
224
225
    /**
226
     * @param null|string $calendar ICU calendar
227
     * @return self
228
     */
229
    public function withCalendar(?string $calendar): self
230
    {
231
        $clone = clone $this;
232
        $clone->calendar = $calendar;
233
        return $clone;
234
    }
235
236
237
    /**
238
     * @return null|string ICU collation
239
     */
240
    public function getCollation(): ?string
241
    {
242
        return $this->collation;
243
    }
244
245
    /**
246
     * @param null|string $collation ICU collation
247
     * @return self
248
     */
249
    public function withCollation(?string $collation): self
250
    {
251
        $clone = clone $this;
252
        $clone->collation = $collation;
253
        return $clone;
254
    }
255
256
    /**
257
     * @return null|string ICU numbers
258
     */
259
    public function getNumbers(): ?string
260
    {
261
        return $this->numbers;
262
    }
263
264
    /**
265
     * @param null|string $numbers ICU numbers
266
     * @return self
267
     */
268
    public function withNumbers(?string $numbers): self
269
    {
270
        $clone = clone $this;
271
        $clone->numbers = $numbers;
272
        return $clone;
273
    }
274
275
    /**
276
     * @return string Two-letter ISO 3166-1 country code
277
     * @see https://www.iso.org/iso-3166-country-codes.html
278
     */
279
    public function getRegion(): ?string
280
    {
281
        return $this->region;
282
    }
283
284
    /**
285
     * @param null|string $region Two-letter ISO 3166-1 country code
286
     * @see https://www.iso.org/iso-3166-country-codes.html
287
     * @return self
288
     */
289
    public function withRegion(?string $region): self
290
    {
291
        $clone = clone $this;
292
        $clone->region = $region;
293
        return $clone;
294
    }
295
296
    /**
297
     * @return string ICU currency
298
     */
299
    public function getCurrency(): ?string
300
    {
301
        return $this->currency;
302
    }
303
304
    /**
305
     * @param null|string $currency ICU currency
306
     * @return self
307
     */
308
    public function withCurrency(?string $currency): self
309
    {
310
        $clone = clone $this;
311
        $clone->currency = $currency;
312
313
        return $clone;
314
    }
315
316
    /**
317
     * @return null|string extended language subtags
318
     */
319
    public function getExtendedLanguage(): ?string
320
    {
321
        return $this->extendedLanguage;
322
    }
323
324
    /**
325
     * @param null|string $extendedLanguage extended language subtags
326
     * @return self
327
     */
328
    public function withExtendedLanguage(?string $extendedLanguage): self
329
    {
330
        $clone = clone $this;
331
        $clone->extendedLanguage = $extendedLanguage;
332
333
        return $clone;
334
    }
335
336
337
    /**
338
     * @return null|string
339
     */
340
    public function getPrivate(): ?string
341
    {
342
        return $this->private;
343
    }
344
345
    /**
346
     * @param null|string $private
347
     * @return self
348
     */
349
    public function withPrivate(?string $private): self
350
    {
351
        $clone = clone $this;
352
        $clone->private = $private;
353
354
        return $clone;
355
    }
356
357
    /**
358
     * @return string regular expression for parsing BCP 47
359
     * @see https://tools.ietf.org/html/bcp47
360
     */
361
    private static function getBCP47Regex(): string
362
    {
363
        $regular = '(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)';
364
        $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)';
365
        $grandfathered = '(?<grandfathered>' . $irregular . '|' . $regular . ')';
366
        $private = '(?<private>x(?:-[A-Za-z0-9]{1,8})+)';
367
        $singleton = '[0-9A-WY-Za-wy-z]';
368
        $extension = '(?<extension>' . $singleton . '(?:-[A-Za-z0-9]{2,8})+)';
369
        $variant = '(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})';
370
        $region = '(?<region>[A-Za-z]{2}|[0-9]{3})';
371
        $script = '(?<script>[A-Za-z]{4})';
372
        $extendedLanguage = '(?<extendedLanguage>[A-Za-z]{3}(?:-[A-Za-z]{3}){0,2})';
373
        $language = '(?:(?<language>[A-Za-z]{4,8})|(?<language>[A-Za-z]{2,3})(?:-' . $extendedLanguage . ')?)';
374
        $icuKeywords = '(?:@(?<keywords>.*?))?';
375
        $languageTag = '(?:' . $language . '(?:-' . $script . ')?' . '(?:-' . $region . ')?' . '(?:-' . $variant . ')*' . '(?:-' . $extension . ')*' . '(?:-' . $private . ')?' . ')';
376
        return '/^(?J:' . $grandfathered . '|' . $languageTag . '|' . $private . ')' . $icuKeywords . '$/';
377
    }
378
379
    public function __toString(): string
380
    {
381
        return $this->asString();
382
    }
383
384
    /**
385
     * @return string
386
     */
387
    public function asString(): string
388
    {
389
        if ($this->grandfathered !== null) {
390
            return $this->grandfathered;
391
        }
392
393
        $result = [];
394
        if ($this->language !== null) {
395
            $result[] = $this->language;
396
397
            if ($this->extendedLanguage !== null) {
398
                $result[] = $this->extendedLanguage;
399
            }
400
401
            if ($this->script !== null) {
402
                $result[] = $this->script;
403
            }
404
405
            if ($this->region !== null) {
406
                $result[] = $this->region;
407
            }
408
409
            if ($this->variant !== null) {
410
                $result[] = $this->variant;
411
            }
412
413
            if ($this->extension !== null) {
414
                $result[] = $this->extension;
415
            }
416
        }
417
418
        if ($this->private !== null) {
419
            $result[] = $this->private;
420
        }
421
422
        $keywords = [];
423
        if ($this->currency !== null) {
424
            $keywords[] = 'currency=' . $this->currency;
425
        }
426
        if ($this->collation !== null) {
427
            $keywords[] = 'collation=' . $this->collation;
428
        }
429
        if ($this->calendar !== null) {
430
            $keywords[] = 'calendar=' . $this->calendar;
431
        }
432
        if ($this->numbers !== null) {
433
            $keywords[] = 'numbers=' . $this->numbers;
434
        }
435
436
        $string = implode('-', $result);
437
438
        if ($keywords !== []) {
439
            $string .= '@' . implode(';', $keywords);
440
        }
441
442
        return $string;
443
    }
444
445
    /**
446
     * Returns fallback locale
447
     *
448
     * @return self fallback locale
449
     */
450
    public function getFallbackLocale(): self
451
    {
452
        if ($this->variant !== null) {
453
            return $this->withVariant(null);
454
        }
455
456
        if ($this->region !== null) {
457
            return $this->withRegion(null);
458
        }
459
460
        if ($this->script !== null) {
461
            return $this->withScript(null);
462
        }
463
464
        return $this;
465
    }
466
}
467