fix: set language when init
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
3d39308696
commit
fb3b59210e
|
|
@ -22,6 +22,7 @@ export const viteRenderBaseConfig = {
|
|||
"@shared": resolve("src/shared/src"),
|
||||
"@pkg": resolve("./package.json"),
|
||||
"@env": resolve("./src/env.ts"),
|
||||
"@locales": resolve("./locales"),
|
||||
},
|
||||
},
|
||||
base: "/",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"confirm": "Confirm",
|
||||
"ok": "OK",
|
||||
"cancel": "Cancel"
|
||||
"cancel": "Cancel",
|
||||
"tips": {
|
||||
"load-lng-error": "Failed to load language pack"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"confirm": "确认",
|
||||
"ok": "好",
|
||||
"cancel": "取消"
|
||||
"cancel": "取消",
|
||||
"tips": {
|
||||
"load-lng-error": "加载语言包失败"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
import en from "../../../../locales/en.json"
|
||||
import common_en from "../../../../locales/namespaces/common/en.json"
|
||||
import common_zhCN from "../../../../locales/namespaces/common/zh_CN.json"
|
||||
import lang_en from "../../../../locales/namespaces/lang/en.json"
|
||||
|
||||
/**
|
||||
* This file is the language resource that is loaded in full when the app is initialized.
|
||||
* When switching languages, the app will automatically download the required language resources,
|
||||
* we will not load all the language resources to minimize the first screen loading time of the app.
|
||||
* Generally, we only load english resources synchronously by default.
|
||||
* In addition, we attach common resources for other languages, and the size of the common resources must be controlled.
|
||||
*/
|
||||
|
||||
export const defaultResources = {
|
||||
en: {
|
||||
translation: en,
|
||||
lang: lang_en,
|
||||
common: common_en,
|
||||
},
|
||||
|
||||
zh_CN: {
|
||||
common: common_zhCN,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import { getGeneralSettings } from "@renderer/atoms/settings/general"
|
||||
import { EventBus } from "@renderer/lib/event-bus"
|
||||
import i18next from "i18next"
|
||||
import { atom } from "jotai"
|
||||
import { initReactI18next } from "react-i18next"
|
||||
|
||||
import { currentSupportedLanguages } from "./@types/constants"
|
||||
import { defaultResources } from "./@types/default-resource"
|
||||
import { jotaiStore } from "./lib/jotai"
|
||||
|
||||
export const i18nAtom = atom(i18next)
|
||||
|
||||
export const defaultNS = "translation"
|
||||
|
||||
const loadingLangLock = new Set<string>()
|
||||
export const fallbackLanguage = "en"
|
||||
export const initI18n = async () => {
|
||||
const i18next = jotaiStore.get(i18nAtom)
|
||||
await i18next.use(initReactI18next).init({
|
||||
lng: "en",
|
||||
fallbackLng: fallbackLanguage,
|
||||
|
|
@ -18,58 +20,12 @@ export const initI18n = async () => {
|
|||
debug: import.meta.env.DEV,
|
||||
resources: defaultResources,
|
||||
})
|
||||
|
||||
i18next.on("languageChanged", async (lang) => {
|
||||
if (loadingLangLock.has(lang)) return
|
||||
const isSupport = currentSupportedLanguages.includes(lang)
|
||||
if (!isSupport) {
|
||||
return
|
||||
}
|
||||
const loaded = i18next.getResourceBundle(lang, defaultNS)
|
||||
|
||||
if (loaded) return
|
||||
|
||||
loadingLangLock.add(lang)
|
||||
|
||||
const rootGlobbyMap = import.meta.glob("../../../locales/*.json")
|
||||
const nsGlobbyMap = import.meta.glob("../../../locales/namespaces/*/*.json")
|
||||
|
||||
const rootResources = await rootGlobbyMap[`../../../locales/${lang}.json`]().then(
|
||||
(m: any) => m.default,
|
||||
)
|
||||
|
||||
i18next.addResourceBundle(lang, defaultNS, rootResources, true, true)
|
||||
|
||||
const namespaces = Object.keys(defaultResources.en)
|
||||
|
||||
await Promise.all(
|
||||
namespaces.map(async (ns) => {
|
||||
if (ns === defaultNS) return
|
||||
|
||||
const loader = nsGlobbyMap[`../../../locales/namespaces/${ns}/${lang}.json`]
|
||||
|
||||
if (!loader) return
|
||||
const nsResources = await loader().then((m: any) => m.default)
|
||||
|
||||
i18next.addResourceBundle(lang, ns, nsResources, true, true)
|
||||
}),
|
||||
)
|
||||
|
||||
await i18next.reloadResources()
|
||||
await i18next.changeLanguage(lang)
|
||||
loadingLangLock.delete(lang)
|
||||
EventBus.dispatch("I18N_UPDATE", "")
|
||||
})
|
||||
|
||||
const { language } = getGeneralSettings()
|
||||
|
||||
i18next.changeLanguage(language)
|
||||
}
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.on("i18n-update", ({ file, content }: { file: string; content: string }) => {
|
||||
const resources = JSON.parse(content)
|
||||
|
||||
const i18next = jotaiStore.get(i18nAtom)
|
||||
// `file` is absolute path e.g. /Users/innei/git/follow/locales/en.json
|
||||
// Absolute path e.g. /Users/innei/git/follow/locales/namespaces/<module-name>/en.json
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,113 @@
|
|||
import { defaultResources } from "@renderer/@types/default-resource"
|
||||
import { getGeneralSettings } from "@renderer/atoms/settings/general"
|
||||
import { currentSupportedLanguages, defaultNS, i18nAtom } from "@renderer/i18n"
|
||||
import { nextFrame } from "@renderer/lib/dom"
|
||||
import { EventBus } from "@renderer/lib/event-bus"
|
||||
import { jotaiStore } from "@renderer/lib/jotai"
|
||||
import i18next from "i18next"
|
||||
import { useAtom } from "jotai"
|
||||
import type { FC, PropsWithChildren } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useEffect, useLayoutEffect, useRef } from "react"
|
||||
import { I18nextProvider } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
export const I18nProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [currentI18NInstance, update] = useState(i18next)
|
||||
const loadingLangLock = new Set<string>()
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
EventBus.subscribe("I18N_UPDATE", () => {
|
||||
update(i18next.cloneInstance())
|
||||
}),
|
||||
[],
|
||||
const langChangedHandler = async (lang: string) => {
|
||||
const { t } = jotaiStore.get(i18nAtom)
|
||||
if (loadingLangLock.has(lang)) return
|
||||
const isSupport = currentSupportedLanguages.includes(lang)
|
||||
if (!isSupport) {
|
||||
return
|
||||
}
|
||||
const loaded = i18next.getResourceBundle(lang, defaultNS)
|
||||
|
||||
if (loaded) {
|
||||
EventBus.dispatch("I18N_UPDATE", lang)
|
||||
return
|
||||
}
|
||||
|
||||
loadingLangLock.add(lang)
|
||||
|
||||
const rootGlobbyMap = import.meta.glob("@locales/*.json")
|
||||
const nsGlobbyMap = import.meta.glob("@locales/namespaces/*/*.json")
|
||||
|
||||
const rootResources = await rootGlobbyMap[`../../locales/${lang}.json`]()
|
||||
.then((m: any) => m.default)
|
||||
.catch(() => {
|
||||
toast.error(`${t("common:tips.load-lng-error")}: ${lang}`)
|
||||
})
|
||||
|
||||
i18next.addResourceBundle(lang, defaultNS, rootResources, true, true)
|
||||
|
||||
const namespaces = Object.keys(defaultResources.en)
|
||||
|
||||
const res = await Promise.allSettled(
|
||||
namespaces.map(async (ns) => {
|
||||
if (ns === defaultNS) return
|
||||
|
||||
const loader = nsGlobbyMap[`../../locales/namespaces/${ns}/${lang}.json`]
|
||||
|
||||
if (!loader) return
|
||||
const nsResources = await loader().then((m: any) => m.default)
|
||||
|
||||
i18next.addResourceBundle(lang, ns, nsResources, true, true)
|
||||
}),
|
||||
)
|
||||
|
||||
for (const r of res) {
|
||||
if (r.status === "rejected") {
|
||||
toast.error(`${t("common:tips.load-lng-error")}: ${lang}`)
|
||||
loadingLangLock.delete(lang)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
await i18next.reloadResources()
|
||||
await i18next.changeLanguage(lang)
|
||||
loadingLangLock.delete(lang)
|
||||
}
|
||||
|
||||
langChangedHandler(getGeneralSettings().language)
|
||||
export const I18nProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [currentI18NInstance, update] = useAtom(i18nAtom)
|
||||
|
||||
if (import.meta.env.DEV)
|
||||
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/rules-of-hooks
|
||||
useEffect(
|
||||
() =>
|
||||
EventBus.subscribe("I18N_UPDATE", (lang) => {
|
||||
nextFrame(() => {
|
||||
update(
|
||||
i18next.cloneInstance({
|
||||
lng: lang,
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
[update],
|
||||
)
|
||||
|
||||
const callOnce = useRef(false)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const i18next = currentI18NInstance
|
||||
|
||||
i18next.on("languageChanged", langChangedHandler)
|
||||
|
||||
return () => {
|
||||
i18next.off("languageChanged")
|
||||
}
|
||||
}, [currentI18NInstance])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (callOnce.current) return
|
||||
const { language } = getGeneralSettings()
|
||||
|
||||
i18next.changeLanguage(language)
|
||||
callOnce.current = true
|
||||
}, [])
|
||||
|
||||
return <I18nextProvider i18n={currentI18NInstance}>{children}</I18nextProvider>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@
|
|||
"@main/*": ["src/main/*"],
|
||||
"@shared/*": ["src/shared/src/*"],
|
||||
"@pkg": ["./package.json"],
|
||||
"@env": ["./src/env.ts"]
|
||||
"@env": ["./src/env.ts"],
|
||||
"@locales/*": ["./locales/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue