diff --git a/src/LocalesResolvers/Header.php b/src/LocalesResolvers/Header.php index a0d65d8..8c74286 100644 --- a/src/LocalesResolvers/Header.php +++ b/src/LocalesResolvers/Header.php @@ -2,28 +2,19 @@ namespace Contributte\Translation\LocalesResolvers; -use Contributte\Translation\Exceptions\InvalidArgument; use Contributte\Translation\Translator; use Nette\Http\IRequest; -use Nette\Http\Request; use Nette\Utils\Strings; class Header implements ResolverInterface { - private Request $httpRequest; + private IRequest $httpRequest; - /** - * @throws \Contributte\Translation\Exceptions\InvalidArgument - */ public function __construct( IRequest $httpRequest ) { - if (!is_a($httpRequest, Request::class, true)) { - throw new InvalidArgument('Header locale resolver need "Nette\\Http\\Request" or his child for using "detectLanguage" method.'); - } - $this->httpRequest = $httpRequest; } @@ -31,24 +22,61 @@ public function resolve( Translator $translator ): ?string { - /** @var array $langs */ - $langs = []; + /** @var array $locales */ + $locales = []; foreach ($translator->getAvailableLocales() as $v1) { - $langs[] = $v1; + $locales[] = $v1; if (Strings::length($v1) < 3) { continue; } - $langs[] = Strings::substring($v1, 0, 2);// en_US => en + $locales[] = Strings::substring($v1, 0, 2);// en_US => en } - if (count($langs) === 0) { + if (count($locales) === 0) { return null; } - return $this->httpRequest->detectLanguage($langs); + return $this->detectLocale($locales); + } + + /** + * This function is a copy of \Nette\Http\Request::detectLanguage() + * Returns the most preferred locale by browser. Uses the `Accept-Language` header. If no match is reached, it returns `null`. + * + * @param array $supportedLocales + */ + private function detectLocale(array $supportedLocales): ?string + { + $header = $this->httpRequest->getHeader('Accept-Language'); + if ($header === null) { + return null; + } + + rsort($supportedLocales); // first more specific + preg_match_all( + '#(' . implode('|', $supportedLocales) . ')(?:-[^\s,;=]+)?\s*(?:;\s*q=([0-9.]+))?#', + strtr(strtolower($header), '_', '-'), // CS_CZ -> cs-CZ + $matches + ); + + if (count($matches) === 0) { + return null; + } + + $max = 0; + $locale = null; + foreach ($matches[1] as $key => $value) { + $q = $matches[2][$key] === '' ? 1.0 : (float) $matches[2][$key]; + if ($q > $max) { + $max = $q; + $locale = $value; + } + } + + return $locale; } } diff --git a/tests/Tests/LocalesResolvers/HeaderTest.phpt b/tests/Tests/LocalesResolvers/HeaderTest.phpt index 4a44e2b..d932539 100644 --- a/tests/Tests/LocalesResolvers/HeaderTest.phpt +++ b/tests/Tests/LocalesResolvers/HeaderTest.phpt @@ -2,13 +2,10 @@ namespace Tests\LocalesResolvers; -use Contributte\Translation\Exceptions\InvalidArgument; use Contributte\Translation\LocalesResolvers\Header; use Contributte\Translation\Translator; use Mockery; -use Nette\Http\IRequest; use Nette\Http\Request; -use Nette\Http\UrlImmutable; use Nette\Http\UrlScript; use Tester\Assert; use Tests\TestAbstract; @@ -31,137 +28,6 @@ final class HeaderTest extends TestAbstract Assert::same('en-us', $this->resolve('da, en_us', ['en', 'en-us'])); } - public function test02(): void - { - Assert::exception(static function (): void { - new Header(new class implements IRequest { - - public function getReferer(): ?UrlImmutable - { - return null; - } - - public function isSameSite(): bool - { - return true; - } - - public function getUrl(): UrlScript - { - return new UrlScript(); - } - - /** - * @return mixed - */ - public function getQuery( - ?string $key = null - ) - { - return null; - } - - /** - * @return mixed - */ - public function getPost( - ?string $key = null - ) - { - return null; - } - - /** - * @return mixed - */ - public function getFile( - string $key - ) - { - return null; - } - - /** - * @return array<\Nette\Http\FileUpload> - */ - public function getFiles(): array - { - return []; - } - - /** - * @return mixed - */ - public function getCookie( - string $key - ) - { - return null; - } - - /** - * @return array - */ - public function getCookies(): array - { - return []; - } - - public function getMethod(): string - { - return ''; - } - - public function isMethod( - string $method - ): bool - { - return true; - } - public function getHeader( - string $header - ): ?string - { - return null; - } - - /** - * @return array - */ - public function getHeaders(): array - { - return []; - } - - public function isSecured(): bool - { - return true; - } - - public function isAjax(): bool - { - return true; - } - - public function getRemoteAddress(): ?string - { - return null; - } - - public function getRemoteHost(): ?string - { - return null; - } - - public function getRawBody(): ?string - { - return null; - } - - }); - }, InvalidArgument::class, 'Header locale resolver need "' . Request::class . '" or his child for using "detectLanguage" method.'); - } - /** * @param array $availableLocales */