diff --git a/bun.lockb b/bun.lockb index 4c2db6c..5d3ebef 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index fb2182d..58c36f1 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,12 @@ "clsx": "^2.1.1", "country-flag-icons": "^1.5.13", "framer-motion": "^11.11.17", + "i18next": "^24.0.0", "lucide-react": "^0.460.0", "luxon": "^3.5.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-i18next": "^15.1.1", "react-router-dom": "^6.28.0", "react-use-websocket": "^4.11.1", "recharts": "^2.13.3", diff --git a/src/components/Header.tsx b/src/components/Header.tsx index ee204c7..ce1372c 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,4 +1,3 @@ -// import { LanguageSwitcher } from "@/components/LanguageSwitcher"; import { ModeToggle } from "@/components/ThemeSwitcher"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; @@ -6,6 +5,8 @@ import { fetchLoginUser } from "@/lib/nezha-api"; import { useQuery } from "@tanstack/react-query"; import { DateTime } from "luxon"; import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { LanguageSwitcher } from "./LanguageSwitcher"; function Header() { return ( @@ -32,7 +33,7 @@ function Header() {
- {/* */} +
@@ -80,6 +81,7 @@ const useInterval = (callback: () => void, delay: number | null) => { }, [delay]); }; function Overview() { + const { t } = useTranslation(); const [mouted, setMounted] = useState(false); useEffect(() => { setMounted(true); @@ -94,7 +96,7 @@ function Overview() { }, 1000); return (
-

👋 Overview

+

👋 {t("overview")}

where the time is

{mouted ? ( diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..872ebe1 --- /dev/null +++ b/src/components/LanguageSwitcher.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +import { CheckCircleIcon } from "@heroicons/react/20/solid"; +import { useTranslation } from "react-i18next"; + +export function LanguageSwitcher() { + const { t, i18n } = useTranslation(); + + const locale = i18n.language; + + const handleSelect = (e: Event, newLocale: string) => { + e.preventDefault(); // 阻止默认的关闭行为 + i18n.changeLanguage(newLocale); + }; + + const localeItems = [ + { name: t("language.zh-CN"), code: "zh-CN" }, + { name: t("language.zh-TW"), code: "zh-TW" }, + { name: t("language.en"), code: "en" }, + ]; + + return ( + + + + + + {localeItems.map((item) => ( + handleSelect(e, item.code)} + className={locale === item.code ? "bg-muted gap-3" : ""} + > + {item.name}{" "} + {locale === item.code && } + + ))} + + + ); +} diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx index b8b1384..2926e14 100644 --- a/src/components/ThemeSwitcher.tsx +++ b/src/components/ThemeSwitcher.tsx @@ -9,8 +9,11 @@ import { cn } from "@/lib/utils"; import { Moon, Sun } from "lucide-react"; import { Theme } from "@/components/ThemeProvider"; import { useTheme } from "../hooks/use-theme"; +import { CheckCircleIcon } from "@heroicons/react/20/solid"; +import { useTranslation } from 'react-i18next'; export function ModeToggle() { + const {t} = useTranslation(); const { setTheme, theme } = useTheme(); const handleSelect = (e: Event, newTheme: Theme) => { @@ -36,19 +39,22 @@ export function ModeToggle() { className={cn({ "gap-3 bg-muted": theme === "light" })} onSelect={(e) => handleSelect(e, "light")} > - Light + {t("theme.light")} + {theme === "light" && } handleSelect(e, "dark")} > - Dark + {t("theme.dark")} + {theme === "dark" && } handleSelect(e, "system")} > - System + {t("theme.system")} + {theme === "system" && } diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index e209caa..702e290 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -63,7 +63,7 @@ const DropdownMenuContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-2xl data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className, )} {...props} @@ -81,7 +81,7 @@ const DropdownMenuItem = React.forwardRef<