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

View file

@ -1,12 +1,12 @@
import { import {
ParentComponent, catchError,
createContext, createContext,
createMemo, createMemo,
createResource, createResource,
useContext, useContext,
} from "solid-js"; } from "solid-js";
import { match } from "@formatjs/intl-localematcher"; import { match } from "@formatjs/intl-localematcher";
import { Accessor, createEffect, createSignal } from "solid-js"; import { Accessor } from "solid-js";
import { $settings } from "../settings/stores"; import { $settings } from "../settings/stores";
import { enGB } from "date-fns/locale/en-GB"; import { enGB } from "date-fns/locale/en-GB";
import { useStore } from "@nanostores/solid"; import { useStore } from "@nanostores/solid";
@ -17,13 +17,6 @@ import {
type Template, type Template,
} from "@solid-primitives/i18n"; } 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_LANGS = ["en", "zh-Hans"] as const;
export const SUPPORTED_REGIONS = ["en_US", "en_GB", "zh_CN"] 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); 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() { export function autoMatchRegion() {
const specifiers = navigator.languages.map((x) => x.split("-")); const specifiers = navigator.languages.map((x) => x.split("-"));
@ -70,7 +55,7 @@ export function autoMatchRegion() {
return "en_GB"; return "en_GB";
} }
export function useRegion() { export function createCurrentRegion() {
const appSettings = useStore($settings); const appSettings = useStore($settings);
return createMemo( 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. * Get the {@link Locale} object for date-fns.
* *
@ -155,11 +93,11 @@ export const DateFnScope: ParentComponent = (props) => {
* @returns Accessor for Locale * @returns Accessor for Locale
*/ */
export function useDateFnLocale(): Accessor<Locale> { export function useDateFnLocale(): Accessor<Locale> {
const cx = useContext(DateFnLocaleCx); const { dateFn } = useAppLocale();
return cx; return dateFn;
} }
export function useLanguage() { export function createCurrentLanguage() {
const settings = useStore($settings); const settings = useStore($settings);
return () => settings().language || autoMatchLangTag(); return () => settings().language || autoMatchLangTag();
} }
@ -179,7 +117,7 @@ type MergedImportedModule<T> = T extends []
export function createStringResource< export function createStringResource<
T extends ImportFn<Record<string, string | Template<any> | undefined>>[], T extends ImportFn<Record<string, string | Template<any> | undefined>>[],
>(...importFns: T) { >(...importFns: T) {
const language = useLanguage(); // TODO: this function costs to much, provide a global cache const language = createCurrentLanguage();
const cache: Record<string, MergedImportedModule<T>> = {}; const cache: Record<string, MergedImportedModule<T>> = {};
return createResource( return createResource(
@ -209,3 +147,38 @@ export function createTranslator<
return [translator(res[0], resolveTemplate), res] as const; 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,
);
}