mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-01-25 02:47:31 +00:00
feat: complete internationalization (i18n) for components
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 等部分)
This commit is contained in:
@@ -3,8 +3,10 @@ import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
import { Key, Plus, Trash2, Eye, EyeOff, Copy, Check, Github } from 'lucide-react';
|
||||
import { authenticatedFetch } from '../utils/api';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function ApiKeysSettings() {
|
||||
const { t } = useTranslation('settings');
|
||||
const [apiKeys, setApiKeys] = useState([]);
|
||||
const [githubTokens, setGithubTokens] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -63,7 +65,7 @@ function ApiKeysSettings() {
|
||||
};
|
||||
|
||||
const deleteApiKey = async (keyId) => {
|
||||
if (!confirm('Are you sure you want to delete this API key?')) return;
|
||||
if (!confirm(t('apiKeys.confirmDelete'))) return;
|
||||
|
||||
try {
|
||||
await authenticatedFetch(`/api/settings/api-keys/${keyId}`, {
|
||||
@@ -113,7 +115,7 @@ function ApiKeysSettings() {
|
||||
};
|
||||
|
||||
const deleteGithubToken = async (tokenId) => {
|
||||
if (!confirm('Are you sure you want to delete this GitHub token?')) return;
|
||||
if (!confirm(t('apiKeys.github.confirmDelete'))) return;
|
||||
|
||||
try {
|
||||
await authenticatedFetch(`/api/settings/credentials/${tokenId}`, {
|
||||
@@ -144,7 +146,7 @@ function ApiKeysSettings() {
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-muted-foreground">Loading...</div>;
|
||||
return <div className="text-muted-foreground">{t('apiKeys.loading')}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -152,9 +154,9 @@ function ApiKeysSettings() {
|
||||
{/* New API Key Alert */}
|
||||
{newlyCreatedKey && (
|
||||
<div className="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
|
||||
<h4 className="font-semibold text-yellow-500 mb-2">⚠️ Save Your API Key</h4>
|
||||
<h4 className="font-semibold text-yellow-500 mb-2">{t('apiKeys.newKey.alertTitle')}</h4>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
This is the only time you'll see this key. Store it securely.
|
||||
{t('apiKeys.newKey.alertMessage')}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 px-3 py-2 bg-background/50 rounded font-mono text-sm break-all">
|
||||
@@ -174,7 +176,7 @@ function ApiKeysSettings() {
|
||||
className="mt-3"
|
||||
onClick={() => setNewlyCreatedKey(null)}
|
||||
>
|
||||
I've saved it
|
||||
{t('apiKeys.newKey.iveSavedIt')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -184,33 +186,33 @@ function ApiKeysSettings() {
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Key className="h-5 w-5" />
|
||||
<h3 className="text-lg font-semibold">API Keys</h3>
|
||||
<h3 className="text-lg font-semibold">{t('apiKeys.title')}</h3>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setShowNewKeyForm(!showNewKeyForm)}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
New API Key
|
||||
{t('apiKeys.newButton')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Generate API keys to access the external API from other applications.
|
||||
{t('apiKeys.description')}
|
||||
</p>
|
||||
|
||||
{showNewKeyForm && (
|
||||
<div className="mb-4 p-4 border rounded-lg bg-card">
|
||||
<Input
|
||||
placeholder="API Key Name (e.g., Production Server)"
|
||||
placeholder={t('apiKeys.form.placeholder')}
|
||||
value={newKeyName}
|
||||
onChange={(e) => setNewKeyName(e.target.value)}
|
||||
className="mb-2"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={createApiKey}>Create</Button>
|
||||
<Button onClick={createApiKey}>{t('apiKeys.form.createButton')}</Button>
|
||||
<Button variant="outline" onClick={() => setShowNewKeyForm(false)}>
|
||||
Cancel
|
||||
{t('apiKeys.form.cancelButton')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -218,7 +220,7 @@ function ApiKeysSettings() {
|
||||
|
||||
<div className="space-y-2">
|
||||
{apiKeys.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground italic">No API keys created yet.</p>
|
||||
<p className="text-sm text-muted-foreground italic">{t('apiKeys.empty')}</p>
|
||||
) : (
|
||||
apiKeys.map((key) => (
|
||||
<div
|
||||
@@ -229,8 +231,8 @@ function ApiKeysSettings() {
|
||||
<div className="font-medium">{key.key_name}</div>
|
||||
<code className="text-xs text-muted-foreground">{key.api_key}</code>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Created: {new Date(key.created_at).toLocaleDateString()}
|
||||
{key.last_used && ` • Last used: ${new Date(key.last_used).toLocaleDateString()}`}
|
||||
{t('apiKeys.list.created')} {new Date(key.created_at).toLocaleDateString()}
|
||||
{key.last_used && ` • ${t('apiKeys.list.lastUsed')} ${new Date(key.last_used).toLocaleDateString()}`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -239,7 +241,7 @@ function ApiKeysSettings() {
|
||||
variant={key.is_active ? 'outline' : 'secondary'}
|
||||
onClick={() => toggleApiKey(key.id, key.is_active)}
|
||||
>
|
||||
{key.is_active ? 'Active' : 'Inactive'}
|
||||
{key.is_active ? t('apiKeys.status.active') : t('apiKeys.status.inactive')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -260,25 +262,25 @@ function ApiKeysSettings() {
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Github className="h-5 w-5" />
|
||||
<h3 className="text-lg font-semibold">GitHub Tokens</h3>
|
||||
<h3 className="text-lg font-semibold">{t('apiKeys.github.title')}</h3>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setShowNewTokenForm(!showNewTokenForm)}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Add Token
|
||||
{t('apiKeys.github.addButton')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Add GitHub Personal Access Tokens to clone private repositories via the external API.
|
||||
{t('apiKeys.github.description')}
|
||||
</p>
|
||||
|
||||
{showNewTokenForm && (
|
||||
<div className="mb-4 p-4 border rounded-lg bg-card">
|
||||
<Input
|
||||
placeholder="Token Name (e.g., Personal Repos)"
|
||||
placeholder={t('apiKeys.github.form.namePlaceholder')}
|
||||
value={newTokenName}
|
||||
onChange={(e) => setNewTokenName(e.target.value)}
|
||||
className="mb-2"
|
||||
@@ -286,7 +288,7 @@ function ApiKeysSettings() {
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showToken['new'] ? 'text' : 'password'}
|
||||
placeholder="GitHub Personal Access Token (ghp_...)"
|
||||
placeholder={t('apiKeys.github.form.tokenPlaceholder')}
|
||||
value={newGithubToken}
|
||||
onChange={(e) => setNewGithubToken(e.target.value)}
|
||||
className="mb-2 pr-10"
|
||||
@@ -300,13 +302,13 @@ function ApiKeysSettings() {
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={createGithubToken}>Add Token</Button>
|
||||
<Button onClick={createGithubToken}>{t('apiKeys.github.form.addButton')}</Button>
|
||||
<Button variant="outline" onClick={() => {
|
||||
setShowNewTokenForm(false);
|
||||
setNewTokenName('');
|
||||
setNewGithubToken('');
|
||||
}}>
|
||||
Cancel
|
||||
{t('apiKeys.github.form.cancelButton')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -314,7 +316,7 @@ function ApiKeysSettings() {
|
||||
|
||||
<div className="space-y-2">
|
||||
{githubTokens.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground italic">No GitHub tokens added yet.</p>
|
||||
<p className="text-sm text-muted-foreground italic">{t('apiKeys.github.empty')}</p>
|
||||
) : (
|
||||
githubTokens.map((token) => (
|
||||
<div
|
||||
@@ -324,7 +326,7 @@ function ApiKeysSettings() {
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{token.credential_name}</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Added: {new Date(token.created_at).toLocaleDateString()}
|
||||
{t('apiKeys.github.added')} {new Date(token.created_at).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -333,7 +335,7 @@ function ApiKeysSettings() {
|
||||
variant={token.is_active ? 'outline' : 'secondary'}
|
||||
onClick={() => toggleGithubToken(token.id, token.is_active)}
|
||||
>
|
||||
{token.is_active ? 'Active' : 'Inactive'}
|
||||
{token.is_active ? t('apiKeys.status.active') : t('apiKeys.status.inactive')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -351,9 +353,9 @@ function ApiKeysSettings() {
|
||||
|
||||
{/* Documentation Link */}
|
||||
<div className="p-4 bg-muted/50 rounded-lg">
|
||||
<h4 className="font-semibold mb-2">External API Documentation</h4>
|
||||
<h4 className="font-semibold mb-2">{t('apiKeys.documentation.title')}</h4>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Learn how to use the external API to trigger Claude/Cursor sessions from your applications.
|
||||
{t('apiKeys.documentation.description')}
|
||||
</p>
|
||||
<a
|
||||
href="/EXTERNAL_API.md"
|
||||
@@ -361,7 +363,7 @@ function ApiKeysSettings() {
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-primary hover:underline"
|
||||
>
|
||||
View API Documentation →
|
||||
{t('apiKeys.documentation.viewLink')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,6 +30,7 @@ import CursorLogo from './CursorLogo.jsx';
|
||||
import CodexLogo from './CodexLogo.jsx';
|
||||
import NextTaskBanner from './NextTaskBanner.jsx';
|
||||
import { useTasksSettings } from '../contexts/TasksSettingsContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ClaudeStatus from './ClaudeStatus';
|
||||
import TokenUsagePie from './TokenUsagePie';
|
||||
@@ -336,27 +337,27 @@ function grantClaudeToolPermission(entry) {
|
||||
}
|
||||
|
||||
// Common markdown components to ensure consistent rendering (tables, inline code, links, etc.)
|
||||
const markdownComponents = {
|
||||
code: ({ node, inline, className, children, ...props }) => {
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
const raw = Array.isArray(children) ? children.join('') : String(children ?? '');
|
||||
const looksMultiline = /[\r\n]/.test(raw);
|
||||
const inlineDetected = inline || (node && node.type === 'inlineCode');
|
||||
const shouldInline = inlineDetected || !looksMultiline; // fallback to inline if single-line
|
||||
const CodeBlock = ({ node, inline, className, children, ...props }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
const raw = Array.isArray(children) ? children.join('') : String(children ?? '');
|
||||
const looksMultiline = /[\r\n]/.test(raw);
|
||||
const inlineDetected = inline || (node && node.type === 'inlineCode');
|
||||
const shouldInline = inlineDetected || !looksMultiline; // fallback to inline if single-line
|
||||
|
||||
// Inline code rendering
|
||||
if (shouldInline) {
|
||||
return (
|
||||
<code
|
||||
className={`font-mono text-[0.9em] px-1.5 py-0.5 rounded-md bg-gray-100 text-gray-900 border border-gray-200 dark:bg-gray-800/60 dark:text-gray-100 dark:border-gray-700 whitespace-pre-wrap break-words ${
|
||||
className || ''
|
||||
}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
// Inline code rendering
|
||||
if (shouldInline) {
|
||||
return (
|
||||
<code
|
||||
className={`font-mono text-[0.9em] px-1.5 py-0.5 rounded-md bg-gray-100 text-gray-900 border border-gray-200 dark:bg-gray-800/60 dark:text-gray-100 dark:border-gray-700 whitespace-pre-wrap break-words ${
|
||||
className || ''
|
||||
}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
// Extract language from className (format: language-xxx)
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
@@ -411,15 +412,15 @@ const markdownComponents = {
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 focus:opacity-100 active:opacity-100 transition-opacity text-xs px-2 py-1 rounded-md bg-gray-700/80 hover:bg-gray-700 text-white border border-gray-600"
|
||||
title={copied ? 'Copied' : 'Copy code'}
|
||||
aria-label={copied ? 'Copied' : 'Copy code'}
|
||||
title={copied ? t('codeBlock.copied') : t('codeBlock.copyCode')}
|
||||
aria-label={copied ? t('codeBlock.copied') : t('codeBlock.copyCode')}
|
||||
>
|
||||
{copied ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<svg className="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Copied
|
||||
{t('codeBlock.copied')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-1">
|
||||
@@ -427,7 +428,7 @@ const markdownComponents = {
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
|
||||
</svg>
|
||||
Copy
|
||||
{t('codeBlock.copy')}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
@@ -452,7 +453,11 @@ const markdownComponents = {
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
// Common markdown components to ensure consistent rendering (tables, inline code, links, etc.)
|
||||
const markdownComponents = {
|
||||
code: CodeBlock,
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="border-l-4 border-gray-300 dark:border-gray-600 pl-4 italic text-gray-600 dark:text-gray-400 my-2">
|
||||
{children}
|
||||
@@ -485,6 +490,7 @@ const markdownComponents = {
|
||||
|
||||
// Memoized message component to prevent unnecessary re-renders
|
||||
const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFileOpen, onShowSettings, onGrantToolPermission, autoExpandTools, showRawParameters, showThinking, selectedProject, provider }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const isGrouped = prevMessage && prevMessage.type === message.type &&
|
||||
((prevMessage.type === 'assistant') ||
|
||||
(prevMessage.type === 'user') ||
|
||||
@@ -587,7 +593,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{message.type === 'error' ? 'Error' : message.type === 'tool' ? 'Tool' : ((localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? 'Cursor' : (localStorage.getItem('selected-provider') || 'claude') === 'codex' ? 'Codex' : 'Claude')}
|
||||
{message.type === 'error' ? t('messageTypes.error') : message.type === 'tool' ? t('messageTypes.tool') : ((localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? t('messageTypes.cursor') : (localStorage.getItem('selected-provider') || 'claude') === 'codex' ? t('messageTypes.codex') : t('messageTypes.claude'))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -615,8 +621,8 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
||||
const input = JSON.parse(message.toolInput);
|
||||
return (
|
||||
<span className="font-mono truncate flex-1 min-w-0">
|
||||
{input.pattern && <span>pattern: <span className="text-blue-600 dark:text-blue-400">{input.pattern}</span></span>}
|
||||
{input.path && <span className="ml-2">in: {input.path}</span>}
|
||||
{input.pattern && <span>{t('search.pattern')} <span className="text-blue-600 dark:text-blue-400">{input.pattern}</span></span>}
|
||||
{input.path && <span className="ml-2">{t('search.in')} {input.path}</span>}
|
||||
</span>
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -629,7 +635,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
||||
href={`#tool-result-${message.toolId}`}
|
||||
className="flex-shrink-0 text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium transition-colors flex items-center gap-1"
|
||||
>
|
||||
<span>results</span>
|
||||
<span>{t('tools.searchResults')}</span>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
@@ -673,7 +679,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
||||
onShowSettings();
|
||||
}}
|
||||
className="p-2 rounded-lg hover:bg-white/60 dark:hover:bg-gray-800/60 transition-all duration-200 group/btn backdrop-blur-sm"
|
||||
title="Tool Settings"
|
||||
title={t('tools.settings')}
|
||||
>
|
||||
<svg className="w-4 h-4 text-gray-600 dark:text-gray-400 group-hover/btn:text-blue-600 dark:group-hover/btn:text-blue-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
@@ -1851,6 +1857,7 @@ const ImageAttachment = ({ file, onRemove, uploadProgress, error }) => {
|
||||
// This ensures uninterrupted chat experience by pausing sidebar refreshes during conversations.
|
||||
function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, messages, onFileOpen, onInputFocusChange, onSessionActive, onSessionInactive, onSessionProcessing, onSessionNotProcessing, processingSessions, onReplaceTemporarySession, onNavigateToSession, onShowSettings, autoExpandTools, showRawParameters, showThinking, autoScrollToBottom, sendByCtrlEnter, externalMessageUpdate, onTaskClick, onShowAllTasks }) {
|
||||
const { tasksEnabled, isTaskMasterInstalled } = useTasksSettings();
|
||||
const { t } = useTranslation('chat');
|
||||
const [input, setInput] = useState(() => {
|
||||
if (typeof window !== 'undefined' && selectedProject) {
|
||||
return safeLocalStorage.getItem(`draft_input_${selectedProject.name}`) || '';
|
||||
@@ -5191,7 +5198,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
? 'bg-orange-50 dark:bg-orange-900/20 text-orange-700 dark:text-orange-300 border-orange-300 dark:border-orange-600 hover:bg-orange-100 dark:hover:bg-orange-900/30'
|
||||
: 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-600 hover:bg-blue-100 dark:hover:bg-blue-900/30'
|
||||
}`}
|
||||
title="Click to change permission mode (or press Tab in input)"
|
||||
title={t('input.clickToChangeMode')}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${
|
||||
@@ -5204,10 +5211,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
: 'bg-blue-500'
|
||||
}`} />
|
||||
<span>
|
||||
{permissionMode === 'default' && 'Default Mode'}
|
||||
{permissionMode === 'acceptEdits' && 'Accept Edits'}
|
||||
{permissionMode === 'bypassPermissions' && 'Bypass Permissions'}
|
||||
{permissionMode === 'plan' && 'Plan Mode'}
|
||||
{permissionMode === 'default' && t('codex.modes.default')}
|
||||
{permissionMode === 'acceptEdits' && t('codex.modes.acceptEdits')}
|
||||
{permissionMode === 'bypassPermissions' && t('codex.modes.bypassPermissions')}
|
||||
{permissionMode === 'plan' && t('codex.modes.plan')}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
@@ -5236,7 +5243,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
}
|
||||
}}
|
||||
className="relative w-8 h-8 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 rounded-full flex items-center justify-center transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:ring-offset-gray-800"
|
||||
title="Show all commands"
|
||||
title={t('input.showAllCommands')}
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
@@ -5421,7 +5428,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
const isExpanded = e.target.scrollHeight > lineHeight * 2;
|
||||
setIsTextareaExpanded(isExpanded);
|
||||
}}
|
||||
placeholder={`Type / for commands, @ for files, or ask ${provider === 'cursor' ? 'Cursor' : 'Claude'} anything...`}
|
||||
placeholder={t('input.placeholder', { provider: provider === 'cursor' ? t('messageTypes.cursor') : provider === 'codex' ? t('messageTypes.codex') : t('messageTypes.claude') })}
|
||||
disabled={isLoading}
|
||||
className="chat-input-placeholder block w-full pl-12 pr-20 sm:pr-40 py-1.5 sm:py-4 bg-transparent rounded-2xl focus:outline-none text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 disabled:opacity-50 resize-none min-h-[50px] sm:min-h-[80px] max-h-[40vh] sm:max-h-[300px] overflow-y-auto text-sm sm:text-base leading-[21px] sm:leading-6 transition-all duration-200"
|
||||
style={{ height: '50px' }}
|
||||
@@ -5431,7 +5438,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
type="button"
|
||||
onClick={open}
|
||||
className="absolute left-2 top-1/2 transform -translate-y-1/2 p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
title="Attach images"
|
||||
title={t('input.attachImages')}
|
||||
>
|
||||
<svg className="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
@@ -5480,8 +5487,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
input.trim() ? 'opacity-0' : 'opacity-100'
|
||||
}`}>
|
||||
{sendByCtrlEnter
|
||||
? "Ctrl+Enter to send • Shift+Enter for new line • Tab to change modes • / for slash commands"
|
||||
: "Enter to send • Shift+Enter for new line • Tab to change modes • / for slash commands"}
|
||||
? t('input.hintText.ctrlEnter')
|
||||
: t('input.hintText.enter')}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -5,8 +5,10 @@ import { Key, Plus, Trash2, Eye, EyeOff, Copy, Check, Github, ExternalLink } fro
|
||||
import { useVersionCheck } from '../hooks/useVersionCheck';
|
||||
import { version } from '../../package.json';
|
||||
import { authenticatedFetch } from '../utils/api';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function CredentialsSettings() {
|
||||
const { t } = useTranslation('settings');
|
||||
const [apiKeys, setApiKeys] = useState([]);
|
||||
const [githubCredentials, setGithubCredentials] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -69,7 +71,7 @@ function CredentialsSettings() {
|
||||
};
|
||||
|
||||
const deleteApiKey = async (keyId) => {
|
||||
if (!confirm('Are you sure you want to delete this API key?')) return;
|
||||
if (!confirm(t('apiKeys.confirmDelete'))) return;
|
||||
|
||||
try {
|
||||
await authenticatedFetch(`/api/settings/api-keys/${keyId}`, {
|
||||
@@ -121,7 +123,7 @@ function CredentialsSettings() {
|
||||
};
|
||||
|
||||
const deleteGithubCredential = async (credentialId) => {
|
||||
if (!confirm('Are you sure you want to delete this GitHub token?')) return;
|
||||
if (!confirm(t('apiKeys.github.confirmDelete'))) return;
|
||||
|
||||
try {
|
||||
await authenticatedFetch(`/api/settings/credentials/${credentialId}`, {
|
||||
@@ -152,7 +154,7 @@ function CredentialsSettings() {
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-muted-foreground">Loading...</div>;
|
||||
return <div className="text-muted-foreground">{t('apiKeys.loading')}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -160,9 +162,9 @@ function CredentialsSettings() {
|
||||
{/* New API Key Alert */}
|
||||
{newlyCreatedKey && (
|
||||
<div className="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
|
||||
<h4 className="font-semibold text-yellow-500 mb-2">⚠️ Save Your API Key</h4>
|
||||
<h4 className="font-semibold text-yellow-500 mb-2">{t('apiKeys.newKey.alertTitle')}</h4>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
This is the only time you'll see this key. Store it securely.
|
||||
{t('apiKeys.newKey.alertMessage')}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 px-3 py-2 bg-background/50 rounded font-mono text-sm break-all">
|
||||
@@ -182,7 +184,7 @@ function CredentialsSettings() {
|
||||
className="mt-3"
|
||||
onClick={() => setNewlyCreatedKey(null)}
|
||||
>
|
||||
I've saved it
|
||||
{t('apiKeys.newKey.iveSavedIt')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -192,20 +194,20 @@ function CredentialsSettings() {
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Key className="h-5 w-5" />
|
||||
<h3 className="text-lg font-semibold">API Keys</h3>
|
||||
<h3 className="text-lg font-semibold">{t('apiKeys.title')}</h3>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setShowNewKeyForm(!showNewKeyForm)}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
New API Key
|
||||
{t('apiKeys.newButton')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Generate API keys to access the external API from other applications.
|
||||
{t('apiKeys.description')}
|
||||
</p>
|
||||
<a
|
||||
href="/api-docs.html"
|
||||
@@ -213,7 +215,7 @@ function CredentialsSettings() {
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-primary hover:underline inline-flex items-center gap-1"
|
||||
>
|
||||
API Documentation
|
||||
{t('apiKeys.apiDocsLink')}
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -221,15 +223,15 @@ function CredentialsSettings() {
|
||||
{showNewKeyForm && (
|
||||
<div className="mb-4 p-4 border rounded-lg bg-card">
|
||||
<Input
|
||||
placeholder="API Key Name (e.g., Production Server)"
|
||||
placeholder={t('apiKeys.form.placeholder')}
|
||||
value={newKeyName}
|
||||
onChange={(e) => setNewKeyName(e.target.value)}
|
||||
className="mb-2"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={createApiKey}>Create</Button>
|
||||
<Button onClick={createApiKey}>{t('apiKeys.form.createButton')}</Button>
|
||||
<Button variant="outline" onClick={() => setShowNewKeyForm(false)}>
|
||||
Cancel
|
||||
{t('apiKeys.form.cancelButton')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -237,7 +239,7 @@ function CredentialsSettings() {
|
||||
|
||||
<div className="space-y-2">
|
||||
{apiKeys.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground italic">No API keys created yet.</p>
|
||||
<p className="text-sm text-muted-foreground italic">{t('apiKeys.empty')}</p>
|
||||
) : (
|
||||
apiKeys.map((key) => (
|
||||
<div
|
||||
@@ -248,8 +250,8 @@ function CredentialsSettings() {
|
||||
<div className="font-medium">{key.key_name}</div>
|
||||
<code className="text-xs text-muted-foreground">{key.api_key}</code>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Created: {new Date(key.created_at).toLocaleDateString()}
|
||||
{key.last_used && ` • Last used: ${new Date(key.last_used).toLocaleDateString()}`}
|
||||
{t('apiKeys.list.created')} {new Date(key.created_at).toLocaleDateString()}
|
||||
{key.last_used && ` • ${t('apiKeys.list.lastUsed')} ${new Date(key.last_used).toLocaleDateString()}`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -258,7 +260,7 @@ function CredentialsSettings() {
|
||||
variant={key.is_active ? 'outline' : 'secondary'}
|
||||
onClick={() => toggleApiKey(key.id, key.is_active)}
|
||||
>
|
||||
{key.is_active ? 'Active' : 'Inactive'}
|
||||
{key.is_active ? t('apiKeys.status.active') : t('apiKeys.status.inactive')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -279,25 +281,25 @@ function CredentialsSettings() {
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Github className="h-5 w-5" />
|
||||
<h3 className="text-lg font-semibold">GitHub Credentials</h3>
|
||||
<h3 className="text-lg font-semibold">{t('apiKeys.github.title')}</h3>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setShowNewGithubForm(!showNewGithubForm)}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Add Token
|
||||
{t('apiKeys.github.addButton')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Add GitHub Personal Access Tokens to clone private repositories. You can also pass tokens directly in API requests without storing them.
|
||||
{t('apiKeys.github.descriptionAlt')}
|
||||
</p>
|
||||
|
||||
{showNewGithubForm && (
|
||||
<div className="mb-4 p-4 border rounded-lg bg-card space-y-3">
|
||||
<Input
|
||||
placeholder="Token Name (e.g., Personal Repos)"
|
||||
placeholder={t('apiKeys.github.form.namePlaceholder')}
|
||||
value={newGithubName}
|
||||
onChange={(e) => setNewGithubName(e.target.value)}
|
||||
/>
|
||||
@@ -305,7 +307,7 @@ function CredentialsSettings() {
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showToken['new'] ? 'text' : 'password'}
|
||||
placeholder="GitHub Personal Access Token (ghp_...)"
|
||||
placeholder={t('apiKeys.github.form.tokenPlaceholder')}
|
||||
value={newGithubToken}
|
||||
onChange={(e) => setNewGithubToken(e.target.value)}
|
||||
className="pr-10"
|
||||
@@ -320,20 +322,20 @@ function CredentialsSettings() {
|
||||
</div>
|
||||
|
||||
<Input
|
||||
placeholder="Description (optional)"
|
||||
placeholder={t('apiKeys.github.form.descriptionPlaceholder')}
|
||||
value={newGithubDescription}
|
||||
onChange={(e) => setNewGithubDescription(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={createGithubCredential}>Add Token</Button>
|
||||
<Button onClick={createGithubCredential}>{t('apiKeys.github.form.addButton')}</Button>
|
||||
<Button variant="outline" onClick={() => {
|
||||
setShowNewGithubForm(false);
|
||||
setNewGithubName('');
|
||||
setNewGithubToken('');
|
||||
setNewGithubDescription('');
|
||||
}}>
|
||||
Cancel
|
||||
{t('apiKeys.github.form.cancelButton')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -343,14 +345,14 @@ function CredentialsSettings() {
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-primary hover:underline block"
|
||||
>
|
||||
How to create a GitHub Personal Access Token →
|
||||
{t('apiKeys.github.form.howToCreate')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
{githubCredentials.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground italic">No GitHub tokens added yet.</p>
|
||||
<p className="text-sm text-muted-foreground italic">{t('apiKeys.github.empty')}</p>
|
||||
) : (
|
||||
githubCredentials.map((credential) => (
|
||||
<div
|
||||
@@ -363,7 +365,7 @@ function CredentialsSettings() {
|
||||
<div className="text-xs text-muted-foreground">{credential.description}</div>
|
||||
)}
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Added: {new Date(credential.created_at).toLocaleDateString()}
|
||||
{t('apiKeys.github.added')} {new Date(credential.created_at).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -372,7 +374,7 @@ function CredentialsSettings() {
|
||||
variant={credential.is_active ? 'outline' : 'secondary'}
|
||||
onClick={() => toggleGithubCredential(credential.id, credential.is_active)}
|
||||
>
|
||||
{credential.is_active ? 'Active' : 'Inactive'}
|
||||
{credential.is_active ? t('apiKeys.status.active') : t('apiKeys.status.inactive')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -406,7 +408,7 @@ function CredentialsSettings() {
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1.5 px-2 py-0.5 bg-green-500/10 text-green-600 dark:text-green-400 rounded-full hover:bg-green-500/20 transition-colors not-italic font-medium"
|
||||
>
|
||||
<span className="text-[10px]">Update available: v{latestVersion}</span>
|
||||
<span className="text-[10px]">{t('apiKeys.version.updateAvailable', { version: latestVersion })}</span>
|
||||
<ExternalLink className="h-2.5 w-2.5" />
|
||||
</a>
|
||||
)}
|
||||
|
||||
@@ -3,8 +3,10 @@ import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
import { GitBranch, Check } from 'lucide-react';
|
||||
import { authenticatedFetch } from '../utils/api';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function GitSettings() {
|
||||
const { t } = useTranslation('settings');
|
||||
const [gitName, setGitName] = useState('');
|
||||
const [gitEmail, setGitEmail] = useState('');
|
||||
const [gitConfigLoading, setGitConfigLoading] = useState(false);
|
||||
@@ -61,17 +63,17 @@ function GitSettings() {
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<GitBranch className="h-5 w-5" />
|
||||
<h3 className="text-lg font-semibold">Git Configuration</h3>
|
||||
<h3 className="text-lg font-semibold">{t('git.title')}</h3>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Configure your git identity for commits. These settings will be applied globally via <code className="bg-muted px-2 py-0.5 rounded text-xs">git config --global</code>
|
||||
{t('git.description')}
|
||||
</p>
|
||||
|
||||
<div className="p-4 border rounded-lg bg-card space-y-3">
|
||||
<div>
|
||||
<label htmlFor="settings-git-name" className="block text-sm font-medium text-foreground mb-2">
|
||||
Git Name
|
||||
{t('git.name.label')}
|
||||
</label>
|
||||
<Input
|
||||
id="settings-git-name"
|
||||
@@ -83,13 +85,13 @@ function GitSettings() {
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Your name for git commits
|
||||
{t('git.name.help')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="settings-git-email" className="block text-sm font-medium text-foreground mb-2">
|
||||
Git Email
|
||||
{t('git.email.label')}
|
||||
</label>
|
||||
<Input
|
||||
id="settings-git-email"
|
||||
@@ -101,7 +103,7 @@ function GitSettings() {
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Your email for git commits
|
||||
{t('git.email.help')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -110,13 +112,13 @@ function GitSettings() {
|
||||
onClick={saveGitConfig}
|
||||
disabled={gitConfigSaving || !gitName || !gitEmail}
|
||||
>
|
||||
{gitConfigSaving ? 'Saving...' : 'Save Configuration'}
|
||||
{gitConfigSaving ? t('git.actions.saving') : t('git.actions.save')}
|
||||
</Button>
|
||||
|
||||
{saveStatus === 'success' && (
|
||||
<div className="text-sm text-green-600 dark:text-green-400 flex items-center gap-2">
|
||||
<Check className="w-4 h-4" />
|
||||
Saved successfully
|
||||
{t('git.status.success')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -950,7 +950,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="flex items-center gap-3">
|
||||
<SettingsIcon className="w-5 h-5 md:w-6 md:h-6 text-blue-600" />
|
||||
<h2 className="text-lg md:text-xl font-semibold text-foreground">
|
||||
Settings
|
||||
{t('title')}
|
||||
</h2>
|
||||
</div>
|
||||
<Button
|
||||
@@ -975,7 +975,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
Agents
|
||||
{t('mainTabs.agents')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('appearance')}
|
||||
@@ -985,7 +985,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
Appearance
|
||||
{t('mainTabs.appearance')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('git')}
|
||||
@@ -996,7 +996,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
}`}
|
||||
>
|
||||
<GitBranch className="w-4 h-4 inline mr-2" />
|
||||
Git
|
||||
{t('mainTabs.git')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('api')}
|
||||
@@ -1007,7 +1007,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
}`}
|
||||
>
|
||||
<Key className="w-4 h-4 inline mr-2" />
|
||||
API & Tokens
|
||||
{t('mainTabs.apiTokens')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('tasks')}
|
||||
@@ -1017,7 +1017,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
Tasks
|
||||
{t('mainTabs.tasks')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1035,10 +1035,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-foreground">
|
||||
Dark Mode
|
||||
{t('appearanceSettings.darkMode.label')}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Toggle between light and dark themes
|
||||
{t('appearanceSettings.darkMode.description')}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -1076,10 +1076,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-foreground">
|
||||
Project Sorting
|
||||
{t('appearanceSettings.projectSorting.label')}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
How projects are ordered in the sidebar
|
||||
{t('appearanceSettings.projectSorting.description')}
|
||||
</div>
|
||||
</div>
|
||||
<select
|
||||
@@ -1087,8 +1087,8 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
onChange={(e) => setProjectSortOrder(e.target.value)}
|
||||
className="text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2 w-32"
|
||||
>
|
||||
<option value="name">Alphabetical</option>
|
||||
<option value="date">Recent Activity</option>
|
||||
<option value="name">{t('appearanceSettings.projectSorting.alphabetical')}</option>
|
||||
<option value="date">{t('appearanceSettings.projectSorting.recentActivity')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1096,17 +1096,17 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
|
||||
{/* Code Editor Settings */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-foreground">Code Editor</h3>
|
||||
<h3 className="text-lg font-semibold text-foreground">{t('appearanceSettings.codeEditor.title')}</h3>
|
||||
|
||||
{/* Editor Theme */}
|
||||
<div 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-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-foreground">
|
||||
Editor Theme
|
||||
{t('appearanceSettings.codeEditor.theme.label')}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Default theme for the code editor
|
||||
{t('appearanceSettings.codeEditor.theme.description')}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -1137,10 +1137,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-foreground">
|
||||
Word Wrap
|
||||
{t('appearanceSettings.codeEditor.wordWrap.label')}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Enable word wrapping by default in the editor
|
||||
{t('appearanceSettings.codeEditor.wordWrap.description')}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -1165,10 +1165,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-foreground">
|
||||
Show Minimap
|
||||
{t('appearanceSettings.codeEditor.showMinimap.label')}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Display a minimap for easier navigation in diff view
|
||||
{t('appearanceSettings.codeEditor.showMinimap.description')}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -1193,10 +1193,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-foreground">
|
||||
Show Line Numbers
|
||||
{t('appearanceSettings.codeEditor.lineNumbers.label')}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Display line numbers in the editor
|
||||
{t('appearanceSettings.codeEditor.lineNumbers.description')}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -1221,10 +1221,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-foreground">
|
||||
Font Size
|
||||
{t('appearanceSettings.codeEditor.fontSize.label')}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Editor font size in pixels
|
||||
{t('appearanceSettings.codeEditor.fontSize.description')}
|
||||
</div>
|
||||
</div>
|
||||
<select
|
||||
@@ -1452,7 +1452,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="bg-background border border-border rounded-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
{editingMcpServer ? 'Edit MCP Server' : 'Add MCP Server'}
|
||||
{editingMcpServer ? t('mcpForm.title.edit') : t('mcpForm.title.add')}
|
||||
</h3>
|
||||
<Button variant="ghost" size="sm" onClick={resetMcpForm}>
|
||||
<X className="w-4 h-4" />
|
||||
@@ -1472,7 +1472,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
Form Input
|
||||
{t('mcpForm.importMode.form')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1483,7 +1483,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
JSON Import
|
||||
{t('mcpForm.importMode.json')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -1492,12 +1492,12 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
{mcpFormData.importMode === 'form' && editingMcpServer && (
|
||||
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-3">
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Scope
|
||||
{t('mcpForm.scope.label')}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
{mcpFormData.scope === 'user' ? <Globe className="w-4 h-4" /> : <FolderOpen className="w-4 h-4" />}
|
||||
<span className="text-sm">
|
||||
{mcpFormData.scope === 'user' ? 'User (Global)' : 'Project (Local)'}
|
||||
{mcpFormData.scope === 'user' ? t('mcpForm.scope.userGlobal') : t('mcpForm.scope.projectLocal')}
|
||||
</span>
|
||||
{mcpFormData.scope === 'local' && mcpFormData.projectPath && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
@@ -1506,7 +1506,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Scope cannot be changed when editing an existing server
|
||||
{t('mcpForm.scope.cannotChange')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -1516,7 +1516,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Scope *
|
||||
{t('mcpForm.scope.label')} *
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
@@ -1530,7 +1530,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Globe className="w-4 h-4" />
|
||||
<span>User (Global)</span>
|
||||
<span>{t('mcpForm.scope.userGlobal')}</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
@@ -1544,14 +1544,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<FolderOpen className="w-4 h-4" />
|
||||
<span>Project (Local)</span>
|
||||
<span>{t('mcpForm.scope.projectLocal')}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
{mcpFormData.scope === 'user'
|
||||
? 'User scope: Available across all projects on your machine'
|
||||
: 'Local scope: Only available in the selected project'
|
||||
{mcpFormData.scope === 'user'
|
||||
? t('mcpForm.scope.userDescription')
|
||||
: t('mcpForm.scope.projectDescription')
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
@@ -1560,7 +1560,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
{mcpFormData.scope === 'local' && !editingMcpServer && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Project *
|
||||
{t('mcpForm.fields.selectProject')} *
|
||||
</label>
|
||||
<select
|
||||
value={mcpFormData.projectPath}
|
||||
@@ -1568,7 +1568,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500"
|
||||
required={mcpFormData.scope === 'local'}
|
||||
>
|
||||
<option value="">Select a project...</option>
|
||||
<option value="">{t('mcpForm.fields.selectProject')}...</option>
|
||||
{projects.map(project => (
|
||||
<option key={project.name} value={project.path || project.fullPath}>
|
||||
{project.displayName || project.name}
|
||||
@@ -1577,7 +1577,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
</select>
|
||||
{mcpFormData.projectPath && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Path: {mcpFormData.projectPath}
|
||||
{t('mcpForm.projectPath', { path: mcpFormData.projectPath })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -1589,22 +1589,22 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className={mcpFormData.importMode === 'json' ? 'md:col-span-2' : ''}>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Server Name *
|
||||
{t('mcpForm.fields.serverName')} *
|
||||
</label>
|
||||
<Input
|
||||
value={mcpFormData.name}
|
||||
onChange={(e) => {
|
||||
setMcpFormData(prev => ({...prev, name: e.target.value}));
|
||||
}}
|
||||
placeholder="my-server"
|
||||
placeholder={t('mcpForm.placeholders.serverName')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{mcpFormData.importMode === 'form' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Transport Type *
|
||||
{t('mcpForm.fields.transportType')} *
|
||||
</label>
|
||||
<select
|
||||
value={mcpFormData.type}
|
||||
@@ -1626,7 +1626,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
{editingMcpServer && mcpFormData.raw && mcpFormData.importMode === 'form' && (
|
||||
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||||
<h4 className="text-sm font-medium text-foreground mb-2">
|
||||
Configuration Details (from {editingMcpServer.scope === 'global' ? '~/.claude.json' : 'project config'})
|
||||
{t('mcpForm.configDetails', { configFile: editingMcpServer.scope === 'global' ? '~/.claude.json' : 'project config' })}
|
||||
</h4>
|
||||
<pre className="text-xs bg-gray-100 dark:bg-gray-800 p-3 rounded overflow-x-auto">
|
||||
{JSON.stringify(mcpFormData.raw, null, 2)}
|
||||
@@ -1639,7 +1639,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
JSON Configuration *
|
||||
{t('mcpForm.fields.jsonConfig')} *
|
||||
</label>
|
||||
<textarea
|
||||
value={mcpFormData.jsonInput}
|
||||
@@ -1651,18 +1651,18 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
const parsed = JSON.parse(e.target.value);
|
||||
// Basic validation
|
||||
if (!parsed.type) {
|
||||
setJsonValidationError('Missing required field: type');
|
||||
setJsonValidationError(t('mcpForm.validation.missingType'));
|
||||
} else if (parsed.type === 'stdio' && !parsed.command) {
|
||||
setJsonValidationError('stdio type requires a command field');
|
||||
setJsonValidationError(t('mcpForm.validation.stdioRequiresCommand'));
|
||||
} else if ((parsed.type === 'http' || parsed.type === 'sse') && !parsed.url) {
|
||||
setJsonValidationError(`${parsed.type} type requires a url field`);
|
||||
setJsonValidationError(t('mcpForm.validation.httpRequiresUrl', { type: parsed.type }));
|
||||
} else {
|
||||
setJsonValidationError('');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (e.target.value.trim()) {
|
||||
setJsonValidationError('Invalid JSON format');
|
||||
setJsonValidationError(t('mcpForm.validation.invalidJson'));
|
||||
} else {
|
||||
setJsonValidationError('');
|
||||
}
|
||||
@@ -1677,7 +1677,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<p className="text-xs text-red-500 mt-1">{jsonValidationError}</p>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Paste your MCP server configuration in JSON format. Example formats:
|
||||
{t('mcpForm.validation.jsonHelp')}
|
||||
<br />• stdio: {`{"type":"stdio","command":"npx","args":["@upstash/context7-mcp"]}`}
|
||||
<br />• http/sse: {`{"type":"http","url":"https://api.example.com/mcp"}`}
|
||||
</p>
|
||||
@@ -1690,7 +1690,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Command *
|
||||
{t('mcpForm.fields.command')} *
|
||||
</label>
|
||||
<Input
|
||||
value={mcpFormData.config.command}
|
||||
@@ -1699,10 +1699,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Arguments (one per line)
|
||||
{t('mcpForm.fields.arguments')}
|
||||
</label>
|
||||
<textarea
|
||||
value={Array.isArray(mcpFormData.config.args) ? mcpFormData.config.args.join('\n') : ''}
|
||||
@@ -1718,7 +1718,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
{mcpFormData.importMode === 'form' && (mcpFormData.type === 'sse' || mcpFormData.type === 'http') && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
URL *
|
||||
{t('mcpForm.fields.url')} *
|
||||
</label>
|
||||
<Input
|
||||
value={mcpFormData.config.url}
|
||||
@@ -1734,7 +1734,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
{mcpFormData.importMode === 'form' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Environment Variables (KEY=value, one per line)
|
||||
{t('mcpForm.fields.envVars')}
|
||||
</label>
|
||||
<textarea
|
||||
value={Object.entries(mcpFormData.config.env || {}).map(([k, v]) => `${k}=${v}`).join('\n')}
|
||||
@@ -1758,7 +1758,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
{mcpFormData.importMode === 'form' && (mcpFormData.type === 'sse' || mcpFormData.type === 'http') && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Headers (KEY=value, one per line)
|
||||
{t('mcpForm.fields.headers')}
|
||||
</label>
|
||||
<textarea
|
||||
value={Object.entries(mcpFormData.config.headers || {}).map(([k, v]) => `${k}=${v}`).join('\n')}
|
||||
@@ -1782,14 +1782,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<Button type="button" variant="outline" onClick={resetMcpForm}>
|
||||
Cancel
|
||||
{t('mcpForm.actions.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={mcpLoading}
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={mcpLoading}
|
||||
className="bg-purple-600 hover:bg-purple-700 disabled:opacity-50"
|
||||
>
|
||||
{mcpLoading ? 'Saving...' : (editingMcpServer ? 'Update Server' : 'Add Server')}
|
||||
{mcpLoading ? t('mcpForm.actions.saving') : (editingMcpServer ? t('mcpForm.actions.updateServer') : t('mcpForm.actions.addServer'))}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1803,7 +1803,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<div className="bg-background border border-border rounded-lg w-full max-w-lg max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
{editingCodexMcpServer ? 'Edit MCP Server' : 'Add MCP Server'}
|
||||
{editingCodexMcpServer ? t('mcpForm.title.edit') : t('mcpForm.title.add')}
|
||||
</h3>
|
||||
<Button variant="ghost" size="sm" onClick={resetCodexMcpForm}>
|
||||
<X className="w-4 h-4" />
|
||||
@@ -1813,19 +1813,19 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<form onSubmit={handleCodexMcpSubmit} className="p-4 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Server Name *
|
||||
{t('mcpForm.fields.serverName')} *
|
||||
</label>
|
||||
<Input
|
||||
value={codexMcpFormData.name}
|
||||
onChange={(e) => setCodexMcpFormData(prev => ({...prev, name: e.target.value}))}
|
||||
placeholder="my-mcp-server"
|
||||
placeholder={t('mcpForm.placeholders.serverName')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Command *
|
||||
{t('mcpForm.fields.command')} *
|
||||
</label>
|
||||
<Input
|
||||
value={codexMcpFormData.config?.command || ''}
|
||||
@@ -1840,7 +1840,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Arguments (one per line)
|
||||
{t('mcpForm.fields.arguments')}
|
||||
</label>
|
||||
<textarea
|
||||
value={(codexMcpFormData.config?.args || []).join('\n')}
|
||||
@@ -1856,7 +1856,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Environment Variables (KEY=VALUE, one per line)
|
||||
{t('mcpForm.fields.envVars')}
|
||||
</label>
|
||||
<textarea
|
||||
value={Object.entries(codexMcpFormData.config?.env || {}).map(([k, v]) => `${k}=${v}`).join('\n')}
|
||||
@@ -1881,14 +1881,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4 border-t border-border">
|
||||
<Button type="button" variant="outline" onClick={resetCodexMcpForm}>
|
||||
Cancel
|
||||
{t('mcpForm.actions.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={codexMcpLoading || !codexMcpFormData.name || !codexMcpFormData.config?.command}
|
||||
className="bg-green-600 hover:bg-green-700 text-white"
|
||||
>
|
||||
{codexMcpLoading ? 'Saving...' : (editingCodexMcpServer ? 'Update Server' : 'Add Server')}
|
||||
{codexMcpLoading ? t('mcpForm.actions.saving') : (editingCodexMcpServer ? t('mcpForm.actions.updateServer') : t('mcpForm.actions.addServer'))}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1919,7 +1919,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Settings saved successfully!
|
||||
{t('saveStatus.success')}
|
||||
</div>
|
||||
)}
|
||||
{saveStatus === 'error' && (
|
||||
@@ -1927,31 +1927,31 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Failed to save settings
|
||||
{t('saveStatus.error')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3 order-1 sm:order-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
disabled={isSaving}
|
||||
className="flex-1 sm:flex-none h-10 touch-manipulation"
|
||||
>
|
||||
Cancel
|
||||
{t('footerActions.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={saveSettings}
|
||||
<Button
|
||||
onClick={saveSettings}
|
||||
disabled={isSaving}
|
||||
className="flex-1 sm:flex-none h-10 bg-blue-600 hover:bg-blue-700 disabled:opacity-50 touch-manipulation"
|
||||
>
|
||||
{isSaving ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 animate-spin rounded-full border-2 border-white border-t-transparent" />
|
||||
Saving...
|
||||
{t('saveStatus.saving')}
|
||||
</div>
|
||||
) : (
|
||||
'Save Settings'
|
||||
t('footerActions.save')
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Zap } from 'lucide-react';
|
||||
import { useTasksSettings } from '../contexts/TasksSettingsContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function TasksSettings() {
|
||||
const { t } = useTranslation('settings');
|
||||
const {
|
||||
tasksEnabled,
|
||||
setTasksEnabled,
|
||||
@@ -16,7 +18,7 @@ function TasksSettings() {
|
||||
<div 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-center gap-3">
|
||||
<div className="animate-spin w-5 h-5 border-2 border-blue-600 border-t-transparent rounded-full"></div>
|
||||
<span className="text-sm text-muted-foreground">Checking TaskMaster installation...</span>
|
||||
<span className="text-sm text-muted-foreground">{t('tasks.checking')}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -32,13 +34,13 @@ function TasksSettings() {
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-orange-900 dark:text-orange-100 mb-2">
|
||||
TaskMaster AI CLI Not Installed
|
||||
{t('tasks.notInstalled.title')}
|
||||
</div>
|
||||
<div className="text-sm text-orange-800 dark:text-orange-200 space-y-3">
|
||||
<p>TaskMaster CLI is required to use task management features. Install it to get started:</p>
|
||||
<p>{t('tasks.notInstalled.description')}</p>
|
||||
|
||||
<div className="bg-orange-100 dark:bg-orange-900/50 rounded-lg p-3 font-mono text-sm">
|
||||
<code>npm install -g task-master-ai</code>
|
||||
<code>{t('tasks.notInstalled.installCommand')}</code>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -51,7 +53,7 @@ function TasksSettings() {
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
View on GitHub
|
||||
{t('tasks.notInstalled.viewOnGitHub')}
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
@@ -59,11 +61,11 @@ function TasksSettings() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="font-medium">After installation:</p>
|
||||
<p className="font-medium">{t('tasks.notInstalled.afterInstallation')}</p>
|
||||
<ol className="list-decimal list-inside space-y-1 text-xs">
|
||||
<li>Restart this application</li>
|
||||
<li>TaskMaster features will automatically become available</li>
|
||||
<li>Use <code className="bg-orange-100 dark:bg-orange-800 px-1 rounded">task-master init</code> in your project directory</li>
|
||||
<li>{t('tasks.notInstalled.steps.restart')}</li>
|
||||
<li>{t('tasks.notInstalled.steps.autoAvailable')}</li>
|
||||
<li>Use <code className="bg-orange-100 dark:bg-orange-800 px-1 rounded">task-master init</code> {t('tasks.notInstalled.steps.initCommand')}</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,10 +81,10 @@ function TasksSettings() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-foreground">
|
||||
Enable TaskMaster Integration
|
||||
{t('tasks.settings.enableLabel')}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground mt-1">
|
||||
Show TaskMaster tasks, banners, and sidebar indicators across the interface
|
||||
{t('tasks.settings.enableDescription')}
|
||||
</div>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { LogIn } from 'lucide-react';
|
||||
import ClaudeLogo from '../ClaudeLogo';
|
||||
import CursorLogo from '../CursorLogo';
|
||||
import CodexLogo from '../CodexLogo';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const agentConfig = {
|
||||
claude: {
|
||||
@@ -39,6 +40,7 @@ const agentConfig = {
|
||||
};
|
||||
|
||||
export default function AccountContent({ agent, authStatus, onLogin }) {
|
||||
const { t } = useTranslation('settings');
|
||||
const config = agentConfig[agent];
|
||||
const { Logo } = config;
|
||||
|
||||
@@ -47,8 +49,8 @@ export default function AccountContent({ agent, authStatus, onLogin }) {
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Logo className="w-6 h-6" />
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-foreground">{config.name} Account</h3>
|
||||
<p className="text-sm text-muted-foreground">{config.description}</p>
|
||||
<h3 className="text-lg font-medium text-foreground">{config.name}</h3>
|
||||
<p className="text-sm text-muted-foreground">{t(`agents.account.${agent}.description`)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -58,30 +60,30 @@ export default function AccountContent({ agent, authStatus, onLogin }) {
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1">
|
||||
<div className={`font-medium ${config.textClass}`}>
|
||||
Connection Status
|
||||
{t('agents.connectionStatus')}
|
||||
</div>
|
||||
<div className={`text-sm ${config.subtextClass}`}>
|
||||
{authStatus?.loading ? (
|
||||
'Checking authentication status...'
|
||||
t('agents.authStatus.checkingAuth')
|
||||
) : authStatus?.authenticated ? (
|
||||
`Logged in as ${authStatus.email || 'authenticated user'}`
|
||||
t('agents.authStatus.loggedInAs', { email: authStatus.email || t('agents.authStatus.authenticatedUser') })
|
||||
) : (
|
||||
'Not connected'
|
||||
t('agents.authStatus.notConnected')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{authStatus?.loading ? (
|
||||
<Badge variant="secondary" className="bg-gray-100 dark:bg-gray-800">
|
||||
Checking...
|
||||
{t('agents.authStatus.checking')}
|
||||
</Badge>
|
||||
) : authStatus?.authenticated ? (
|
||||
<Badge variant="success" className="bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300">
|
||||
Connected
|
||||
{t('agents.authStatus.connected')}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary" className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300">
|
||||
Disconnected
|
||||
{t('agents.authStatus.disconnected')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -91,12 +93,12 @@ export default function AccountContent({ agent, authStatus, onLogin }) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className={`font-medium ${config.textClass}`}>
|
||||
{authStatus?.authenticated ? 'Re-authenticate' : 'Login'}
|
||||
{authStatus?.authenticated ? t('agents.login.reAuthenticate') : t('agents.login.title')}
|
||||
</div>
|
||||
<div className={`text-sm ${config.subtextClass}`}>
|
||||
{authStatus?.authenticated
|
||||
? 'Sign in with a different account or refresh credentials'
|
||||
: `Sign in to your ${config.name} account to enable AI features`}
|
||||
? t('agents.login.reAuthDescription')
|
||||
: t('agents.login.description', { agent: config.name })}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@@ -105,7 +107,7 @@ export default function AccountContent({ agent, authStatus, onLogin }) {
|
||||
size="sm"
|
||||
>
|
||||
<LogIn className="w-4 h-4 mr-2" />
|
||||
{authStatus?.authenticated ? 'Re-login' : 'Login'}
|
||||
{authStatus?.authenticated ? t('agents.login.reLoginButton') : t('agents.login.button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,7 +115,7 @@ export default function AccountContent({ agent, authStatus, onLogin }) {
|
||||
{authStatus?.error && (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 pt-4">
|
||||
<div className="text-sm text-red-600 dark:text-red-400">
|
||||
Error: {authStatus.error}
|
||||
{t('agents.error', { error: authStatus.error })}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ClaudeLogo from '../ClaudeLogo';
|
||||
import CursorLogo from '../CursorLogo';
|
||||
import CodexLogo from '../CodexLogo';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const agentConfig = {
|
||||
claude: {
|
||||
@@ -42,6 +43,7 @@ const colorClasses = {
|
||||
};
|
||||
|
||||
export default function AgentListItem({ agentId, authStatus, isSelected, onClick, isMobile = false }) {
|
||||
const { t } = useTranslation('settings');
|
||||
const config = agentConfig[agentId];
|
||||
const colors = colorClasses[config.color];
|
||||
const { Logo } = config;
|
||||
@@ -84,18 +86,18 @@ export default function AgentListItem({ agentId, authStatus, isSelected, onClick
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground pl-6">
|
||||
{authStatus?.loading ? (
|
||||
<span className="text-gray-400">Checking...</span>
|
||||
<span className="text-gray-400">{t('agents.authStatus.checking')}</span>
|
||||
) : authStatus?.authenticated ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${colors.dot}`} />
|
||||
<span className="truncate max-w-[120px]" title={authStatus.email}>
|
||||
{authStatus.email || 'Connected'}
|
||||
{authStatus.email || t('agents.authStatus.connected')}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-gray-400" />
|
||||
<span>Not connected</span>
|
||||
<span>{t('agents.authStatus.notConnected')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ 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) {
|
||||
@@ -25,16 +26,17 @@ function ClaudeMcpServers({
|
||||
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">
|
||||
MCP Servers
|
||||
{t('mcpServers.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Model Context Protocol servers provide additional tools and data sources to Claude
|
||||
{t('mcpServers.description.claude')}
|
||||
</p>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
@@ -44,7 +46,7 @@ function ClaudeMcpServers({
|
||||
size="sm"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add MCP Server
|
||||
{t('mcpServers.addButton')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -60,19 +62,19 @@ function ClaudeMcpServers({
|
||||
{server.type}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{server.scope === 'local' ? 'local' : server.scope === 'user' ? 'user' : server.scope}
|
||||
{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>Command: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.command}</code></div>
|
||||
<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>URL: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.url}</code></div>
|
||||
<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>Args: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.args.join(' ')}</code></div>
|
||||
<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>
|
||||
|
||||
@@ -90,13 +92,13 @@ function ClaudeMcpServers({
|
||||
{/* 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">Tools ({serverTools[server.id].tools.length}):</div>
|
||||
<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">+{serverTools[server.id].tools.length - 5} more</span>
|
||||
<span className="text-xs opacity-75">{t('mcpServers.tools.more', { count: serverTools[server.id].tools.length - 5 })}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,7 +111,7 @@ function ClaudeMcpServers({
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-gray-600 hover:text-gray-700"
|
||||
title="Edit server"
|
||||
title={t('mcpServers.actions.edit')}
|
||||
>
|
||||
<Edit3 className="w-4 h-4" />
|
||||
</Button>
|
||||
@@ -118,7 +120,7 @@ function ClaudeMcpServers({
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-red-600 hover:text-red-700"
|
||||
title="Delete server"
|
||||
title={t('mcpServers.actions.delete')}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
@@ -128,7 +130,7 @@ function ClaudeMcpServers({
|
||||
))}
|
||||
{servers.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
No MCP servers configured
|
||||
{t('mcpServers.empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -138,16 +140,17 @@ function ClaudeMcpServers({
|
||||
|
||||
// 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">
|
||||
MCP Servers
|
||||
{t('mcpServers.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Model Context Protocol servers provide additional tools and data sources to Cursor
|
||||
{t('mcpServers.description.cursor')}
|
||||
</p>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
@@ -157,7 +160,7 @@ function CursorMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
size="sm"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add MCP Server
|
||||
{t('mcpServers.addButton')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -173,7 +176,7 @@ function CursorMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{server.config?.command && (
|
||||
<div>Command: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.command}</code></div>
|
||||
<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>
|
||||
@@ -183,6 +186,7 @@ function CursorMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-gray-600 hover:text-gray-700"
|
||||
title={t('mcpServers.actions.edit')}
|
||||
>
|
||||
<Edit3 className="w-4 h-4" />
|
||||
</Button>
|
||||
@@ -191,6 +195,7 @@ function CursorMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-red-600 hover:text-red-700"
|
||||
title={t('mcpServers.actions.delete')}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
@@ -200,7 +205,7 @@ function CursorMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
))}
|
||||
{servers.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
No MCP servers configured
|
||||
{t('mcpServers.empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -210,16 +215,17 @@ function CursorMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
|
||||
// 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">
|
||||
MCP Servers
|
||||
{t('mcpServers.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Model Context Protocol servers provide additional tools and data sources to Codex
|
||||
{t('mcpServers.description.codex')}
|
||||
</p>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
@@ -229,7 +235,7 @@ function CodexMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
size="sm"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add MCP Server
|
||||
{t('mcpServers.addButton')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -246,13 +252,13 @@ function CodexMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
{server.config?.command && (
|
||||
<div>Command: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.command}</code></div>
|
||||
<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>Args: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.args.join(' ')}</code></div>
|
||||
<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>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>{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>
|
||||
@@ -263,7 +269,7 @@ function CodexMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-gray-600 hover:text-gray-700"
|
||||
title="Edit server"
|
||||
title={t('mcpServers.actions.edit')}
|
||||
>
|
||||
<Edit3 className="w-4 h-4" />
|
||||
</Button>
|
||||
@@ -272,7 +278,7 @@ function CodexMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-red-600 hover:text-red-700"
|
||||
title="Delete server"
|
||||
title={t('mcpServers.actions.delete')}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
@@ -282,17 +288,16 @@ function CodexMcpServers({ servers, onAdd, onEdit, onDelete }) {
|
||||
))}
|
||||
{servers.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
No MCP servers configured
|
||||
{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">About Codex MCP</h4>
|
||||
<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">
|
||||
Codex supports stdio-based MCP servers. You can add servers that extend Codex's capabilities
|
||||
with additional tools and resources.
|
||||
{t('mcpServers.help.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button } from '../ui/button';
|
||||
import { Input } from '../ui/input';
|
||||
import { Shield, AlertTriangle, Plus, X } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// Common tool patterns for Claude
|
||||
const commonClaudeTools = [
|
||||
@@ -49,6 +50,7 @@ function ClaudePermissions({
|
||||
newDisallowedTool,
|
||||
setNewDisallowedTool,
|
||||
}) {
|
||||
const { t } = useTranslation('settings');
|
||||
const addAllowedTool = (tool) => {
|
||||
if (tool && !allowedTools.includes(tool)) {
|
||||
setAllowedTools([...allowedTools, tool]);
|
||||
@@ -78,7 +80,7 @@ function ClaudePermissions({
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertTriangle className="w-5 h-5 text-orange-500" />
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
Permission Settings
|
||||
{t('permissions.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg p-4">
|
||||
@@ -91,10 +93,10 @@ function ClaudePermissions({
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-orange-900 dark:text-orange-100">
|
||||
Skip permission prompts (use with caution)
|
||||
{t('permissions.skipPermissions.label')}
|
||||
</div>
|
||||
<div className="text-sm text-orange-700 dark:text-orange-300">
|
||||
Equivalent to --dangerously-skip-permissions flag
|
||||
{t('permissions.skipPermissions.claudeDescription')}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@@ -106,18 +108,18 @@ function ClaudePermissions({
|
||||
<div className="flex items-center gap-3">
|
||||
<Shield className="w-5 h-5 text-green-500" />
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
Allowed Tools
|
||||
{t('permissions.allowedTools.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Tools that are automatically allowed without prompting for permission
|
||||
{t('permissions.allowedTools.description')}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<Input
|
||||
value={newAllowedTool}
|
||||
onChange={(e) => setNewAllowedTool(e.target.value)}
|
||||
placeholder='e.g., "Bash(git log:*)" or "Write"'
|
||||
placeholder={t('permissions.allowedTools.placeholder')}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
@@ -133,14 +135,14 @@ function ClaudePermissions({
|
||||
className="h-10 px-4"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2 sm:mr-0" />
|
||||
<span className="sm:hidden">Add</span>
|
||||
<span className="sm:hidden">{t('permissions.actions.add')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Quick add buttons */}
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Quick add common tools:
|
||||
{t('permissions.allowedTools.quickAdd')}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{commonClaudeTools.map(tool => (
|
||||
@@ -176,7 +178,7 @@ function ClaudePermissions({
|
||||
))}
|
||||
{allowedTools.length === 0 && (
|
||||
<div className="text-center py-6 text-gray-500 dark:text-gray-400">
|
||||
No allowed tools configured
|
||||
{t('permissions.allowedTools.empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -187,18 +189,18 @@ function ClaudePermissions({
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertTriangle className="w-5 h-5 text-red-500" />
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
Blocked Tools
|
||||
{t('permissions.blockedTools.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Tools that are automatically blocked without prompting for permission
|
||||
{t('permissions.blockedTools.description')}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<Input
|
||||
value={newDisallowedTool}
|
||||
onChange={(e) => setNewDisallowedTool(e.target.value)}
|
||||
placeholder='e.g., "Bash(rm:*)"'
|
||||
placeholder={t('permissions.blockedTools.placeholder')}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
@@ -214,7 +216,7 @@ function ClaudePermissions({
|
||||
className="h-10 px-4"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2 sm:mr-0" />
|
||||
<span className="sm:hidden">Add</span>
|
||||
<span className="sm:hidden">{t('permissions.actions.add')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -236,7 +238,7 @@ function ClaudePermissions({
|
||||
))}
|
||||
{disallowedTools.length === 0 && (
|
||||
<div className="text-center py-6 text-gray-500 dark:text-gray-400">
|
||||
No blocked tools configured
|
||||
{t('permissions.blockedTools.empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -245,13 +247,13 @@ function ClaudePermissions({
|
||||
{/* Help Section */}
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||
<h4 className="font-medium text-blue-900 dark:text-blue-100 mb-2">
|
||||
Tool Pattern Examples:
|
||||
{t('permissions.toolExamples.title')}
|
||||
</h4>
|
||||
<ul className="text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
||||
<li><code className="bg-blue-100 dark:bg-blue-800 px-1 rounded">"Bash(git log:*)"</code> - Allow all git log commands</li>
|
||||
<li><code className="bg-blue-100 dark:bg-blue-800 px-1 rounded">"Bash(git diff:*)"</code> - Allow all git diff commands</li>
|
||||
<li><code className="bg-blue-100 dark:bg-blue-800 px-1 rounded">"Write"</code> - Allow all Write tool usage</li>
|
||||
<li><code className="bg-blue-100 dark:bg-blue-800 px-1 rounded">"Bash(rm:*)"</code> - Block all rm commands (dangerous)</li>
|
||||
<li><code className="bg-blue-100 dark:bg-blue-800 px-1 rounded">"Bash(git log:*)"</code> {t('permissions.toolExamples.bashGitLog')}</li>
|
||||
<li><code className="bg-blue-100 dark:bg-blue-800 px-1 rounded">"Bash(git diff:*)"</code> {t('permissions.toolExamples.bashGitDiff')}</li>
|
||||
<li><code className="bg-blue-100 dark:bg-blue-800 px-1 rounded">"Write"</code> {t('permissions.toolExamples.write')}</li>
|
||||
<li><code className="bg-blue-100 dark:bg-blue-800 px-1 rounded">"Bash(rm:*)"</code> {t('permissions.toolExamples.bashRm')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -271,6 +273,7 @@ function CursorPermissions({
|
||||
newDisallowedCommand,
|
||||
setNewDisallowedCommand,
|
||||
}) {
|
||||
const { t } = useTranslation('settings');
|
||||
const addAllowedCommand = (cmd) => {
|
||||
if (cmd && !allowedCommands.includes(cmd)) {
|
||||
setAllowedCommands([...allowedCommands, cmd]);
|
||||
@@ -300,7 +303,7 @@ function CursorPermissions({
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertTriangle className="w-5 h-5 text-orange-500" />
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
Permission Settings
|
||||
{t('permissions.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg p-4">
|
||||
@@ -313,10 +316,10 @@ function CursorPermissions({
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-orange-900 dark:text-orange-100">
|
||||
Skip permission prompts (use with caution)
|
||||
{t('permissions.skipPermissions.label')}
|
||||
</div>
|
||||
<div className="text-sm text-orange-700 dark:text-orange-300">
|
||||
Equivalent to -f flag in Cursor CLI
|
||||
{t('permissions.skipPermissions.cursorDescription')}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@@ -328,18 +331,18 @@ function CursorPermissions({
|
||||
<div className="flex items-center gap-3">
|
||||
<Shield className="w-5 h-5 text-green-500" />
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
Allowed Shell Commands
|
||||
{t('permissions.allowedCommands.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Shell commands that are automatically allowed without prompting
|
||||
{t('permissions.allowedCommands.description')}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<Input
|
||||
value={newAllowedCommand}
|
||||
onChange={(e) => setNewAllowedCommand(e.target.value)}
|
||||
placeholder='e.g., "Shell(ls)" or "Shell(git status)"'
|
||||
placeholder={t('permissions.allowedCommands.placeholder')}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
@@ -355,14 +358,14 @@ function CursorPermissions({
|
||||
className="h-10 px-4"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2 sm:mr-0" />
|
||||
<span className="sm:hidden">Add</span>
|
||||
<span className="sm:hidden">{t('permissions.actions.add')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Quick add buttons */}
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Quick add common commands:
|
||||
{t('permissions.allowedCommands.quickAdd')}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{commonCursorCommands.map(cmd => (
|
||||
@@ -398,7 +401,7 @@ function CursorPermissions({
|
||||
))}
|
||||
{allowedCommands.length === 0 && (
|
||||
<div className="text-center py-6 text-gray-500 dark:text-gray-400">
|
||||
No allowed commands configured
|
||||
{t('permissions.allowedCommands.empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -409,18 +412,18 @@ function CursorPermissions({
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertTriangle className="w-5 h-5 text-red-500" />
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
Blocked Shell Commands
|
||||
{t('permissions.blockedCommands.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Shell commands that are automatically blocked
|
||||
{t('permissions.blockedCommands.description')}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<Input
|
||||
value={newDisallowedCommand}
|
||||
onChange={(e) => setNewDisallowedCommand(e.target.value)}
|
||||
placeholder='e.g., "Shell(rm -rf)" or "Shell(sudo)"'
|
||||
placeholder={t('permissions.blockedCommands.placeholder')}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
@@ -436,7 +439,7 @@ function CursorPermissions({
|
||||
className="h-10 px-4"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2 sm:mr-0" />
|
||||
<span className="sm:hidden">Add</span>
|
||||
<span className="sm:hidden">{t('permissions.actions.add')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -458,7 +461,7 @@ function CursorPermissions({
|
||||
))}
|
||||
{disallowedCommands.length === 0 && (
|
||||
<div className="text-center py-6 text-gray-500 dark:text-gray-400">
|
||||
No blocked commands configured
|
||||
{t('permissions.blockedCommands.empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -467,13 +470,13 @@ function CursorPermissions({
|
||||
{/* Help Section */}
|
||||
<div className="bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800 rounded-lg p-4">
|
||||
<h4 className="font-medium text-purple-900 dark:text-purple-100 mb-2">
|
||||
Shell Command Examples:
|
||||
{t('permissions.shellExamples.title')}
|
||||
</h4>
|
||||
<ul className="text-sm text-purple-800 dark:text-purple-200 space-y-1">
|
||||
<li><code className="bg-purple-100 dark:bg-purple-800 px-1 rounded">"Shell(ls)"</code> - Allow ls command</li>
|
||||
<li><code className="bg-purple-100 dark:bg-purple-800 px-1 rounded">"Shell(git status)"</code> - Allow git status</li>
|
||||
<li><code className="bg-purple-100 dark:bg-purple-800 px-1 rounded">"Shell(npm install)"</code> - Allow npm install</li>
|
||||
<li><code className="bg-purple-100 dark:bg-purple-800 px-1 rounded">"Shell(rm -rf)"</code> - Block recursive delete</li>
|
||||
<li><code className="bg-purple-100 dark:bg-purple-800 px-1 rounded">"Shell(ls)"</code> {t('permissions.shellExamples.ls')}</li>
|
||||
<li><code className="bg-purple-100 dark:bg-purple-800 px-1 rounded">"Shell(git status)"</code> {t('permissions.shellExamples.gitStatus')}</li>
|
||||
<li><code className="bg-purple-100 dark:bg-purple-800 px-1 rounded">"Shell(npm install)"</code> {t('permissions.shellExamples.npmInstall')}</li>
|
||||
<li><code className="bg-purple-100 dark:bg-purple-800 px-1 rounded">"Shell(rm -rf)"</code> {t('permissions.shellExamples.rmRf')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -482,17 +485,18 @@ function CursorPermissions({
|
||||
|
||||
// Codex Permissions
|
||||
function CodexPermissions({ permissionMode, setPermissionMode }) {
|
||||
const { t } = useTranslation('settings');
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Shield className="w-5 h-5 text-green-500" />
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
Permission Mode
|
||||
{t('permissions.codex.permissionMode')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Controls how Codex handles file modifications and command execution
|
||||
{t('permissions.codex.description')}
|
||||
</p>
|
||||
|
||||
{/* Default Mode */}
|
||||
@@ -513,10 +517,9 @@ function CodexPermissions({ permissionMode, setPermissionMode }) {
|
||||
className="mt-1 w-4 h-4 text-green-600"
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-foreground">Default</div>
|
||||
<div className="font-medium text-foreground">{t('permissions.codex.modes.default.title')}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Only trusted commands (ls, cat, grep, git status, etc.) run automatically.
|
||||
Other commands are skipped. Can write to workspace.
|
||||
{t('permissions.codex.modes.default.description')}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@@ -540,10 +543,9 @@ function CodexPermissions({ permissionMode, setPermissionMode }) {
|
||||
className="mt-1 w-4 h-4 text-green-600"
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-green-900 dark:text-green-100">Accept Edits</div>
|
||||
<div className="font-medium text-green-900 dark:text-green-100">{t('permissions.codex.modes.acceptEdits.title')}</div>
|
||||
<div className="text-sm text-green-700 dark:text-green-300">
|
||||
All commands run automatically within the workspace.
|
||||
Full auto mode with sandboxed execution.
|
||||
{t('permissions.codex.modes.acceptEdits.description')}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@@ -568,12 +570,11 @@ function CodexPermissions({ permissionMode, setPermissionMode }) {
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-orange-900 dark:text-orange-100 flex items-center gap-2">
|
||||
Bypass Permissions
|
||||
{t('permissions.codex.modes.bypassPermissions.title')}
|
||||
<AlertTriangle className="w-4 h-4" />
|
||||
</div>
|
||||
<div className="text-sm text-orange-700 dark:text-orange-300">
|
||||
Full system access with no restrictions. All commands run automatically
|
||||
with full disk and network access. Use with caution.
|
||||
{t('permissions.codex.modes.bypassPermissions.description')}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@@ -582,13 +583,13 @@ function CodexPermissions({ permissionMode, setPermissionMode }) {
|
||||
{/* Technical Details */}
|
||||
<details className="text-sm">
|
||||
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
||||
Technical details
|
||||
{t('permissions.codex.technicalDetails')}
|
||||
</summary>
|
||||
<div className="mt-2 p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg text-xs text-muted-foreground space-y-2">
|
||||
<p><strong>Default:</strong> sandboxMode=workspace-write, approvalPolicy=untrusted. Trusted commands: cat, cd, grep, head, ls, pwd, tail, git status/log/diff/show, find (without -exec), etc.</p>
|
||||
<p><strong>Accept Edits:</strong> sandboxMode=workspace-write, approvalPolicy=never. All commands auto-execute within project directory.</p>
|
||||
<p><strong>Bypass Permissions:</strong> sandboxMode=danger-full-access, approvalPolicy=never. Full system access, use only in trusted environments.</p>
|
||||
<p className="text-xs opacity-75">You can override this per-session using the mode button in the chat interface.</p>
|
||||
<p><strong>{t('permissions.codex.modes.default.title')}:</strong> {t('permissions.codex.technicalInfo.default')}</p>
|
||||
<p><strong>{t('permissions.codex.modes.acceptEdits.title')}:</strong> {t('permissions.codex.technicalInfo.acceptEdits')}</p>
|
||||
<p><strong>{t('permissions.codex.modes.bypassPermissions.title')}:</strong> {t('permissions.codex.technicalInfo.bypassPermissions')}</p>
|
||||
<p className="text-xs opacity-75">{t('permissions.codex.technicalInfo.overrideNote')}</p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@@ -18,11 +18,13 @@ import enCommon from './locales/en/common.json';
|
||||
import enSettings from './locales/en/settings.json';
|
||||
import enAuth from './locales/en/auth.json';
|
||||
import enSidebar from './locales/en/sidebar.json';
|
||||
import enChat from './locales/en/chat.json';
|
||||
|
||||
import zhCommon from './locales/zh-CN/common.json';
|
||||
import zhSettings from './locales/zh-CN/settings.json';
|
||||
import zhAuth from './locales/zh-CN/auth.json';
|
||||
import zhSidebar from './locales/zh-CN/sidebar.json';
|
||||
import zhChat from './locales/zh-CN/chat.json';
|
||||
|
||||
// Import supported languages configuration
|
||||
import { languages } from './languages.js';
|
||||
@@ -53,12 +55,14 @@ i18n
|
||||
settings: enSettings,
|
||||
auth: enAuth,
|
||||
sidebar: enSidebar,
|
||||
chat: enChat,
|
||||
},
|
||||
'zh-CN': {
|
||||
common: zhCommon,
|
||||
settings: zhSettings,
|
||||
auth: zhAuth,
|
||||
sidebar: zhSidebar,
|
||||
chat: zhChat,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -72,7 +76,7 @@ i18n
|
||||
debug: import.meta.env.DEV,
|
||||
|
||||
// Namespaces - load only what's needed
|
||||
ns: ['common', 'settings', 'auth', 'sidebar'],
|
||||
ns: ['common', 'settings', 'auth', 'sidebar', 'chat'],
|
||||
defaultNS: 'common',
|
||||
|
||||
// Key separator for nested keys (default: '.')
|
||||
|
||||
111
src/i18n/locales/en/chat.json
Normal file
111
src/i18n/locales/en/chat.json
Normal file
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"codeBlock": {
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
"copyCode": "Copy code"
|
||||
},
|
||||
"messageTypes": {
|
||||
"user": "U",
|
||||
"error": "Error",
|
||||
"tool": "Tool",
|
||||
"claude": "Claude",
|
||||
"cursor": "Cursor",
|
||||
"codex": "Codex"
|
||||
},
|
||||
"tools": {
|
||||
"settings": "Tool Settings",
|
||||
"error": "Tool Error",
|
||||
"result": "Tool Result",
|
||||
"viewParams": "View input parameters",
|
||||
"viewRawParams": "View raw parameters",
|
||||
"viewDiff": "View edit diff for",
|
||||
"creatingFile": "Creating new file:",
|
||||
"updatingTodo": "Updating Todo List",
|
||||
"read": "Read",
|
||||
"readFile": "Read file",
|
||||
"updateTodo": "Update todo list",
|
||||
"readTodo": "Read todo list",
|
||||
"searchResults": "results"
|
||||
},
|
||||
"search": {
|
||||
"found": "Found {{count}} {{type}}",
|
||||
"file": "file",
|
||||
"files": "files",
|
||||
"pattern": "pattern:",
|
||||
"in": "in:"
|
||||
},
|
||||
"fileOperations": {
|
||||
"updated": "File updated successfully",
|
||||
"created": "File created successfully",
|
||||
"written": "File written successfully",
|
||||
"diff": "Diff",
|
||||
"newFile": "New File",
|
||||
"viewContent": "View file content",
|
||||
"viewFullOutput": "View full output ({{count}} chars)",
|
||||
"contentDisplayed": "The file content is displayed in the diff view above"
|
||||
},
|
||||
"interactive": {
|
||||
"title": "Interactive Prompt",
|
||||
"waiting": "Waiting for your response in the CLI",
|
||||
"instruction": "Please select an option in your terminal where Claude is running.",
|
||||
"selectedOption": "✓ Claude selected option {{number}}",
|
||||
"instructionDetail": "In the CLI, you would select this option interactively using arrow keys or by typing the number."
|
||||
},
|
||||
"thinking": {
|
||||
"title": "Thinking...",
|
||||
"emoji": "💭 Thinking..."
|
||||
},
|
||||
"json": {
|
||||
"response": "JSON Response"
|
||||
},
|
||||
"permissions": {
|
||||
"grant": "Grant permission for {{tool}}",
|
||||
"added": "Permission added",
|
||||
"addTo": "Adds <span class=\"font-mono\">{{entry}}</span> to Allowed Tools.",
|
||||
"retry": "Permission saved. Retry the request to use the tool.",
|
||||
"error": "Unable to update permissions. Please try again.",
|
||||
"openSettings": "Open settings"
|
||||
},
|
||||
"todo": {
|
||||
"updated": "Todo list has been updated successfully",
|
||||
"current": "Current Todo List"
|
||||
},
|
||||
"plan": {
|
||||
"viewPlan": "📋 View implementation plan",
|
||||
"title": "Implementation Plan"
|
||||
},
|
||||
"usageLimit": {
|
||||
"resetAt": "Claude usage limit reached. Your limit will reset at **{{time}} {{timezone}}** - {{date}}"
|
||||
},
|
||||
"codex": {
|
||||
"permissionMode": "Permission Mode",
|
||||
"modes": {
|
||||
"default": "Default Mode",
|
||||
"acceptEdits": "Accept Edits",
|
||||
"bypassPermissions": "Bypass Permissions",
|
||||
"plan": "Plan Mode"
|
||||
},
|
||||
"descriptions": {
|
||||
"default": "Only trusted commands (ls, cat, grep, git status, etc.) run automatically. Other commands are skipped. Can write to workspace.",
|
||||
"acceptEdits": "All commands run automatically within the workspace. Full auto mode with sandboxed execution.",
|
||||
"bypassPermissions": "Full system access with no restrictions. All commands run automatically with full disk and network access. Use with caution.",
|
||||
"plan": "Planning mode - no commands are executed"
|
||||
},
|
||||
"technicalDetails": "Technical details"
|
||||
},
|
||||
"input": {
|
||||
"placeholder": "Type / for commands, @ for files, or ask {{provider}} anything...",
|
||||
"placeholderDefault": "Type your message...",
|
||||
"disabled": "Input disabled",
|
||||
"attachFiles": "Attach files",
|
||||
"attachImages": "Attach images",
|
||||
"send": "Send",
|
||||
"stop": "Stop",
|
||||
"hintText": {
|
||||
"ctrlEnter": "Ctrl+Enter to send • Shift+Enter for new line • Tab to change modes • / for slash commands",
|
||||
"enter": "Enter to send • Shift+Enter for new line • Tab to change modes • / for slash commands"
|
||||
},
|
||||
"clickToChangeMode": "Click to change permission mode (or press Tab in input)",
|
||||
"showAllCommands": "Show all commands"
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
"appearance": "Appearance"
|
||||
},
|
||||
"account": {
|
||||
"title": "Account",
|
||||
"language": "Language",
|
||||
"languageLabel": "Display Language",
|
||||
"languageDescription": "Choose your preferred language for the interface",
|
||||
@@ -15,13 +16,6 @@
|
||||
"profile": "Profile",
|
||||
"changePassword": "Change Password"
|
||||
},
|
||||
"permissions": {
|
||||
"allowedTools": "Allowed Tools",
|
||||
"disallowedTools": "Disallowed Tools",
|
||||
"addTool": "Add Tool",
|
||||
"removeTool": "Remove Tool",
|
||||
"description": "Configure which tools Claude can use. Tools must be enabled here before Claude can access them."
|
||||
},
|
||||
"mcp": {
|
||||
"title": "MCP Servers",
|
||||
"addServer": "Add Server",
|
||||
@@ -71,5 +65,337 @@
|
||||
"autoScrollToBottom": "Auto-scroll to bottom",
|
||||
"sendByCtrlEnter": "Send by Ctrl+Enter",
|
||||
"sendByCtrlEnterDescription": "When enabled, pressing Ctrl+Enter will send the message instead of just Enter. This is useful for IME users to avoid accidental sends."
|
||||
},
|
||||
"mainTabs": {
|
||||
"agents": "Agents",
|
||||
"appearance": "Appearance",
|
||||
"git": "Git",
|
||||
"apiTokens": "API & Tokens",
|
||||
"tasks": "Tasks"
|
||||
},
|
||||
"appearanceSettings": {
|
||||
"darkMode": {
|
||||
"label": "Dark Mode",
|
||||
"description": "Toggle between light and dark themes"
|
||||
},
|
||||
"projectSorting": {
|
||||
"label": "Project Sorting",
|
||||
"description": "How projects are ordered in the sidebar",
|
||||
"alphabetical": "Alphabetical",
|
||||
"recentActivity": "Recent Activity"
|
||||
},
|
||||
"codeEditor": {
|
||||
"title": "Code Editor",
|
||||
"theme": {
|
||||
"label": "Editor Theme",
|
||||
"description": "Default theme for the code editor"
|
||||
},
|
||||
"wordWrap": {
|
||||
"label": "Word Wrap",
|
||||
"description": "Enable word wrapping by default in the editor"
|
||||
},
|
||||
"showMinimap": {
|
||||
"label": "Show Minimap",
|
||||
"description": "Display a minimap for easier navigation in diff view"
|
||||
},
|
||||
"lineNumbers": {
|
||||
"label": "Show Line Numbers",
|
||||
"description": "Display line numbers in the editor"
|
||||
},
|
||||
"fontSize": {
|
||||
"label": "Font Size",
|
||||
"description": "Editor font size in pixels"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpForm": {
|
||||
"title": {
|
||||
"add": "Add MCP Server",
|
||||
"edit": "Edit MCP Server"
|
||||
},
|
||||
"importMode": {
|
||||
"form": "Form Input",
|
||||
"json": "JSON Import"
|
||||
},
|
||||
"scope": {
|
||||
"label": "Scope",
|
||||
"userGlobal": "User (Global)",
|
||||
"projectLocal": "Project (Local)",
|
||||
"userDescription": "User scope: Available across all projects on your machine",
|
||||
"projectDescription": "Local scope: Only available in the selected project",
|
||||
"cannotChange": "Scope cannot be changed when editing an existing server"
|
||||
},
|
||||
"fields": {
|
||||
"serverName": "Server Name",
|
||||
"transportType": "Transport Type",
|
||||
"command": "Command",
|
||||
"arguments": "Arguments (one per line)",
|
||||
"jsonConfig": "JSON Configuration",
|
||||
"url": "URL",
|
||||
"envVars": "Environment Variables (KEY=value, one per line)",
|
||||
"headers": "Headers (KEY=value, one per line)",
|
||||
"selectProject": "Select a project..."
|
||||
},
|
||||
"placeholders": {
|
||||
"serverName": "my-server"
|
||||
},
|
||||
"validation": {
|
||||
"missingType": "Missing required field: type",
|
||||
"stdioRequiresCommand": "stdio type requires a command field",
|
||||
"httpRequiresUrl": "{{type}} type requires a url field",
|
||||
"invalidJson": "Invalid JSON format",
|
||||
"jsonHelp": "Paste your MCP server configuration in JSON format. Example formats:",
|
||||
"jsonExampleStdio": "• stdio: {\"type\":\"stdio\",\"command\":\"npx\",\"args\":[\"@upstash/context7-mcp\"]}",
|
||||
"jsonExampleHttp": "• http/sse: {\"type\":\"http\",\"url\":\"https://api.example.com/mcp\"}"
|
||||
},
|
||||
"configDetails": "Configuration Details (from {{configFile}})",
|
||||
"projectPath": "Path: {{path}}",
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"saving": "Saving...",
|
||||
"addServer": "Add Server",
|
||||
"updateServer": "Update Server"
|
||||
}
|
||||
},
|
||||
"saveStatus": {
|
||||
"success": "Settings saved successfully!",
|
||||
"error": "Failed to save settings",
|
||||
"saving": "Saving..."
|
||||
},
|
||||
"footerActions": {
|
||||
"save": "Save Settings",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"git": {
|
||||
"title": "Git Configuration",
|
||||
"description": "Configure your git identity for commits. These settings will be applied globally via <code />git config --global<code />",
|
||||
"name": {
|
||||
"label": "Git Name",
|
||||
"help": "Your name for git commits"
|
||||
},
|
||||
"email": {
|
||||
"label": "Git Email",
|
||||
"help": "Your email for git commits"
|
||||
},
|
||||
"actions": {
|
||||
"save": "Save Configuration",
|
||||
"saving": "Saving..."
|
||||
},
|
||||
"status": {
|
||||
"success": "Saved successfully"
|
||||
}
|
||||
},
|
||||
"apiKeys": {
|
||||
"title": "API Keys",
|
||||
"description": "Generate API keys to access the external API from other applications.",
|
||||
"newKey": {
|
||||
"alertTitle": "⚠️ Save Your API Key",
|
||||
"alertMessage": "This is the only time you'll see this key. Store it securely.",
|
||||
"iveSavedIt": "I've saved it"
|
||||
},
|
||||
"form": {
|
||||
"placeholder": "API Key Name (e.g., Production Server)",
|
||||
"createButton": "Create",
|
||||
"cancelButton": "Cancel"
|
||||
},
|
||||
"newButton": "New API Key",
|
||||
"empty": "No API keys created yet.",
|
||||
"list": {
|
||||
"created": "Created:",
|
||||
"lastUsed": "Last used:"
|
||||
},
|
||||
"confirmDelete": "Are you sure you want to delete this API key?",
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive"
|
||||
},
|
||||
"github": {
|
||||
"title": "GitHub Tokens",
|
||||
"description": "Add GitHub Personal Access Tokens to clone private repositories via the external API.",
|
||||
"descriptionAlt": "Add GitHub Personal Access Tokens to clone private repositories. You can also pass tokens directly in API requests without storing them.",
|
||||
"addButton": "Add Token",
|
||||
"form": {
|
||||
"namePlaceholder": "Token Name (e.g., Personal Repos)",
|
||||
"tokenPlaceholder": "GitHub Personal Access Token (ghp_...)",
|
||||
"descriptionPlaceholder": "Description (optional)",
|
||||
"addButton": "Add Token",
|
||||
"cancelButton": "Cancel",
|
||||
"howToCreate": "How to create a GitHub Personal Access Token →"
|
||||
},
|
||||
"empty": "No GitHub tokens added yet.",
|
||||
"added": "Added:",
|
||||
"confirmDelete": "Are you sure you want to delete this GitHub token?"
|
||||
},
|
||||
"apiDocsLink": "API Documentation",
|
||||
"documentation": {
|
||||
"title": "External API Documentation",
|
||||
"description": "Learn how to use the external API to trigger Claude/Cursor sessions from your applications.",
|
||||
"viewLink": "View API Documentation →"
|
||||
},
|
||||
"loading": "Loading...",
|
||||
"version": {
|
||||
"updateAvailable": "Update available: v{{version}}"
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"checking": "Checking TaskMaster installation...",
|
||||
"notInstalled": {
|
||||
"title": "TaskMaster AI CLI Not Installed",
|
||||
"description": "TaskMaster CLI is required to use task management features. Install it to get started:",
|
||||
"installCommand": "npm install -g task-master-ai",
|
||||
"viewOnGitHub": "View on GitHub",
|
||||
"afterInstallation": "After installation:",
|
||||
"steps": {
|
||||
"restart": "Restart this application",
|
||||
"autoAvailable": "TaskMaster features will automatically become available",
|
||||
"initCommand": "Use task-master init in your project directory"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"enableLabel": "Enable TaskMaster Integration",
|
||||
"enableDescription": "Show TaskMaster tasks, banners, and sidebar indicators across the interface"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"authStatus": {
|
||||
"checking": "Checking...",
|
||||
"connected": "Connected",
|
||||
"notConnected": "Not connected",
|
||||
"disconnected": "Disconnected",
|
||||
"checkingAuth": "Checking authentication status...",
|
||||
"loggedInAs": "Logged in as {{email}}",
|
||||
"authenticatedUser": "authenticated user"
|
||||
},
|
||||
"account": {
|
||||
"claude": {
|
||||
"description": "Anthropic Claude AI assistant"
|
||||
},
|
||||
"cursor": {
|
||||
"description": "Cursor AI-powered code editor"
|
||||
},
|
||||
"codex": {
|
||||
"description": "OpenAI Codex AI assistant"
|
||||
}
|
||||
},
|
||||
"connectionStatus": "Connection Status",
|
||||
"login": {
|
||||
"title": "Login",
|
||||
"reAuthenticate": "Re-authenticate",
|
||||
"description": "Sign in to your {{agent}} account to enable AI features",
|
||||
"reAuthDescription": "Sign in with a different account or refresh credentials",
|
||||
"button": "Login",
|
||||
"reLoginButton": "Re-login"
|
||||
},
|
||||
"error": "Error: {{error}}"
|
||||
},
|
||||
"permissions": {
|
||||
"title": "Permission Settings",
|
||||
"skipPermissions": {
|
||||
"label": "Skip permission prompts (use with caution)",
|
||||
"claudeDescription": "Equivalent to --dangerously-skip-permissions flag",
|
||||
"cursorDescription": "Equivalent to -f flag in Cursor CLI"
|
||||
},
|
||||
"allowedTools": {
|
||||
"title": "Allowed Tools",
|
||||
"description": "Tools that are automatically allowed without prompting for permission",
|
||||
"placeholder": "e.g., \"Bash(git log:*)\" or \"Write\"",
|
||||
"quickAdd": "Quick add common tools:",
|
||||
"empty": "No allowed tools configured"
|
||||
},
|
||||
"blockedTools": {
|
||||
"title": "Blocked Tools",
|
||||
"description": "Tools that are automatically blocked without prompting for permission",
|
||||
"placeholder": "e.g., \"Bash(rm:*)\"",
|
||||
"empty": "No blocked tools configured"
|
||||
},
|
||||
"allowedCommands": {
|
||||
"title": "Allowed Shell Commands",
|
||||
"description": "Shell commands that are automatically allowed without prompting",
|
||||
"placeholder": "e.g., \"Shell(ls)\" or \"Shell(git status)\"",
|
||||
"quickAdd": "Quick add common commands:",
|
||||
"empty": "No allowed commands configured"
|
||||
},
|
||||
"blockedCommands": {
|
||||
"title": "Blocked Shell Commands",
|
||||
"description": "Shell commands that are automatically blocked",
|
||||
"placeholder": "e.g., \"Shell(rm -rf)\" or \"Shell(sudo)\"",
|
||||
"empty": "No blocked commands configured"
|
||||
},
|
||||
"toolExamples": {
|
||||
"title": "Tool Pattern Examples:",
|
||||
"bashGitLog": "- Allow all git log commands",
|
||||
"bashGitDiff": "- Allow all git diff commands",
|
||||
"write": "- Allow all Write tool usage",
|
||||
"bashRm": "- Block all rm commands (dangerous)"
|
||||
},
|
||||
"shellExamples": {
|
||||
"title": "Shell Command Examples:",
|
||||
"ls": "- Allow ls command",
|
||||
"gitStatus": "- Allow git status",
|
||||
"npmInstall": "- Allow npm install",
|
||||
"rmRf": "- Block recursive delete"
|
||||
},
|
||||
"codex": {
|
||||
"permissionMode": "Permission Mode",
|
||||
"description": "Controls how Codex handles file modifications and command execution",
|
||||
"modes": {
|
||||
"default": {
|
||||
"title": "Default",
|
||||
"description": "Only trusted commands (ls, cat, grep, git status, etc.) run automatically. Other commands are skipped. Can write to workspace."
|
||||
},
|
||||
"acceptEdits": {
|
||||
"title": "Accept Edits",
|
||||
"description": "All commands run automatically within the workspace. Full auto mode with sandboxed execution."
|
||||
},
|
||||
"bypassPermissions": {
|
||||
"title": "Bypass Permissions",
|
||||
"description": "Full system access with no restrictions. All commands run automatically with full disk and network access. Use with caution."
|
||||
}
|
||||
},
|
||||
"technicalDetails": "Technical details",
|
||||
"technicalInfo": {
|
||||
"default": "sandboxMode=workspace-write, approvalPolicy=untrusted. Trusted commands: cat, cd, grep, head, ls, pwd, tail, git status/log/diff/show, find (without -exec), etc.",
|
||||
"acceptEdits": "sandboxMode=workspace-write, approvalPolicy=never. All commands auto-execute within project directory.",
|
||||
"bypassPermissions": "sandboxMode=danger-full-access, approvalPolicy=never. Full system access, use only in trusted environments.",
|
||||
"overrideNote": "You can override this per-session using the mode button in the chat interface."
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"add": "Add"
|
||||
}
|
||||
},
|
||||
"mcpServers": {
|
||||
"title": "MCP Servers",
|
||||
"description": {
|
||||
"claude": "Model Context Protocol servers provide additional tools and data sources to Claude",
|
||||
"cursor": "Model Context Protocol servers provide additional tools and data sources to Cursor",
|
||||
"codex": "Model Context Protocol servers provide additional tools and data sources to Codex"
|
||||
},
|
||||
"addButton": "Add MCP Server",
|
||||
"empty": "No MCP servers configured",
|
||||
"serverType": "Type",
|
||||
"scope": {
|
||||
"local": "local",
|
||||
"user": "user"
|
||||
},
|
||||
"config": {
|
||||
"command": "Command",
|
||||
"url": "URL",
|
||||
"args": "Args",
|
||||
"environment": "Environment"
|
||||
},
|
||||
"tools": {
|
||||
"title": "Tools",
|
||||
"count": "({{count}}):",
|
||||
"more": "+{{count}} more"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "Edit server",
|
||||
"delete": "Delete server"
|
||||
},
|
||||
"help": {
|
||||
"title": "About Codex MCP",
|
||||
"description": "Codex supports stdio-based MCP servers. You can add servers that extend Codex's capabilities with additional tools and resources."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
111
src/i18n/locales/zh-CN/chat.json
Normal file
111
src/i18n/locales/zh-CN/chat.json
Normal file
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"codeBlock": {
|
||||
"copy": "复制",
|
||||
"copied": "已复制",
|
||||
"copyCode": "复制代码"
|
||||
},
|
||||
"messageTypes": {
|
||||
"user": "U",
|
||||
"error": "错误",
|
||||
"tool": "工具",
|
||||
"claude": "Claude",
|
||||
"cursor": "Cursor",
|
||||
"codex": "Codex"
|
||||
},
|
||||
"tools": {
|
||||
"settings": "工具设置",
|
||||
"error": "工具错误",
|
||||
"result": "工具结果",
|
||||
"viewParams": "查看输入参数",
|
||||
"viewRawParams": "查看原始参数",
|
||||
"viewDiff": "查看编辑差异",
|
||||
"creatingFile": "创建新文件:",
|
||||
"updatingTodo": "更新待办事项",
|
||||
"read": "读取",
|
||||
"readFile": "读取文件",
|
||||
"updateTodo": "更新待办列表",
|
||||
"readTodo": "读取待办列表",
|
||||
"searchResults": "结果"
|
||||
},
|
||||
"search": {
|
||||
"found": "找到 {{count}} 个{{type}}",
|
||||
"file": "文件",
|
||||
"files": "文件",
|
||||
"pattern": "模式:",
|
||||
"in": "在:"
|
||||
},
|
||||
"fileOperations": {
|
||||
"updated": "文件更新成功",
|
||||
"created": "文件创建成功",
|
||||
"written": "文件写入成功",
|
||||
"diff": "差异",
|
||||
"newFile": "新文件",
|
||||
"viewContent": "查看文件内容",
|
||||
"viewFullOutput": "查看完整输出({{count}} 个字符)",
|
||||
"contentDisplayed": "文件内容显示在上面的差异视图中"
|
||||
},
|
||||
"interactive": {
|
||||
"title": "交互式提示",
|
||||
"waiting": "等待您在 CLI 中响应",
|
||||
"instruction": "请在 Claude 运行的终端中选择一个选项。",
|
||||
"selectedOption": "✓ Claude 选择了选项 {{number}}",
|
||||
"instructionDetail": "在 CLI 中,您可以使用方向键或输入数字来交互式地选择此选项。"
|
||||
},
|
||||
"thinking": {
|
||||
"title": "思考中...",
|
||||
"emoji": "💭 思考中..."
|
||||
},
|
||||
"json": {
|
||||
"response": "JSON 响应"
|
||||
},
|
||||
"permissions": {
|
||||
"grant": "授予 {{tool}} 权限",
|
||||
"added": "权限已添加",
|
||||
"addTo": "将 <span class=\"font-mono\">{{entry}}</span> 添加到允许的工具。",
|
||||
"retry": "权限已保存。重试请求以使用该工具。",
|
||||
"error": "无法更新权限。请重试。",
|
||||
"openSettings": "打开设置"
|
||||
},
|
||||
"todo": {
|
||||
"updated": "待办列表已成功更新",
|
||||
"current": "当前待办列表"
|
||||
},
|
||||
"plan": {
|
||||
"viewPlan": "📋 查看实施计划",
|
||||
"title": "实施计划"
|
||||
},
|
||||
"usageLimit": {
|
||||
"resetAt": "Claude 使用限制已达到。您的限制将在 **{{time}} {{timezone}}** - {{date}} 重置"
|
||||
},
|
||||
"codex": {
|
||||
"permissionMode": "权限模式",
|
||||
"modes": {
|
||||
"default": "默认模式",
|
||||
"acceptEdits": "编辑模式",
|
||||
"bypassPermissions": "无限制模式",
|
||||
"plan": "计划模式"
|
||||
},
|
||||
"descriptions": {
|
||||
"default": "只有受信任的命令(ls、cat、grep、git status 等)自动运行。其他命令将被跳过。可以写入工作区。",
|
||||
"acceptEdits": "工作区内的所有命令自动运行。完全自动模式,具有沙盒执行功能。",
|
||||
"bypassPermissions": "完全的系统访问,无限制。所有命令自动运行,具有完整的磁盘和网络访问权限。请谨慎使用。",
|
||||
"plan": "计划模式 - 不执行任何命令"
|
||||
},
|
||||
"technicalDetails": "技术细节"
|
||||
},
|
||||
"input": {
|
||||
"placeholder": "输入 / 调用命令,@ 选择文件,或向 {{provider}} 提问...",
|
||||
"placeholderDefault": "输入您的消息...",
|
||||
"disabled": "输入已禁用",
|
||||
"attachFiles": "附加文件",
|
||||
"attachImages": "附加图片",
|
||||
"send": "发送",
|
||||
"stop": "停止",
|
||||
"hintText": {
|
||||
"ctrlEnter": "Ctrl+Enter 发送 • Shift+Enter 换行 • Tab 切换模式 • / 斜杠命令",
|
||||
"enter": "Enter 发送 • Shift+Enter 换行 • Tab 切换模式 • / 斜杠命令"
|
||||
},
|
||||
"clickToChangeMode": "点击更改权限模式(或在输入框中按 Tab)",
|
||||
"showAllCommands": "显示所有命令"
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
"appearance": "外观"
|
||||
},
|
||||
"account": {
|
||||
"title": "账户",
|
||||
"language": "语言",
|
||||
"languageLabel": "显示语言",
|
||||
"languageDescription": "选择您偏好的界面语言",
|
||||
@@ -15,13 +16,6 @@
|
||||
"profile": "个人资料",
|
||||
"changePassword": "修改密码"
|
||||
},
|
||||
"permissions": {
|
||||
"allowedTools": "允许的工具",
|
||||
"disallowedTools": "禁止的工具",
|
||||
"addTool": "添加工具",
|
||||
"removeTool": "移除工具",
|
||||
"description": "配置 Claude 可以使用的工具。工具必须在此处启用后,Claude 才能访问它们。"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "MCP 服务器",
|
||||
"addServer": "添加服务器",
|
||||
@@ -71,5 +65,337 @@
|
||||
"autoScrollToBottom": "自动滚动到底部",
|
||||
"sendByCtrlEnter": "使用 Ctrl+Enter 发送",
|
||||
"sendByCtrlEnterDescription": "启用后,按 Ctrl+Enter 发送消息,而不是仅按 Enter。这对于使用输入法的用户可以避免意外发送。"
|
||||
},
|
||||
"mainTabs": {
|
||||
"agents": "智能体",
|
||||
"appearance": "外观",
|
||||
"git": "Git",
|
||||
"apiTokens": "API 和令牌",
|
||||
"tasks": "任务"
|
||||
},
|
||||
"appearanceSettings": {
|
||||
"darkMode": {
|
||||
"label": "深色模式",
|
||||
"description": "切换浅色和深色主题"
|
||||
},
|
||||
"projectSorting": {
|
||||
"label": "项目排序",
|
||||
"description": "项目在侧边栏中的排列方式",
|
||||
"alphabetical": "按字母顺序",
|
||||
"recentActivity": "最近活动"
|
||||
},
|
||||
"codeEditor": {
|
||||
"title": "代码编辑器",
|
||||
"theme": {
|
||||
"label": "编辑器主题",
|
||||
"description": "代码编辑器的默认主题"
|
||||
},
|
||||
"wordWrap": {
|
||||
"label": "自动换行",
|
||||
"description": "在编辑器中默认启用自动换行"
|
||||
},
|
||||
"showMinimap": {
|
||||
"label": "显示缩略图",
|
||||
"description": "在差异视图中显示缩略图以便于导航"
|
||||
},
|
||||
"lineNumbers": {
|
||||
"label": "显示行号",
|
||||
"description": "在编辑器中显示行号"
|
||||
},
|
||||
"fontSize": {
|
||||
"label": "字体大小",
|
||||
"description": "编辑器字体大小(px)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpForm": {
|
||||
"title": {
|
||||
"add": "添加 MCP 服务器",
|
||||
"edit": "编辑 MCP 服务器"
|
||||
},
|
||||
"importMode": {
|
||||
"form": "表单输入",
|
||||
"json": "JSON 导入"
|
||||
},
|
||||
"scope": {
|
||||
"label": "范围",
|
||||
"userGlobal": "用户(全局)",
|
||||
"projectLocal": "项目(本地)",
|
||||
"userDescription": "用户范围:在您机器上的所有项目中可用",
|
||||
"projectDescription": "本地范围:仅在选定项目中可用",
|
||||
"cannotChange": "编辑现有服务器时无法更改范围"
|
||||
},
|
||||
"fields": {
|
||||
"serverName": "服务器名称",
|
||||
"transportType": "传输类型",
|
||||
"command": "命令",
|
||||
"arguments": "参数(每行一个)",
|
||||
"jsonConfig": "JSON 配置",
|
||||
"url": "URL",
|
||||
"envVars": "环境变量(KEY=值,每行一个)",
|
||||
"headers": "请求头(KEY=值,每行一个)",
|
||||
"selectProject": "选择项目..."
|
||||
},
|
||||
"placeholders": {
|
||||
"serverName": "我的服务"
|
||||
},
|
||||
"validation": {
|
||||
"missingType": "缺少必填字段:type",
|
||||
"stdioRequiresCommand": "stdio 类型需要 command 字段",
|
||||
"httpRequiresUrl": "{{type}} 类型需要 url 字段",
|
||||
"invalidJson": "无效的 JSON 格式",
|
||||
"jsonHelp": "粘贴您的 MCP 服务器配置(JSON 格式)。示例格式:",
|
||||
"jsonExampleStdio": "• stdio: {\"type\":\"stdio\",\"command\":\"npx\",\"args\":[\"@upstash/context7-mcp\"]}",
|
||||
"jsonExampleHttp": "• http/sse: {\"type\":\"http\",\"url\":\"https://api.example.com/mcp\"}"
|
||||
},
|
||||
"configDetails": "配置详细信息(来自 {{configFile}})",
|
||||
"projectPath": "路径:{{path}}",
|
||||
"actions": {
|
||||
"cancel": "取消",
|
||||
"saving": "保存中...",
|
||||
"addServer": "添加服务器",
|
||||
"updateServer": "更新服务器"
|
||||
}
|
||||
},
|
||||
"saveStatus": {
|
||||
"success": "设置保存成功!",
|
||||
"error": "保存设置失败",
|
||||
"saving": "保存中..."
|
||||
},
|
||||
"footerActions": {
|
||||
"save": "保存设置",
|
||||
"cancel": "取消"
|
||||
},
|
||||
"git": {
|
||||
"title": "Git 配置",
|
||||
"description": "配置您的 git 提交身份。这些设置将通过 <code />git config --global<code /> 全局应用",
|
||||
"name": {
|
||||
"label": "Git 名称",
|
||||
"help": "您的 git 提交名称"
|
||||
},
|
||||
"email": {
|
||||
"label": "Git 邮箱",
|
||||
"help": "您的 git 提交邮箱"
|
||||
},
|
||||
"actions": {
|
||||
"save": "保存配置",
|
||||
"saving": "保存中..."
|
||||
},
|
||||
"status": {
|
||||
"success": "保存成功"
|
||||
}
|
||||
},
|
||||
"apiKeys": {
|
||||
"title": "API 密钥",
|
||||
"description": "生成 API 密钥以从其他应用访问外部 API。",
|
||||
"newKey": {
|
||||
"alertTitle": "⚠️ 保存您的 API 密钥",
|
||||
"alertMessage": "这是您唯一一次看到此密钥。请妥善保存。",
|
||||
"iveSavedIt": "我已保存"
|
||||
},
|
||||
"form": {
|
||||
"placeholder": "API 密钥名称(例如:生产服务器)",
|
||||
"createButton": "创建",
|
||||
"cancelButton": "取消"
|
||||
},
|
||||
"newButton": "新建 API 密钥",
|
||||
"empty": "尚未创建 API 密钥。",
|
||||
"list": {
|
||||
"created": "创建时间:",
|
||||
"lastUsed": "最后使用:"
|
||||
},
|
||||
"confirmDelete": "确定要删除此 API 密钥吗?",
|
||||
"status": {
|
||||
"active": "激活",
|
||||
"inactive": "未激活"
|
||||
},
|
||||
"github": {
|
||||
"title": "GitHub 令牌",
|
||||
"description": "添加 GitHub 个人访问令牌以通过外部 API 克隆私有仓库。",
|
||||
"descriptionAlt": "添加 GitHub 个人访问令牌以克隆私有仓库。您也可以直接在 API 请求中传递令牌而无需存储。",
|
||||
"addButton": "添加令牌",
|
||||
"form": {
|
||||
"namePlaceholder": "令牌名称(例如:个人仓库)",
|
||||
"tokenPlaceholder": "GitHub 个人访问令牌(ghp_...)",
|
||||
"descriptionPlaceholder": "描述(可选)",
|
||||
"addButton": "添加令牌",
|
||||
"cancelButton": "取消",
|
||||
"howToCreate": "如何创建 GitHub 个人访问令牌 →"
|
||||
},
|
||||
"empty": "尚未添加 GitHub 令牌。",
|
||||
"added": "添加时间:",
|
||||
"confirmDelete": "确定要删除此 GitHub 令牌吗?"
|
||||
},
|
||||
"apiDocsLink": "API 文档",
|
||||
"documentation": {
|
||||
"title": "外部 API 文档",
|
||||
"description": "了解如何使用外部 API 从您的应用程序触发 Claude/Cursor 会话。",
|
||||
"viewLink": "查看 API 文档 →"
|
||||
},
|
||||
"loading": "加载中...",
|
||||
"version": {
|
||||
"updateAvailable": "有可用更新:v{{version}}"
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"checking": "正在检查 TaskMaster 安装...",
|
||||
"notInstalled": {
|
||||
"title": "未安装 TaskMaster AI CLI",
|
||||
"description": "需要 TaskMaster CLI 才能使用任务管理功能。安装它以开始使用:",
|
||||
"installCommand": "npm install -g task-master-ai",
|
||||
"viewOnGitHub": "在 GitHub 上查看",
|
||||
"afterInstallation": "安装后:",
|
||||
"steps": {
|
||||
"restart": "重启此应用程序",
|
||||
"autoAvailable": "TaskMaster 功能将自动可用",
|
||||
"initCommand": "在项目目录中使用 task-master init"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"enableLabel": "启用 TaskMaster 集成",
|
||||
"enableDescription": "在整个界面中显示 TaskMaster 任务、横幅和侧边栏指示器"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"authStatus": {
|
||||
"checking": "检查中...",
|
||||
"connected": "已连接",
|
||||
"notConnected": "未连接",
|
||||
"disconnected": "已断开",
|
||||
"checkingAuth": "正在检查认证状态...",
|
||||
"loggedInAs": "登录为 {{email}}",
|
||||
"authenticatedUser": "已认证用户"
|
||||
},
|
||||
"account": {
|
||||
"claude": {
|
||||
"description": "Anthropic Claude AI 助手"
|
||||
},
|
||||
"cursor": {
|
||||
"description": "Cursor AI 驱动的代码编辑器"
|
||||
},
|
||||
"codex": {
|
||||
"description": "OpenAI Codex AI 助手"
|
||||
}
|
||||
},
|
||||
"connectionStatus": "连接状态",
|
||||
"login": {
|
||||
"title": "登录",
|
||||
"reAuthenticate": "重新认证",
|
||||
"description": "登录您的 {{agent}} 账户以启用 AI 功能",
|
||||
"reAuthDescription": "使用其他账户登录或刷新凭据",
|
||||
"button": "登录",
|
||||
"reLoginButton": "重新登录"
|
||||
},
|
||||
"error": "错误:{{error}}"
|
||||
},
|
||||
"permissions": {
|
||||
"title": "权限设置",
|
||||
"skipPermissions": {
|
||||
"label": "跳过权限提示(请谨慎使用)",
|
||||
"claudeDescription": "等同于 --dangerously-skip-permissions 标志",
|
||||
"cursorDescription": "等同于 Cursor CLI 中的 -f 标志"
|
||||
},
|
||||
"allowedTools": {
|
||||
"title": "允许的工具",
|
||||
"description": "无需权限提示即可自动使用的工具",
|
||||
"placeholder": "例如:\"Bash(git log:*)\" 或 \"Write\"",
|
||||
"quickAdd": "快速添加常用工具:",
|
||||
"empty": "未配置允许的工具"
|
||||
},
|
||||
"blockedTools": {
|
||||
"title": "禁用的工具",
|
||||
"description": "无需权限提示即可自动禁用的工具",
|
||||
"placeholder": "例如:\"Bash(rm:*)\"",
|
||||
"empty": "未配置禁用的工具"
|
||||
},
|
||||
"allowedCommands": {
|
||||
"title": "允许的 Shell 命令",
|
||||
"description": "无需权限提示即可自动执行的 Shell 命令",
|
||||
"placeholder": "例如:\"Shell(ls)\" 或 \"Shell(git status)\"",
|
||||
"quickAdd": "快速添加常用命令:",
|
||||
"empty": "未配置允许的命令"
|
||||
},
|
||||
"blockedCommands": {
|
||||
"title": "阻止的 Shell 命令",
|
||||
"description": "自动阻止的 Shell 命令",
|
||||
"placeholder": "例如:\"Shell(rm -rf)\" 或 \"Shell(sudo)\"",
|
||||
"empty": "未配置阻止的命令"
|
||||
},
|
||||
"toolExamples": {
|
||||
"title": "工具模式示例:",
|
||||
"bashGitLog": "- 允许所有 git log 命令",
|
||||
"bashGitDiff": "- 允许所有 git diff 命令",
|
||||
"write": "- 允许所有 Write 工具使用",
|
||||
"bashRm": "- 阻止所有 rm 命令(危险)"
|
||||
},
|
||||
"shellExamples": {
|
||||
"title": "Shell 命令示例:",
|
||||
"ls": "- 允许 ls 命令",
|
||||
"gitStatus": "- 允许 git status",
|
||||
"npmInstall": "- 允许 npm install",
|
||||
"rmRf": "- 阻止递归删除"
|
||||
},
|
||||
"codex": {
|
||||
"permissionMode": "权限模式",
|
||||
"description": "控制 Codex 如何处理文件修改和命令执行",
|
||||
"modes": {
|
||||
"default": {
|
||||
"title": "默认",
|
||||
"description": "只有受信任的命令(ls、cat、grep、git status 等)会自动运行。其他命令将被跳过。可以写入工作区。"
|
||||
},
|
||||
"acceptEdits": {
|
||||
"title": "接受编辑",
|
||||
"description": "所有命令在工作区内自动运行。具有沙箱执行的全自动模式。"
|
||||
},
|
||||
"bypassPermissions": {
|
||||
"title": "绕过权限",
|
||||
"description": "完全系统访问,无任何限制。所有命令自动运行,具有完整的磁盘和网络访问权限。请谨慎使用。"
|
||||
}
|
||||
},
|
||||
"technicalDetails": "技术详情",
|
||||
"technicalInfo": {
|
||||
"default": "sandboxMode=workspace-write, approvalPolicy=untrusted。受信任的命令:cat、cd、grep、head、ls、pwd、tail、git status/log/diff/show、find(不带 -exec)等。",
|
||||
"acceptEdits": "sandboxMode=workspace-write, approvalPolicy=never。所有命令在项目目录内自动执行。",
|
||||
"bypassPermissions": "sandboxMode=danger-full-access, approvalPolicy=never。完全系统访问权限,仅在可信环境中使用。",
|
||||
"overrideNote": "您可以使用聊天界面中的模式按钮按会话覆盖此设置。"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"add": "添加"
|
||||
}
|
||||
},
|
||||
"mcpServers": {
|
||||
"title": "MCP 服务器",
|
||||
"description": {
|
||||
"claude": "Model Context Protocol 服务器为 Claude 提供额外的工具和数据源",
|
||||
"cursor": "Model Context Protocol 服务器为 Cursor 提供额外的工具和数据源",
|
||||
"codex": "Model Context Protocol 服务器为 Codex 提供额外的工具和数据源"
|
||||
},
|
||||
"addButton": "添加 MCP 服务器",
|
||||
"empty": "未配置 MCP 服务器",
|
||||
"serverType": "类型",
|
||||
"scope": {
|
||||
"local": "本地",
|
||||
"user": "用户"
|
||||
},
|
||||
"config": {
|
||||
"command": "命令",
|
||||
"url": "URL",
|
||||
"args": "参数",
|
||||
"environment": "环境变量"
|
||||
},
|
||||
"tools": {
|
||||
"title": "工具",
|
||||
"count": "({{count}}):",
|
||||
"more": "还有 {{count}} 个"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "编辑服务器",
|
||||
"delete": "删除服务器"
|
||||
},
|
||||
"help": {
|
||||
"title": "关于 Codex MCP",
|
||||
"description": "Codex 支持基于 stdio 的 MCP 服务器。您可以添加服务器,通过额外的工具和资源来扩展 Codex 的功能。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user