diff --git a/.changeset/fix-preferred-locale-quality-sort.md b/.changeset/fix-preferred-locale-quality-sort.md new file mode 100644 index 000000000000..a675a66296ad --- /dev/null +++ b/.changeset/fix-preferred-locale-quality-sort.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes `Astro.preferredLocale` and `Astro.preferredLocaleList` ignoring `Accept-Language` quality values when they are absent or `0`. An entry without an explicit `q=` now correctly counts as quality `1.0` (per RFC 7231) and an entry with `q=0` is treated as not acceptable, so the highest-quality locale is selected regardless of header order. diff --git a/packages/astro/src/i18n/utils.ts b/packages/astro/src/i18n/utils.ts index dc816d2b2759..55a3d380a9d1 100644 --- a/packages/astro/src/i18n/utils.ts +++ b/packages/astro/src/i18n/utils.ts @@ -71,10 +71,11 @@ function sortAndFilterLocales(browserLocaleList: BrowserLocale[], locales: Local return true; }) .sort((a, b) => { - if (a.qualityValue && b.qualityValue) { - return Math.sign(b.qualityValue - a.qualityValue); - } - return 0; + // An absent `q=` means quality 1.0 (RFC 7231), while a bare `*` defaults + // to 0 so it is never ranked above a real locale. + const qa = a.locale === '*' ? (a.qualityValue ?? 0) : (a.qualityValue ?? 1); + const qb = b.locale === '*' ? (b.qualityValue ?? 0) : (b.qualityValue ?? 1); + return qb - qa; }); } diff --git a/packages/astro/test/units/i18n/i18n-utils.test.ts b/packages/astro/test/units/i18n/i18n-utils.test.ts index dfcfa6e49761..3e42b861eb76 100644 --- a/packages/astro/test/units/i18n/i18n-utils.test.ts +++ b/packages/astro/test/units/i18n/i18n-utils.test.ts @@ -67,6 +67,20 @@ describe('computePreferredLocale', () => { assert.equal(computePreferredLocale(req, locales), 'fr'); }); + it('prefers an implicit q=1 entry over a lower explicit-q entry regardless of header order', () => { + const req = new Request('http://example.com/', { + headers: { 'Accept-Language': 'en;q=0.7, de' }, + }); + assert.equal(computePreferredLocale(req, ['en', 'fr', 'de']), 'de'); + }); + + it('excludes a q=0 entry from winning', () => { + const req = new Request('http://example.com/', { + headers: { 'Accept-Language': 'de;q=0, en;q=0.5' }, + }); + assert.equal(computePreferredLocale(req, ['en', 'fr', 'de']), 'en'); + }); + it('returns undefined when no match', () => { const req = new Request('http://example.com/', { headers: { 'Accept-Language': 'de,ja' },