commercetools-8.x-1.2-alpha1/src/CommercetoolsLocalization.php
src/CommercetoolsLocalization.php
<?php
namespace Drupal\commercetools;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Symfony\Component\Intl\Countries;
use Symfony\Component\Intl\Currencies;
use Symfony\Component\Intl\Languages;
/**
* Provides localization actions.
*/
class CommercetoolsLocalization {
const CONFIGURATION_NAME = 'commercetools.locale';
const MAP_FILE = 'localization' . DIRECTORY_SEPARATOR . 'language-country-currency.yml';
const CONFIG_LANGUAGE_FALLBACK = 'language_fallback';
const CONFIG_STORE = 'store';
const CONFIG_LANGUAGE = 'language';
const CONFIG_COUNTRY = 'country';
const CONFIG_CURRENCY = 'currency';
const CONFIG_CHANNEL = 'channel';
const LANGUAGE_ARGUMENT = 'acceptLanguage';
const LOCALE_SUGGESTED = 1;
const LOCALE_ERROR = -1;
const MATCH_EXACT = 0;
const MATCH_PARTIAL = 1;
const MATCH_SUGGESTED_DIRECT = 2;
const MATCH_SUGGESTED = 3;
/**
* Helper constants to inform about the incompatible dependencies.
*/
const SYMFONY_POLYFILL_INTL_ICU_EXCEPTION_CLASS = 'Symfony\Polyfill\Intl\Icu\Exception\NotImplementedException';
const SYMFONY_POLYFILL_INTL_ICU_ERROR_MESSAGE = 'The "symfony/polyfill-intl-icu" library does not provide the method, replace it with "php-intl" extension or "punic/punic" library.';
/**
* The map of suggestions.
*
* @var array
*/
protected $map;
/**
* The list of enabled Drupal languages.
*
* @var \Drupal\Core\Language\LanguageInterface[]
*/
public $languages;
/**
* The current Drupal language.
*
* @var \Drupal\Core\Language\LanguageInterface
*/
public $currentLanguage;
/**
* The default Drupal language.
*
* @var \Drupal\Core\Language\LanguageInterface
*/
public $defaultLanguage;
/**
* The current localization settings config.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* The default commercetools locale.
*
* @var string
*/
protected $ctLocaleDefault;
/**
* CommercetoolsLocalization constructor.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
* The language manager service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The config factory service.
*/
public function __construct(
protected LanguageManagerInterface $languageManager,
protected readonly ConfigFactoryInterface $configFactory,
) {
$this->languages = $languageManager->getLanguages();
$this->currentLanguage = $languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
$this->defaultLanguage = $languageManager->getDefaultLanguage();
$this->config = $configFactory->getEditable(self::CONFIGURATION_NAME);
}
/**
* Provides a list of related configuration options.
*
* @return string[]
* A list of related configuration options.
*/
public function listConfigOptions(): array {
return [
self::CONFIG_STORE,
self::CONFIG_LANGUAGE,
self::CONFIG_COUNTRY,
self::CONFIG_CURRENCY,
self::CONFIG_CHANNEL,
];
}
/**
* Gets localization configuration settings for all locales.
*
* @return array
* Localization configuration settings for all locales.
*/
public function getAllLocalesSettings(): array {
$settings = [];
foreach (array_keys($this->languages) as $code) {
if ($code == $this->defaultLanguage->getId()) {
$config = $this->config;
}
elseif ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
/** @var \Drupal\Core\Config\Config $config */
$config = $this->languageManager
->getLanguageConfigOverride($code, self::CONFIGURATION_NAME);
}
foreach ($this->listConfigOptions() as $option) {
$settings[$code][$option] = $config->get($option);
}
}
return $settings;
}
/**
* Determines whether the whole localization configuration is complete.
*
* @return bool
* TRUE if all configuration options are set, false otherwise.
*/
public function isConfigured(): bool {
$settings = $this->getAllLocalesSettings();
// Store and Channel are optional.
$options = array_diff(
$this->listConfigOptions(),
[self::CONFIG_STORE, self::CONFIG_CHANNEL]
);
foreach ($settings as $config) {
foreach ($options as $option) {
if (empty($config[$option])) {
return FALSE;
}
}
}
return TRUE;
}
/**
* Saves the localization configuration settings for a locale.
*
* @param string $code
* The Drupal locale code (language code).
* @param array $settings
* The localization configuration settings for a Drupal locale.
*/
public function saveLocaleSettings(string $code, array $settings): void {
$config = $this->config;
if ($code !== $this->defaultLanguage->getId() && $this->languageManager instanceof ConfigurableLanguageManagerInterface) {
/** @var \Drupal\Core\Config\Config $config */
$config = $this->languageManager
->getLanguageConfigOverride($code, self::CONFIGURATION_NAME);
}
foreach ($this->listConfigOptions() as $option) {
$config->set($option, $settings[$option] ?? '');
}
$config->set('langcode', $code);
$config->save();
}
/**
* Saves the localization configuration settings for all locales.
*
* @param array $settings
* The localization configuration settings per Drupal locale.
*/
public function saveAllLocalesSettings(array $settings): void {
if ($this->ctLocaleDefault) {
$this->config->set(self::CONFIG_LANGUAGE_FALLBACK, $this->ctLocaleDefault)
->save();
}
foreach ($settings as $code => $options) {
$this->saveLocaleSettings($code, $options);
}
}
/**
* Deletes all localization configuration settings.
*/
public function clearSettings(): void {
if ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
foreach (array_keys($this->languages) as $code) {
$config = $this->languageManager
->getLanguageConfigOverride($code, self::CONFIGURATION_NAME);
$config->delete();
}
}
else {
foreach ($this->listConfigOptions() as $option) {
$this->config->set($option, '');
}
$this->config->save();
}
}
/**
* Returns localization configuration option value by key.
*
* @param string $key
* The configuration option key.
*
* @return mixed
* Returns a localization configuration option value.
*/
public function getOption(string $key): mixed {
// Check for translated value first.
if ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
if ($this->currentLanguage->getId() != $this->defaultLanguage->getId()) {
/** @var \Drupal\Core\Config\Config $config */
$config = $this->languageManager->getLanguageConfigOverride(
$this->currentLanguage->getId(),
self::CONFIGURATION_NAME,
);
if ($value = $config->get($key)) {
return $value;
}
}
}
return $this->config->get($key);
}
/**
* Gets localized language name.
*
* @param string $input
* The input language code.
*
* @return string
* Returns a name of the language in the current locale.
*/
public function getLanguageName(string $input): string {
$language = $this->getPrimaryLanguage($input);
return Languages::getName($language, $this->currentLanguage->getId());
}
/**
* Gets localized country name.
*
* @param string $input
* The input country code.
*
* @return string
* Returns a name of the country in the current locale.
*/
public function getCountryName(string $input): string {
return Countries::getName($input, $this->currentLanguage->getId());
}
/**
* Gets localized currency name.
*
* @param string $input
* The input currency code.
*
* @return string
* Returns a name of the currency in the current locale.
*/
public function getCurrencyName(string $input): string {
return Currencies::getName($input, $this->currentLanguage->getId());
}
/**
* Matches localized variants to the current one.
*
* This method is useful when data is retrieved via 'nameAllLocales'.
*
* @param string[] $variants
* The list of localized variants. Keys must be locale codes.
*
* @return string
* Returns a most matched variant. First if no match.
*/
public function fromLocalizedArray(array $variants): string {
// First, check for expected language.
if ($expectedLanguage = $this->getOption(self::CONFIG_LANGUAGE)) {
if (isset($variants[$expectedLanguage])) {
return $variants[$expectedLanguage];
}
}
// If no, match by current language.
$currentLanguage = $this->getPrimaryLanguage($this->currentLanguage->getId());
foreach ($variants as $code => $variant) {
$variantLanguage = $this->getPrimaryLanguage($code);
if ($variantLanguage == $currentLanguage) {
return $variant;
}
}
// Default language otherwise.
return reset($variants);
}
/**
* Adds language fallback if a current Drupal locale is not default.
*
* @param ?string $initial
* The initial Drupal locale to start from. Optional.
*
* @return string[]
* A list of language fallbacks.
*/
public function getLanguageFallbacks(?string $initial = NULL): array {
$list = [
$this->getOption(self::CONFIG_LANGUAGE),
$this->getOption(self::CONFIG_LANGUAGE_FALLBACK),
];
$default = $this->defaultLanguage->getId();
if ($initial && $initial != $default && $this->isConfigured()) {
$list[] = $this->getAllLocalesSettings()[$initial][self::CONFIG_LANGUAGE];
}
$list = array_filter(array_unique($list));
// Fallback to Drupal locales.
return $list ? array_values($list) : array_keys($this->languages);
}
/**
* Provides formed language argument.
*
* @return string[]
* A list of language fallbacks.
*/
public function getLanguageArgument(): array {
return [
self::LANGUAGE_ARGUMENT => $this->getLanguageFallbacks(),
];
}
/**
* Determine whether the provided commercetools project info is not valid.
*
* @param array $info
* The provided commercetools project info.
*
* @return bool
* True if the provided commercetools project info is not valid.
*/
public function isInvalidProjectInfo(array $info): bool {
return empty($info['languages']) || empty($info['countries'] || empty($info['currencies']));
}
/**
* Process localization of the provided commercetools project info.
*
* @param array $info
* The provided commercetools project info.
*
* @return int
* An operation result code.
*/
public function completeProjectLocalization(array $info): int {
if ($this->isInvalidProjectInfo($info)) {
return self::LOCALE_ERROR;
}
$this->clearSettings();
$settings = $this->processProjectInfo($info);
$this->saveAllLocalesSettings($settings);
return self::LOCALE_SUGGESTED;
}
/**
* Process project info for localization.
*
* @param array $info
* The provided commercetools project info.
*
* @return array
* A suggested localization parameters set.
*/
public function processProjectInfo(array $info): array {
$params = [];
$this->ctLocaleDefault = reset($info['languages']);
foreach (array_keys($this->languages) as $language) {
$params[$language] = $this->assortLanguageParameters($language, $info);
}
return $params;
}
/**
* Performs match of parameters for a Drupal language.
*
* @param string $language
* The Drupal language to match parameters for.
* @param array $info
* The provided commercetools project info.
*
* @return array
* A set of localization parameters.
*/
public function assortLanguageParameters(string $language, array $info): array {
$drupalLocaleRegion = $this->getRegion($language);
$context = [
'drupal_locale_language' => $this->getPrimaryLanguage($language),
'drupal_locale_region' => $drupalLocaleRegion,
'drupal_locale_currency' => $this->getCurrencyForLocale($language),
'ct_languages' => $info['languages'],
'ct_countries' => $info['countries'],
'ct_currencies' => $info['currencies'],
];
$result = $this->matchParameters($context) + $this->suggestParameters($context);
ksort($result);
$highestMatch = reset($result);
return $highestMatch ? reset($highestMatch) : [];
}
/**
* Performs match of parameters by well-formed locales.
*
* @param array $context
* Array of available data used for the selection process.
*
* @return array
* A set of localization parameters.
*/
private function matchParameters(array $context): array {
$result = [];
// Unable to match if Drupal locale is not well-formed.
if (empty($context['drupal_locale_region'])) {
return $result;
}
foreach ($context['ct_languages'] as $ctLocale) {
$ctRegion = $this->getRegion($ctLocale);
// Skip if commercetools locale is not well-formed.
if (empty($ctRegion)) {
continue;
}
$ctCurrency = $this->getCurrencyForLocale($ctLocale);
// Exact match by well-formed locales in Drupal and commercetools.
if ($context['drupal_locale_region'] === $ctRegion) {
if (in_array($ctRegion, $context['ct_countries']) && in_array($ctCurrency, $context['ct_currencies'])) {
$result[self::MATCH_EXACT][] = $this->toParametersArray($ctLocale, $ctRegion, $ctCurrency);
}
}
$ctLanguage = $this->getPrimaryLanguage($ctLocale);
// Partial match by well-formed locale in commercetools.
if ($ctLanguage === $context['drupal_locale_language']) {
if (in_array($ctRegion, $context['ct_countries']) && in_array($ctCurrency, $context['ct_currencies'])) {
$result[self::MATCH_PARTIAL][] = $this->toParametersArray($ctLocale, $ctRegion, $ctCurrency);
}
}
// Partial match by well-formed locale in Drupal.
if ($context['drupal_locale_region'] && $ctLanguage === $context['drupal_locale_language']) {
$currency = $this->getCurrencyForLocale($ctLocale);
if (in_array($context['drupal_locale_region'], $context['ct_countries']) && in_array($currency, $context['ct_currencies'])) {
$result[self::MATCH_PARTIAL][] = $this->toParametersArray($ctLocale, $context['drupal_locale_region'], $currency);
}
}
}
return $result;
}
/**
* Performs suggestion of parameters by static mapping.
*
* @param array $context
* Array of available data used for the selection process.
*
* @return array
* A set of localization parameters.
*/
private function suggestParameters(array $context): array {
$result = [];
$map = $this->getMap();
foreach ($context['ct_languages'] as $ctLocale) {
$ctLanguage = $this->getPrimaryLanguage($ctLocale);
// Skip if not mapping or different languages.
if (empty($map[$ctLanguage]) || $ctLanguage != $context['drupal_locale_language']) {
continue;
}
// Suggest by country.
foreach ($map[$ctLanguage] as $country => $currenciesList) {
if (in_array($country, $context['ct_countries'])) {
foreach ($currenciesList as $currency) {
if (in_array($currency, $context['ct_currencies'])) {
$targetLocale = $ctLanguage . '-' . $country;
if (!in_array($targetLocale, $context['ct_languages'])) {
continue;
}
$result[self::MATCH_SUGGESTED_DIRECT][] = $this->toParametersArray($targetLocale, $country, $currency);
}
}
}
}
// Suggest by currency.
$currenciesMap = $this->flattenCurrenciesMap($ctLanguage);
foreach ($currenciesMap as $currency => $countriesList) {
if (in_array($currency, $context['ct_currencies'])) {
foreach ($countriesList as $country) {
if (in_array($country, $context['ct_countries'])) {
$result[self::MATCH_SUGGESTED][] = $this->toParametersArray($ctLocale, $country, $currency);
}
}
}
}
}
return $result;
}
/**
* Provides a map for suggestions.
*
* @return array
* A composed map of languages, countries and currencies.
*/
protected function getMap(): array {
if ($this->map) {
return $this->map;
}
$this->map = Yaml::decode(
file_get_contents(
dirname(__DIR__) . DIRECTORY_SEPARATOR . self::MAP_FILE,
)
);
return $this->map;
}
/**
* Gets currency code for a given locale code.
*
* @param string $locale
* The locale code to get the currency for.
*
* @return string
* The currency code.
*/
private function getCurrencyForLocale(string $locale): string {
// Use the existing YAML mapping file as the primary method.
$map = $this->getMap();
$language = $this->getPrimaryLanguage($locale);
$region = $this->getRegion($locale);
// Try exact locale match first (language_region)
if (!empty($region) && isset($map[$language][$region])) {
return reset($map[$language][$region]);
}
// Try language-only match.
if (isset($map[$language])) {
$firstCurrency = reset($map[$language]);
return reset($firstCurrency);
}
// Fallback to a default currency if no match found.
return 'USD';
}
/**
* Builds the array of parameters.
*
* @param string $language
* The language code.
* @param string $country
* The country code.
* @param string $currency
* The currency code.
*
* @return array
* The result array.
*/
private function toParametersArray(string $language, string $country, string $currency): array {
return [
self::CONFIG_LANGUAGE => $language,
self::CONFIG_COUNTRY => $country,
self::CONFIG_CURRENCY => $currency,
];
}
/**
* Reverses country-currencies mapping into currency-countries one.
*
* @param string $language
* The input country-currencies mapping.
*
* @return array
* A reversed currency-countries mapping.
*/
private function flattenCurrenciesMap(string $language): array {
$currenciesMap = [];
$map = $this->getMap();
$currenciesList = [];
foreach ($map[$language] as $currencies) {
foreach ($currencies as $currency) {
$currenciesList[$currency] = $currency;
}
}
foreach ($map as $list) {
foreach ($list as $country => $currencies) {
if ($currenciesMatch = array_intersect($currenciesList, $currencies)) {
$currency = reset($currenciesMatch);
$currenciesMap[$currency][$country] = $country;
}
}
}
return $currenciesMap;
}
/**
* Returns the primary language for the locale.
*
* @param string $locale
* The locale to extract the primary language code from.
*
* @return string|null
* The extracted language code or null in case of error.
*
* @see https://php.net/locale.getprimarylanguage
*/
private function getPrimaryLanguage(string $locale): ?string {
if (class_exists(\Locale::class) && method_exists(\Locale::class, 'getPrimaryLanguage')) {
try {
$lang = \Locale::getPrimaryLanguage($locale);
return $lang !== '' && strtolower($lang) !== 'und' ? strtolower($lang) : NULL;
}
catch (\Throwable $e) {
// The symfony/polyfill-intl-icu library does not provide the required
// method and throws an exception. We should inform about this.
$exceptionClass = self::SYMFONY_POLYFILL_INTL_ICU_EXCEPTION_CLASS;
if (class_exists($exceptionClass) && $e instanceof $exceptionClass) {
throw new \Exception(self::SYMFONY_POLYFILL_INTL_ICU_ERROR_MESSAGE, 0, $e);
}
else {
throw $e;
}
}
}
return static::getPrimaryLanguageFallback($locale);
}
/**
* Polyfill/fallback for primary language when ext-intl is missing or failed.
*/
public static function getPrimaryLanguageFallback(string $locale): ?string {
$tag = preg_replace('/@.*$/', '', $locale);
$tag = str_replace('_', '-', $tag);
$parts = explode('-', $tag);
$lang = $parts[0] ?? '';
if ($lang !== '' && preg_match('/^[A-Za-z]{2,3}$|^[A-Za-z]{4,8}$/', $lang) && strtolower($lang) !== 'und') {
return strtolower($lang);
}
return NULL;
}
/**
* Returns the region for the locale.
*
* @param string $locale
* The locale to extract the region code from.
*
* @return string|null
* The extracted region code or null if not present.
*
* @see https://php.net/locale.getregion
*/
private function getRegion(string $locale): ?string {
if (class_exists(\Locale::class) && method_exists(\Locale::class, 'getRegion')) {
try {
$region = \Locale::getRegion($locale);
return $region !== '' ? $region : NULL;
}
catch (\Throwable $e) {
// The symfony/polyfill-intl-icu library does not provide the required
// method and throws an exception. We should inform about this.
$exceptionClass = self::SYMFONY_POLYFILL_INTL_ICU_EXCEPTION_CLASS;
if (class_exists($exceptionClass) && $e instanceof $exceptionClass) {
throw new \Exception(self::SYMFONY_POLYFILL_INTL_ICU_ERROR_MESSAGE, 0, $e);
}
else {
throw $e;
}
}
}
return static::getRegionFallback($locale);
}
/**
* Polyfill/fallback for region when ext-intl is missing or failed.
*/
public static function getRegionFallback(string $locale): ?string {
$tag = preg_replace('/@.*$/', '', $locale);
$tag = str_replace('_', '-', $tag);
$parts = explode('-', $tag);
$i = 1;
if (isset($parts[$i]) && preg_match('/^[A-Za-z]{4}$/', $parts[$i])) {
$i++;
}
$candidateRegion = $parts[$i] ?? NULL;
if ($candidateRegion && preg_match('/^\d{3}$/', $candidateRegion)) {
return $candidateRegion;
}
if ($candidateRegion && preg_match('/^[A-Za-z]{2}$/', $candidateRegion)) {
return strtoupper($candidateRegion);
}
foreach ($parts as $part) {
if (preg_match('/^[A-Za-z]{2}$/', $part)) {
// Just a quick fix to make the 'en' language map to 'US' country.
// @todo Rework this to match the most popular country per language
// according to the `localization/language-country-currency.yml`file.
if ($part == 'en') {
return strtoupper('us');
}
return strtoupper($part);
}
// @todo Find out do we really need this.
if (preg_match('/^\d{3}$/', $part)) {
return $part;
}
}
return NULL;
}
/**
* Get fraction digits for the current locale's currency.
*
* @return int
* The number of fraction digits for the currency.
*/
public function getFractionDigits(): int {
$language_fallback = $this->getLanguageFallbacks();
$locale = !empty($language_fallback) ? reset($language_fallback) : NULL;
if (!$locale) {
// A fallback to 2 fraction digits if no locale is available.
return 2;
}
if (class_exists(\NumberFormatter::class) && $locale) {
$formatter = new \NumberFormatter(str_replace('-', '_', $locale), \NumberFormatter::CURRENCY);
return $formatter->getAttribute(\NumberFormatter::FRACTION_DIGITS);
}
else {
// Use a polyfill/fallback method.
$currency = $this->getOption(self::CONFIG_CURRENCY);
$currencyInfo = $this->getCurrencyInfo($currency);
return $currencyInfo['fraction_digits'];
}
}
/**
* Get currency formatting info for the current locale's currency.
*
* @param string $currency
* The currency code.
*
* @return array|null
* An array with currency formatting info or null if currency is unknown.
* The array contains:
* - symbol: The currency symbol or null.
* - fraction digits: The number of fraction digits.
* - decimal separator: The decimal separator character.
* - thousands separator: The thousands separator character.
* - symbol position: 0 for prefix, 1 for suffix.
*/
private function getCurrencyInfo(string $currency): array {
$currencies = [
'ARS' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'AMD' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'AWG' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'AUD' => [
'symbol' => 'AU$',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ' ',
'symbol_position' => 0,
],
'BSD' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'BHD' => [
'symbol' => NULL,
'fraction_digits' => 3,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'BDT' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'BZD' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'BMD' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'BOB' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'BAM' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'BWP' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'BRL' => [
'symbol' => 'R$',
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'BND' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'CAD' => [
'symbol' => 'CA$',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'KYD' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'CLP' => [
'symbol' => NULL,
'fraction_digits' => 0,
'decimal_separator' => '',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'CNY' => [
'symbol' => 'CN¥',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'COP' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'CRC' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'HRK' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'CUC' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'CUP' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'CYP' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'CZK' => [
'symbol' => 'Kc',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 1,
],
'DKK' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'DOP' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'XCD' => [
'symbol' => 'EC$',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'EGP' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'SVC' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'EUR' => [
'symbol' => '€',
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'GHC' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'GIP' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'GTQ' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'HNL' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'HKD' => [
'symbol' => 'HK$',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'HUF' => [
'symbol' => 'HK$',
'fraction_digits' => 0,
'decimal_separator' => '',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'ISK' => [
'symbol' => 'kr',
'fraction_digits' => 0,
'decimal_separator' => '',
'thousands_separator' => '.',
'symbol_position' => 1,
],
'INR' => [
'symbol' => 'र',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'IDR' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'IRR' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'JMD' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'JPY' => [
'symbol' => '¥',
'fraction_digits' => 0,
'decimal_separator' => '',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'JOD' => [
'symbol' => NULL,
'fraction_digits' => 3,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'KES' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'KWD' => [
'symbol' => NULL,
'fraction_digits' => 3,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'LVL' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'LBP' => [
'symbol' => NULL,
'fraction_digits' => 0,
'decimal_separator' => '',
'thousands_separator' => ' ',
'symbol_position' => 0,
],
'LTL' => [
'symbol' => 'Lt',
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => ' ',
'symbol_position' => 1,
],
'MKD' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'MYR' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'MTL' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'MUR' => [
'symbol' => NULL,
'fraction_digits' => 0,
'decimal_separator' => '',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'MXN' => [
'symbol' => 'MX$',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'MZM' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'NPR' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'ANG' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'ILS' => [
'symbol' => '₪',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'TRY' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'NZD' => [
'symbol' => 'NZ$',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'NOK' => [
'symbol' => 'kr',
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 1,
],
'PKR' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'PEN' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'UYU' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'PHP' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'PLN' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ' ',
'symbol_position' => 0,
],
'GBP' => [
'symbol' => '£',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'OMR' => [
'symbol' => NULL,
'fraction_digits' => 3,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'RON' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'ROL' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'RUB' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'SAR' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'SGD' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'SKK' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => ' ',
'symbol_position' => 0,
],
'SIT' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'ZAR' => [
'symbol' => 'R',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ' ',
'symbol_position' => 0,
],
'KRW' => [
'symbol' => '₩',
'fraction_digits' => 0,
'decimal_separator' => '',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'SZL' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ', ',
'symbol_position' => 0,
],
'SEK' => [
'symbol' => 'kr',
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 1,
],
'CHF' => [
'symbol' => 'SFr ',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => "'",
'symbol_position' => 0,
],
'TZS' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'THB' => [
'symbol' => '฿',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 1,
],
'TOP' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'AED' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'UAH' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => ' ',
'symbol_position' => 0,
],
'USD' => [
'symbol' => '$',
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'VUV' => [
'symbol' => NULL,
'fraction_digits' => 0,
'decimal_separator' => '',
'thousands_separator' => ',',
'symbol_position' => 0,
],
'VEF' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'VEB' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => ',',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'VND' => [
'symbol' => '₫',
'fraction_digits' => 0,
'decimal_separator' => '',
'thousands_separator' => '.',
'symbol_position' => 0,
],
'ZWD' => [
'symbol' => NULL,
'fraction_digits' => 2,
'decimal_separator' => '.',
'thousands_separator' => ' ',
'symbol_position' => 0,
],
];
return $currencies[$currency] ?? $currencies['USD'];
}
}
