[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', ] ] ] ];

TYPO3: Finding unused files in fileadmin

Do you want to delete unused or orphaned files in fileadmin or another storage location? Unfortunately, there's no direct core functionality for this. But a small command in your site package can...

Go to news

TYPO3: Editors with individual user_upload folders

Perhaps you're familiar with this client requirement? Editors should be able to add videos using the "Add media by URL" button. But the files shouldn't be located in fileadmin/user_upload/, but rather...

Go to news

TYPO3: Finding pages in mixed mode

In TYPO3, Mixed Mode refers to translated pages that contain content only partially related to the corresponding content in the main language. This is indicated in the backend by an error message. But...

Go to news

Extbase Extensions: Think extensibility with data, site and language

Today, I have a small request for the TYPO3 extension authors out there: Make sure your extensions are extensible. This will also promote the distribution of the corresponding plugins.

Go to news

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