Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"illuminate/filesystem": "^7.0|^8.0",
"league/iso3166": "^3.0",
"guzzlehttp/guzzle": "^6.3.1|^7.0.1",
"mpociot/vat-calculator": "^2.4"
"mpociot/vat-calculator": "^2.4",
"ext-intl": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.5.8|^9.3.3"
Expand Down
5 changes: 5 additions & 0 deletions src/CountriesServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ protected function registerBindings()
$this->app->singleton('country.helper', function ($app) {
return new CountryHelper($app->config);
});

$this->app->singleton('number.helper', function ($app) {
return new NumberHelper($app->config);
});
}

/**
Expand All @@ -137,6 +141,7 @@ protected function registerFacades()
$this->app->alias('currency.helper', CurrencyHelper::class);
$this->app->alias('i18n.taxrate', TaxRate::class);
$this->app->alias('country.helper', CountryHelper::class);
$this->app->alias('number.helper', NumberHelper::class);
});
}
}
124 changes: 13 additions & 111 deletions src/CurrencyHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@

class CurrencyHelper extends LocalizedHelper
{
/**
* Cached formatters.
*
* @var array
*/
protected $cachedFormatters = [];

/**
* Return a value in the given currency formatted for the given locale.
*
Expand All @@ -28,8 +21,11 @@ public function toFormat(
string $currencyCode = CurrencyCode::POUND_STERLING,
string $locale = 'en'
): string {
/*
* Sometimes we can display up to 6 decimals of the monetary unit (ex: £0.106544) for energy prices.
*/
return $this
->getDefaultFormatter($locale)
->getDefaultFormatter($locale, 6)
->formatCurrency($value, $currencyCode);
}

Expand Down Expand Up @@ -105,74 +101,15 @@ public function toStandardFormat(
string $locale = 'en',
int $precision = null
): string {
if (is_null($precision)) {
return $this->getDefaultFormatter($locale)->formatCurrency($value, $currencyCode);
}

return $this
->getFixedPrecisionFormatter($locale, $precision)
->formatCurrency($value, $currencyCode);
}

/**
* Get the formatter cache key.
*
* @param array $params
* @return string
*/
protected function getFormatterCacheKey(array $params): string
{
return hash('sha256', serialize($params));
}

/**
* Gets a formatter or creates and returns it if not already.
*
* @param string $key
* @param callable $createHandler
* @return NumberFormatter
*/
protected function getFormatter(string $key, callable $createHandler): NumberFormatter
{
if (array_key_exists($key, $this->cachedFormatters)) {
return $this->cachedFormatters[$key];
}

return $this->cachedFormatters[$key] = $createHandler();
}

/**
* Create the default formatter for the given locale.
*
* @param string $locale
* @return NumberFormatter
*/
protected function getDefaultFormatter(string $locale): NumberFormatter
{
return $this->getFormatter(
$this->getFormatterCacheKey(func_get_args()),
function () use ($locale) {
$formatter = $this->getBaseFormatter($this->getSystemLocale($locale));

/*
* NumberFormatter will round up with 2 decimals only by default.
* Sometimes we can display up to 6 decimals of the monetary unit (ex: £0.106544) for energy prices.
*/
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, 6);

return $formatter;
}
);
}

/**
* Create a basic number formatter for the given locale in the given style.
*
* @param string $locale
* @param int|null $style
* @return NumberFormatter
*/
protected function getBaseFormatter(string $locale, ?int $style = null): NumberFormatter
{
return new NumberFormatter($locale, $style ?? NumberFormatter::CURRENCY);
}

/**
* Create a number formatter for retrieving symbols.
*
Expand All @@ -183,7 +120,7 @@ protected function getBaseFormatter(string $locale, ?int $style = null): NumberF
protected function getSymbolFormatter(string $locale, string $currencyCode): NumberFormatter
{
return $this->getFormatter(
$this->getFormatterCacheKey(func_get_args()),
$this->getFormatterCacheKey(__FUNCTION__, func_get_args()),
function () use ($locale, $currencyCode) {
return $this->getBaseFormatter(
$this->getSystemLocale($locale) . "@currency=$currencyCode"
Expand All @@ -193,47 +130,12 @@ function () use ($locale, $currencyCode) {
}

/**
* Create a number formatter with a given precision.
* Return the formatter style used by the number formatter.
*
* @param string $locale
* @param int|null $precision
* @return NumberFormatter
* @return int
*/
protected function getFixedPrecisionFormatter(string $locale, ?int $precision): NumberFormatter
protected function getFormatterStyle(): int
{
return $this->getFormatter(
$this->getFormatterCacheKey(func_get_args()),
function () use ($locale, $precision) {
$formatter = $this->getBaseFormatter($this->getSystemLocale($locale));

if (is_int($precision)) {
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $precision);
$formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $precision);
}

return $formatter;
}
);
}

