汉化
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
24
src/App.tsx
24
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<View>("tabs");
|
||||
const { createClaudeMdTab, createSettingsTab, createUsageTab, createMCPTab } = useTabState();
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
@@ -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() {
|
||||
>
|
||||
<h1 className="text-4xl font-bold tracking-tight">
|
||||
<span className="rotating-symbol"></span>
|
||||
Welcome to Claudia
|
||||
{t('welcomeToClaudia')}
|
||||
</h1>
|
||||
</motion.div>
|
||||
|
||||
@@ -264,7 +266,7 @@ function AppContent() {
|
||||
>
|
||||
<div className="h-full flex flex-col items-center justify-center p-8">
|
||||
<Bot className="h-16 w-16 mb-4 text-primary" />
|
||||
<h2 className="text-xl font-semibold">CC Agents</h2>
|
||||
<h2 className="text-xl font-semibold">{t('ccAgents')}</h2>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
@@ -281,7 +283,7 @@ function AppContent() {
|
||||
>
|
||||
<div className="h-full flex flex-col items-center justify-center p-8">
|
||||
<FolderCode className="h-16 w-16 mb-4 text-primary" />
|
||||
<h2 className="text-xl font-semibold">CC Projects</h2>
|
||||
<h2 className="text-xl font-semibold">{t('ccProjects')}</h2>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
@@ -329,12 +331,12 @@ function AppContent() {
|
||||
onClick={() => handleViewChange("welcome")}
|
||||
className="mb-4"
|
||||
>
|
||||
← Back to Home
|
||||
{t('backToHome')}
|
||||
</Button>
|
||||
<div className="mb-4">
|
||||
<h1 className="text-3xl font-bold tracking-tight">CC Projects</h1>
|
||||
<h1 className="text-3xl font-bold tracking-tight">{t('ccProjects')}</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Browse your Claude Code sessions
|
||||
{t('browseClaudeCodeSessions')}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
@@ -396,7 +398,7 @@ function AppContent() {
|
||||
className="w-full max-w-md"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New Claude Code session
|
||||
{t('newClaudeCodeSession')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
@@ -415,7 +417,7 @@ function AppContent() {
|
||||
) : (
|
||||
<div className="py-8 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No projects found in ~/.claude/projects
|
||||
{t('noProjectsFound')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -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();
|
||||
}}
|
||||
|
@@ -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<AgentsModalProps> = ({ open, onOpenChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState('agents');
|
||||
const [agents, setAgents] = useState<Agent[]>([]);
|
||||
const [runningAgents, setRunningAgents] = useState<AgentRunWithMetrics[]>([]);
|
||||
@@ -201,18 +203,18 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
<DialogHeader className="px-6 pt-6">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Bot className="w-5 h-5" />
|
||||
Agent Management
|
||||
{t('agents.agentManagement')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create new agents or manage running agent executions
|
||||
{t('agents.createNewOrManageAgents')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
|
||||
<TabsList className="mx-6">
|
||||
<TabsTrigger value="agents">Available Agents</TabsTrigger>
|
||||
<TabsTrigger value="agents">{t('agents.availableAgents')}</TabsTrigger>
|
||||
<TabsTrigger value="running" className="relative">
|
||||
Running Agents
|
||||
{t('agents.runningAgents')}
|
||||
{runningAgents.length > 0 && (
|
||||
<Badge variant="secondary" className="ml-2 h-5 px-1.5">
|
||||
{runningAgents.length}
|
||||
@@ -257,9 +259,9 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
) : agents.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
||||
<Bot className="w-12 h-12 text-muted-foreground mb-4" />
|
||||
<p className="text-lg font-medium mb-2">No agents available</p>
|
||||
<p className="text-lg font-medium mb-2">{t('agents.noAgentsAvailable')}</p>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Create your first agent to get started
|
||||
{t('agents.createFirstAgentToGetStarted')}
|
||||
</p>
|
||||
<Button onClick={() => {
|
||||
onOpenChange(false);
|
||||
|
@@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { analytics } from '@/lib/analytics';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
interface AnalyticsConsentProps {
|
||||
open?: boolean;
|
||||
@@ -18,6 +19,7 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
onOpenChange,
|
||||
onComplete,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [internalOpen, setInternalOpen] = useState(false);
|
||||
const [hasShownConsent, setHasShownConsent] = useState(false);
|
||||
|
||||
@@ -70,10 +72,10 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
<div className="p-2 bg-purple-100 dark:bg-purple-900/20 rounded-lg">
|
||||
<BarChart3 className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<DialogTitle className="text-2xl">Help Improve Claudia</DialogTitle>
|
||||
<DialogTitle className="text-2xl">{t('analytics.helpImproveClaudia')}</DialogTitle>
|
||||
</div>
|
||||
<DialogDescription className="text-base mt-2">
|
||||
We'd like to collect anonymous usage data to improve your experience.
|
||||
{t('analytics.collectAnonymousData')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
</div>
|
||||
@@ -84,12 +86,12 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
<div className="flex gap-3">
|
||||
<Check className="h-5 w-5 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-green-900 dark:text-green-100">What we collect:</p>
|
||||
<p className="font-medium text-green-900 dark:text-green-100">{t('analytics.whatWeCollect')}</p>
|
||||
<ul className="text-sm text-green-800 dark:text-green-200 space-y-1">
|
||||
<li>• Feature usage (which tools and commands you use)</li>
|
||||
<li>• Performance metrics (app speed and reliability)</li>
|
||||
<li>• Error reports (to fix bugs and improve stability)</li>
|
||||
<li>• General usage patterns (session frequency and duration)</li>
|
||||
<li>• {t('analytics.featureUsageDesc')}</li>
|
||||
<li>• {t('analytics.performanceMetricsDesc')}</li>
|
||||
<li>• {t('analytics.errorReportsDesc')}</li>
|
||||
<li>• {t('analytics.usagePatternsDesc')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,13 +101,13 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
<div className="flex gap-3">
|
||||
<Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-blue-900 dark:text-blue-100">Your privacy is protected:</p>
|
||||
<p className="font-medium text-blue-900 dark:text-blue-100">{t('analytics.privacyProtected')}</p>
|
||||
<ul className="text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
||||
<li>• No personal information is collected</li>
|
||||
<li>• No file contents, paths, or project names</li>
|
||||
<li>• No API keys or sensitive data</li>
|
||||
<li>• Completely anonymous with random IDs</li>
|
||||
<li>• You can opt-out anytime in Settings</li>
|
||||
<li>• {t('analytics.noPersonalInfo')}</li>
|
||||
<li>• {t('analytics.noFileContents')}</li>
|
||||
<li>• {t('analytics.noApiKeys')}</li>
|
||||
<li>• {t('analytics.anonymousData')}</li>
|
||||
<li>• {t('analytics.canOptOut')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,8 +118,7 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
<div className="flex gap-2 items-start">
|
||||
<Info className="h-4 w-4 text-gray-500 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
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')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,13 +130,13 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
No Thanks
|
||||
{t('analytics.noThanks')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleAccept}
|
||||
className="flex-1 bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
Allow Analytics
|
||||
{t('analytics.allowAnalytics')}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
@@ -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;
|
||||
* <CCAgents onBack={() => setView('home')} />
|
||||
*/
|
||||
export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
const { t } = useTranslation();
|
||||
const [agents, setAgents] = useState<Agent[]>([]);
|
||||
const [runs, setRuns] = useState<AgentRunWithMetrics[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -94,8 +96,8 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ 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<CCAgentsProps> = ({ 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<CCAgentsProps> = ({ 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<CCAgentsProps> = ({ 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<CCAgentsProps> = ({ 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<CCAgentsProps> = ({ onBack, className }) => {
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">CC Agents</h1>
|
||||
<h1 className="text-2xl font-bold">{t('navigation.agents')}</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Manage your Claude Code agents
|
||||
{t('agents.manageAgents')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -323,18 +325,18 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Import
|
||||
{t('agents.import')}
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={handleImportAgent}>
|
||||
<FileJson className="h-4 w-4 mr-2" />
|
||||
From File
|
||||
{t('agents.importFromFile')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setShowGitHubBrowser(true)}>
|
||||
<Globe className="h-4 w-4 mr-2" />
|
||||
From GitHub
|
||||
{t('agents.importFromGitHub')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -344,7 +346,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Create CC Agent
|
||||
{t('agents.createAgent')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -381,13 +383,13 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
) : agents.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-64 text-center">
|
||||
<Bot className="h-16 w-16 text-muted-foreground mb-4" />
|
||||
<h3 className="text-lg font-medium mb-2">No agents yet</h3>
|
||||
<h3 className="text-lg font-medium mb-2">{t('agents.noAgentsYet')}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Create your first CC Agent to get started
|
||||
{t('agents.createFirstAgent')}
|
||||
</p>
|
||||
<Button onClick={() => setView("create")} size="default">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Create CC Agent
|
||||
{t('agents.createAgent')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
@@ -411,7 +413,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
{agent.name}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Created: {new Date(agent.created_at).toLocaleDateString()}
|
||||
{t('agents.created')}: {new Date(agent.created_at).toLocaleDateString()}
|
||||
</p>
|
||||
</CardContent>
|
||||
<CardFooter className="p-4 pt-0 flex justify-center gap-1 flex-wrap">
|
||||
@@ -420,20 +422,20 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
variant="ghost"
|
||||
onClick={() => handleExecuteAgent(agent)}
|
||||
className="flex items-center gap-1"
|
||||
title="Execute agent"
|
||||
title={t('agents.executeAgentTitle')}
|
||||
>
|
||||
<Play className="h-3 w-3" />
|
||||
Execute
|
||||
{t('agents.execute')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleEditAgent(agent)}
|
||||
className="flex items-center gap-1"
|
||||
title="Edit agent"
|
||||
title={t('agents.editAgent')}
|
||||
>
|
||||
<Edit className="h-3 w-3" />
|
||||
Edit
|
||||
{t('app.edit')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -443,17 +445,17 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
title="Export agent to .claudia.json"
|
||||
>
|
||||
<Upload className="h-3 w-3" />
|
||||
Export
|
||||
{t('agents.export')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleDeleteAgent(agent)}
|
||||
className="flex items-center gap-1 text-destructive hover:text-destructive"
|
||||
title="Delete agent"
|
||||
title={t('agents.deleteAgent')}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
Delete
|
||||
{t('app.delete')}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
@@ -471,10 +473,10 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
Previous
|
||||
{t('app.previous')}
|
||||
</Button>
|
||||
<span className="flex items-center px-3 text-sm">
|
||||
Page {currentPage} of {totalPages}
|
||||
{t('app.page')} {currentPage} {t('app.of')} {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -482,7 +484,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
{t('app.next')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -495,7 +497,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
<div className="overflow-hidden">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<History className="h-5 w-5 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold">Recent Executions</h2>
|
||||
<h2 className="text-lg font-semibold">{t('agents.recentExecutions')}</h2>
|
||||
</div>
|
||||
{runsLoading ? (
|
||||
<div className="flex items-center justify-center h-32">
|
||||
@@ -531,7 +533,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ 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<CCAgentsProps> = ({ onBack, className }) => {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Trash2 className="h-5 w-5 text-destructive" />
|
||||
Delete Agent
|
||||
{t('agents.deleteAgentTitle')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
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 })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="flex flex-col-reverse sm:flex-row sm:justify-end gap-2">
|
||||
@@ -555,7 +556,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
disabled={isDeleting}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
Cancel
|
||||
{t('app.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -566,12 +567,12 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2" />
|
||||
Deleting...
|
||||
{t('agents.deleting')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Delete Agent
|
||||
{t('agents.deleteAgentButton')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
@@ -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<CheckpointSettingsProps> = ({
|
||||
onClose,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [autoCheckpointEnabled, setAutoCheckpointEnabled] = useState(true);
|
||||
const [checkpointStrategy, setCheckpointStrategy] = useState<CheckpointStrategy>("smart");
|
||||
const [totalCheckpoints, setTotalCheckpoints] = useState(0);
|
||||
@@ -50,10 +52,10 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
const [successMessage, setSuccessMessage] = useState<string | null>(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<CheckpointSettingsProps> = ({
|
||||
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<CheckpointSettingsProps> = ({
|
||||
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<CheckpointSettingsProps> = ({
|
||||
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<CheckpointSettingsProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5" />
|
||||
<h3 className="text-lg font-semibold">Checkpoint Settings</h3>
|
||||
<h3 className="text-lg font-semibold">{t('checkpoint.checkpointSettingsTitle')}</h3>
|
||||
</div>
|
||||
{onClose && (
|
||||
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||
Close
|
||||
{t('app.close')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -151,9 +153,9 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-4 w-4 text-yellow-600 mt-0.5" />
|
||||
<div className="text-xs">
|
||||
<p className="font-medium text-yellow-600">Experimental Feature</p>
|
||||
<p className="font-medium text-yellow-600">{t('checkpoint.experimentalFeature')}</p>
|
||||
<p className="text-yellow-600/80">
|
||||
Checkpointing may affect directory structure or cause data loss. Use with caution.
|
||||
{t('checkpoint.checkpointWarning')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,9 +188,9 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
{/* Auto-checkpoint toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="auto-checkpoint">Automatic Checkpoints</Label>
|
||||
<Label htmlFor="auto-checkpoint">{t('checkpoint.automaticCheckpoints')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Automatically create checkpoints based on the selected strategy
|
||||
{t('checkpoint.automaticCheckpointsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -201,7 +203,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
|
||||
{/* Checkpoint strategy */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="strategy">Checkpoint Strategy</Label>
|
||||
<Label htmlFor="strategy">{t('checkpoint.checkpointStrategy')}</Label>
|
||||
<SelectComponent
|
||||
value={checkpointStrategy}
|
||||
onValueChange={(value: string) => setCheckpointStrategy(value as CheckpointStrategy)}
|
||||
@@ -209,10 +211,10 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
disabled={isLoading || !autoCheckpointEnabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{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')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -225,12 +227,12 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Save className="h-4 w-4 mr-2 animate-spin" />
|
||||
Saving...
|
||||
{t('checkpoint.saving')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
Save Settings
|
||||
{t('checkpoint.saveSettings')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -239,9 +241,9 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
<div className="border-t pt-6 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label>Storage Management</Label>
|
||||
<Label>{t('checkpoint.storageManagement')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Total checkpoints: {totalCheckpoints}
|
||||
{t('checkpoint.totalCheckpoints')}: {totalCheckpoints}
|
||||
</p>
|
||||
</div>
|
||||
<HardDrive className="h-5 w-5 text-muted-foreground" />
|
||||
@@ -249,7 +251,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
|
||||
{/* Cleanup settings */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="keep-count">Keep Recent Checkpoints</Label>
|
||||
<Label htmlFor="keep-count">{t('checkpoint.keepRecentCheckpoints')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="keep-count"
|
||||
@@ -267,11 +269,11 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
disabled={isLoading || totalCheckpoints <= keepCount}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Clean Up
|
||||
{t('checkpoint.cleanUp')}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Remove old checkpoints, keeping only the most recent {keepCount}
|
||||
{t('checkpoint.removeOldCheckpoints', { count: keepCount })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -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<ClaudeInstallation | null>(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
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FileQuestion className="w-5 h-5" />
|
||||
Select Claude Code Installation
|
||||
{t('selectClaudeCodeInstallation')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="space-y-3 mt-4">
|
||||
{checkingInstallations ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">Searching for Claude installations...</span>
|
||||
<span className="ml-2 text-sm text-muted-foreground">{t('searchingInstallations')}</span>
|
||||
</div>
|
||||
) : hasInstallations ? (
|
||||
<p>
|
||||
Multiple Claude Code installations were found on your system.
|
||||
Please select which one you'd like to use.
|
||||
{t('multipleInstallationsFound')}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
Claude Code was not found in any of the common installation locations.
|
||||
Please install Claude Code to continue.
|
||||
{t('claudeCodeNotFoundDialog')}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 p-3 bg-muted rounded-md">
|
||||
<AlertCircle className="w-4 h-4 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="font-medium">Searched locations:</span> PATH, /usr/local/bin,
|
||||
<span className="font-medium">{t('searchedLocations')}:</span> PATH, /usr/local/bin,
|
||||
/opt/homebrew/bin, ~/.nvm/versions/node/*/bin, ~/.claude/local, ~/.local/bin
|
||||
</p>
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
<div className="flex items-center gap-2 p-3 bg-muted rounded-md">
|
||||
<Terminal className="w-4 h-4 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="font-medium">Tip:</span> You can install Claude Code using{" "}
|
||||
<span className="font-medium">{t('validation.required')}:</span> {t('installationTip')}{" "}
|
||||
<code className="px-1 py-0.5 bg-black/10 dark:bg-white/10 rounded">npm install -g @claude</code>
|
||||
</p>
|
||||
</div>
|
||||
@@ -118,20 +118,20 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
className="mr-auto"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
Installation Guide
|
||||
{t('installationGuide')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={isValidating}
|
||||
>
|
||||
Cancel
|
||||
{t('app.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isValidating || !selectedInstallation || !hasInstallations}
|
||||
>
|
||||
{isValidating ? "Validating..." : hasInstallations ? "Save Selection" : "No Installations Found"}
|
||||
{isValidating ? t('validating') : hasInstallations ? t('saveSelection') : t('noInstallationsFound')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
@@ -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<ClaudeFileEditorProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [content, setContent] = useState<string>("");
|
||||
const [originalContent, setOriginalContent] = useState<string>("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -59,7 +61,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
||||
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<ClaudeFileEditorProps> = ({
|
||||
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<ClaudeFileEditorProps> = ({
|
||||
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<ClaudeFileEditorProps> = ({
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-lg font-semibold truncate">{file.relative_path}</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Edit project-specific Claude Code system prompt
|
||||
{t('editProjectSpecificPrompt')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,7 +131,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
||||
) : (
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{saving ? "Saving..." : "Save"}
|
||||
{saving ? t('saving') : t('app.save')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
|
@@ -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<ClaudeVersionSelectorProps> = ({
|
||||
onSave,
|
||||
isSaving = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [installations, setInstallations] = useState<ClaudeInstallation[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -132,8 +134,8 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle>Claude Code Installation</CardTitle>
|
||||
<CardDescription>Loading available installations...</CardDescription>
|
||||
<CardTitle>{t('settings.claudeCodeInstallation')}</CardTitle>
|
||||
<CardDescription>{t('settings.loadingAvailableInstallations')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-center py-4">
|
||||
@@ -148,13 +150,13 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle>Claude Code Installation</CardTitle>
|
||||
<CardDescription>Error loading installations</CardDescription>
|
||||
<CardTitle>{t('settings.claudeCodeInstallation')}</CardTitle>
|
||||
<CardDescription>{t('settings.errorLoadingInstallations')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-sm text-destructive mb-4">{error}</div>
|
||||
<Button onClick={loadInstallations} variant="outline" size="sm">
|
||||
Retry
|
||||
{t('app.retry')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -169,16 +171,16 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CheckCircle className="h-5 w-5" />
|
||||
Claude Code Installation
|
||||
{t('settings.claudeCodeInstallation')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Choose your preferred Claude Code installation.
|
||||
{t('settings.choosePreferredInstallation')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Available Installations */}
|
||||
<div className="space-y-3">
|
||||
<Label className="text-sm font-medium">Available Installations</Label>
|
||||
<Label className="text-sm font-medium">{t('settings.availableInstallations')}</Label>
|
||||
<Select value={selectedInstallation?.path || ""} onValueChange={handleInstallationChange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select Claude installation">
|
||||
|
@@ -59,12 +59,12 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!name.trim()) {
|
||||
setError("Agent name is required");
|
||||
setError(t('agents.agentNameRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!systemPrompt.trim()) {
|
||||
setError("System prompt is required");
|
||||
setError(t('agents.systemPromptRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,24 +181,24 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
{/* Basic Information */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium mb-4">Basic Information</h3>
|
||||
<h3 className="text-sm font-medium mb-4">{t('agents.basicInformation')}</h3>
|
||||
</div>
|
||||
|
||||
{/* Name and Icon */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Agent Name</Label>
|
||||
<Label htmlFor="name">{t('agents.agentName')}</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="e.g., Code Assistant"
|
||||
placeholder={t('placeholders.enterAgentName')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Agent Icon</Label>
|
||||
<Label>{t('agents.agentIcon')}</Label>
|
||||
<div
|
||||
onClick={() => 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<CreateAgentProps> = ({
|
||||
|
||||
{/* Model Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label>Model</Label>
|
||||
<Label>{t('agents.model')}</Label>
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@@ -245,7 +245,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-semibold">Claude 4 Sonnet</div>
|
||||
<div className="text-xs opacity-80">Faster, efficient for most tasks</div>
|
||||
<div className="text-xs opacity-80">{t('agents.sonnetDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@@ -272,7 +272,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-semibold">Claude 4 Opus</div>
|
||||
<div className="text-xs opacity-80">More capable, better for complex tasks</div>
|
||||
<div className="text-xs opacity-80">{t('agents.opusDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@@ -281,25 +281,25 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
|
||||
{/* Default Task */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="default-task">Default Task (Optional)</Label>
|
||||
<Label htmlFor="default-task">{t('agents.defaultTask')} ({t('agents.optional')})</Label>
|
||||
<Input
|
||||
id="default-task"
|
||||
type="text"
|
||||
placeholder="e.g., Review this code for security issues"
|
||||
placeholder={t('placeholders.enterDefaultTask')}
|
||||
value={defaultTask}
|
||||
onChange={(e) => setDefaultTask(e.target.value)}
|
||||
className="max-w-md"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
This will be used as the default task placeholder when executing the agent
|
||||
{t('agents.defaultTaskDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* System Prompt Editor */}
|
||||
<div className="space-y-2">
|
||||
<Label>System Prompt</Label>
|
||||
<Label>{t('agents.systemPrompt')}</Label>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
Define the behavior and capabilities of your CC Agent
|
||||
{t('agents.systemPromptDescription')}
|
||||
</p>
|
||||
<div className="rounded-lg border border-border overflow-hidden shadow-sm" data-color-mode="dark">
|
||||
<MDEditor
|
||||
|
@@ -20,6 +20,7 @@ import { SlashCommandPicker } from "./SlashCommandPicker";
|
||||
import { ImagePreview } from "./ImagePreview";
|
||||
import { type FileEntry, type SlashCommand } from "@/lib/api";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface FloatingPromptInputProps {
|
||||
/**
|
||||
@@ -72,42 +73,7 @@ type ThinkingModeConfig = {
|
||||
phrase?: string; // The phrase to append
|
||||
};
|
||||
|
||||
const THINKING_MODES: ThinkingModeConfig[] = [
|
||||
{
|
||||
id: "auto",
|
||||
name: "Auto",
|
||||
description: "Let Claude decide",
|
||||
level: 0
|
||||
},
|
||||
{
|
||||
id: "think",
|
||||
name: "Think",
|
||||
description: "Basic reasoning",
|
||||
level: 1,
|
||||
phrase: "think"
|
||||
},
|
||||
{
|
||||
id: "think_hard",
|
||||
name: "Think Hard",
|
||||
description: "Deeper analysis",
|
||||
level: 2,
|
||||
phrase: "think hard"
|
||||
},
|
||||
{
|
||||
id: "think_harder",
|
||||
name: "Think Harder",
|
||||
description: "Extensive reasoning",
|
||||
level: 3,
|
||||
phrase: "think harder"
|
||||
},
|
||||
{
|
||||
id: "ultrathink",
|
||||
name: "Ultrathink",
|
||||
description: "Maximum computation",
|
||||
level: 4,
|
||||
phrase: "ultrathink"
|
||||
}
|
||||
];
|
||||
// Thinking modes configuration will be defined inside the component to use translations
|
||||
|
||||
/**
|
||||
* ThinkingModeIndicator component - Shows visual indicator bars for thinking level
|
||||
@@ -173,6 +139,45 @@ const FloatingPromptInputInner = (
|
||||
}: FloatingPromptInputProps,
|
||||
ref: React.Ref<FloatingPromptInputRef>,
|
||||
) => {
|
||||
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<ThinkingMode>("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 = (
|
||||
</div>
|
||||
|
||||
<div className="mt-2 text-xs text-muted-foreground">
|
||||
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')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -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<HooksEditorProps> = ({
|
||||
onChange,
|
||||
hideActions = false
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedEvent, setSelectedEvent] = useState<HookEvent>('PreToolUse');
|
||||
const [showTemplateDialog, setShowTemplateDialog] = useState(false);
|
||||
const [validationErrors, setValidationErrors] = useState<string[]>([]);
|
||||
@@ -754,7 +756,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
{/* Header */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">Hooks Configuration</h3>
|
||||
<h3 className="text-lg font-semibold">{t('hooks.hooksConfiguration')}</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={scope === 'project' ? 'secondary' : scope === 'local' ? 'outline' : 'default'}>
|
||||
{scope === 'project' ? 'Project' : scope === 'local' ? 'Local' : 'User'} Scope
|
||||
@@ -789,12 +791,12 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
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')}
|
||||
</p>
|
||||
{hasUnsavedChanges && !readOnly && (
|
||||
<p className="text-sm text-amber-600">
|
||||
You have unsaved changes. Click Save to persist them.
|
||||
{t('hooks.unsavedChanges')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -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<MCPAddServerProps> = ({
|
||||
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<MCPAddServerProps> = ({
|
||||
*/
|
||||
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<MCPAddServerProps> = ({
|
||||
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<MCPAddServerProps> = ({
|
||||
*/
|
||||
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<MCPAddServerProps> = ({
|
||||
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<MCPAddServerProps> = ({
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium">Environment Variables</Label>
|
||||
<Label className="text-sm font-medium">{t('environmentVariables')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -233,7 +235,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add Variable
|
||||
{t('addVariable')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -273,9 +275,9 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">Add MCP Server</h3>
|
||||
<h3 className="text-base font-semibold">{t('addMcpServer')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Configure a new Model Context Protocol server
|
||||
{t('configureNewMcpServer')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -296,7 +298,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
<Card className="p-6 space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-name">Server Name</Label>
|
||||
<Label htmlFor="stdio-name">{t('serverName')}</Label>
|
||||
<Input
|
||||
id="stdio-name"
|
||||
placeholder="my-server"
|
||||
@@ -304,12 +306,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onChange={(e) => setStdioName(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
A unique name to identify this server
|
||||
{t('uniqueNameToIdentify')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-command">Command</Label>
|
||||
<Label htmlFor="stdio-command">{t('command')}</Label>
|
||||
<Input
|
||||
id="stdio-command"
|
||||
placeholder="/path/to/server"
|
||||
@@ -318,12 +320,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
The command to execute the server
|
||||
{t('commandToExecuteServer')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-args">Arguments (optional)</Label>
|
||||
<Label htmlFor="stdio-args">{t('argumentsOptional')}</Label>
|
||||
<Input
|
||||
id="stdio-args"
|
||||
placeholder="arg1 arg2 arg3"
|
||||
@@ -332,19 +334,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Space-separated command arguments
|
||||
{t('spaceSeparatedArgs')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-scope">Scope</Label>
|
||||
<Label htmlFor="stdio-scope">{t('scope')}</Label>
|
||||
<SelectComponent
|
||||
value={stdioScope}
|
||||
onValueChange={(value: string) => 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') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -361,12 +363,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Adding Server...
|
||||
{t('addingServer')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Stdio Server
|
||||
{t('addStdioServer')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -379,7 +381,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
<Card className="p-6 space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-name">Server Name</Label>
|
||||
<Label htmlFor="sse-name">{t('serverName')}</Label>
|
||||
<Input
|
||||
id="sse-name"
|
||||
placeholder="sse-server"
|
||||
@@ -387,12 +389,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onChange={(e) => setSseName(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
A unique name to identify this server
|
||||
{t('uniqueNameToIdentify')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-url">URL</Label>
|
||||
<Label htmlFor="sse-url">{t('url')}</Label>
|
||||
<Input
|
||||
id="sse-url"
|
||||
placeholder="https://example.com/sse-endpoint"
|
||||
@@ -401,19 +403,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
The SSE endpoint URL
|
||||
{t('sseEndpointUrl')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-scope">Scope</Label>
|
||||
<Label htmlFor="sse-scope">{t('scope')}</Label>
|
||||
<SelectComponent
|
||||
value={sseScope}
|
||||
onValueChange={(value: string) => 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') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -430,12 +432,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Adding Server...
|
||||
{t('addingServer')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add SSE Server
|
||||
{t('addSseServer')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -449,7 +451,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<Info className="h-4 w-4 text-primary" />
|
||||
<span>Example Commands</span>
|
||||
<span>{t('exampleCommands')}</span>
|
||||
</div>
|
||||
<div className="space-y-2 text-xs text-muted-foreground">
|
||||
<div className="font-mono bg-background p-2 rounded">
|
||||
|
@@ -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<MCPManagerProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState("servers");
|
||||
const [servers, setServers] = useState<MCPServer[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -55,7 +57,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
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<MCPManagerProps> = ({
|
||||
*/
|
||||
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<MCPManagerProps> = ({
|
||||
*/
|
||||
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<MCPManagerProps> = ({
|
||||
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<MCPManagerProps> = ({
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||
<Network className="h-5 w-5 text-blue-500" />
|
||||
MCP Servers
|
||||
{t('mcpServers')}
|
||||
</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Manage Model Context Protocol servers
|
||||
{t('manageMcpServers')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,15 +155,15 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
<TabsList className="grid w-full max-w-md grid-cols-3">
|
||||
<TabsTrigger value="servers" className="gap-2">
|
||||
<Network className="h-4 w-4 text-blue-500" />
|
||||
Servers
|
||||
{t('servers')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="add" className="gap-2">
|
||||
<Plus className="h-4 w-4 text-green-500" />
|
||||
Add Server
|
||||
{t('addServer')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="import" className="gap-2">
|
||||
<Download className="h-4 w-4 text-purple-500" />
|
||||
Import/Export
|
||||
{t('importExport')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
|
@@ -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<MCPServerListProps> = ({
|
||||
onServerRemoved,
|
||||
onRefresh,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [removingServer, setRemovingServer] = useState<string | null>(null);
|
||||
const [testingServer, setTestingServer] = useState<string | null>(null);
|
||||
const [expandedServers, setExpandedServers] = useState<Set<string>>(new Set());
|
||||
@@ -184,11 +186,11 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
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<MCPServerListProps> = ({
|
||||
{server.status?.running && (
|
||||
<Badge variant="outline" className="gap-1 flex-shrink-0 border-green-500/50 text-green-600 bg-green-500/10">
|
||||
<CheckCircle className="h-3 w-3" />
|
||||
Running
|
||||
{t('running')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -237,7 +239,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||
>
|
||||
<ChevronDown className="h-3 w-3 mr-1" />
|
||||
Show full
|
||||
{t('showFull')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -299,7 +301,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
{server.command && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs font-medium text-muted-foreground">Command</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('command')}</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -308,7 +310,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||
>
|
||||
<Copy className="h-3 w-3 mr-1" />
|
||||
{isCopied ? "Copied!" : "Copy"}
|
||||
{isCopied ? t('copied') : t('copy')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -317,7 +319,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||
>
|
||||
<ChevronUp className="h-3 w-3 mr-1" />
|
||||
Hide
|
||||
{t('hide')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -329,7 +331,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">Arguments</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('arguments')}</p>
|
||||
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
||||
{server.args.map((arg, idx) => (
|
||||
<div key={idx} className="break-all">
|
||||
@@ -343,7 +345,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{server.transport === "sse" && server.url && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">URL</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('url')}</p>
|
||||
<p className="text-xs font-mono bg-muted/50 p-2 rounded break-all">
|
||||
{server.url}
|
||||
</p>
|
||||
@@ -352,7 +354,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{Object.keys(server.env).length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">Environment Variables</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('environmentVariables')}</p>
|
||||
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="break-all">
|
||||
@@ -384,9 +386,9 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">Configured Servers</h3>
|
||||
<h3 className="text-base font-semibold">{t('configuredServers')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{servers.length} server{servers.length !== 1 ? "s" : ""} configured
|
||||
{servers.length} {servers.length !== 1 ? t('servers') : 'server'} {t('serversConfigured')}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -396,7 +398,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="gap-2 hover:bg-primary/10 hover:text-primary hover:border-primary/50"
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
Refresh
|
||||
{t('app.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -406,9 +408,9 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
<div className="p-4 bg-primary/10 rounded-full mb-4">
|
||||
<Network className="h-12 w-12 text-primary" />
|
||||
</div>
|
||||
<p className="text-muted-foreground mb-2 font-medium">No MCP servers configured</p>
|
||||
<p className="text-muted-foreground mb-2 font-medium">{t('noMcpServersConfigured')}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Add a server to get started with Model Context Protocol
|
||||
{t('addServerToGetStarted')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
@@ -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<MarkdownEditorProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [content, setContent] = useState<string>("");
|
||||
const [originalContent, setOriginalContent] = useState<string>("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -51,7 +53,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
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<MarkdownEditorProps> = ({
|
||||
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<MarkdownEditorProps> = ({
|
||||
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<MarkdownEditorProps> = ({
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">CLAUDE.md</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Edit your Claude Code system prompt
|
||||
{t('editSystemPrompt')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -121,7 +123,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
) : (
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{saving ? "Saving..." : "Save"}
|
||||
{saving ? t('app.saving') : t('app.save')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
|
@@ -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<ProjectListProps> = ({
|
||||
onProjectSettings,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
// Calculate pagination
|
||||
@@ -150,7 +152,7 @@ export const ProjectList: React.FC<ProjectListProps> = ({
|
||||
}}
|
||||
>
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
Hooks
|
||||
{t('settings.hooks')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
@@ -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<ProxySettings>({
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Proxy Settings</h3>
|
||||
<h3 className="text-lg font-medium">{t('settings.proxySettingsTitle')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configure proxy settings for Claude API requests
|
||||
{t('settings.proxySettingsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="proxy-enabled">Enable Proxy</Label>
|
||||
<Label htmlFor="proxy-enabled">{t('settings.enableProxy')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Use proxy for all Claude API requests
|
||||
{t('settings.enableProxyDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -112,7 +114,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
|
||||
<div className="space-y-4" style={{ opacity: settings.enabled ? 1 : 0.5 }}>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="http-proxy">HTTP Proxy</Label>
|
||||
<Label htmlFor="http-proxy">{t('settings.httpProxy')}</Label>
|
||||
<Input
|
||||
id="http-proxy"
|
||||
placeholder="http://proxy.example.com:8080"
|
||||
@@ -123,7 +125,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="https-proxy">HTTPS Proxy</Label>
|
||||
<Label htmlFor="https-proxy">{t('settings.httpsProxy')}</Label>
|
||||
<Input
|
||||
id="https-proxy"
|
||||
placeholder="http://proxy.example.com:8080"
|
||||
@@ -134,7 +136,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="no-proxy">No Proxy</Label>
|
||||
<Label htmlFor="no-proxy">{t('settings.noProxy')}</Label>
|
||||
<Input
|
||||
id="no-proxy"
|
||||
placeholder="localhost,127.0.0.1,.example.com"
|
||||
@@ -143,12 +145,12 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
disabled={!settings.enabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Comma-separated list of hosts that should bypass the proxy
|
||||
{t('settings.noProxyDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="all-proxy">All Proxy (Optional)</Label>
|
||||
<Label htmlFor="all-proxy">All Proxy ({t('agents.optional')})</Label>
|
||||
<Input
|
||||
id="all-proxy"
|
||||
placeholder="socks5://proxy.example.com:1080"
|
||||
@@ -157,7 +159,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
disabled={!settings.enabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Proxy URL to use for all protocols if protocol-specific proxies are not set
|
||||
{t('settings.allProxyDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -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<SettingsProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [settings, setSettings] = useState<ClaudeSettings | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -178,7 +179,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
}
|
||||
} 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<SettingsProps> = ({
|
||||
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<SettingsProps> = ({
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Settings</h2>
|
||||
<h2 className="text-lg font-semibold">{t('settings.title')}</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Configure Claude Code preferences
|
||||
{t('settings.configurePreferences')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,12 +364,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Saving...
|
||||
{t('settings.saveButton.saving')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-4 w-4" />
|
||||
Save Settings
|
||||
{t('settings.saveButton.saveSettings')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -398,55 +399,55 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid grid-cols-9 w-full">
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="permissions">Permissions</TabsTrigger>
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||
<TabsTrigger value="hooks">Hooks</TabsTrigger>
|
||||
<TabsTrigger value="commands">Commands</TabsTrigger>
|
||||
<TabsTrigger value="storage">Storage</TabsTrigger>
|
||||
<TabsTrigger value="proxy">Proxy</TabsTrigger>
|
||||
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
||||
<TabsTrigger value="general">{t('settings.general')}</TabsTrigger>
|
||||
<TabsTrigger value="permissions">{t('settings.permissionsTab')}</TabsTrigger>
|
||||
<TabsTrigger value="environment">{t('settings.environmentTab')}</TabsTrigger>
|
||||
<TabsTrigger value="advanced">{t('settings.advancedTab')}</TabsTrigger>
|
||||
<TabsTrigger value="hooks">{t('settings.hooksTab')}</TabsTrigger>
|
||||
<TabsTrigger value="commands">{t('settings.commands')}</TabsTrigger>
|
||||
<TabsTrigger value="storage">{t('settings.storage')}</TabsTrigger>
|
||||
<TabsTrigger value="proxy">{t('settings.proxy')}</TabsTrigger>
|
||||
<TabsTrigger value="analytics">{t('settings.analyticsTab')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* General Settings */}
|
||||
<TabsContent value="general" className="space-y-6">
|
||||
<Card className="p-6 space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-4">General Settings</h3>
|
||||
<h3 className="text-base font-semibold mb-4">{t('settings.generalSettings')}</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Theme Selector */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="theme">Theme</Label>
|
||||
<Label htmlFor="theme">{t('settings.theme')}</Label>
|
||||
<Select
|
||||
value={theme}
|
||||
onValueChange={(value) => setTheme(value as any)}
|
||||
>
|
||||
<SelectTrigger id="theme" className="w-full">
|
||||
<SelectValue placeholder="Select a theme" />
|
||||
<SelectValue placeholder={t('settings.themeSelector.selectATheme')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="dark">Dark</SelectItem>
|
||||
<SelectItem value="gray">Gray</SelectItem>
|
||||
<SelectItem value="light">Light</SelectItem>
|
||||
<SelectItem value="custom">Custom</SelectItem>
|
||||
<SelectItem value="dark">{t('settings.themeSelector.dark')}</SelectItem>
|
||||
<SelectItem value="gray">{t('settings.themeSelector.gray')}</SelectItem>
|
||||
<SelectItem value="light">{t('settings.themeSelector.light')}</SelectItem>
|
||||
<SelectItem value="custom">{t('settings.themeSelector.custom')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Choose your preferred color theme for the interface
|
||||
{t('settings.themeSelector.choosePreferredTheme')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Custom Color Editor */}
|
||||
{theme === 'custom' && (
|
||||
<div className="space-y-4 p-4 border rounded-lg bg-muted/20">
|
||||
<h4 className="text-sm font-medium">Custom Theme Colors</h4>
|
||||
<h4 className="text-sm font-medium">{t('settings.customTheme.title')}</h4>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* Background Color */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="color-background" className="text-xs">Background</Label>
|
||||
<Label htmlFor="color-background" className="text-xs">{t('settings.customTheme.background')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="color-background"
|
||||
@@ -465,7 +466,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Foreground Color */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="color-foreground" className="text-xs">Foreground</Label>
|
||||
<Label htmlFor="color-foreground" className="text-xs">{t('settings.customTheme.foreground')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="color-foreground"
|
||||
@@ -484,7 +485,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Primary Color */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="color-primary" className="text-xs">Primary</Label>
|
||||
<Label htmlFor="color-primary" className="text-xs">{t('settings.customTheme.primary')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="color-primary"
|
||||
@@ -503,7 +504,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Card Color */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="color-card" className="text-xs">Card</Label>
|
||||
<Label htmlFor="color-card" className="text-xs">{t('settings.customTheme.card')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="color-card"
|
||||
@@ -522,7 +523,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Accent Color */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="color-accent" className="text-xs">Accent</Label>
|
||||
<Label htmlFor="color-accent" className="text-xs">{t('settings.customTheme.accent')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="color-accent"
|
||||
@@ -541,7 +542,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Destructive Color */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="color-destructive" className="text-xs">Destructive</Label>
|
||||
<Label htmlFor="color-destructive" className="text-xs">{t('settings.customTheme.destructive')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="color-destructive"
|
||||
@@ -560,7 +561,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Use CSS color values (hex, rgb, oklch, etc.). Changes apply immediately.
|
||||
{t('settings.customTheme.colorValuesDesc')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -568,9 +569,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{/* Include Co-authored By */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5 flex-1">
|
||||
<Label htmlFor="coauthored">Include "Co-authored by Claude"</Label>
|
||||
<Label htmlFor="coauthored">{t('settings.generalOptions.includeCoAuthor')}</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Add Claude attribution to git commits and pull requests
|
||||
{t('settings.generalOptions.includeCoAuthorDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -583,9 +584,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{/* Verbose Output */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5 flex-1">
|
||||
<Label htmlFor="verbose">Verbose Output</Label>
|
||||
<Label htmlFor="verbose">{t('settings.generalOptions.verboseOutput')}</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Show full bash and command outputs
|
||||
{t('settings.generalOptions.verboseOutputDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -597,7 +598,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Cleanup Period */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cleanup">Chat Transcript Retention (days)</Label>
|
||||
<Label htmlFor="cleanup">{t('settings.generalOptions.chatRetention')}</Label>
|
||||
<Input
|
||||
id="cleanup"
|
||||
type="number"
|
||||
@@ -610,16 +611,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
}}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
How long to retain chat transcripts locally (default: 30 days)
|
||||
{t('settings.generalOptions.chatRetentionDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Claude Binary Path Selector */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="text-sm font-medium mb-2 block">Claude Code Installation</Label>
|
||||
<Label className="text-sm font-medium mb-2 block">{t('settings.generalOptions.claudeCodeInstallation')}</Label>
|
||||
<p className="text-xs text-muted-foreground mb-4">
|
||||
Select which Claude Code installation to use.
|
||||
{t('settings.generalOptions.claudeCodeInstallationDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<ClaudeVersionSelector
|
||||
@@ -628,7 +629,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
/>
|
||||
{binaryPathChanged && (
|
||||
<p className="text-xs text-amber-600 dark:text-amber-400">
|
||||
⚠️ Claude binary path has been changed. Remember to save your settings.
|
||||
{t('settings.generalOptions.binaryPathChanged')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -642,16 +643,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<Card className="p-6">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-2">Permission Rules</h3>
|
||||
<h3 className="text-base font-semibold mb-2">{t('settings.permissions.permissionRules')}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Control which tools Claude Code can use without manual approval
|
||||
{t('settings.permissions.permissionRulesDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Allow Rules */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium text-green-500">Allow Rules</Label>
|
||||
<Label className="text-sm font-medium text-green-500">{t('settings.permissions.allowRules')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -659,13 +660,13 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="gap-2 hover:border-green-500/50 hover:text-green-500"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add Rule
|
||||
{t('settings.permissions.addRule')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{allowRules.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground py-2">
|
||||
No allow rules configured. Claude will ask for approval for all tools.
|
||||
{t('settings.permissions.noAllowRules')}
|
||||
</p>
|
||||
) : (
|
||||
allowRules.map((rule) => (
|
||||
@@ -676,7 +677,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g., Bash(npm run test:*)"
|
||||
placeholder={t('settings.placeholders.allowRuleExample')}
|
||||
value={rule.value}
|
||||
onChange={(e) => updatePermissionRule("allow", rule.id, e.target.value)}
|
||||
className="flex-1"
|
||||
@@ -698,7 +699,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{/* Deny Rules */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium text-red-500">Deny Rules</Label>
|
||||
<Label className="text-sm font-medium text-red-500">{t('settings.permissions.denyRules')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -706,13 +707,13 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="gap-2 hover:border-red-500/50 hover:text-red-500"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add Rule
|
||||
{t('settings.permissions.addRule')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{denyRules.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground py-2">
|
||||
No deny rules configured.
|
||||
{t('settings.permissions.noDenyRules')}
|
||||
</p>
|
||||
) : (
|
||||
denyRules.map((rule) => (
|
||||
@@ -723,7 +724,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g., Bash(curl:*)"
|
||||
placeholder={t('settings.placeholders.denyRuleExample')}
|
||||
value={rule.value}
|
||||
onChange={(e) => updatePermissionRule("deny", rule.id, e.target.value)}
|
||||
className="flex-1"
|
||||
@@ -744,14 +745,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
<div className="pt-2 space-y-2">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>Examples:</strong>
|
||||
<strong>{t('settings.permissions.examples')}</strong>
|
||||
</p>
|
||||
<ul className="text-xs text-muted-foreground space-y-1 ml-4">
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash</code> - Allow all bash commands</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash(npm run build)</code> - Allow exact command</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash(npm run test:*)</code> - Allow commands with prefix</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Read(~/.zshrc)</code> - Allow reading specific file</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Edit(docs/**)</code> - Allow editing files in docs directory</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash</code> - {t('settings.permissions.exampleBash')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash(npm run build)</code> - {t('settings.permissions.exampleExactCommand')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash(npm run test:*)</code> - {t('settings.permissions.examplePrefix')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Read(~/.zshrc)</code> - {t('settings.permissions.exampleReadFile')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Edit(docs/**)</code> - {t('settings.permissions.exampleEditDir')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -764,9 +765,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">Environment Variables</h3>
|
||||
<h3 className="text-base font-semibold">{t('settings.environment.environmentVariables')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Environment variables applied to every Claude Code session
|
||||
{t('settings.environment.environmentVariablesDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -776,14 +777,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add Variable
|
||||
{t('settings.environment.addVariable')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{envVars.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground py-2">
|
||||
No environment variables configured.
|
||||
{t('settings.environment.noEnvironmentVariables')}
|
||||
</p>
|
||||
) : (
|
||||
envVars.map((envVar) => (
|
||||
@@ -794,14 +795,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Input
|
||||
placeholder="KEY"
|
||||
placeholder={t('settings.placeholders.envVarKey')}
|
||||
value={envVar.key}
|
||||
onChange={(e) => updateEnvVar(envVar.id, "key", e.target.value)}
|
||||
className="flex-1 font-mono text-sm"
|
||||
/>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<Input
|
||||
placeholder="value"
|
||||
placeholder={t('settings.placeholders.envVarValue')}
|
||||
value={envVar.value}
|
||||
onChange={(e) => updateEnvVar(envVar.id, "value", e.target.value)}
|
||||
className="flex-1 font-mono text-sm"
|
||||
@@ -821,12 +822,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
<div className="pt-2 space-y-2">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>Common variables:</strong>
|
||||
<strong>{t('settings.environment.commonVariables')}</strong>
|
||||
</p>
|
||||
<ul className="text-xs text-muted-foreground space-y-1 ml-4">
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">CLAUDE_CODE_ENABLE_TELEMETRY</code> - Enable/disable telemetry (0 or 1)</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">ANTHROPIC_MODEL</code> - Custom model name</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">DISABLE_COST_WARNINGS</code> - Disable cost warnings (1)</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">CLAUDE_CODE_ENABLE_TELEMETRY</code> - {t('settings.environment.telemetryDesc')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">ANTHROPIC_MODEL</code> - {t('settings.environment.modelDesc')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">DISABLE_COST_WARNINGS</code> - {t('settings.environment.costWarningsDesc')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -837,34 +838,34 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<Card className="p-6">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-4">Advanced Settings</h3>
|
||||
<h3 className="text-base font-semibold mb-4">{t('settings.advanced.advancedSettings')}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
Additional configuration options for advanced users
|
||||
{t('settings.advanced.advancedSettingsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Key Helper */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="apiKeyHelper">API Key Helper Script</Label>
|
||||
<Label htmlFor="apiKeyHelper">{t('settings.advanced.apiKeyHelper')}</Label>
|
||||
<Input
|
||||
id="apiKeyHelper"
|
||||
placeholder="/path/to/generate_api_key.sh"
|
||||
placeholder={t('settings.placeholders.apiKeyHelperPath')}
|
||||
value={settings?.apiKeyHelper || ""}
|
||||
onChange={(e) => updateSetting("apiKeyHelper", e.target.value || undefined)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Custom script to generate auth values for API requests
|
||||
{t('settings.advanced.apiKeyHelperDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Raw JSON Editor */}
|
||||
<div className="space-y-2">
|
||||
<Label>Raw Settings (JSON)</Label>
|
||||
<Label>{t('settings.advanced.rawSettings')}</Label>
|
||||
<div className="p-3 rounded-md bg-muted font-mono text-xs overflow-x-auto whitespace-pre-wrap">
|
||||
<pre>{JSON.stringify(settings, null, 2)}</pre>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
This shows the raw JSON that will be saved to ~/.claude/settings.json
|
||||
{t('settings.advanced.rawSettingsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -876,10 +877,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-2">User Hooks</h3>
|
||||
<h3 className="text-base font-semibold mb-2">{t('settings.hooks.userHooks')}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Configure hooks that apply to all Claude Code sessions for your user account.
|
||||
These are stored in <code className="mx-1 px-2 py-1 bg-muted rounded text-xs">~/.claude/settings.json</code>
|
||||
{t('settings.hooks.userHooksDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -928,16 +928,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<BarChart3 className="h-5 w-5 text-purple-600 dark:text-purple-400" />
|
||||
<h3 className="text-base font-semibold">Analytics Settings</h3>
|
||||
<h3 className="text-base font-semibold">{t('settings.analytics.analyticsSettings')}</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Analytics Toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="analytics-enabled" className="text-base">Enable Analytics</Label>
|
||||
<Label htmlFor="analytics-enabled" className="text-base">{t('settings.analytics.enableAnalytics')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Help improve Claudia by sharing anonymous usage data
|
||||
{t('settings.analytics.enableAnalyticsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -950,12 +950,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
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<SettingsProps> = ({
|
||||
<div className="flex gap-3">
|
||||
<Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="space-y-2">
|
||||
<p className="font-medium text-blue-900 dark:text-blue-100">Your privacy is protected</p>
|
||||
<p className="font-medium text-blue-900 dark:text-blue-100">{t('settings.analytics.privacyProtected')}</p>
|
||||
<ul className="text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
||||
<li>• No personal information is collected</li>
|
||||
<li>• No file contents, paths, or project names</li>
|
||||
<li>• All data is anonymous with random IDs</li>
|
||||
<li>• You can disable analytics at any time</li>
|
||||
<li>• {t('settings.analytics.noPersonalInfo')}</li>
|
||||
<li>• {t('settings.analytics.noFileContents')}</li>
|
||||
<li>• {t('settings.analytics.anonymousData')}</li>
|
||||
<li>• {t('settings.analytics.canDisable')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -981,12 +981,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{analyticsEnabled && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">What we collect:</h4>
|
||||
<h4 className="text-sm font-medium mb-2">{t('settings.analytics.whatWeCollect')}</h4>
|
||||
<ul className="text-sm text-muted-foreground space-y-1">
|
||||
<li>• Feature usage patterns</li>
|
||||
<li>• Performance metrics</li>
|
||||
<li>• Error reports (without sensitive data)</li>
|
||||
<li>• Session frequency and duration</li>
|
||||
<li>• {t('settings.analytics.featureUsage')}</li>
|
||||
<li>• {t('settings.analytics.performanceMetrics')}</li>
|
||||
<li>• {t('settings.analytics.errorReports')}</li>
|
||||
<li>• {t('settings.analytics.sessionFrequency')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -999,11 +999,11 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
await analytics.deleteAllData();
|
||||
setAnalyticsEnabled(false);
|
||||
setAnalyticsConsented(false);
|
||||
setToast({ message: "All analytics data deleted", type: "success" });
|
||||
setToast({ message: t('settings.analytics.allDataDeleted'), type: "success" });
|
||||
}}
|
||||
>
|
||||
<Trash className="mr-2 h-4 w-4" />
|
||||
Delete All Analytics Data
|
||||
{t('settings.analytics.deleteAllData')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -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<SlashCommandsManagerProps> = ({
|
||||
className,
|
||||
scopeFilter = 'all',
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [commands, setCommands] = useState<SlashCommand[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -305,17 +307,17 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">
|
||||
{scopeFilter === 'project' ? 'Project Slash Commands' : 'Slash Commands'}
|
||||
{scopeFilter === 'project' ? t('slashCommands.projectSlashCommands') : t('slashCommands.slashCommands')}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{scopeFilter === 'project'
|
||||
? 'Create custom commands for this project'
|
||||
: 'Create custom commands to streamline your workflow'}
|
||||
? t('slashCommands.createCustomCommandsProject')
|
||||
: t('slashCommands.createCustomCommandsWorkflow')}
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={handleCreateNew} size="sm" className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
New Command
|
||||
{t('slashCommands.newCommand')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -325,7 +327,7 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search commands..."
|
||||
placeholder={t('placeholders.searchCommands')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9"
|
||||
@@ -338,9 +340,9 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Commands</SelectItem>
|
||||
<SelectItem value="project">Project</SelectItem>
|
||||
<SelectItem value="user">User</SelectItem>
|
||||
<SelectItem value="all">{t('slashCommands.allCommands')}</SelectItem>
|
||||
<SelectItem value="project">{t('slashCommands.project')}</SelectItem>
|
||||
<SelectItem value="user">{t('slashCommands.user')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
@@ -365,16 +367,16 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||
<Command className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{searchQuery
|
||||
? "No commands found"
|
||||
? t('slashCommands.noCommandsFound')
|
||||
: scopeFilter === 'project'
|
||||
? "No project commands created yet"
|
||||
: "No commands created yet"}
|
||||
? t('slashCommands.noProjectCommandsYet')
|
||||
: t('slashCommands.noCommandsYet')}
|
||||
</p>
|
||||
{!searchQuery && (
|
||||
<Button onClick={handleCreateNew} variant="outline" size="sm" className="mt-4">
|
||||
{scopeFilter === 'project'
|
||||
? "Create your first project command"
|
||||
: "Create your first command"}
|
||||
? t('slashCommands.createFirstProjectCommand')
|
||||
: t('slashCommands.createFirstCommand')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -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<TabPanelProps> = ({ tab, isActive }) => {
|
||||
const { t } = useTranslation();
|
||||
const { updateTab, createChatTab } = useTabState();
|
||||
const [projects, setProjects] = React.useState<Project[]>([]);
|
||||
const [selectedProject, setSelectedProject] = React.useState<Project | null>(null);
|
||||
@@ -54,7 +56,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ 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<TabPanelProps> = ({ 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<TabPanelProps> = ({ tab, isActive }) => {
|
||||
<div className="container mx-auto p-6">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold tracking-tight">CC Projects</h1>
|
||||
<h1 className="text-3xl font-bold tracking-tight">{t('ccProjects')}</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Browse your Claude Code sessions
|
||||
{t('browseClaudeCodeSessions')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -174,7 +176,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
className="w-full max-w-md"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New Claude Code session
|
||||
{t('newClaudeCodeSession')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
@@ -196,7 +198,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
) : (
|
||||
<div className="py-8 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No projects found in ~/.claude/projects
|
||||
{t('noProjectsFound')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -217,7 +219,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ 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<TabPanelProps> = ({ tab, isActive }) => {
|
||||
|
||||
case 'agent':
|
||||
if (!tab.agentRunId) {
|
||||
return <div className="p-4">No agent run ID specified</div>;
|
||||
return <div className="p-4">{t('messages.noAgentRunIdSpecified')}</div>;
|
||||
}
|
||||
return (
|
||||
<AgentRunOutputViewer
|
||||
@@ -249,15 +251,15 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
|
||||
case 'claude-file':
|
||||
if (!tab.claudeFileId) {
|
||||
return <div className="p-4">No Claude file ID specified</div>;
|
||||
return <div className="p-4">{t('messages.noClaudeFileIdSpecified')}</div>;
|
||||
}
|
||||
// Note: We need to get the actual file object for ClaudeFileEditor
|
||||
// For now, returning a placeholder
|
||||
return <div className="p-4">Claude file editor not yet implemented in tabs</div>;
|
||||
return <div className="p-4">{t('messages.claudeFileEditorNotImplemented')}</div>;
|
||||
|
||||
case 'agent-execution':
|
||||
if (!tab.agentData) {
|
||||
return <div className="p-4">No agent data specified</div>;
|
||||
return <div className="p-4">{t('messages.noAgentDataSpecified')}</div>;
|
||||
}
|
||||
return (
|
||||
<AgentExecution
|
||||
@@ -282,10 +284,10 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
|
||||
case 'import-agent':
|
||||
// TODO: Implement import agent component
|
||||
return <div className="p-4">Import agent functionality coming soon...</div>;
|
||||
return <div className="p-4">{t('messages.importAgentComingSoon')}</div>;
|
||||
|
||||
default:
|
||||
return <div className="p-4">Unknown tab type: {tab.type}</div>;
|
||||
return <div className="p-4">{t('messages.unknownTabType')}: {tab.type}</div>;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -311,6 +313,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ 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 && (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||
<div className="text-center">
|
||||
<p className="text-lg mb-2">No tabs open</p>
|
||||
<p className="text-sm">Click the + button to start a new chat</p>
|
||||
<p className="text-lg mb-2">{t('messages.noTabsOpen')}</p>
|
||||
<p className="text-sm">{t('messages.clickPlusToStartChat')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@@ -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 {
|
||||
* <UsageDashboard onBack={() => setView('welcome')} />
|
||||
*/
|
||||
export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [stats, setStats] = useState<UsageStats | null>(null);
|
||||
@@ -82,7 +84,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ 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<UsageDashboardProps> = ({ onBack }) => {
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold">Usage Dashboard</h1>
|
||||
<h1 className="text-lg font-semibold">{t('usage.usageDashboardTitle')}</h1>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Track your Claude Code usage and costs
|
||||
{t('usage.trackUsageAndCosts')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,7 +167,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ 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')}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
@@ -179,7 +181,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-sm text-muted-foreground">Loading usage statistics...</p>
|
||||
<p className="text-sm text-muted-foreground">{t('usage.loadingUsageStats')}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
@@ -187,7 +189,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<div className="text-center max-w-md">
|
||||
<p className="text-sm text-destructive mb-4">{error}</p>
|
||||
<Button onClick={loadUsageStats} size="sm">
|
||||
Try Again
|
||||
{t('usage.tryAgain')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,7 +206,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Total Cost</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.totalCost')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatCurrency(stats.total_cost)}
|
||||
</p>
|
||||
@@ -217,7 +219,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Total Sessions</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.totalSessions')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatNumber(stats.total_sessions)}
|
||||
</p>
|
||||
@@ -230,7 +232,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Total Tokens</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.totalTokens')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatTokens(stats.total_tokens)}
|
||||
</p>
|
||||
@@ -243,7 +245,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Avg Cost/Session</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.avgCostPerSession')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatCurrency(
|
||||
stats.total_sessions > 0
|
||||
@@ -260,32 +262,32 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Tabs for different views */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="models">By Model</TabsTrigger>
|
||||
<TabsTrigger value="projects">By Project</TabsTrigger>
|
||||
<TabsTrigger value="sessions">By Session</TabsTrigger>
|
||||
<TabsTrigger value="timeline">Timeline</TabsTrigger>
|
||||
<TabsTrigger value="overview">{t('usage.overview')}</TabsTrigger>
|
||||
<TabsTrigger value="models">{t('usage.byModel')}</TabsTrigger>
|
||||
<TabsTrigger value="projects">{t('usage.byProject')}</TabsTrigger>
|
||||
<TabsTrigger value="sessions">{t('usage.byDate')}</TabsTrigger>
|
||||
<TabsTrigger value="timeline">{t('usage.timeline')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Overview Tab */}
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Token Breakdown</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.tokenBreakdown')}</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Input Tokens</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.inputTokens')}</p>
|
||||
<p className="text-lg font-semibold">{formatTokens(stats.total_input_tokens)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Output Tokens</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.outputTokens')}</p>
|
||||
<p className="text-lg font-semibold">{formatTokens(stats.total_output_tokens)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Cache Write</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.cacheWrite')}</p>
|
||||
<p className="text-lg font-semibold">{formatTokens(stats.total_cache_creation_tokens)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Cache Read</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.cacheRead')}</p>
|
||||
<p className="text-lg font-semibold">{formatTokens(stats.total_cache_read_tokens)}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,7 +296,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Most Used Models</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.mostUsedModels')}</h3>
|
||||
<div className="space-y-3">
|
||||
{stats.by_model.slice(0, 3).map((model) => (
|
||||
<div key={model.model} className="flex items-center justify-between">
|
||||
@@ -303,7 +305,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{getModelDisplayName(model.model)}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{model.session_count} sessions
|
||||
{model.session_count} {t('usage.sessions')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
@@ -315,7 +317,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Top Projects</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.topProjects')}</h3>
|
||||
<div className="space-y-3">
|
||||
{stats.by_project.slice(0, 3).map((project) => (
|
||||
<div key={project.project_path} className="flex items-center justify-between">
|
||||
@@ -324,7 +326,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{project.project_path}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{project.session_count} sessions
|
||||
{project.session_count} {t('usage.sessions')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
@@ -340,7 +342,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Models Tab */}
|
||||
<TabsContent value="models">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Usage by Model</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.usageByModel')}</h3>
|
||||
<div className="space-y-4">
|
||||
{stats.by_model.map((model) => (
|
||||
<div key={model.model} className="space-y-2">
|
||||
@@ -353,7 +355,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{getModelDisplayName(model.model)}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{model.session_count} sessions
|
||||
{model.session_count} {t('usage.sessions')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-semibold">
|
||||
@@ -362,11 +364,11 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2 text-xs">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Input: </span>
|
||||
<span className="text-muted-foreground">{t('usage.input')}: </span>
|
||||
<span className="font-medium">{formatTokens(model.input_tokens)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Output: </span>
|
||||
<span className="text-muted-foreground">{t('usage.output')}: </span>
|
||||
<span className="font-medium">{formatTokens(model.output_tokens)}</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -387,7 +389,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Projects Tab */}
|
||||
<TabsContent value="projects">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Usage by Project</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.usageByProject')}</h3>
|
||||
<div className="space-y-3">
|
||||
{stats.by_project.map((project) => (
|
||||
<div key={project.project_path} className="flex items-center justify-between py-2 border-b border-border last:border-0">
|
||||
@@ -397,17 +399,17 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
</span>
|
||||
<div className="flex items-center space-x-3 mt-1">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{project.session_count} sessions
|
||||
{project.session_count} {t('usage.sessions')}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatTokens(project.total_tokens)} tokens
|
||||
{formatTokens(project.total_tokens)} {t('usage.tokens')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-semibold">{formatCurrency(project.total_cost)}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatCurrency(project.total_cost / project.session_count)}/session
|
||||
{formatCurrency(project.total_cost / project.session_count)}/{t('usage.session')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -419,7 +421,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Sessions Tab */}
|
||||
<TabsContent value="sessions">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Usage by Session</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.usageBySession')}</h3>
|
||||
<div className="space-y-3">
|
||||
{sessionStats?.map((session) => (
|
||||
<div key={`${session.project_path}-${session.project_name}`} className="flex items-center justify-between py-2 border-b border-border last:border-0">
|
||||
@@ -451,7 +453,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-6 flex items-center space-x-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>Daily Usage</span>
|
||||
<span>{t('usage.dailyUsage')}</span>
|
||||
</h3>
|
||||
{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<UsageDashboardProps> = ({ onBack }) => {
|
||||
<div className="bg-background border border-border rounded-lg shadow-lg p-3 whitespace-nowrap">
|
||||
<p className="text-sm font-semibold">{formattedDate}</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Cost: {formatCurrency(day.total_cost)}
|
||||
{t('usage.cost')}: {formatCurrency(day.total_cost)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatTokens(day.total_tokens)} tokens
|
||||
{formatTokens(day.total_tokens)} {t('usage.tokens')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{day.models_used.length} model{day.models_used.length !== 1 ? 's' : ''}
|
||||
{day.models_used.length} {t('usage.models')}{day.models_used.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</div>
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 -mt-1">
|
||||
@@ -517,13 +519,13 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
|
||||
{/* X-axis label */}
|
||||
<div className="mt-8 text-center text-xs text-muted-foreground">
|
||||
Daily Usage Over Time
|
||||
{t('usage.dailyUsageOverTime')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})() : (
|
||||
<div className="text-center py-8 text-sm text-muted-foreground">
|
||||
No usage data available for the selected period
|
||||
{t('usage.noUsageData')}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
@@ -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<TabContextType | undefined>(undefined);
|
||||
const MAX_TABS = 20;
|
||||
|
||||
export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { t } = useTranslation();
|
||||
const [tabs, setTabs] = useState<Tab[]>([]);
|
||||
const [activeTabId, setActiveTabId] = useState<string | null>(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<Tab, 'id' | 'order' | 'createdAt' | 'updatedAt'>): 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 => {
|
||||
|
@@ -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<boolean> => {
|
||||
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<boolean> => {
|
||||
if (!activeTabId) return true;
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -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": ",@ 提及文件,/ 调用命令,拖拽或粘贴图片"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user