Skip to content

Commit b3748cc

Browse files
committed
feat: cache i18n resource to speed up loading time
Signed-off-by: Innei <i@innei.in>
1 parent 5f87190 commit b3748cc

File tree

2 files changed

+59
-10
lines changed

2 files changed

+59
-10
lines changed

src/renderer/src/i18n.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,57 @@ import { initReactI18next } from "react-i18next"
55

66
import { defaultNS, ns } from "./@types/constants"
77
import { defaultResources } from "./@types/default-resource"
8+
import { getGeneralSettings } from "./atoms/settings/general"
89
import { jotaiStore } from "./lib/jotai"
10+
import { getStorageNS } from "./lib/ns"
911

1012
export const i18nAtom = atom(i18next)
1113

14+
export class LocaleCache {
15+
static shared = new LocaleCache()
16+
private getKey(lang: string) {
17+
return getStorageNS(`locale-${lang}`)
18+
}
19+
get(lang: string) {
20+
const key = this.getKey(lang)
21+
const cache = localStorage.getItem(key)
22+
if (!cache) return null
23+
return JSON.parse(cache)
24+
}
25+
set(lang: string) {
26+
const key = this.getKey(lang)
27+
const mergedResources = {} as any
28+
for (const nsKey of ns) {
29+
const nsResources = i18next.getResourceBundle(lang, nsKey)
30+
mergedResources[nsKey] = nsResources
31+
}
32+
localStorage.setItem(key, JSON.stringify(mergedResources))
33+
}
34+
}
35+
1236
export const fallbackLanguage = "en"
1337
export const initI18n = async () => {
1438
const i18next = jotaiStore.get(i18nAtom)
39+
40+
const lang = getGeneralSettings().language
41+
const cache = LocaleCache.shared.get(lang)
42+
43+
const mergedResources = {
44+
...defaultResources,
45+
}
46+
47+
if (cache) {
48+
mergedResources[lang] = cache
49+
}
50+
1551
await i18next.use(initReactI18next).init({
1652
ns,
17-
lng: "en",
53+
lng: cache ? lang : fallbackLanguage,
1854
fallbackLng: fallbackLanguage,
1955
defaultNS,
2056
debug: import.meta.env.DEV,
2157

22-
resources: defaultResources,
58+
resources: mergedResources,
2359
})
2460
}
2561

src/renderer/src/providers/i18n-provider.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import {
22
currentSupportedLanguages,
33
dayjsLocaleImportMap,
4-
defaultNS,
54
} from "@renderer/@types/constants"
65
import { defaultResources } from "@renderer/@types/default-resource"
7-
import { getGeneralSettings } from "@renderer/atoms/settings/general"
8-
import { i18nAtom } from "@renderer/i18n"
6+
import { getGeneralSettings, setGeneralSetting } from "@renderer/atoms/settings/general"
7+
import { fallbackLanguage, i18nAtom, LocaleCache } from "@renderer/i18n"
98
import { EventBus } from "@renderer/lib/event-bus"
109
import { jotaiStore } from "@renderer/lib/jotai"
1110
import { isEmptyObject } from "@renderer/lib/utils"
1211
import dayjs from "dayjs"
1312
import i18next from "i18next"
13+
import LanguageDetector from "i18next-browser-languagedetector"
1414
import { useAtom } from "jotai"
1515
import type { FC, PropsWithChildren } from "react"
1616
import { useEffect, useLayoutEffect, useRef } from "react"
1717
import { I18nextProvider } from "react-i18next"
1818
import { toast } from "sonner"
1919

2020
const loadingLangLock = new Set<string>()
21+
const loadedLangs = new Set<string>([fallbackLanguage])
2122

2223
const langChangedHandler = async (lang: string) => {
2324
const dayjsImport = dayjsLocaleImportMap[lang]
@@ -35,7 +36,7 @@ const langChangedHandler = async (lang: string) => {
3536
if (!isSupport) {
3637
return
3738
}
38-
const loaded = i18next.getResourceBundle(lang, defaultNS)
39+
const loaded = loadedLangs.has(lang)
3940

4041
if (loaded) {
4142
return
@@ -68,8 +69,8 @@ const langChangedHandler = async (lang: string) => {
6869
}
6970
}
7071
} else {
71-
const res = await import(`/locales/${lang}.js`)
72-
.then((res) => res?.default || res)
72+
const res = await eval(`import('/locales/${lang}.js')`)
73+
.then((res: any) => res?.default || res)
7374
.catch(() => {
7475
toast.error(`${t("common:tips.load-lng-error")}: ${lang}`)
7576
loadingLangLock.delete(lang)
@@ -86,10 +87,10 @@ const langChangedHandler = async (lang: string) => {
8687

8788
await i18next.reloadResources()
8889
await i18next.changeLanguage(lang)
90+
LocaleCache.shared.set(lang)
91+
loadedLangs.add(lang)
8992
loadingLangLock.delete(lang)
9093
}
91-
92-
langChangedHandler(getGeneralSettings().language)
9394
export const I18nProvider: FC<PropsWithChildren> = ({ children }) => {
9495
const [currentI18NInstance, update] = useAtom(i18nAtom)
9596

@@ -108,6 +109,18 @@ export const I18nProvider: FC<PropsWithChildren> = ({ children }) => {
108109
)
109110

110111
const callOnce = useRef(false)
112+
const detectOnce = useRef(false)
113+
useLayoutEffect(() => {
114+
if (detectOnce.current) return
115+
const languageDetector = new LanguageDetector()
116+
const userLang = languageDetector.detect()
117+
if (!userLang) return
118+
const firstUserLang = Array.isArray(userLang) ? userLang[0] : userLang
119+
if (currentSupportedLanguages.includes(firstUserLang)) {
120+
setGeneralSetting("language", firstUserLang)
121+
}
122+
detectOnce.current = true
123+
}, [])
111124

112125
useLayoutEffect(() => {
113126
const i18next = currentI18NInstance

0 commit comments

Comments
 (0)