magento / magento2

Prior to making any Submission(s), you must sign an Adobe Contributor License Agreement, available here at: https://opensource.adobe.com/cla.html. All Submissions you make to Adobe Inc. and its affiliates, assigns and subsidiaries (collectively “Adobe”) are subject to the terms of the Adobe Contributor License Agreement.
http://www.magento.com
Open Software License 3.0
11.39k stars 9.29k forks source link

Uncaught exception 'Symfony\Component\Intl\Exception\ResourceBundleNotFoundException' with message #38862

Open in-session opened 4 weeks ago

in-session commented 4 weeks ago

Preconditions and environment

Steps to reproduce

image

Expected result

Actual result

Uncaught exception 'Symfony\Component\Intl\Exception\ResourceBundleNotFoundException' with message 'The resource bundle "/htdocs/vendor/symfony/intl/Resources/data/currencies/de_DE.php" does not exist.' in htdocs/vendor/symfony/intl/Data/Bundle/Reader/PhpBundleReader.php:38

image

Additional information

If I look at the path, it should actually be htdocs/vendor/symfony/intl/Resources/data/currencies/de.php default language "de_DE" is used but symfony/intl expects "de".

https://github.com/magento/magento2/blob/71370a9ca1a0645bd4913de8f1483e5e9d400820/lib/internal/Magento/Framework/Currency/Data/Currency.php#L270-L285

Release note

No response

Triage and priority

m2-assistant[bot] commented 4 weeks ago

Hi @in-session. Thank you for your report. To speed up processing of this issue, make sure that the issue is reproducible on the vanilla Magento instance following Steps to reproduce. To deploy vanilla Magento instance on our environment, Add a comment to the issue:

in-session commented 3 weeks ago

The problem relates to all Symfony\Component\Intl components. if i see it correctly magento tries to use the language iso codes ISO-639-1 with ISO-3166-1. There should be a check which then falls back $isValidLanguage = Languages::exists($languageCode); to ISO-639-1 if not available.

in-session commented 3 weeks ago

I think that this issue is due to the problem: https://github.com/magento/magento2/issues/37780

m2-assistant[bot] commented 3 weeks ago

Hi @engcom-Hotel. Thank you for working on this issue. In order to make sure that issue has enough information and ready for development, please read and check the following instruction: :point_down:

engcom-Hotel commented 3 weeks ago

Hello @in-session,

Thanks for the report and collaboration!

We have tried to reproduce the issue in the latest development branch of Magento i.e. 2.4-develop, but it seems the issue is not reproducible for us. We have followed the below steps in order to reproduce the issue:

Scenario 1

  1. Setup fresh Magento instance
  2. Goto Stores -> Settings -> Configuration -> General -> General
  3. Change Locale to German(Germany)

We are not getting the error mentioned in the main description.

Scenario 2

  1. Setup fresh Magento instance
  2. Goto Stores -> Settings -> Configuration -> General -> General
  3. Change Locale to German(Germany)
  4. Create products and try to debug the files vendor/symfony/intl/Data/Bundle/Reader/PhpBundleReader.php and lib/internal/Magento/Framework/Currency/Data/Currency.php while getting the product from the frontend

We are not getting the error mentioned in the main description.

Please let us know if we missed anything for this issue.

Thanks

in-session commented 3 weeks ago

@engcom-Hotel When I make an error_log here, I always get the value de_DE, but the Currencies::getName and Currencies::getSymbol are 'de' expected. The problem has only arisen through the integration with NewRelic and when, for example, product sliders are stored in their own ESI block. We received 50 GB of Tracelogs within 7 days. You won't be able to replicate this so easily with a dev instance. Can you please categorise this as S1 and open Jira Issus. To reproduce you should set the locale from de_DE and the currencies to EUR.

Perhaps @ihor-sviziev @mrtuvn has an idea for this issus?

Here, however, Symfony\Component\Intl expects 'DE' so that it can load the data from htdocs/vendor/symfony/intl/Resources/data/currencies

https://symfony.com/doc/5.x/components/intl.html#currencies

$currencies = Currencies::getNames('de');
// => ['AFN' => 'Afghanischer Afghani', 'EGP' => 'Ägyptisches Pfund', ...]

$currency = Currencies::getName('EUR', 'de');
// => 'Indische Rupie'

