mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-25 13:35:32 +02:00
Merge pull request #3509 from shubhamsinnh/codex/fix-zh-tw-language-detection
Fix regional locale detection
This commit is contained in:
@@ -0,0 +1,74 @@
|
|||||||
|
import { assertEquals } from "@test/assert";
|
||||||
|
import { detectLocale } from "./detectLocale";
|
||||||
|
|
||||||
|
function runTests() {
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6"),
|
||||||
|
"zh-TW",
|
||||||
|
"An exact regional match should take precedence over a language fallback"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("ZH-tw"),
|
||||||
|
"zh-TW",
|
||||||
|
"Locale matching should be case-insensitive"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale(" zh-TW ; q=1 , zh-CN;q=0.8 "),
|
||||||
|
"zh-TW",
|
||||||
|
"Whitespace and quality parameters should not prevent an exact match"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("zh-CN,zh-TW;q=0.9"),
|
||||||
|
"zh-CN",
|
||||||
|
"Simplified Chinese should still match exactly"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("zh"),
|
||||||
|
"zh-CN",
|
||||||
|
"A generic Chinese preference should retain the existing fallback"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("en-GB,en;q=0.9"),
|
||||||
|
"en-US",
|
||||||
|
"An unsupported region should fall back to a supported locale for the language"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("ja-JP,zh-TW;q=0.9"),
|
||||||
|
"zh-TW",
|
||||||
|
"The next preference should be used when a language is unsupported"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("zh-CN;q=0.5,zh-TW;q=0.9"),
|
||||||
|
"zh-TW",
|
||||||
|
"Preferences should be evaluated by quality"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("zh-TW;q=0,fr-FR;q=0.8"),
|
||||||
|
"fr-FR",
|
||||||
|
"Locales with zero quality should be excluded"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("*,de-DE;q=0.8"),
|
||||||
|
"de-DE",
|
||||||
|
"A wildcard should not obscure a supported preference"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale("ja-JP"),
|
||||||
|
undefined,
|
||||||
|
"An unsupported language should not match"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
detectLocale(""),
|
||||||
|
undefined,
|
||||||
|
"An empty Accept-Language header should not match"
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("All locale detection tests passed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
runTests();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Locale detection test failed:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Locale, locales } from "./config";
|
||||||
|
|
||||||
|
export function detectLocale(acceptLanguage: string): Locale | undefined {
|
||||||
|
const browserLocales = acceptLanguage
|
||||||
|
.split(",")
|
||||||
|
.map((entry, index) => {
|
||||||
|
const [locale, ...parameters] = entry.trim().split(";");
|
||||||
|
const qualityParameter = parameters.find((parameter) =>
|
||||||
|
parameter.trim().toLowerCase().startsWith("q=")
|
||||||
|
);
|
||||||
|
const quality = qualityParameter
|
||||||
|
? Number(qualityParameter.trim().slice(2))
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale: locale.trim().toLowerCase(),
|
||||||
|
quality,
|
||||||
|
index
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(
|
||||||
|
({ locale, quality }) =>
|
||||||
|
locale && locale !== "*" && quality > 0 && quality <= 1
|
||||||
|
)
|
||||||
|
.sort(
|
||||||
|
(left, right) =>
|
||||||
|
right.quality - left.quality || left.index - right.index
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const { locale: browserLocale } of browserLocales) {
|
||||||
|
const exactMatch = locales.find(
|
||||||
|
(locale) => locale.toLowerCase() === browserLocale
|
||||||
|
);
|
||||||
|
if (exactMatch) {
|
||||||
|
return exactMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
const browserLanguage = browserLocale.split("-")[0];
|
||||||
|
const languageMatch = locales.find(
|
||||||
|
(locale) => locale.split("-")[0].toLowerCase() === browserLanguage
|
||||||
|
);
|
||||||
|
if (languageMatch) {
|
||||||
|
return languageMatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { cookies, headers } from "next/headers";
|
import { cookies, headers } from "next/headers";
|
||||||
import { Locale, defaultLocale, locales } from "@/i18n/config";
|
import { Locale, defaultLocale, locales } from "@/i18n/config";
|
||||||
|
import { detectLocale } from "@/i18n/detectLocale";
|
||||||
import { internal } from "@app/lib/api";
|
import { internal } from "@app/lib/api";
|
||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
|
|
||||||
@@ -47,12 +48,7 @@ export async function getUserLocale(): Promise<Locale> {
|
|||||||
const acceptLang = headerList.get("accept-language");
|
const acceptLang = headerList.get("accept-language");
|
||||||
|
|
||||||
if (acceptLang) {
|
if (acceptLang) {
|
||||||
const browserLang = acceptLang.split(",")[0];
|
const matched = detectLocale(acceptLang);
|
||||||
const matched = locales.find((locale) =>
|
|
||||||
browserLang
|
|
||||||
.toLowerCase()
|
|
||||||
.startsWith(locale.split("-")[0].toLowerCase())
|
|
||||||
);
|
|
||||||
if (matched) {
|
if (matched) {
|
||||||
return matched;
|
return matched;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user