For coders TYPO3 Tech Corner

[PHP] Redirect to the best fitting language based on the browserlanguage

[PHP] Redirect to the best fitting language based on the browserlanguage

Do you want to automatically redirect your visitor to the desired language? This is very easy in newer TYPO3 projects with PSR-15 middleware.

The codezero/browser-locale package helps us to identify the visitor's browser language. You can load this e.g. via composer.json:

{ "name": "in2code/in2template", "description": "Sitepackage extension", "type": "typo3-cms-extension", "homepage": "https://www.in2code.de", "require": { "typo3/cms-core": "^10.4", "codezero/browser-locale": "^3.0" }, "autoload": { "psr-4": { "In2code\\In2template\\": "Classes/" } } }

Logic is based in LanguageRedirect.php for the redirect:

<?php declare(strict_types=1); namespace In2code\In2template\Middleware; use CodeZero\BrowserLocale\BrowserLocale; use CodeZero\BrowserLocale\Filters\LanguageFilter; use In2code\In2template\Utility\ObjectUtility; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use TYPO3\CMS\Core\Http\RedirectResponse; use TYPO3\CMS\Core\Routing\PageArguments; use TYPO3\CMS\Core\Site\Entity\Site; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Object\Exception; /** * Class LanguageRedirect * to redirect users to additional language of home site for browsers that do not support default language */ class LanguageRedirect implements MiddlewareInterface { /** * @var ServerRequestInterface */ protected $request = null; /** * @param ServerRequestInterface $request * @param RequestHandlerInterface $handler * @return ResponseInterface * @throws Exception */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $this->request = $request; if ($this->isRedirectNeeded()) { return $this->redirectToSecondaryLanguage(); } return $handler->handle($request); } /** * Redirect only if: * - Home page * - Referrer is not current domain (to enable a language switch on the website) * - If is default language * - Is no bot (do not redirect if a bot is watching your home page) * - Browser does not support default language * - Is additional language existing * * @return bool */ protected function isRedirectNeeded(): bool { return $this->isHomepage() && $this->isExternalReferrer() && $this->isDefaultLanguage() && $this->isNoBot() && $this->isDefaultLanguageNotSupportedInVisitorBrowser() && $this->isAdditionalLanguageExisting(); } /** * @return bool */ protected function isNoBot(): bool { $bots = [ 'google', 'bingbot', 'slurp', 'duckduck', 'baidu', 'yandex', 'sogou', 'exabot', 'lighthouse' ]; $userAgent = GeneralUtility::getIndpEnv('HTTP_USER_AGENT'); if (empty($userAgent)) { // no useragent - crawler, caretaker or bot? return false; } foreach ($bots as $bot) { if (stristr($userAgent, $bot) !== false) { return false; } } return true; } /** * @return ResponseInterface * @throws Exception */ protected function redirectToSecondaryLanguage(): ResponseInterface { $configuration = [ 'parameter' => $this->getHomePageIdentifier(), 'language' => $this->getSecondaryLanguageIdentifier(), 'forceAbsoluteUrl' => true ]; $url = ObjectUtility::getContentObject()->typoLink_URL($configuration); return new RedirectResponse($url, 302); } /** * @return bool */ protected function isHomepage(): bool { /** @var PageArguments $pageArguments */ $pageArguments = $this->request->getAttribute('routing'); return $pageArguments->getPageId() === $this->getHomePageIdentifier(); } /** * Check if referrer is not within current domain (also empty referrer is used) * * @return bool */ protected function isExternalReferrer(): bool { $referrer = GeneralUtility::getIndpEnv('HTTP_REFERER'); $domain = GeneralUtility::getIndpEnv('HTTP_HOST'); return stristr($referrer, $domain) === false; } /** * @return bool */ protected function isDefaultLanguageNotSupportedInVisitorBrowser(): bool { try { $browser = new BrowserLocale(GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE')); $languages = $browser->filter(new LanguageFilter()); return in_array($this->getDefaultLanguageCode(), $languages) === false; } catch (\Exception $exception) { return false; } } /** * @return bool */ protected function isAdditionalLanguageExisting(): bool { $languages = $this->request->getAttribute('site')->getLanguages(); return count($languages) > 1; } /** * @return bool */ protected function isDefaultLanguage(): bool { /** @var SiteLanguage $language */ $language = $this->request->getAttribute('language'); return $language->getLanguageId() === $this->getDefaultLanguageIdentifier(); } /** * Return language code (like "de") from default language configuration in site configuration * * @return string */ protected function getDefaultLanguageCode(): string { /** @var SiteLanguage $defaultLanguage */ $defaultLanguage = $this->request->getAttribute('site')->getDefaultLanguage(); return $defaultLanguage->getTwoLetterIsoCode(); } /** * @return int */ protected function getDefaultLanguageIdentifier(): int { /** @var SiteLanguage $defaultLanguage */ $defaultLanguage = $this->request->getAttribute('site')->getDefaultLanguage(); return $defaultLanguage->getLanguageId(); } /** * Get secondary language id from site configuration * * @return int */ protected function getSecondaryLanguageIdentifier(): int { $languages = $this->request->getAttribute('site')->getLanguages(); if (count($languages) > 1) { /** @var SiteLanguage $secondaryLanguage */ $secondaryLanguage = $languages[1]; return $secondaryLanguage->getLanguageId(); } return 0; } /** * Get page id of the homepage from site configuration * * @return int */ protected function getHomePageIdentifier(): int { /** @var Site $site */ $site = $this->request->getAttribute('site'); return $site->getRootPageId(); } }

RequestMiddlewares.php:

<?php return [ 'frontend' => [ 'in2template-languageredirect' => [ 'target' => \In2code\In2template\Middleware\LanguageRedirect::class, 'after' => [ 'typo3/cms-frontend/tsfe', 'typo3/cms-frontend/page-resolver', ] ] ] ];

"Code faster, look at the time" - does this sound familiar to you?

How about time and respect for code quality? Working in a team? Automated tests?

Join us

SQL: Show all tables sorted by size in descending order

Lately I've been using the SQL command more often to find out which tables in the TYPO3 database are the largest. I've published the snippet once.

Go to news

TYPO3 12 with CKEditor 5: Styles in a single selection

If you set a link in the RTE in TYPO3, you may have to choose between different link classes, for example to create buttons in the frontend. What's new in TYPO3 12 is that you can select not just one...

Go to news

Null-Safe Operator in the TYPO3 area

With the introduction of PHP8, problems with undefined arrays or variables in general can arise in many places. Here are a few examples and simple solutions.

Go to news

Delete the first/last lines of a (SQL) file

There isn't much to say about the following commands. Sometimes it can be useful to delete the first (or last) X lines from a file. And if the file is too large to open with a conventional program, a...

Go to news

b13/container: Add and modify child elements in edit view

Unlike gridelements, you cannot manage the child elements in the B13 Container extension when you open the container in the editing view. I would be happy to show you how you can quickly install this...

Go to news

Menu comparison: Numbers, numbers, numbers

Go to news