From 6798be3b423174385ed05f812c42d6412164f4e3 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Wed, 6 Aug 2025 15:39:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B1=89=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/i18n.rs | 2 + src/App.tsx | 24 +- src/components/AgentsModal.tsx | 14 +- src/components/AnalyticsConsent.tsx | 35 +- src/components/CCAgents.tsx | 77 ++--- src/components/CheckpointSettings.tsx | 56 ++-- src/components/ClaudeBinaryDialog.tsx | 24 +- src/components/ClaudeFileEditor.tsx | 16 +- src/components/ClaudeVersionSelector.tsx | 18 +- src/components/CreateAgent.tsx | 28 +- src/components/FloatingPromptInput.tsx | 83 ++--- src/components/HooksEditor.tsx | 10 +- src/components/MCPAddServer.tsx | 68 ++-- src/components/MCPManager.tsx | 22 +- src/components/MCPServerList.tsx | 34 +- src/components/MarkdownEditor.tsx | 16 +- src/components/ProjectList.tsx | 4 +- src/components/ProxySettings.tsx | 28 +- src/components/Settings.tsx | 192 +++++------ src/components/SlashCommandsManager.tsx | 28 +- src/components/TabContent.tsx | 33 +- src/components/UsageDashboard.tsx | 80 ++--- src/contexts/TabContext.tsx | 10 +- src/hooks/useTabState.ts | 38 +-- src/locales/en/common.json | 382 +++++++++++++++++++++- src/locales/zh/common.json | 390 ++++++++++++++++++++++- 26 files changed, 1243 insertions(+), 469 deletions(-) diff --git a/src-tauri/src/i18n.rs b/src-tauri/src/i18n.rs index cba2544..5894a29 100644 --- a/src-tauri/src/i18n.rs +++ b/src-tauri/src/i18n.rs @@ -30,6 +30,7 @@ impl SimpleI18n { } } + #[allow(dead_code)] pub fn t(&self, key: &str) -> String { let locale = self.get_current_locale(); @@ -63,6 +64,7 @@ fn get_i18n() -> &'static SimpleI18n { } // 便捷函数用于全局访问 +#[allow(dead_code)] pub fn t(key: &str) -> String { get_i18n().t(key) } diff --git a/src/App.tsx b/src/App.tsx index 0d424fb..e9ec114 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,7 @@ import { AgentsModal } from "@/components/AgentsModal"; import { useTabState } from "@/hooks/useTabState"; import { AnalyticsConsentBanner } from "@/components/AnalyticsConsent"; import { useAppLifecycle, useTrackEvent } from "@/hooks"; +import { useTranslation } from "@/hooks/useTranslation"; type View = | "welcome" @@ -48,6 +49,7 @@ type View = * AppContent component - Contains the main app logic, wrapped by providers */ function AppContent() { + const { t } = useTranslation(); const [view, setView] = useState("tabs"); const { createClaudeMdTab, createSettingsTab, createUsageTab, createMCPTab } = useTabState(); const [projects, setProjects] = useState([]); @@ -158,7 +160,7 @@ function AppContent() { setProjects(projectList); } catch (err) { console.error("Failed to load projects:", err); - setError("Failed to load projects. Please ensure ~/.claude directory exists."); + setError(t('failedToLoadProjects')); } finally { setLoading(false); } @@ -176,7 +178,7 @@ function AppContent() { setSelectedProject(project); } catch (err) { console.error("Failed to load sessions:", err); - setError("Failed to load sessions for this project."); + setError(t('failedToLoadSessions')); } finally { setLoading(false); } @@ -246,7 +248,7 @@ function AppContent() { >

- Welcome to Claudia + {t('welcomeToClaudia')}

