3

How to redirect customer to specific store view according to browser language?

  • You want it to automatically detect and redirect right away? – camdixon Jul 23 '16 at 3:19
  • Yes. automatically detect and redirect – Ricky.C Jul 23 '16 at 3:19
  • Cool idea actually - try here: stackoverflow.com/questions/3770513/… – camdixon Jul 23 '16 at 3:21
  • Then you need to find the current URL (does everyone go to the homepage? probably not). stackoverflow.com/questions/25192212/… - to get current URL. Then direct them to that page with their language preference. However, what if someone wants to switch off of that? What will you do? I think you will at the end need to figure out how to do this for only new users. – camdixon Jul 23 '16 at 3:28
4

Using controller_action_predispatch event,you achieve this thing.

At event at Obsever,get browser language by core php code

  $lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);

then redirect to particular store url

<?php
namespace [Vendor]\[modulename]\Observer;
use \Magento\Framework\Event\Observer;
use \Magento\Framework\Event\ObserverInterface;

class [YourClass] implements ObserverInterface {
    protected $_responseFactory;
    protected $_url;

    public function __construct(

        \Magento\Framework\App\ResponseFactory $responseFactory,
        \Magento\Framework\UrlInterface $url
    ) {
        $this->_responseFactory = $responseFactory;
        $this->_url = $url;
    }

    public function execute(Observer $observer) {
             $category = $observer->getEvent()->getCategory();

            switch ($lang){
                case "fr":
                 $this->_responseFactory->create()->setRedirect($FRStoreUrl)->sendResponse();
                    break;
                case "it":
                    $this->_responseFactory->create()->setRedirect($ITStoreUrl)->sendResponse();
                case "en":
                  $this->_responseFactory->create()->setRedirect($EnRedirectUrl)->sendResponse();
                    break;        
                default:
                    //echo "PAGE EN - Setting Default";
                    $this->_responseFactory->create()->setRedirect($EnRedirectUrl)->sendResponse()
                    break;
    }
            $this->_responseFactory->create()->setRedirect($RedirectUrl)->sendResponse();
           exit();

    }
}
0

The example with observer, based on controller_action_predispatch event isn't well because of full page cache. When cache is warmed up and you are trying to open homepage your code inside observer will not be executed and it can be that you receive foreign locale.

I tried to move the idea from above to another place. I added plugin on plugin:

\Magento\PageCache\Model\App\FrontController\BuiltinPlugin::aroundDispatch

And the code started working, but in that case we are facing with redirect. I didn't like this way and decided to go further.

How magento defines in which locale it should open the website? It is based on $_COOKIE['store']. The value of this cookie is the store view code. Let's say that we have several websites and their codes equal "language codes". E.g. en, fr, it, de... (It will make my solution more readable and you will have possibility to understand it better). So if there is a $_COOKIE['store'] and it's value is 'fr'. It will open our website in french language. As you may notice there no any redirects in that case. The website opens immediately in french language.

Here goes source code (there are some explanation below the code):

etc/frontend/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Store\Model\StoreResolver">
        <plugin sortOrder="1" name="vendorStoreSwitcherStoreResolver" type="Vendor\StoreSwitcher\Plugin\Model\StoreResolverPlugin"/>
    </type>
</config>

Plugin/Model/StoreResolverPlugin.php

<?php

declare(strict_types=1);

namespace Vendor\StoreSwitcher\Plugin\Model;

use Magento\Framework\App\Request\Http;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Api\StoreCookieManagerInterface;
use Magento\Store\Api\StoreRepositoryInterface;
use Magento\Store\App\Request\StorePathInfoValidator;
use Magento\Store\Model\StoreIsInactiveException;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Store\Model\StoreResolver;

class StoreResolverPlugin
{
    /**
     * @var StoreManagerInterface
     */
    private $storeManager;

    /**
     * @var StoreCookieManagerInterface
     */
    private $storeCookieManager;

    /**
     * @var Http
     */
    private $request;

    /**
     * @var StoreRepositoryInterface
     */
    private $storeRepository;

    /**
     * @var StorePathInfoValidator
     */
    private $pathInfoValidator;

    /**
     * StoreResolverPlugin constructor.
     * @param StoreManagerInterface $storeManager
     * @param StoreCookieManagerInterface $storeCookieManager
     * @param Http $request
     * @param StoreRepositoryInterface $storeRepository
     * @param StorePathInfoValidator $pathInfoValidator
     */
    public function __construct(
        StoreManagerInterface $storeManager,
        StoreCookieManagerInterface $storeCookieManager,
        Http $request,
        StoreRepositoryInterface $storeRepository,
        StorePathInfoValidator $pathInfoValidator
    ) {
        $this->storeManager = $storeManager;
        $this->storeCookieManager = $storeCookieManager;
        $this->request = $request;
        $this->storeRepository = $storeRepository;
        $this->pathInfoValidator = $pathInfoValidator;
    }

