mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-14 02:40:45 +08:00
177 lines
5.2 KiB
JavaScript
177 lines
5.2 KiB
JavaScript
import zhCN from "./zh-CN.js";
|
|
import enUS from "./en-US.js";
|
|
|
|
export const SUPPORTED_LOCALES = Object.freeze(["zh-CN", "en-US"]);
|
|
export const DEFAULT_LOCALE = "zh-CN";
|
|
export const DEFAULT_LOCALE_MODE = "auto";
|
|
|
|
export const catalogs = Object.freeze({
|
|
"zh-CN": zhCN,
|
|
"en-US": enUS,
|
|
});
|
|
|
|
let localeMode = DEFAULT_LOCALE_MODE;
|
|
let resolvedLocale = DEFAULT_LOCALE;
|
|
|
|
function normalizeLocaleTag(value) {
|
|
const tag = String(value || "").trim();
|
|
if (!tag) return "";
|
|
const lower = tag.toLowerCase();
|
|
if (lower.startsWith("zh")) return "zh-CN";
|
|
if (lower.startsWith("en")) return "en-US";
|
|
return "";
|
|
}
|
|
|
|
function getNavigatorLanguages() {
|
|
const nav = globalThis.navigator;
|
|
const languages = Array.isArray(nav?.languages) ? nav.languages : [];
|
|
if (nav?.language) return [...languages, nav.language];
|
|
return languages;
|
|
}
|
|
|
|
export function resolveLocale(mode = DEFAULT_LOCALE_MODE, options = {}) {
|
|
const requested = String(mode || DEFAULT_LOCALE_MODE);
|
|
if (SUPPORTED_LOCALES.includes(requested)) return requested;
|
|
|
|
if (requested !== "auto") return DEFAULT_LOCALE;
|
|
|
|
const hostLocale = normalizeLocaleTag(options.hostLocale);
|
|
if (hostLocale) return hostLocale;
|
|
|
|
const languages = Array.isArray(options.navigatorLanguages)
|
|
? options.navigatorLanguages
|
|
: getNavigatorLanguages();
|
|
for (const language of languages) {
|
|
const locale = normalizeLocaleTag(language);
|
|
if (locale) return locale;
|
|
}
|
|
|
|
return DEFAULT_LOCALE;
|
|
}
|
|
|
|
export function setLocale(mode = DEFAULT_LOCALE_MODE, options = {}) {
|
|
localeMode = SUPPORTED_LOCALES.includes(mode) || mode === "auto"
|
|
? mode
|
|
: DEFAULT_LOCALE_MODE;
|
|
resolvedLocale = resolveLocale(localeMode, options);
|
|
if (globalThis.document?.documentElement) {
|
|
globalThis.document.documentElement.lang = resolvedLocale;
|
|
}
|
|
return resolvedLocale;
|
|
}
|
|
|
|
export function getLocale() {
|
|
return resolvedLocale;
|
|
}
|
|
|
|
export function getLocaleMode() {
|
|
return localeMode;
|
|
}
|
|
|
|
export function hasI18nKey(key, locale = resolvedLocale) {
|
|
return Object.prototype.hasOwnProperty.call(catalogs[locale] || {}, key) ||
|
|
Object.prototype.hasOwnProperty.call(zhCN, key);
|
|
}
|
|
|
|
function interpolate(template, params = {}) {
|
|
return String(template).replace(/\{\{?\s*([A-Za-z_][\w.-]*)\s*\}?\}/g, (match, name) => {
|
|
if (Object.prototype.hasOwnProperty.call(params || {}, name)) {
|
|
const value = params[name];
|
|
return value == null ? "" : String(value);
|
|
}
|
|
return match;
|
|
});
|
|
}
|
|
|
|
export function t(key, params = {}, options = {}) {
|
|
const locale = options.locale || resolvedLocale;
|
|
const catalog = catalogs[locale] || zhCN;
|
|
const fallbackCatalog = catalogs[DEFAULT_LOCALE] || zhCN;
|
|
const template = Object.prototype.hasOwnProperty.call(catalog, key)
|
|
? catalog[key]
|
|
: fallbackCatalog[key];
|
|
if (template == null) {
|
|
return options.fallback ?? key;
|
|
}
|
|
return interpolate(template, params);
|
|
}
|
|
|
|
function hydrateAttribute(root, selector, attributeName, setter) {
|
|
const elements = [];
|
|
if (root?.matches?.(selector)) elements.push(root);
|
|
if (typeof root?.querySelectorAll === "function") {
|
|
elements.push(...root.querySelectorAll(selector));
|
|
}
|
|
for (const element of elements) {
|
|
const key = element.getAttribute?.(attributeName);
|
|
if (!key) continue;
|
|
setter(element, t(key));
|
|
}
|
|
}
|
|
|
|
export function hydrateI18n(root = globalThis.document) {
|
|
if (!root) return;
|
|
hydrateAttribute(root, "[data-i18n]", "data-i18n", (element, value) => {
|
|
element.textContent = value;
|
|
});
|
|
hydrateAttribute(root, "[data-i18n-title]", "data-i18n-title", (element, value) => {
|
|
element.setAttribute?.("title", value);
|
|
});
|
|
hydrateAttribute(root, "[data-i18n-placeholder]", "data-i18n-placeholder", (element, value) => {
|
|
element.setAttribute?.("placeholder", value);
|
|
});
|
|
hydrateAttribute(root, "[data-i18n-aria-label]", "data-i18n-aria-label", (element, value) => {
|
|
element.setAttribute?.("aria-label", value);
|
|
});
|
|
}
|
|
|
|
export function createI18nStatus({
|
|
textKey = "",
|
|
textParams = {},
|
|
textFallback = "",
|
|
metaKey = "",
|
|
metaParams = {},
|
|
metaFallback = "",
|
|
level = "idle",
|
|
extra = {},
|
|
} = {}) {
|
|
return {
|
|
...extra,
|
|
textKey,
|
|
textParams,
|
|
textFallback,
|
|
metaKey,
|
|
metaParams,
|
|
metaFallback,
|
|
text: textKey ? t(textKey, textParams, { fallback: textFallback }) : textFallback,
|
|
meta: metaKey ? t(metaKey, metaParams, { fallback: metaFallback }) : metaFallback,
|
|
level,
|
|
updatedAt: Date.now(),
|
|
};
|
|
}
|
|
|
|
export function formatUiStatusText(status) {
|
|
if (typeof status === "string") return status;
|
|
if (status?.textKey) {
|
|
return t(status.textKey, status.textParams || {}, {
|
|
fallback: status.textFallback ?? status.text ?? status.textKey,
|
|
});
|
|
}
|
|
return status?.text ?? "";
|
|
}
|
|
|
|
export function formatUiStatusMeta(status) {
|
|
if (typeof status === "string") return status;
|
|
if (status?.metaKey) {
|
|
return t(status.metaKey, status.metaParams || {}, {
|
|
fallback: status.metaFallback ?? status.meta ?? status.metaKey,
|
|
});
|
|
}
|
|
return status?.meta ?? "";
|
|
}
|
|
|
|
// Keep module import side-effect stable for non-UI tests/modules. Runtime UI
|
|
// entry points explicitly call setLocale(settings.uiLocale), where `auto` may
|
|
// resolve from the host/navigator environment.
|
|
setLocale(DEFAULT_LOCALE_MODE, { navigatorLanguages: [] });
|