refactor(accept-language): prefer the highest quality language

This commit is contained in:
Nicolas Meienberger 2026-04-04 13:16:32 +02:00
parent 0a4768df6b
commit 598307263c
2 changed files with 41 additions and 5 deletions

View file

@ -2,10 +2,18 @@ import { describe, expect, test } from "vitest";
import { getLocaleFromAcceptLanguage } from "~/server/lib/accept-language"; import { getLocaleFromAcceptLanguage } from "~/server/lib/accept-language";
describe("getLocaleFromAcceptLanguage", () => { describe("getLocaleFromAcceptLanguage", () => {
test("strips quality values from the preferred language", () => { test("strips quality values from a single preferred language", () => {
expect(getLocaleFromAcceptLanguage("en;q=0.5")).toBe("en"); expect(getLocaleFromAcceptLanguage("en;q=0.5")).toBe("en");
}); });
test("prefers the highest quality language", () => {
expect(getLocaleFromAcceptLanguage("en;q=0.5, fr;q=0.9")).toBe("fr");
});
test("treats language tags without q as the highest priority", () => {
expect(getLocaleFromAcceptLanguage("en;q=0.9, fr-CH")).toBe("fr-CH");
});
test("skips invalid language tags and uses the next valid one", () => { test("skips invalid language tags and uses the next valid one", () => {
expect(getLocaleFromAcceptLanguage("*, en-GB;q=0.8")).toBe("en-GB"); expect(getLocaleFromAcceptLanguage("*, en-GB;q=0.8")).toBe("en-GB");
}); });

View file

@ -5,20 +5,48 @@ export function getLocaleFromAcceptLanguage(acceptLanguage?: string | null) {
return DEFAULT_LOCALE; return DEFAULT_LOCALE;
} }
let locale = DEFAULT_LOCALE;
let highestQuality = -1;
for (const value of acceptLanguage.split(",")) { for (const value of acceptLanguage.split(",")) {
const languageTag = value.split(";")[0]?.trim(); const [rawLanguageTag, ...parameters] = value.split(";");
const languageTag = rawLanguageTag?.trim();
if (!languageTag) { if (!languageTag) {
continue; continue;
} }
let quality = 1;
for (const parameter of parameters) {
const [key, value] = parameter.split("=");
if (key?.trim().toLowerCase() !== "q") {
continue;
}
const parsedQuality = Number(value?.trim());
if (!Number.isFinite(parsedQuality) || parsedQuality < 0 || parsedQuality > 1) {
quality = -1;
break;
}
quality = parsedQuality;
break;
}
if (quality <= highestQuality) {
continue;
}
try { try {
Intl.getCanonicalLocales(languageTag); locale = Intl.getCanonicalLocales(languageTag)[0] || DEFAULT_LOCALE;
return languageTag; highestQuality = quality;
} catch { } catch {
continue; continue;
} }
} }
return DEFAULT_LOCALE; return locale;
} }