/**
* Create a number formatter using a pattern.
*
* @param string $locale
* @param string $pattern
* @return NumberFormatter
*/
protected function getPatternedFormatter(string $locale, string $pattern): NumberFormatter
{
return $this->getFormatter(
$this->getFormatterCacheKey(func_get_args()),
function () use ($locale, $pattern) {
$formatter = $this->getBaseFormatter($this->getSystemLocale($locale));

$formatter->setPattern($pattern);

return $formatter;
}
);
return NumberFormatter::CURRENCY;
}
}
21 changes: 21 additions & 0 deletions src/Facades/NumberHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace PodPoint\I18n\Facades;

use Illuminate\Support\Facades\Facade;

/**
* @method static string toFormat(float|int $value, string $locale, int $precision = null)
*/
class NumberHelper extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor(): string
{
return 'number.helper';
}
}
121 changes: 121 additions & 0 deletions src/LocalizedHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PodPoint\I18n;

use Illuminate\Config\Repository;
use NumberFormatter;

abstract class LocalizedHelper extends Helper
{
Expand All @@ -13,6 +14,20 @@ abstract class LocalizedHelper extends Helper
*/
protected $countryHelper;

/**
* Cached formatters.
*
* @var array
*/
protected $cachedFormatters = [];

/**
* Return the formatter style used by the number formatter.
*
* @return int
*/
abstract protected function getFormatterStyle(): int;

/**
* @param Repository $config
*/
Expand Down Expand Up @@ -74,4 +89,110 @@ protected function getFallbackSystemLocale(): ?string

return $country['systemLocale'] ?? null;
}

/**
* Gets a formatter or creates and returns it if not already.
*
* @param string $key
* @param callable $createHandler
* @return NumberFormatter
*/
protected function getFormatter(string $key, callable $createHandler): NumberFormatter
{
if (array_key_exists($key, $this->cachedFormatters)) {
return $this->cachedFormatters[$key];
}

$this->cachedFormatters[$key] = $createHandler();

return $this->cachedFormatters[$key] = $createHandler();
}

/**
* Get the formatter cache key.
*
* @param string $method
* @param array $params
* @return string
*/
protected function getFormatterCacheKey(string $method, array $params): string
{
return hash('sha256', $method . serialize($params));
}

/**
* Create a basic number formatter for the given locale in the given style.
*
* @param string $locale
* @return NumberFormatter
*/
protected function getBaseFormatter(string $locale): NumberFormatter
{
return new NumberFormatter($locale, $this->getFormatterStyle());
}

/**
* Create a number formatter with a fixed given precision.
*
* @param string $locale
* @param int $precision
* @return NumberFormatter
*/
protected function getFixedPrecisionFormatter(string $locale, int $precision): NumberFormatter
{
return $this->getDefaultFormatter($locale, $precision, $precision);
}

/**
* Create the default formatter for the given locale. The precision can be variable based on the max and min values.
* If no precision is specified, the default one for the locale and NumberFormatter style will be used.
*
* @param string $locale
* @param int|null $maxPrecision
* @param int|null $minPrecision
* @return NumberFormatter
*/
protected function getDefaultFormatter(
string $locale,
int $maxPrecision = null,
int $minPrecision = null
): NumberFormatter {
return $this->getFormatter(
$this->getFormatterCacheKey(__FUNCTION__, func_get_args()),
function () use ($locale, $maxPrecision, $minPrecision) {
$formatter = $this->getBaseFormatter($this->getSystemLocale($locale));

if (! is_null($maxPrecision)) {
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $maxPrecision);
}

if (! is_null($minPrecision)) {
$formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $minPrecision);
}

return $formatter;
}
);
}

/**
* Create a number formatter using a pattern.
*
* @param string $locale
* @param string $pattern
* @return NumberFormatter
*/
protected function getPatternedFormatter(string $locale, string $pattern): NumberFormatter
{
return $this->getFormatter(
$this->getFormatterCacheKey(__FUNCTION__, func_get_args()),
function () use ($locale, $pattern) {
$formatter = $this->getBaseFormatter($this->getSystemLocale($locale));

$formatter->setPattern($pattern);

return $formatter;
}
);
}
}
24 changes: 17 additions & 7 deletions src/NumberHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,29 @@
class NumberHelper extends LocalizedHelper
{
/**
* Return a number formatted for the given locale.
* Return a number formatted for the given locale. Specify precision to define the exact number of decimal digits.
*
* @param float|int $value
* @param string $locale
* @param int|null $precision
* @return string
*/
public function toFormat($value, string $locale = 'en'): string
public function toFormat($value, string $locale = 'en', int $precision = null): string
{
$formatter = new NumberFormatter(
$this->getSystemLocale($locale),
NumberFormatter::DEFAULT_STYLE
);
if (is_null($precision)) {
return $this->getDefaultFormatter($locale)->format($value);
}

return $formatter->format($value);
return $this->getFixedPrecisionFormatter($locale, $precision)->format($value);
}

/**
* Return the formatter style used by the number formatter.
*
* @return int
*/
protected function getFormatterStyle(): int
{
return NumberFormatter::DEFAULT_STYLE;
}
}
Loading