Skip to content

Commit

Permalink
fix: wait language load then switch to target language
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Sep 16, 2024
1 parent b9c6a8b commit bbe4ea6
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 87 deletions.
4 changes: 2 additions & 2 deletions locales/settings/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"feeds.tableHeaders.entryCount": "条目数",
"feeds.tableHeaders.name": "名称",
"feeds.tableHeaders.subscriptionCount": "订阅数",
"feeds.tableHeaders.tipAmount": "收到的打赏" ,
"feeds.tableHeaders.tipAmount": "收到的打赏",
"general.app": "应用程序",
"general.data_persist.description": "在本地保留数据以启用离线访问和本地搜索",
"general.data_persist.label": "保留数据以供离线使用",
Expand Down Expand Up @@ -159,7 +159,7 @@
"titles.about": "关于",
"titles.actions": "自动化",
"titles.appearance": "外观",
"titles.general": "常规",
"titles.general": "通用",
"titles.integration": "集成",
"titles.invitations": "邀请",
"titles.power": "Power",
Expand Down
83 changes: 83 additions & 0 deletions src/renderer/src/lib/load-language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { currentSupportedLanguages, dayjsLocaleImportMap } from "@renderer/@types/constants"
import { defaultResources } from "@renderer/@types/default-resource"
import { fallbackLanguage, i18nAtom, LocaleCache } from "@renderer/i18n"
import { jotaiStore } from "@renderer/lib/jotai"
import { isEmptyObject } from "@renderer/lib/utils"
import dayjs from "dayjs"
import i18next from "i18next"
import { toast } from "sonner"

const loadingLangLock = new Set<string>()
const loadedLangs = new Set<string>([fallbackLanguage])

