mirror of
https://github.com/fankes/beszel.git
synced 2025-10-20 02:09:28 +08:00
dynamically load translation files
This commit is contained in:
@@ -6,6 +6,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
|||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import languages from "../lib/languages.json"
|
import languages from "../lib/languages.json"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { setLang } from "@/lib/i18n"
|
||||||
|
|
||||||
export function LangToggle() {
|
export function LangToggle() {
|
||||||
const { i18n } = useTranslation()
|
const { i18n } = useTranslation()
|
||||||
@@ -27,7 +28,7 @@ export function LangToggle() {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={lang}
|
key={lang}
|
||||||
className={cn("pl-4", lang === i18n.language ? "font-bold" : "")}
|
className={cn("pl-4", lang === i18n.language ? "font-bold" : "")}
|
||||||
onClick={() => i18n.changeLanguage(lang)}
|
onClick={() => setLang(lang)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
@@ -29,7 +29,7 @@ import {
|
|||||||
import { AddSystemButton } from "./add-system"
|
import { AddSystemButton } from "./add-system"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
const CommandPalette = lazy(() => import("./command-palette.tsx"))
|
const CommandPalette = lazy(() => import("./command-palette"))
|
||||||
|
|
||||||
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
|
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export default function Navbar() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center h-14 md:h-16 bg-card px-4 pr-3 sm:px-6 border bt-0 rounded-md my-4">
|
<div className="flex items-center h-14 md:h-16 bg-card px-4 pr-3 sm:px-6 border bt-0 rounded-md my-4">
|
||||||
<Link href="/" aria-label="Home" className={"p-2 pl-0"}>
|
<Link href="/" aria-label="Home" className={"p-2 pl-0"}>
|
||||||
<Logo className="h-[1.3em] fill-foreground" />
|
<Logo className="h-[1.15rem] md:h-[1.3em] fill-foreground" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<SearchButton />
|
<SearchButton />
|
||||||
|
@@ -10,6 +10,7 @@ import { useState, useEffect } from "react"
|
|||||||
// import { Input } from '@/components/ui/input'
|
// import { Input } from '@/components/ui/input'
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import languages from "../../../lib/languages.json"
|
import languages from "../../../lib/languages.json"
|
||||||
|
import { setLang } from "@/lib/i18n"
|
||||||
|
|
||||||
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
|
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
|
||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
@@ -54,7 +55,7 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
|||||||
<Label className="block" htmlFor="lang">
|
<Label className="block" htmlFor="lang">
|
||||||
{t("settings.general.language.preferred_language")}
|
{t("settings.general.language.preferred_language")}
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={i18n.language} onValueChange={(lang: string) => i18n.changeLanguage(lang)}>
|
<Select value={i18n.language} onValueChange={(lang: string) => setLang(lang)}>
|
||||||
<SelectTrigger id="lang">
|
<SelectTrigger id="lang">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
@@ -1,37 +1,24 @@
|
|||||||
import i18n from "i18next"
|
import i18n from "i18next"
|
||||||
import { initReactI18next } from "react-i18next"
|
import { initReactI18next } from "react-i18next"
|
||||||
|
|
||||||
import en from "../locales/en/translation.json"
|
|
||||||
import es from "../locales/es/translation.json"
|
|
||||||
import fr from "../locales/fr/translation.json"
|
|
||||||
import de from "../locales/de/translation.json"
|
|
||||||
import ru from "../locales/ru/translation.json"
|
|
||||||
import zhHans from "../locales/zh-CN/translation.json"
|
|
||||||
import zhHant from "../locales/zh-HK/translation.json"
|
|
||||||
|
|
||||||
// Custom language detector to use localStorage
|
// Custom language detector to use localStorage
|
||||||
const languageDetector: any = {
|
const languageDetector: any = {
|
||||||
type: "languageDetector",
|
type: "languageDetector",
|
||||||
async: true,
|
async: true,
|
||||||
detect: (callback: (lng: string) => void) => {
|
detect: (callback: (lng: string) => void) => {
|
||||||
const savedLanguage = localStorage.getItem("i18nextLng")
|
const savedLanguage = localStorage.getItem("i18nextLng")
|
||||||
const fallbackLanguage = (() => {
|
const zhVariantMap: Record<string, string> = {
|
||||||
switch (navigator.language) {
|
"zh-CN": "zh-CN",
|
||||||
case "zh-CN":
|
"zh-SG": "zh-CN",
|
||||||
case "zh-SG":
|
"zh-MY": "zh-CN",
|
||||||
case "zh-MY":
|
zh: "zh-CN",
|
||||||
case "zh":
|
"zh-Hans": "zh-CN",
|
||||||
case "zh-Hans":
|
"zh-HK": "zh-HK",
|
||||||
return "zh-CN"
|
"zh-TW": "zh-HK",
|
||||||
case "zh-HK":
|
"zh-MO": "zh-HK",
|
||||||
case "zh-TW":
|
"zh-Hant": "zh-HK",
|
||||||
case "zh-MO":
|
}
|
||||||
case "zh-Hant":
|
const fallbackLanguage = zhVariantMap[navigator.language] || navigator.language
|
||||||
return "zh-HK"
|
|
||||||
default:
|
|
||||||
return navigator.language
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
callback(savedLanguage || fallbackLanguage)
|
callback(savedLanguage || fallbackLanguage)
|
||||||
},
|
},
|
||||||
init: () => {},
|
init: () => {},
|
||||||
@@ -40,25 +27,39 @@ const languageDetector: any = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to dynamically load translation files
|
||||||
|
async function loadMessages(locale: string) {
|
||||||
|
try {
|
||||||
|
const translation = await import(`../locales/${locale}/translation.json`)
|
||||||
|
return translation.default
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error loading ${locale}`, error)
|
||||||
|
// Fallback to English if translation fails to load
|
||||||
|
const enTranslation = await import(`../locales/en/translation.json`)
|
||||||
|
return enTranslation.default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(languageDetector)
|
.use(languageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources: {
|
resources: {}, // Start with empty resources
|
||||||
en: { translation: en },
|
|
||||||
es: { translation: es },
|
|
||||||
fr: { translation: fr },
|
|
||||||
de: { translation: de },
|
|
||||||
ru: { translation: ru },
|
|
||||||
// Chinese (Simplified)
|
|
||||||
"zh-CN": { translation: zhHans },
|
|
||||||
// Chinese (Traditional)
|
|
||||||
"zh-HK": { translation: zhHant },
|
|
||||||
},
|
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Function to dynamically activate a language
|
||||||
|
export async function setLang(locale: string) {
|
||||||
|
const messages = await loadMessages(locale)
|
||||||
|
i18n.addResourceBundle(locale, "translation", messages)
|
||||||
|
await i18n.changeLanguage(locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize with detected/saved language
|
||||||
|
const initialLanguage = localStorage.getItem("i18nextLng") || navigator.language
|
||||||
|
setLang(initialLanguage)
|
||||||
|
|
||||||
export { i18n }
|
export { i18n }
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import "./index.css"
|
import "./index.css"
|
||||||
import { Suspense, lazy, useEffect, StrictMode } from "react"
|
// import { Suspense, lazy, useEffect, StrictMode } from "react"
|
||||||
|
import { Suspense, lazy, useEffect } from "react"
|
||||||
import ReactDOM from "react-dom/client"
|
import ReactDOM from "react-dom/client"
|
||||||
import Home from "./components/routes/home.tsx"
|
import Home from "./components/routes/home.tsx"
|
||||||
import { ThemeProvider } from "./components/theme-provider.tsx"
|
import { ThemeProvider } from "./components/theme-provider.tsx"
|
||||||
@@ -87,7 +88,9 @@ const Layout = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="container">{Navbar()}</div>
|
<div className="container">
|
||||||
|
<Navbar />
|
||||||
|
</div>
|
||||||
<div className="container mb-14 relative">
|
<div className="container mb-14 relative">
|
||||||
<App />
|
<App />
|
||||||
{copyContent && (
|
{copyContent && (
|
||||||
|
Reference in New Issue
Block a user