@@ -264,7 +266,7 @@ function AppContent() { >
-

CC Agents

+

{t('ccAgents')}

@@ -281,7 +283,7 @@ function AppContent() { >
-

CC Projects

+

{t('ccProjects')}

@@ -329,12 +331,12 @@ function AppContent() { onClick={() => handleViewChange("welcome")} className="mb-4" > - ← Back to Home + {t('backToHome')}
-

CC Projects

+

{t('ccProjects')}

- Browse your Claude Code sessions + {t('browseClaudeCodeSessions')}

@@ -396,7 +398,7 @@ function AppContent() { className="w-full max-w-md" > - New Claude Code session + {t('newClaudeCodeSession')} @@ -415,7 +417,7 @@ function AppContent() { ) : (

- No projects found in ~/.claude/projects + {t('noProjectsFound')}

)} @@ -508,7 +510,7 @@ function AppContent() { open={showClaudeBinaryDialog} onOpenChange={setShowClaudeBinaryDialog} onSuccess={() => { - setToast({ message: "Claude binary path saved successfully", type: "success" }); + setToast({ message: t('claudeBinaryPathSaved'), type: "success" }); // Trigger a refresh of the Claude version check window.location.reload(); }} diff --git a/src/components/AgentsModal.tsx b/src/components/AgentsModal.tsx index a93d2df..4eebfd5 100644 --- a/src/components/AgentsModal.tsx +++ b/src/components/AgentsModal.tsx @@ -21,6 +21,7 @@ import { ScrollArea } from '@/components/ui/scroll-area'; import { Toast } from '@/components/ui/toast'; import { api, type Agent, type AgentRunWithMetrics } from '@/lib/api'; import { useTabState } from '@/hooks/useTabState'; +import { useTranslation } from '@/hooks/useTranslation'; import { formatISOTimestamp } from '@/lib/date-utils'; import { open as openDialog, save } from '@tauri-apps/plugin-dialog'; import { invoke } from '@tauri-apps/api/core'; @@ -32,6 +33,7 @@ interface AgentsModalProps { } export const AgentsModal: React.FC = ({ open, onOpenChange }) => { + const { t } = useTranslation(); const [activeTab, setActiveTab] = useState('agents'); const [agents, setAgents] = useState([]); const [runningAgents, setRunningAgents] = useState([]); @@ -201,18 +203,18 @@ export const AgentsModal: React.FC = ({ open, onOpenChange }) - Agent Management + {t('agents.agentManagement')} - Create new agents or manage running agent executions + {t('agents.createNewOrManageAgents')} - Available Agents + {t('agents.availableAgents')} - Running Agents + {t('agents.runningAgents')} {runningAgents.length > 0 && ( {runningAgents.length} @@ -257,9 +259,9 @@ export const AgentsModal: React.FC = ({ open, onOpenChange }) ) : agents.length === 0 ? (
-

No agents available

+

{t('agents.noAgentsAvailable')}

- Create your first agent to get started + {t('agents.createFirstAgentToGetStarted')}

- We'd like to collect anonymous usage data to improve your experience. + {t('analytics.collectAnonymousData')} @@ -84,12 +86,12 @@ export const AnalyticsConsent: React.FC = ({
-

What we collect:

+

{t('analytics.whatWeCollect')}

    -
  • • Feature usage (which tools and commands you use)
  • -
  • • Performance metrics (app speed and reliability)
  • -
  • • Error reports (to fix bugs and improve stability)
  • -
  • • General usage patterns (session frequency and duration)
  • +
  • • {t('analytics.featureUsageDesc')}
  • +
  • • {t('analytics.performanceMetricsDesc')}
  • +
  • • {t('analytics.errorReportsDesc')}
  • +
  • • {t('analytics.usagePatternsDesc')}
@@ -99,13 +101,13 @@ export const AnalyticsConsent: React.FC = ({
-

Your privacy is protected:

+

{t('analytics.privacyProtected')}

    -
  • • No personal information is collected
  • -
  • • No file contents, paths, or project names
  • -
  • • No API keys or sensitive data
  • -
  • • Completely anonymous with random IDs
  • -
  • • You can opt-out anytime in Settings
  • +
  • • {t('analytics.noPersonalInfo')}
  • +
  • • {t('analytics.noFileContents')}
  • +
  • • {t('analytics.noApiKeys')}
  • +
  • • {t('analytics.anonymousData')}
  • +
  • • {t('analytics.canOptOut')}
@@ -116,8 +118,7 @@ export const AnalyticsConsent: React.FC = ({

- This data helps us understand which features are most valuable, identify performance - issues, and prioritize improvements. Your choice won't affect any functionality. + {t('analytics.dataHelpsUs')}

@@ -129,13 +130,13 @@ export const AnalyticsConsent: React.FC = ({ variant="outline" className="flex-1" > - No Thanks + {t('analytics.noThanks')} diff --git a/src/components/CCAgents.tsx b/src/components/CCAgents.tsx index 3f272fa..807f480 100644 --- a/src/components/CCAgents.tsx +++ b/src/components/CCAgents.tsx @@ -40,6 +40,7 @@ import { AgentExecution } from "./AgentExecution"; import { AgentRunsList } from "./AgentRunsList"; import { GitHubAgentBrowser } from "./GitHubAgentBrowser"; import { ICON_MAP } from "./IconPicker"; +import { useTranslation } from "@/hooks/useTranslation"; interface CCAgentsProps { /** @@ -64,6 +65,7 @@ export type AgentIconName = keyof typeof AGENT_ICONS; * setView('home')} /> */ export const CCAgents: React.FC = ({ onBack, className }) => { + const { t } = useTranslation(); const [agents, setAgents] = useState([]); const [runs, setRuns] = useState([]); const [loading, setLoading] = useState(true); @@ -94,8 +96,8 @@ export const CCAgents: React.FC = ({ onBack, className }) => { setAgents(agentsList); } catch (err) { console.error("Failed to load agents:", err); - setError("Failed to load agents"); - setToast({ message: "Failed to load agents", type: "error" }); + setError(t('agents.loadAgentsFailed')); + setToast({ message: t('agents.loadAgentsFailed'), type: "error" }); } finally { setLoading(false); } @@ -132,12 +134,12 @@ export const CCAgents: React.FC = ({ onBack, className }) => { try { setIsDeleting(true); await api.deleteAgent(agentToDelete.id); - setToast({ message: "Agent deleted successfully", type: "success" }); + setToast({ message: t('agents.agentDeleted'), type: "success" }); await loadAgents(); await loadRuns(); // Reload runs as they might be affected } catch (err) { console.error("Failed to delete agent:", err); - setToast({ message: "Failed to delete agent", type: "error" }); + setToast({ message: t('agents.deleteFailed'), type: "error" }); } finally { setIsDeleting(false); setShowDeleteDialog(false); @@ -166,13 +168,13 @@ export const CCAgents: React.FC = ({ onBack, className }) => { const handleAgentCreated = async () => { setView("list"); await loadAgents(); - setToast({ message: "Agent created successfully", type: "success" }); + setToast({ message: t('agents.agentCreated'), type: "success" }); }; const handleAgentUpdated = async () => { setView("list"); await loadAgents(); - setToast({ message: "Agent updated successfully", type: "success" }); + setToast({ message: t('agents.agentUpdated'), type: "success" }); }; // const handleRunClick = (run: AgentRunWithMetrics) => { @@ -209,10 +211,10 @@ export const CCAgents: React.FC = ({ onBack, className }) => { filePath }); - setToast({ message: `Agent "${agent.name}" exported successfully`, type: "success" }); + setToast({ message: t('agents.exportedSuccessfully', { name: agent.name }), type: "success" }); } catch (err) { console.error("Failed to export agent:", err); - setToast({ message: "Failed to export agent", type: "error" }); + setToast({ message: t('agents.exportFailed'), type: "error" }); } }; @@ -235,11 +237,11 @@ export const CCAgents: React.FC = ({ onBack, className }) => { // Import the agent from the selected file await api.importAgentFromFile(filePath as string); - setToast({ message: "Agent imported successfully", type: "success" }); + setToast({ message: t('agents.importedSuccessfully'), type: "success" }); await loadAgents(); } catch (err) { console.error("Failed to import agent:", err); - const errorMessage = err instanceof Error ? err.message : "Failed to import agent"; + const errorMessage = err instanceof Error ? err.message : t('agents.importFailed'); setToast({ message: errorMessage, type: "error" }); } }; @@ -308,9 +310,9 @@ export const CCAgents: React.FC = ({ onBack, className }) => {
-

CC Agents

+

{t('navigation.agents')}

- Manage your Claude Code agents + {t('agents.manageAgents')}

@@ -323,18 +325,18 @@ export const CCAgents: React.FC = ({ onBack, className }) => { className="flex items-center gap-2" > - Import + {t('agents.import')} - From File + {t('agents.importFromFile')} setShowGitHubBrowser(true)}> - From GitHub + {t('agents.importFromGitHub')} @@ -344,7 +346,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => { className="flex items-center gap-2" > - Create CC Agent + {t('agents.createAgent')} @@ -381,13 +383,13 @@ export const CCAgents: React.FC = ({ onBack, className }) => { ) : agents.length === 0 ? (
-

No agents yet

+

{t('agents.noAgentsYet')}

- Create your first CC Agent to get started + {t('agents.createFirstAgent')}

) : ( @@ -411,7 +413,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => { {agent.name}

- Created: {new Date(agent.created_at).toLocaleDateString()} + {t('agents.created')}: {new Date(agent.created_at).toLocaleDateString()}

@@ -420,20 +422,20 @@ export const CCAgents: React.FC = ({ onBack, className }) => { variant="ghost" onClick={() => handleExecuteAgent(agent)} className="flex items-center gap-1" - title="Execute agent" + title={t('agents.executeAgentTitle')} > - Execute + {t('agents.execute')} @@ -471,10 +473,10 @@ export const CCAgents: React.FC = ({ onBack, className }) => { onClick={() => setCurrentPage(p => Math.max(1, p - 1))} disabled={currentPage === 1} > - Previous + {t('app.previous')} - Page {currentPage} of {totalPages} + {t('app.page')} {currentPage} {t('app.of')} {totalPages} )} @@ -495,7 +497,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => {
-

Recent Executions

+

{t('agents.recentExecutions')}

{runsLoading ? (
@@ -531,7 +533,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => { onImportSuccess={async () => { setShowGitHubBrowser(false); await loadAgents(); - setToast({ message: "Agent imported successfully from GitHub", type: "success" }); + setToast({ message: t('agents.importFromGitHubSuccess'), type: "success" }); }} /> @@ -541,11 +543,10 @@ export const CCAgents: React.FC = ({ onBack, className }) => { - Delete Agent + {t('agents.deleteAgentTitle')} - Are you sure you want to delete the agent "{agentToDelete?.name}"? - This action cannot be undone and will permanently remove the agent and all its associated data. + {t('agents.deleteConfirmation', { name: agentToDelete?.name })} @@ -555,7 +556,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => { disabled={isDeleting} className="w-full sm:w-auto" > - Cancel + {t('app.cancel')} diff --git a/src/components/CheckpointSettings.tsx b/src/components/CheckpointSettings.tsx index 8c429f8..9bd6a1a 100644 --- a/src/components/CheckpointSettings.tsx +++ b/src/components/CheckpointSettings.tsx @@ -14,6 +14,7 @@ import { SelectComponent, type SelectOption } from "@/components/ui/select"; import { Input } from "@/components/ui/input"; import { api, type CheckpointStrategy } from "@/lib/api"; import { cn } from "@/lib/utils"; +import { useTranslation } from "@/hooks/useTranslation"; interface CheckpointSettingsProps { sessionId: string; @@ -40,6 +41,7 @@ export const CheckpointSettings: React.FC = ({ onClose, className, }) => { + const { t } = useTranslation(); const [autoCheckpointEnabled, setAutoCheckpointEnabled] = useState(true); const [checkpointStrategy, setCheckpointStrategy] = useState("smart"); const [totalCheckpoints, setTotalCheckpoints] = useState(0); @@ -50,10 +52,10 @@ export const CheckpointSettings: React.FC = ({ const [successMessage, setSuccessMessage] = useState(null); const strategyOptions: SelectOption[] = [ - { value: "manual", label: "Manual Only" }, - { value: "per_prompt", label: "After Each Prompt" }, - { value: "per_tool_use", label: "After Tool Use" }, - { value: "smart", label: "Smart (Recommended)" }, + { value: "manual", label: t('checkpoint.manualOnly') }, + { value: "per_prompt", label: t('checkpoint.afterEachPrompt') }, + { value: "per_tool_use", label: t('checkpoint.afterToolUse') }, + { value: "smart", label: t('checkpoint.smart') }, ]; useEffect(() => { @@ -71,7 +73,7 @@ export const CheckpointSettings: React.FC = ({ setTotalCheckpoints(settings.total_checkpoints); } catch (err) { console.error("Failed to load checkpoint settings:", err); - setError("Failed to load checkpoint settings"); + setError(t('checkpoint.checkpointSettingsFailed')); } finally { setIsLoading(false); } @@ -91,11 +93,11 @@ export const CheckpointSettings: React.FC = ({ checkpointStrategy ); - setSuccessMessage("Settings saved successfully"); + setSuccessMessage(t('messages.saveSuccess')); setTimeout(() => setSuccessMessage(null), 3000); } catch (err) { console.error("Failed to save checkpoint settings:", err); - setError("Failed to save checkpoint settings"); + setError(t('checkpoint.saveCheckpointSettingsFailed')); } finally { setIsSaving(false); } @@ -114,14 +116,14 @@ export const CheckpointSettings: React.FC = ({ keepCount ); - setSuccessMessage(`Removed ${removed} old checkpoints`); + setSuccessMessage(t('checkpoint.removedOldCheckpoints', { count: removed })); setTimeout(() => setSuccessMessage(null), 3000); // Reload settings to get updated count await loadSettings(); } catch (err) { console.error("Failed to cleanup checkpoints:", err); - setError("Failed to cleanup checkpoints"); + setError(t('checkpoint.cleanupCheckpointsFailed')); } finally { setIsLoading(false); } @@ -137,11 +139,11 @@ export const CheckpointSettings: React.FC = ({
-

Checkpoint Settings

+

{t('checkpoint.checkpointSettingsTitle')}

{onClose && ( )}
@@ -151,9 +153,9 @@ export const CheckpointSettings: React.FC = ({
-

Experimental Feature

+

{t('checkpoint.experimentalFeature')}

- Checkpointing may affect directory structure or cause data loss. Use with caution. + {t('checkpoint.checkpointWarning')}

@@ -186,9 +188,9 @@ export const CheckpointSettings: React.FC = ({ {/* Auto-checkpoint toggle */}
- +

- Automatically create checkpoints based on the selected strategy + {t('checkpoint.automaticCheckpointsDesc')}

= ({ {/* Checkpoint strategy */}
- + setCheckpointStrategy(value as CheckpointStrategy)} @@ -209,10 +211,10 @@ export const CheckpointSettings: React.FC = ({ disabled={isLoading || !autoCheckpointEnabled} />

- {checkpointStrategy === "manual" && "Checkpoints will only be created manually"} - {checkpointStrategy === "per_prompt" && "A checkpoint will be created after each user prompt"} - {checkpointStrategy === "per_tool_use" && "A checkpoint will be created after each tool use"} - {checkpointStrategy === "smart" && "Checkpoints will be created after destructive operations"} + {checkpointStrategy === "manual" && t('checkpoint.manualOnlyDesc')} + {checkpointStrategy === "per_prompt" && t('checkpoint.afterEachPromptDesc')} + {checkpointStrategy === "per_tool_use" && t('checkpoint.afterToolUseDesc')} + {checkpointStrategy === "smart" && t('checkpoint.smartDesc')}

@@ -225,12 +227,12 @@ export const CheckpointSettings: React.FC = ({ {isSaving ? ( <> - Saving... + {t('checkpoint.saving')} ) : ( <> - Save Settings + {t('checkpoint.saveSettings')} )} @@ -239,9 +241,9 @@ export const CheckpointSettings: React.FC = ({
- +

- Total checkpoints: {totalCheckpoints} + {t('checkpoint.totalCheckpoints')}: {totalCheckpoints}

@@ -249,7 +251,7 @@ export const CheckpointSettings: React.FC = ({ {/* Cleanup settings */}
- +
= ({ disabled={isLoading || totalCheckpoints <= keepCount} > - Clean Up + {t('checkpoint.cleanUp')}

- Remove old checkpoints, keeping only the most recent {keepCount} + {t('checkpoint.removeOldCheckpoints', { count: keepCount })}

diff --git a/src/components/ClaudeBinaryDialog.tsx b/src/components/ClaudeBinaryDialog.tsx index eb72ccd..7f7adc9 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 "@/hooks/useTranslation"; 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("Please select a Claude installation"); + onError(t('pleaseSelectInstallation')); return; } @@ -62,29 +64,27 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C - Select Claude Code Installation + {t('selectClaudeCodeInstallation')} {checkingInstallations ? (
- Searching for Claude installations... + {t('searchingInstallations')}
) : hasInstallations ? (

- Multiple Claude Code installations were found on your system. - Please select which one you'd like to use. + {t('multipleInstallationsFound')}

) : ( <>

- Claude Code was not found in any of the common installation locations. - Please install Claude Code to continue. + {t('claudeCodeNotFoundDialog')}

- Searched locations: PATH, /usr/local/bin, + {t('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

- Tip: You can install Claude Code using{" "} + {t('validation.required')}: {t('installationTip')}{" "} npm install -g @claude

@@ -118,20 +118,20 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C className="mr-auto" > - Installation Guide + {t('installationGuide')} diff --git a/src/components/ClaudeFileEditor.tsx b/src/components/ClaudeFileEditor.tsx index 252e3e7..c04ad9b 100644 --- a/src/components/ClaudeFileEditor.tsx +++ b/src/components/ClaudeFileEditor.tsx @@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"; import { Toast, ToastContainer } from "@/components/ui/toast"; import { api, type ClaudeMdFile } from "@/lib/api"; import { cn } from "@/lib/utils"; +import { useTranslation } from "@/hooks/useTranslation"; interface ClaudeFileEditorProps { /** @@ -36,6 +37,7 @@ export const ClaudeFileEditor: React.FC = ({ onBack, className, }) => { + const { t } = useTranslation(); const [content, setContent] = useState(""); const [originalContent, setOriginalContent] = useState(""); const [loading, setLoading] = useState(true); @@ -59,7 +61,7 @@ export const ClaudeFileEditor: React.FC = ({ setOriginalContent(fileContent); } catch (err) { console.error("Failed to load file:", err); - setError("Failed to load CLAUDE.md file"); + setError(t('loadFileFailed')); } finally { setLoading(false); } @@ -72,11 +74,11 @@ export const ClaudeFileEditor: React.FC = ({ setToast(null); await api.saveClaudeMdFile(file.absolute_path, content); setOriginalContent(content); - setToast({ message: "File saved successfully", type: "success" }); + setToast({ message: t('fileSavedSuccess'), type: "success" }); } catch (err) { console.error("Failed to save file:", err); - setError("Failed to save CLAUDE.md file"); - setToast({ message: "Failed to save file", type: "error" }); + setError(t('saveFileFailed')); + setToast({ message: t('saveFileFailed'), type: "error" }); } finally { setSaving(false); } @@ -85,7 +87,7 @@ export const ClaudeFileEditor: React.FC = ({ const handleBack = () => { if (hasChanges) { const confirmLeave = window.confirm( - "You have unsaved changes. Are you sure you want to leave?" + t('unsavedChangesConfirm') ); if (!confirmLeave) return; } @@ -114,7 +116,7 @@ export const ClaudeFileEditor: React.FC = ({

{file.relative_path}

- Edit project-specific Claude Code system prompt + {t('editProjectSpecificPrompt')}

@@ -129,7 +131,7 @@ export const ClaudeFileEditor: React.FC = ({ ) : ( )} - {saving ? "Saving..." : "Save"} + {saving ? t('saving') : t('app.save')} diff --git a/src/components/ClaudeVersionSelector.tsx b/src/components/ClaudeVersionSelector.tsx index 763c092..82ea17b 100644 --- a/src/components/ClaudeVersionSelector.tsx +++ b/src/components/ClaudeVersionSelector.tsx @@ -7,6 +7,7 @@ import { Label } from "@/components/ui/label"; import { api, type ClaudeInstallation } from "@/lib/api"; import { cn } from "@/lib/utils"; import { CheckCircle, HardDrive, Settings } from "lucide-react"; +import { useTranslation } from "@/hooks/useTranslation"; interface ClaudeVersionSelectorProps { /** @@ -53,6 +54,7 @@ export const ClaudeVersionSelector: React.FC = ({ onSave, isSaving = false, }) => { + const { t } = useTranslation(); const [installations, setInstallations] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -132,8 +134,8 @@ export const ClaudeVersionSelector: React.FC = ({ return ( - Claude Code Installation - Loading available installations... + {t('settings.claudeCodeInstallation')} + {t('settings.loadingAvailableInstallations')}
@@ -148,13 +150,13 @@ export const ClaudeVersionSelector: React.FC = ({ return ( - Claude Code Installation - Error loading installations + {t('settings.claudeCodeInstallation')} + {t('settings.errorLoadingInstallations')}
{error}
@@ -169,16 +171,16 @@ export const ClaudeVersionSelector: React.FC = ({ - Claude Code Installation + {t('settings.claudeCodeInstallation')} - Choose your preferred Claude Code installation. + {t('settings.choosePreferredInstallation')} {/* Available Installations */}
- + setName(e.target.value)} - placeholder="e.g., Code Assistant" + placeholder={t('placeholders.enterAgentName')} 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" @@ -221,7 +221,7 @@ export const CreateAgent: React.FC = ({ {/* Model Selection */}
- +
Claude 4 Sonnet
-
Faster, efficient for most tasks
+
{t('agents.sonnetDescription')}
@@ -272,7 +272,7 @@ export const CreateAgent: React.FC = ({
Claude 4 Opus
-
More capable, better for complex tasks
+
{t('agents.opusDescription')}
@@ -281,25 +281,25 @@ export const CreateAgent: React.FC = ({ {/* Default Task */}
- + setDefaultTask(e.target.value)} className="max-w-md" />

- This will be used as the default task placeholder when executing the agent + {t('agents.defaultTaskDescription')}

{/* System Prompt Editor */}
- +

- Define the behavior and capabilities of your CC Agent + {t('agents.systemPromptDescription')}

, ) => { + const { t } = useTranslation(); + + // Define THINKING_MODES inside component to access translations + const THINKING_MODES: ThinkingModeConfig[] = [ + { + id: "auto", + name: "Auto", + description: t('messages.letClaudeDecide'), + level: 0 + }, + { + id: "think", + name: "Think", + description: t('messages.basicReasoning'), + level: 1, + phrase: "think" + }, + { + id: "think_hard", + name: "Think Hard", + description: t('messages.deeperAnalysis'), + level: 2, + phrase: "think hard" + }, + { + id: "think_harder", + name: "Think Harder", + description: t('messages.extensiveReasoning'), + level: 3, + phrase: "think harder" + }, + { + id: "ultrathink", + name: "Ultrathink", + description: t('messages.maximumAnalysis'), + level: 4, + phrase: "ultrathink" + } + ]; const [prompt, setPrompt] = useState(""); const [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel); const [selectedThinkingMode, setSelectedThinkingMode] = useState("auto"); @@ -758,7 +763,7 @@ const FloatingPromptInputInner = ( value={prompt} onChange={handleTextChange} onPaste={handlePaste} - placeholder="Type your prompt here..." + placeholder={t('messages.typeYourPromptHere')} className="min-h-[200px] resize-none" disabled={disabled} onDragEnter={handleDrag} @@ -1001,7 +1006,7 @@ const FloatingPromptInputInner = ( onChange={handleTextChange} onKeyDown={handleKeyDown} onPaste={handlePaste} - placeholder={dragActive ? "Drop images here..." : "Ask Claude anything..."} + placeholder={dragActive ? t('messages.dropImagesHere') : t('messages.askClaudeAnything')} disabled={disabled} className={cn( "min-h-[44px] max-h-[120px] resize-none pr-10", @@ -1065,7 +1070,7 @@ const FloatingPromptInputInner = (
- Press Enter to send, Shift+Enter for new line{projectPath?.trim() && ", @ to mention files, / for commands, drag & drop or paste images"} + {t('input.pressEnterToSend')}{projectPath?.trim() && t('input.withFileAndCommandSupport')}
diff --git a/src/components/HooksEditor.tsx b/src/components/HooksEditor.tsx index 142b529..4cd4a86 100644 --- a/src/components/HooksEditor.tsx +++ b/src/components/HooksEditor.tsx @@ -51,6 +51,7 @@ import { import { cn } from '@/lib/utils'; import { HooksManager } from '@/lib/hooksManager'; import { api } from '@/lib/api'; +import { useTranslation } from '@/hooks/useTranslation'; import { HooksConfiguration, HookEvent, @@ -116,6 +117,7 @@ export const HooksEditor: React.FC = ({ onChange, hideActions = false }) => { + const { t } = useTranslation(); const [selectedEvent, setSelectedEvent] = useState('PreToolUse'); const [showTemplateDialog, setShowTemplateDialog] = useState(false); const [validationErrors, setValidationErrors] = useState([]); @@ -754,7 +756,7 @@ export const HooksEditor: React.FC = ({ {/* Header */}
-

Hooks Configuration

+

{t('hooks.hooksConfiguration')}

{scope === 'project' ? 'Project' : scope === 'local' ? 'Local' : 'User'} Scope @@ -789,12 +791,12 @@ export const HooksEditor: React.FC = ({

- Configure shell commands to execute at various points in Claude Code's lifecycle. - {scope === 'local' && ' These settings are not committed to version control.'} + {t('hooks.configureShellCommands')} + {scope === 'local' && t('hooks.localSettingsNote')}

{hasUnsavedChanges && !readOnly && (

- You have unsaved changes. Click Save to persist them. + {t('hooks.unsavedChanges')}

)}
diff --git a/src/components/MCPAddServer.tsx b/src/components/MCPAddServer.tsx index e4b6c30..ad81ef3 100644 --- a/src/components/MCPAddServer.tsx +++ b/src/components/MCPAddServer.tsx @@ -8,6 +8,7 @@ import { SelectComponent } from "@/components/ui/select"; import { Card } from "@/components/ui/card"; import { api } from "@/lib/api"; import { useTrackEvent } from "@/hooks"; +import { useTranslation } from "@/hooks/useTranslation"; interface MCPAddServerProps { /** @@ -34,6 +35,7 @@ export const MCPAddServer: React.FC = ({ onServerAdded, onError, }) => { + const { t } = useTranslation(); const [transport, setTransport] = useState<"stdio" | "sse">("stdio"); const [saving, setSaving] = useState(false); @@ -101,12 +103,12 @@ export const MCPAddServer: React.FC = ({ */ const handleAddStdioServer = async () => { if (!stdioName.trim()) { - onError("Server name is required"); + onError(t('serverNameRequired')); return; } if (!stdioCommand.trim()) { - onError("Command is required"); + onError(t('commandRequired')); return; } @@ -152,7 +154,7 @@ export const MCPAddServer: React.FC = ({ onError(result.message); } } catch (error) { - onError("Failed to add server"); + onError(t('failedToAddServer')); console.error("Failed to add stdio server:", error); } finally { setSaving(false); @@ -164,12 +166,12 @@ export const MCPAddServer: React.FC = ({ */ const handleAddSseServer = async () => { if (!sseName.trim()) { - onError("Server name is required"); + onError(t('serverNameRequired')); return; } if (!sseUrl.trim()) { - onError("URL is required"); + onError(t('urlRequired')); return; } @@ -211,7 +213,7 @@ export const MCPAddServer: React.FC = ({ onError(result.message); } } catch (error) { - onError("Failed to add server"); + onError(t('failedToAddServer')); console.error("Failed to add SSE server:", error); } finally { setSaving(false); @@ -225,7 +227,7 @@ export const MCPAddServer: React.FC = ({ return (
- +
@@ -273,9 +275,9 @@ export const MCPAddServer: React.FC = ({ return (
-

Add MCP Server

+

{t('addMcpServer')}

- Configure a new Model Context Protocol server + {t('configureNewMcpServer')}

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

- A unique name to identify this server + {t('uniqueNameToIdentify')}

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

- The command to execute the server + {t('commandToExecuteServer')}

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

- Space-separated command arguments + {t('spaceSeparatedArgs')}

- + setStdioScope(value)} options={[ - { value: "local", label: "Local (this project only)" }, - { value: "project", label: "Project (shared via .mcp.json)" }, - { value: "user", label: "User (all projects)" }, + { value: "local", label: t('localProjectOnly') }, + { value: "project", label: t('projectSharedViaMcp') }, + { value: "user", label: t('userAllProjects') }, ]} />
@@ -361,12 +363,12 @@ export const MCPAddServer: React.FC = ({ {saving ? ( <> - Adding Server... + {t('addingServer')} ) : ( <> - Add Stdio Server + {t('addStdioServer')} )} @@ -379,7 +381,7 @@ export const MCPAddServer: React.FC = ({
- + = ({ onChange={(e) => setSseName(e.target.value)} />

- A unique name to identify this server + {t('uniqueNameToIdentify')}

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

- The SSE endpoint URL + {t('sseEndpointUrl')}

- + setSseScope(value)} options={[ - { value: "local", label: "Local (this project only)" }, - { value: "project", label: "Project (shared via .mcp.json)" }, - { value: "user", label: "User (all projects)" }, + { value: "local", label: t('localProjectOnly') }, + { value: "project", label: t('projectSharedViaMcp') }, + { value: "user", label: t('userAllProjects') }, ]} />
@@ -430,12 +432,12 @@ export const MCPAddServer: React.FC = ({ {saving ? ( <> - Adding Server... + {t('addingServer')} ) : ( <> - Add SSE Server + {t('addSseServer')} )} @@ -449,7 +451,7 @@ export const MCPAddServer: React.FC = ({
- Example Commands + {t('exampleCommands')}
diff --git a/src/components/MCPManager.tsx b/src/components/MCPManager.tsx index 91be7ec..f296984 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 "@/hooks/useTranslation"; 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); @@ -55,7 +57,7 @@ export const MCPManager: React.FC = ({ setServers(serverList); } catch (err) { console.error("MCPManager: Failed to load MCP servers:", err); - setError("Failed to load MCP servers. Make sure Claude Code is installed."); + setError(t('loadMcpServersFailed')); } finally { setLoading(false); } @@ -66,7 +68,7 @@ export const MCPManager: React.FC = ({ */ const handleServerAdded = () => { loadServers(); - setToast({ message: "MCP server added successfully!", type: "success" }); + setToast({ message: t('mcpServerAdded'), type: "success" }); setActiveTab("servers"); }; @@ -75,7 +77,7 @@ export const MCPManager: React.FC = ({ */ const handleServerRemoved = (name: string) => { setServers(prev => prev.filter(s => s.name !== name)); - setToast({ message: `Server "${name}" removed successfully!`, type: "success" }); + setToast({ message: t('serverRemovedSuccess', { name }), type: "success" }); }; /** @@ -85,12 +87,12 @@ export const MCPManager: React.FC = ({ loadServers(); if (failed === 0) { setToast({ - message: `Successfully imported ${imported} server${imported > 1 ? 's' : ''}!`, + message: t('importedServersSuccess', { count: imported, plural: imported > 1 ? 's' : '' }), type: "success" }); } else { setToast({ - message: `Imported ${imported} server${imported > 1 ? 's' : ''}, ${failed} failed`, + message: t('importedServersFailed', { imported, importedPlural: imported > 1 ? 's' : '', failed }), type: "error" }); } @@ -118,10 +120,10 @@ export const MCPManager: React.FC = ({

- MCP Servers + {t('mcpServers')}

- Manage Model Context Protocol servers + {t('manageMcpServers')}

@@ -153,15 +155,15 @@ export const MCPManager: React.FC = ({ - Servers + {t('servers')} - Add Server + {t('addServer')} - Import/Export + {t('importExport')} diff --git a/src/components/MCPServerList.tsx b/src/components/MCPServerList.tsx index b493692..28bd7dc 100644 --- a/src/components/MCPServerList.tsx +++ b/src/components/MCPServerList.tsx @@ -20,6 +20,7 @@ import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { api, type MCPServer } from "@/lib/api"; import { useTrackEvent } from "@/hooks"; +import { useTranslation } from "@/hooks/useTranslation"; interface MCPServerListProps { /** @@ -50,6 +51,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()); @@ -184,11 +186,11 @@ export const MCPServerList: React.FC = ({ const getScopeDisplayName = (scope: string) => { switch (scope) { case "local": - return "Local (Project-specific)"; + return t('localProjectSpecific'); case "project": - return "Project (Shared via .mcp.json)"; + return t('projectSharedMcp'); case "user": - return "User (All projects)"; + return t('userAllProjects'); default: return scope; } @@ -220,7 +222,7 @@ export const MCPServerList: React.FC = ({ {server.status?.running && ( - Running + {t('running')} )}
@@ -237,7 +239,7 @@ export const MCPServerList: React.FC = ({ className="h-6 px-2 text-xs hover:bg-primary/10" > - Show full + {t('showFull')}
)} @@ -299,7 +301,7 @@ export const MCPServerList: React.FC = ({ {server.command && (
-

Command

+

{t('command')}

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

Arguments

+

{t('arguments')}

{server.args.map((arg, idx) => (
@@ -343,7 +345,7 @@ export const MCPServerList: React.FC = ({ {server.transport === "sse" && server.url && (
-

URL

+

{t('url')}

{server.url}

@@ -352,7 +354,7 @@ export const MCPServerList: React.FC = ({ {Object.keys(server.env).length > 0 && (
-

Environment Variables

+

{t('environmentVariables')}

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

Configured Servers

+

{t('configuredServers')}

- {servers.length} server{servers.length !== 1 ? "s" : ""} configured + {servers.length} {servers.length !== 1 ? t('servers') : 'server'} {t('serversConfigured')}

@@ -406,9 +408,9 @@ export const MCPServerList: React.FC = ({
-

No MCP servers configured

+

{t('noMcpServersConfigured')}

- Add a server to get started with Model Context Protocol + {t('addServerToGetStarted')}

) : ( diff --git a/src/components/MarkdownEditor.tsx b/src/components/MarkdownEditor.tsx index 9ffa0f9..3ae4c36 100644 --- a/src/components/MarkdownEditor.tsx +++ b/src/components/MarkdownEditor.tsx @@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"; import { Toast, ToastContainer } from "@/components/ui/toast"; import { api } from "@/lib/api"; import { cn } from "@/lib/utils"; +import { useTranslation } from "@/hooks/useTranslation"; interface MarkdownEditorProps { /** @@ -28,6 +29,7 @@ export const MarkdownEditor: React.FC = ({ onBack, className, }) => { + const { t } = useTranslation(); const [content, setContent] = useState(""); const [originalContent, setOriginalContent] = useState(""); const [loading, setLoading] = useState(true); @@ -51,7 +53,7 @@ export const MarkdownEditor: React.FC = ({ setOriginalContent(prompt); } catch (err) { console.error("Failed to load system prompt:", err); - setError("Failed to load CLAUDE.md file"); + setError(t('loadClaudemdFailed')); } finally { setLoading(false); } @@ -64,11 +66,11 @@ export const MarkdownEditor: React.FC = ({ setToast(null); await api.saveSystemPrompt(content); setOriginalContent(content); - setToast({ message: "CLAUDE.md saved successfully", type: "success" }); + setToast({ message: t('claudemdSavedSuccess'), type: "success" }); } catch (err) { console.error("Failed to save system prompt:", err); - setError("Failed to save CLAUDE.md file"); - setToast({ message: "Failed to save CLAUDE.md", type: "error" }); + setError(t('saveClaudemdFailed')); + setToast({ message: t('saveClaudemdFailed'), type: "error" }); } finally { setSaving(false); } @@ -77,7 +79,7 @@ export const MarkdownEditor: React.FC = ({ const handleBack = () => { if (hasChanges) { const confirmLeave = window.confirm( - "You have unsaved changes. Are you sure you want to leave?" + t('unsavedChangesConfirm') ); if (!confirmLeave) return; } @@ -106,7 +108,7 @@ export const MarkdownEditor: React.FC = ({

CLAUDE.md

- Edit your Claude Code system prompt + {t('editSystemPrompt')}

@@ -121,7 +123,7 @@ export const MarkdownEditor: React.FC = ({ ) : ( )} - {saving ? "Saving..." : "Save"} + {saving ? t('app.saving') : t('app.save')} diff --git a/src/components/ProjectList.tsx b/src/components/ProjectList.tsx index 058de96..f34657d 100644 --- a/src/components/ProjectList.tsx +++ b/src/components/ProjectList.tsx @@ -17,6 +17,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { useTranslation } from "@/hooks/useTranslation"; import type { Project } from "@/lib/api"; import { cn } from "@/lib/utils"; import { formatTimeAgo } from "@/lib/date-utils"; @@ -70,6 +71,7 @@ export const ProjectList: React.FC = ({ onProjectSettings, className, }) => { + const { t } = useTranslation(); const [currentPage, setCurrentPage] = useState(1); // Calculate pagination @@ -150,7 +152,7 @@ export const ProjectList: React.FC = ({ }} > - Hooks + {t('settings.hooks')} diff --git a/src/components/ProxySettings.tsx b/src/components/ProxySettings.tsx index 0b8d6e2..94d5b06 100644 --- a/src/components/ProxySettings.tsx +++ b/src/components/ProxySettings.tsx @@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api/core'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; +import { useTranslation } from '@/hooks/useTranslation'; export interface ProxySettings { http_proxy: string | null; @@ -18,6 +19,7 @@ interface ProxySettingsProps { } export function ProxySettings({ setToast, onChange }: ProxySettingsProps) { + const { t } = useTranslation(); const [settings, setSettings] = useState({ http_proxy: null, https_proxy: null, @@ -43,13 +45,13 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) { await invoke('save_proxy_settings', { settings }); setOriginalSettings(settings); setToast({ - message: 'Proxy settings saved and applied successfully.', + message: t('settings.proxySettingsSaved'), type: 'success', }); } catch (error) { console.error('Failed to save proxy settings:', error); setToast({ - message: 'Failed to save proxy settings', + message: t('settings.saveProxySettingsFailed'), type: 'error', }); throw error; // Re-throw to let parent handle the error @@ -72,7 +74,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) { } catch (error) { console.error('Failed to load proxy settings:', error); setToast({ - message: 'Failed to load proxy settings', + message: t('settings.loadProxySettingsFailed'), type: 'error', }); } @@ -89,18 +91,18 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) { return (
-

Proxy Settings

+

{t('settings.proxySettingsTitle')}

- Configure proxy settings for Claude API requests + {t('settings.proxySettingsDesc')}

- +

- Use proxy for all Claude API requests + {t('settings.enableProxyDesc')}

- +
- +
- +

- Comma-separated list of hosts that should bypass the proxy + {t('settings.noProxyDesc')}

- +

- Proxy URL to use for all protocols if protocol-specific proxies are not set + {t('settings.allProxyDesc')}

diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 1202516..3269dd4 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -31,7 +31,7 @@ import { HooksEditor } from "./HooksEditor"; import { SlashCommandsManager } from "./SlashCommandsManager"; import { ProxySettings } from "./ProxySettings"; import { AnalyticsConsent } from "./AnalyticsConsent"; -import { useTheme, useTrackEvent } from "@/hooks"; +import { useTheme, useTrackEvent, useTranslation } from "@/hooks"; import { analytics } from "@/lib/analytics"; interface SettingsProps { @@ -64,6 +64,7 @@ export const Settings: React.FC = ({ onBack, className, }) => { + const { t } = useTranslation(); const [settings, setSettings] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); @@ -178,7 +179,7 @@ export const Settings: React.FC = ({ } } catch (err) { console.error("Failed to load settings:", err); - setError("Failed to load settings. Please ensure ~/.claude directory exists."); + setError(t('settings.messages.loadFailed')); setSettings({}); } finally { setLoading(false); @@ -232,11 +233,11 @@ export const Settings: React.FC = ({ setProxySettingsChanged(false); } - setToast({ message: "Settings saved successfully!", type: "success" }); + setToast({ message: t('settings.saveButton.settingsSavedSuccess'), type: "success" }); } catch (err) { console.error("Failed to save settings:", err); - setError("Failed to save settings."); - setToast({ message: "Failed to save settings", type: "error" }); + setError(t('settings.messages.saveFailed')); + setToast({ message: t('settings.saveButton.settingsSaveFailed'), type: "error" }); } finally { setSaving(false); } @@ -347,9 +348,9 @@ export const Settings: React.FC = ({
-

Settings

+

{t('settings.title')}

- Configure Claude Code preferences + {t('settings.configurePreferences')}

@@ -363,12 +364,12 @@ export const Settings: React.FC = ({ {saving ? ( <> - Saving... + {t('settings.saveButton.saving')} ) : ( <> - Save Settings + {t('settings.saveButton.saveSettings')} )} @@ -398,55 +399,55 @@ export const Settings: React.FC = ({
- General - Permissions - Environment - Advanced - Hooks - Commands - Storage - Proxy - Analytics + {t('settings.general')} + {t('settings.permissionsTab')} + {t('settings.environmentTab')} + {t('settings.advancedTab')} + {t('settings.hooksTab')} + {t('settings.commands')} + {t('settings.storage')} + {t('settings.proxy')} + {t('settings.analyticsTab')} {/* General Settings */}
-

General Settings

+

{t('settings.generalSettings')}

{/* Theme Selector */}
- +

- Choose your preferred color theme for the interface + {t('settings.themeSelector.choosePreferredTheme')}

{/* Custom Color Editor */} {theme === 'custom' && (
-

Custom Theme Colors

+

{t('settings.customTheme.title')}

{/* Background Color */}
- +
= ({ {/* Foreground Color */}
- +
= ({ {/* Primary Color */}
- +
= ({ {/* Card Color */}
- +
= ({ {/* Accent Color */}
- +
= ({ {/* Destructive Color */}
- +
= ({

- Use CSS color values (hex, rgb, oklch, etc.). Changes apply immediately. + {t('settings.customTheme.colorValuesDesc')}

)} @@ -568,9 +569,9 @@ export const Settings: React.FC = ({ {/* Include Co-authored By */}
- +

- Add Claude attribution to git commits and pull requests + {t('settings.generalOptions.includeCoAuthorDesc')}

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

- Show full bash and command outputs + {t('settings.generalOptions.verboseOutputDesc')}

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

- How long to retain chat transcripts locally (default: 30 days) + {t('settings.generalOptions.chatRetentionDesc')}

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

- Select which Claude Code installation to use. + {t('settings.generalOptions.claudeCodeInstallationDesc')}

= ({ /> {binaryPathChanged && (

- ⚠️ Claude binary path has been changed. Remember to save your settings. + {t('settings.generalOptions.binaryPathChanged')}

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

Permission Rules

+

{t('settings.permissions.permissionRules')}

- Control which tools Claude Code can use without manual approval + {t('settings.permissions.permissionRulesDesc')}

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

- No allow rules configured. Claude will ask for approval for all tools. + {t('settings.permissions.noAllowRules')}

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

- No deny rules configured. + {t('settings.permissions.noDenyRules')}

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

- Examples: + {t('settings.permissions.examples')}

    -
  • Bash - Allow all bash commands
  • -
  • Bash(npm run build) - Allow exact command
  • -
  • Bash(npm run test:*) - Allow commands with prefix
  • -
  • Read(~/.zshrc) - Allow reading specific file
  • -
  • Edit(docs/**) - Allow editing files in docs directory
  • +
  • Bash - {t('settings.permissions.exampleBash')}
  • +
  • Bash(npm run build) - {t('settings.permissions.exampleExactCommand')}
  • +
  • Bash(npm run test:*) - {t('settings.permissions.examplePrefix')}
  • +
  • Read(~/.zshrc) - {t('settings.permissions.exampleReadFile')}
  • +
  • Edit(docs/**) - {t('settings.permissions.exampleEditDir')}
@@ -764,9 +765,9 @@ export const Settings: React.FC = ({
-

Environment Variables

+

{t('settings.environment.environmentVariables')}

- Environment variables applied to every Claude Code session + {t('settings.environment.environmentVariablesDesc')}

{envVars.length === 0 ? (

- No environment variables configured. + {t('settings.environment.noEnvironmentVariables')}

) : ( envVars.map((envVar) => ( @@ -794,14 +795,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" @@ -821,12 +822,12 @@ export const Settings: React.FC = ({

- Common variables: + {t('settings.environment.commonVariables')}

    -
  • CLAUDE_CODE_ENABLE_TELEMETRY - Enable/disable telemetry (0 or 1)
  • -
  • ANTHROPIC_MODEL - Custom model name
  • -
  • DISABLE_COST_WARNINGS - Disable cost warnings (1)
  • +
  • CLAUDE_CODE_ENABLE_TELEMETRY - {t('settings.environment.telemetryDesc')}
  • +
  • ANTHROPIC_MODEL - {t('settings.environment.modelDesc')}
  • +
  • DISABLE_COST_WARNINGS - {t('settings.environment.costWarningsDesc')}
@@ -837,34 +838,34 @@ export const Settings: React.FC = ({
-

Advanced Settings

+

{t('settings.advanced.advancedSettings')}

- Additional configuration options for advanced users + {t('settings.advanced.advancedSettingsDesc')}

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

- Custom script to generate auth values for API requests + {t('settings.advanced.apiKeyHelperDesc')}

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

- This shows the raw JSON that will be saved to ~/.claude/settings.json + {t('settings.advanced.rawSettingsDesc')}

@@ -876,10 +877,9 @@ export const Settings: React.FC = ({
-

User Hooks

+

{t('settings.hooks.userHooks')}

- Configure hooks that apply to all Claude Code sessions for your user account. - These are stored in ~/.claude/settings.json + {t('settings.hooks.userHooksDesc')}

@@ -928,16 +928,16 @@ export const Settings: React.FC = ({
-

Analytics Settings

+

{t('settings.analytics.analyticsSettings')}

{/* Analytics Toggle */}
- +

- Help improve Claudia by sharing anonymous usage data + {t('settings.analytics.enableAnalyticsDesc')}

= ({ await analytics.enable(); setAnalyticsEnabled(true); trackEvent.settingsChanged('analytics_enabled', true); - setToast({ message: "Analytics enabled", type: "success" }); + setToast({ message: t('settings.analytics.analyticsEnabled'), type: "success" }); } else { await analytics.disable(); setAnalyticsEnabled(false); trackEvent.settingsChanged('analytics_enabled', false); - setToast({ message: "Analytics disabled", type: "success" }); + setToast({ message: t('settings.analytics.analyticsDisabled'), type: "success" }); } }} /> @@ -966,12 +966,12 @@ export const Settings: React.FC = ({
-

Your privacy is protected

+

{t('settings.analytics.privacyProtected')}

    -
  • • No personal information is collected
  • -
  • • No file contents, paths, or project names
  • -
  • • All data is anonymous with random IDs
  • -
  • • You can disable analytics at any time
  • +
  • • {t('settings.analytics.noPersonalInfo')}
  • +
  • • {t('settings.analytics.noFileContents')}
  • +
  • • {t('settings.analytics.anonymousData')}
  • +
  • • {t('settings.analytics.canDisable')}
@@ -981,12 +981,12 @@ export const Settings: React.FC = ({ {analyticsEnabled && (
-

What we collect:

+

{t('settings.analytics.whatWeCollect')}

    -
  • • Feature usage patterns
  • -
  • • Performance metrics
  • -
  • • Error reports (without sensitive data)
  • -
  • • Session frequency and duration
  • +
  • • {t('settings.analytics.featureUsage')}
  • +
  • • {t('settings.analytics.performanceMetrics')}
  • +
  • • {t('settings.analytics.errorReports')}
  • +
  • • {t('settings.analytics.sessionFrequency')}
@@ -999,11 +999,11 @@ export const Settings: React.FC = ({ await analytics.deleteAllData(); setAnalyticsEnabled(false); setAnalyticsConsented(false); - setToast({ message: "All analytics data deleted", type: "success" }); + setToast({ message: t('settings.analytics.allDataDeleted'), type: "success" }); }} > - Delete All Analytics Data + {t('settings.analytics.deleteAllData')}
diff --git a/src/components/SlashCommandsManager.tsx b/src/components/SlashCommandsManager.tsx index 1592632..00b84e6 100644 --- a/src/components/SlashCommandsManager.tsx +++ b/src/components/SlashCommandsManager.tsx @@ -30,6 +30,7 @@ import { api, type SlashCommand } from "@/lib/api"; import { cn } from "@/lib/utils"; import { COMMON_TOOL_MATCHERS } from "@/types/hooks"; import { useTrackEvent } from "@/hooks"; +import { useTranslation } from "@/hooks/useTranslation"; interface SlashCommandsManagerProps { projectPath?: string; @@ -92,6 +93,7 @@ export const SlashCommandsManager: React.FC = ({ className, scopeFilter = 'all', }) => { + const { t } = useTranslation(); const [commands, setCommands] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); @@ -305,17 +307,17 @@ export const SlashCommandsManager: React.FC = ({

- {scopeFilter === 'project' ? 'Project Slash Commands' : 'Slash Commands'} + {scopeFilter === 'project' ? t('slashCommands.projectSlashCommands') : t('slashCommands.slashCommands')}

{scopeFilter === 'project' - ? 'Create custom commands for this project' - : 'Create custom commands to streamline your workflow'} + ? t('slashCommands.createCustomCommandsProject') + : t('slashCommands.createCustomCommandsWorkflow')}

@@ -325,7 +327,7 @@ export const SlashCommandsManager: React.FC = ({
setSearchQuery(e.target.value)} className="pl-9" @@ -338,9 +340,9 @@ export const SlashCommandsManager: React.FC = ({ - All Commands - Project - User + {t('slashCommands.allCommands')} + {t('slashCommands.project')} + {t('slashCommands.user')} )} @@ -365,16 +367,16 @@ export const SlashCommandsManager: React.FC = ({

{searchQuery - ? "No commands found" + ? t('slashCommands.noCommandsFound') : scopeFilter === 'project' - ? "No project commands created yet" - : "No commands created yet"} + ? t('slashCommands.noProjectCommandsYet') + : t('slashCommands.noCommandsYet')}

{!searchQuery && ( )}
diff --git a/src/components/TabContent.tsx b/src/components/TabContent.tsx index cb5ca82..75bca11 100644 --- a/src/components/TabContent.tsx +++ b/src/components/TabContent.tsx @@ -9,6 +9,7 @@ import { ProjectList } from '@/components/ProjectList'; import { SessionList } from '@/components/SessionList'; import { RunningClaudeSessions } from '@/components/RunningClaudeSessions'; import { Button } from '@/components/ui/button'; +import { useTranslation } from '@/hooks/useTranslation'; // Lazy load heavy components const ClaudeCodeSession = lazy(() => import('@/components/ClaudeCodeSession').then(m => ({ default: m.ClaudeCodeSession }))); @@ -29,6 +30,7 @@ interface TabPanelProps { } const TabPanel: React.FC = ({ tab, isActive }) => { + const { t } = useTranslation(); const { updateTab, createChatTab } = useTabState(); const [projects, setProjects] = React.useState([]); const [selectedProject, setSelectedProject] = React.useState(null); @@ -54,7 +56,7 @@ const TabPanel: React.FC = ({ tab, isActive }) => { setProjects(projectList); } catch (err) { console.error("Failed to load projects:", err); - setError("Failed to load projects. Please ensure ~/.claude directory exists."); + setError(t('failedToLoadProjects')); } finally { setLoading(false); } @@ -69,7 +71,7 @@ const TabPanel: React.FC = ({ tab, isActive }) => { setSelectedProject(project); } catch (err) { console.error("Failed to load sessions:", err); - setError("Failed to load sessions for this project."); + setError(t('failedToLoadSessions')); } finally { setLoading(false); } @@ -96,9 +98,9 @@ const TabPanel: React.FC = ({ tab, isActive }) => {
{/* Header */}
-

CC Projects

+

{t('ccProjects')}

- Browse your Claude Code sessions + {t('browseClaudeCodeSessions')}

@@ -174,7 +176,7 @@ const TabPanel: React.FC = ({ tab, isActive }) => { className="w-full max-w-md" > - New Claude Code session + {t('newClaudeCodeSession')} @@ -196,7 +198,7 @@ const TabPanel: React.FC = ({ tab, isActive }) => { ) : (

- No projects found in ~/.claude/projects + {t('noProjectsFound')}

)} @@ -217,7 +219,7 @@ const TabPanel: React.FC = ({ tab, isActive }) => { // Go back to projects view in the same tab updateTab(tab.id, { type: 'projects', - title: 'CC Projects', + title: t('ccProjects'), }); }} /> @@ -225,7 +227,7 @@ const TabPanel: React.FC = ({ tab, isActive }) => { case 'agent': if (!tab.agentRunId) { - return
No agent run ID specified
; + return
{t('messages.noAgentRunIdSpecified')}
; } return ( = ({ tab, isActive }) => { case 'claude-file': if (!tab.claudeFileId) { - return
No Claude file ID specified
; + return
{t('messages.noClaudeFileIdSpecified')}
; } // Note: We need to get the actual file object for ClaudeFileEditor // For now, returning a placeholder - return
Claude file editor not yet implemented in tabs
; + return
{t('messages.claudeFileEditorNotImplemented')}
; case 'agent-execution': if (!tab.agentData) { - return
No agent data specified
; + return
{t('messages.noAgentDataSpecified')}
; } return ( = ({ tab, isActive }) => { case 'import-agent': // TODO: Implement import agent component - return
Import agent functionality coming soon...
; + return
{t('messages.importAgentComingSoon')}
; default: - return
Unknown tab type: {tab.type}
; + return
{t('messages.unknownTabType')}: {tab.type}
; } }; @@ -311,6 +313,7 @@ const TabPanel: React.FC = ({ tab, isActive }) => { }; export const TabContent: React.FC = () => { + const { t } = useTranslation(); const { tabs, activeTabId, createChatTab, findTabBySessionId, createClaudeFileTab, createAgentExecutionTab, createCreateAgentTab, createImportAgentTab, closeTab, updateTab } = useTabState(); // Listen for events to open sessions in tabs @@ -415,8 +418,8 @@ export const TabContent: React.FC = () => { {tabs.length === 0 && (
-

No tabs open

-

Click the + button to start a new chat

+

{t('messages.noTabsOpen')}

+

{t('messages.clickPlusToStartChat')}

)} diff --git a/src/components/UsageDashboard.tsx b/src/components/UsageDashboard.tsx index 81890fb..512c5a9 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 "@/hooks/useTranslation"; 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("Failed to load usage statistics. Please try again."); + setError(t('usage.failedToLoadUsageStats')); } finally { setLoading(false); } @@ -146,9 +148,9 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
-

Usage Dashboard

+

{t('usage.usageDashboardTitle')}

- Track your Claude Code usage and costs + {t('usage.trackUsageAndCosts')}

@@ -165,7 +167,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { onClick={() => setSelectedDateRange(range)} className="text-xs" > - {range === "all" ? "All Time" : range === "7d" ? "Last 7 Days" : "Last 30 Days"} + {range === "all" ? t('usage.allTime') : range === "7d" ? t('usage.last7Days') : t('usage.last30Days')} ))}
@@ -179,7 +181,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
-

Loading usage statistics...

+

{t('usage.loadingUsageStats')}

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

{error}

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

Total Cost

+

{t('usage.totalCost')}

{formatCurrency(stats.total_cost)}

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

Total Sessions

+

{t('usage.totalSessions')}

{formatNumber(stats.total_sessions)}

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

Total Tokens

+

{t('usage.totalTokens')}

{formatTokens(stats.total_tokens)}

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

Avg Cost/Session

+

{t('usage.avgCostPerSession')}

{formatCurrency( stats.total_sessions > 0 @@ -260,32 +262,32 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* Tabs for different views */} - Overview - By Model - By Project - By Session - Timeline + {t('usage.overview')} + {t('usage.byModel')} + {t('usage.byProject')} + {t('usage.byDate')} + {t('usage.timeline')} {/* Overview Tab */} -

Token Breakdown

+

{t('usage.tokenBreakdown')}

-

Input Tokens

+

{t('usage.inputTokens')}

{formatTokens(stats.total_input_tokens)}

-

Output Tokens

+

{t('usage.outputTokens')}

{formatTokens(stats.total_output_tokens)}

-

Cache Write

+

{t('usage.cacheWrite')}

{formatTokens(stats.total_cache_creation_tokens)}

-

Cache Read

+

{t('usage.cacheRead')}

{formatTokens(stats.total_cache_read_tokens)}

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

Most Used Models

+

{t('usage.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} sessions + {model.session_count} {t('usage.sessions')}
@@ -315,7 +317,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { -

Top Projects

+

{t('usage.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} sessions + {project.session_count} {t('usage.sessions')}
@@ -340,7 +342,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* Models Tab */} -

Usage by Model

+

{t('usage.usageByModel')}

{stats.by_model.map((model) => (
@@ -353,7 +355,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {getModelDisplayName(model.model)} - {model.session_count} sessions + {model.session_count} {t('usage.sessions')}
@@ -362,11 +364,11 @@ export const UsageDashboard: React.FC = ({ onBack }) => {
- Input: + {t('usage.input')}: {formatTokens(model.input_tokens)}
- Output: + {t('usage.output')}: {formatTokens(model.output_tokens)}
@@ -387,7 +389,7 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* Projects Tab */} -

Usage by Project

+

{t('usage.usageByProject')}

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

{formatCurrency(project.total_cost)}

- {formatCurrency(project.total_cost / project.session_count)}/session + {formatCurrency(project.total_cost / project.session_count)}/{t('usage.session')}

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

Usage by Session

+

{t('usage.usageBySession')}

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

- Daily Usage + {t('usage.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}

- Cost: {formatCurrency(day.total_cost)} + {t('usage.cost')}: {formatCurrency(day.total_cost)}

- {formatTokens(day.total_tokens)} tokens + {formatTokens(day.total_tokens)} {t('usage.tokens')}

- {day.models_used.length} model{day.models_used.length !== 1 ? 's' : ''} + {day.models_used.length} {t('usage.models')}{day.models_used.length !== 1 ? 's' : ''}

@@ -517,13 +519,13 @@ export const UsageDashboard: React.FC = ({ onBack }) => { {/* X-axis label */}
- Daily Usage Over Time + {t('usage.dailyUsageOverTime')}
) })() : (
- No usage data available for the selected period + {t('usage.noUsageData')}
)}
diff --git a/src/contexts/TabContext.tsx b/src/contexts/TabContext.tsx index 73fc33b..c00e2eb 100644 --- a/src/contexts/TabContext.tsx +++ b/src/contexts/TabContext.tsx @@ -1,4 +1,5 @@ import React, { createContext, useState, useContext, useCallback, useEffect } from 'react'; +import { useTranslation } from '@/hooks/useTranslation'; export interface Tab { id: string; @@ -37,6 +38,7 @@ const TabContext = createContext(undefined); const MAX_TABS = 20; export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const { t } = useTranslation(); const [tabs, setTabs] = useState([]); const [activeTabId, setActiveTabId] = useState(null); @@ -46,7 +48,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children const defaultTab: Tab = { id: generateTabId(), type: 'projects', - title: 'CC Projects', + title: t('ccProjects'), status: 'idle', hasUnsavedChanges: false, order: 0, @@ -55,7 +57,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children }; setTabs([defaultTab]); setActiveTabId(defaultTab.id); - }, []); + }, [t]); // Tab persistence disabled - no longer saving to localStorage // useEffect(() => { @@ -75,7 +77,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children const addTab = useCallback((tabData: Omit): string => { if (tabs.length >= MAX_TABS) { - throw new Error(`Maximum number of tabs (${MAX_TABS}) reached`); + throw new Error(t('maximumTabsReached', { max: MAX_TABS })); } const newTab: Tab = { @@ -89,7 +91,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children setTabs(prevTabs => [...prevTabs, newTab]); setActiveTabId(newTab.id); return newTab.id; - }, [tabs.length]); + }, [tabs.length, t]); const removeTab = useCallback((id: string) => { setTabs(prevTabs => { diff --git a/src/hooks/useTabState.ts b/src/hooks/useTabState.ts index a3e650f..be6de94 100644 --- a/src/hooks/useTabState.ts +++ b/src/hooks/useTabState.ts @@ -1,6 +1,7 @@ import { useCallback, useMemo } from 'react'; import { useTabContext } from '@/contexts/TabContext'; import { Tab } from '@/contexts/TabContext'; +import { useTranslation } from '@/hooks/useTranslation'; interface UseTabStateReturn { // State @@ -40,6 +41,7 @@ interface UseTabStateReturn { } export const useTabState = (): UseTabStateReturn => { + const { t } = useTranslation(); const { tabs, activeTabId, @@ -100,12 +102,12 @@ export const useTabState = (): UseTabStateReturn => { return addTab({ type: 'projects', - title: 'CC Projects', + title: t('ccProjects'), status: 'idle', hasUnsavedChanges: false, icon: 'folder' }); - }, [addTab, tabs, setActiveTab]); + }, [addTab, tabs, setActiveTab, t]); const createUsageTab = useCallback((): string | null => { // Check if usage tab already exists (singleton) @@ -117,12 +119,12 @@ export const useTabState = (): UseTabStateReturn => { return addTab({ type: 'usage', - title: 'Usage', + title: t('messages.usage'), status: 'idle', hasUnsavedChanges: false, icon: 'bar-chart' }); - }, [addTab, tabs, setActiveTab]); + }, [addTab, tabs, setActiveTab, t]); const createMCPTab = useCallback((): string | null => { // Check if MCP tab already exists (singleton) @@ -134,12 +136,12 @@ export const useTabState = (): UseTabStateReturn => { return addTab({ type: 'mcp', - title: 'MCP Servers', + title: t('messages.mcpServersTitle'), status: 'idle', hasUnsavedChanges: false, icon: 'server' }); - }, [addTab, tabs, setActiveTab]); + }, [addTab, tabs, setActiveTab, t]); const createSettingsTab = useCallback((): string | null => { // Check if settings tab already exists (singleton) @@ -151,12 +153,12 @@ export const useTabState = (): UseTabStateReturn => { return addTab({ type: 'settings', - title: 'Settings', + title: t('settings.title'), status: 'idle', hasUnsavedChanges: false, icon: 'settings' }); - }, [addTab, tabs, setActiveTab]); + }, [addTab, tabs, setActiveTab, t]); const createClaudeMdTab = useCallback((): string | null => { // Check if claude-md tab already exists (singleton) @@ -168,12 +170,12 @@ export const useTabState = (): UseTabStateReturn => { return addTab({ type: 'claude-md', - title: 'CLAUDE.md', + title: t('messages.claudemdTitle'), status: 'idle', hasUnsavedChanges: false, icon: 'file-text' }); - }, [addTab, tabs, setActiveTab]); + }, [addTab, tabs, setActiveTab, t]); const createClaudeFileTab = useCallback((fileId: string, fileName: string): string => { // Check if tab already exists for this file @@ -196,13 +198,13 @@ export const useTabState = (): UseTabStateReturn => { const createAgentExecutionTab = useCallback((agent: any, _tabId: string): string => { return addTab({ type: 'agent-execution', - title: `Run: ${agent.name}`, + title: t('messages.runAgent', { name: agent.name }), agentData: agent, status: 'idle', hasUnsavedChanges: false, icon: 'bot' }); - }, [addTab]); + }, [addTab, t]); const createCreateAgentTab = useCallback((): string => { // Check if create agent tab already exists (singleton) @@ -214,12 +216,12 @@ export const useTabState = (): UseTabStateReturn => { return addTab({ type: 'create-agent', - title: 'Create Agent', + title: t('messages.createAgent'), status: 'idle', hasUnsavedChanges: false, icon: 'plus' }); - }, [addTab, tabs, setActiveTab]); + }, [addTab, tabs, setActiveTab, t]); const createImportAgentTab = useCallback((): string => { // Check if import agent tab already exists (singleton) @@ -231,12 +233,12 @@ export const useTabState = (): UseTabStateReturn => { return addTab({ type: 'import-agent', - title: 'Import Agent', + title: t('messages.importAgent'), status: 'idle', hasUnsavedChanges: false, icon: 'import' }); - }, [addTab, tabs, setActiveTab]); + }, [addTab, tabs, setActiveTab, t]); const closeTab = useCallback(async (id: string, force: boolean = false): Promise => { const tab = getTabById(id); @@ -245,13 +247,13 @@ export const useTabState = (): UseTabStateReturn => { // Check for unsaved changes if (!force && tab.hasUnsavedChanges) { // In a real implementation, you'd show a confirmation dialog here - const confirmed = window.confirm(`Tab "${tab.title}" has unsaved changes. Close anyway?`); + const confirmed = window.confirm(t('messages.unsavedChangesCloseConfirm', { title: tab.title })); if (!confirmed) return false; } removeTab(id); return true; - }, [getTabById, removeTab]); + }, [getTabById, removeTab, t]); const closeCurrentTab = useCallback(async (): Promise => { if (!activeTabId) return true; diff --git a/src/locales/en/common.json b/src/locales/en/common.json index 872837d..bef9c34 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -1,4 +1,10 @@ { + "ccProjects": "CC Projects", + "browseClaudeCodeSessions": "Browse your Claude Code sessions", + "newClaudeCodeSession": "New Claude Code session", + "ccAgents": "CC Agents", + "mcpServers": "MCP Servers", + "manageMcpServers": "Manage Model Context Protocol servers", "app": { "name": "Claudia", "welcome": "Welcome to Claudia", @@ -19,7 +25,12 @@ "previous": "Previous", "refresh": "Refresh", "close": "Close", - "open": "Open" + "open": "Open", + "page": "Page", + "of": "of", + "loading": "Loading...", + "from": "from", + "retry": "Retry" }, "navigation": { "projects": "CC Projects", @@ -47,9 +58,10 @@ "editAgent": "Edit Agent", "deleteAgent": "Delete Agent", "executeAgent": "Execute Agent", - "agentName": "Agent Name", + "agentNameRequired": "Agent name is required", "agentIcon": "Agent Icon", "systemPrompt": "System Prompt", + "systemPromptRequired": "System prompt is required", "defaultTask": "Default Task", "model": "Model", "permissions": "Permissions", @@ -66,7 +78,60 @@ "createAgentDescription": "Create a new Claude Code agent", "updateAgentDescription": "Update your Claude Code agent", "createFailed": "Failed to create agent", - "updateFailed": "Failed to update agent" + "updateFailed": "Failed to update agent", + "basicInformation": "Basic Information", + "optional": "Optional", + "sonnetDescription": "Faster, efficient for most tasks", + "opusDescription": "More capable, better for complex tasks", + "defaultTaskDescription": "This will be used as the default task placeholder when executing the agent", + "systemPromptDescription": "Define the behavior and capabilities of your CC Agent", + "manageAgents": "Manage your Claude Code agents", + "noAgentsYet": "No agents yet", + "createFirstAgent": "Create your first CC Agent to get started", + "created": "Created", + "execute": "Execute", + "export": "Export", + "import": "Import", + "importFromFile": "From File", + "importFromGitHub": "From GitHub", + "recentExecutions": "Recent Executions", + "exportedSuccessfully": "Agent \"{{name}}\" exported successfully", + "exportFailed": "Failed to export agent", + "importedSuccessfully": "Agent imported successfully", + "importFailed": "Failed to import agent", + "importFromGitHubSuccess": "Agent imported successfully from GitHub", + "executeAgentTitle": "Execute agent", + "deleteAgentTitle": "Delete Agent", + "deleteConfirmation": "Are you sure you want to delete the agent \"{{name}}\"? This action cannot be undone and will permanently remove the agent and all its associated data.", + "deleting": "Deleting...", + "deleteAgentButton": "Delete Agent", + "agentManagement": "Agent Management", + "createNewOrManageAgents": "Create new agents or manage running agent executions", + "noAgentsAvailable": "No agents available", + "availableAgents": "Available Agents", + "runningAgents": "Running Agents", + "createFirstAgentToGetStarted": "Create your first agent to get started" + }, + "slashCommands": { + "slashCommands": "Slash Commands", + "projectSlashCommands": "Project Slash Commands", + "createCustomCommandsProject": "Create custom commands for this project", + "createCustomCommandsWorkflow": "Create custom commands to streamline your workflow", + "newCommand": "New Command", + "allCommands": "All Commands", + "project": "Project", + "user": "User", + "noCommandsFound": "No commands found", + "noProjectCommandsYet": "No project commands created yet", + "noCommandsYet": "No commands created yet", + "createFirstProjectCommand": "Create your first project command", + "createFirstCommand": "Create your first command" + }, + "hooks": { + "hooksConfiguration": "Hooks Configuration", + "configureShellCommands": "Configure shell commands to execute at various points in Claude Code's lifecycle.", + "localSettingsNote": " These settings are not committed to version control.", + "unsavedChanges": "You have unsaved changes. Click Save to persist them." }, "settings": { "title": "Settings", @@ -85,7 +150,142 @@ "noProxy": "No Proxy", "analyticsConsent": "Analytics Consent", "enableAnalytics": "Enable Analytics", - "disableAnalytics": "Disable Analytics" + "disableAnalytics": "Disable Analytics", + "configurePreferences": "Configure Claude Code preferences", + "generalSettings": "General Settings", + "permissionsTab": "Permissions", + "environmentTab": "Environment", + "advancedTab": "Advanced", + "hooksTab": "Hooks", + "commands": "Commands", + "storage": "Storage", + "proxy": "Proxy", + "analyticsTab": "Analytics", + "proxySettingsTitle": "Proxy Settings", + "proxySettingsDesc": "Configure proxy settings for Claude API requests", + "enableProxyDesc": "Use proxy for all Claude API requests", + "noProxyDesc": "Comma-separated list of hosts that should bypass the proxy", + "allProxyDesc": "Proxy URL to use for all protocols if protocol-specific proxies are not set", + "proxySettingsSaved": "Proxy settings saved and applied successfully.", + "loadProxySettingsFailed": "Failed to load proxy settings", + "saveProxySettingsFailed": "Failed to save proxy settings", + "themeSelector": { + "selectATheme": "Select a theme", + "dark": "Dark", + "gray": "Gray", + "light": "Light", + "custom": "Custom", + "choosePreferredTheme": "Choose your preferred color theme for the interface" + }, + "customTheme": { + "title": "Custom Theme Colors", + "background": "Background", + "foreground": "Foreground", + "primary": "Primary", + "card": "Card", + "accent": "Accent", + "destructive": "Destructive", + "colorValuesDesc": "Use CSS color values (hex, rgb, oklch, etc.). Changes apply immediately." + }, + "generalOptions": { + "includeCoAuthor": "Include \"Co-authored by Claude\"", + "includeCoAuthorDesc": "Add Claude attribution to git commits and pull requests", + "verboseOutput": "Verbose Output", + "verboseOutputDesc": "Show full bash and command outputs", + "chatRetention": "Chat Transcript Retention (days)", + "chatRetentionDesc": "How long to retain chat transcripts locally (default: 30 days)", + "claudeCodeInstallation": "Claude Code Installation", + "choosePreferredInstallation": "Choose your preferred Claude Code installation.", + "loadingAvailableInstallations": "Loading available installations...", + "errorLoadingInstallations": "Error loading installations", + "availableInstallations": "Available Installations", + "claudeCodeInstallationDesc": "Select which Claude Code installation to use.", + "binaryPathChanged": "⚠️ Claude binary path has been changed. Remember to save your settings." + }, + "permissions": { + "permissionRules": "Permission Rules", + "permissionRulesDesc": "Control which tools Claude Code can use without manual approval", + "allowRules": "Allow Rules", + "denyRules": "Deny Rules", + "addRule": "Add Rule", + "noAllowRules": "No allow rules configured. Claude will ask for approval for all tools.", + "noDenyRules": "No deny rules configured.", + "examples": "Examples:", + "exampleBash": "Allow all bash commands", + "exampleExactCommand": "Allow exact command", + "examplePrefix": "Allow commands with prefix", + "exampleReadFile": "Allow reading specific file", + "exampleEditDir": "Allow editing files in docs directory" + }, + "environment": { + "environmentVariables": "Environment Variables", + "environmentVariablesDesc": "Environment variables applied to every Claude Code session", + "addVariable": "Add Variable", + "noEnvironmentVariables": "No environment variables configured.", + "commonVariables": "Common variables:", + "telemetryDesc": "Enable/disable telemetry (0 or 1)", + "modelDesc": "Custom model name", + "costWarningsDesc": "Disable cost warnings (1)" + }, + "advanced": { + "advancedSettings": "Advanced Settings", + "advancedSettingsDesc": "Additional configuration options for advanced users", + "apiKeyHelper": "API Key Helper Script", + "apiKeyHelperDesc": "Custom script to generate auth values for API requests", + "rawSettings": "Raw Settings (JSON)", + "rawSettingsDesc": "This shows the raw JSON that will be saved to ~/.claude/settings.json" + }, + "hooks": { + "userHooks": "User Hooks", + "userHooksDesc": "Configure hooks that apply to all Claude Code sessions for your user account. These are stored in ~/.claude/settings.json" + }, + "analytics": { + "analyticsSettings": "Analytics Settings", + "enableAnalytics": "Enable Analytics", + "enableAnalyticsDesc": "Help improve Claudia by sharing anonymous usage data", + "privacyProtected": "Your privacy is protected", + "noPersonalInfo": "No personal information is collected", + "noFileContents": "No file contents, paths, or project names", + "anonymousData": "All data is anonymous with random IDs", + "canDisable": "You can disable analytics at any time", + "whatWeCollect": "What we collect:", + "featureUsage": "Feature usage patterns", + "performanceMetrics": "Performance metrics", + "errorReports": "Error reports (without sensitive data)", + "sessionFrequency": "Session frequency and duration", + "deleteAllData": "Delete All Analytics Data", + "analyticsEnabled": "Analytics enabled", + "analyticsDisabled": "Analytics disabled", + "allDataDeleted": "All analytics data deleted", + "helpImproveClaudia": "Help Improve Claudia", + "collectAnonymousData": "We'd like to collect anonymous usage data to improve your experience.", + "featureUsageDesc": "Feature usage (which tools and commands you use)", + "performanceMetricsDesc": "Performance metrics (app speed and reliability)", + "errorReportsDesc": "Error reports (to fix bugs and improve stability)", + "usagePatternsDesc": "General usage patterns (session frequency and duration)", + "noApiKeys": "No API keys or sensitive data", + "canOptOut": "You can opt-out anytime in Settings", + "dataHelpsUs": "This data helps us understand which features are most valuable, identify performance issues, and prioritize improvements. Your choice won't affect any functionality.", + "noThanks": "No Thanks", + "allowAnalytics": "Allow Analytics" + }, + "saveButton": { + "saving": "Saving...", + "saveSettings": "Save Settings", + "settingsSavedSuccess": "Settings saved successfully!", + "settingsSaveFailed": "Failed to save settings" + }, + "messages": { + "loadFailed": "Failed to load settings. Please ensure ~/.claude directory exists.", + "saveFailed": "Failed to save settings." + }, + "placeholders": { + "envVarKey": "KEY", + "envVarValue": "value", + "allowRuleExample": "e.g., Bash(npm run test:*)", + "denyRuleExample": "e.g., Bash(curl:*)", + "apiKeyHelperPath": "/path/to/generate_api_key.sh" + } }, "mcp": { "title": "MCP Server Management", @@ -110,7 +310,118 @@ "last7Days": "Last 7 Days", "last30Days": "Last 30 Days", "allTime": "All Time", - "exportData": "Export Data" + "exportData": "Export Data", + "usageDashboardTitle": "Usage Dashboard", + "trackUsageAndCosts": "Track your Claude Code usage and costs", + "allTime": "All Time", + "totalCost": "Total Cost", + "totalSessions": "Total Sessions", + "totalTokens": "Total Tokens", + "avgCostPerSession": "Avg Cost/Session", + "overview": "Overview", + "tokenBreakdown": "Token Breakdown", + "inputTokens": "Input Tokens", + "outputTokens": "Output Tokens", + "cacheWrite": "Cache Write", + "cacheRead": "Cache Read", + "mostUsedModels": "Most Used Models", + "topProjects": "Top Projects", + "sessions": "sessions", + "usageByModel": "Usage by Model", + "usageByProject": "Usage by Project", + "usageBySession": "Usage by Session", + "timeline": "Timeline", + "dailyUsage": "Daily Usage", + "tokens": "tokens", + "models": "models", + "input": "Input", + "output": "Output", + "session": "session", + "loadingUsageStats": "Loading usage statistics...", + "failedToLoadUsageStats": "Failed to load usage statistics. Please try again.", + "tryAgain": "Try Again", + "dailyUsageOverTime": "Daily Usage Over Time", + "noUsageData": "No usage data available for the selected period", + "cost": "Cost", + "lastUsed": "Last Used", + "markdownEditorTitle": "Markdown Editor", + "editSystemPrompt": "Edit your Claude Code system prompt", + "claudemdFile": "CLAUDE.md file", + "loadClaudemdFailed": "Failed to load CLAUDE.md file", + "saveClaudemdFailed": "Failed to save CLAUDE.md file", + "claudemdSavedSuccess": "CLAUDE.md saved successfully", + "saveClaudemd": "Save CLAUDE.md", + "unsavedChangesConfirm": "You have unsaved changes. Are you sure you want to leave?", + "servers": "Servers", + "addServer": "Add Server", + "importExport": "Import/Export", + "mcpServerAdded": "MCP server added successfully!", + "serverRemovedSuccess": "Server \"{{name}}\" removed successfully!", + "importedServersSuccess": "Successfully imported {{count}} server{{plural}}!", + "importedServersFailed": "Imported {{imported}} server{{importedPlural}}, {{failed}} failed", + "loadMcpServersFailed": "Failed to load MCP servers. Make sure Claude Code is installed.", + "addMcpServer": "Add MCP Server", + "configureNewMcpServer": "Configure a new Model Context Protocol server", + "serverNameRequired": "Server name is required", + "commandRequired": "Command is required", + "urlRequired": "URL is required", + "failedToAddServer": "Failed to add server", + "environmentVariables": "Environment Variables", + "addVariable": "Add Variable", + "serverName": "Server Name", + "command": "Command", + "argumentsOptional": "Arguments (optional)", + "scope": "Scope", + "localProjectOnly": "Local (this project only)", + "projectSharedViaMcp": "Project (shared via .mcp.json)", + "userAllProjects": "User (all projects)", + "addingServer": "Adding Server...", + "addStdioServer": "Add Stdio Server", + "url": "URL", + "addSseServer": "Add SSE Server", + "exampleCommands": "Example Commands", + "uniqueNameToIdentify": "A unique name to identify this server", + "commandToExecuteServer": "The command to execute the server", + "spaceSeparatedArgs": "Space-separated command arguments", + "sseEndpointUrl": "The SSE endpoint URL", + "running": "Running", + "showFull": "Show full", + "hide": "Hide", + "copied": "Copied!", + "copy": "Copy", + "arguments": "Arguments", + "environmentVariables": "Environment Variables", + "configuredServers": "Configured Servers", + "serversConfigured": "configured", + "refresh": "Refresh", + "noMcpServersConfigured": "No MCP servers configured", + "addServerToGetStarted": "Add a server to get started with Model Context Protocol", + "localProjectSpecific": "Local (Project-specific)", + "projectSharedMcp": "Project (Shared via .mcp.json)", + "userAllProjects": "User (All projects)", + "welcomeToClaudia": "Welcome to Claudia", + "backToHome": "Back to Home", + "noProjectsFound": "No projects found in ~/.claude/projects", + "failedToLoadProjects": "Failed to load projects. Please ensure ~/.claude directory exists.", + "failedToLoadSessions": "Failed to load sessions for this project.", + "claudeBinaryPathSaved": "Claude binary path saved successfully", + "selectClaudeCodeInstallation": "Select Claude Code Installation", + "multipleInstallationsFound": "Multiple Claude Code installations were found on your system. Please select which one you'd like to use.", + "claudeCodeNotFoundDialog": "Claude Code was not found in any of the common installation locations. Please install Claude Code to continue.", + "searchedLocations": "Searched locations", + "installationTip": "You can install Claude Code using", + "searchingInstallations": "Searching for Claude installations...", + "installationGuide": "Installation Guide", + "validating": "Validating...", + "saveSelection": "Save Selection", + "noInstallationsFound": "No Installations Found", + "pleaseSelectInstallation": "Please select a Claude installation", + "fileSavedSuccess": "File saved successfully", + "saveFileFailed": "Failed to save file", + "loadFileFailed": "Failed to load CLAUDE.md file", + "editProjectSpecificPrompt": "Edit project-specific Claude Code system prompt", + "maximumTabsReached": "Maximum number of tabs ({{max}}) reached", + "saving": "Saving..." }, "checkpoint": { "title": "Checkpoints", @@ -121,14 +432,39 @@ "checkpointMessage": "Checkpoint Message", "timeline": "Timeline", "diff": "Diff", - "noCheckpoints": "No checkpoints found" + "noCheckpoints": "No checkpoints found", + "checkpointSettingsTitle": "Checkpoint Settings", + "experimentalFeature": "Experimental Feature", + "checkpointWarning": "Checkpointing may affect directory structure or cause data loss. Use with caution.", + "automaticCheckpoints": "Automatic Checkpoints", + "automaticCheckpointsDesc": "Automatically create checkpoints based on the selected strategy", + "checkpointStrategy": "Checkpoint Strategy", + "manualOnly": "Manual Only", + "afterEachPrompt": "After Each Prompt", + "afterToolUse": "After Tool Use", + "smart": "Smart (Recommended)", + "manualOnlyDesc": "Checkpoints will only be created manually", + "afterEachPromptDesc": "A checkpoint will be created after each user prompt", + "afterToolUseDesc": "A checkpoint will be created after each tool use", + "smartDesc": "Checkpoints will be created after destructive operations", + "saving": "Saving...", + "saveSettings": "Save Settings", + "storageManagement": "Storage Management", + "totalCheckpoints": "Total checkpoints", + "keepRecentCheckpoints": "Keep Recent Checkpoints", + "cleanUp": "Clean Up", + "removeOldCheckpoints": "Remove old checkpoints, keeping only the most recent {{count}}", + "checkpointSettingsFailed": "Failed to load checkpoint settings", + "saveCheckpointSettingsFailed": "Failed to save checkpoint settings", + "cleanupCheckpointsFailed": "Failed to cleanup checkpoints", + "removedOldCheckpoints": "Removed {{count}} old checkpoints" }, "placeholders": { "searchProjects": "Search projects...", "searchAgents": "Search agents...", - "enterAgentName": "Enter agent name...", + "enterAgentName": "e.g., Code Assistant", "enterSystemPrompt": "Enter system prompt...", - "enterDefaultTask": "Enter default task...", + "enterDefaultTask": "e.g., Review this code for security issues", "enterURL": "Enter URL...", "searchCommands": "Search commands...", "enterCommand": "Enter command...", @@ -151,6 +487,34 @@ "unknownError": "Unknown error occurred", "claudeCodeNotFound": "Claude Code not found", "selectClaudeInstallation": "Select Claude Installation", - "installClaudeCode": "Install Claude Code" + "installClaudeCode": "Install Claude Code", + "noTabsOpen": "No tabs open", + "clickPlusToStartChat": "Click the + button to start a new chat", + "noAgentRunIdSpecified": "No agent run ID specified", + "noClaudeFileIdSpecified": "No Claude file ID specified", + "claudeFileEditorNotImplemented": "Claude file editor not yet implemented in tabs", + "noAgentDataSpecified": "No agent data specified", + "importAgentComingSoon": "Import agent functionality coming soon...", + "unknownTabType": "Unknown tab type", + "letClaudeDecide": "Let Claude decide", + "basicReasoning": "Basic reasoning", + "deeperAnalysis": "Deeper analysis", + "extensiveReasoning": "Extensive reasoning", + "maximumAnalysis": "Maximum analysis", + "typeYourPromptHere": "Type your prompt here...", + "dropImagesHere": "Drop images here...", + "askClaudeAnything": "Ask Claude anything...", + "usage": "Usage", + "mcpServersTitle": "MCP Servers", + "claudemdTitle": "CLAUDE.md", + "runAgent": "Run: {{name}}", + "createAgent": "Create Agent", + "importAgent": "Import Agent", + "unsavedChangesCloseConfirm": "Tab \"{{title}}\" has unsaved changes. Close anyway?", + "session": "Session" + }, + "input": { + "pressEnterToSend": "Press Enter to send, Shift+Enter for new line", + "withFileAndCommandSupport": ", @ to mention files, / for commands, drag & drop or paste images" } } \ No newline at end of file diff --git a/src/locales/zh/common.json b/src/locales/zh/common.json index 6579fea..c9ff2c5 100644 --- a/src/locales/zh/common.json +++ b/src/locales/zh/common.json @@ -1,4 +1,10 @@ { + "ccProjects": "Claude Code 项目", + "browseClaudeCodeSessions": "浏览您的 Claude Code 会话", + "newClaudeCodeSession": "新建 Claude Code 会话", + "ccAgents": "CC 智能体", + "mcpServers": "MCP 服务器", + "manageMcpServers": "管理模型上下文协议服务器", "app": { "name": "Claudia", "welcome": "欢迎使用 Claudia", @@ -18,11 +24,13 @@ "next": "下一步", "previous": "上一步", "refresh": "刷新", - "close": "关闭", - "open": "打开" + "page": "页面", + "of": "共", + "loading": "加载中...", + "from": "从" }, "navigation": { - "projects": "CC 项目", + "projects": "Claude Code 项目", "agents": "CC 智能体", "settings": "设置", "usage": "用量仪表板", @@ -47,9 +55,10 @@ "editAgent": "编辑智能体", "deleteAgent": "删除智能体", "executeAgent": "执行智能体", - "agentName": "智能体名称", + "agentNameRequired": "代理名称为必填项", "agentIcon": "智能体图标", "systemPrompt": "系统提示", + "systemPromptRequired": "系统提示为必填项", "defaultTask": "默认任务", "model": "模型", "permissions": "权限", @@ -66,7 +75,60 @@ "createAgentDescription": "创建新的 Claude Code 智能体", "updateAgentDescription": "更新您的 Claude Code 智能体", "createFailed": "创建智能体失败", - "updateFailed": "更新智能体失败" + "updateFailed": "更新智能体失败", + "basicInformation": "基本信息", + "optional": "可选", + "sonnetDescription": "更快,适用于大多数任务", + "opusDescription": "功能更强,适用于复杂任务", + "defaultTaskDescription": "执行智能体时将用作默认任务占位符", + "systemPromptDescription": "定义您的 CC 智能体的行为和功能", + "manageAgents": "管理您的 Claude Code 智能体", + "noAgentsYet": "还没有智能体", + "createFirstAgent": "创建您的第一个 CC 智能体来开始", + "created": "创建于", + "execute": "执行", + "export": "导出", + "import": "导入", + "importFromFile": "从文件导入", + "importFromGitHub": "从 GitHub 导入", + "recentExecutions": "最近执行记录", + "exportedSuccessfully": "智能体 \"{{name}}\" 导出成功", + "exportFailed": "导出智能体失败", + "importedSuccessfully": "智能体导入成功", + "importFailed": "导入智能体失败", + "importFromGitHubSuccess": "从 GitHub 导入智能体成功", + "executeAgentTitle": "执行智能体", + "deleteAgentTitle": "删除智能体", + "deleteConfirmation": "确定要删除智能体 \"{{name}}\" 吗?此操作无法撤销,将永久删除智能体及其所有相关数据。", + "deleting": "删除中...", + "deleteAgentButton": "删除智能体", + "agentManagement": "代理管理", + "createNewOrManageAgents": "创建新代理或管理运行中的代理执行", + "noAgentsAvailable": "无可用代理", + "availableAgents": "可用代理", + "runningAgents": "运行中的代理", + "createFirstAgentToGetStarted": "创建您的第一个代理开始使用" + }, + "slashCommands": { + "slashCommands": "斜杠命令", + "projectSlashCommands": "项目斜杠命令", + "createCustomCommandsProject": "为此项目创建自定义命令", + "createCustomCommandsWorkflow": "创建自定义命令来简化您的工作流程", + "newCommand": "新建命令", + "allCommands": "所有命令", + "project": "项目", + "user": "用户", + "noCommandsFound": "未找到命令", + "noProjectCommandsYet": "尚未创建项目命令", + "noCommandsYet": "尚未创建命令", + "createFirstProjectCommand": "创建您的第一个项目命令", + "createFirstCommand": "创建您的第一个命令" + }, + "hooks": { + "hooksConfiguration": "钩子配置", + "configureShellCommands": "配置在 Claude Code 生命周期的各个阶段执行的 shell 命令。", + "localSettingsNote": " 这些设置不会提交到版本控制中。", + "unsavedChanges": "您有未保存的更改。点击保存以持久化它们。" }, "settings": { "title": "设置", @@ -85,7 +147,142 @@ "noProxy": "无代理", "analyticsConsent": "分析同意", "enableAnalytics": "启用分析", - "disableAnalytics": "禁用分析" + "disableAnalytics": "禁用分析", + "configurePreferences": "配置 Claude Code 偏好设置", + "generalSettings": "常规设置", + "permissionsTab": "权限", + "environmentTab": "环境", + "advancedTab": "高级", + "hooksTab": "钩子", + "commands": "命令", + "storage": "存储", + "proxy": "代理", + "analyticsTab": "分析", + "proxySettingsTitle": "代理设置", + "proxySettingsDesc": "为 Claude API 请求配置代理设置", + "enableProxyDesc": "为所有 Claude API 请求使用代理", + "noProxyDesc": "逗号分隔的主机列表,这些主机应绕过代理", + "allProxyDesc": "如果未设置协议特定代理,则用于所有协议的代理 URL", + "proxySettingsSaved": "代理设置已保存并应用成功。", + "loadProxySettingsFailed": "加载代理设置失败", + "saveProxySettingsFailed": "保存代理设置失败", + "themeSelector": { + "selectATheme": "选择主题", + "dark": "暗色", + "gray": "灰色", + "light": "浅色", + "custom": "自定义", + "choosePreferredTheme": "选择您偏好的界面配色主题" + }, + "customTheme": { + "title": "自定义主题配色", + "background": "背景色", + "foreground": "前景色", + "primary": "主色", + "card": "卡片色", + "accent": "强调色", + "destructive": "危险色", + "colorValuesDesc": "使用 CSS 颜色值 (hex、rgb、oklch 等)。更改将立即生效。" + }, + "generalOptions": { + "includeCoAuthor": "包含 \"由 Claude 协作\" 标识", + "includeCoAuthorDesc": "在 git 提交和拉取请求中添加 Claude 归属标识", + "verboseOutput": "详细输出", + "verboseOutputDesc": "显示完整的 bash 和命令输出", + "chatRetention": "聊天记录保留期 (天)", + "chatRetentionDesc": "本地保留聊天记录的时长(默认:30 天)", + "claudeCodeInstallation": "Claude Code 安装", + "choosePreferredInstallation": "选择您偏好的 Claude Code 安装。", + "loadingAvailableInstallations": "正在加载可用安装...", + "errorLoadingInstallations": "加载安装时出错", + "availableInstallations": "可用安装", + "claudeCodeInstallationDesc": "选择要使用的 Claude Code 安装。", + "binaryPathChanged": "⚠️ Claude 二进制路径已更改。请记住保存您的设置。" + }, + "permissions": { + "permissionRules": "权限规则", + "permissionRulesDesc": "控制 Claude Code 可以使用哪些工具而无需手动批准", + "allowRules": "允许规则", + "denyRules": "拒绝规则", + "addRule": "添加规则", + "noAllowRules": "未配置允许规则。Claude 将请求批准所有工具。", + "noDenyRules": "未配置拒绝规则。", + "examples": "示例:", + "exampleBash": "允许所有 bash 命令", + "exampleExactCommand": "允许精确命令", + "examplePrefix": "允许带前缀的命令", + "exampleReadFile": "允许读取指定文件", + "exampleEditDir": "允许编辑 docs 目录中的文件" + }, + "environment": { + "environmentVariables": "环境变量", + "environmentVariablesDesc": "应用于每个 Claude Code 会话的环境变量", + "addVariable": "添加变量", + "noEnvironmentVariables": "未配置环境变量。", + "commonVariables": "常用变量:", + "telemetryDesc": "启用/禁用遥测 (0 或 1)", + "modelDesc": "自定义模型名称", + "costWarningsDesc": "禁用成本警告 (1)" + }, + "advanced": { + "advancedSettings": "高级设置", + "advancedSettingsDesc": "面向高级用户的额外配置选项", + "apiKeyHelper": "API 密钥辅助脚本", + "apiKeyHelperDesc": "为 API 请求生成认证值的自定义脚本", + "rawSettings": "原始设置 (JSON)", + "rawSettingsDesc": "这显示了将保存到 ~/.claude/settings.json 的原始 JSON 数据" + }, + "hooks": { + "userHooks": "用户钩子", + "userHooksDesc": "配置适用于您用户账户所有 Claude Code 会话的钩子。这些存储在 ~/.claude/settings.json 中" + }, + "analytics": { + "analyticsSettings": "分析设置", + "enableAnalytics": "启用分析", + "enableAnalyticsDesc": "通过分享匿名使用数据帮助改善 Claudia", + "privacyProtected": "您的隐私受到保护", + "noPersonalInfo": "不收集个人信息", + "noFileContents": "不收集文件内容、路径或项目名称", + "anonymousData": "所有数据都是匿名的,使用随机 ID", + "canDisable": "您可以随时禁用分析", + "whatWeCollect": "我们收集什么:", + "featureUsage": "功能使用模式", + "performanceMetrics": "性能指标", + "errorReports": "错误报告(不包含敏感数据)", + "sessionFrequency": "会话频率和时长", + "deleteAllData": "删除所有分析数据", + "analyticsEnabled": "分析已启用", + "analyticsDisabled": "分析已禁用", + "allDataDeleted": "所有分析数据已删除", + "helpImproveClaudia": "帮助改善 Claudia", + "collectAnonymousData": "我们希望收集匿名使用数据以改善您的使用体验。", + "featureUsageDesc": "功能使用(您使用的工具和命令)", + "performanceMetricsDesc": "性能指标(应用速度和可靠性)", + "errorReportsDesc": "错误报告(用于修复错误和提高稳定性)", + "usagePatternsDesc": "一般使用模式(会话频率和持续时间)", + "noApiKeys": "不收集 API 密钥或敏感数据", + "canOptOut": "您可以随时在设置中选择退出", + "dataHelpsUs": "这些数据有助于我们了解哪些功能最有价值,识别性能问题,并优先进行改进。您的选择不会影响任何功能。", + "noThanks": "不用了", + "allowAnalytics": "允许分析" + }, + "saveButton": { + "saving": "保存中...", + "saveSettings": "保存设置", + "settingsSavedSuccess": "设置保存成功!", + "settingsSaveFailed": "保存设置失败" + }, + "messages": { + "loadFailed": "加载设置失败。请确保 ~/.claude 目录存在。", + "saveFailed": "保存设置失败。" + }, + "placeholders": { + "envVarKey": "键名", + "envVarValue": "值", + "allowRuleExample": "例如:Bash(npm run test:*)", + "denyRuleExample": "例如:Bash(curl:*)", + "apiKeyHelperPath": "/path/to/generate_api_key.sh" + } }, "mcp": { "title": "MCP 服务器管理", @@ -110,7 +307,140 @@ "last7Days": "最近 7 天", "last30Days": "最近 30 天", "allTime": "全部时间", - "exportData": "导出数据" + "exportData": "导出数据", + "usageDashboardTitle": "用量仪表板", + "trackUsageAndCosts": "跟踪您的 Claude Code 用量和成本", + "allTime": "全部时间", + "totalCost": "总成本", + "totalSessions": "总会话数", + "totalTokens": "总令牌数", + "avgCostPerSession": "平均每会话成本", + "overview": "概览", + "tokenBreakdown": "令牌明细", + "inputTokens": "输入令牌", + "outputTokens": "输出令牌", + "cacheWrite": "缓存写入", + "cacheRead": "缓存读取", + "mostUsedModels": "最常用模型", + "topProjects": "热门项目", + "sessions": "会话", + "usageByModel": "按模型统计用量", + "usageByProject": "按项目统计用量", + "usageBySession": "按会话统计用量", + "timeline": "时间线", + "dailyUsage": "日常用量", + "tokens": "令牌", + "models": "模型", + "input": "输入", + "output": "输出", + "session": "会话", + "loadingUsageStats": "加载用量统计中...", + "failedToLoadUsageStats": "加载用量统计失败。请重试。", + "tryAgain": "重试", + "dailyUsageOverTime": "随时间变化的日常用量", + "noUsageData": "选定时期内无用量数据", + "cost": "成本", + "lastUsed": "上次使用", + "markdownEditorTitle": "Markdown 编辑器", + "editSystemPrompt": "编辑您的 Claude Code 系统提示", + "claudemdFile": "CLAUDE.md 文件", + "loadClaudemدFailed": "加载 CLAUDE.md 文件失败", + "saveClaudemdFailed": "保存 CLAUDE.md 文件失败", + "claudemdSavedSuccess": "CLAUDE.md 保存成功", + "saveClaudemd": "保存 CLAUDE.md", + "unsavedChangesConfirm": "您有未保存的更改。确定要离开吗?", + "servers": "服务器", + "addServer": "添加服务器", + "importExport": "导入/导出", + "mcpServerAdded": "MCP 服务器添加成功!", + "serverRemovedSuccess": "服务器 \"{{name}}\" 删除成功!", + "importedServersSuccess": "成功导入 {{count}} 个服务器!", + "importedServersFailed": "导入 {{imported}} 个服务器,{{failed}} 个失败", + "loadMcpServersFailed": "加载 MCP 服务器失败。请确保 Claude Code 已安装。", + "addMcpServer": "添加 MCP 服务器", + "configureNewMcpServer": "配置新的模型上下文协议服务器", + "serverNameRequired": "服务器名称为必填项", + "commandRequired": "命令为必填项", + "urlRequired": "URL为必填项", + "failedToAddServer": "添加服务器失败", + "environmentVariables": "环境变量", + "addVariable": "添加变量", + "serverName": "服务器名称", + "command": "命令", + "argumentsOptional": "参数(可选)", + "scope": "作用域", + "localProjectOnly": "本地(仅此项目)", + "projectSharedViaMcp": "项目(通过 .mcp.json 共享)", + "addingServer": "添加服务器中...", + "addStdioServer": "添加 Stdio 服务器", + "url": "URL", + "addSseServer": "添加 SSE 服务器", + "exampleCommands": "示例命令", + "uniqueNameToIdentify": "用于标识此服务器的唯一名称", + "commandToExecuteServer": "执行服务器的命令", + "spaceSeparatedArgs": "空格分隔的命令参数", + "sseEndpointUrl": "SSE 端点 URL", + "running": "运行中", + "showFull": "显示全部", + "hide": "隐藏", + "copied": "已复制", + "copy": "复制", + "arguments": "参数", + "environmentVariables": "环境变量", + "configuredServers": "已配置的服务器", + "serversConfigured": "个服务器已配置", + "refresh": "刷新", + "noMcpServersConfigured": "未配置 MCP 服务器", + "addServerToGetStarted": "添加一个服务器开始使用模型上下文协议", + "localProjectSpecific": "本地(项目特定)", + "projectSharedMcp": "项目(通过 .mcp.json 共享)", + "userAllProjects": "用户(所有项目)", + "letClaudeDecide": "让 Claude 决定", + "basicReasoning": "基础推理", + "deeperAnalysis": "更深入的分析", + "extensiveReasoning": "广泛推理", + "maximumAnalysis": "最大化分析", + "typeYourPromptHere": "在此输入您的提示...", + "dropImagesHere": "在此拖放图像...", + "askClaudeAnything": "向 Claude 提问任何问题...", + "usage": "用量", + "mcpServersTitle": "MCP 服务器", + "claudemdTitle": "CLAUDE.md", + "runAgent": "运行: {{name}}", + "createAgent": "创建智能体", + "importAgent": "导入智能体", + "unsavedChangesCloseConfirm": "标签页 \"{{title}}\" 有未保存的更改。仍要关闭吗?", + "welcomeToClaudia": "欢迎使用 Claudia", + "backToHome": "返回首页", + "noProjectsFound": "在 ~/.claude/projects 中未找到项目", + "failedToLoadProjects": "加载项目失败。请确保 ~/.claude 目录存在。", + "failedToLoadSessions": "加载此项目的会话失败。", + "claudeBinaryPathSaved": "Claude 二进制路径保存成功", + "noTabsOpen": "未打开标签页", + "clickPlusToStartChat": "点击 + 按钮开始新聊天", + "noAgentRunIdSpecified": "未指定智能体运行ID", + "noClaudeFileIdSpecified": "未指定Claude文件ID", + "claudeFileEditorNotImplemented": "标签页中的Claude文件编辑器尚未实现", + "noAgentDataSpecified": "未指定智能体数据", + "importAgentComingSoon": "导入智能体功能即将推出...", + "unknownTabType": "未知的标签页类型", + "selectClaudeCodeInstallation": "选择 Claude Code 安装", + "multipleInstallationsFound": "在您的系统上找到了多个 Claude Code 安装。请选择您要使用的安装。", + "claudeCodeNotFoundDialog": "在常见安装位置中未找到 Claude Code。请安装 Claude Code 以继续。", + "searchedLocations": "搜索位置", + "installationTip": "提示:您可以使用以下命令安装 Claude Code", + "searchingInstallations": "正在搜索 Claude 安装...", + "installationGuide": "安装指南", + "validating": "验证中...", + "saveSelection": "保存选择", + "noInstallationsFound": "未找到安装", + "pleaseSelectInstallation": "请选择一个 Claude 安装", + "fileSavedSuccess": "文件保存成功", + "saveFileFailed": "保存文件失败", + "loadFileFailed": "加载 CLAUDE.md 文件失败", + "editProjectSpecificPrompt": "编辑项目特定的 Claude Code 系统提示", + "maximumTabsReached": "已达到最大标签页数量 ({{max}})", + "saving": "保存中..." }, "checkpoint": { "title": "检查点", @@ -121,14 +451,44 @@ "checkpointMessage": "检查点消息", "timeline": "时间线", "diff": "差异", - "noCheckpoints": "未找到检查点" + "deleteCheckpoint": "删除检查点", + "checkpointName": "检查点名称", + "checkpointMessage": "检查点消息", + "timeline": "时间线", + "diff": "差异", + "noCheckpoints": "未找到检查点", + "checkpointSettingsTitle": "检查点设置", + "experimentalFeature": "实验性功能", + "checkpointWarning": "检查点可能会影响目录结构或导致数据丢失。请谨慎使用。", + "automaticCheckpoints": "自动检查点", + "automaticCheckpointsDesc": "根据所选策略自动创建检查点", + "checkpointStrategy": "检查点策略", + "manualOnly": "仅手动", + "afterEachPrompt": "每个提示后", + "afterToolUse": "工具使用后", + "smart": "智能(推荐)", + "manualOnlyDesc": "检查点将仅手动创建", + "afterEachPromptDesc": "在每个用户提示后创建检查点", + "afterToolUseDesc": "在每次工具使用后创建检查点", + "smartDesc": "在破坏性操作后创建检查点", + "saving": "保存中...", + "saveSettings": "保存设置", + "storageManagement": "存储管理", + "totalCheckpoints": "总检查点数", + "keepRecentCheckpoints": "保留最近检查点", + "cleanUp": "清理", + "removeOldCheckpoints": "删除旧检查点,仅保留最近的 {{count}} 个", + "checkpointSettingsFailed": "加载检查点设置失败", + "saveCheckpointSettingsFailed": "保存检查点设置失败", + "cleanupCheckpointsFailed": "清理检查点失败", + "removedOldCheckpoints": "已删除 {{count}} 个旧检查点" }, "placeholders": { "searchProjects": "搜索项目...", "searchAgents": "搜索智能体...", - "enterAgentName": "输入智能体名称...", + "enterAgentName": "例如:代码助手", "enterSystemPrompt": "输入系统提示...", - "enterDefaultTask": "输入默认任务...", + "enterDefaultTask": "例如:检查这段代码的安全问题", "enterURL": "输入 URL...", "searchCommands": "搜索命令...", "enterCommand": "输入命令...", @@ -151,6 +511,12 @@ "unknownError": "未知错误", "claudeCodeNotFound": "未找到 Claude Code", "selectClaudeInstallation": "选择 Claude 安装", - "installClaudeCode": "安装 Claude Code" + "installClaudeCode": "安装 Claude Code", + "session": "会话" + }, + "input": { + "pressEnterToSend": "按 Enter 发送,Shift+Enter 换行", + "withFileAndCommandSupport": ",@ 提及文件,/ 调用命令,拖拽或粘贴图片" } -} \ No newline at end of file +} +