diff --git a/src/components/AgentExecution.tsx b/src/components/AgentExecution.tsx index 7225738..655583a 100644 --- a/src/components/AgentExecution.tsx +++ b/src/components/AgentExecution.tsx @@ -25,6 +25,7 @@ import { StreamMessage } from "./StreamMessage"; import { ExecutionControlBar } from "./ExecutionControlBar"; import { ErrorBoundary } from "./ErrorBoundary"; import { useVirtualizer } from "@tanstack/react-virtual"; +import { useTranslation } from "react-i18next"; interface AgentExecutionProps { /** @@ -69,6 +70,7 @@ export const AgentExecution: React.FC = ({ onBack, className, }) => { + const { t } = useTranslation(); const [projectPath, setProjectPath] = useState(""); const [task, setTask] = useState(agent.default_task || ""); const [model, setModel] = useState(agent.model || "sonnet"); @@ -251,7 +253,7 @@ export const AgentExecution: React.FC = ({ const selected = await open({ directory: true, multiple: false, - title: "选择项目目录" + title: t('agentExecution.selectProjectDirectory') }); if (selected) { @@ -262,7 +264,7 @@ export const AgentExecution: React.FC = ({ console.error("Failed to select directory:", err); // More detailed error logging const errorMessage = err instanceof Error ? err.message : String(err); - setError(`选择目录失败: ${errorMessage}`); + setError(`${t('agentExecution.selectDirectoryFailed')}: ${errorMessage}`); } }; @@ -306,14 +308,14 @@ export const AgentExecution: React.FC = ({ setIsRunning(false); setExecutionStartTime(null); if (!event.payload) { - setError("智能体执行失败"); + setError(t('agentExecution.executionFailed')); } }); const cancelUnlisten = await listen(`agent-cancelled:${executionRunId}`, () => { setIsRunning(false); setExecutionStartTime(null); - setError("智能体执行已取消"); + setError(t('agentExecution.executionCancelled')); }); unlistenRefs.current = [outputUnlisten, errorUnlisten, completeUnlisten, cancelUnlisten]; @@ -327,7 +329,7 @@ export const AgentExecution: React.FC = ({ type: "result", subtype: "error", is_error: true, - result: `智能体执行失败: ${err instanceof Error ? err.message : '未知错误'}`, + result: `${t('agentExecution.executionFailed')}: ${err instanceof Error ? err.message : t('common.unknown')}`, duration_ms: 0, usage: { input_tokens: 0, @@ -366,7 +368,7 @@ export const AgentExecution: React.FC = ({ type: "result", subtype: "error", is_error: true, - result: "执行已被用户停止", + result: t('agentExecution.executionStopped'), duration_ms: elapsedTime * 1000, usage: { input_tokens: totalTokens, @@ -384,7 +386,7 @@ export const AgentExecution: React.FC = ({ type: "result", subtype: "error", is_error: true, - result: `停止执行失败: ${err instanceof Error ? err.message : '未知错误'}`, + result: `${t('agentExecution.stopFailed')}: ${err instanceof Error ? err.message : t('common.unknown')}`, duration_ms: elapsedTime * 1000, usage: { input_tokens: totalTokens, @@ -398,7 +400,7 @@ export const AgentExecution: React.FC = ({ if (isRunning) { // Show confirmation dialog before navigating away during execution const shouldLeave = window.confirm( - "智能体正在运行中。如果您离开此页面,智能体将继续在后台运行。您可以在 CC Agents 的 '运行中的会话' 标签页中查看运行状态。\n\n您要继续吗?" + t('agentExecution.navigationWarning') ); if (!shouldLeave) { return; @@ -520,12 +522,12 @@ export const AgentExecution: React.FC = ({ {isRunning && (
- 运行中 + {t('agentExecution.running')}
)}

- {isRunning ? "点击返回主菜单 - 在 CC Agents > 运行中的会话 中查看" : "执行 CC 智能体"} + {isRunning ? t('agentExecution.output') + " - " + t('agentExecution.running') : t('agentExecution.title')}

@@ -541,7 +543,7 @@ export const AgentExecution: React.FC = ({ className="flex items-center gap-2" > - 全屏 + {t('agentExecution.fullscreen')} = ({ className="flex items-center gap-2" > - 复制输出 + {t('agentExecution.copyOutput')} } @@ -563,7 +565,7 @@ export const AgentExecution: React.FC = ({ className="w-full justify-start" onClick={handleCopyAsJsonl} > - 复制为 JSONL + {t('agentExecution.copyAsJsonl')} } @@ -603,12 +605,12 @@ export const AgentExecution: React.FC = ({ {/* Project Path */}
- +
setProjectPath(e.target.value)} - placeholder="选择或输入项目路径" + placeholder={t('agentExecution.selectProjectPath')} disabled={isRunning} className="flex-1" /> @@ -625,7 +627,7 @@ export const AgentExecution: React.FC = ({ {/* Model Selection */}
- +
@@ -741,9 +743,9 @@ export const AgentExecution: React.FC = ({ {messages.length === 0 && !isRunning && (
-

准备执行

+

{t('agentExecution.readyToExecute')}

- 选择项目路径并输入任务以运行智能体 + {t('agentExecution.selectProject')}

)} @@ -752,7 +754,7 @@ export const AgentExecution: React.FC = ({
- 初始化智能体中... + {t('agentExecution.initializing')}
)} @@ -806,11 +808,11 @@ export const AgentExecution: React.FC = ({
{renderIcon()} -

{agent.name} - 输出

+

{agent.name} - {t('agentExecution.output')}

{isRunning && (
- 运行中 + {t('agentExecution.running')}
)}
@@ -823,7 +825,7 @@ export const AgentExecution: React.FC = ({ className="flex items-center gap-2" > - 复制输出 + {t('agentExecution.copyOutput')} } @@ -835,7 +837,7 @@ export const AgentExecution: React.FC = ({ className="w-full justify-start" onClick={handleCopyAsJsonl} > - 复制为 JSONL + {t('agentExecution.copyAsJsonl')}
} @@ -858,7 +860,7 @@ export const AgentExecution: React.FC = ({ className="flex items-center gap-2" > - 关闭 + {t('agentExecution.close')}
@@ -883,9 +885,9 @@ export const AgentExecution: React.FC = ({ {messages.length === 0 && !isRunning && (
-

准备执行

+

{t('agentExecution.readyToExecute')}

- 选择项目路径并输入任务以运行智能体 + {t('agentExecution.selectProject')}

)} @@ -894,7 +896,7 @@ export const AgentExecution: React.FC = ({
- 初始化智能体中... + {t('agentExecution.initializing')}
)} diff --git a/src/components/ClaudeBinaryDialog.tsx b/src/components/ClaudeBinaryDialog.tsx index 1e40b9b..e88e144 100644 --- a/src/components/ClaudeBinaryDialog.tsx +++ b/src/components/ClaudeBinaryDialog.tsx @@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { ExternalLink, FileQuestion, Terminal, AlertCircle, Loader2 } from "lucide-react"; import { ClaudeVersionSelector } from "./ClaudeVersionSelector"; +import { useTranslation } from "react-i18next"; interface ClaudeBinaryDialogProps { open: boolean; @@ -13,6 +14,7 @@ interface ClaudeBinaryDialogProps { } export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: ClaudeBinaryDialogProps) { + const { t } = useTranslation(); const [selectedInstallation, setSelectedInstallation] = useState(null); const [isValidating, setIsValidating] = useState(false); const [hasInstallations, setHasInstallations] = useState(true); @@ -39,7 +41,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C const handleSave = async () => { if (!selectedInstallation) { - onError("请选择一个 Claude 安装"); + onError(t('claudeBinary.selectInstallation')); return; } @@ -50,7 +52,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C onOpenChange(false); } catch (error) { console.error("Failed to save Claude binary path:", error); - onError(error instanceof Error ? error.message : "保存 Claude 二进制路径失败"); + onError(error instanceof Error ? error.message : t('claudeBinary.saveFailed')); } finally { setIsValidating(false); } @@ -62,29 +64,27 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C - 选择 Claude Code 安装 + {t('claudeBinary.title')} {checkingInstallations ? (
- 正在搜索 Claude 安装... + {t('claudeBinary.searching')}
) : hasInstallations ? (

- 在您的系统中发现了多个 Claude Code 安装。 - 请选择您想要使用的版本。 + {t('claudeBinary.multipleFound')}

) : ( <>

- 在常见安装位置未找到 Claude Code。 - 请安装 Claude Code 以继续。 + {t('claudeBinary.notFound')}

- 搜索位置: PATH, /usr/local/bin, + {t('claudeBinary.searchedLocations')} PATH, /usr/local/bin, /opt/homebrew/bin, ~/.nvm/versions/node/*/bin, ~/.claude/local, ~/.local/bin

@@ -94,7 +94,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C

- 提示: 您可以使用以下命令安装 Claude Code{" "} + {t('claudeBinary.tip')} 您可以使用以下命令安装 Claude Code{" "} npm install -g @claude

@@ -118,20 +118,20 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C className="mr-auto" > - 安装指南 + {t('claudeBinary.installationGuide')} diff --git a/src/components/ClaudeCodeSession.tsx b/src/components/ClaudeCodeSession.tsx index 5860388..b454a62 100644 --- a/src/components/ClaudeCodeSession.tsx +++ b/src/components/ClaudeCodeSession.tsx @@ -32,6 +32,7 @@ import { SplitPane } from "@/components/ui/split-pane"; import { WebviewPreview } from "./WebviewPreview"; import type { ClaudeStreamMessage } from "./AgentExecution"; import { useVirtualizer } from "@tanstack/react-virtual"; +import { useTranslation } from "react-i18next"; interface ClaudeCodeSessionProps { /** @@ -69,6 +70,7 @@ export const ClaudeCodeSession: React.FC = ({ className, onStreamingChange, }) => { + const { t } = useTranslation(); const [projectPath, setProjectPath] = useState(initialProjectPath || session?.project_path || ""); const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(false); @@ -275,7 +277,7 @@ export const ClaudeCodeSession: React.FC = ({ setIsFirstPrompt(false); } catch (err) { console.error("Failed to load session history:", err); - setError("加载会话历史失败"); + setError(t('sessions.loadHistoryFailed')); } finally { setIsLoading(false); } @@ -377,7 +379,7 @@ export const ClaudeCodeSession: React.FC = ({ const selected = await open({ directory: true, multiple: false, - title: "选择项目目录" + title: t('sessions.selectProjectDirectory') }); if (selected) { @@ -387,7 +389,7 @@ export const ClaudeCodeSession: React.FC = ({ } catch (err) { console.error("Failed to select directory:", err); const errorMessage = err instanceof Error ? err.message : String(err); - setError(`选择目录失败: ${errorMessage}`); + setError(t('sessions.selectDirectoryFailed', { error: errorMessage })); } }; @@ -395,7 +397,7 @@ export const ClaudeCodeSession: React.FC = ({ console.log('[ClaudeCodeSession] handleSendPrompt called with:', { prompt, model, projectPath, claudeSessionId, effectiveSession }); if (!projectPath) { - setError("请先选择项目目录"); + setError(t('sessions.selectProjectFirst')); return; } @@ -597,7 +599,7 @@ export const ClaudeCodeSession: React.FC = ({ } } catch (err) { console.error("Failed to send prompt:", err); - setError("发送提示失败"); + setError(t('sessions.sendPromptFailed')); setIsLoading(false); hasActiveSessionRef.current = false; } @@ -711,7 +713,7 @@ export const ClaudeCodeSession: React.FC = ({ const cancelMessage: ClaudeStreamMessage = { type: "system", subtype: "info", - result: "会话已被用户取消", + result: t('sessions.sessionCancelled'), timestamp: new Date().toISOString() }; setMessages(prev => [...prev, cancelMessage]); @@ -723,7 +725,7 @@ export const ClaudeCodeSession: React.FC = ({ const errorMessage: ClaudeStreamMessage = { type: "system", subtype: "error", - result: `取消执行失败: ${err instanceof Error ? err.message : '未知错误'}. 该进程可能仍在后台运行。`, + result: t('sessions.cancelSessionFailed', { error: err instanceof Error ? err.message : t('common.unknown') }), timestamp: new Date().toISOString() }; setMessages(prev => [...prev, errorMessage]); @@ -772,7 +774,7 @@ export const ClaudeCodeSession: React.FC = ({ setForkSessionName(""); } catch (err) { console.error("Failed to fork checkpoint:", err); - setError("分叉检查点失败"); + setError(t('sessions.forkFailed')); } finally { setIsLoading(false); } @@ -904,7 +906,7 @@ export const ClaudeCodeSession: React.FC = ({ className="p-4 border-b border-border flex-shrink-0" >
= ({
-

Claude Code 会话

+

{t('sessions.claudeCodeSession')}

- {session ? `恢复会话 ${session.id.slice(0, 8)}...` : '交互式会话'} + {session ? t('sessions.resumingSession', { sessionId: session.id.slice(0, 8) }) : t('sessions.interactiveSession')}

@@ -991,7 +993,7 @@ export const ClaudeCodeSession: React.FC = ({ className="flex items-center gap-2" > - 设置 + {t('common.settings')} )} @@ -1023,13 +1025,13 @@ export const ClaudeCodeSession: React.FC = ({ className="flex items-center gap-2" > - {showPreview ? "关闭预览" : "预览"} + {showPreview ? t('sessions.closePreview') : t('sessions.preview')} {showPreview - ? "关闭预览面板" - : "打开浏览器预览以测试您的Web应用程序" + ? t('sessions.closePreviewTooltip') + : t('sessions.openPreviewTooltip') } @@ -1044,7 +1046,7 @@ export const ClaudeCodeSession: React.FC = ({ className="flex items-center gap-2" > - 复制输出 + {t('sessions.copyOutput')} } @@ -1056,7 +1058,7 @@ export const ClaudeCodeSession: React.FC = ({ onClick={handleCopyAsMarkdown} className="w-full justify-start" > - 复制为Markdown + {t('sessions.copyAsMarkdown')}
} @@ -1115,7 +1117,7 @@ export const ClaudeCodeSession: React.FC = ({
- {session ? "正在加载会话历史..." : "正在初始化Claude Code..."} + {session ? t('sessions.loadingHistory') : t('sessions.initializingClaude')}
@@ -1138,7 +1140,7 @@ export const ClaudeCodeSession: React.FC = ({
- 队列提示 ({queuedPrompts.length}) + {t('sessions.queuedPrompts', { count: queuedPrompts.length })}
@@ -1236,7 +1238,7 @@ export const ClaudeCodeSession: React.FC = ({ } }} className="px-3 py-2 hover:bg-accent rounded-none" - title="滚动到底部" + title={t('sessions.scrollToBottom')} > @@ -1272,7 +1274,7 @@ export const ClaudeCodeSession: React.FC = ({
{totalTokens.toLocaleString()} - 标记 + {t('sessions.tokens')}
@@ -1294,7 +1296,7 @@ export const ClaudeCodeSession: React.FC = ({
{/* Timeline Header */}
-

会话时间线

+

{t('sessions.sessionTimeline')}

diff --git a/src/components/CreateAgent.tsx b/src/components/CreateAgent.tsx index f893091..69fd793 100644 --- a/src/components/CreateAgent.tsx +++ b/src/components/CreateAgent.tsx @@ -10,6 +10,7 @@ import { cn } from "@/lib/utils"; import MDEditor from "@uiw/react-md-editor"; import { type AgentIconName } from "./CCAgents"; import { IconPicker, ICON_MAP } from "./IconPicker"; +import { useTranslation } from "react-i18next"; interface CreateAgentProps { /** @@ -42,6 +43,7 @@ export const CreateAgent: React.FC = ({ onAgentCreated, className, }) => { + const { t } = useTranslation(); const [name, setName] = useState(agent?.name || ""); const [selectedIcon, setSelectedIcon] = useState((agent?.icon as AgentIconName) || "bot"); const [systemPrompt, setSystemPrompt] = useState(agent?.system_prompt || ""); @@ -56,12 +58,12 @@ export const CreateAgent: React.FC = ({ const handleSave = async () => { if (!name.trim()) { - setError("智能体名称为必填项"); + setError(t('agents.nameRequired')); return; } if (!systemPrompt.trim()) { - setError("系统提示词为必填项"); + setError(t('agents.systemPromptRequired')); return; } @@ -91,9 +93,9 @@ export const CreateAgent: React.FC = ({ onAgentCreated(); } catch (err) { console.error("Failed to save agent:", err); - setError(isEditMode ? "更新智能体失败" : "创建智能体失败"); + setError(isEditMode ? t('agents.updateFailed') : t('agents.createFailed')); setToast({ - message: isEditMode ? "更新智能体失败" : "创建智能体失败", + message: isEditMode ? t('agents.updateFailed') : t('agents.createFailed'), type: "error" }); } finally { @@ -107,7 +109,7 @@ export const CreateAgent: React.FC = ({ systemPrompt !== (agent?.system_prompt || "") || defaultTask !== (agent?.default_task || "") || model !== (agent?.model || "sonnet")) && - !confirm("您有未保存的更改。您确定要离开吗?")) { + !confirm(t('agents.unsavedChanges'))) { return; } onBack(); @@ -134,10 +136,10 @@ export const CreateAgent: React.FC = ({

- {isEditMode ? "编辑 CC 智能体" : "创建 CC 智能体"} + {isEditMode ? t('agents.editAgent') : t('agents.createAgent')}

- {isEditMode ? "更新您的 Claude Code 智能体" : "创建一个新的 Claude Code 智能体"} + {isEditMode ? t('agents.updateAgentDesc') : t('agents.createAgentDesc')}

@@ -152,7 +154,7 @@ export const CreateAgent: React.FC = ({ ) : ( )} - {saving ? "保存中..." : "保存"} + {saving ? t('common.saving') : t('common.save')} @@ -178,24 +180,24 @@ export const CreateAgent: React.FC = ({ {/* Basic Information */}
-

基本信息

+

{t('agents.basicInformation')}

{/* Name and Icon */}
- + setName(e.target.value)} - placeholder="例如:代码助手" + placeholder={t('agents.agentNamePlaceholder')} required />
- +
setShowIconPicker(true)} className="h-10 px-3 py-2 bg-background border border-input rounded-md cursor-pointer hover:bg-accent hover:text-accent-foreground transition-colors flex items-center justify-between" @@ -218,7 +220,7 @@ export const CreateAgent: React.FC = ({ {/* Model Selection */}
- +
Claude 4 Sonnet
-
速度快,适用于大多数任务
+
{t('models.sonnetDescription')}
@@ -269,7 +271,7 @@ export const CreateAgent: React.FC = ({
Claude 4 Opus
-
功能更强,适用于复杂任务
+
{t('models.opusDescription')}
@@ -278,17 +280,17 @@ export const CreateAgent: React.FC = ({ {/* Default Task */}
- + setDefaultTask(e.target.value)} className="max-w-md" />

- 这将作为执行智能体时的默认任务占位符 + {t('agents.placeholderTask')}

@@ -296,9 +298,9 @@ export const CreateAgent: React.FC = ({ {/* System Prompt Editor */}
- +

- 定义您的 CC 智能体的行为和能力 + {t('agents.definePrompt')}

= ({ level }) => { return (
@@ -134,32 +95,6 @@ type Model = { icon: React.ReactNode; }; -const MODELS: Model[] = [ - { - id: "sonnet", - name: "Claude 4 Sonnet", - description: "更快速,适用于大多数任务", - icon: - }, - { - id: "opus", - name: "Claude 4 Opus", - description: "更强大,适用于复杂任务", - icon: - } -]; - -/** - * FloatingPromptInput component - Fixed position prompt input with model picker - * - * @example - * const promptRef = useRef(null); - * console.log('Send:', prompt, model)} - * isLoading={false} - * /> - */ const FloatingPromptInputInner = ( { onSend, @@ -172,6 +107,7 @@ const FloatingPromptInputInner = ( }: FloatingPromptInputProps, ref: React.Ref, ) => { + const { t } = useTranslation(); const [prompt, setPrompt] = useState(""); const [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel); const [selectedThinkingMode, setSelectedThinkingMode] = useState("auto"); @@ -184,6 +120,58 @@ const FloatingPromptInputInner = ( const [embeddedImages, setEmbeddedImages] = useState([]); const [dragActive, setDragActive] = useState(false); + const THINKING_MODES: ThinkingModeConfig[] = [ + { + id: "auto", + name: t('thinking.auto'), + description: t('thinking.autoDesc'), + level: 0 + }, + { + id: "think", + name: t('thinking.think'), + description: t('thinking.thinkDesc'), + level: 1, + phrase: "think" + }, + { + id: "think_hard", + name: t('thinking.thinkHard'), + description: t('thinking.thinkHardDesc'), + level: 2, + phrase: "think hard" + }, + { + id: "think_harder", + name: t('thinking.thinkHarder'), + description: t('thinking.thinkHarderDesc'), + level: 3, + phrase: "think harder" + }, + { + id: "ultrathink", + name: t('thinking.ultrathink'), + description: t('thinking.ultrathinkDesc'), + level: 4, + phrase: "ultrathink" + } + ]; + + const MODELS: Model[] = [ + { + id: "sonnet", + name: t('models.sonnet'), + description: t('prompts.faster'), + icon: + }, + { + id: "opus", + name: t('models.opus'), + description: t('prompts.stronger'), + icon: + } + ]; + const textareaRef = useRef(null); const expandedTextareaRef = useRef(null); const unlistenDragDropRef = useRef<(() => void) | null>(null); @@ -490,7 +478,7 @@ const FloatingPromptInputInner = ( onClick={(e) => e.stopPropagation()} >
-

编写您的提示词

+

{t('prompts.composePrompt')}

-

{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.name || "自动"}

+

{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.name || t('prompts.auto')}

{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.description}

@@ -756,7 +744,7 @@ const FloatingPromptInputInner = ( value={prompt} onChange={handleTextChange} onKeyDown={handleKeyDown} - placeholder={dragActive ? "将图片拖放至此..." : "向 Claude 提问任何问题..."} + placeholder={dragActive ? t('prompts.dropImages') : t('prompts.askClaude')} disabled={disabled} className={cn( "min-h-[44px] max-h-[120px] resize-none pr-10", @@ -799,7 +787,7 @@ const FloatingPromptInputInner = ( {isLoading ? ( <> - 停止 + {t('prompts.stop')} ) : ( @@ -808,7 +796,7 @@ const FloatingPromptInputInner = (
- 按回车发送,Shift+回车换行{projectPath?.trim() && ",@ 符号提及文件,拖放图片"} + {t('prompts.pressEnterToSend')}
diff --git a/src/components/MCPAddServer.tsx b/src/components/MCPAddServer.tsx index 0a3c3b5..244fb6a 100644 --- a/src/components/MCPAddServer.tsx +++ b/src/components/MCPAddServer.tsx @@ -7,6 +7,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { SelectComponent } from "@/components/ui/select"; import { Card } from "@/components/ui/card"; import { api } from "@/lib/api"; +import { useTranslation } from "react-i18next"; interface MCPAddServerProps { /** @@ -33,6 +34,7 @@ export const MCPAddServer: React.FC = ({ onServerAdded, onError, }) => { + const { t } = useTranslation(); const [transport, setTransport] = useState<"stdio" | "sse">("stdio"); const [saving, setSaving] = useState(false); @@ -97,12 +99,12 @@ export const MCPAddServer: React.FC = ({ */ const handleAddStdioServer = async () => { if (!stdioName.trim()) { - onError("服务器名称为必填项"); + onError(t('mcp.serverNameRequired')); return; } if (!stdioCommand.trim()) { - onError("命令为必填项"); + onError(t('mcp.commandRequired')); return; } @@ -142,7 +144,7 @@ export const MCPAddServer: React.FC = ({ onError(result.message); } } catch (error) { - onError("添加服务器失败"); + onError(t('mcp.addServerFailed')); console.error("Failed to add stdio server:", error); } finally { setSaving(false); @@ -154,12 +156,12 @@ export const MCPAddServer: React.FC = ({ */ const handleAddSseServer = async () => { if (!sseName.trim()) { - onError("服务器名称为必填项"); + onError(t('mcp.serverNameRequired')); return; } if (!sseUrl.trim()) { - onError("URL 为必填项"); + onError(t('mcp.urlRequired')); return; } @@ -195,7 +197,7 @@ export const MCPAddServer: React.FC = ({ onError(result.message); } } catch (error) { - onError("添加服务器失败"); + onError(t('mcp.addServerFailed')); console.error("Failed to add SSE server:", error); } finally { setSaving(false); @@ -209,7 +211,7 @@ export const MCPAddServer: React.FC = ({ return (
- +
@@ -257,9 +259,9 @@ export const MCPAddServer: React.FC = ({ return (
-

添加 MCP 服务器

+

{t('mcp.addServer')}

- 配置新的 Model Context Protocol 服务器 + {t('mcp.configureNewServer')}

@@ -280,7 +282,7 @@ export const MCPAddServer: React.FC = ({
- + = ({ onChange={(e) => setStdioName(e.target.value)} />

- 用于标识此服务器的唯一名称 + {t('mcp.serverNameDesc')}

- + = ({ className="font-mono" />

- 执行服务器的命令 + {t('mcp.commandDesc')}

- + = ({ className="font-mono" />

- 空格分隔的命令参数 + {t('mcp.argumentsDesc')}

- + setStdioScope(value)} options={[ - { value: "local", label: "本地(仅限此项目)" }, - { value: "project", label: "项目(通过 .mcp.json 共享)" }, - { value: "user", label: "用户(所有项目)" }, + { value: "local", label: t('mcp.localScope') }, + { value: "project", label: t('mcp.projectScope') }, + { value: "user", label: t('mcp.userScope') }, ]} />
@@ -345,12 +347,12 @@ export const MCPAddServer: React.FC = ({ {saving ? ( <> - 正在添加服务器... + {t('mcp.addingServer')} ) : ( <> - 添加 Stdio 服务器 + {t('mcp.addStdioServer')} )} @@ -363,7 +365,7 @@ export const MCPAddServer: React.FC = ({
- + = ({ onChange={(e) => setSseName(e.target.value)} />

- 用于标识此服务器的唯一名称 + {t('mcp.serverNameDesc')}

@@ -385,19 +387,19 @@ export const MCPAddServer: React.FC = ({ className="font-mono" />

- SSE 端点 URL + {t('mcp.sseUrlDesc')}

- + setSseScope(value)} options={[ - { value: "local", label: "本地(仅限此项目)" }, - { value: "project", label: "项目(通过 .mcp.json 共享)" }, - { value: "user", label: "用户(所有项目)" }, + { value: "local", label: t('mcp.localScope') }, + { value: "project", label: t('mcp.projectScope') }, + { value: "user", label: t('mcp.userScope') }, ]} />
@@ -414,12 +416,12 @@ export const MCPAddServer: React.FC = ({ {saving ? ( <> - 正在添加服务器... + {t('mcp.addingServer')} ) : ( <> - 添加 SSE 服务器 + {t('mcp.addSseServer')} )} @@ -433,7 +435,7 @@ export const MCPAddServer: React.FC = ({
- 命令示例 + {t('mcp.exampleCommands')}
diff --git a/src/components/MCPManager.tsx b/src/components/MCPManager.tsx index 2a60a88..7297aa7 100644 --- a/src/components/MCPManager.tsx +++ b/src/components/MCPManager.tsx @@ -9,6 +9,7 @@ import { api, type MCPServer } from "@/lib/api"; import { MCPServerList } from "./MCPServerList"; import { MCPAddServer } from "./MCPAddServer"; import { MCPImportExport } from "./MCPImportExport"; +import { useTranslation } from "react-i18next"; interface MCPManagerProps { /** @@ -29,6 +30,7 @@ export const MCPManager: React.FC = ({ onBack, className, }) => { + const { t } = useTranslation(); const [activeTab, setActiveTab] = useState("servers"); const [servers, setServers] = useState([]); const [loading, setLoading] = useState(true); @@ -54,7 +56,7 @@ export const MCPManager: React.FC = ({ setServers(serverList); } catch (err) { console.error("MCPManager: Failed to load MCP servers:", err); - setError("无法加载 MCP 服务器。请确保已安装 Claude Code。"); + setError(t('mcp.loadFailed')); } finally { setLoading(false); } @@ -65,7 +67,7 @@ export const MCPManager: React.FC = ({ */ const handleServerAdded = () => { loadServers(); - setToast({ message: "MCP 服务器添加成功!", type: "success" }); + setToast({ message: t('mcp.serverAddSuccess'), type: "success" }); setActiveTab("servers"); }; @@ -74,7 +76,7 @@ export const MCPManager: React.FC = ({ */ const handleServerRemoved = (name: string) => { setServers(prev => prev.filter(s => s.name !== name)); - setToast({ message: `服务器 "${name}" 删除成功!`, type: "success" }); + setToast({ message: t('mcp.serverRemoveSuccess'), type: "success" }); }; /** @@ -84,12 +86,12 @@ export const MCPManager: React.FC = ({ loadServers(); if (failed === 0) { setToast({ - message: `成功导入 ${imported} 个服务器!`, + message: t('mcp.importSuccess', { count: imported }), type: "success" }); } else { setToast({ - message: `已导入 ${imported} 个服务器,${failed} 个失败`, + message: t('mcp.importPartialSuccess', { imported, failed }), type: "error" }); } @@ -117,10 +119,10 @@ export const MCPManager: React.FC = ({

- MCP 服务器 + {t('mcp.title')}

- 管理 Model Context Protocol 服务器 + {t('mcp.description')}

@@ -152,15 +154,15 @@ export const MCPManager: React.FC = ({ - 服务器 + {t('mcp.servers')} - 添加服务器 + {t('mcp.addServer')} - 导入/导出 + {t('mcp.importExport')} diff --git a/src/components/MCPServerList.tsx b/src/components/MCPServerList.tsx index f175d86..f4f2b52 100644 --- a/src/components/MCPServerList.tsx +++ b/src/components/MCPServerList.tsx @@ -19,6 +19,7 @@ import { import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { api, type MCPServer } from "@/lib/api"; +import { useTranslation } from "react-i18next"; interface MCPServerListProps { /** @@ -49,6 +50,7 @@ export const MCPServerList: React.FC = ({ onServerRemoved, onRefresh, }) => { + const { t } = useTranslation(); const [removingServer, setRemovingServer] = useState(null); const [testingServer, setTestingServer] = useState(null); const [expandedServers, setExpandedServers] = useState>(new Set()); @@ -157,11 +159,11 @@ export const MCPServerList: React.FC = ({ const getScopeDisplayName = (scope: string) => { switch (scope) { case "local": - return "本地(项目特定)"; + return t('mcp.localScope'); case "project": - return "项目(通过 .mcp.json 共享)"; + return t('mcp.projectScope'); case "user": - return "用户(所有项目)"; + return t('mcp.userScope'); default: return scope; } @@ -193,7 +195,7 @@ export const MCPServerList: React.FC = ({ {server.status?.running && ( - 运行中 + {t('mcp.running')} )}
@@ -210,7 +212,7 @@ export const MCPServerList: React.FC = ({ className="h-6 px-2 text-xs hover:bg-primary/10" > - 展开全部 + {t('mcp.showFull')}
)} @@ -225,7 +227,7 @@ export const MCPServerList: React.FC = ({ {Object.keys(server.env).length > 0 && !isExpanded && (
- 环境变量:{Object.keys(server.env).length} + {t('mcp.environmentVars', { count: Object.keys(server.env).length })}
)}
@@ -272,7 +274,7 @@ export const MCPServerList: React.FC = ({ {server.command && (
-

命令

+

{t('mcp.command')}

@@ -302,7 +304,7 @@ export const MCPServerList: React.FC = ({ {server.args && server.args.length > 0 && (
-

参数

+

{t('mcp.arguments')}

{server.args.map((arg, idx) => (
@@ -325,7 +327,7 @@ export const MCPServerList: React.FC = ({ {Object.keys(server.env).length > 0 && (
-

环境变量

+

{t('mcp.environmentVariables')}

{Object.entries(server.env).map(([key, value]) => (
@@ -357,9 +359,9 @@ export const MCPServerList: React.FC = ({ {/* Header */}
-

已配置的服务器

+

{t('mcp.configuredServers')}

- 已配置 {servers.length} 个服务器 + {t('mcp.configuredCount', { count: servers.length })}

@@ -379,9 +381,9 @@ export const MCPServerList: React.FC = ({
-

没有配置 MCP 服务器

+

{t('mcp.noServers')}

- 添加服务器以开始使用 Model Context Protocol + {t('mcp.getStartedText')}

) : ( diff --git a/src/components/RunningSessionsView.tsx b/src/components/RunningSessionsView.tsx index 1adba7b..e8d4da2 100644 --- a/src/components/RunningSessionsView.tsx +++ b/src/components/RunningSessionsView.tsx @@ -8,6 +8,7 @@ import { Toast, ToastContainer } from '@/components/ui/toast'; import { SessionOutputViewer } from './SessionOutputViewer'; import { api } from '@/lib/api'; import type { AgentRun } from '@/lib/api'; +import { useTranslation } from 'react-i18next'; interface RunningSessionsViewProps { className?: string; @@ -16,6 +17,7 @@ interface RunningSessionsViewProps { } export function RunningSessionsView({ className, showBackButton = false, onBack }: RunningSessionsViewProps) { + const { t } = useTranslation(); const [runningSessions, setRunningSessions] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); @@ -28,7 +30,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack setRunningSessions(sessions); } catch (error) { console.error('Failed to load running sessions:', error); - setToast({ message: '加载运行中的会话失败', type: 'error' }); + setToast({ message: t('runningSessions.loadFailed'), type: 'error' }); } finally { setLoading(false); } @@ -41,10 +43,10 @@ export function RunningSessionsView({ className, showBackButton = false, onBack await api.cleanupFinishedProcesses(); // Then reload the list await loadRunningSessions(); - setToast({ message: '运行中的会话列表已更新', type: 'success' }); + setToast({ message: t('runningSessions.refreshSuccess'), type: 'success' }); } catch (error) { console.error('Failed to refresh sessions:', error); - setToast({ message: '刷新会话失败', type: 'error' }); + setToast({ message: t('runningSessions.refreshFailed'), type: 'error' }); } finally { setRefreshing(false); } @@ -54,15 +56,15 @@ export function RunningSessionsView({ className, showBackButton = false, onBack try { const success = await api.killAgentSession(runId); if (success) { - setToast({ message: `${agentName} 会话已停止`, type: 'success' }); + setToast({ message: t('runningSessions.sessionStopped', { session: agentName }), type: 'success' }); // Refresh the list after killing await loadRunningSessions(); } else { - setToast({ message: '会话可能已经结束', type: 'error' }); + setToast({ message: t('runningSessions.sessionMayFinished'), type: 'error' }); } } catch (error) { console.error('Failed to kill session:', error); - setToast({ message: '终止会话失败', type: 'error' }); + setToast({ message: t('runningSessions.terminateFailed'), type: 'error' }); } }; @@ -78,9 +80,9 @@ export function RunningSessionsView({ className, showBackButton = false, onBack const getStatusBadge = (status: string) => { switch (status) { case 'running': - return 运行中; + return {t('runningSessions.running')}; case 'pending': - return 等待中; + return {t('runningSessions.pending')}; default: return {status}; } @@ -104,7 +106,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
- 正在加载运行中的会话... + {t('runningSessions.loadingSessions')}
); @@ -125,7 +127,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack )} -

运行中的代理会话

+

{t('runningSessions.title')}

{runningSessions.length}
@@ -145,7 +147,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
-

当前没有正在运行的代理会话

+

{t('runningSessions.noSessions')}

@@ -187,7 +189,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack className="flex items-center space-x-2" > - 查看输出 + {t('runningSessions.viewOutput')}
@@ -204,28 +206,28 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
-

任务

+

{t('runningSessions.task')}

{session.task}

-

模型

+

{t('models.model')}

{session.model}

-

持续时间

+

{t('runningSessions.duration')}

{session.process_started_at ? formatDuration(session.process_started_at) - : '未知' + : t('common.unknown') }

-

项目路径

+

{t('runningSessions.projectPath')}

{session.project_path}

@@ -233,7 +235,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack {session.session_id && (
-

会话ID

+

{t('runningSessions.sessionId')}

{session.session_id}

diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 9fc526f..f878f56 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -26,6 +26,7 @@ import { import { cn } from "@/lib/utils"; import { Toast, ToastContainer } from "@/components/ui/toast"; import { ClaudeVersionSelector } from "./ClaudeVersionSelector"; +import { useTranslation } from "react-i18next"; interface SettingsProps { /** @@ -57,6 +58,7 @@ export const Settings: React.FC = ({ onBack, className, }) => { + const { t } = useTranslation(); const [activeTab, setActiveTab] = useState("general"); const [settings, setSettings] = useState({}); const [loading, setLoading] = useState(true); @@ -145,7 +147,7 @@ export const Settings: React.FC = ({ } } catch (err) { console.error("Failed to load settings:", err); - setError("加载设置失败。请确保 ~/.claude 目录存在。"); + setError(t('settings.loadFailed')); setSettings({}); } finally { setLoading(false); @@ -187,11 +189,11 @@ export const Settings: React.FC = ({ setBinaryPathChanged(false); } - setToast({ message: "设置保存成功!", type: "success" }); + setToast({ message: t('settings.saveSuccess'), type: "success" }); } catch (err) { console.error("Failed to save settings:", err); - setError("保存设置失败。"); - setToast({ message: "保存设置失败", type: "error" }); + setError(t('settings.saveFailed')); + setToast({ message: t('settings.saveFailed'), type: "error" }); } finally { setSaving(false); } @@ -302,9 +304,9 @@ export const Settings: React.FC = ({
-

设置

+

{t('settings.title')}

- 配置 Claude Code 偏好设置 + {t('settings.description')}

@@ -318,12 +320,12 @@ export const Settings: React.FC = ({ {saving ? ( <> - 保存中... + {t('common.saving')} ) : ( <> - 保存设置 + {t('settings.saveSettings')} )} @@ -355,19 +357,19 @@ export const Settings: React.FC = ({ - 常规 + {t('settings.general')} - 权限 + {t('settings.permissions')} - 环境 + {t('settings.environment')} - 高级 + {t('settings.advanced')} @@ -375,15 +377,15 @@ export const Settings: React.FC = ({
-

常规设置

+

{t('settings.generalSettings')}

{/* Include Co-authored By */}
- +

- 在 git 提交和拉取请求中添加 Claude 署名 + {t('settings.includeCoAuthorDesc')}

= ({ {/* Verbose Output */}
- +

- 显示完整的 bash 和命令输出 + {t('settings.verboseOutputDesc')}

= ({ {/* Cleanup Period */}
- + = ({ }} />

- 本地保留聊天记录的时长(默认:30 天) + {t('settings.chatRetentionDesc')}

{/* Claude Binary Path Selector */}
- +

- 选择要使用的 Claude Code 安装版本 + {t('settings.claudeInstallationDesc')}

= ({ /> {binaryPathChanged && (

- ⚠️ Claude 二进制路径已更改。请记得保存您的设置。 + {t('settings.binaryPathChanged')}

)}
@@ -455,16 +457,16 @@ export const Settings: React.FC = ({
-

权限规则

+

{t('settings.permissionRules')}

- 控制 Claude Code 可以在无需手动批准的情况下使用哪些工具 + {t('settings.permissionRulesDesc')}

{/* Allow Rules */}
- +
{allowRules.length === 0 ? (

- 未配置允许规则。Claude 将对所有工具请求批准。 + {t('settings.noAllowRules')}

) : ( allowRules.map((rule) => ( @@ -489,7 +491,7 @@ export const Settings: React.FC = ({ className="flex items-center gap-2" > updatePermissionRule("allow", rule.id, e.target.value)} className="flex-1" @@ -511,7 +513,7 @@ export const Settings: React.FC = ({ {/* Deny Rules */}
- +
{denyRules.length === 0 ? (

- 未配置拒绝规则。 + {t('settings.noDenyRules')}

) : ( denyRules.map((rule) => ( @@ -536,7 +538,7 @@ export const Settings: React.FC = ({ className="flex items-center gap-2" > updatePermissionRule("deny", rule.id, e.target.value)} className="flex-1" @@ -557,14 +559,14 @@ export const Settings: React.FC = ({

- 示例: + {t('settings.examplesLabel')}:

    -
  • Bash - 允许所有 bash 命令
  • -
  • Bash(npm run build) - 允许精确命令
  • -
  • Bash(npm run test:*) - 允许带前缀的命令
  • -
  • Read(~/.zshrc) - 允许读取特定文件
  • -
  • Edit(docs/**) - 允许编辑 docs 目录中的文件
  • +
  • Bash - {t('settings.bashAllExample')}
  • +
  • Bash(npm run build) - {t('settings.bashExactExample')}
  • +
  • Bash(npm run test:*) - {t('settings.bashPrefixExample')}
  • +
  • Read(~/.zshrc) - {t('settings.readFileExample')}
  • +
  • Edit(docs/**) - {t('settings.editDocsExample')}
@@ -577,9 +579,9 @@ export const Settings: React.FC = ({
-

环境变量

+

{t('settings.environmentVariables')}

- 应用于每个 Claude Code 会话的环境变量 + {t('settings.environmentVariablesDesc')}

{envVars.length === 0 ? (

- 未配置环境变量。 + {t('settings.noEnvironmentVars')}

) : ( envVars.map((envVar) => ( @@ -607,14 +609,14 @@ export const Settings: React.FC = ({ className="flex items-center gap-2" > updateEnvVar(envVar.id, "key", e.target.value)} className="flex-1 font-mono text-sm" /> = updateEnvVar(envVar.id, "value", e.target.value)} className="flex-1 font-mono text-sm" @@ -634,12 +636,12 @@ export const Settings: React.FC = ({

- 常用变量: + {t('settings.commonVariables')}:

    -
  • CLAUDE_CODE_ENABLE_TELEMETRY - 启用/禁用遥测 (0 或 1)
  • -
  • ANTHROPIC_MODEL - 自定义模型名称
  • -
  • DISABLE_COST_WARNINGS - 禁用成本警告 (1)
  • +
  • CLAUDE_CODE_ENABLE_TELEMETRY - {t('settings.telemetryDesc')}
  • +
  • ANTHROPIC_MODEL - {t('settings.modelNameDesc')}
  • +
  • DISABLE_COST_WARNINGS - {t('settings.costWarningsDesc')}
@@ -650,15 +652,15 @@ export const Settings: React.FC = ({
-

高级设置

+

{t('settings.advancedSettings')}

- 为高级用户提供的额外配置选项 + {t('settings.advancedSettingsDesc')}

{/* API Key Helper */}
- + = ({ onChange={(e) => updateSetting("apiKeyHelper", e.target.value || undefined)} />

- 用于为 API 请求生成身份验证值的自定义脚本 + {t('settings.apiKeyHelperDesc')}

{/* Raw JSON Editor */}
- +
{JSON.stringify(settings, null, 2)}

- 这显示了将保存到 ~/.claude/settings.json 的原始 JSON 数据 + {t('settings.rawSettingsDesc')}

diff --git a/src/components/UsageDashboard.tsx b/src/components/UsageDashboard.tsx index f5c730f..93dbf3a 100644 --- a/src/components/UsageDashboard.tsx +++ b/src/components/UsageDashboard.tsx @@ -17,6 +17,7 @@ import { Briefcase } from "lucide-react"; import { cn } from "@/lib/utils"; +import { useTranslation } from "react-i18next"; interface UsageDashboardProps { /** @@ -32,6 +33,7 @@ interface UsageDashboardProps { * setView('welcome')} /> */ export const UsageDashboard: React.FC = ({ onBack }) => { + const { t } = useTranslation(); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [stats, setStats] = useState(null); @@ -82,7 +84,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { setSessionStats(sessionData); } catch (err) { console.error("Failed to load usage stats:", err); - setError("加载使用统计失败,请重试。"); + setError(t('usageDashboard.loadFailed')); } finally { setLoading(false); } @@ -146,9 +148,9 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
-

使用情况仪表板

+

{t('usageDashboard.title')}

- 跟踪您的 Claude Code 使用情况和费用 + {t('usageDashboard.description')}

@@ -165,7 +167,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { onClick={() => setSelectedDateRange(range)} className="text-xs" > - {range === "all" ? "全部时间" : range === "7d" ? "最近 7 天" : "最近 30 天"} + {range === "all" ? t('usageDashboard.allTime') : range === "7d" ? t('usageDashboard.last7Days') : t('usageDashboard.last30Days')} ))}
@@ -179,7 +181,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
-

正在加载使用统计...

+

{t('usageDashboard.loadingStats')}

) : error ? ( @@ -187,7 +189,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => {

{error}

@@ -204,7 +206,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
-

总费用

+

{t('usageDashboard.totalCost')}

{formatCurrency(stats.total_cost)}

@@ -217,7 +219,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
-

总会话数

+

{t('usageDashboard.totalSessions')}

{formatNumber(stats.total_sessions)}

@@ -230,7 +232,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
-

总令牌数

+

{t('usageDashboard.totalTokens')}

{formatTokens(stats.total_tokens)}

@@ -243,7 +245,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
-

平均会话费用

+

{t('usageDashboard.avgCostPerSession')}

{formatCurrency( stats.total_sessions > 0 @@ -260,32 +262,32 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* Tabs for different views */} - 概览 - 按模型 - 按项目 - 按会话 - 时间线 + {t('usageDashboard.overview')} + {t('usageDashboard.byModel')} + {t('usageDashboard.byProject')} + {t('usageDashboard.bySession')} + {t('usageDashboard.timeline')} {/* Overview Tab */} -

令牌分布

+

{t('usageDashboard.tokenBreakdown')}

-

输入令牌

+

{t('usageDashboard.inputTokens')}

{formatTokens(stats.total_input_tokens)}

-

输出令牌

+

{t('usageDashboard.outputTokens')}

{formatTokens(stats.total_output_tokens)}

-

缓存写入

+

{t('usageDashboard.cacheWrite')}

{formatTokens(stats.total_cache_creation_tokens)}

-

缓存读取

+

{t('usageDashboard.cacheRead')}

{formatTokens(stats.total_cache_read_tokens)}

@@ -294,7 +296,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* Quick Stats */}
-

常用模型

+

{t('usageDashboard.mostUsedModels')}

{stats.by_model.slice(0, 3).map((model) => (
@@ -303,7 +305,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {getModelDisplayName(model.model)} - {model.session_count} 个会话 + {model.session_count} {t('usageDashboard.sessions')}
@@ -315,7 +317,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { -

热门项目

+

{t('usageDashboard.topProjects')}

{stats.by_project.slice(0, 3).map((project) => (
@@ -324,7 +326,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {project.project_path} - {project.session_count} 个会话 + {project.session_count} {t('usageDashboard.sessions')}
@@ -340,7 +342,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* Models Tab */} -

按模型使用情况

+

{t('usageDashboard.byModel')}

{stats.by_model.map((model) => (
@@ -353,7 +355,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {getModelDisplayName(model.model)} - {model.session_count} 个会话 + {model.session_count} {t('usageDashboard.sessions')}
@@ -362,19 +364,19 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
- 输入: + {t('usageDashboard.input')} {formatTokens(model.input_tokens)}
- 输出: + {t('usageDashboard.output')} {formatTokens(model.output_tokens)}
- 缓存写: + {t('usageDashboard.cacheW')} {formatTokens(model.cache_creation_tokens)}
- 缓存读: + {t('usageDashboard.cacheR')} {formatTokens(model.cache_read_tokens)}
@@ -387,7 +389,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* Projects Tab */} -

按项目使用情况

+

{t('usageDashboard.byProject')}

{stats.by_project.map((project) => (
@@ -397,17 +399,17 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
- {project.session_count} 个会话 + {project.session_count} {t('usageDashboard.sessions')} - {formatTokens(project.total_tokens)} 个令牌 + {formatTokens(project.total_tokens)} {t('usageDashboard.tokens')}

{formatCurrency(project.total_cost)}

- {formatCurrency(project.total_cost / project.session_count)}/会话 + {formatCurrency(project.total_cost / project.session_count)}{t('usageDashboard.perSession')}

@@ -419,7 +421,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* Sessions Tab */} -

按会话使用情况

+

{t('usageDashboard.bySession')}

{sessionStats?.map((session) => (
@@ -451,7 +453,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => {

- 每日使用情况 + {t('usageDashboard.dailyUsage')}

{stats.by_date.length > 0 ? (() => { const maxCost = Math.max(...stats.by_date.map(d => d.total_cost), 0); @@ -484,13 +486,13 @@ export const UsageDashboard: React.FC = ({ onBack }) => {

{formattedDate}

- 费用: {formatCurrency(day.total_cost)} + {t('usageDashboard.cost')}: {formatCurrency(day.total_cost)}

- {formatTokens(day.total_tokens)} 个令牌 + {formatTokens(day.total_tokens)} {t('usageDashboard.tokens')}

- {day.models_used.length} 个模型 + {day.models_used.length} {t('usageDashboard.models')}

@@ -517,13 +519,13 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* X-axis label */}
- 每日使用趋势 + {t('usageDashboard.dailyUsageOverTime')}
) })() : (
- 所选时间段内没有可用的使用数据 + {t('usageDashboard.noDataAvailable')}
)}
diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx index 347595c..bd072d4 100644 --- a/src/components/ui/pagination.tsx +++ b/src/components/ui/pagination.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; +import { useTranslation } from "react-i18next"; interface PaginationProps { /** @@ -38,6 +39,7 @@ export const Pagination: React.FC = ({ onPageChange, className, }) => { + const { t } = useTranslation(); if (totalPages <= 1) { return null; } @@ -55,7 +57,7 @@ export const Pagination: React.FC = ({ - 第 {currentPage} 页,共 {totalPages} 页 + {t('common.page', { current: currentPage, total: totalPages })}