Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
31 changes: 15 additions & 16 deletions src/_locales/zh_CN/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

"settingsTitle": { "message": "设置" },
"language": { "message": "语言" },
"langFollowBrowser": { "message": "跟随浏览器" },
"openSourced": { "message": "本项目已开源!" },
"viewOnGithub": { "message": "在 GitHub 上查看" },
"tokenLabel": { "message": "GitHub 个人访问令牌" },
Expand All @@ -34,7 +33,7 @@
"groupHome": { "message": "首页" },
"groupPRsIssues": { "message": "PR 和 Issue" },
"groupPRDetails": { "message": "PR 详情" },
"groupCommits": { "message": "提交" },
"groupCommits": { "message": "Commits" },
"groupRepository": { "message": "仓库" },

"featBetterTopReposName": { "message": "增强的热门仓库" },
Expand Down Expand Up @@ -67,17 +66,17 @@
},
"featPrCollapseExpandName": { "message": "折叠/展开所有文件" },
"featPrCollapseExpandDesc": {
"message": "在 PR、提交和对比页面添加一个折叠或展开所有文件差异的按钮。"
"message": "在 PR、Commit 和对比页面添加一个折叠或展开所有文件差异的按钮。"
},
"featCommitTagsName": { "message": "提交标签" },
"featCommitTagsName": { "message": "Commit Tags" },
"featCommitTagsDesc": {
"message": "在提交列表页显示 git 标签,便于识别。"
"message": "在 Commits 列表页显示 git tags,便于识别。"
},
"featCommitDiffStatsName": { "message": "提交差异统计" },
"featCommitDiffStatsName": { "message": "Commit 差异统计" },
"featCommitDiffStatsDesc": {
"message": "在提交列表页显示新增、删除行数和文件数(如 +223 −114 · 5 files)。需要令牌。"
"message": "在 Commits 列表页显示新增、删除行数和文件数(如 +223 −114 · 5 files)。需要令牌。"
},
"featReleaseTabName": { "message": "发布标签页" },
"featReleaseTabName": { "message": "Releases 标签页" },
"featReleaseTabDesc": {
"message": "在仓库导航栏添加一个 Releases 标签页,方便快速访问。"
},
Expand All @@ -100,7 +99,7 @@
"saveFailed": { "message": "保存失败" },
"saved": { "message": "已保存!" },

"releases": { "message": "发布" },
"releases": { "message": "Releases" },
"approveNow": { "message": "立即批准" },
"approveDialogTitle": { "message": "批准此 Pull Request?" },
"approveCommentPlaceholder": { "message": "添加评论(可选)" },
Expand All @@ -113,7 +112,7 @@
},

"commitTagTitle": {
"message": "标签:$name$",
"message": "Tag: $name$",
"placeholders": { "name": { "content": "$1", "example": "v1.0.0" } }
},
"copied": { "message": "已复制!" },
Expand All @@ -122,13 +121,13 @@
"pinRepository": { "message": "置顶仓库" },
"unpinRepository": { "message": "取消置顶" },

"watchers": { "message": "关注者" },
"forks": { "message": "复刻" },
"stargazers": { "message": "星标用户" },
"watchers": { "message": "Watchers" },
"forks": { "message": "Forks" },
"stargazers": { "message": "Stargazers" },
"viewAll": { "message": "查看全部" },
"noWatchers": { "message": "暂无关注者" },
"noStargazers": { "message": "暂无星标用户" },
"noForks": { "message": "暂无复刻" },
"noWatchers": { "message": "暂无 Watchers" },
"noStargazers": { "message": "暂无 Stargazers" },
"noForks": { "message": "暂无 Forks" },
"failedToLoad": { "message": "加载失败" },

"collapseTree": { "message": "折叠目录树" },
Expand Down
31 changes: 15 additions & 16 deletions src/_locales/zh_TW/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

"settingsTitle": { "message": "設定" },
"language": { "message": "語言" },
"langFollowBrowser": { "message": "跟隨瀏覽器" },
"openSourced": { "message": "本專案已開源!" },
"viewOnGithub": { "message": "在 GitHub 上查看" },
"tokenLabel": { "message": "GitHub 個人存取權杖" },
Expand All @@ -34,7 +33,7 @@
"groupHome": { "message": "首頁" },
"groupPRsIssues": { "message": "PR 和 Issue" },
"groupPRDetails": { "message": "PR 詳情" },
"groupCommits": { "message": "提交" },
"groupCommits": { "message": "Commits" },
"groupRepository": { "message": "儲存庫" },

