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&yen;',
        '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' => '&euro;',
        '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' => '&#2352;',
        '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' => '&yen;',
        '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' => '&#8362;',
        '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' => '&pound;',
        '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' => '&#8361;',
        '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' => '&#3647;',
        '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' => '&#x20ab;',
        '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'];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc