Skip to content

Commit fa3be1e

Browse files
committed
[Intl] Add PHP 8.5 IntlListFormatter as installable polyfill
Adds a new polyfill component `symfony/polyfill-intl-listformatter` that provides the functionality of the new `IntlListFormatter` to PHP 7.2 and later. It is a new composer-installable package (similar to how Intl `MessageFormatter` is a separate installable package, and the PHP 8.5 polyfill can require it as a dependency too. It only supports `en` locale. However, PHP applications that require this package can extend the namespaced polyfill (`\Symfony\Polyfill\Intl\ListFormatter\IntlListFormatter`) and add their own list patterns by extending the static variable (`\Symfony\Polyfill\Intl\ListFormatter\IntlListFormatter::$listPatterns`) that holds these patterns. - [ICU listPatterns](https://github.com/unicode-org/cldr-json/blob/main/cldr-json/cldr-misc-full/main/en/listPatterns.json) - [php-src commit](php/php-src@3f7545245) - [PHP.Watch: IntlListFormatter](https://php.watch/versions/8.5/IntlListFormatter) Closes GH-530.
1 parent 634ccdf commit fa3be1e

File tree

8 files changed

+486
-0
lines changed

8 files changed

+486
-0
lines changed

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"symfony/polyfill-intl-grapheme": "self.version",
3939
"symfony/polyfill-intl-icu": "self.version",
4040
"symfony/polyfill-intl-messageformatter": "self.version",
41+
"symfony/polyfill-intl-listformatter": "self.version",
4142
"symfony/polyfill-intl-idn": "self.version",
4243
"symfony/polyfill-intl-normalizer": "self.version",
4344
"symfony/polyfill-mbstring": "self.version",
@@ -62,6 +63,7 @@
6263
"classmap": [
6364
"src/Intl/Icu/Resources/stubs",
6465
"src/Intl/MessageFormatter/Resources/stubs",
66+
"src/Intl/ListFormatter/Resources/stubs",
6567
"src/Intl/Normalizer/Resources/stubs",
6668
"src/Php85/Resources/stubs",
6769
"src/Php84/Resources/stubs",
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
<?php
2+
3+
/*
4+
* Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
5+
* All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions
9+
* are met:
10+
*
11+
* * Redistributions of source code must retain the above copyright
12+
* notice, this list of conditions and the following disclaimer.
13+
* * Redistributions in binary form must reproduce the above copyright
14+
* notice, this list of conditions and the following disclaimer in
15+
* the documentation and/or other materials provided with the
16+
* distribution.
17+
* * Neither the name of Yii Software LLC nor the names of its
18+
* contributors may be used to endorse or promote products derived
19+
* from this software without specific prior written permission.
20+
*
21+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24+
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25+
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27+
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32+
* POSSIBILITY OF SUCH DAMAGE.
33+
*
34+
* Originally forked from
35+
* https://github.com/yiisoft/yii2/blob/2.0.15/framework/i18n/MessageFormatter.php
36+
*/
37+
38+
namespace Symfony\Polyfill\Intl\ListFormatter;
39+
40+
/**
41+
* A polyfill implementation of the IntlListFormatter class provided by the intl extension.
42+
*
43+
* @author Ayesh Karunaratne <[email protected]>
44+
*
45+
* @internal
46+
*/
47+
class IntlListFormatter
48+
{
49+
public const TYPE_AND = 0;
50+
public const TYPE_OR = 1;
51+
public const TYPE_UNITS = 3;
52+
53+
public const WIDTH_WIDE = 0;
54+
public const WIDTH_SHORT = 1;
55+
public const WIDTH_NARROW = 2;
56+
57+
/**
58+
* @var string
59+
*/
60+
private $locale;
61+
/**
62+
* @var int
63+
*/
64+
private $type;
65+
/**
66+
* @var int
67+
*/
68+
private $width;
69+
70+
protected static $listPatterns = [
71+
'en' => [
72+
'listPattern-type-standard' => [
73+
'start' => '{0}, {1}',
74+
'middle' => '{0}, {1}',
75+
'end' => '{0}, and {1}',
76+
2 => '{0} and {1}',
77+
],
78+
'listPattern-type-or' => [
79+
'start' => '{0}, {1}',
80+
'middle' => '{0}, {1}',
81+
'end' => '{0}, or {1}',
82+
2 => '{0} or {1}',
83+
],
84+
'listPattern-type-or-narrow' => [
85+
'start' => '{0}, {1}',
86+
'middle' => '{0}, {1}',
87+
'end' => '{0}, or {1}',
88+
2 => '{0} or {1}',
89+
],
90+
'listPattern-type-or-short' => [
91+
'start' => '{0}, {1}',
92+
'middle' => '{0}, {1}',
93+
'end' => '{0}, or {1}',
94+
2 => '{0} or {1}',
95+
],
96+
'listPattern-type-standard-narrow' => [
97+
'start' => '{0}, {1}',
98+
'middle' => '{0}, {1}',
99+
'end' => '{0}, {1}',
100+
2 => '{0}, {1}',
101+
],
102+
'listPattern-type-standard-short' => [
103+
'start' => '{0}, {1}',
104+
'middle' => '{0}, {1}',
105+
'end' => '{0}, & {1}',
106+
2 => '{0} & {1}',
107+
],
108+
'listPattern-type-unit' => [
109+
'start' => '{0}, {1}',
110+
'middle' => '{0}, {1}',
111+
'end' => '{0}, {1}',
112+
2 => '{0}, {1}',
113+
],
114+
'listPattern-type-unit-narrow' => [
115+
'start' => '{0} {1}',
116+
'middle' => '{0} {1}',
117+
'end' => '{0} {1}',
118+
2 => '{0} {1}',
119+
],
120+
'listPattern-type-unit-short' => [
121+
'start' => '{0}, {1}',
122+
'middle' => '{0}, {1}',
123+
'end' => '{0}, {1}',
124+
2 => '{0}, {1}',
125+
],
126+
],
127+
];
128+
129+
public function __construct(
130+
string $locale,
131+
int $type = self::TYPE_AND,
132+
int $width = self::WIDTH_WIDE
133+
) {
134+
$exceptionClass = PHP_VERSION_ID >= 80000 ? \ValueError::class : \InvalidArgumentException::class;
135+
if ($locale !== 'en' && strpos($locale, 'en') !== 0) {
136+
throw new $exceptionClass('Invalid locale, only "en" and "en-*" locales are supported');
137+
}
138+
139+
if ($type !== self::TYPE_AND && $type !== self::TYPE_OR && $type !== self::TYPE_UNITS) {
140+
throw new $exceptionClass('Argument #2 ($type) must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS.');
141+
}
142+
143+
if ($width !== self::WIDTH_WIDE && $width !== self::WIDTH_SHORT && $width !== self::WIDTH_NARROW) {
144+
throw new $exceptionClass('Argument #3 ($width) must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW.');
145+
}
146+
147+
148+
$this->locale = 'en';
149+
$this->type = $type;
150+
$this->width = $width;
151+
}
152+
153+
public function format(array $strings): string
154+
{
155+
$itemCount = count($strings);
156+
157+
if ($itemCount === 0) {
158+
return '';
159+
}
160+
161+
$strings = array_values($strings);
162+
163+
if ($itemCount === 1) {
164+
return (string) $strings[0];
165+
}
166+
167+
$pattern = $this->getListPattern();
168+
169+
switch ($this->type) {
170+
case self::TYPE_AND:
171+
$lookupKeyType = 'standard';
172+
break;
173+
case self::TYPE_OR:
174+
$lookupKeyType = 'or';
175+
break;
176+
case self::TYPE_UNITS:
177+
$lookupKeyType = 'unit';
178+
break;
179+
}
180+
181+
switch ($this->width) {
182+
case self::WIDTH_WIDE:
183+
$lookupKeyWidth = '';
184+
break;
185+
case self::WIDTH_SHORT:
186+
$lookupKeyWidth = '-short';
187+
break;
188+
case self::WIDTH_NARROW:
189+
$lookupKeyWidth = '-narrow';
190+
break;
191+
}
192+
193+
$pattern = $pattern['listPattern-type-' . $lookupKeyType . $lookupKeyWidth];
194+
195+
if ($itemCount === 2) {
196+
return strtr($pattern[2], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
197+
}
198+
199+
if ($itemCount === 3) {
200+
$start = strtr($pattern['start'], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
201+
return strtr($pattern['end'], ['{0}' => $start, '{1}' => (string) $strings[2]]);
202+
}
203+
204+
$result = strtr($pattern['start'], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
205+
206+
for ($i = 2; $i < $itemCount - 1; $i++) {
207+
$result = strtr($pattern["middle"], [
208+
"{0}" => $result,
209+
"{1}" => $strings[$i],
210+
]);
211+
}
212+
213+
return strtr($pattern["end"], [
214+
"{0}" => $result,
215+
"{1}" => $strings[$itemCount - 1],
216+
]);
217+
}
218+
219+
protected function getListPattern(): array {
220+
return self::$listPatterns[$this->locale];
221+
}
222+
223+
public function getErrorCode()
224+
{
225+
return 0;
226+
}
227+
228+
public function getErrorMessage()
229+
{
230+
return '';
231+
}
232+
}

src/Intl/ListFormatter/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2018-present Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

src/Intl/ListFormatter/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Symfony Polyfill / Intl: ListFormatter
2+
=========================================
3+
4+
This component provides a fallback implementation for the
5+
[`ListFormatter`](https://php.net/ListFormatter) class provided
6+
by the [Intl](https://php.net/intl) extension.
7+
8+
More information can be found in the
9+
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
10+
11+
License
12+
=======
13+
14+
This library is released under the [MIT license](LICENSE).
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
class IntlException extends Exception
13+
{
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
final class IntlListFormatter extends Symfony\Polyfill\Intl\ListFormatter\IntlListFormatter
13+
{
14+
}

src/Intl/ListFormatter/composer.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "symfony/polyfill-intl-listformatter",
3+
"type": "library",
4+
"description": "Symfony polyfill for intl's ListFormatter class and related functions",
5+
"keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "listformatter"],
6+
"homepage": "https://symfony.com",
7+
"license": "MIT",
8+
"authors": [
9+
{
10+
"name": "Nicolas Grekas",
11+
"email": "[email protected]"
12+
},
13+
{
14+
"name": "Symfony Community",
15+
"homepage": "https://symfony.com/contributors"
16+
}
17+
],
18+
"require": {
19+
"php": ">=7.2"
20+
},
21+
"autoload": {
22+
"psr-4": { "Symfony\\Polyfill\\Intl\\ListFormatter\\": "" },
23+
"classmap": [ "Resources/stubs" ]
24+
},
25+
"suggest": {
26+
"ext-intl": "For best performance"
27+
},
28+
"minimum-stability": "dev",
29+
"extra": {
30+
"thanks": {
31+
"name": "symfony/polyfill",
32+
"url": "https://github.com/symfony/polyfill"
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)