$symbol = Currencies::getSymbol('EUR', 'de');
// => '€'
/**
     * @param array|string|null $options
     * @param string|null $locale
     * @throws CurrencyException
     * @SuppressWarnings(PHPMD.NPathComplexity)
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     */
    public function __construct($options = null, $locale = null)
    {
        $callOptions = $options;

        if (is_array($options) && isset($options['display'])) {
            $this->options['display'] = $options['display'];
        }
        $this->setLocale($locale);

        //error_log("setLocale: " . print_r($locale, true));

        if (!isset($this->options['currency']) || !is_array($options)) {
            $this->options['currency'] = $this->getShortName($options, $this->options['locale']);
        }
        if (!isset($this->options['name']) || !is_array($options)) {
            $this->options['name'] = $this->getName($options, $this->options['locale']);
        }
        if (!isset($this->options['symbol']) || !is_array($options)) {
            $this->options['symbol'] = $this->getSymbol($options, $this->options['locale']);
        }
        if ($this->options['currency'] === null && $this->options['name'] === null) {
            throw new CurrencyException(__(
                'Currency "%1" not found',
                $options
            ));
        }
        if ((is_array($callOptions) && !isset($callOptions['display']))
            || (!is_array($callOptions) && $this->options['display'] == self::NO_SYMBOL)) {
            if (!empty($this->options['symbol'])) {
                $this->options['display'] = self::USE_SYMBOL;
            } elseif (!empty($this->options['currency'])) {
                $this->options['display'] = self::USE_SHORTNAME;
            }
        }
    }

This returns de_DE which is incorrect.

    /**
     * Returns the actual or details of other currency symbols.
     *
     * @param string|null $currency
     * @param string|null $locale
     * @return string|null
     * @throws CurrencyException
     */
    public function getSymbol($currency = null, $locale = null): ?string
    {
        if ($currency === null && $locale === null) {
            return $this->options['symbol'];
        }
        $params = $this->checkParams($currency, $locale);

        if (!empty($params['currency'])) {
            $locale = $this->displayLocale[$params['currency']] ?? $params['locale'];
            $symbol = Currencies::getSymbol($params['currency'], $locale);
            error_log("setLocale: " . print_r($locale, true));            
        } else {
            $symbol = Currencies::getSymbol($params['name'], $params['locale']);
        }

        return !empty($symbol) ? $symbol : null;
    }

As soon as we insert it hard-coded, the problem no longer exists:

    /**
     * Returns the actual or details of other currency symbols.
     *
     * @param string|null $currency
     * @param string|null $locale
     * @return string|null
     * @throws CurrencyException
     */
    public function getSymbol($currency = null, $locale = null): ?string
    {
        if ($currency === null && $locale === null) {
            return $this->options['symbol'];
        }
        $params = $this->checkParams($currency, $locale);

        if (!empty($params['currency'])) {
            $locale = $this->displayLocale[$params['currency']] ?? $params['locale'];
            $symbol = Currencies::getSymbol($params['currency'], 'de');
        } else {
            $symbol = Currencies::getSymbol($params['name'], $params['locale']);
        }

        return !empty($symbol) ? $symbol : null;
    }
engcom-Hotel commented 3 weeks ago

Hello @in-session,

Thanks for the reply!

We have more dig into this issue and tried to debug the following method call:

https://github.com/magento/magento2/blob/6a18520462ec536ba423ef0ece19fcf64e51d707/lib/internal/Magento/Framework/Currency/Data/Currency.php#L313-L331

Actually here we are trying to get the Name from the method call Currencies::getName with param de and de_DE. But what we have observed here is, we are getting the same and correct Euro name in return. Please refer to the below screenshots for reference:

If we pass de

with_de

_If we pass de_DE_

without_de

But we have also gone through the Symfony documentation here: https://symfony.com/doc/6.4/components/intl.html#currencies and it requires de as a second parameter.

But still we are unable to reproduce the issue as mentioned in the main description.

Thanks

in-session commented 3 weeks ago

@engcom-Hotel

thanks for your check i have looked at everything again and i think this only affects the getSymbol.

public function getSymbol($currency = null, $locale = null): ?string
    {
        if ($currency === null && $locale === null) {
            return $this->options['symbol'];
        }
        $params = $this->checkParams($currency, $locale);

        if (!empty($params['currency'])) {
            $locale = $this->displayLocale[$params['currency']] ?? $params['locale'];
            $symbol = Currencies::getSymbol($params['currency'], $locale);
            // $symbol = Currencies::getSymbol($params['currency'], 'de');
        } else {
            $symbol = Currencies::getSymbol($params['name'], $params['locale']);
        }

        return !empty($symbol) ? $symbol : null;
    }

But I have found a way to reproduce this:

Firstly, you should change the following value for EUR in Symfony\Component\Intl\Resources\data\currencies\de.php::377

        'EUR' => [
            '€ New',
            'Euro',
        ],

As a result, you will not yet see any changes in the frontend. If you change the value $symbol = Currencies::getSymbol($params['currency'], $locale); to $symbol = Currencies::getSymbol($params['currency'], 'de'); Only then will the prices be displayed with '€ New'

This is because there is no de_DE.php in the intl and it uses en.php as a fallback.

This means that all locations always use the default en.php, like:

