diff options
Diffstat (limited to 'library/intl/src')
25 files changed, 2330 insertions, 0 deletions
diff --git a/library/intl/src/Country/Country.php b/library/intl/src/Country/Country.php new file mode 100644 index 000000000..5ac65fd0d --- /dev/null +++ b/library/intl/src/Country/Country.php @@ -0,0 +1,168 @@ +<?php + +namespace CommerceGuys\Intl\Country; + +class Country implements CountryInterface +{ + /** + * The two-letter country code. + * + * @var string + */ + protected $countryCode; + + /** + * The country name. + * + * @var string + */ + protected $name; + + /** + * The three-letter country code. + * + * @var string + */ + protected $threeLetterCode; + + /** + * The numeric country code. + * + * @var string + */ + protected $numericCode; + + /** + * The country telephone code. + * + * @var string + */ + protected $telephoneCode; + + /** + * The country locale (i.e. "en_US"). + * + * The country name is locale specific. + * + * @var string + */ + protected $locale; + + /** + * Returns the string representation of the Country. + * + * @return string + */ + public function __toString() + { + return $this->getCountryCode(); + } + + /** + * {@inheritdoc} + */ + public function getCountryCode() + { + return $this->countryCode; + } + + /** + * {@inheritdoc} + */ + public function setCountryCode($countryCode) + { + $this->countryCode = $countryCode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getThreeLetterCode() + { + return $this->threeLetterCode; + } + + /** + * {@inheritdoc} + */ + public function setThreeLetterCode($threeLetterCode) + { + $this->threeLetterCode = $threeLetterCode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getNumericCode() + { + return $this->numericCode; + } + + /** + * {@inheritdoc} + */ + public function setNumericCode($numericCode) + { + $this->numericCode = $numericCode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getTelephoneCode() + { + return $this->telephoneCode; + } + + /** + * {@inheritdoc} + */ + public function setTelephoneCode($telephoneCode) + { + $this->telephoneCode = $telephoneCode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale; + } + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->locale = $locale; + + return $this; + } +} diff --git a/library/intl/src/Country/CountryInterface.php b/library/intl/src/Country/CountryInterface.php new file mode 100644 index 000000000..245a49be9 --- /dev/null +++ b/library/intl/src/Country/CountryInterface.php @@ -0,0 +1,99 @@ +<?php + +namespace CommerceGuys\Intl\Country; + +interface CountryInterface +{ + /** + * Gets the two-letter country code. + * + * @return string + */ + public function getCountryCode(); + + /** + * Sets the two-letter country code. + * + * @param string $countryCode The two-letter country code. + */ + public function setCountryCode($countryCode); + + /** + * Gets the country name. + * + * Note that certain locales have incomplete translations, in which + * case the english version of the country name is used instead. + * + * @return string + */ + public function getName(); + + /** + * Sets the country name. + * + * @param string $name The country name. + */ + public function setName($name); + + /** + * Gets the three-letter country code. + * + * Note that not every country has a three-letter code. + * CLDR lists "Canary Islands" (IC) and "Ceuta and Melilla" (EA) + * as separate countries, even though they are formally a part of Spain + * and have no three-letter or numeric ISO codes. + * + * @return string|null + */ + public function getThreeLetterCode(); + + /** + * Sets the three-letter country code. + * + * @param string $threeLetterCode The three-letter country code. + */ + public function setThreeLetterCode($threeLetterCode); + + /** + * Gets the numeric country code. + * + * The numeric code has three digits, and the first one can be a zero, + * hence the need to pass it around as a string. + * + * Note that not every country has a numeric code. + * CLDR lists "Canary Islands" (IC) and "Ceuta and Melilla" (EA) + * as separate countries, even though they are formally a part of Spain + * and have no three-letter or numeric ISO codes. + * "Ascension Island" (AE) also has no numeric code, even though it has a + * three-letter code. + * + * @return string|null + */ + public function getNumericCode(); + + /** + * Sets the numeric country code. + * + * @param string $numericCode The numeric country code. + */ + public function setNumericCode($numericCode); + + /** + * Gets the country telephone code. + * + * Also known as the calling code. + * + * Note that not every country has a telephone code. + * Right now Tristan da Cunha (TI) is the only such example. + * + * @return string|null + */ + public function getTelephoneCode(); + + /** + * Sets the country telephone code. + * + * @param string $telephoneCode The telephone code. + */ + public function setTelephoneCode($telephoneCode); +} diff --git a/library/intl/src/Country/CountryRepository.php b/library/intl/src/Country/CountryRepository.php new file mode 100644 index 000000000..b1fea803d --- /dev/null +++ b/library/intl/src/Country/CountryRepository.php @@ -0,0 +1,124 @@ +<?php + +namespace CommerceGuys\Intl\Country; + +use CommerceGuys\Intl\LocaleResolverTrait; +use CommerceGuys\Intl\Exception\UnknownCountryException; + +/** + * Manages countries based on JSON definitions. + */ +class CountryRepository implements CountryRepositoryInterface +{ + use LocaleResolverTrait; + + /** + * Base country definitions. + * + * Contains data common to all locales, such as the country numeric, + * three-letter, telephone codes. + * + * @var array + */ + protected $baseDefinitions = array(); + + /** + * Per-locale country definitions. + * + * @var array + */ + protected $definitions = array(); + + /** + * Creates a CountryRepository instance. + * + * @param string $definitionPath The path to the country definitions. + * Defaults to 'resources/country'. + */ + public function __construct($definitionPath = null) + { + $this->definitionPath = $definitionPath ? $definitionPath : __DIR__ . '/../../resources/country/'; + } + + /** + * {@inheritdoc} + */ + public function get($countryCode, $locale = null, $fallbackLocale = null) + { + $locale = $this->resolveLocale($locale, $fallbackLocale); + $definitions = $this->loadDefinitions($locale); + if (!isset($definitions[$countryCode])) { + throw new UnknownCountryException($countryCode); + } + + return $this->createCountryFromDefinition($definitions[$countryCode], $locale); + } + + /** + * {@inheritdoc} + */ + public function getAll($locale = null, $fallbackLocale = null) + { + $locale = $this->resolveLocale($locale, $fallbackLocale); + $definitions = $this->loadDefinitions($locale); + $countries = array(); + foreach ($definitions as $countryCode => $definition) { + $countries[$countryCode] = $this->createCountryFromDefinition($definition, $locale); + } + + return $countries; + } + + /** + * Loads the country definitions for the provided locale. + * + * @param string $locale The desired locale. + * + * @return array + */ + protected function loadDefinitions($locale) + { + if (!isset($this->definitions[$locale])) { + $filename = $this->definitionPath . $locale . '.json'; + $this->definitions[$locale] = json_decode(file_get_contents($filename), true); + + // Make sure the base definitions have been loaded. + if (empty($this->baseDefinitions)) { + $this->baseDefinitions = json_decode(file_get_contents($this->definitionPath . 'base.json'), true); + } + // Merge-in base definitions. + foreach ($this->definitions[$locale] as $countryCode => $definition) { + $this->definitions[$locale][$countryCode] += $this->baseDefinitions[$countryCode]; + } + } + + return $this->definitions[$locale]; + } + + /** + * Creates a country object from the provided definition. + * + * @param array $definition The country definition. + * @param string $locale The locale of the country definition. + * + * @return Country + */ + protected function createCountryFromDefinition(array $definition, $locale) + { + $country = new Country(); + $country->setCountryCode($definition['code']); + $country->setName($definition['name']); + $country->setLocale($locale); + if (isset($definition['three_letter_code'])) { + $country->setThreeLetterCode($definition['three_letter_code']); + } + if (isset($definition['numeric_code'])) { + $country->setNumericCode($definition['numeric_code']); + } + if (isset($definition['telephone_code'])) { + $country->setTelephoneCode($definition['telephone_code']); + } + + return $country; + } +} diff --git a/library/intl/src/Country/CountryRepositoryInterface.php b/library/intl/src/Country/CountryRepositoryInterface.php new file mode 100644 index 000000000..ae1cbd0c9 --- /dev/null +++ b/library/intl/src/Country/CountryRepositoryInterface.php @@ -0,0 +1,31 @@ +<?php + +namespace CommerceGuys\Intl\Country; + +/** + * Country repository interface. + */ +interface CountryRepositoryInterface +{ + /** + * Returns a country instance matching the provided country code. + * + * @param string $countryCode The country code. + * @param string $locale The locale (i.e. fr-FR). + * @param string $fallbackLocale A fallback locale (i.e "en"). + * + * @return CountryInterface + */ + public function get($countryCode, $locale = null, $fallbackLocale = null); + + /** + * Returns all available country instances. + * + * @param string $locale The locale (i.e. fr-FR). + * @param string $fallbackLocale A fallback locale (i.e "en"). + * + * @return array An array of countries implementing the CountryInterface, + * keyed by country code. + */ + public function getAll($locale = null, $fallbackLocale = null); +} diff --git a/library/intl/src/Currency/Currency.php b/library/intl/src/Currency/Currency.php new file mode 100644 index 000000000..6138210b5 --- /dev/null +++ b/library/intl/src/Currency/Currency.php @@ -0,0 +1,168 @@ +<?php + +namespace CommerceGuys\Intl\Currency; + +class Currency implements CurrencyInterface +{ + /** + * The alphanumeric currency code. + * + * @var string + */ + protected $currencyCode; + + /** + * The currency name. + * + * @var string + */ + protected $name; + + /** + * The numeric currency code. + * + * @var string + */ + protected $numericCode; + + /** + * The currency symbol. + * + * @var string + */ + protected $symbol; + + /** + * The number of fraction digits. + * + * @var int + */ + protected $fractionDigits; + + /** + * The currency locale (i.e. "en_US"). + * + * The currency name and symbol are locale specific. + * + * @var string + */ + protected $locale; + + /** + * Returns the string representation of the currency. + * + * @return string + */ + public function __toString() + { + return $this->getCurrencyCode(); + } + + /** + * {@inheritdoc} + */ + public function getCurrencyCode() + { + return $this->currencyCode; + } + + /** + * {@inheritdoc} + */ + public function setCurrencyCode($currencyCode) + { + $this->currencyCode = $currencyCode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getNumericCode() + { + return $this->numericCode; + } + + /** + * {@inheritdoc} + */ + public function setNumericCode($numericCode) + { + $this->numericCode = $numericCode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getSymbol() + { + return $this->symbol; + } + + /** + * {@inheritdoc} + */ + public function setSymbol($symbol) + { + $this->symbol = $symbol; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFractionDigits() + { + return $this->fractionDigits; + } + + /** + * {@inheritdoc} + */ + public function setFractionDigits($fractionDigits) + { + $this->fractionDigits = $fractionDigits; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale; + } + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->locale = $locale; + + return $this; + } +} diff --git a/library/intl/src/Currency/CurrencyInterface.php b/library/intl/src/Currency/CurrencyInterface.php new file mode 100644 index 000000000..ccd03c7e0 --- /dev/null +++ b/library/intl/src/Currency/CurrencyInterface.php @@ -0,0 +1,82 @@ +<?php + +namespace CommerceGuys\Intl\Currency; + +interface CurrencyInterface +{ + /** + * Gets the alphabetic currency code. + * + * @return string + */ + public function getCurrencyCode(); + + /** + * Sets the alphabetic currency code. + * + * @param string $currencyCode The alphabetic currency code. + */ + public function setCurrencyCode($currencyCode); + + /** + * Gets the currency name. + * + * @return string + */ + public function getName(); + + /** + * Sets the currency name. + * + * @param string $name The currency name. + */ + public function setName($name); + + /** + * Gets the numeric currency code. + * + * The numeric code has three digits, and the first one can be a zero, + * hence the need to pass it around as a string. + * + * @return string + */ + public function getNumericCode(); + + /** + * Sets the numeric currency code. + * + * @param string $numericCode The numeric currency code. + */ + public function setNumericCode($numericCode); + + /** + * Gets the currency symbol. + * + * @return string + */ + public function getSymbol(); + + /** + * Sets the currency symbol. + * + * @param string $symbol The currency symbol. + */ + public function setSymbol($symbol); + + /** + * Gets the number of fraction digits. + * + * Used when rounding or formatting an amount for display. + * Actual storage precision can be greater. + * + * @return int + */ + public function getFractionDigits(); + + /** + * Sets the number of fraction digits. + * + * @param int $fractionDigits The number of fraction digits. + */ + public function setFractionDigits($fractionDigits); +} diff --git a/library/intl/src/Currency/CurrencyRepository.php b/library/intl/src/Currency/CurrencyRepository.php new file mode 100644 index 000000000..84d0d4522 --- /dev/null +++ b/library/intl/src/Currency/CurrencyRepository.php @@ -0,0 +1,122 @@ +<?php + +namespace CommerceGuys\Intl\Currency; + +use CommerceGuys\Intl\LocaleResolverTrait; +use CommerceGuys\Intl\Exception\UnknownCurrencyException; + +/** + * Manages currencies based on JSON definitions. + */ +class CurrencyRepository implements CurrencyRepositoryInterface +{ + use LocaleResolverTrait; + + /** + * Base currency definitions. + * + * Contains data common to all locales, such as the currency numeric + * code, number of fraction digits. + * + * @var array + */ + protected $baseDefinitions = array(); + + /** + * Per-locale currency definitions. + * + * @var array + */ + protected $definitions = array(); + + /** + * Creates a CurrencyRepository instance. + * + * @param string $definitionPath The path to the currency definitions. + * Defaults to 'resources/currency'. + */ + public function __construct($definitionPath = null) + { + $this->definitionPath = $definitionPath ? $definitionPath : __DIR__ . '/../../resources/currency/'; + } + + /** + * {@inheritdoc} + */ + public function get($currencyCode, $locale = null, $fallbackLocale = null) + { + $locale = $this->resolveLocale($locale, $fallbackLocale); + $definitions = $this->loadDefinitions($locale); + if (!isset($definitions[$currencyCode])) { + throw new UnknownCurrencyException($currencyCode); + } + + return $this->createCurrencyFromDefinition($definitions[$currencyCode], $locale); + } + + /** + * {@inheritdoc} + */ + public function getAll($locale = null, $fallbackLocale = null) + { + $locale = $this->resolveLocale($locale, $fallbackLocale); + $definitions = $this->loadDefinitions($locale); + $currencies = array(); + foreach ($definitions as $currencyCode => $definition) { + $currencies[$currencyCode] = $this->createCurrencyFromDefinition($definition, $locale); + } + + return $currencies; + } + + /** + * Loads the currency definitions for the provided locale. + * + * @param string $locale The desired locale. + * + * @return array + */ + protected function loadDefinitions($locale) + { + if (!isset($this->definitions[$locale])) { + $filename = $this->definitionPath . $locale . '.json'; + $this->definitions[$locale] = json_decode(file_get_contents($filename), true); + + // Make sure the base definitions have been loaded. + if (empty($this->baseDefinitions)) { + $this->baseDefinitions = json_decode(file_get_contents($this->definitionPath . 'base.json'), true); + } + // Merge-in base definitions. + foreach ($this->definitions[$locale] as $currencyCode => $definition) { + $this->definitions[$locale][$currencyCode] += $this->baseDefinitions[$currencyCode]; + } + } + + return $this->definitions[$locale]; + } + + /** + * Creates a currency object from the provided definition. + * + * @param array $definition The currency definition. + * @param string $locale The locale of the currency definition. + * + * @return Currency + */ + protected function createCurrencyFromDefinition(array $definition, $locale) + { + if (!isset($definition['fraction_digits'])) { + $definition['fraction_digits'] = 2; + } + + $currency = new Currency(); + $currency->setCurrencyCode($definition['code']); + $currency->setName($definition['name']); + $currency->setNumericCode($definition['numeric_code']); + $currency->setFractionDigits($definition['fraction_digits']); + $currency->setSymbol($definition['symbol']); + $currency->setLocale($locale); + + return $currency; + } +} diff --git a/library/intl/src/Currency/CurrencyRepositoryInterface.php b/library/intl/src/Currency/CurrencyRepositoryInterface.php new file mode 100644 index 000000000..d72fcf137 --- /dev/null +++ b/library/intl/src/Currency/CurrencyRepositoryInterface.php @@ -0,0 +1,31 @@ +<?php + +namespace CommerceGuys\Intl\Currency; + +/** + * Currency repository interface. + */ +interface CurrencyRepositoryInterface +{ + /** + * Returns a currency instance matching the provided currency code. + * + * @param string $currencyCode The currency code. + * @param string $locale The locale (i.e. fr-FR). + * @param string $fallbackLocale A fallback locale (i.e "en"). + * + * @return CurrencyInterface + */ + public function get($currencyCode, $locale = null, $fallbackLocale = null); + + /** + * Returns all available currency instances. + * + * @param string $locale The locale (i.e. fr-FR). + * @param string $fallbackLocale A fallback locale (i.e "en"). + * + * @return array An array of currencies implementing the CurrencyInterface, + * keyed by currency code. + */ + public function getAll($locale = null, $fallbackLocale = null); +} diff --git a/library/intl/src/Exception/ExceptionInterface.php b/library/intl/src/Exception/ExceptionInterface.php new file mode 100644 index 000000000..a7d17f9ab --- /dev/null +++ b/library/intl/src/Exception/ExceptionInterface.php @@ -0,0 +1,7 @@ +<?php + +namespace CommerceGuys\Intl\Exception; + +interface ExceptionInterface +{ +} diff --git a/library/intl/src/Exception/InvalidArgumentException.php b/library/intl/src/Exception/InvalidArgumentException.php new file mode 100644 index 000000000..afbe114a4 --- /dev/null +++ b/library/intl/src/Exception/InvalidArgumentException.php @@ -0,0 +1,11 @@ +<?php + +namespace CommerceGuys\Intl\Exception; + +/** + * This exception is thrown when an invalid argument is passed to a method. + * For example, a float amount instead of the expected string amount. + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/library/intl/src/Exception/UnknownCountryException.php b/library/intl/src/Exception/UnknownCountryException.php new file mode 100644 index 000000000..c89845b23 --- /dev/null +++ b/library/intl/src/Exception/UnknownCountryException.php @@ -0,0 +1,11 @@ +<?php + +namespace CommerceGuys\Intl\Exception; + +/** + * This exception is thrown when an unknown country code is passed to the + * CountryRepository. + */ +class UnknownCountryException extends InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/library/intl/src/Exception/UnknownCurrencyException.php b/library/intl/src/Exception/UnknownCurrencyException.php new file mode 100644 index 000000000..f94c93d8a --- /dev/null +++ b/library/intl/src/Exception/UnknownCurrencyException.php @@ -0,0 +1,11 @@ +<?php + +namespace CommerceGuys\Intl\Exception; + +/** + * This exception is thrown when an unknown currency code is passed to the + * CurrencyRepository. + */ +class UnknownCurrencyException extends InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/library/intl/src/Exception/UnknownLanguageException.php b/library/intl/src/Exception/UnknownLanguageException.php new file mode 100644 index 000000000..922b42185 --- /dev/null +++ b/library/intl/src/Exception/UnknownLanguageException.php @@ -0,0 +1,11 @@ +<?php + +namespace CommerceGuys\Intl\Exception; + +/** + * This exception is thrown when an unknown language code is passed to the + * LanguageRepository. + */ +class UnknownLanguageException extends InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/library/intl/src/Exception/UnknownLocaleException.php b/library/intl/src/Exception/UnknownLocaleException.php new file mode 100644 index 000000000..0deca13e8 --- /dev/null +++ b/library/intl/src/Exception/UnknownLocaleException.php @@ -0,0 +1,10 @@ +<?php + +namespace CommerceGuys\Intl\Exception; + +/** + * This exception is thrown when an unknown locale is passed to a repository. + */ +class UnknownLocaleException extends InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/library/intl/src/Formatter/NumberFormatter.php b/library/intl/src/Formatter/NumberFormatter.php new file mode 100644 index 000000000..0b8e2ae59 --- /dev/null +++ b/library/intl/src/Formatter/NumberFormatter.php @@ -0,0 +1,406 @@ +<?php + +namespace CommerceGuys\Intl\Formatter; + +use CommerceGuys\Intl\Currency\CurrencyInterface; +use CommerceGuys\Intl\Exception\InvalidArgumentException; +use CommerceGuys\Intl\NumberFormat\NumberFormatInterface; + +/** + * Formats numbers using locale-specific patterns. + */ +class NumberFormatter implements NumberFormatterInterface +{ + /** + * The number format. + * + * @var NumberFormatInterface + */ + protected $numberFormat; + + /** + * The number pattern used to format positive numbers. + * + * @var string + */ + protected $positivePattern; + + /** + * The number pattern used to format negative numbers. + * + * @var string + */ + protected $negativePattern; + + /** + * Whether grouping is used. + * + * @var bool + */ + protected $groupingUsed; + + /** + * The size of the group of digits closest to the decimal point. + * + * @var int + */ + protected $primaryGroupSize; + + /** + * The size of every group of digits after the primary group. + * + * @var int + */ + protected $secondaryGroupSize; + + /** + * The minimum number of fraction digits to show. + * + * @var int + */ + protected $minimumFractionDigits; + + /** + * The maximum number of fraction digits to show. + * + * @var int + */ + protected $maximumFractionDigits; + + /** + * The currency display style. + * + * @var int + */ + protected $currencyDisplay; + + /** + * Localized digits. + * + * @var array + */ + protected $digits = array( + NumberFormatInterface::NUMBERING_SYSTEM_ARABIC => array( + 0 => '٠', 1 => '١', 2 => '٢', 3 => '٣', 4 => '٤', + 5 => '٥', 6 => '٦', 7 => '٧', 8 => '٨', 9 => '٩', + ), + NumberFormatInterface::NUMBERING_SYSTEM_ARABIC_EXTENDED => array( + 0 => '۰', 1 => '۱', 2 => '۲', 3 => '۳', 4 => '۴', + 5 => '۵', 6 => '۶', 7 => '۷', 8 => '۸', 9 => '۹', + ), + NumberFormatInterface::NUMBERING_SYSTEM_BENGALI => array( + 0 => '০', 1 => '১', 2 => '২', 3 => '৩', 4 => '৪', + 5 => '৫', 6 => '৬', 7 => '৭', 8 => '৮', 9 => '৯', + ), + NumberFormatInterface::NUMBERING_SYSTEM_DEVANAGARI => array( + 0 => '०', 1 => '१', 2 => '२', 3 => '३', 4 => '४', + 5 => '५', 6 => '६', 7 => '७', 8 => '८', 9 => '९', + ), + ); + + /** + * Creaes a NumberFormatter instance. + * + * @param NumberFormatInterface $numberFormat The number format. + * @param int $style The formatting style. + * + * @throws InvalidArgumentException + */ + public function __construct(NumberFormatInterface $numberFormat, $style = self::DECIMAL) + { + $availablePatterns = array( + self::DECIMAL => $numberFormat->getDecimalPattern(), + self::PERCENT => $numberFormat->getPercentPattern(), + self::CURRENCY => $numberFormat->getCurrencyPattern(), + self::CURRENCY_ACCOUNTING => $numberFormat->getAccountingCurrencyPattern(), + ); + if (!array_key_exists($style, $availablePatterns)) { + // Unknown type. + throw new InvalidArgumentException('Unknown format style provided to NumberFormatter::__construct().'); + } + + // Split the selected pattern into positive and negative patterns. + $patterns = explode(';', $availablePatterns[$style]); + if (!isset($patterns[1])) { + // No explicit negative pattern was provided, construct it. + $patterns[1] = '-' . $patterns[0]; + } + + $this->numberFormat = $numberFormat; + $this->positivePattern = $patterns[0]; + $this->negativePattern = $patterns[1]; + $this->groupingUsed = (strpos($this->positivePattern, ',') !== false); + // This pattern has number groups, parse them. + if ($this->groupingUsed) { + preg_match('/#+0/', $this->positivePattern, $primaryGroupMatches); + $this->primaryGroupSize = $this->secondaryGroupSize = strlen($primaryGroupMatches[0]); + $numberGroups = explode(',', $this->positivePattern); + if (count($numberGroups) > 2) { + // This pattern has a distinct secondary group size. + $this->secondaryGroupSize = strlen($numberGroups[1]); + } + } + + // Initialize the fraction digit settings for decimal and percent + // styles only. The currency ones will default to the currency values. + if (in_array($style, array(self::DECIMAL, self::PERCENT))) { + $this->minimumFractionDigits = 0; + $this->maximumFractionDigits = 3; + } + $this->currencyDisplay = self::CURRENCY_DISPLAY_SYMBOL; + } + + /** + * {@inheritdoc} + */ + public function format($value) + { + if (!is_numeric($value)) { + $message = sprintf('The provided value "%s" must be a valid number or numeric string.', $value); + throw new InvalidArgumentException($message); + } + + // Ensure that the value is positive and has the right number of digits. + $negative = (bccomp('0', $value, 12) == 1); + $signMultiplier = $negative ? '-1' : '1'; + $value = bcdiv($value, $signMultiplier, $this->maximumFractionDigits); + // Split the number into major and minor digits. + $valueParts = explode('.', $value); + $majorDigits = $valueParts[0]; + // Account for maximumFractionDigits = 0, where the number won't + // have a decimal point, and $valueParts[1] won't be set. + $minorDigits = isset($valueParts[1]) ? $valueParts[1] : ''; + + if ($this->groupingUsed) { + // Reverse the major digits, since they are grouped from the right. + $majorDigits = array_reverse(str_split($majorDigits)); + // Group the major digits. + $groups = array(); + $groups[] = array_splice($majorDigits, 0, $this->primaryGroupSize); + while (!empty($majorDigits)) { + $groups[] = array_splice($majorDigits, 0, $this->secondaryGroupSize); + } + // Reverse the groups and the digits inside of them. + $groups = array_reverse($groups); + foreach ($groups as &$group) { + $group = implode(array_reverse($group)); + } + // Reconstruct the major digits. + $majorDigits = implode(',', $groups); + } + + if ($this->minimumFractionDigits < $this->maximumFractionDigits) { + // Strip any trailing zeroes. + $minorDigits = rtrim($minorDigits, '0'); + if (strlen($minorDigits) < $this->minimumFractionDigits) { + // Now there are too few digits, re-add trailing zeroes + // until the desired length is reached. + $neededZeroes = $this->minimumFractionDigits - strlen($minorDigits); + $minorDigits .= str_repeat('0', $neededZeroes); + } + } + + // Assemble the final number and insert it into the pattern. + $value = $minorDigits ? $majorDigits . '.' . $minorDigits : $majorDigits; + $pattern = $negative ? $this->negativePattern : $this->positivePattern; + $value = preg_replace('/#(?:[\.,]#+)*0(?:[,\.][0#]+)*/', $value, $pattern); + + // Localize the number. + $value = $this->replaceDigits($value); + $value = $this->replaceSymbols($value); + + return $value; + } + + /** + * {@inheritdoc} + */ + public function formatCurrency($value, CurrencyInterface $currency) + { + // Use the currency defaults if the values weren't set by the caller. + $resetMinimumFractionDigits = $resetMaximumFractionDigits = false; + if (!isset($this->minimumFractionDigits)) { + $this->minimumFractionDigits = $currency->getFractionDigits(); + $resetMinimumFractionDigits = true; + } + if (!isset($this->maximumFractionDigits)) { + $this->maximumFractionDigits = $currency->getFractionDigits(); + $resetMaximumFractionDigits = true; + } + + // Format the decimal part of the value first. + $value = $this->format($value); + + // Reset the fraction digit settings, so that they don't affect + // future formattings with different currencies. + if ($resetMinimumFractionDigits) { + $this->minimumFractionDigits = null; + } + if ($resetMaximumFractionDigits) { + $this->maximumFractionDigits = null; + } + + // Determine whether to show the currency symbol or the currency code. + if ($this->currencyDisplay == self::CURRENCY_DISPLAY_SYMBOL) { + $symbol = $currency->getSymbol(); + } else { + $symbol = $currency->getCurrencyCode(); + } + + return str_replace('¤', $symbol, $value); + } + + /** + * {@inheritdoc} + */ + public function parseCurrency($value, CurrencyInterface $currency) + { + $replacements = array( + // Convert the localized symbols back to their original form. + $this->numberFormat->getDecimalSeparator() => '.', + $this->numberFormat->getPlusSign() => '+', + $this->numberFormat->getMinusSign() => '-', + + // Strip any grouping separators, the currency code or symbol. + $this->numberFormat->getGroupingSeparator() => '', + $currency->getCurrencyCode() => '', + $currency->getSymbol() => '', + + // Strip whitespace (spaces and non-breaking spaces). + ' ' => '', + chr(0xC2) . chr(0xA0) => '', + ); + $numberingSystem = $this->numberFormat->getNumberingSystem(); + if (isset($this->digits[$numberingSystem])) { + // Convert the localized digits back to latin. + $replacements += array_flip($this->digits[$numberingSystem]); + } + + $value = strtr($value, $replacements); + if (substr($value, 0, 1) == '(' && substr($value, -1, 1) == ')') { + // This is an accounting formatted negative number. + $value = '-' . str_replace(array('(', ')'), '', $value); + } + + return is_numeric($value) ? $value : false; + } + + /** + * Replaces digits with their localized equivalents. + * + * @param string $value The value being formatted. + * + * @return string + */ + protected function replaceDigits($value) + { + $numberingSystem = $this->numberFormat->getNumberingSystem(); + if (isset($this->digits[$numberingSystem])) { + $value = strtr($value, $this->digits[$numberingSystem]); + } + + return $value; + } + + /** + * Replaces number symbols with their localized equivalents. + * + * @param string $value The value being formatted. + * + * @return string + * + * @see http://cldr.unicode.org/translation/number-symbols + */ + protected function replaceSymbols($value) + { + $replacements = array( + '.' => $this->numberFormat->getDecimalSeparator(), + ',' => $this->numberFormat->getGroupingSeparator(), + '+' => $this->numberFormat->getPlusSign(), + '-' => $this->numberFormat->getMinusSign(), + '%' => $this->numberFormat->getPercentSign(), + ); + + return strtr($value, $replacements); + } + + /** + * {@inheritdoc} + */ + public function getNumberFormat() + { + return $this->numberFormat; + } + + /** + * {@inheritdoc} + */ + public function getMinimumFractionDigits() + { + return $this->minimumFractionDigits; + } + + /** + * {@inheritdoc} + */ + public function setMinimumFractionDigits($minimumFractionDigits) + { + $this->minimumFractionDigits = $minimumFractionDigits; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMaximumFractionDigits() + { + return $this->maximumFractionDigits; + } + + /** + * {@inheritdoc} + */ + public function setMaximumFractionDigits($maximumFractionDigits) + { + $this->maximumFractionDigits = $maximumFractionDigits; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isGroupingUsed() + { + return $this->groupingUsed; + } + + /** + * {@inheritdoc} + */ + public function setGroupingUsed($groupingUsed) + { + $this->groupingUsed = $groupingUsed; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCurrencyDisplay() + { + return $this->currencyDisplay; + } + + /** + * {@inheritdoc} + */ + public function setCurrencyDisplay($currencyDisplay) + { + $this->currencyDisplay = $currencyDisplay; + + return $this; + } +} diff --git a/library/intl/src/Formatter/NumberFormatterInterface.php b/library/intl/src/Formatter/NumberFormatterInterface.php new file mode 100644 index 000000000..721107555 --- /dev/null +++ b/library/intl/src/Formatter/NumberFormatterInterface.php @@ -0,0 +1,134 @@ +<?php + +namespace CommerceGuys\Intl\Formatter; + +use CommerceGuys\Intl\Currency\CurrencyInterface; +use CommerceGuys\Intl\NumberFormat\NumberFormatInterface; + +interface NumberFormatterInterface +{ + /* Format style constants */ + const DECIMAL = 1; + const PERCENT = 2; + const CURRENCY = 3; + const CURRENCY_ACCOUNTING = 4; + + /* Currency display style constants */ + const CURRENCY_DISPLAY_SYMBOL = 1; + const CURRENCY_DISPLAY_CODE = 2; + + /** + * Formats a number. + * + * Please note that the provided value should already be rounded. + * This formatter doesn't do any rounding of its own, and will simply + * truncate extra digits. + * + * @param string $value The value to format. + * + * @return string + */ + public function format($value); + + /** + * Formats a currency value. + * + * Please note that the provided value should already be rounded. + * This formatter doesn't do any rounding of its own, and will simply + * truncate extra digits. + * + * @param string $value The value to format. + * @param CurrencyInterface $currency The currency. + * + * @return string + */ + public function formatCurrency($value, CurrencyInterface $currency); + + /** + * Parses a formatted currency value. + * + * Commonly used in price input widgets where the end-user might + * input a value using digits and symbols common to their locale. + * + * @param string $value The value to parse. + * @param CurrencyInterface $currency The currency. + * + * @return string|false The parsed numeric value or FALSE on error. + */ + public function parseCurrency($value, CurrencyInterface $currency); + + /** + * Gets the number format. + * + * @return NumberFormatInterface + */ + public function getNumberFormat(); + + /** + * Gets the minimum number of fraction digits. + * + * Defaults to 0 for decimal and percentage styles. + * Defaults to null for currency styles, since the currency number of + * fraction digits is used as the default in that case. + * + * @return int + */ + public function getMinimumFractionDigits(); + + /** + * Sets the minimum number of fraction digits. + * + * @param int $minimumFractionDigits + */ + public function setMinimumFractionDigits($minimumFractionDigits); + + /** + * Gets the maximum number of fraction digits. + * + * Defaults to 3 for decimal and percentage styles. + * Defaults to null for currency styles, since the currency number of + * fraction digits is used as the default in that case. + * + * @return int + */ + public function getMaximumFractionDigits(); + + /** + * Sets the maximum number of fraction digits. + * + * @param int $maximumFractionDigits + */ + public function setMaximumFractionDigits($maximumFractionDigits); + + /** + * Returns whether the major digits will be grouped. + * + * @return bool + */ + public function isGroupingUsed(); + + /** + * Sets whether or not major digits should be grouped + * + * @param bool $groupingUsed + */ + public function setGroupingUsed($groupingUsed); + + /** + * Gets the currency display style. + * + * Controls whether a currency amount will be shown with the + * currency symbol (CURRENCY_DISPLAY_SYMBOL) or the + * currency code (CURRENCY_DISPLAY_CODE). + * + * @return int + */ + public function getCurrencyDisplay(); + + /** + * Sets the currency display style. + * + * @param int $currencyDisplay One of the CURRENCY_DISPLAY_ constants. + */ + public function setCurrencyDisplay($currencyDisplay); +} diff --git a/library/intl/src/Language/Language.php b/library/intl/src/Language/Language.php new file mode 100644 index 000000000..259b57249 --- /dev/null +++ b/library/intl/src/Language/Language.php @@ -0,0 +1,91 @@ +<?php + +namespace CommerceGuys\Intl\Language; + +class Language implements LanguageInterface +{ + /** + * The two-letter language code. + * + * @var string + */ + protected $languageCode; + + /** + * The language name. + * + * @var string + */ + protected $name; + + /** + * The language locale (i.e. "en-US"). + * + * @var string + */ + protected $locale; + + /** + * Returns the string representation of the Language. + * + * @return string + */ + public function __toString() + { + return $this->getLanguageCode(); + } + + /** + * {@inheritdoc} + */ + public function getLanguageCode() + { + return $this->languageCode; + } + + /** + * {@inheritdoc} + */ + public function setLanguageCode($languageCode) + { + $this->languageCode = $languageCode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale; + } + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->locale = $locale; + + return $this; + } +} diff --git a/library/intl/src/Language/LanguageInterface.php b/library/intl/src/Language/LanguageInterface.php new file mode 100644 index 000000000..612389142 --- /dev/null +++ b/library/intl/src/Language/LanguageInterface.php @@ -0,0 +1,37 @@ +<?php + +namespace CommerceGuys\Intl\Language; + +interface LanguageInterface +{ + /** + * Gets the two-letter language code. + * + * @return string + */ + public function getLanguageCode(); + + /** + * Sets the two-letter language code. + * + * @param string $languageCode The two-letter language code. + */ + public function setLanguageCode($languageCode); + + /** + * Gets the language name. + * + * Note that certain locales have incomplete translations, in which + * case the english version of the language name is used instead. + * + * @return string + */ + public function getName(); + + /** + * Sets the language name. + * + * @param string $name The language name. + */ + public function setName($name); +} diff --git a/library/intl/src/Language/LanguageRepository.php b/library/intl/src/Language/LanguageRepository.php new file mode 100644 index 000000000..50335cdc0 --- /dev/null +++ b/library/intl/src/Language/LanguageRepository.php @@ -0,0 +1,96 @@ +<?php + +namespace CommerceGuys\Intl\Language; + +use CommerceGuys\Intl\LocaleResolverTrait; +use CommerceGuys\Intl\Exception\UnknownLanguageException; + +/** + * Manages languages based on JSON definitions. + */ +class LanguageRepository implements LanguageRepositoryInterface +{ + use LocaleResolverTrait; + + /** + * Per-locale language definitions. + * + * @var array + */ + protected $definitions = array(); + + /** + * Creates a LanguageRepository instance. + * + * @param string $definitionPath The path to the currency definitions. + * Defaults to 'resources/language'. + */ + public function __construct($definitionPath = null) + { + $this->definitionPath = $definitionPath ? $definitionPath : __DIR__ . '/../../resources/language/'; + } + + /** + * {@inheritdoc} + */ + public function get($languageCode, $locale = null, $fallbackLocale = null) + { + $locale = $this->resolveLocale($locale, $fallbackLocale); + $definitions = $this->loadDefinitions($locale); + if (!isset($definitions[$languageCode])) { + throw new UnknownLanguageException($languageCode); + } + + return $this->createLanguageFromDefinition($definitions[$languageCode], $locale); + } + + /** + * {@inheritdoc} + */ + public function getAll($locale = null, $fallbackLocale = null) + { + $locale = $this->resolveLocale($locale, $fallbackLocale); + $definitions = $this->loadDefinitions($locale); + $languages = array(); + foreach ($definitions as $languageCode => $definition) { + $languages[$languageCode] = $this->createLanguageFromDefinition($definition, $locale); + } + + return $languages; + } + + /** + * Loads the language definitions for the provided locale. + * + * @param string $locale The desired locale. + * + * @return array + */ + protected function loadDefinitions($locale) + { + if (!isset($this->definitions[$locale])) { + $filename = $this->definitionPath . $locale . '.json'; + $this->definitions[$locale] = json_decode(file_get_contents($filename), true); + } + + return $this->definitions[$locale]; + } + + /** + * Creates a language object from the provided definition. + * + * @param array $definition The language definition. + * @param string $locale The locale of the language definition. + * + * @return Language + */ + protected function createLanguageFromDefinition(array $definition, $locale) + { + $language = new Language(); + $language->setLanguageCode($definition['code']); + $language->setName($definition['name']); + $language->setLocale($locale); + + return $language; + } +} diff --git a/library/intl/src/Language/LanguageRepositoryInterface.php b/library/intl/src/Language/LanguageRepositoryInterface.php new file mode 100644 index 000000000..ebdc0200a --- /dev/null +++ b/library/intl/src/Language/LanguageRepositoryInterface.php @@ -0,0 +1,31 @@ +<?php + +namespace CommerceGuys\Intl\Language; + +/** + * Language repository interface. + */ +interface LanguageRepositoryInterface +{ + /** + * Returns a language instance matching the provided language code. + * + * @param string $languageCode The language code. + * @param string $locale The locale (i.e. fr-FR). + * @param string $fallbackLocale A fallback locale (i.e "en"). + * + * @return LanguageInterface + */ + public function get($languageCode, $locale = null, $fallbackLocale = null); + + /** + * Returns all available language instances. + * + * @param string $locale The locale (i.e. fr-FR). + * @param string $fallbackLocale A fallback locale (i.e "en"). + * + * @return array An array of language implementing the LanguageInterface, + * keyed by language code. + */ + public function getAll($locale = null, $fallbackLocale = null); +} diff --git a/library/intl/src/LocaleResolverTrait.php b/library/intl/src/LocaleResolverTrait.php new file mode 100644 index 000000000..21c463c7e --- /dev/null +++ b/library/intl/src/LocaleResolverTrait.php @@ -0,0 +1,89 @@ +<?php + +namespace CommerceGuys\Intl; + +use CommerceGuys\Intl\Exception\UnknownLocaleException; + +trait LocaleResolverTrait +{ + /** + * The path where per-locale definitions are stored. + */ + protected $definitionPath; + + /** + * Determines which locale should be used for loading definitions. + * + * If the "bs-Cyrl-BA" locale is requested, with an "en" fallback, + * the system will try to find the definitions for: + * 1) bs-Cyrl-BA + * 2) bs-Cyrl + * 3) bs + * 4) en + * The first locale for which a definition file is found, wins. + * Otherwise, an exception is thrown. + * + * @param string $locale The desired locale (i.e. fr-FR). + * @param string $fallbackLocale A fallback locale (i.e "en"). + * + * @return string + * + * @throws UnknownLocaleException + */ + protected function resolveLocale($locale = null, $fallbackLocale = null) + { + if (is_null($locale)) { + // Use the default locale if none was provided. + // @todo Provide a way to override this. + $locale = 'en'; + } + // Normalize the locale. Allows en_US to work the same as en-US, etc. + $locale = str_replace('_', '-', $locale); + // List all possible variants (i.e. en-US gives "en-US" and "en"). + $localeVariants = $this->getLocaleVariants($locale); + // A fallback locale was provided, add it to the end of the chain. + if (isset($fallbackLocale)) { + $localeVariants[] = $fallbackLocale; + } + + // Try to resolve a locale by finding a matching definition file. + $resolvedLocale = null; + foreach ($localeVariants as $localeVariant) { + $path = $this->definitionPath . $localeVariant . '.json'; + if (file_exists($path)) { + $resolvedLocale = $localeVariant; + break; + } + } + // No locale could be resolved, stop here. + if (!$resolvedLocale) { + throw new UnknownLocaleException($locale); + } + + return $resolvedLocale; + } + + /** + * Returns all variants of a locale. + * + * For example, "bs-Cyrl-BA" has the following variants: + * 1) bs-Cyrl-BA + * 2) bs-Cyrl + * 3) bs + * + * @param string $locale The locale (i.e. fr-FR). + * + * @return array An array of all variants of a locale. + */ + protected function getLocaleVariants($locale) + { + $localeVariants = array(); + $localeParts = explode('-', $locale); + while (!empty($localeParts)) { + $localeVariants[] = implode('-', $localeParts); + array_pop($localeParts); + } + + return $localeVariants; + } +} diff --git a/library/intl/src/NumberFormat/NumberFormat.php b/library/intl/src/NumberFormat/NumberFormat.php new file mode 100644 index 000000000..0c512b7ab --- /dev/null +++ b/library/intl/src/NumberFormat/NumberFormat.php @@ -0,0 +1,269 @@ +<?php + +namespace CommerceGuys\Intl\NumberFormat; + +class NumberFormat implements NumberFormatInterface +{ + /** + * The locale (i.e. "en_US"). + * + * @var string + */ + protected $locale; + + /** + * The numbering system. + * + * @var string + */ + protected $numberingSystem = array(); + + /** + * The decimal separator. + * + * @var string + */ + protected $decimalSeparator = array(); + + /** + * The grouping separator. + * + * @var string + */ + protected $groupingSeparator = array(); + + /** + * The plus sign. + * + * @var string + */ + protected $plusSign = array(); + + /** + * The number symbols. + * + * @var string + */ + protected $minusSign = array(); + + /** + * The percent sign. + * + * @var string + */ + protected $percentSign = array(); + + /** + * The number pattern used to format decimal numbers. + * + * @var string + */ + protected $decimalPattern; + + /** + * The number pattern used to format percentages. + * + * @var string + */ + protected $percentPattern; + + /** + * The number pattern used to format currency amounts. + * + * @var string + */ + protected $currencyPattern; + + /** + * The number pattern used to format accounting currency amounts. + * + * @var string + */ + protected $accountingCurrencyPattern; + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale; + } + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->locale = $locale; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getNumberingSystem() + { + return $this->numberingSystem; + } + + /** + * {@inheritdoc} + */ + public function setNumberingSystem($numberingSystem) + { + $this->numberingSystem = $numberingSystem; + } + + /** + * {@inheritdoc} + */ + public function getDecimalSeparator() + { + return $this->decimalSeparator; + } + + /** + * {@inheritdoc} + */ + public function setDecimalSeparator($decimalSeparator) + { + $this->decimalSeparator = $decimalSeparator; + } + + /** + * {@inheritdoc} + */ + public function getGroupingSeparator() + { + return $this->groupingSeparator; + } + + /** + * {@inheritdoc} + */ + public function setGroupingSeparator($groupingSeparator) + { + $this->groupingSeparator = $groupingSeparator; + } + + /** + * {@inheritdoc} + */ + public function getPlusSign() + { + return $this->plusSign; + } + + /** + * {@inheritdoc} + */ + public function setPlusSign($plusSign) + { + $this->plusSign = $plusSign; + } + + /** + * {@inheritdoc} + */ + public function getMinusSign() + { + return $this->minusSign; + } + + /** + * {@inheritdoc} + */ + public function setMinusSign($minusSign) + { + $this->minusSign = $minusSign; + } + + /** + * {@inheritdoc} + */ + public function getPercentSign() + { + return $this->percentSign; + } + + /** + * {@inheritdoc} + */ + public function setPercentSign($percentSign) + { + $this->percentSign = $percentSign; + } + + /** + * {@inheritdoc} + */ + public function getDecimalPattern() + { + return $this->decimalPattern; + } + + /** + * {@inheritdoc} + */ + public function setDecimalPattern($decimalPattern) + { + $this->decimalPattern = $decimalPattern; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPercentPattern() + { + return $this->percentPattern; + } + + /** + * {@inheritdoc} + */ + public function setPercentPattern($percentPattern) + { + $this->percentPattern = $percentPattern; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCurrencyPattern() + { + return $this->currencyPattern; + } + + /** + * {@inheritdoc} + */ + public function setCurrencyPattern($currencyPattern) + { + $this->currencyPattern = $currencyPattern; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAccountingCurrencyPattern() + { + return $this->accountingCurrencyPattern; + } + + /** + * {@inheritdoc} + */ + public function setAccountingCurrencyPattern($accountingCurrencyPattern) + { + $this->accountingCurrencyPattern = $accountingCurrencyPattern; + + return $this; + } +} diff --git a/library/intl/src/NumberFormat/NumberFormatInterface.php b/library/intl/src/NumberFormat/NumberFormatInterface.php new file mode 100644 index 000000000..fa382df70 --- /dev/null +++ b/library/intl/src/NumberFormat/NumberFormatInterface.php @@ -0,0 +1,185 @@ +<?php + +namespace CommerceGuys\Intl\NumberFormat; + +interface NumberFormatInterface +{ + // Arabic-Indic digits. + const NUMBERING_SYSTEM_ARABIC = 'arab'; + // Extended Arabic-Indic digits. + const NUMBERING_SYSTEM_ARABIC_EXTENDED = 'arabext'; + // Bengali digits. + const NUMBERING_SYSTEM_BENGALI = 'beng'; + // Devanagari digits. + const NUMBERING_SYSTEM_DEVANAGARI = 'deva'; + // Latin digits + const NUMBERING_SYSTEM_LATIN = 'latn'; + + /** + * Gets the locale. + * + * @return string + */ + public function getLocale(); + + /** + * Sets the locale. + * + * @param string $locale The locale (i.e. "en_US"). + */ + public function setLocale($locale); + + /** + * Gets the numbering system. + * + * The value is one of the NUMBERING_SYSTEM_ constants. + * + * @return string + */ + public function getNumberingSystem(); + + /** + * Sets the numbering system. + * + * @param string $numberingSystem One of the NUMBERING_SYSTEM_ constants. + */ + public function setNumberingSystem($numberingSystem); + + /** + * Gets the decimal separator. + * + * @return string + */ + public function getDecimalSeparator(); + + /** + * Sets the decimal separator. + * + * @var string $decimalSeparator + */ + public function setDecimalSeparator($decimalSeparator); + + /** + * Gets the grouping separator. + * + * @return string + */ + public function getGroupingSeparator(); + + /** + * Sets the grouping separator. + * + * @var string $groupingSeparator + */ + public function setGroupingSeparator($groupingSeparator); + + /** + * Gets the plus sign. + * + * @return string + */ + public function getPlusSign(); + + /** + * Sets the plus sign. + * + * @var string $plusSign + */ + public function setPlusSign($plusSign); + + /** + * Gets the minus sign. + * + * @return string + */ + public function getMinusSign(); + + /** + * Sets the minus sign. + * + * @var string $minusSign + */ + public function setMinusSign($minusSign); + + /** + * Gets the percent sign. + * + * @return string + */ + public function getPercentSign(); + + /** + * Sets the percent sign. + * + * @var string $percentSign + */ + public function setPercentSign($percentSign); + + /** + * Gets the number pattern used to format decimal numbers. + * + * @return string + * + * @see http://cldr.unicode.org/translation/number-patterns + */ + public function getDecimalPattern(); + + /** + * Sets the number pattern used to format decimal numbers. + * + * @param string $decimalPattern The decimal pattern. + */ + public function setDecimalPattern($decimalPattern); + + /** + * Gets the number pattern used to format percentages. + * + * @return string + * + * @see http://cldr.unicode.org/translation/number-patterns + */ + public function getPercentPattern(); + + /** + * Sets the number pattern used to format percentages. + * + * @param string $percentPattern The percent pattern. + */ + public function setPercentPattern($percentPattern); + + /** + * Gets the number pattern used to format currency amounts. + * + * @return string + * + * @see http://cldr.unicode.org/translation/number-patterns + */ + public function getCurrencyPattern(); + + /** + * Sets the number pattern used to format currency amounts. + * + * @param string $currencyPattern The currency pattern. + */ + public function setCurrencyPattern($currencyPattern); + + /** + * Gets the number pattern used to format accounting currency amounts. + * + * Most commonly used when formatting amounts on invoices. + * + * @return string + * + * @see http://cldr.unicode.org/translation/number-patterns + */ + public function getAccountingCurrencyPattern(); + + /** + * Sets the number pattern used to format accounting currency amounts. + * + * Most commonly used when formatting amounts on invoices. + * + * @param string $accountingCurrencyPattern The accounting currency pattern. + */ + public function setAccountingCurrencyPattern($accountingCurrencyPattern); +} diff --git a/library/intl/src/NumberFormat/NumberFormatRepository.php b/library/intl/src/NumberFormat/NumberFormatRepository.php new file mode 100644 index 000000000..29a597906 --- /dev/null +++ b/library/intl/src/NumberFormat/NumberFormatRepository.php @@ -0,0 +1,87 @@ +<?php + +namespace CommerceGuys\Intl\NumberFormat; + +use CommerceGuys\Intl\LocaleResolverTrait; + +/** + * Repository for number formats based on JSON definitions. + */ +class NumberFormatRepository implements NumberFormatRepositoryInterface +{ + use LocaleResolverTrait; + + /** + * Number format definitions. + * + * @var array + */ + protected $definitions = array(); + + /** + * Creates a NumberFormatRepository instance. + * + * @param string $definitionPath The path to the number format definitions. + * Defaults to 'resources/number_format'. + */ + public function __construct($definitionPath = null) + { + $this->definitionPath = $definitionPath ? $definitionPath : __DIR__ . '/../../resources/number_format/'; + } + + /** + * {@inheritdoc} + */ + public function get($locale, $fallbackLocale = null) + { + $locale = $this->resolveLocale($locale, $fallbackLocale); + if (!isset($this->definitions[$locale])) { + $filename = $this->definitionPath . $locale . '.json'; + $this->definitions[$locale] = json_decode(file_get_contents($filename), true); + } + + return $this->createNumberFormatFromDefinition($this->definitions[$locale], $locale); + } + + /** + * Creates a number format object from the provided definition. + * + * @param array $definition The number format definition. + * @param string $locale The locale of the number format definition. + * + * @return NumberFormat + */ + protected function createNumberFormatFromDefinition(array $definition, $locale) + { + if (!isset($definition['decimal_separator'])) { + $definition['decimal_separator'] = '.'; + } + if (!isset($definition['grouping_separator'])) { + $definition['grouping_separator'] = ','; + } + if (!isset($definition['plus_sign'])) { + $definition['plus_sign'] = '+'; + } + if (!isset($definition['minus_sign'])) { + $definition['minus_sign'] = '-'; + } + if (!isset($definition['percent_sign'])) { + $definition['percent_sign'] = '%'; + } + + $numberFormat = new NumberFormat(); + $numberFormat->setLocale($locale); + $numberFormat->setNumberingSystem($definition['numbering_system']); + $numberFormat->setDecimalSeparator($definition['decimal_separator']); + $numberFormat->setGroupingSeparator($definition['grouping_separator']); + $numberFormat->setPlusSign($definition['plus_sign']); + $numberFormat->setMinusSign($definition['minus_sign']); + $numberFormat->setPercentSign($definition['percent_sign']); + $numberFormat->setDecimalPattern($definition['decimal_pattern']); + $numberFormat->setPercentPattern($definition['percent_pattern']); + $numberFormat->setCurrencyPattern($definition['currency_pattern']); + $numberFormat->setAccountingCurrencyPattern($definition['accounting_currency_pattern']); + + return $numberFormat; + } +} diff --git a/library/intl/src/NumberFormat/NumberFormatRepositoryInterface.php b/library/intl/src/NumberFormat/NumberFormatRepositoryInterface.php new file mode 100644 index 000000000..ff162b522 --- /dev/null +++ b/library/intl/src/NumberFormat/NumberFormatRepositoryInterface.php @@ -0,0 +1,19 @@ +<?php + +namespace CommerceGuys\Intl\NumberFormat; + +/** + * Number format repository interface. + */ +interface NumberFormatRepositoryInterface +{ + /** + * Returns a number format instance for the provided locale. + * + * @param string $locale The locale (i.e. fr-FR). + * @param string $fallbackLocale A fallback locale (i.e "en"). + * + * @return NumberFormatInterface + */ + public function get($locale, $fallbackLocale = null); +} |