From 9fe86d12b0e9b81a70c8e3881a9040e0a84e4d0b Mon Sep 17 00:00:00 2001 From: thislight Date: Sat, 23 Nov 2024 23:36:51 +0800 Subject: [PATCH] i18n: optimize performance --- src/App.tsx | 23 +++++++-- src/platform/i18n.tsx | 111 ++++++++++++++++-------------------------- 2 files changed, 60 insertions(+), 74 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 8f7ba44..e7ca588 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 = () => { }} > - + { - + ); diff --git a/src/platform/i18n.tsx b/src/platform/i18n.tsx index ade3868..b64b920 100644 --- a/src/platform/i18n.tsx +++ b/src/platform/i18n.tsx @@ -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, -): Promise { - 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>( - () => enGB, -); - -const cachedDateFnLocale: Record = { - 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 { } } -/** - * 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 ( - - {props.children} - - ); -}; - /** * 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 { - 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 extends [] export function createStringResource< T extends ImportFn | undefined>>[], >(...importFns: T) { - const language = useLanguage(); // TODO: this function costs to much, provide a global cache + const language = createCurrentLanguage(); const cache: Record> = {}; 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(); + +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, + ); +}