diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 4de9e583dca5676f5bda4cf2b7af9fe24ea41abb..b9779195031342161f155990574b61e2c69dd731 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -18,6 +18,7 @@ $config->setRules([ 'strict_param' => true, 'declare_strict_types' => true, 'method_argument_space' => ['on_multiline' => 'ignore'], + 'phpdoc_to_comment' => false, ]) ->setRiskyAllowed(true) ->setFinder($finder); diff --git a/composer.json b/composer.json index 42a924cb6a3061d8f5c3643211eb07bacf3a8395..f54c7e207eb445ac4c688c257c5e767958605d9c 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "symfony/twig-bundle": "^5.4", "symfony/uid": "^5.4", "symfony/validator": "^5.4", - "symfony/yaml": "^5.4" + "symfony/yaml": "^5.4", + "ext-intl": "*" }, "require-dev": { "brainmaestro/composer-git-hooks": "^2.8.5", diff --git a/composer.lock b/composer.lock index fd00a981838c0f5ed372e482ddb1aaa383e9130c..b63c9186ae289b79e8c8351bf69061f418d2f14f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9c42ac8c7816cdf96cd4c2f000cabaf3", + "content-hash": "6ddd4889d724c8beabfe6b3b58bf35d7", "packages": [ { "name": "api-platform/core", @@ -9843,7 +9843,8 @@ "platform": { "php": ">=7.3", "ext-fileinfo": "*", - "ext-json": "*" + "ext-json": "*", + "ext-intl": "*" }, "platform-dev": [], "platform-overrides": { diff --git a/src/Locale/Locale.php b/src/Locale/Locale.php new file mode 100644 index 0000000000000000000000000000000000000000..c06bd95a66da260ba44978a8ce038167ab1ea87b --- /dev/null +++ b/src/Locale/Locale.php @@ -0,0 +1,89 @@ +<?php + +declare(strict_types=1); + +namespace Dbp\Relay\CoreBundle\Locale; + +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * A service which can be injected, which provides the current active language and allows setting the active + * language based on a query parameters. + * + * This assumes that Symfony is configured to apply the 'Accept-Language' header by default to all requests. + */ +class Locale +{ + /** @var RequestStack */ + private $requestStack; + + /** + * @var ParameterBagInterface + */ + private $parameters; + + public function __construct(RequestStack $requestStack, ParameterBagInterface $parameters) + { + $this->requestStack = $requestStack; + $this->parameters = $parameters; + } + + /** + * Returns the primary language (in ISO 639‑1 format) for the current context. + * In case there is a request then the request language, otherwise the default language. + */ + public function getCurrentPrimaryLanguage(): string + { + $locale = $this->getCurrentLocale(); + $lang = \Locale::getPrimaryLanguage($locale); + /** @psalm-suppress RedundantCondition */ + assert($lang !== null); + + return $lang; + } + + /** + * Sets the locale for the active request via a query parameter. + * The query parameter format is the same as the 'Accept-Language' HTTP header format. + * In case the query parameter isn't part of the request then nothing changes. + */ + public function setCurrentRequestLocaleFromQuery(string $queryParam = 'lang'): void + { + $request = $this->requestStack->getCurrentRequest(); + if ($request === null) { + throw new \RuntimeException('No active request'); + } + self::setRequestLocaleFromQuery($request, $queryParam); + } + + /** + * Returns the current locale, either from the active request, or the default one. + */ + private function getCurrentLocale(): string + { + $request = $this->requestStack->getCurrentRequest(); + if ($request !== null) { + $locale = $request->getLocale(); + } else { + $locale = $this->parameters->get('kernel.default_locale'); + assert(is_string($locale)); + } + + return $locale; + } + + private static function setRequestLocaleFromQuery(Request $request, string $queryParam): void + { + if ($request->query->has($queryParam)) { + $lang = $request->query->get($queryParam); + assert(is_string($lang)); + $locale = \Locale::acceptFromHttp($lang); + if ($locale === false) { + throw new \RuntimeException('Failed to parse Accept-Language'); + } + $request->setLocale($locale); + } + } +} diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index fef566f3e835fd5ff3bccce6ffb91d70a59ff8af..91df5c01c6fa1f70c8b75bea3972a639525ebb8c 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -100,3 +100,7 @@ services: Dbp\Relay\CoreBundle\Authorization\DebugCommand: autowire: true autoconfigure: true + + Dbp\Relay\CoreBundle\Locale\Locale: + autowire: true + autoconfigure: true diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e9f924c0fc5f302762f989efe7ad7547a8ba1016 --- /dev/null +++ b/tests/LocaleTest.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + +namespace Dbp\Relay\CoreBundle\Tests; + +use Dbp\Relay\CoreBundle\Locale\Locale; +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +class LocaleTest extends TestCase +{ + public function testWithRequest() + { + $stack = new RequestStack(); + $request = new Request(['lang' => 'de']); + + $request->setLocale(\Locale::acceptFromHttp('en')); + $stack->push($request); + $params = new ParameterBag([]); + $service = new Locale($stack, $params); + + $lang = $service->getCurrentPrimaryLanguage(); + $this->assertSame('en', $lang); + + $service->setCurrentRequestLocaleFromQuery('lang'); + $lang = $service->getCurrentPrimaryLanguage(); + $this->assertSame('de', $lang); + } + + public function testWithoutRequest() + { + $stack = new RequestStack(); + $params = new ParameterBag(['kernel.default_locale' => \Locale::acceptFromHttp('de')]); + $service = new Locale($stack, $params); + + $lang = $service->getCurrentPrimaryLanguage(); + $this->assertSame('de', $lang); + } +}