diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ffe96..da8b1f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -All Notable changes to `bakame/laravel-intl-formatter` will be documented in this file +All Notable changes to `bakame/intl-formatter` will be documented in this file -## [0.1.0] - 2022-06-04 +## [0.1.0] - 2022-06-05 **Initial release!** diff --git a/README.md b/README.md index 3975a58..2441c94 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Laravel Intl Formatter +Intl Formatter ======================================= [![Author](http://img.shields.io/badge/author-@nyamsprod-blue.svg?style=flat-square)](https://twitter.com/nyamsprod) @@ -8,8 +8,8 @@ Laravel Intl Formatter [![Total Downloads](https://img.shields.io/packagist/dt/bakame/intl-formatter.svg?style=flat-square)](https://packagist.org/packages/bakame/intl-formatter) [![Sponsor development of this project](https://img.shields.io/badge/sponsor%20this%20package-%E2%9D%A4-ff69b4.svg?style=flat-square)](https://github.com/sponsors/nyamsprod) -The package can be used in any Laravel based application to quickly handle internationalization -by providing helper functions in Blade templates or Laravel codebase. +The package can be used in any PHP application to quickly handle internationalization +by providing classes to format locale, language, timezones, currency and dates. System Requirements ------- diff --git a/phpunit.xml b/phpunit.xml index 1552064..efe6f4c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -27,7 +27,7 @@ - + src diff --git a/src/Configuration.php b/src/Configuration.php index 02fa820..99a4612 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -5,7 +5,10 @@ namespace Bakame\Intl; use Bakame\Intl\Options\DateType; +use Bakame\Intl\Options\NumberAttribute; use Bakame\Intl\Options\NumberStyle; +use Bakame\Intl\Options\SymbolAttribute; +use Bakame\Intl\Options\TextAttribute; use Bakame\Intl\Options\TimeType; final class Configuration @@ -22,24 +25,24 @@ final class Configuration public ?string $numberPattern; /** * @readonly - * @var array + * @var array */ public array $attributes; /** * @readonly - * @var array + * @var array */ public array $textAttributes; /** * @readonly - * @var array + * @var array */ public array $symbolAttributes; /** - * @param array $attributes - * @param array $textAttributes - * @param array $symbolAttributes + * @param array $attributes + * @param array $textAttributes + * @param array $symbolAttributes */ public function __construct( DateType $dateType, @@ -64,16 +67,16 @@ public function __construct( /** * @param array{ * date:array{ - * dateType:int, - * timeType:int, + * dateFormat:string, + * timeFormat:string, * pattern?:?string * }, * number:array{ - * style:int, + * style:string, * pattern?:?string, - * attributes?:array, - * textAttributes?:array, - * symbolAttributes?:array + * attributes?:array, + * textAttributes?:array, + * symbolAttributes?:array * } * } $settings */ @@ -100,14 +103,59 @@ public static function fromApplication(array $settings): self } return new self( - DateType::from($settings['date']['dateType']), - TimeType::from($settings['date']['timeType']), - NumberStyle::from($settings['number']['style']), + DateType::fromName($settings['date']['dateFormat']), + TimeType::fromName($settings['date']['timeFormat']), + NumberStyle::fromName($settings['number']['style']), $settings['date']['pattern'], $settings['number']['pattern'], - $settings['number']['attributes'], - $settings['number']['textAttributes'], - $settings['number']['symbolAttributes'], + self::filterNumberAttributes($settings['number']['attributes']), + self::filterTextAttributes($settings['number']['textAttributes']), + self::filterSymboAttributes($settings['number']['symbolAttributes']), ); } + + /** + * @param array $attributes + * + * @return array + */ + private static function filterNumberAttributes(array $attributes): array + { + $res = []; + foreach ($attributes as $name => $value) { + $res[] = NumberAttribute::from($name, $value); + } + + return $res; + } + + /** + * @param array $attributes + * + * @return array + */ + private static function filterTextAttributes(array $attributes): array + { + $res = []; + foreach ($attributes as $name => $value) { + $res[] = TextAttribute::from($name, $value); + } + + return $res; + } + + /** + * @param array $attributes + * + * @return array + */ + private static function filterSymboAttributes(array $attributes): array + { + $res = []; + foreach ($attributes as $name => $value) { + $res[] = SymbolAttribute::from($name, $value); + } + + return $res; + } } diff --git a/src/ConfigurationTest.php b/src/ConfigurationTest.php new file mode 100644 index 0000000..c4fe332 --- /dev/null +++ b/src/ConfigurationTest.php @@ -0,0 +1,101 @@ +attributes); + self::assertSame(NumberFormatter::GROUPING_USED, $config->attributes[0]->name); + self::assertSame(2, $config->attributes[0]->value); + self::assertCount(0, $config->textAttributes); + self::assertCount(0, $config->symbolAttributes); + self::assertNull($config->datePattern); + self::assertEmpty($config->numberPattern); + self::assertSame(IntlDateFormatter::FULL, $config->dateType->value); + self::assertSame(IntlDateFormatter::SHORT, $config->timeType->value); + self::assertSame(NumberFormatter::CURRENCY, $config->style->value); + } + + /** @test */ + public function it_can_be_instantiated_with_only_required_properties_via_settings(): void + { + $config = Configuration::fromApplication([ + 'date' => [ + 'dateFormat' => 'full', + 'timeFormat' => 'short', + ], + 'number' => [ + 'style' => 'currency', + ], + ]); + + self::assertCount(0, $config->attributes); + self::assertCount(0, $config->textAttributes); + self::assertCount(0, $config->symbolAttributes); + self::assertNull($config->datePattern); + self::assertNull($config->numberPattern); + self::assertSame(IntlDateFormatter::FULL, $config->dateType->value); + self::assertSame(IntlDateFormatter::SHORT, $config->timeType->value); + self::assertSame(NumberFormatter::CURRENCY, $config->style->value); + } + + /** @test */ + public function it_fails_load_configuration_with_invalid_attribute_name(): void + { + $this->expectException(FailedFormatting::class); + + Configuration::fromApplication([ + 'date' => [ + 'dateFormat' => 'full', + 'timeFormat' => 'short', + ], + 'number' => [ + 'style' => 'currency', + 'attributes' => [ + 'foobar' => 1, + ], + ], + ]); + } + + /** @test */ + public function it_fails_load_configuration_with_invalid_attribute_value(): void + { + $this->expectException(FailedFormatting::class); + + Configuration::fromApplication([ + 'date' => [ + 'dateFormat' => 'full', + 'timeFormat' => 'short', + ], + 'number' => [ + 'style' => 'currency', + 'attributes' => [ + 'grouping_used' => '2', + ], + ], + ]); + } +} diff --git a/src/Formatter.php b/src/Formatter.php index cf26ad8..99fefcb 100644 --- a/src/Formatter.php +++ b/src/Formatter.php @@ -283,16 +283,16 @@ private function setNumberFormatterAttributes(NumberFormatter $numberFormatter, private function addDefaultAttributes(NumberFormatter $numberFormatter): void { - foreach ($this->configuration->attributes as $attribute => $value) { - $numberFormatter->setAttribute($attribute, $value); + foreach ($this->configuration->attributes as $attribute) { + $attribute->addTo($numberFormatter); } - foreach ($this->configuration->textAttributes as $attribute => $value) { - $numberFormatter->setTextAttribute($attribute, $value); + foreach ($this->configuration->textAttributes as $textAttribute) { + $textAttribute->addTo($numberFormatter); } - foreach ($this->configuration->symbolAttributes as $attribute => $value) { - $numberFormatter->setSymbol($attribute, $value); + foreach ($this->configuration->symbolAttributes as $symbolAttribute) { + $symbolAttribute->addTo($numberFormatter); } if (null !== $this->configuration->numberPattern) { diff --git a/src/FormatterTest.php b/src/FormatterTest.php index d238049..ca065c4 100644 --- a/src/FormatterTest.php +++ b/src/FormatterTest.php @@ -7,8 +7,6 @@ use DateTime; use DateTimeImmutable; use DateTimeZone; -use IntlDateFormatter; -use NumberFormatter; use PHPUnit\Framework\TestCase; /** @@ -25,11 +23,11 @@ protected function setUp(): void $this->formatter = new Formatter( Configuration::fromApplication([ 'date' => [ - 'dateType' => IntlDateFormatter::FULL, - 'timeType' => IntlDateFormatter::FULL, + 'dateFormat' => 'full', + 'timeFormat' => 'full', ], 'number' => [ - 'style' => NumberFormatter::DECIMAL, + 'style' => 'decimal', ], ]), SystemDateResolver::fromSystem() @@ -41,14 +39,14 @@ public function it_can_be_instantiated_with_a_different_configuration(): void { $configuration = Configuration::fromApplication([ 'date' => [ - 'dateType' => IntlDateFormatter::FULL, - 'timeType' => IntlDateFormatter::FULL, + 'dateFormat' => 'full', + 'timeFormat' => 'full', ], 'number' => [ - 'style' => NumberFormatter::DECIMAL, - 'attributes' => [NumberFormatter::FRACTION_DIGITS => 1], - 'textAttributes' => [NumberFormatter::POSITIVE_PREFIX => '++'], - 'symbolAttributes' => [NumberFormatter::DECIMAL_SEPARATOR_SYMBOL => 'x'], + 'style' => 'decimal', + 'attributes' => ['fraction_digit' => 1], + 'textAttributes' => ['positive_prefix' => '++'], + 'symbolAttributes' => ['decimal_separator' => 'x'], ], ]); diff --git a/src/Options/DateType.php b/src/Options/DateType.php index 4e1aa69..eda38eb 100644 --- a/src/Options/DateType.php +++ b/src/Options/DateType.php @@ -6,15 +6,34 @@ use IntlDateFormatter; -final class DateType extends PseudoEnum -{ - protected const CONSTANTS = [ - 'none' => IntlDateFormatter::NONE, - 'short' => IntlDateFormatter::SHORT, - 'medium' => IntlDateFormatter::MEDIUM, - 'long' => IntlDateFormatter::LONG, - 'full' => IntlDateFormatter::FULL, - ]; +if (7 < PHP_MAJOR_VERSION && extension_loaded('intl')) { + final class DateType extends PseudoEnum + { + protected const CONSTANTS = [ + 'none' => IntlDateFormatter::NONE, + 'short' => IntlDateFormatter::SHORT, + 'medium' => IntlDateFormatter::MEDIUM, + 'long' => IntlDateFormatter::LONG, + 'full' => IntlDateFormatter::FULL, + 'relative_short' => IntlDateFormatter::RELATIVE_SHORT, + 'relative_medium' => IntlDateFormatter::RELATIVE_MEDIUM, + 'relative_long' => IntlDateFormatter::RELATIVE_LONG, + 'relative_full' => IntlDateFormatter::RELATIVE_FULL, + ]; - protected static string $description = 'date format'; + protected static string $description = 'date format'; + } +} else { + final class DateType extends PseudoEnum + { + protected const CONSTANTS = [ + 'none' => IntlDateFormatter::NONE, + 'short' => IntlDateFormatter::SHORT, + 'medium' => IntlDateFormatter::MEDIUM, + 'long' => IntlDateFormatter::LONG, + 'full' => IntlDateFormatter::FULL, + ]; + + protected static string $description = 'date format'; + } } diff --git a/src/Options/SymbolAttribute.php b/src/Options/SymbolAttribute.php new file mode 100644 index 0000000..20ccc38 --- /dev/null +++ b/src/Options/SymbolAttribute.php @@ -0,0 +1,58 @@ + NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, + 'grouping_separator' => NumberFormatter::GROUPING_SEPARATOR_SYMBOL, + 'pattern_separator' => NumberFormatter::PATTERN_SEPARATOR_SYMBOL, + 'percent' => NumberFormatter::PERCENT_SYMBOL, + 'zero_digit' => NumberFormatter::ZERO_DIGIT_SYMBOL, + 'digit' => NumberFormatter::DIGIT_SYMBOL, + 'minus_sign' => NumberFormatter::MINUS_SIGN_SYMBOL, + 'plus_sign' => NumberFormatter::PLUS_SIGN_SYMBOL, + 'currency' => NumberFormatter::CURRENCY_SYMBOL, + 'intl_currency' => NumberFormatter::INTL_CURRENCY_SYMBOL, + 'monetary_separator' => NumberFormatter::MONETARY_SEPARATOR_SYMBOL, + 'exponential' => NumberFormatter::EXPONENTIAL_SYMBOL, + 'permill' => NumberFormatter::PERMILL_SYMBOL, + 'pad_escape' => NumberFormatter::PAD_ESCAPE_SYMBOL, + 'infinity' => NumberFormatter::INFINITY_SYMBOL, + 'nan' => NumberFormatter::NAN_SYMBOL, + 'significant_digit' => NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL, + 'monetary_grouping_separator' => NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, + ]; + + /** @readonly */ + public int $name; + + /** @readonly */ + public string $value; + + private function __construct(int $name, string $value) + { + $this->value = $value; + $this->name = $name; + } + + public static function from(string $name, string $value): self + { + if (!isset(self::ATTRIBUTES[$name])) { + throw FailedFormatting::dueToUnknownNumberFormatterAttributeName($name, self::ATTRIBUTES); + } + + return new self(self::ATTRIBUTES[$name], $value); + } + + public function addTo(NumberFormatter $numberFormatter): void + { + $numberFormatter->setSymbol($this->name, $this->value); + } +} diff --git a/src/Options/TextAttribute.php b/src/Options/TextAttribute.php new file mode 100644 index 0000000..f7c341b --- /dev/null +++ b/src/Options/TextAttribute.php @@ -0,0 +1,48 @@ + NumberFormatter::POSITIVE_PREFIX, + 'positive_suffix' => NumberFormatter::POSITIVE_SUFFIX, + 'negative_prefix' => NumberFormatter::NEGATIVE_PREFIX, + 'negative_suffix' => NumberFormatter::NEGATIVE_SUFFIX, + 'padding_character' => NumberFormatter::PADDING_CHARACTER, + 'currency_code' => NumberFormatter::CURRENCY_CODE, + 'default_ruleset' => NumberFormatter::DEFAULT_RULESET, + 'public_rulesets' => NumberFormatter::PUBLIC_RULESETS, + ]; + + /** @readonly */ + public string $value; + + /** @readonly */ + public int $name; + + private function __construct(int $name, string $value) + { + $this->value = $value; + $this->name = $name; + } + + public static function from(string $name, string $value): self + { + if (!isset(self::ATTRIBUTES[$name])) { + throw FailedFormatting::dueToUnknownNumberFormatterAttributeName($name, self::ATTRIBUTES); + } + + return new self(self::ATTRIBUTES[$name], $value); + } + + public function addTo(NumberFormatter $numberFormatter): void + { + $numberFormatter->setTextAttribute($this->name, $this->value); + } +}