i18n: optimize performance

This commit is contained in:
thislight 2024-11-23 23:36:51 +08:00
parent 296de7d23b
commit 9fe86d12b0
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E
2 changed files with 60 additions and 74 deletions

View file

@ -1,4 +1,4 @@
import { Route, Router } from "@solidjs/router";
import { Route } from "@solidjs/router";
import { ThemeProvider } from "@suid/material";
import {
Component,
@ -17,7 +17,12 @@ import {
} from "./masto/clients.js";
import { $accounts, updateAcctInf } from "./accounts/stores.js";
import { useStore } from "@nanostores/solid";
import { DateFnScope, useLanguage } from "./platform/i18n.jsx";
import {
AppLocaleProvider,
createCurrentLanguage,
createCurrentRegion,
createDateFnLocaleResource,
} from "./platform/i18n.jsx";
import { useRegisterSW } from "virtual:pwa-register/solid";
import {
isJSONRPCResult,
@ -67,7 +72,9 @@ const Routing: Component = () => {
const App: Component = () => {
const theme = useRootTheme();
const accts = useStore($accounts);
const lang = useLanguage();
const lang = createCurrentLanguage();
const region = createCurrentRegion();
const dateFnLocale = createDateFnLocaleResource(region);
const [serviceWorker, setServiceWorker] = createSignal<
ServiceWorker | undefined
>(undefined, { name: "serviceWorker" });
@ -150,7 +157,13 @@ const App: Component = () => {
}}
>
<ThemeProvider theme={theme}>
<DateFnScope>
<AppLocaleProvider
value={{
language: lang,
region: region,
dateFn: dateFnLocale,
}}
>
<ClientProvider value={clients}>
<ServiceWorkerProvider
value={{
@ -162,7 +175,7 @@ const App: Component = () => {
<Routing />
</ServiceWorkerProvider>
</ClientProvider>
</DateFnScope>
</AppLocaleProvider>
</ThemeProvider>
</ErrorBoundary>
);

View file

@ -1,12 +1,12 @@
import {
ParentComponent,
catchError,
createContext,
createMemo,
createResource,
useContext,
} from "solid-js";
import { match } from "@formatjs/intl-localematcher";
import { Accessor, createEffect, createSignal } from "solid-js";
import { Accessor } from "solid-js";
import { $settings } from "../settings/stores";
import { enGB } from "date-fns/locale/en-GB";
import { useStore } from "@nanostores/solid";
@ -17,13 +17,6 @@ import {
type Template,
} from "@solid-primitives/i18n";
async function synchronised(
name: string,
callback: () => Promise<void> | void,
): Promise<void> {
await navigator.locks.request(name, callback);
}
export const SUPPORTED_LANGS = ["en", "zh-Hans"] as const;
export const SUPPORTED_REGIONS = ["en_US", "en_GB", "zh_CN"] as const;
@ -38,14 +31,6 @@ export function autoMatchLangTag() {
return match(Array.from(navigator.languages), SUPPORTED_LANGS, DEFAULT_LANG);
}
const DateFnLocaleCx = /* __@PURE__ */ createContext<Accessor<Locale>>(
() => enGB,
);
const cachedDateFnLocale: Record<string, Locale> = {
enGB,
};
export function autoMatchRegion() {
const specifiers = navigator.languages.map((x) => x.split("-"));
@ -70,7 +55,7 @@ export function autoMatchRegion() {
return "en_GB";
}
export function useRegion() {
export function createCurrentRegion() {
const appSettings = useStore($settings);
return createMemo(
@ -100,53 +85,6 @@ async function importDateFnLocale(tag: string): Promise<Locale> {
}
}
/**
* Provides runtime values and fetch dependencies for date-fns locale
*/
export const DateFnScope: ParentComponent = (props) => {
const [dateFnLocale, setDateFnLocale] = createSignal(enGB, {
name: "dateFnLocale",
});
const region = useRegion();
createEffect(() => {
const dateFnLocaleName = region();
if (cachedDateFnLocale[dateFnLocaleName]) {
setDateFnLocale(cachedDateFnLocale[dateFnLocaleName]);
} else {
synchronised("i18n-wrapper-load-date-fns-locale", async () => {
if (cachedDateFnLocale[dateFnLocaleName]) {
setDateFnLocale(cachedDateFnLocale[dateFnLocaleName]);
return;
}
const target = `date-fns/locale/${dateFnLocaleName}`;
try {
const mod = await importDateFnLocale(dateFnLocaleName);
cachedDateFnLocale[dateFnLocaleName] = mod;
setDateFnLocale(mod);
} catch (reason) {
console.error(
{
act: "load-date-fns-locale",
stat: "failed",
reason,
target,
},
"failed to load date-fns locale",
);
}
});
}
});
return (
<DateFnLocaleCx.Provider value={dateFnLocale}>
{props.children}
</DateFnLocaleCx.Provider>
);
};
/**
* Get the {@link Locale} object for date-fns.
*
@ -155,11 +93,11 @@ export const DateFnScope: ParentComponent = (props) => {
* @returns Accessor for Locale
*/
export function useDateFnLocale(): Accessor<Locale> {
const cx = useContext(DateFnLocaleCx);
return cx;
const { dateFn } = useAppLocale();
return dateFn;
}
export function useLanguage() {
export function createCurrentLanguage() {
const settings = useStore($settings);
return () => settings().language || autoMatchLangTag();
}
@ -179,7 +117,7 @@ type MergedImportedModule<T> = T extends []
export function createStringResource<
T extends ImportFn<Record<string, string | Template<any> | undefined>>[],
>(...importFns: T) {
const language = useLanguage(); // TODO: this function costs to much, provide a global cache
const language = createCurrentLanguage();
const cache: Record<string, MergedImportedModule<T>> = {};
return createResource(
@ -209,3 +147,38 @@ export function createTranslator<
return [translator(res[0], resolveTemplate), res] as const;
}
export type AppLocale = {
dateFn: () => Locale;
language: () => string;
region: () => string;
};
const AppLocaleContext = /* @__PURE__ */ createContext<AppLocale>();
export const AppLocaleProvider = AppLocaleContext.Provider;
export function useAppLocale() {
const l = useContext(AppLocaleContext);
if (!l) {
throw new TypeError("app locale not found");
}
return l;
}
export function createDateFnLocaleResource(region: () => string) {
const [localeUncaught] = createResource(
region,
async (region) => {
return await importDateFnLocale(region);
},
{ initialValue: enGB },
);
return createMemo(
() =>
catchError(localeUncaught, (reason) => {
console.error("fetch date-fns locale", reason);
}) ?? enGB,
);
}