diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx
index 7bfe807..941769f 100644
--- a/components/chat-panel.tsx
+++ b/components/chat-panel.tsx
@@ -21,6 +21,11 @@ import { ChatInput } from "@/components/chat-input"
import { ModelConfigDialog } from "@/components/model-config-dialog"
import { ResetWarningModal } from "@/components/reset-warning-modal"
import { SettingsDialog } from "@/components/settings-dialog"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
import { useDiagram } from "@/contexts/diagram-context"
import { useDictionary } from "@/hooks/use-dictionary"
import { getSelectedAIConfig, useModelConfig } from "@/hooks/use-model-config"
@@ -30,7 +35,6 @@ import { type FileData, useFileProcessor } from "@/lib/use-file-processor"
import { useQuotaManager } from "@/lib/use-quota-manager"
import { formatXML, isMxCellXmlComplete, wrapWithMxFile } from "@/lib/utils"
import { ChatMessageDisplay } from "./chat-message-display"
-import LanguageToggle from "./language-toggle"
// localStorage keys for persistence
const STORAGE_MESSAGES_KEY = "next-ai-draw-io-messages"
@@ -1304,16 +1308,23 @@ Continue from EXACTLY where you stopped.`,
/>
-
-
-
+
+
+
+
+
+
+
+ {dict.nav.github}
+
+
-
{!isMobile && (
= {
- en: "EN",
- zh: "中文",
- ja: "日本語",
-}
-
-function LanguageToggleInner({ className = "" }: { className?: string }) {
- const router = useRouter()
- const pathname = usePathname() || "/"
- const search = useSearchParams()
- const [open, setOpen] = useState(false)
- const [value, setValue] = useState(i18n.defaultLocale)
- const ref = useRef(null)
-
- useEffect(() => {
- const seg = pathname.split("/").filter(Boolean)
- const first = seg[0]
- if (first && i18n.locales.includes(first as Locale))
- setValue(first as Locale)
- else setValue(i18n.defaultLocale)
- }, [pathname])
-
- useEffect(() => {
- function onDoc(e: MouseEvent) {
- if (!ref.current) return
- if (!ref.current.contains(e.target as Node)) setOpen(false)
- }
- if (open) document.addEventListener("mousedown", onDoc)
- return () => document.removeEventListener("mousedown", onDoc)
- }, [open])
-
- const changeLocale = (lang: string) => {
- const parts = pathname.split("/")
- if (parts.length > 1 && i18n.locales.includes(parts[1] as Locale)) {
- parts[1] = lang
- } else {
- parts.splice(1, 0, lang)
- }
- const newPath = parts.join("/") || "/"
- const searchStr = search?.toString() ? `?${search.toString()}` : ""
- setOpen(false)
- router.push(newPath + searchStr)
- }
-
- return (
-
-
- {open && (
-
-
- {i18n.locales.map((loc) => (
-
- ))}
-
-
- )}
-
- )
-}
-
-export default function LanguageToggle({
- className = "",
-}: {
- className?: string
-}) {
- return (
-
-
-
- }
- >
-
-
- )
-}
diff --git a/components/settings-dialog.tsx b/components/settings-dialog.tsx
index 029fc8d..720e039 100644
--- a/components/settings-dialog.tsx
+++ b/components/settings-dialog.tsx
@@ -1,7 +1,8 @@
"use client"
import { Moon, Sun } from "lucide-react"
-import { useEffect, useState } from "react"
+import { usePathname, useRouter, useSearchParams } from "next/navigation"
+import { Suspense, useEffect, useState } from "react"
import { Button } from "@/components/ui/button"
import {
Dialog,
@@ -12,8 +13,22 @@ import {
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { useDictionary } from "@/hooks/use-dictionary"
+import { i18n, type Locale } from "@/lib/i18n/config"
+
+const LANGUAGE_LABELS: Record = {
+ en: "English",
+ zh: "中文",
+ ja: "日本語",
+}
interface SettingsDialogProps {
open: boolean
@@ -36,7 +51,7 @@ function getStoredAccessCodeRequired(): boolean | null {
return stored === "true"
}
-export function SettingsDialog({
+function SettingsContent({
open,
onOpenChange,
onCloseProtectionChange,
@@ -46,6 +61,9 @@ export function SettingsDialog({
onToggleDarkMode,
}: SettingsDialogProps) {
const dict = useDictionary()
+ const router = useRouter()
+ const pathname = usePathname() || "/"
+ const search = useSearchParams()
const [accessCode, setAccessCode] = useState("")
const [closeProtection, setCloseProtection] = useState(true)
const [isVerifying, setIsVerifying] = useState(false)
@@ -53,6 +71,7 @@ export function SettingsDialog({
const [accessCodeRequired, setAccessCodeRequired] = useState(
() => getStoredAccessCodeRequired() ?? false,
)
+ const [currentLang, setCurrentLang] = useState("en")
useEffect(() => {
// Only fetch if not cached in localStorage
@@ -77,6 +96,17 @@ export function SettingsDialog({
})
}, [])
+ // Detect current language from pathname
+ useEffect(() => {
+ const seg = pathname.split("/").filter(Boolean)
+ const first = seg[0]
+ if (first && i18n.locales.includes(first as Locale)) {
+ setCurrentLang(first)
+ } else {
+ setCurrentLang(i18n.defaultLocale)
+ }
+ }, [pathname])
+
useEffect(() => {
if (open) {
const storedCode =
@@ -93,6 +123,18 @@ export function SettingsDialog({
}
}, [open])
+ const changeLanguage = (lang: string) => {
+ const parts = pathname.split("/")
+ if (parts.length > 1 && i18n.locales.includes(parts[1] as Locale)) {
+ parts[1] = lang
+ } else {
+ parts.splice(1, 0, lang)
+ }
+ const newPath = parts.join("/") || "/"
+ const searchStr = search?.toString() ? `?${search.toString()}` : ""
+ router.push(newPath + searchStr)
+ }
+
const handleSave = async () => {
if (!accessCodeRequired) return
@@ -131,128 +173,166 @@ export function SettingsDialog({
}
return (
-