Therefore it should be checked here whether the locale exists, if not the language code should be manipulated from de_DE to de and als fallback en, as a rough example

  public function getSymbol($currency = null, $locale = null): ?string
    {
        if ($currency === null && $locale === null) {
            return $this->options['symbol'];
        }
        $params = $this->checkParams($currency, $locale);

        if (!empty($params['currency'])) {
            $locale = $this->displayLocale[$params['currency']] ?? $params['locale'];

            // Check whether the locale code is valid
            $isValidLocale = Locales::exists($locale);

            if (!$isValidLocale) {                
                $croppedLocale = substr($params['locale'], (strpos($params['locale'], '_') + 1));
                $symbol = Currencies::getSymbol($params['currency'], $croppedLocale);
            } else {
                $symbol = Currencies::getSymbol($params['currency'], $locale);                
            }           

        } else {
            $symbol = Currencies::getSymbol($params['name'], $params['locale']);
        }

        return !empty($symbol) ? $symbol : null;
    }

This is also the reason why the value in the Issus is not taken here, the MXN cannot be displayed correctly because de.php is not loaded https://github.com/magento/magento2/issues/37780

engcom-Hotel commented 3 weeks ago

Hello @in-session,

Thanks for the reply!

We have debugged the issue and are able to reproduce it, but there is a catch, let me explain here.

Please refer to the below screenshot from method vendor/symfony/intl/Data/Bundle/Reader/BundleEntryReader:readEntry, when the $locale is de_DE:

error_resource_not_found

But after that on line no. 136, the below method has been called:

$currentLocale = Locale::getFallback($currentLocale);

Which then called getFallback method, the documentation of the method is as follows:

Returns the fallback locale for a given locale.
For example, the fallback of "fr_FR" is "fr". The fallback of "fr" is the default fallback locale configured with setDefaultFallback(). The default fallback locale has no fallback.
Returns:
null|string The ICU locale code of the fallback locale, or null if no fallback exists

Now this method will return de, which is available on the vendor/symfony/intl/Resources/data/currencies/ path, hence it is returning the correct value for us:

image

It seems to us an expected behaviour, let me know in case we have missed anything.

Thanks

in-session commented 3 weeks ago

@engcom-Hotel

Thank you for your feedback

This does indeed return the correct value:

// Then determine fallback locale
            $currentLocale = Locale::getFallback($currentLocale);

            error_log("currentLocale: " . print_r($currentLocale, true));

But if you make this change, it will not be recognised in the frontend. You should change the following value for EUR in Symfony\Component\Intl\Resources\data\currencies\de.php::377

        'EUR' => [
            '€ New',
            'Euro',
        ],

Which means for me that getFallback might be loaded but the frontend does not take over. With the value "€ New" all article prices should actually appear in the frontend if the fallback should work.

Which symfony/intl version are you using 6.4? Because version 2.4.6-p6 should actually be loaded with version 5 image

engcom-Hotel commented 2 weeks ago

Hello @in-session,

Thanks for the reply!

But if you make this change, it will not be recognised in the frontend. You should change the following value for EUR in Symfony\Component\Intl\Resources\data\currencies\de.php::377

        'EUR' => [
            '€ New',
            'Euro',
        ],

Which means for me that getFallback might be loaded but the frontend does not take over. With the value "€ New" all article prices should actually appear in the frontend if the fallback should work.

I made the changes you asked for and made the changes in the de.php as below:

'EUR' => [
      '€ New',
      'Euro',
  ],

Please refer to the screenshots attached to this https://github.com/magento/magento2/issues/38862#issuecomment-2196389524.

Which symfony/intl version are you using 6.4? Because version 2.4.6-p6 should actually be loaded with version 5

We are using version 6.4.

image

in-session commented 2 weeks ago

@engcom-Hotel Can you please try with 5.4.40 because version 2.4.6-p6 should actually be loaded with version 5

engcom-Hotel commented 2 weeks ago

Hello @in-session,

Yes, we have tried it on 2.4.6-p6 with symphony/intl version 5.4.40 and it behaves the same as explained here in this https://github.com/magento/magento2/issues/38862#issuecomment-2196389524.

But I think we can confirm this issue, due to the logs you are getting in the NewRelic. We are also getting while debugging this issue.

Please refer to the below screenshot from method vendor/symfony/intl/Data/Bundle/Reader/BundleEntryReader:readEntry, when the $locale is de_DE:

error_resource_not_found

But after that on line no. 136, the below method has been called:

Hence confirming this issue for further processing.

Thanks

github-jira-sync-bot commented 2 weeks ago

Unfortunately, not enough information was provided to create a Jira ticket. Please make sure you added the following label(s): Reproduced on 2.4.x, ^Area:.*

Once all required labels are present, please add Issue: Confirmed label again.

github-jira-sync-bot commented 2 weeks ago

:white_check_mark: Jira issue https://jira.corp.adobe.com/browse/AC-12323 is successfully created for this GitHub issue.

m2-assistant[bot] commented 2 weeks ago

:white_check_mark: Confirmed by @engcom-Hotel. Thank you for verifying the issue.
Issue Available: @engcom-Hotel, You will be automatically unassigned. Contributors/Maintainers can claim this issue to continue. To reclaim and continue work, reassign the ticket to yourself.