feat(i18n): detect initial locale from user language
This commit is contained in:
parent
a87b8ece31
commit
fb7d9dc847
|
|
@ -17,18 +17,49 @@ const localeLoaders: Record<Exclude<Locale, "zh-CN">, () => Promise<{ default: L
|
|||
es: () => import("./locales/es"),
|
||||
};
|
||||
|
||||
function normalizeLocale(value: string | null): Locale {
|
||||
export function normalizeLocale(value: string | null): Locale | null {
|
||||
if (value && supportedLocales.includes(value as Locale)) {
|
||||
return value as Locale;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function localeFromLanguageTag(value: string | null | undefined): Locale | null {
|
||||
if (!value) return null;
|
||||
const normalized = value.replace("_", "-").toLowerCase();
|
||||
if (normalized === "zh-cn" || normalized.startsWith("zh-")) return "zh-CN";
|
||||
if (normalized === "zh") return "zh-CN";
|
||||
if (normalized === "en" || normalized.startsWith("en-")) return "en";
|
||||
if (normalized === "es" || normalized.startsWith("es-")) return "es";
|
||||
return null;
|
||||
}
|
||||
|
||||
export function detectLocaleFromLanguages(languages: readonly string[]): Locale {
|
||||
for (const language of languages) {
|
||||
const locale = normalizeLocale(language) ?? localeFromLanguageTag(language);
|
||||
if (locale) return locale;
|
||||
}
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
function detectUserLocale(): Locale {
|
||||
try {
|
||||
const languages = globalThis.navigator?.languages;
|
||||
const language = globalThis.navigator?.language;
|
||||
const candidates = Array.isArray(languages) ? [...languages] : [];
|
||||
if (language) candidates.push(language);
|
||||
return detectLocaleFromLanguages(candidates);
|
||||
} catch {
|
||||
return defaultLocale;
|
||||
}
|
||||
}
|
||||
|
||||
const savedLocale = normalizeLocale(safeLocalStorageGet("dbx-locale"));
|
||||
const initialLocale = savedLocale ?? detectUserLocale();
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: savedLocale,
|
||||
locale: initialLocale,
|
||||
fallbackLocale: defaultLocale,
|
||||
messages: {
|
||||
"zh-CN": zhCN,
|
||||
|
|
@ -46,7 +77,7 @@ export async function loadLocaleMessages(locale: Locale) {
|
|||
}
|
||||
|
||||
export async function loadSavedLocale() {
|
||||
await loadLocaleMessages(savedLocale);
|
||||
await loadLocaleMessages(initialLocale);
|
||||
}
|
||||
|
||||
export async function setLocale(locale: Locale) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { strict as assert } from "node:assert";
|
||||
import test from "node:test";
|
||||
import {
|
||||
detectLocaleFromLanguages,
|
||||
localeFromLanguageTag,
|
||||
normalizeLocale,
|
||||
} from "../../apps/desktop/src/i18n/index.ts";
|
||||
|
||||
test("normalizes exact supported locales", () => {
|
||||
assert.equal(normalizeLocale("en"), "en");
|
||||
assert.equal(normalizeLocale("es"), "es");
|
||||
assert.equal(normalizeLocale("zh-CN"), "zh-CN");
|
||||
assert.equal(normalizeLocale("fr-FR"), null);
|
||||
assert.equal(normalizeLocale(null), null);
|
||||
});
|
||||
|
||||
test("maps user language tags to supported locales", () => {
|
||||
assert.equal(localeFromLanguageTag("zh-Hans-CN"), "zh-CN");
|
||||
assert.equal(localeFromLanguageTag("zh_TW"), "zh-CN");
|
||||
assert.equal(localeFromLanguageTag("en-US"), "en");
|
||||
assert.equal(localeFromLanguageTag("es-MX"), "es");
|
||||
assert.equal(localeFromLanguageTag("fr-FR"), null);
|
||||
});
|
||||
|
||||
test("detects the first supported user locale and falls back to Chinese", () => {
|
||||
assert.equal(detectLocaleFromLanguages(["fr-FR", "es-MX", "en-US"]), "es");
|
||||
assert.equal(detectLocaleFromLanguages(["de-DE", "ja-JP"]), "zh-CN");
|
||||
assert.equal(detectLocaleFromLanguages([]), "zh-CN");
|
||||
});
|
||||
Loading…
Reference in New Issue