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": {