Hide the language switcher for English-only browsers; gate /zh via Accept-Language and a client cookie. Co-authored-by: Cursor <cursoragent@cursor.com>
98 lines
2.7 KiB
TypeScript
98 lines
2.7 KiB
TypeScript
/**
|
|
* Detect whether the environment has Chinese language signals.
|
|
* Used to show/hide the 中文 UI option — English-only otherwise.
|
|
*/
|
|
|
|
const ZH_TAG = /^(zh|yue|cmn)([-_]|$)/i;
|
|
export const ZH_OK_COOKIE = "cyd-zh-ok";
|
|
|
|
export function isChineseLanguageTag(tag: string | null | undefined): boolean {
|
|
if (!tag) return false;
|
|
const primary = tag.trim().replace("_", "-");
|
|
return ZH_TAG.test(primary);
|
|
}
|
|
|
|
/** Parse Accept-Language / comma-separated locale list. */
|
|
export function acceptLanguageHasChinese(
|
|
header: string | null | undefined
|
|
): boolean {
|
|
if (!header) return false;
|
|
return header
|
|
.split(",")
|
|
.map((part) => part.split(";")[0]?.trim())
|
|
.some((tag) => isChineseLanguageTag(tag));
|
|
}
|
|
|
|
export type ChineseSignal =
|
|
| "navigator.language"
|
|
| "navigator.languages"
|
|
| "intl.locale"
|
|
| "accept-language"
|
|
| "cookie";
|
|
|
|
export type ChineseDetection = {
|
|
likely: boolean;
|
|
signals: ChineseSignal[];
|
|
};
|
|
|
|
function readCookie(name: string): string | null {
|
|
if (typeof document === "undefined") return null;
|
|
const match = document.cookie.match(
|
|
new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`)
|
|
);
|
|
return match ? decodeURIComponent(match[1]) : null;
|
|
}
|
|
|
|
function writeCookie(name: string, value: string) {
|
|
if (typeof document === "undefined") return;
|
|
const maxAge = 60 * 60 * 24 * 365;
|
|
document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; Max-Age=${maxAge}; SameSite=Lax`;
|
|
}
|
|
|
|
/**
|
|
* Client-side detection (browser / OS language prefs exposed to the page).
|
|
*/
|
|
export function detectChineseClient(): ChineseDetection {
|
|
const signals: ChineseSignal[] = [];
|
|
|
|
if (typeof navigator !== "undefined") {
|
|
if (isChineseLanguageTag(navigator.language)) {
|
|
signals.push("navigator.language");
|
|
}
|
|
if (navigator.languages?.some((l) => isChineseLanguageTag(l))) {
|
|
signals.push("navigator.languages");
|
|
}
|
|
const legacy = (navigator as Navigator & { userLanguage?: string })
|
|
.userLanguage;
|
|
if (isChineseLanguageTag(legacy) && !signals.includes("navigator.language")) {
|
|
signals.push("navigator.language");
|
|
}
|
|
}
|
|
|
|
try {
|
|
const intlLocale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
if (isChineseLanguageTag(intlLocale)) {
|
|
signals.push("intl.locale");
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
|
|
if (readCookie(ZH_OK_COOKIE) === "1") {
|
|
signals.push("cookie");
|
|
}
|
|
|
|
return { likely: signals.length > 0, signals };
|
|
}
|
|
|
|
/** Persist so middleware allows /zh after client-side Chinese detection. */
|
|
export function markChineseLocaleAllowed() {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
writeCookie(ZH_OK_COOKIE, "1");
|
|
window.localStorage.setItem(ZH_OK_COOKIE, "1");
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|