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