mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-12 01:17:48 +00:00
Implemented comprehensive i18n translation support for the following components: 1. GitSettings.jsx - Git configuration interface 2. ApiKeysSettings.jsx - API keys settings 3. CredentialsSettings.jsx - Credentials settings (GitHub tokens) 4. TasksSettings.jsx - TaskMaster task management settings 5. ChatInterface.jsx - Chat interface (major translation work) New translation files: - src/i18n/locales/en/chat.json - English chat interface translations - src/i18n/locales/zh-CN/chat.json - Chinese chat interface translations ChatInterface.jsx translations: - Code block copy buttons (Copy, Copied, Copy code) - Message type labels (User, Error, Tool, Claude, Cursor, Codex) - Tool settings tooltip - Search result display (pattern, in, results) - Codex permission modes (Default, Accept Edits, Bypass Permissions, Plan) - Input placeholder and hint text - Keyboard shortcut hints (Ctrl+Enter/Enter modes) - Command menu button i18n configuration updates: - Registered chat namespace in config.js - Extended settings.json translations (git, apiKeys, tasks, agents, mcpServers sections) 完成以下组件的 i18n 翻译工作: 1. GitSettings.jsx - Git 配置界面 2. ApiKeysSettings.jsx - API 密钥设置 3. CredentialsSettings.jsx - 凭据设置(GitHub Token) 4. TasksSettings.jsx - TaskMaster 任务管理设置 5. ChatInterface.jsx - 聊天界面(主要翻译工作) 新增翻译文件: - src/i18n/locales/en/chat.json - 英文聊天界面翻译 - src/i18n/locales/zh-CN/chat.json - 中文聊天界面翻译 ChatInterface.jsx 翻译内容: - 代码块复制按钮 - 消息类型标签 - 工具设置提示 - 搜索结果显示 - Codex 权限模式(默认、编辑、无限制、计划模式) - 输入框占位符和提示文本 - 键盘快捷键提示 - 命令菜单按钮 更新 i18n 配置: - 在 config.js 中注册 chat 命名空间 - 扩展 settings.json 翻译(git、apiKeys、tasks、agents、mcpServers 等部分)
320 lines
13 KiB
JavaScript
320 lines
13 KiB
JavaScript
import { useState } from 'react';
|
|
import { Button } from '../ui/button';
|
|
import { Input } from '../ui/input';
|
|
import { Badge } from '../ui/badge';
|
|
import { Server, Plus, Edit3, Trash2, Terminal, Globe, Zap, X } from 'lucide-react';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
const getTransportIcon = (type) => {
|
|
switch (type) {
|
|
case 'stdio': return <Terminal className="w-4 h-4" />;
|
|
case 'sse': return <Zap className="w-4 h-4" />;
|
|
case 'http': return <Globe className="w-4 h-4" />;
|
|
default: return <Server className="w-4 h-4" />;
|
|
}
|
|
};
|
|
|
|
// Claude MCP Servers
|
|
function ClaudeMcpServers({
|
|
servers,
|
|
onAdd,
|
|
onEdit,
|
|
onDelete,
|
|
onTest,
|
|
onDiscoverTools,
|
|
testResults,
|
|
serverTools,
|
|
toolsLoading,
|
|
}) {
|
|
const { t } = useTranslation('settings');
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-3">
|
|
<Server className="w-5 h-5 text-purple-500" />
|
|
<h3 className="text-lg font-medium text-foreground">
|
|
{t('mcpServers.title')}
|
|
</h3>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{t('mcpServers.description.claude')}
|
|
</p>
|
|
|
|
<div className="flex justify-between items-center">
|
|
<Button
|
|
onClick={onAdd}
|
|
className="bg-purple-600 hover:bg-purple-700 text-white"
|
|
size="sm"
|
|
>
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
{t('mcpServers.addButton')}
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
{servers.map(server => (
|
|
<div key={server.id} className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
{getTransportIcon(server.type)}
|
|
<span className="font-medium text-foreground">{server.name}</span>
|
|
<Badge variant="outline" className="text-xs">
|
|
{server.type}
|
|
</Badge>
|
|
<Badge variant="outline" className="text-xs">
|
|
{server.scope === 'local' ? t('mcpServers.scope.local') : server.scope === 'user' ? t('mcpServers.scope.user') : server.scope}
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="text-sm text-muted-foreground space-y-1">
|
|
{server.type === 'stdio' && server.config?.command && (
|
|
<div>{t('mcpServers.config.command')}: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.command}</code></div>
|
|
)}
|
|
{(server.type === 'sse' || server.type === 'http') && server.config?.url && (
|
|
<div>{t('mcpServers.config.url')}: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.url}</code></div>
|
|
)}
|
|
{server.config?.args && server.config.args.length > 0 && (
|
|
<div>{t('mcpServers.config.args')}: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.args.join(' ')}</code></div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Test Results */}
|
|
{testResults?.[server.id] && (
|
|
<div className={`mt-2 p-2 rounded text-xs ${
|
|
testResults[server.id].success
|
|
? 'bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200'
|
|
: 'bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200'
|
|
}`}>
|
|
<div className="font-medium">{testResults[server.id].message}</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tools Discovery Results */}
|
|
{serverTools?.[server.id] && serverTools[server.id].tools?.length > 0 && (
|
|
<div className="mt-2 p-2 rounded text-xs bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-200">
|
|
<div className="font-medium">{t('mcpServers.tools.title')} {t('mcpServers.tools.count', { count: serverTools[server.id].tools.length })}</div>
|
|
<div className="flex flex-wrap gap-1 mt-1">
|
|
{serverTools[server.id].tools.slice(0, 5).map((tool, i) => (
|
|
<code key={i} className="bg-blue-100 dark:bg-blue-800 px-1 rounded">{tool.name}</code>
|
|
))}
|
|
{serverTools[server.id].tools.length > 5 && (
|
|
<span className="text-xs opacity-75">{t('mcpServers.tools.more', { count: serverTools[server.id].tools.length - 5 })}</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 ml-4">
|
|
<Button
|
|
onClick={() => onEdit(server)}
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-gray-600 hover:text-gray-700"
|
|
title={t('mcpServers.actions.edit')}
|
|
>
|
|
<Edit3 className="w-4 h-4" />
|
|
</Button>
|
|
<Button
|
|
onClick={() => onDelete(server.id, server.scope)}
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-red-600 hover:text-red-700"
|
|
title={t('mcpServers.actions.delete')}
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{servers.length === 0 && (
|
|
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
{t('mcpServers.empty')}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Cursor MCP Servers
|
|
function CursorMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
|
const { t } = useTranslation('settings');
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-3">
|
|
<Server className="w-5 h-5 text-purple-500" />
|
|
<h3 className="text-lg font-medium text-foreground">
|
|
{t('mcpServers.title')}
|
|
</h3>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{t('mcpServers.description.cursor')}
|
|
</p>
|
|
|
|
<div className="flex justify-between items-center">
|
|
<Button
|
|
onClick={onAdd}
|
|
className="bg-purple-600 hover:bg-purple-700 text-white"
|
|
size="sm"
|
|
>
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
{t('mcpServers.addButton')}
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
{servers.map(server => (
|
|
<div key={server.name || server.id} className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<Terminal className="w-4 h-4" />
|
|
<span className="font-medium text-foreground">{server.name}</span>
|
|
<Badge variant="outline" className="text-xs">stdio</Badge>
|
|
</div>
|
|
<div className="text-sm text-muted-foreground">
|
|
{server.config?.command && (
|
|
<div>{t('mcpServers.config.command')}: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.command}</code></div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 ml-4">
|
|
<Button
|
|
onClick={() => onEdit(server)}
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-gray-600 hover:text-gray-700"
|
|
title={t('mcpServers.actions.edit')}
|
|
>
|
|
<Edit3 className="w-4 h-4" />
|
|
</Button>
|
|
<Button
|
|
onClick={() => onDelete(server.name)}
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-red-600 hover:text-red-700"
|
|
title={t('mcpServers.actions.delete')}
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{servers.length === 0 && (
|
|
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
{t('mcpServers.empty')}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Codex MCP Servers
|
|
function CodexMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
|
const { t } = useTranslation('settings');
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-3">
|
|
<Server className="w-5 h-5 text-gray-700 dark:text-gray-300" />
|
|
<h3 className="text-lg font-medium text-foreground">
|
|
{t('mcpServers.title')}
|
|
</h3>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{t('mcpServers.description.codex')}
|
|
</p>
|
|
|
|
<div className="flex justify-between items-center">
|
|
<Button
|
|
onClick={onAdd}
|
|
className="bg-gray-800 hover:bg-gray-900 dark:bg-gray-700 dark:hover:bg-gray-600 text-white"
|
|
size="sm"
|
|
>
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
{t('mcpServers.addButton')}
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
{servers.map(server => (
|
|
<div key={server.name} className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<Terminal className="w-4 h-4" />
|
|
<span className="font-medium text-foreground">{server.name}</span>
|
|
<Badge variant="outline" className="text-xs">stdio</Badge>
|
|
</div>
|
|
|
|
<div className="text-sm text-muted-foreground space-y-1">
|
|
{server.config?.command && (
|
|
<div>{t('mcpServers.config.command')}: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.command}</code></div>
|
|
)}
|
|
{server.config?.args && server.config.args.length > 0 && (
|
|
<div>{t('mcpServers.config.args')}: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.args.join(' ')}</code></div>
|
|
)}
|
|
{server.config?.env && Object.keys(server.config.env).length > 0 && (
|
|
<div>{t('mcpServers.config.environment')}: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{Object.entries(server.config.env).map(([k, v]) => `${k}=${v}`).join(', ')}</code></div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 ml-4">
|
|
<Button
|
|
onClick={() => onEdit(server)}
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-gray-600 hover:text-gray-700"
|
|
title={t('mcpServers.actions.edit')}
|
|
>
|
|
<Edit3 className="w-4 h-4" />
|
|
</Button>
|
|
<Button
|
|
onClick={() => onDelete(server.name)}
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-red-600 hover:text-red-700"
|
|
title={t('mcpServers.actions.delete')}
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{servers.length === 0 && (
|
|
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
{t('mcpServers.empty')}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Help Section */}
|
|
<div className="bg-gray-100 dark:bg-gray-800/50 border border-gray-300 dark:border-gray-600 rounded-lg p-4">
|
|
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-2">{t('mcpServers.help.title')}</h4>
|
|
<p className="text-sm text-gray-700 dark:text-gray-300">
|
|
{t('mcpServers.help.description')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Main component
|
|
export default function McpServersContent({ agent, ...props }) {
|
|
if (agent === 'claude') {
|
|
return <ClaudeMcpServers {...props} />;
|
|
}
|
|
if (agent === 'cursor') {
|
|
return <CursorMcpServers {...props} />;
|
|
}
|
|
if (agent === 'codex') {
|
|
return <CodexMcpServers {...props} />;
|
|
}
|
|
return null;
|
|
}
|