i18n: optimize performance
This commit is contained in:
parent
296de7d23b
commit
9fe86d12b0
2 changed files with 60 additions and 74 deletions
23
src/App.tsx
23
src/App.tsx
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue