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:
YuanNiancai
2026-01-21 13:56:49 +08:00
parent 50f8c4ba72
commit 0517ee609e
15 changed files with 1214 additions and 311 deletions

View File

@@ -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>
)}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>