Skip to content

Commit ec949bd

Browse files
committed
feat(i18n): add English and Chinese localization files for errors and help sections
feat(locales): implement Chinese translations for common phrases and UI elements fix(stats): update stats page to reflect changes in Rust stats payload and remove tags section refactor(api): modify RustSpec and RustStats interfaces for improved clarity and consistency chore(deps): update dependencies and ensure deduplication in Vite config chore(lock): update pnpm-lock.yaml with new dependencies for i18next and react-i18next
1 parent b85ff28 commit ec949bd

25 files changed

+1669
-202
lines changed

packages/ui-vite/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,27 @@
1616
"dependencies": {
1717
"@dagrejs/dagre": "^1.1.8",
1818
"@leanspec/ui-components": "workspace:*",
19+
"@radix-ui/react-tooltip": "^1.2.8",
1920
"clsx": "^2.1.1",
2021
"cmdk": "^1.1.1",
2122
"dayjs": "^1.11.19",
2223
"fuse.js": "^7.1.0",
2324
"github-slugger": "^2.0.0",
25+
"i18next": "^25.7.3",
2426
"lucide-react": "^0.553.0",
2527
"mermaid": "^11.12.2",
2628
"react": "^19.2.3",
2729
"react-dom": "^19.2.3",
30+
"react-i18next": "^16.5.0",
2831
"react-markdown": "^9.1.0",
2932
"react-router-dom": "^7.11.0",
3033
"react-syntax-highlighter": "^16.1.0",
3134
"react-window": "^2.2.3",
3235
"reactflow": "^11.11.4",
3336
"recharts": "^3.6.0",
3437
"rehype-highlight": "^7.0.2",
35-
"rehype-slug": "^6.0.0",
3638
"rehype-raw": "^7.0.0",
39+
"rehype-slug": "^6.0.0",
3740
"remark-gfm": "^4.0.1",
3841
"tailwind-merge": "^3.4.0"
3942
},
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 17 additions & 0 deletions
Loading
Lines changed: 17 additions & 0 deletions
Loading
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { useEffect, useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { Languages } from 'lucide-react';
4+
import { Button } from '@leanspec/ui-components';
5+
import { cn } from '../lib/utils';
6+
7+
const languages = [
8+
{ code: 'en', labelKey: 'language.english', shortLabel: 'EN' },
9+
{ code: 'zh-CN', labelKey: 'language.chinese', shortLabel: '中文' },
10+
];
11+
12+
export function LanguageSwitcher() {
13+
const { i18n, t } = useTranslation('common');
14+
const [mounted, setMounted] = useState(false);
15+
const [open, setOpen] = useState(false);
16+
17+
useEffect(() => {
18+
setMounted(true);
19+
}, []);
20+
21+
const changeLanguage = (lng: string) => {
22+
i18n.changeLanguage(lng);
23+
setOpen(false);
24+
};
25+
26+
if (!mounted) {
27+
return (
28+
<Button variant="ghost" size="sm" className="h-9 w-9 p-0" aria-hidden>
29+
<Languages className="h-4 w-4" />
30+
</Button>
31+
);
32+
}
33+
34+
return (
35+
<div className="relative">
36+
<Button
37+
variant="ghost"
38+
size="sm"
39+
className="h-9 w-9 p-0"
40+
onClick={() => setOpen(!open)}
41+
aria-label={t('language.changeLanguage')}
42+
>
43+
<Languages className="h-4 w-4" />
44+
</Button>
45+
{open && (
46+
<>
47+
<div className="fixed inset-0 z-50" onClick={() => setOpen(false)} />
48+
<div className="absolute right-0 mt-2 w-32 z-50 rounded-md border bg-popover p-1 shadow-md">
49+
{languages.map((language) => (
50+
<button
51+
key={language.code}
52+
onClick={() => changeLanguage(language.code)}
53+
className={cn(
54+
'w-full text-left px-2 py-1.5 text-sm rounded-sm hover:bg-accent transition-colors',
55+
i18n.language === language.code && 'bg-accent'
56+
)}
57+
>
58+
{t(language.labelKey)}
59+
</button>
60+
))}
61+
</div>
62+
</>
63+
)}
64+
</div>
65+
);
66+
}

packages/ui-vite/src/components/Layout.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Outlet, useLocation } from 'react-router-dom';
2-
import { useState } from 'react';
2+
import { useState, useEffect } from 'react';
33
import { Navigation } from './Navigation';
44
import { MainSidebar } from './MainSidebar';
55
import { useGlobalShortcuts } from '../hooks/useKeyboardShortcuts';
@@ -43,16 +43,31 @@ function KeyboardShortcutsHelp({ onClose }: { onClose: () => void }) {
4343

4444
export function Layout() {
4545
const [showShortcuts, setShowShortcuts] = useState(false);
46+
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
4647
const location = useLocation();
4748

4849
// Register global keyboard shortcuts
4950
useGlobalShortcuts();
5051

52+
// Expose toggle function to window for Navigation component
53+
useEffect(() => {
54+
(window as any).toggleMainSidebar = () => {
55+
setMobileSidebarOpen(prev => !prev);
56+
};
57+
58+
return () => {
59+
delete (window as any).toggleMainSidebar;
60+
};
61+
}, []);
62+
5163
return (
5264
<div className="min-h-screen flex flex-col bg-background">
5365
<Navigation onShowShortcuts={() => setShowShortcuts(true)} />
5466
<div className="flex w-full min-w-0">
55-
<MainSidebar />
67+
<MainSidebar
68+
mobileOpen={mobileSidebarOpen}
69+
onMobileClose={() => setMobileSidebarOpen(false)}
70+
/>
5671
<main className="flex-1 min-w-0 w-full lg:w-[calc(100vw-var(--main-sidebar-width,240px))] px-4 py-6 lg:px-6">
5772
<ErrorBoundary key={location.pathname} onReset={() => window.location.reload()}>
5873
<PageTransition>
Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useState } from 'react';
22
import { Link, useLocation } from 'react-router-dom';
3-
import { Home, FileText, BarChart3, Network, Settings, ChevronLeft, ChevronRight, BookOpen } from 'lucide-react';
3+
import { Home, FileText, BarChart3, Network, Settings, ChevronLeft, ChevronRight, BookOpen, X } from 'lucide-react';
44
import { cn } from '../lib/utils';
55
import { ProjectSwitcher } from './ProjectSwitcher';
66

@@ -21,7 +21,12 @@ const navItems: NavItem[] = [
2121
{ path: '/settings', label: 'Settings', icon: Settings },
2222
];
2323

24-
export function MainSidebar() {
24+
interface MainSidebarProps {
25+
mobileOpen?: boolean;
26+
onMobileClose?: () => void;
27+
}
28+
29+
export function MainSidebar({ mobileOpen = false, onMobileClose }: MainSidebarProps) {
2530
const location = useLocation();
2631
const [collapsed, setCollapsed] = useState(false);
2732

@@ -35,51 +40,87 @@ export function MainSidebar() {
3540
localStorage.setItem(STORAGE_KEY, String(collapsed));
3641
}, [collapsed]);
3742

43+
// Close mobile sidebar when route changes
44+
useEffect(() => {
45+
if (mobileOpen && onMobileClose) {
46+
onMobileClose();
47+
}
48+
}, [location.pathname]);
49+
3850
return (
39-
<aside
40-
className={cn(
41-
'hidden lg:flex flex-col border-r bg-background h-[calc(100vh-3.5rem)] sticky top-14 transition-all duration-300',
42-
collapsed ? 'w-16' : 'w-60'
51+
<>
52+
{/* Mobile overlay */}
53+
{mobileOpen && (
54+
<div
55+
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
56+
onClick={onMobileClose}
57+
/>
4358
)}
44-
>
45-
<div className="px-3 py-4">
46-
<ProjectSwitcher />
47-
</div>
4859

49-
<nav className="flex-1 px-2 space-y-1">
50-
{navItems.map((item) => {
51-
const Icon = item.icon;
52-
const isActive = item.path === '/'
53-
? location.pathname === '/'
54-
: location.pathname.startsWith(item.path);
60+
{/* Sidebar */}
61+
<aside
62+
className={cn(
63+
'flex flex-col border-r bg-background h-[calc(100vh-3.5rem)] transition-all duration-300',
64+
// Desktop styles
65+
'hidden lg:flex sticky top-14',
66+
collapsed ? 'w-16' : 'w-60',
67+
// Mobile styles
68+
'lg:hidden fixed left-0 top-14 z-50 w-60',
69+
mobileOpen ? 'translate-x-0' : '-translate-x-full',
70+
// Show on desktop OR when mobile menu is open
71+
'lg:translate-x-0 lg:flex'
72+
)}
73+
>
74+
{/* Mobile close button */}
75+
<div className="lg:hidden flex justify-end p-2 border-b">
76+
<button
77+
onClick={onMobileClose}
78+
className="p-2 hover:bg-secondary rounded-md transition-colors"
79+
aria-label="Close menu"
80+
>
81+
<X className="h-5 w-5" />
82+
</button>
83+
</div>
84+
85+
<div className="px-3 py-4">
86+
<ProjectSwitcher />
87+
</div>
88+
89+
<nav className="flex-1 px-2 space-y-1">
90+
{navItems.map((item) => {
91+
const Icon = item.icon;
92+
const isActive = item.path === '/'
93+
? location.pathname === '/'
94+
: location.pathname.startsWith(item.path);
5595

56-
return (
57-
<Link
58-
key={item.path}
59-
to={item.path}
60-
className={cn(
61-
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors',
62-
'hover:bg-accent hover:text-accent-foreground',
63-
isActive && 'bg-accent text-accent-foreground font-medium',
64-
collapsed && 'justify-center px-2'
65-
)}
66-
>
67-
<Icon className={cn('h-5 w-5 shrink-0', isActive && 'text-primary')} />
68-
{!collapsed && <span className="truncate">{item.label}</span>}
69-
</Link>
70-
);
71-
})}
72-
</nav>
96+
return (
97+
<Link
98+
key={item.path}
99+
to={item.path}
100+
className={cn(
101+
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors',
102+
'hover:bg-accent hover:text-accent-foreground',
103+
isActive && 'bg-accent text-accent-foreground font-medium',
104+
collapsed && 'justify-center px-2'
105+
)}
106+
>
107+
<Icon className={cn('h-5 w-5 shrink-0', isActive && 'text-primary')} />
108+
{!collapsed && <span className="truncate">{item.label}</span>}
109+
</Link>
110+
);
111+
})}
112+
</nav>
73113

74-
<div className="p-2 border-t">
75-
<button
76-
onClick={() => setCollapsed((prev) => !prev)}
77-
className={cn('w-full flex items-center justify-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-secondary transition-colors', collapsed && 'px-2')}
78-
>
79-
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
80-
{!collapsed && <span className="text-xs">Collapse</span>}
81-
</button>
82-
</div>
83-
</aside>
114+
<div className="p-2 border-t hidden lg:block">
115+
<button
116+
onClick={() => setCollapsed((prev) => !prev)}
117+
className={cn('w-full flex items-center justify-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-secondary transition-colors', collapsed && 'px-2')}
118+
>
119+
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
120+
{!collapsed && <span className="text-xs">Collapse</span>}
121+
</button>
122+
</div>
123+
</aside>
124+
</>
84125
);
85126
}

0 commit comments

Comments
 (0)