126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
import { createI18n } from "vue-i18n";
|
|
import en from "./locales/en";
|
|
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
|
|
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
|
|
|
export type Locale = "en" | "es" | "it" | "ja" | "ko" | "pt-BR" | "zh-CN" | "zh-TW";
|
|
type LocaleMessages = Record<string, unknown>;
|
|
type I18nGlobal = {
|
|
locale: { value: Locale };
|
|
setLocaleMessage: (locale: Locale, messages: LocaleMessages) => void;
|
|
};
|
|
|
|
const supportedLocales: Locale[] = ["en", "es", "it", "ja", "ko", "pt-BR", "zh-CN", "zh-TW"];
|
|
const defaultLocale: Locale = "en";
|
|
const loadedLocales = new Set<Locale>([defaultLocale]);
|
|
const localeLoaders: Record<Exclude<Locale, "en">, () => Promise<{ default: LocaleMessages }>> = {
|
|
es: () => import("./locales/es"),
|
|
it: () => import("./locales/it"),
|
|
ja: () => import("./locales/ja"),
|
|
ko: () => import("./locales/ko"),
|
|
"pt-BR": () => import("./locales/pt-BR"),
|
|
"zh-CN": () => import("./locales/zh-CN"),
|
|
"zh-TW": () => import("./locales/zh-TW"),
|
|
};
|
|
|
|
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" || normalized.startsWith("zh-")) {
|
|
if (normalized.includes("hant") || normalized.startsWith("zh-tw") || normalized.startsWith("zh-hk") || normalized.startsWith("zh-mo")) {
|
|
return "zh-TW";
|
|
}
|
|
return "zh-CN";
|
|
}
|
|
if (normalized === "en" || normalized.startsWith("en-")) return "en";
|
|
if (normalized === "es" || normalized.startsWith("es-")) return "es";
|
|
if (normalized === "it" || normalized.startsWith("it-")) return "it";
|
|
if (normalized === "ja" || normalized.startsWith("ja-")) return "ja";
|
|
if (normalized === "ko" || normalized.startsWith("ko-")) return "ko";
|
|
if (normalized === "pt" || normalized.startsWith("pt-")) return "pt-BR";
|
|
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: initialLocale,
|
|
fallbackLocale: defaultLocale,
|
|
messages: {
|
|
en,
|
|
},
|
|
});
|
|
const i18nGlobal = i18n.global as unknown as I18nGlobal;
|
|
|
|
export async function loadLocaleMessages(locale: Locale) {
|
|
if (loadedLocales.has(locale)) return;
|
|
const loader = localeLoaders[locale as Exclude<Locale, "en">];
|
|
if (!loader) return;
|
|
const messages = await loader();
|
|
i18nGlobal.setLocaleMessage(locale, messages.default);
|
|
loadedLocales.add(locale);
|
|
}
|
|
|
|
async function syncLocaleToBackend(locale: Locale) {
|
|
if (!isTauriRuntime()) return;
|
|
try {
|
|
const { invoke } = await import("@tauri-apps/api/core");
|
|
await invoke("set_app_locale", { locale });
|
|
} catch (error) {
|
|
console.warn("[DBX][i18n] failed to sync locale to backend", error);
|
|
}
|
|
}
|
|
|
|
export async function loadSavedLocale() {
|
|
await loadLocaleMessages(initialLocale);
|
|
void syncLocaleToBackend(initialLocale);
|
|
}
|
|
|
|
export async function setLocale(locale: Locale) {
|
|
await loadLocaleMessages(locale);
|
|
i18nGlobal.locale.value = locale;
|
|
safeLocalStorageSet("dbx-locale", locale);
|
|
void syncLocaleToBackend(locale);
|
|
}
|
|
|
|
export function currentLocale(): Locale {
|
|
return i18nGlobal.locale.value;
|
|
}
|
|
|
|
export function nextLocale(current: Locale): Locale {
|
|
const index = supportedLocales.indexOf(current);
|
|
const nextIndex = index === -1 ? 0 : (index + 1) % supportedLocales.length;
|
|
return supportedLocales[nextIndex];
|
|
}
|
|
|
|
export default i18n;
|