fix(i18n): match browser locale by language subtag instead of exact code - #174
Conversation
|
简单说:需求对,但是实现方式有问题,如下: 问题当前 if (primary === "en") return "en-US";
if (primary === "zh") { /* ... */ }
return code;这等于后面每新增一种语言,都必须回来修改这个函数。 #161 正在添加韩语 ( 建议将 export function createLanguageNormalizer(
resourceKeys: string[],
): (code: string) => string {
const keySet = new Set(resourceKeys);
const index = buildPrimarySubtagIndex(resourceKeys);
// index: Map { "en" → ["en-US"], "zh" → ["zh-CN"], "ko" → ["ko-KR"], ... }
return (code: string): string => {
// 1. 精确匹配 → 直接返回
if (keySet.has(code)) return code;
const parts = code.split(/[-_]/).map((p) => p.toLowerCase());
const primary = parts[0];
const candidates = index.get(primary);
if (!candidates || candidates.length === 0) return code;
// 2. 一个 primary subtag 只对应一个 resource → 自动映射
// ko → ko-KR, en → en-US, ja → ja-JP ...
if (candidates.length === 1) return candidates[0];
// 3. 一个 primary subtag 对应多个 resource → 消歧(目前只有 zh)
if (primary === "zh") return resolveChineseVariant(parts, candidates);
return candidates[0];
};
}调用侧传入 resource keys: // i18n.ts
const resourceKeys = Object.keys(resources);
// ...
detection: getLanguageDetectionOptions(resourceKeys),
export function getLanguageDetectionOptions(resourceKeys: string[]) {
const normalize = createLanguageNormalizer(resourceKeys);
return {
order: ["chromeUILanguage", "navigator"],
caches: [],
convertDetectedLanguage: normalize,
};
}中文简繁消歧( 收益
这样也能避免和 #161 之间的合并顺序依赖——无论谁先合,后合的只需要在 测试方面,现有 28 个 case 全部兼容,建议额外补两组验证扩展性的 case: it("auto-resolves new languages from resource keys", () => {
const normalize = createLanguageNormalizer(["en-US", "zh-CN", "ko-KR"]);
expect(normalize("ko")).toBe("ko-KR");
expect(normalize("ko-KR")).toBe("ko-KR");
});
it("disambiguates zh when multiple zh resources exist", () => {
const normalize = createLanguageNormalizer(["en-US", "zh-CN", "zh-TW"]);
expect(normalize("zh-Hant")).toBe("zh-TW");
expect(normalize("zh-HK")).toBe("zh-TW");
expect(normalize("zh")).toBe("zh-CN");
});其余( |
The extension ships en-US and zh-CN resources, but only en-US was ever
selected: every other browser language fell through to fallbackLng
("zh-CN"), so users on en, en-GB, en-CN, en-AU, ja-JP, fr-FR saw Chinese.
Match on the BCP 47 language subtag and normalise it to a resource key:
en* -> en-US
zh-Hans* / zh-CN / zh-SG / zh -> zh-CN
zh-Hant* / zh-TW / zh-HK / zh-MO -> zh-TW
anything else -> unchanged (fallbackLng default: en-US)
Simplified vs Traditional is decided by the script subtag (Hans/Hant) when
present, then by the region subtag, since Chrome reports zh-CN / zh-TW
rather than script subtags. The zh-TW branch is written now so adding a
Traditional translation later needs no code change.
chrome.i18n.getUILanguage() is now preferred over navigator.language, which
is unreliable on the chrome-extension:// origin the popup runs on.
Adds a locale-resolution regression matrix (28 cases).
Closes Tencent#168
Per review feedback on Tencent#174: the hardcoded if/else in normalizeLanguageCode required touching the function for every new language, so a bare `ko` would miss the `ko-KR` resource Tencent#161 is about to add and Korean users would land on English despite the fix. Replace it with createLanguageNormalizer(resourceKeys), a factory that derives its mapping from the resource keys i18next is configured with: 1. exact resource key -> unchanged 2. one resource for the language -> that key (ko -> ko-KR) 3. several (zh-CN / zh-TW) -> script subtag, then region 4. nothing shipped -> unchanged, fallbackLng applies getLanguageDetectionOptions now takes the resource keys and i18n.ts passes Object.keys(resources). Chinese disambiguation stays as the only special case - one language, two scripts; every other mapping derives from the index automatically. Test expectations for the zh-TW family change from "zh-TW" to "zh-CN": only zh-CN ships today and the factory maps to shipped keys only, so Traditional resolves directly to Simplified instead of leaking into the fallback chain. The rendered UI is identical either way, and the new extensibility cases lock in the automatic disambiguation once a zh-TW bundle registers - plus the auto-resolution of a newly registered language like ko-KR. 28 -> 31 tests; extension suite 832/832; tsc, biome and ext:build clean.
c9ab9e7 to
93853eb
Compare
已按建议重构完成(93853eb)。 实现与您的伪代码逐行一致:
测试:建议的两组 case 均已补上( 一处期望值变化说明:
|
|
LGTM |
Problem
The extension ships
en-USandzh-CN, but onlyen-USwas ever selected - every other browser language fell through tofallbackLng: "zh-CN", so users onen,en-GB,en-CN,en-AU,ja-JP,fr-FRsaw a Chinese UI.Fix
Match on the BCP 47 language subtag instead of the full locale code:
en*->en-US(every English variant)zh-Hans*/zh-CN/zh-SG/zh->zh-CNzh-Hant*/zh-TW/zh-HK/zh-MO->zh-TWfallbackLngThree layers, all in
packages/i18n:chrome.i18n.getUILanguage()overnavigator.language, which is unreliable on thechrome-extension://origin the popup runs on.convertDetectedLanguageapplying the table above.fallbackLngis now an object:{ en: ["en-US"], zh: ["zh-CN"], default: ["en-US"] }- unmatched languages land on English, not Chinese.Simplified vs Traditional
Decided by the script subtag (
Hans/Hant) when present, then by the region subtag, since Chrome reportszh-CN/zh-TWrather than script subtags.The
zh-TWbranch is written now, so adding a Traditional translation later needs no code change - only a newzh-TWresource. Until then i18next falls back tozh-CN(graceful degradation, not a misroute).Tests
Adds
packages/i18n/tests/locale-resolution.test.ts(28 cases) locking the table in as regression tests; the package previously had no tests.28/28 tests pass;
tsc --noEmit,biome check, andpnpm ext:buildare all clean.Closes #168