"featBetterTopReposName": { "message": "增強的熱門儲存庫" },
Expand Down Expand Up @@ -67,17 +66,17 @@
},
"featPrCollapseExpandName": { "message": "摺疊/展開所有檔案" },
"featPrCollapseExpandDesc": {
"message": "在 PR、提交和比較頁面新增一個摺疊或展開所有檔案差異的按鈕。"
"message": "在 PR、Commit 和比較頁面新增一個摺疊或展開所有檔案差異的按鈕。"
},
"featCommitTagsName": { "message": "提交標籤" },
"featCommitTagsName": { "message": "Commit Tags" },
"featCommitTagsDesc": {
"message": "在提交清單頁顯示 git 標籤,便於識別。"
"message": "在 Commits 清單頁顯示 git tags,便於識別。"
},
"featCommitDiffStatsName": { "message": "提交差異統計" },
"featCommitDiffStatsName": { "message": "Commit 差異統計" },
"featCommitDiffStatsDesc": {
"message": "在提交清單頁顯示新增、刪除行數和檔案數(如 +223 −114 · 5 files)。需要權杖。"
"message": "在 Commits 清單頁顯示新增、刪除行數和檔案數(如 +223 −114 · 5 files)。需要權杖。"
},
"featReleaseTabName": { "message": "發佈分頁" },
"featReleaseTabName": { "message": "Releases 分頁" },
"featReleaseTabDesc": {
"message": "在儲存庫導覽列新增一個 Releases 分頁,方便快速存取。"
},
Expand All @@ -100,7 +99,7 @@
"saveFailed": { "message": "儲存失敗" },
"saved": { "message": "已儲存!" },

"releases": { "message": "發佈" },
"releases": { "message": "Releases" },
"approveNow": { "message": "立即核准" },
"approveDialogTitle": { "message": "核准此 Pull Request?" },
"approveCommentPlaceholder": { "message": "新增留言(選填)" },
Expand All @@ -113,7 +112,7 @@
},

"commitTagTitle": {
"message": "標籤:$name$",
"message": "Tag: $name$",
"placeholders": { "name": { "content": "$1", "example": "v1.0.0" } }
},
"copied": { "message": "已複製!" },
Expand All @@ -122,13 +121,13 @@
"pinRepository": { "message": "置頂儲存庫" },
"unpinRepository": { "message": "取消置頂" },

"watchers": { "message": "關注者" },
"forks": { "message": "復刻" },
"stargazers": { "message": "星標使用者" },
"watchers": { "message": "Watchers" },
"forks": { "message": "Forks" },
"stargazers": { "message": "Stargazers" },
"viewAll": { "message": "查看全部" },
"noWatchers": { "message": "尚無關注者" },
"noStargazers": { "message": "尚無星標使用者" },
"noForks": { "message": "尚無復刻" },
"noWatchers": { "message": "尚無 Watchers" },
"noStargazers": { "message": "尚無 Stargazers" },
"noForks": { "message": "尚無 Forks" },
"failedToLoad": { "message": "載入失敗" },

"collapseTree": { "message": "摺疊目錄樹" },
Expand Down
2 changes: 1 addition & 1 deletion src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 9 additions & 2 deletions src/lib/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -32,7 +31,15 @@ describe("i18n", () => {
"1,234 additions, 56 deletions across 3 files",
);
setLocale("zh_CN");
expect(t("commitTagTitle", "v1.0.0")).toBe("标签:v1.0.0");
expect(t("commitTagTitle", "v1.0.0")).toBe("Tag: 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<typeof setLocale>[0]);
expect(t("settingsTitle")).toBe("Settings");
expect(t("approveNow")).toBe("approve now");
});

it("returns empty string for an unknown key", () => {
Expand Down
48 changes: 20 additions & 28 deletions src/lib/i18n.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -19,52 +20,43 @@ const CATALOGS: Record<string, Catalog> = {
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<LocalePref> {
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);
}
});
}
Expand Down
1 change: 0 additions & 1 deletion static/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,6 @@ <h2 class="brand-title" data-i18n="settingsTitle">Settings</h2>
<div class="lang-row">
<span class="lang-label" data-i18n="language">Language</span>
<select id="langSelect" class="lang-select">
<option value="auto" data-i18n="langFollowBrowser">Follow browser</option>
<option value="en">English</option>
<option value="zh_CN">简体中文</option>
<option value="zh_TW">繁體中文</option>
Expand Down