From f67dbcc3c53adeaf2b570a85bc477b58c478d426 Mon Sep 17 00:00:00 2001 From: shawn Date: Wed, 3 Jun 2026 20:43:30 +0800 Subject: [PATCH 1/2] feat: default all users to English, make locale opt-in Drop browser locale auto-detection from i18n. Everyone now defaults to English; Simplified/Traditional Chinese are opt-in via the options page. - Remove the "auto" / "Follow browser" preference and resolveAuto() (chrome.i18n.getUILanguage probing) - LocalePref is now "en" | "zh_CN" | "zh_TW"; add DEFAULT_LOCALE = "en" - normalize() folds any legacy/unknown stored value (incl. "auto") to English, so existing "Follow browser" users degrade gracefully - Drop the now-unused langFollowBrowser message from all catalogs - Test the legacy "auto" fallback Co-Authored-By: Claude Opus 4.8 (1M context) --- src/_locales/en/messages.json | 1 - src/_locales/zh_CN/messages.json | 1 - src/_locales/zh_TW/messages.json | 1 - src/content.ts | 2 +- src/lib/i18n.test.ts | 9 +++++- src/lib/i18n.ts | 48 +++++++++++++------------------- static/options.html | 1 - 7 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 986ec37..0718c25 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -14,7 +14,6 @@ "settingsTitle": { "message": "Settings" }, "language": { "message": "Language" }, - "langFollowBrowser": { "message": "Follow browser" }, "openSourced": { "message": "It's open sourced!" }, "viewOnGithub": { "message": "View on GitHub" }, "tokenLabel": { "message": "GitHub Personal Access Token" }, diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json index 57c124f..b7309ba 100644 --- a/src/_locales/zh_CN/messages.json +++ b/src/_locales/zh_CN/messages.json @@ -14,7 +14,6 @@ "settingsTitle": { "message": "设置" }, "language": { "message": "语言" }, - "langFollowBrowser": { "message": "跟随浏览器" }, "openSourced": { "message": "本项目已开源!" }, "viewOnGithub": { "message": "在 GitHub 上查看" }, "tokenLabel": { "message": "GitHub 个人访问令牌" }, diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json index 12e2575..1c6dd91 100644 --- a/src/_locales/zh_TW/messages.json +++ b/src/_locales/zh_TW/messages.json @@ -14,7 +14,6 @@ "settingsTitle": { "message": "設定" }, "language": { "message": "語言" }, - "langFollowBrowser": { "message": "跟隨瀏覽器" }, "openSourced": { "message": "本專案已開源!" }, "viewOnGithub": { "message": "在 GitHub 上查看" }, "tokenLabel": { "message": "GitHub 個人存取權杖" }, diff --git a/src/content.ts b/src/content.ts index 133f7c4..3a44014 100644 --- a/src/content.ts +++ b/src/content.ts @@ -128,7 +128,7 @@ if (isExtensionValid()) { if (area !== "local") return; // Picked-up on the next injection/navigation; a refresh re-renders all text. if (LOCALE_KEY in changes) { - setLocale((changes[LOCALE_KEY].newValue as LocalePref) ?? "auto"); + setLocale((changes[LOCALE_KEY].newValue as LocalePref) ?? "en"); } for (const key of FEATURE_KEYS) { if (!(key in changes)) continue; diff --git a/src/lib/i18n.test.ts b/src/lib/i18n.test.ts index 1749791..3786f90 100644 --- a/src/lib/i18n.test.ts +++ b/src/lib/i18n.test.ts @@ -15,7 +15,6 @@ describe("i18n", () => { setLocale("zh_CN"); expect(t("approveNow")).toBe("立即批准"); expect(t("settingsTitle")).toBe("设置"); - expect(t("langFollowBrowser")).toBe("跟随浏览器"); }); it("switches to Traditional Chinese via setLocale", () => { @@ -35,6 +34,14 @@ describe("i18n", () => { expect(t("commitTagTitle", "v1.0.0")).toBe("标签:v1.0.0"); }); + it("falls back to English for a legacy/unknown preference", () => { + // "auto" was a valid preference before we dropped browser auto-detection; + // an existing user may still have it stored. It must resolve to English. + setLocale("auto" as unknown as Parameters[0]); + expect(t("settingsTitle")).toBe("Settings"); + expect(t("approveNow")).toBe("approve now"); + }); + it("returns empty string for an unknown key", () => { setLocale("zh_CN"); expect(t("__does_not_exist__")).toBe(""); diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 928389c..9c829f1 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -1,8 +1,9 @@ // Lightweight i18n that, unlike chrome.i18n (which is locked to the browser UI // locale), supports an in-extension manual language override stored in -// chrome.storage.local. Catalogs are bundled so t() stays synchronous; the -// active locale is resolved once from the stored preference (default: follow -// the browser locale) and can be changed at runtime via setLocale(). +// chrome.storage.local. Catalogs are bundled so t() stays synchronous. The +// active locale defaults to English for everyone; we deliberately do NOT +// auto-detect the browser locale. Users opt into another language explicitly +// on the options page, and the choice can be applied at runtime via setLocale(). import enMessages from "../_locales/en/messages.json"; import zhCNMessages from "../_locales/zh_CN/messages.json"; import zhTWMessages from "../_locales/zh_TW/messages.json"; @@ -19,52 +20,43 @@ const CATALOGS: Record = { zh_TW: zhTWMessages as unknown as Catalog, }; -/** Locale preference: "auto" follows the browser, or a concrete catalog id. */ -export type LocalePref = "auto" | "en" | "zh_CN" | "zh_TW"; +/** A concrete catalog id. English is the default; other locales are opt-in. */ +export type LocalePref = "en" | "zh_CN" | "zh_TW"; /** Order shown in the language picker. */ -export const LOCALE_OPTIONS: LocalePref[] = ["auto", "en", "zh_CN", "zh_TW"]; +export const LOCALE_OPTIONS: LocalePref[] = ["en", "zh_CN", "zh_TW"]; /** chrome.storage.local key holding the LocalePref. */ export const LOCALE_KEY = "locale"; +/** Locale used when the user hasn't explicitly picked one. */ +export const DEFAULT_LOCALE: LocalePref = "en"; -/** Map the browser UI language to one of our catalogs (en fallback). */ -function resolveAuto(): string { - let ui = "en"; - try { - const g = globalThis as unknown as { chrome?: { i18n?: { getUILanguage?: () => string } } }; - ui = g.chrome?.i18n?.getUILanguage?.() || "en"; - } catch { - ui = "en"; - } - const lc = ui.toLowerCase(); - if (lc.startsWith("zh")) { - // Traditional for Taiwan / Hong Kong / Macau / explicit Hant script. - return /hant|-tw|-hk|-mo/.test(lc) ? "zh_TW" : "zh_CN"; - } - return "en"; +/** Normalize an arbitrary stored value to a known locale (en fallback). Also + * absorbs the legacy "auto" preference, which no longer exists. */ +function normalize(pref: unknown): LocalePref { + return typeof pref === "string" && pref in CATALOGS ? (pref as LocalePref) : DEFAULT_LOCALE; } -let currentLocale = resolveAuto(); +let currentLocale: LocalePref = DEFAULT_LOCALE; /** Apply a preference immediately (synchronous). */ export function setLocale(pref: LocalePref): void { - currentLocale = pref === "auto" ? resolveAuto() : pref; + currentLocale = normalize(pref); } /** - * Load the stored preference and apply it. Resolves to the stored pref - * ("auto" when unset or storage is unavailable) so callers can reflect it in a - * picker. Safe to call repeatedly. + * Load the stored preference and apply it. Resolves to the effective locale + * (DEFAULT_LOCALE when unset, unrecognized, or storage is unavailable) so + * callers can reflect it in a picker. Safe to call repeatedly. */ export function initLocale(): Promise { return new Promise((resolve) => { try { chrome.storage.local.get([LOCALE_KEY], (result) => { - const pref = (result?.[LOCALE_KEY] as LocalePref) || "auto"; + const pref = normalize(result?.[LOCALE_KEY]); setLocale(pref); resolve(pref); }); } catch { - resolve("auto"); + resolve(DEFAULT_LOCALE); } }); } diff --git a/static/options.html b/static/options.html index a2371e0..6759d2b 100644 --- a/static/options.html +++ b/static/options.html @@ -460,7 +460,6 @@

Settings

Language