diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php index 9c0eee00d5e3..4291256a2818 100644 --- a/system/HTTP/Negotiate.php +++ b/system/HTTP/Negotiate.php @@ -123,16 +123,16 @@ public function encoding(array $supported = []): string * types the application says it supports, and the types requested * by the client. * - * If loose locale negotiation is enabled and no match is found, the first, highest-ranking client requested + * If strict locale negotiation is disabled and no match is found, the first, highest-ranking client requested * type is returned. */ public function language(array $supported): string { - if (config(Feature::class)->looseLocaleNegotiation) { - return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-language'), false, false, true); + if (config(Feature::class)->strictLocaleNegotiation) { + return $this->getBestLocaleMatch($supported, $this->request->getHeaderLine('accept-language')); } - return $this->getBestLocaleMatch($supported, $this->request->getHeaderLine('accept-language')); + return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-language'), false, false, true); } // -------------------------------------------------------------------- @@ -195,19 +195,26 @@ protected function getBestMatch( } /** - * Strict locale search, including territories (en-*) + * Try to find the best matching locale. It supports strict locale comparison. * - * @param list $supported App-supported values - * @param ?string $header Compatible 'Accept-Language' header string + * If Config\App::$supportedLocales have "en-US" and "en-GB" locales, they can be recognized + * as two different locales. This method checks first for the strict match, then fallback + * to the most general locale (in this case "en") ISO 639-1 and finally to the locale variant + * "en-*" (ISO 639-1 plus "wildcard" for ISO 3166-1 alpha-2). + * + * If nothing from above is matched, then it returns the first option from the $supportedLocales array. + * + * @param list $supportedLocales App-supported values + * @param ?string $header Compatible 'Accept-Language' header string */ - protected function getBestLocaleMatch(array $supported, ?string $header): string + protected function getBestLocaleMatch(array $supportedLocales, ?string $header): string { - if ($supported === []) { + if ($supportedLocales === []) { throw HTTPException::forEmptySupportedNegotiations(); } if ($header === null || $header === '') { - return $supported[0]; + return $supportedLocales[0]; } $acceptable = $this->parseHeader($header); @@ -221,11 +228,11 @@ protected function getBestLocaleMatch(array $supported, ?string $header): string // if acceptable value is "anything", return the first available if ($accept['value'] === '*' || $accept['value'] === '*/*') { - return $supported[0]; + return $supportedLocales[0]; } // look for exact match - if (in_array($accept['value'], $supported, true)) { + if (in_array($accept['value'], $supportedLocales, true)) { return $accept['value']; } @@ -235,19 +242,19 @@ protected function getBestLocaleMatch(array $supported, ?string $header): string foreach ($fallbackLocales as $fallbackLocale) { // look for exact match - if (in_array($fallbackLocale, $supported, true)) { + if (in_array($fallbackLocale, $supportedLocales, true)) { return $fallbackLocale; } - // look for locale variants match - foreach ($supported as $locale) { + // look for regional locale match + foreach ($supportedLocales as $locale) { if (str_starts_with($locale, $fallbackLocale . '-')) { return $locale; } } } - return $supported[0]; + return $supportedLocales[0]; } /** diff --git a/tests/system/HTTP/NegotiateTest.php b/tests/system/HTTP/NegotiateTest.php index 54fbfe25ffc2..a2731ec5ddbf 100644 --- a/tests/system/HTTP/NegotiateTest.php +++ b/tests/system/HTTP/NegotiateTest.php @@ -122,7 +122,7 @@ public function testAcceptLanguageBasics(): void $this->assertSame('en-us', $this->negotiate->language(['en-us', 'en-gb', 'en'])); $this->assertSame('en', $this->negotiate->language(['en', 'en-us', 'en-gb'])); - config(Feature::class)->looseLocaleNegotiation = false; + config(Feature::class)->strictLocaleNegotiation = true; $this->assertSame('da', $this->negotiate->language(['da', 'en'])); $this->assertSame('en-gb', $this->negotiate->language(['en-gb', 'en'])); @@ -144,7 +144,7 @@ public function testAcceptLanguageMatchesBroadly(): void $this->assertSame('en', $this->negotiate->language(['en', 'en-US'])); $this->assertSame('fr-BE', $this->negotiate->language(['ru', 'en-GB', 'fr-BE'])); - config(Feature::class)->looseLocaleNegotiation = false; + config(Feature::class)->strictLocaleNegotiation = true; $this->assertSame('fr-FR', $this->negotiate->language(['fr', 'fr-FR', 'en'])); $this->assertSame('fr-FR', $this->negotiate->language(['fr-FR', 'fr', 'en']));