• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ICanBoogie / CLDR / 15875162966

25 Jun 2025 11:31AM UTC coverage: 95.929% (-1.3%) from 97.22%
15875162966

push

github

olvlvl
Add LanguageId and LocaleId

204 of 227 new or added lines in 18 files covered. (89.87%)

1296 of 1351 relevant lines covered (95.93%)

36.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.45
/src/Core/UnicodeLocaleExtensions.php
1
<?php
2

3
namespace ICanBoogie\CLDR\Core;
4

5
use ICanBoogie\CLDR\BCP47\BCP47Data;
6
use ICanBoogie\CLDR\Supplemental\Territory\TerritoryData;
7

8
final class UnicodeLocaleExtensions
9
{
10
    private const MAP_PREFIX_TO_PROPERTY = [
11
        'ca' => 'calendar',
12
        'cf' => 'currency_format',
13
        'co' => 'collation',
14
        'cu' => 'currency',
15
        'dx' => 'dictionary_break_script_exclusions',
16
        'em' => 'emoji',
17
        'fw' => 'first_day_of_week',
18
        'hc' => 'hour_cycle',
19
        'lb' => 'line_break_style',
20
        'lw' => 'line_break_word_handling',
21
        'ms' => 'measurement_system',
22
        'mu' => 'measurement_unit_override',
23
        'nu' => 'numbering_system',
24
        'rg' => 'region_override',
25
        'sd' => 'regional_subdivision',
26
        'ss' => 'sentence_break_suppressions',
27
        'tz' => 'time_zone',
28
        'va' => 'common_variant',
29
    ];
30

31
    private const PREFIX_FOR_DICTIONARY_BREAK_SCRIPT_EXCLUSIONS = 'dx';
32
    private const PREFIX_FOR_REGION_OVERRIDE = 'rg';
33
    private const PREFIX_FOR_REGIONAL_SUBDIVISION = 'sd';
34

35
    private const REGEXP_EXTENSION = '/([a-z]{2})-((?:(?!-[a-z]{2}-).)+)/';
36

37
    /**
38
     * @param string $unicode_ext
39
     *     A Unicode Locale Extension (-u-) format (defined in BCP 47).
40
     *
41
     * @link https://unicode.org/reports/tr35/tr35.html#u_Extension
42
     * @link https://unicode.org/reports/tr35/tr35.html#Key_And_Type_Definitions_
43
     */
44
    public static function parse(string $unicode_ext): self
45
    {
46
        if (str_starts_with($unicode_ext, 'u-')) {
9✔
47
            $unicode_ext = substr($unicode_ext, 2);
1✔
48
        }
49

50
        preg_match_all(self::REGEXP_EXTENSION, $unicode_ext, $matches, PREG_SET_ORDER);
9✔
51

52
        $properties = [];
9✔
53

54
        foreach ($matches as [, $prefix, $value]) {
9✔
55
            $property = self::MAP_PREFIX_TO_PROPERTY[$prefix]
9✔
56
                ?? throw new InvalidUnicodeLocaleExtensions(
9✔
57
                    "Invalid extension prefix '$prefix' in '$unicode_ext'",
9✔
58
                );
9✔
59

60
            $value = match ($prefix) {
9✔
61
                self::PREFIX_FOR_DICTIONARY_BREAK_SCRIPT_EXCLUSIONS => explode('-', $value),
62
                self::PREFIX_FOR_REGION_OVERRIDE => self::parse_region_override($value),
2✔
63
                self::PREFIX_FOR_REGIONAL_SUBDIVISION => self::parse_regional_subdivision($value),
64
                default => $value,
7✔
65
            };
9✔
66

67
            $properties[$property] = $value;
9✔
68
        }
69

70
        return new self(...$properties);
9✔
71
    }
72

73
    private static function parse_region_override(string $value): string
74
    {
75
        $region = strtoupper(rtrim($value, 'z'));
2✔
76

77
        return $region;
2✔
78
    }
79

80
    private static function parse_regional_subdivision(string $value): string
81
    {
NEW
82
        throw new \RuntimeException("prefix not implemented yet: sd");
×
83
    }
84

85
    /**
86
     * @param string[] $dictionary_break_script_exclusions
87
     */
88
    public function __construct(
89
        public readonly ?string $calendar = null,
90
        public readonly ?string $currency_format = null,
91
        public readonly ?string $collation = null,
92
        public readonly ?string $currency = null,
93
        public readonly array $dictionary_break_script_exclusions = [],
94
        public readonly ?string $emoji = null,
95
        public readonly ?string $first_day_of_week = null,
96
        public readonly ?string $hour_cycle = null,
97
        public readonly ?string $line_break_style = null,
98
        public readonly ?string $line_break_word_handling = null,
99
        public readonly ?string $measurement_system = null,
100
        public readonly ?string $measurement_unit_override = null,
101
        public readonly ?string $numbering_system = null,
102
        public readonly ?string $region_override = null,
103
        public readonly ?string $regional_subdivision = null,
104
        public readonly ?string $sentence_break_suppressions = null,
105
        public readonly ?string $time_zone = null,
106
        public readonly ?string $common_variant = null,
107
    ) {
108
        $this->validate();
28✔
109
    }
110

111
    public function validate(): void
112
    {
113
        $err = [];
28✔
114

115
        foreach ($this->to_array() as $prefix => $value) {
28✔
116
            if ($value === null) {
28✔
117
                continue;
28✔
118
            }
119

120
            if ($prefix === self::PREFIX_FOR_DICTIONARY_BREAK_SCRIPT_EXCLUSIONS) {
28✔
121
                // TODO: validate scripts
122
                continue;
28✔
123
            }
124

125
            if ($prefix === self::PREFIX_FOR_REGION_OVERRIDE) {
9✔
126
                if (!in_array($value, TerritoryData::CODES)) {
2✔
NEW
127
                    $err[] = "$prefix: $value";
×
128
                }
129

130
                continue;
2✔
131
            }
132

133
            if (!in_array($value, BCP47Data::U_MAPPING[$prefix])) {
7✔
NEW
134
                $err[] = "$prefix: $value";
×
135
            }
136
        }
137

138
        if ($err) {
28✔
NEW
139
            throw new InvalidUnicodeLocaleExtensions("Invalid unicode extension: " . implode("; ", $err));
×
140
        }
141
    }
142

143
    /**
144
     * @return array<string, ?string>
145
     */
146
    public function to_array(): array
147
    {
148
        return [
28✔
149

150
            'ca' => $this->calendar,
28✔
151
            'cf' => $this->currency_format,
28✔
152
            'co' => $this->collation,
28✔
153
            'cu' => $this->currency,
28✔
154
            'dx' => $this->dictionary_break_script_exclusions,
28✔
155
            'em' => $this->emoji,
28✔
156
            'fw' => $this->first_day_of_week,
28✔
157
            'hc' => $this->hour_cycle,
28✔
158
            'lb' => $this->line_break_style,
28✔
159
            'lw' => $this->line_break_word_handling,
28✔
160
            'ms' => $this->measurement_system,
28✔
161
            'mu' => $this->measurement_unit_override,
28✔
162
            'nu' => $this->numbering_system,
28✔
163
            'rg' => $this->region_override,
28✔
164
            'sd' => $this->regional_subdivision,
28✔
165
            'ss' => $this->sentence_break_suppressions,
28✔
166
            'tz' => $this->time_zone,
28✔
167
            'va' => $this->common_variant,
28✔
168

169
        ];
28✔
170
    }
171
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc