diff --git a/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx b/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx index 7b1bb61b0..7f36d021a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx @@ -16,7 +16,6 @@ import { import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { DocumentUploadDialogProvider } from "@/components/assistant-ui/document-upload-popup"; import { DashboardBreadcrumb } from "@/components/dashboard-breadcrumb"; -import { LanguageSwitcher } from "@/components/LanguageSwitcher"; import { LayoutDataProvider } from "@/components/layout"; import { OnboardingTour } from "@/components/onboarding-tour"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; @@ -200,7 +199,6 @@ export function DashboardClientLayout({ } - languageSwitcher={} > {children} diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 7f55e295f..1238cf28e 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -34,14 +34,12 @@ interface LayoutDataProviderProps { searchSpaceId: string; children: React.ReactNode; breadcrumb?: React.ReactNode; - languageSwitcher?: React.ReactNode; } export function LayoutDataProvider({ searchSpaceId, children, breadcrumb, - languageSwitcher, }: LayoutDataProviderProps) { const t = useTranslations("dashboard"); const tCommon = useTranslations("common"); @@ -375,7 +373,6 @@ export function LayoutDataProvider({ onLogout={handleLogout} pageUsage={pageUsage} breadcrumb={breadcrumb} - languageSwitcher={languageSwitcher} theme={theme} onToggleTheme={handleToggleTheme} isChatPage={isChatPage} diff --git a/surfsense_web/components/layout/ui/header/Header.tsx b/surfsense_web/components/layout/ui/header/Header.tsx index 182934d27..4244bba0d 100644 --- a/surfsense_web/components/layout/ui/header/Header.tsx +++ b/surfsense_web/components/layout/ui/header/Header.tsx @@ -7,7 +7,6 @@ import { NotificationButton } from "@/components/notifications/NotificationButto interface HeaderProps { breadcrumb?: React.ReactNode; - languageSwitcher?: React.ReactNode; theme?: string; onToggleTheme?: () => void; mobileMenuTrigger?: React.ReactNode; @@ -15,7 +14,6 @@ interface HeaderProps { export function Header({ breadcrumb, - languageSwitcher, theme, onToggleTheme, mobileMenuTrigger, @@ -45,8 +43,6 @@ export function Header({ {theme === "dark" ? "Light mode" : "Dark mode"} )} - - {languageSwitcher} ); diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index ed3a09099..3047f84b5 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -35,7 +35,6 @@ interface LayoutShellProps { onLogout?: () => void; pageUsage?: PageUsage; breadcrumb?: React.ReactNode; - languageSwitcher?: React.ReactNode; theme?: string; onToggleTheme?: () => void; defaultCollapsed?: boolean; @@ -69,7 +68,6 @@ export function LayoutShell({ onLogout, pageUsage, breadcrumb, - languageSwitcher, theme, onToggleTheme, defaultCollapsed = false, @@ -88,7 +86,6 @@ export function LayoutShell({
setMobileMenuOpen(true)} />} @@ -172,7 +169,6 @@ export function LayoutShell({
diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx index f67dbf7c6..376f27d06 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx @@ -1,19 +1,30 @@ "use client"; -import { ChevronUp, LogOut, Settings } from "lucide-react"; +import { ChevronUp, Languages, LogOut, Settings } from "lucide-react"; import { useTranslations } from "next-intl"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, + DropdownMenuPortal, DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { useLocaleContext } from "@/contexts/LocaleContext"; import { cn } from "@/lib/utils"; import type { User } from "../../types/layout.types"; +// Supported languages configuration +const LANGUAGES = [ + { code: "en" as const, name: "English", flag: "🇺🇸" }, + { code: "zh" as const, name: "简体中文", flag: "🇨🇳" }, +]; + interface SidebarUserProfileProps { user: User; onUserSettings?: () => void; @@ -101,10 +112,15 @@ export function SidebarUserProfile({ isCollapsed = false, }: SidebarUserProfileProps) { const t = useTranslations("sidebar"); + const { locale, setLocale } = useLocaleContext(); const bgColor = stringToColor(user.email); const initials = getInitials(user.email); const displayName = user.name || user.email.split("@")[0]; + const handleLanguageChange = (newLocale: "en" | "zh") => { + setLocale(newLocale); + }; + // Collapsed view - just show avatar with dropdown if (isCollapsed) { return ( @@ -118,7 +134,8 @@ export function SidebarUserProfile({ className={cn( "flex h-10 w-full items-center justify-center rounded-md", "hover:bg-accent transition-colors", - "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" + "focus:outline-none focus-visible:outline-none", + "data-[state=open]:bg-transparent" )} > @@ -129,7 +146,7 @@ export function SidebarUserProfile({ {displayName} - +
@@ -147,6 +164,34 @@ export function SidebarUserProfile({ {t("user_settings")} + + + + {t("language")} + + + + {LANGUAGES.map((language) => { + const isSelected = locale === language.code; + return ( + handleLanguageChange(language.code)} + className={cn( + "mb-1 last:mb-0", + !isSelected && "focus:bg-transparent hover:bg-transparent", + isSelected && "bg-accent focus:!bg-accent hover:!bg-accent" + )} + > + {language.flag} + {language.name} + + ); + })} + + + + @@ -169,7 +214,8 @@ export function SidebarUserProfile({ className={cn( "flex w-full items-center gap-2 px-2 py-3 text-left", "hover:bg-accent transition-colors", - "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" + "focus:outline-none focus-visible:outline-none", + "data-[state=open]:bg-transparent" )} > @@ -185,7 +231,7 @@ export function SidebarUserProfile({ - +
@@ -203,6 +249,34 @@ export function SidebarUserProfile({ {t("user_settings")} + + + + {t("language")} + + + + {LANGUAGES.map((language) => { + const isSelected = locale === language.code; + return ( + handleLanguageChange(language.code)} + className={cn( + "mb-1 last:mb-0", + !isSelected && "focus:bg-transparent hover:bg-transparent", + isSelected && "bg-accent focus:!bg-accent hover:!bg-accent" + )} + > + {language.flag} + {language.name} + + ); + })} + + + + diff --git a/surfsense_web/components/new-chat/model-selector.tsx b/surfsense_web/components/new-chat/model-selector.tsx index ead378e86..f9ebc0077 100644 --- a/surfsense_web/components/new-chat/model-selector.tsx +++ b/surfsense_web/components/new-chat/model-selector.tsx @@ -265,8 +265,8 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp onSelect={() => handleSelectConfig(config)} className={cn( "mx-2 rounded-lg mb-1 cursor-pointer", - "aria-selected:bg-accent/50", - isSelected && "bg-accent/80" + !isSelected && "data-[selected=true]:bg-transparent hover:bg-transparent", + isSelected && "bg-accent/80 data-[selected=true]:!bg-accent/80 hover:!bg-accent/80" )} >
@@ -327,8 +327,8 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp onSelect={() => handleSelectConfig(config)} className={cn( "mx-2 rounded-lg mb-1 cursor-pointer", - "aria-selected:bg-accent/50", - isSelected && "bg-accent/80" + !isSelected && "data-[selected=true]:bg-transparent hover:bg-transparent", + isSelected && "bg-accent/80 data-[selected=true]:!bg-accent/80 hover:!bg-accent/80" )} >
diff --git a/surfsense_web/components/ui/dropdown-menu.tsx b/surfsense_web/components/ui/dropdown-menu.tsx index 810827ea4..a9df1a5b2 100644 --- a/surfsense_web/components/ui/dropdown-menu.tsx +++ b/surfsense_web/components/ui/dropdown-menu.tsx @@ -182,13 +182,13 @@ function DropdownMenuSubTrigger({ data-slot="dropdown-menu-sub-trigger" data-inset={inset} className={cn( - "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8", + "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className )} {...props} > {children} - + ); } diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json index 0c6fe63a8..4d58e51f4 100644 --- a/surfsense_web/messages/en.json +++ b/surfsense_web/messages/en.json @@ -687,6 +687,7 @@ "expand_sidebar": "Expand sidebar", "collapse_sidebar": "Collapse sidebar", "user_settings": "User settings", + "language": "Language", "logout": "Logout" }, "errors": { diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json index b48e3e9c7..9a9061a52 100644 --- a/surfsense_web/messages/zh.json +++ b/surfsense_web/messages/zh.json @@ -672,6 +672,7 @@ "expand_sidebar": "展开侧边栏", "collapse_sidebar": "收起侧边栏", "user_settings": "用户设置", + "language": "语言", "logout": "退出登录" }, "errors": {