    /**
     * @param StoreResolver $subject
     * @param callable $proceed
     * @return mixed
     * @throws NoSuchEntityException
     * @throws StoreIsInactiveException
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function aroundGetCurrentStoreId(StoreResolver $subject, callable $proceed)
    {
        list($stores, $defaultStoreId) = $this->callPrivateMethod($subject, 'getStoresData');

        $storeCode = $this->pathInfoValidator->getValidStoreCode($this->request);

        if (!$storeCode) {
            $storeCode = $this->request->getParam(
                \Magento\Store\Model\StoreManagerInterface::PARAM_NAME,
                $this->storeCookieManager->getStoreCodeFromCookie()
            );
        }

        if (is_array($storeCode)) {
            if (!isset($storeCode['_data']['code'])) {
                throw new \InvalidArgumentException(__('Invalid store parameter.'));
            }
            $storeCode = $storeCode['_data']['code'];
        }

        /**
         * Added the code which modifies native flow.
         * If Magento already have storeCode - it means that Customer already selected desired store view
         * or our functionality already fulfilled.
         * In all other cases we proceed with original code. On one hand part of the code will execute twice,
         * but on the other hand we will not break plugins chain.
         */
        if (!$storeCode) {
            $storeCode = $this->createLocaleCookie($defaultStoreId);
        } else {
            return $proceed();
        }
        // >>> Modifications ends here

        if ($storeCode) {
            try {
                $store = $this->storeRepository->getActiveStoreByCode($storeCode);
            } catch (NoSuchEntityException|StoreIsInactiveException $e) {
                $store = $this->storeRepository->getActiveStoreById($defaultStoreId);
            }

            if (!in_array($store->getId(), $stores)) {
                $store = $this->storeRepository->getActiveStoreById($defaultStoreId);
            }
        } else {
            $store = $this->storeRepository->getActiveStoreById($defaultStoreId);
        }
        return $store->getId();
    }

    /**
     * @return string|null
     */
    private function createLocaleCookie(): ?string
    {
        $browserLang = $this->request->getServer('HTTP_ACCEPT_LANGUAGE');
        $languageCode = substr($browserLang, 0, 2);
        $code = in_array($languageCode, $this->getStoreCodes()) ? $languageCode : 'en';

        try {
            $store = $this->storeRepository->getActiveStoreByCode($code);
        } catch (NoSuchEntityException|StoreIsInactiveException $e) {
            return null;
        }

        $this->storeCookieManager->setStoreCookie($store);

        return $code;
    }

    /**
     * @return array
     */
    private function getStoreCodes(): array
    {
        $stores = [];
        foreach ($this->storeManager->getStores() as $value) {
            $stores[] = $value->getCode();
        }
        return $stores;
    }

    /**
     * @param $object
     * @param $method
     * @return mixed
     */
    private function callPrivateMethod($object, $method)
    {
        $closure = function ($object, $method) {
            return call_user_func_array([$object, $method], []);
        };
        $closure = $closure->bindTo(null, $object);
        return $closure($object, $method);
    }
}

Vendor/StoreSwitcher/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Store\Model\StoreSwitcher">
        <arguments>
            <argument name="storeSwitchers" xsi:type="array">
                <item name="manageStoreCookie" xsi:type="object">Vendor\StoreSwitcher\Model\ManageStoreCookie</item>
            </argument>
        </arguments>
    </type>
</config>

Vendor/StoreSwitcher/Model/ManageStoreCookie.php

<?php

declare(strict_types=1);

namespace Vendor\StoreSwitcher\Model;

use Magento\Store\Model\StoreSwitcherInterface;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Framework\App\Http\Context as HttpContext;
use Magento\Store\Api\StoreCookieManagerInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Store\Model\Store;

/**
 * Manage store cookie depending on what store view customer is.
 */
class ManageStoreCookie implements StoreSwitcherInterface
{
    /**
     * @var StoreCookieManagerInterface
     */
    private $storeCookieManager;

    /**
     * @var HttpContext
     */
    private $httpContext;

    /**
     * @var StoreManagerInterface
     */
    private $storeManager;

    /**
     * @param StoreCookieManagerInterface $storeCookieManager
     * @param HttpContext $httpContext
     * @param StoreManagerInterface $storeManager
     */
    public function __construct(
        StoreCookieManagerInterface $storeCookieManager,
        HttpContext $httpContext,
        StoreManagerInterface $storeManager
    ) {
        $this->storeCookieManager = $storeCookieManager;
        $this->httpContext = $httpContext;
        $this->storeManager = $storeManager;
    }

    /**
     * @param StoreInterface $fromStore store where we came from
     * @param StoreInterface $targetStore store where to go to
     * @param string $redirectUrl original url requested for redirect after switching
     * @return string redirect url
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function switch(StoreInterface $fromStore, StoreInterface $targetStore, string $redirectUrl): string
    {
        $defaultStoreView = $this->storeManager->getDefaultStoreView();
        if ($defaultStoreView !== null) {
            $this->httpContext->setValue(Store::ENTITY, $targetStore->getCode(), $defaultStoreView->getCode());
        }
        $this->storeCookieManager->setStoreCookie($targetStore);

        return $redirectUrl;
    }
}

The function getCurrentStoreId differs from original only in five lines, which are surrounded by comments. All logic is inside createLocaleCookie. We take code from $_SERVER variable and parse it. Basically it may have many languages and you may improve this logic. If first language of the list does not exist among store view codes - then take next and etc. Also in my example I put 'en' as default.

The other important thing is change workflow of original Store Cookie Manager Magento\Store\Model\StoreSwitcher\ManageStoreCookie. We do not need to delete cookie, but we need to always set cookie. It does not matter if it's default store or not.

That's it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.