export const loadLanguageAndApply = async (lang: string) => {
const dayjsImport = dayjsLocaleImportMap[lang]

if (dayjsImport) {
const [locale, loader] = dayjsImport
loader().then(() => {
dayjs.locale(locale)
})
}

const { t } = jotaiStore.get(i18nAtom)
if (loadingLangLock.has(lang)) return
const isSupport = currentSupportedLanguages.includes(lang)
if (!isSupport) {
return
}
const loaded = loadedLangs.has(lang)

if (loaded) {
return
}

loadingLangLock.add(lang)

if (import.meta.env.DEV) {
const nsGlobbyMap = import.meta.glob("@locales/*/*.json")

const namespaces = Object.keys(defaultResources.en)

const res = await Promise.allSettled(
namespaces.map(async (ns) => {
const loader = nsGlobbyMap[`../../locales/${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
}
}
} else {
const res = await eval(`import('/locales/${lang}.js')`)
.then((res: any) => res?.default || res)
.catch(() => {
toast.error(`${t("common:tips.load-lng-error")}: ${lang}`)
loadingLangLock.delete(lang)
return {}
})

if (isEmptyObject(res)) {
return
}
for (const namespace in res) {
i18next.addResourceBundle(lang, namespace, res[namespace], true, true)
}
}

await i18next.reloadResources()
await i18next.changeLanguage(lang)
LocaleCache.shared.set(lang)
loadedLangs.add(lang)
loadingLangLock.delete(lang)
}
10 changes: 7 additions & 3 deletions src/renderer/src/modules/settings/tabs/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import { IS_MANUAL_CHANGE_LANGUAGE_KEY } from "@renderer/constants"
import { fallbackLanguage } from "@renderer/i18n"
import { initPostHog } from "@renderer/initialize/posthog"
import { tipcClient } from "@renderer/lib/client"
import { loadLanguageAndApply } from "@renderer/lib/load-language"
import { clearLocalPersistStoreData } from "@renderer/store/utils/clear"
import { useQuery } from "@tanstack/react-query"
import i18next from "i18next"
import { useCallback, useEffect } from "react"
import { useTranslation } from "react-i18next"

Expand Down Expand Up @@ -200,7 +202,7 @@ export const VoiceSelector = () => {
}

export const LanguageSelector = () => {
const { t, i18n } = useTranslation("settings")
const { t } = useTranslation("settings")
const { t: langT } = useTranslation("lang")
const language = useGeneralSettingSelector((state) => state.language)

Expand All @@ -215,8 +217,10 @@ export const LanguageSelector = () => {
value={finalRenderLanguage}
onValueChange={(value) => {
localStorage.setItem(IS_MANUAL_CHANGE_LANGUAGE_KEY, "true")
setGeneralSetting("language", value as string)
i18n.changeLanguage(value as string)
loadLanguageAndApply(value as string).then(() => {
i18next.changeLanguage(value as string)
setGeneralSetting("language", value as string)
})
}}
>
<SelectTrigger size="sm" className="w-48">
Expand Down
86 changes: 4 additions & 82 deletions src/renderer/src/providers/i18n-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,94 +1,16 @@
import { currentSupportedLanguages, dayjsLocaleImportMap } from "@renderer/@types/constants"
import { defaultResources } from "@renderer/@types/default-resource"
import { currentSupportedLanguages } from "@renderer/@types/constants"
import { getGeneralSettings, setGeneralSetting } from "@renderer/atoms/settings/general"
import { IS_MANUAL_CHANGE_LANGUAGE_KEY } from "@renderer/constants"
import { fallbackLanguage, i18nAtom, LocaleCache } from "@renderer/i18n"
import { i18nAtom } from "@renderer/i18n"
import { EventBus } from "@renderer/lib/event-bus"
import { jotaiStore } from "@renderer/lib/jotai"
import { isEmptyObject } from "@renderer/lib/utils"
import dayjs from "dayjs"
import { loadLanguageAndApply } from "@renderer/lib/load-language"
import i18next from "i18next"
import LanguageDetector from "i18next-browser-languagedetector"
import { useAtom } from "jotai"
import type { FC, PropsWithChildren } from "react"
import { useEffect, useLayoutEffect, useRef } from "react"
import { I18nextProvider } from "react-i18next"
import { toast } from "sonner"

const loadingLangLock = new Set<string>()
const loadedLangs = new Set<string>([fallbackLanguage])

const langChangedHandler = async (lang: string) => {
const dayjsImport = dayjsLocaleImportMap[lang]

if (dayjsImport) {
const [locale, loader] = dayjsImport
loader().then(() => {
dayjs.locale(locale)
})
}

const { t } = jotaiStore.get(i18nAtom)
if (loadingLangLock.has(lang)) return
const isSupport = currentSupportedLanguages.includes(lang)
if (!isSupport) {
return
}
const loaded = loadedLangs.has(lang)

if (loaded) {
return
}

loadingLangLock.add(lang)

if (import.meta.env.DEV) {
const nsGlobbyMap = import.meta.glob("@locales/*/*.json")

const namespaces = Object.keys(defaultResources.en)

const res = await Promise.allSettled(
namespaces.map(async (ns) => {
const loader = nsGlobbyMap[`../../locales/${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
}
}
} else {
const res = await eval(`import('/locales/${lang}.js')`)
.then((res: any) => res?.default || res)
.catch(() => {
toast.error(`${t("common:tips.load-lng-error")}: ${lang}`)
loadingLangLock.delete(lang)
return {}
})

if (isEmptyObject(res)) {
return
}
for (const namespace in res) {
i18next.addResourceBundle(lang, namespace, res[namespace], true, true)
}
}

await i18next.reloadResources()
await i18next.changeLanguage(lang)
LocaleCache.shared.set(lang)
loadedLangs.add(lang)
loadingLangLock.delete(lang)
}
export const I18nProvider: FC<PropsWithChildren> = ({ children }) => {
const [currentI18NInstance, update] = useAtom(i18nAtom)

Expand All @@ -112,7 +34,7 @@ export const I18nProvider: FC<PropsWithChildren> = ({ children }) => {
useLayoutEffect(() => {
const i18next = currentI18NInstance

i18next.on("languageChanged", langChangedHandler)
i18next.on("languageChanged", loadLanguageAndApply)

return () => {
i18next.off("languageChanged")
Expand Down

0 comments on commit bbe4ea6

Please sign in to comment.