diff --git a/bun.lock b/bun.lock index a792c316..a6da890c 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "name": "librechat-admin-panel", "dependencies": { "@clickhouse/click-ui": "0.15.0", + "@fontsource-variable/vazirmatn": "^5.3.0", "@librechat/data-schemas": "^0.0.74", "@radix-ui/react-dialog": "1.3.1", "@tailwindcss/vite": "^4.3.3", @@ -231,6 +232,8 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="], + "@fontsource-variable/vazirmatn": ["@fontsource-variable/vazirmatn@5.3.0", "", {}, "sha512-ZA68W8JHbMdNzzwjYHUWKB2PH22QpxNEnk0ukcXM+XiqYMV6ZWJZAKuNXcrMR2JjuIAwsfAU3KknCXPI6wXUvw=="], + "@h6s/calendar": ["@h6s/calendar@2.2.0", "", { "peerDependencies": { "react": ">= 18", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-o1fb4RlMWDRIOiMEX65vN5qDTiD/OyC++EkJzmoz9l1vUOkN89gTE+wNE83XZBi3hbYqMV3CNhgKf51n/0hcBw=="], "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], diff --git a/package.json b/package.json index 7f414e16..f1d2c0c0 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "@clickhouse/click-ui": "0.15.0", + "@fontsource-variable/vazirmatn": "^5.3.0", "@librechat/data-schemas": "^0.0.74", "@radix-ui/react-dialog": "1.3.1", "@tailwindcss/vite": "^4.3.3", diff --git a/src/components/CommandMenu.tsx b/src/components/CommandMenu.tsx index 870f15ff..925b57cb 100644 --- a/src/components/CommandMenu.tsx +++ b/src/components/CommandMenu.tsx @@ -6,8 +6,9 @@ import { VisuallyHidden } from '@radix-ui/react-visually-hidden'; import { Title as DialogTitle, Description as DialogDescription } from '@radix-ui/react-dialog'; import type * as t from '@/types'; import { matchesQuery, searchConfigIndex } from './configuration/search'; -import { useSearchIndex, useLocalize, useReturnFocus } from '@/hooks'; +import { useSearchIndex, useLocalize, useLanguage, useReturnFocus } from '@/hooks'; import { CONFIG_TABS } from './configuration/configMeta'; +import { LANGUAGES } from '@/constants'; import { pageTitle, restoreFocusLater } from '@/utils'; import { CommandDialog } from '@/components/shared'; import { useTheme } from '@/contexts/ThemeContext'; @@ -24,6 +25,7 @@ export function CommandMenu({ open, onOpenChange }: t.CommandMenuProps) { const localize = useLocalize(); const router = useRouter(); const { setTheme } = useTheme(); + const { setLanguage } = useLanguage(); const { items: settings } = useSearchIndex(localize, open); const [search, setSearch] = useState(''); @@ -111,6 +113,14 @@ export function CommandMenu({ open, onOpenChange }: t.CommandMenuProps) { [setTheme, close], ); + const selectLanguage = useCallback( + (value: t.LanguageCode) => { + setLanguage(value); + close(); + }, + [setLanguage, close], + ); + const navigation: t.StaticCommand[] = [ { id: 'nav-home', @@ -190,6 +200,15 @@ export function CommandMenu({ open, onOpenChange }: t.CommandMenuProps) { keywords: ['theme', 'system', 'auto', 'mode', 'appearance'], onSelect: () => selectTheme('system'), }, + ...LANGUAGES.map( + (option): t.StaticCommand => ({ + id: `language-${option.code}`, + icon: 'globe', + label: localize('com_cmdk_set_language', { language: option.nativeName }), + keywords: ['language', 'locale', 'translation', option.code, option.nativeName], + onSelect: () => selectLanguage(option.code), + }), + ), ]; const query = search.trim(); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 04bbdaf8..1e3afd2b 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -32,7 +32,7 @@ export function Header({ title, onSearchClick, children }: t.HeaderProps) {