mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
[UPMERGE] 1.12 -> 1.13 (#15674)
This PR has been generated automatically. For more details see [upmerge_pr.yaml](/Sylius/Sylius/blob/1.13/.github/workflows/upmerge_pr.yaml). **Remember!** The upmerge should always be merged with using `Merge pull request` button. In case of conflicts, please resolve them manually with usign the following commands: ``` git fetch upstream gh pr checkout git merge upstream/1.13 --no-commit ``` If you use other name for the upstream remote, please replace `upstream` with the name of your remote pointing to the `Sylius/Sylius` repository. Once the conflicts are resolved, please run `git merge --continue` and change the commit title to ``` Resolve conflicts between 1.12 and 1.13 ```
This commit is contained in:
commit
502c817630
8 changed files with 128 additions and 39 deletions
|
|
@ -1,3 +1,8 @@
|
|||
# UPGRADING FROM `v1.12.13` TO `v1.12.14`
|
||||
|
||||
1. The `Accept-Language` header should now correctly resolve locale codes based on RFC 4647 using Symfony's request language negotiation,
|
||||
meaning that values `en_US`, `en-US`, `en-us` etc. will all result in the `en_US` locale.
|
||||
|
||||
# UPGRADE FROM `v1.12.11` TO `v1.12.12`
|
||||
|
||||
1. The `Sylius\Component\User\Model\UserInterface` extends the `Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface`
|
||||
|
|
|
|||
|
|
@ -2,8 +2,13 @@ Translations
|
|||
============
|
||||
|
||||
In the shop part of our API translatable entities are translated at the server-side and users get an already translated field in the response.
|
||||
By default it will be provided in default locale configured on the channel, different locale must be configured in the header (for example `accept-language: de_DE`)
|
||||
By default it will be provided in default locale configured on the channel, different locale must be configured in the header (e.g. `Accept-Language: de-DE`)
|
||||
|
||||
.. warning::
|
||||
|
||||
Remember that the chosen locale has to be enabled in the channel!
|
||||
|
||||
.. note::
|
||||
|
||||
Though Sylius' locale code format (e.g. `de_DE`) will also be accepted in `Accept-Language` header, it's not valid value according to HTTP specification.
|
||||
Also, it silently changes CORS behavior - results with CORS restrictions because the "_" character is not considered safe for this header type (https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header).
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ Third scenario::
|
|||
Then the cart should be empty
|
||||
|
||||
.. note::
|
||||
The cart mentioned in the last scenario will we available once you log in again.
|
||||
The cart mentioned in the last scenario will be available once you log in again.
|
||||
|
||||
Learn more
|
||||
----------
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Feature: Picking up the cart with the locale other than the default
|
|||
|
||||
@api
|
||||
Scenario: Picking up the cart without specified locale
|
||||
When I pick up cart
|
||||
When I pick up cart without specifying locale
|
||||
And I check details of my cart
|
||||
Then my cart's locale should be "French (France)"
|
||||
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ final class CartContext implements Context
|
|||
/**
|
||||
* @When I pick up (my )cart (again)
|
||||
* @When I pick up cart in the :localeCode locale
|
||||
* @When I pick up cart without specifying locale
|
||||
* @When the visitor picks up the cart
|
||||
*/
|
||||
public function iPickUpMyCart(?string $localeCode = null): void
|
||||
|
|
@ -812,8 +813,9 @@ final class CartContext implements Context
|
|||
$request = $this->requestFactory->custom(
|
||||
sprintf('%s/shop/orders', $this->apiUrlPrefix),
|
||||
HttpRequest::METHOD_POST,
|
||||
$localeCode ? ['HTTP_ACCEPT_LANGUAGE' => $localeCode] : [],
|
||||
['HTTP_ACCEPT_LANGUAGE' => $localeCode ?? ''],
|
||||
);
|
||||
|
||||
$this->shopClient->executeCustomRequest($request);
|
||||
|
||||
$tokenValue = $this->responseChecker->getValue($this->shopClient->getLastResponse(), 'tokenValue');
|
||||
|
|
|
|||
|
|
@ -18,8 +18,18 @@ use Sylius\Component\Locale\Context\LocaleNotFoundException;
|
|||
use Sylius\Component\Locale\Provider\LocaleProviderInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Locale context implementation based on Symfony Request's language negotiation (RFC 4647 based).
|
||||
*
|
||||
* @see Request::getPreferredLanguage()
|
||||
*/
|
||||
final class RequestHeaderBasedLocaleContext implements LocaleContextInterface
|
||||
{
|
||||
private const NO_CODE_VALID_STUB = 'NO_CODE_VALID_STUB';
|
||||
|
||||
/** @var array<array-key, string> $availableLocalesCodes */
|
||||
private array $availableLocalesCodes = [];
|
||||
|
||||
public function __construct(private RequestStack $requestStack, private LocaleProviderInterface $localeProvider)
|
||||
{
|
||||
}
|
||||
|
|
@ -31,16 +41,25 @@ final class RequestHeaderBasedLocaleContext implements LocaleContextInterface
|
|||
throw new LocaleNotFoundException('No main request available.');
|
||||
}
|
||||
|
||||
$localeCode = $request->headers->get('Accept-Language');
|
||||
if (null === $localeCode) {
|
||||
throw new LocaleNotFoundException('No locale is set on the master request\'s headers.');
|
||||
if ([] === $this->availableLocalesCodes) {
|
||||
$this->availableLocalesCodes = array_unique(array_merge(
|
||||
[$this->localeProvider->getDefaultLocaleCode()],
|
||||
$this->localeProvider->getAvailableLocalesCodes()
|
||||
));
|
||||
}
|
||||
|
||||
$availableLocalesCodes = $this->localeProvider->getAvailableLocalesCodes();
|
||||
if (!in_array($localeCode, $availableLocalesCodes, true)) {
|
||||
throw LocaleNotFoundException::notAvailable($localeCode, $availableLocalesCodes);
|
||||
// Request::getPreferredLanguage() returns first available locale code if none matches. To allow detection of
|
||||
// this unwanted behavior, we will prepend special locale code to the list of available locale codes.
|
||||
$prependedAvailableLocalesCodes = array_merge([self::NO_CODE_VALID_STUB], $this->availableLocalesCodes);
|
||||
|
||||
$bestLocaleCode = $request->getPreferredLanguage($prependedAvailableLocalesCodes);
|
||||
if (self::NO_CODE_VALID_STUB === $bestLocaleCode) {
|
||||
throw new LocaleNotFoundException(sprintf(
|
||||
'None of the available locales is acceptable: "%s".',
|
||||
implode('", "', $this->availableLocalesCodes),
|
||||
));
|
||||
}
|
||||
|
||||
return $localeCode;
|
||||
return $bestLocaleCode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ use PhpSpec\ObjectBehavior;
|
|||
use Sylius\Component\Locale\Context\LocaleContextInterface;
|
||||
use Sylius\Component\Locale\Context\LocaleNotFoundException;
|
||||
use Sylius\Component\Locale\Provider\LocaleProviderInterface;
|
||||
use Symfony\Component\HttpFoundation\HeaderBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
|
|
@ -40,42 +39,78 @@ final class RequestHeaderBasedLocaleContextSpec extends ObjectBehavior
|
|||
$this->shouldThrow(LocaleNotFoundException::class)->during('getLocaleCode');
|
||||
}
|
||||
|
||||
function it_throws_locale_not_found_exception_if_main_request_does_not_have_accept_language_in_header(
|
||||
RequestStack $requestStack,
|
||||
Request $request,
|
||||
): void {
|
||||
$requestStack->getMainRequest()->willReturn($request);
|
||||
|
||||
$request->headers = new HeaderBag();
|
||||
|
||||
$this->shouldThrow(LocaleNotFoundException::class)->during('getLocaleCode');
|
||||
}
|
||||
|
||||
function it_throws_locale_not_found_exception_if_main_request_locale_code_is_not_among_available_ones(
|
||||
function it_throws_locale_not_found_exception_if_locale_from_main_request_preferred_language_cannot_be_resolved(
|
||||
RequestStack $requestStack,
|
||||
LocaleProviderInterface $localeProvider,
|
||||
Request $request,
|
||||
): void {
|
||||
$request = new Request();
|
||||
$request->headers->set('Accept-Language', 'fr_FR');
|
||||
|
||||
$requestStack->getMainRequest()->willReturn($request);
|
||||
|
||||
$request->headers = new HeaderBag(['ACCEPT_LANGUAGE' => 'en_US']);
|
||||
|
||||
$localeProvider->getDefaultLocaleCode()->willReturn('pl_PL');
|
||||
$localeProvider->getAvailableLocalesCodes()->willReturn(['pl_PL', 'de_DE']);
|
||||
|
||||
$this->shouldThrow(LocaleNotFoundException::class)->during('getLocaleCode');
|
||||
}
|
||||
|
||||
function it_returns_main_request_locale_code(
|
||||
function it_resolves_locale_from_main_request_preferred_language_in_locale_syntax(
|
||||
RequestStack $requestStack,
|
||||
LocaleProviderInterface $localeProvider,
|
||||
Request $request,
|
||||
): void {
|
||||
$request = new Request();
|
||||
$request->headers->set('Accept-Language', 'de_DE');
|
||||
|
||||
$requestStack->getMainRequest()->willReturn($request);
|
||||
|
||||
$request->headers = new HeaderBag(['Accept-Language' => 'pl_PL']);
|
||||
|
||||
$localeProvider->getDefaultLocaleCode()->willReturn('pl_PL');
|
||||
$localeProvider->getAvailableLocalesCodes()->willReturn(['pl_PL', 'de_DE']);
|
||||
|
||||
$this->getLocaleCode()->shouldReturn('pl_PL');
|
||||
$this->getLocaleCode()->shouldReturn('de_DE');
|
||||
}
|
||||
|
||||
function it_resolves_locale_from_main_request_preferred_language_in_mixed_cased_language_syntax(
|
||||
RequestStack $requestStack,
|
||||
LocaleProviderInterface $localeProvider,
|
||||
): void {
|
||||
$request = new Request();
|
||||
$request->headers->set('Accept-Language', 'dE-De');
|
||||
|
||||
$requestStack->getMainRequest()->willReturn($request);
|
||||
|
||||
$localeProvider->getDefaultLocaleCode()->willReturn('pl_PL');
|
||||
$localeProvider->getAvailableLocalesCodes()->willReturn(['pl_PL', 'de_DE']);
|
||||
|
||||
$this->getLocaleCode()->shouldReturn('de_DE');
|
||||
}
|
||||
|
||||
function it_resolves_locale_from_main_request_preferred_language_in_upper_cased_language_syntax(
|
||||
RequestStack $requestStack,
|
||||
LocaleProviderInterface $localeProvider,
|
||||
): void {
|
||||
$request = new Request();
|
||||
$request->headers->set('Accept-Language', 'DE-DE');
|
||||
|
||||
$requestStack->getMainRequest()->willReturn($request);
|
||||
|
||||
$localeProvider->getDefaultLocaleCode()->willReturn('pl_PL');
|
||||
$localeProvider->getAvailableLocalesCodes()->willReturn(['pl_PL', 'de_DE']);
|
||||
|
||||
$this->getLocaleCode()->shouldReturn('de_DE');
|
||||
}
|
||||
|
||||
function it_resolves_locale_from_main_request_preferred_language_in_lower_cased_language_syntax(
|
||||
RequestStack $requestStack,
|
||||
LocaleProviderInterface $localeProvider,
|
||||
): void {
|
||||
$request = new Request();
|
||||
$request->headers->set('Accept-Language', 'de-de');
|
||||
|
||||
$requestStack->getMainRequest()->willReturn($request);
|
||||
|
||||
$localeProvider->getDefaultLocaleCode()->willReturn('pl_PL');
|
||||
$localeProvider->getAvailableLocalesCodes()->willReturn(['pl_PL', 'de_DE']);
|
||||
|
||||
$this->getLocaleCode()->shouldReturn('de_DE');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,8 +55,11 @@ final class ProductsTest extends JsonApiTestCase
|
|||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_product_with_translations_in_locale_from_header(): void
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getGermanLocales
|
||||
*/
|
||||
public function it_returns_product_with_translations_in_locale_from_header(string $germanLocale): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFile('product/product_with_many_locales.yaml');
|
||||
|
||||
|
|
@ -68,7 +71,7 @@ final class ProductsTest extends JsonApiTestCase
|
|||
server: [
|
||||
'CONTENT_TYPE' => 'application/ld+json',
|
||||
'HTTP_ACCEPT' => 'application/ld+json',
|
||||
'HTTP_ACCEPT_LANGUAGE' => 'de_DE',
|
||||
'HTTP_ACCEPT_LANGUAGE' => $germanLocale,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
@ -133,15 +136,19 @@ final class ProductsTest extends JsonApiTestCase
|
|||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_product_attributes_collection_with_translations_in_locale_from_header(): void
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getPolishLocales
|
||||
*/
|
||||
public function it_returns_product_attributes_collection_with_translations_in_locale_from_header(
|
||||
string $polishLocale,
|
||||
): void {
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'product/product_attribute.yaml']);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/shop/products/%s/attributes', 'MUG_SW'),
|
||||
server: array_merge(self::CONTENT_TYPE_HEADER, ['HTTP_ACCEPT_LANGUAGE' => 'pl_PL']),
|
||||
server: array_merge(self::CONTENT_TYPE_HEADER, ['HTTP_ACCEPT_LANGUAGE' => $polishLocale]),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
|
|
@ -164,4 +171,20 @@ final class ProductsTest extends JsonApiTestCase
|
|||
|
||||
$this->assertCount(2, json_decode($this->client->getResponse()->getContent(), true)['hydra:member']);
|
||||
}
|
||||
|
||||
public function getGermanLocales(): iterable
|
||||
{
|
||||
yield ['de_DE']; // Locale code syntax
|
||||
yield ['de-DE']; // RFC 4647 and RFC 3066
|
||||
yield ['DE-DE']; // RFC 4647 and RFC 3066
|
||||
yield ['de-de']; // RFC 4647 and RFC 3066
|
||||
}
|
||||
|
||||
public function getPolishLocales(): iterable
|
||||
{
|
||||
yield ['pl_PL']; // Locale code syntax
|
||||
yield ['pl-PL']; // RFC 4647 and RFC 3066
|
||||
yield ['PL-PL']; // RFC 4647 and RFC 3066
|
||||
yield ['pl-pl']; // RFC 4647 and RFC 3066
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue