- Backend: add PATCH /api/cron/{id} handler (handle_api_cron_patch)
using update_shell_job_with_approval with approved=false; validates
job exists (404 on miss), accepts name/schedule/command patch fields
- Router: register PATCH on /api/cron/{id} alongside existing DELETE
- Frontend API: add patchCronJob(id, patch) calling PATCH /api/cron/{id}
- i18n: add cron.edit, cron.edit_modal_title, cron.edit_error,
cron.saving, cron.save keys to all 3 locales (zh, en, tr)
- UI: Edit (Pencil) button in Actions column opens a pre-populated modal
with the job's current name, schedule expression, and command;
submitting PATCHes the job and updates the table row in-place
Co-authored-by: WareWolf-MoonWall <chris.hengge@gmail.com>
1010 lines
37 KiB
TypeScript
1010 lines
37 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { getStatus } from './api';
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Translation dictionaries
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export type Locale = 'en' | 'zh' | 'tr';
|
||
|
||
const translations: Record<Locale, Record<string, string>> = {
|
||
zh: {
|
||
// Navigation
|
||
'nav.dashboard': '仪表盘',
|
||
'nav.agent': '智能体',
|
||
'nav.tools': '工具',
|
||
'nav.cron': '定时任务',
|
||
'nav.integrations': '集成',
|
||
'nav.memory': '记忆',
|
||
'nav.config': '配置',
|
||
'nav.cost': '成本追踪',
|
||
'nav.logs': '日志',
|
||
'nav.doctor': '诊断',
|
||
|
||
// Dashboard
|
||
'dashboard.title': '仪表盘',
|
||
'dashboard.provider': '提供商',
|
||
'dashboard.model': '模型',
|
||
'dashboard.uptime': '运行时间',
|
||
'dashboard.temperature': '温度',
|
||
'dashboard.gateway_port': '网关端口',
|
||
'dashboard.memory_backend': '记忆后端',
|
||
'dashboard.paired': '已配对',
|
||
'dashboard.channels': '频道',
|
||
'dashboard.health': '健康状态',
|
||
'dashboard.status': '状态',
|
||
'dashboard.overview': '概览',
|
||
'dashboard.system_info': '系统信息',
|
||
'dashboard.quick_actions': '快速操作',
|
||
|
||
// Agent / Chat
|
||
'agent.title': '智能体对话',
|
||
'agent.send': '发送',
|
||
'agent.placeholder': '输入消息...',
|
||
'agent.start_conversation': '发送消息开始对话',
|
||
'agent.type_message': '输入消息...',
|
||
'agent.connecting': '连接中...',
|
||
'agent.connected': '已连接',
|
||
'agent.disconnected': '已断开',
|
||
'agent.reconnecting': '重新连接中...',
|
||
'agent.thinking': '思考中...',
|
||
'agent.tool_call': '工具调用',
|
||
'agent.tool_result': '工具结果',
|
||
'agent.connection_error': '连接错误,正在尝试重连...',
|
||
'agent.tool_call_prefix': '[工具调用]',
|
||
'agent.tool_result_prefix': '[工具结果]',
|
||
'agent.error_prefix': '[错误]',
|
||
'agent.unknown_error': '未知错误',
|
||
'agent.send_error': '发送消息失败,请重试。',
|
||
'agent.copy_message': '复制消息',
|
||
'agent.connected_status': '已连接',
|
||
'agent.disconnected_status': '已断开',
|
||
|
||
// Tools
|
||
'tools.title': '可用工具',
|
||
'tools.name': '名称',
|
||
'tools.description': '描述',
|
||
'tools.parameters': '参数',
|
||
'tools.search': '搜索工具...',
|
||
'tools.empty': '暂无可用工具。',
|
||
'tools.count': '工具总数',
|
||
'tools.agent_tools': '智能体工具箱',
|
||
'tools.cli_tools': 'CLI 工具箱',
|
||
'tools.parameter_schema': '参数结构',
|
||
'tools.path': '路径',
|
||
'tools.version': '版本',
|
||
'tools.category': '类别',
|
||
'tools.load_error': '加载工具失败',
|
||
|
||
// Cron
|
||
'cron.title': '定时任务',
|
||
'cron.scheduled_tasks': '定时任务',
|
||
'cron.add': '添加任务',
|
||
'cron.add_job': '添加任务',
|
||
'cron.add_modal_title': '添加 Cron 任务',
|
||
'cron.delete': '删除',
|
||
'cron.enable': '启用',
|
||
'cron.disable': '禁用',
|
||
'cron.name': '名称',
|
||
'cron.name_optional': '名称(可选)',
|
||
'cron.command': '命令',
|
||
'cron.command_required': '命令',
|
||
'cron.schedule': '计划',
|
||
'cron.schedule_required': '计划',
|
||
'cron.next_run': '下次执行',
|
||
'cron.last_run': '上次执行',
|
||
'cron.last_status': '上次状态',
|
||
'cron.enabled': '已启用',
|
||
'cron.enabled_status': '启用',
|
||
'cron.disabled_status': '禁用',
|
||
'cron.empty': '暂无定时任务。',
|
||
'cron.confirm_delete': '确定要删除此任务吗?',
|
||
'cron.load_error': '加载定时任务失败',
|
||
'cron.validation_error': '计划和命令是必需的。',
|
||
'cron.add_error': '添加任务失败',
|
||
'cron.delete_error': '删除任务失败',
|
||
'cron.cancel': '取消',
|
||
'cron.adding': '添加中...',
|
||
'cron.id': 'ID',
|
||
'cron.actions': '操作',
|
||
'cron.loading_run_history': '加载运行历史...',
|
||
'cron.load_run_history_error': '加载运行历史失败',
|
||
'cron.no_runs': '暂无运行记录。',
|
||
'cron.recent_runs': '最近运行',
|
||
'cron.yes': '是',
|
||
'cron.no': '否',
|
||
'cron.edit': '编辑',
|
||
'cron.edit_modal_title': '编辑 Cron 任务',
|
||
'cron.edit_error': '更新任务失败',
|
||
'cron.saving': '保存中...',
|
||
'cron.save': '保存',
|
||
|
||
// Integrations
|
||
'integrations.title': '集成',
|
||
'integrations.available': '可用',
|
||
'integrations.active': '活跃',
|
||
'integrations.coming_soon': '即将推出',
|
||
'integrations.category': '类别',
|
||
'integrations.status': '状态',
|
||
'integrations.search': '搜索集成...',
|
||
'integrations.empty': '未找到集成。',
|
||
'integrations.activate': '激活',
|
||
'integrations.deactivate': '停用',
|
||
'integrations.load_error': '加载集成失败',
|
||
'integrations.status_active': '活跃',
|
||
'integrations.status_available': '可用',
|
||
'integrations.status_coming_soon': '即将推出',
|
||
|
||
// Memory
|
||
'memory.title': '记忆存储',
|
||
'memory.memory_title': '记忆',
|
||
'memory.search': '搜索记忆...',
|
||
'memory.search_placeholder': '搜索记忆条目...',
|
||
'memory.add': '存储记忆',
|
||
'memory.add_memory': '添加记忆',
|
||
'memory.add_modal_title': '添加记忆',
|
||
'memory.delete': '删除',
|
||
'memory.key': '键',
|
||
'memory.key_required': '键',
|
||
'memory.content': '内容',
|
||
'memory.content_required': '内容',
|
||
'memory.category': '类别',
|
||
'memory.category_optional': '类别(可选)',
|
||
'memory.timestamp': '时间戳',
|
||
'memory.session': '会话',
|
||
'memory.score': '分数',
|
||
'memory.empty': '未找到记忆条目。',
|
||
'memory.confirm_delete': '确定要删除此记忆条目吗?',
|
||
'memory.all_categories': '所有类别',
|
||
'memory.search_button': '搜索',
|
||
'memory.load_error': '加载记忆失败',
|
||
'memory.saving': '保存中...',
|
||
'memory.validation_error': '键和内容是必需的。',
|
||
'memory.store_error': '保存记忆失败',
|
||
'memory.delete_error': '删除记忆失败',
|
||
'memory.delete_confirm': '删除?',
|
||
'memory.yes': '是',
|
||
'memory.no': '否',
|
||
'memory.cancel': '取消',
|
||
|
||
// Config
|
||
'config.title': '配置',
|
||
'config.save': '保存',
|
||
'config.saving': '保存中...',
|
||
'config.reset': '重置',
|
||
'config.saved': '配置保存成功。',
|
||
'config.error': '配置保存失败。',
|
||
'config.loading': '加载配置中...',
|
||
'config.editor_placeholder': 'TOML 配置...',
|
||
'config.configuration_title': '配置',
|
||
'config.sensitive_title': '敏感字段已隐藏',
|
||
'config.sensitive_hint': 'API 密钥、令牌和密码已隐藏以保护安全。要更新已隐藏的字段,请将整个隐藏值替换为您的新值。',
|
||
'config.save_success': '配置保存成功。',
|
||
'config.save_error': '保存配置失败',
|
||
'config.toml_label': 'TOML 配置',
|
||
'config.lines': '行',
|
||
|
||
// Cost
|
||
'cost.title': '成本追踪',
|
||
'cost.session': '会话成本',
|
||
'cost.daily': '每日成本',
|
||
'cost.monthly': '每月成本',
|
||
'cost.total_tokens': '总 Tokens',
|
||
'cost.request_count': '请求数',
|
||
'cost.by_model': '按模型统计',
|
||
'cost.model': '模型',
|
||
'cost.tokens': 'Token',
|
||
'cost.requests': '请求',
|
||
'cost.usd': '成本(美元)',
|
||
'cost.load_error': '加载成本数据失败',
|
||
'cost.session_cost': '会话成本',
|
||
'cost.daily_cost': '每日成本',
|
||
'cost.monthly_cost': '每月成本',
|
||
'cost.total_requests': '总请求数',
|
||
'cost.token_statistics': 'Token 统计',
|
||
'cost.avg_tokens_per_request': '平均 Token / 请求',
|
||
'cost.cost_per_1k_tokens': '每 1K Token 成本',
|
||
'cost.model_breakdown': '模型细分',
|
||
'cost.no_model_data': '没有模型数据可用。',
|
||
'cost.cost': '成本',
|
||
'cost.share': '占比',
|
||
|
||
// Logs
|
||
'logs.title': '实时日志',
|
||
'logs.live_logs': '实时日志',
|
||
'logs.clear': '清除',
|
||
'logs.pause': '暂停',
|
||
'logs.resume': '继续',
|
||
'logs.filter': '筛选日志...',
|
||
'logs.filter_label': '筛选',
|
||
'logs.empty': '暂无日志条目。',
|
||
'logs.connected': '已连接',
|
||
'logs.disconnected': '已断开',
|
||
'logs.events': '事件',
|
||
'logs.jump_to_bottom': '跳转到底部',
|
||
'logs.paused_hint': '日志流已暂停。',
|
||
'logs.waiting_hint': '等待事件...',
|
||
|
||
// Doctor
|
||
'doctor.title': '系统诊断',
|
||
'doctor.diagnostics_title': '系统诊断',
|
||
'doctor.run': '运行诊断',
|
||
'doctor.run_diagnostics': '运行诊断',
|
||
'doctor.running': '正在运行诊断...',
|
||
'doctor.running_btn': '运行中...',
|
||
'doctor.running_desc': '正在运行诊断...',
|
||
'doctor.running_hint': '这可能需要几秒钟。',
|
||
'doctor.ok': '正常',
|
||
'doctor.warn': '警告',
|
||
'doctor.error': '错误',
|
||
'doctor.severity': '严重程度',
|
||
'doctor.category': '类别',
|
||
'doctor.message': '消息',
|
||
'doctor.empty': '尚未运行诊断。',
|
||
'doctor.summary': '诊断摘要',
|
||
'doctor.issues_found': '发现问题',
|
||
'doctor.warnings_summary': '警告',
|
||
'doctor.all_clear': '一切正常',
|
||
'doctor.system_diagnostics': '系统诊断',
|
||
'doctor.empty_hint': '点击"运行诊断"检查您的 ZeroClaw 安装。',
|
||
|
||
// Auth / Pairing
|
||
'auth.pair': '配对设备',
|
||
'auth.pairing_code': '配对码',
|
||
'auth.pair_button': '配对',
|
||
'auth.logout': '退出',
|
||
'auth.pairing_success': '配对成功!',
|
||
'auth.pairing_failed': '配对失败,请重试。',
|
||
'auth.enter_code': '请输入配对码以连接到智能体。',
|
||
|
||
// Common
|
||
'common.loading': '加载中...',
|
||
'common.error': '发生错误。',
|
||
'common.retry': '重试',
|
||
'common.cancel': '取消',
|
||
'common.confirm': '确认',
|
||
'common.save': '保存',
|
||
'common.delete': '删除',
|
||
'common.edit': '编辑',
|
||
'common.close': '关闭',
|
||
'common.yes': '是',
|
||
'common.no': '否',
|
||
'common.search': '搜索...',
|
||
'common.no_data': '暂无数据。',
|
||
'common.refresh': '刷新',
|
||
'common.back': '返回',
|
||
'common.actions': '操作',
|
||
'common.name': '名称',
|
||
'common.description': '描述',
|
||
'common.status': '状态',
|
||
'common.created': '创建时间',
|
||
'common.updated': '更新时间',
|
||
|
||
// Health
|
||
'health.title': '系统健康',
|
||
'health.component': '组件',
|
||
'health.status': '状态',
|
||
'health.last_ok': '上次正常',
|
||
'health.last_error': '上次错误',
|
||
'health.restart_count': '重启次数',
|
||
'health.pid': '进程 ID',
|
||
'health.uptime': '运行时间',
|
||
'health.updated_at': '最后更新',
|
||
|
||
// Dashboard specific labels
|
||
'dashboard.provider_model': '提供商 / 模型',
|
||
'dashboard.since_last_restart': '自上次重启',
|
||
'dashboard.paired_yes': '是',
|
||
'dashboard.paired_no': '否',
|
||
'dashboard.cost_overview': '成本概览',
|
||
'dashboard.active_channels': '活跃频道',
|
||
'dashboard.filter_active': '活跃',
|
||
'dashboard.filter_all': '全部',
|
||
'dashboard.no_active_channels': '没有活跃频道',
|
||
'dashboard.component_health': '组件健康',
|
||
'dashboard.load_error': '加载仪表盘失败',
|
||
'dashboard.session_label': '会话',
|
||
'dashboard.daily_label': '每日',
|
||
'dashboard.monthly_label': '每月',
|
||
'dashboard.total_tokens_label': '总 Tokens',
|
||
'dashboard.requests_label': '请求',
|
||
'dashboard.no_channels': '未配置频道',
|
||
'dashboard.active': '活跃',
|
||
'dashboard.inactive': '非活跃',
|
||
'dashboard.no_components': '没有组件报告',
|
||
'dashboard.restarts': '重启次数',
|
||
},
|
||
|
||
en: {
|
||
// Navigation
|
||
'nav.dashboard': 'Dashboard',
|
||
'nav.agent': 'Agent',
|
||
'nav.tools': 'Tools',
|
||
'nav.cron': 'Scheduled Jobs',
|
||
'nav.integrations': 'Integrations',
|
||
'nav.memory': 'Memory',
|
||
'nav.config': 'Configuration',
|
||
'nav.cost': 'Cost Tracker',
|
||
'nav.logs': 'Logs',
|
||
'nav.doctor': 'Doctor',
|
||
|
||
// Dashboard
|
||
'dashboard.title': 'Dashboard',
|
||
'dashboard.provider': 'Provider',
|
||
'dashboard.model': 'Model',
|
||
'dashboard.uptime': 'Uptime',
|
||
'dashboard.temperature': 'Temperature',
|
||
'dashboard.gateway_port': 'Gateway Port',
|
||
'dashboard.memory_backend': 'Memory Backend',
|
||
'dashboard.paired': 'Paired',
|
||
'dashboard.channels': 'Channels',
|
||
'dashboard.health': 'Health',
|
||
'dashboard.status': 'Status',
|
||
'dashboard.overview': 'Overview',
|
||
'dashboard.system_info': 'System Information',
|
||
'dashboard.quick_actions': 'Quick Actions',
|
||
|
||
// Agent / Chat
|
||
'agent.title': 'Agent Chat',
|
||
'agent.send': 'Send',
|
||
'agent.placeholder': 'Type a message...',
|
||
'agent.start_conversation': 'Send a message to start the conversation',
|
||
'agent.type_message': 'Type a message...',
|
||
'agent.connecting': 'Connecting...',
|
||
'agent.connected': 'Connected',
|
||
'agent.disconnected': 'Disconnected',
|
||
'agent.reconnecting': 'Reconnecting...',
|
||
'agent.thinking': 'Thinking...',
|
||
'agent.tool_call': 'Tool Call',
|
||
'agent.tool_result': 'Tool Result',
|
||
'agent.connection_error': 'Connection error. Attempting to reconnect...',
|
||
'agent.tool_call_prefix': '[Tool Call]',
|
||
'agent.tool_result_prefix': '[Tool Result]',
|
||
'agent.error_prefix': '[Error]',
|
||
'agent.unknown_error': 'Unknown error',
|
||
'agent.send_error': 'Failed to send message. Please try again.',
|
||
'agent.copy_message': 'Copy message',
|
||
'agent.connected_status': 'Connected',
|
||
'agent.disconnected_status': 'Disconnected',
|
||
|
||
// Tools
|
||
'tools.title': 'Available Tools',
|
||
'tools.name': 'Name',
|
||
'tools.description': 'Description',
|
||
'tools.parameters': 'Parameters',
|
||
'tools.search': 'Search tools...',
|
||
'tools.empty': 'No tools available.',
|
||
'tools.count': 'Total tools',
|
||
'tools.agent_tools': 'Agent Tools',
|
||
'tools.cli_tools': 'CLI Tools',
|
||
'tools.parameter_schema': 'Parameter Schema',
|
||
'tools.path': 'Path',
|
||
'tools.version': 'Version',
|
||
'tools.category': 'Category',
|
||
'tools.load_error': 'Failed to load tools',
|
||
|
||
// Cron
|
||
'cron.title': 'Scheduled Jobs',
|
||
'cron.scheduled_tasks': 'Scheduled Tasks',
|
||
'cron.add': 'Add Job',
|
||
'cron.add_job': 'Add Job',
|
||
'cron.add_modal_title': 'Add Cron Job',
|
||
'cron.delete': 'Delete',
|
||
'cron.enable': 'Enable',
|
||
'cron.disable': 'Disable',
|
||
'cron.name': 'Name',
|
||
'cron.name_optional': 'Name (optional)',
|
||
'cron.command': 'Command',
|
||
'cron.command_required': 'Command',
|
||
'cron.schedule': 'Schedule',
|
||
'cron.schedule_required': 'Schedule',
|
||
'cron.next_run': 'Next Run',
|
||
'cron.last_run': 'Last Run',
|
||
'cron.last_status': 'Last Status',
|
||
'cron.enabled': 'Enabled',
|
||
'cron.enabled_status': 'Enabled',
|
||
'cron.disabled_status': 'Disabled',
|
||
'cron.empty': 'No scheduled jobs.',
|
||
'cron.confirm_delete': 'Are you sure you want to delete this job?',
|
||
'cron.load_error': 'Failed to load cron jobs',
|
||
'cron.validation_error': 'Schedule and command are required.',
|
||
'cron.add_error': 'Failed to add job',
|
||
'cron.delete_error': 'Failed to delete job',
|
||
'cron.cancel': 'Cancel',
|
||
'cron.adding': 'Adding...',
|
||
'cron.id': 'ID',
|
||
'cron.actions': 'Actions',
|
||
'cron.loading_run_history': 'Loading run history...',
|
||
'cron.load_run_history_error': 'Failed to load run history',
|
||
'cron.no_runs': 'No runs recorded yet.',
|
||
'cron.recent_runs': 'Recent Runs',
|
||
'cron.yes': 'Yes',
|
||
'cron.no': 'No',
|
||
'cron.edit': 'Edit',
|
||
'cron.edit_modal_title': 'Edit Cron Job',
|
||
'cron.edit_error': 'Failed to update job',
|
||
'cron.saving': 'Saving...',
|
||
'cron.save': 'Save',
|
||
|
||
// Integrations
|
||
'integrations.title': 'Integrations',
|
||
'integrations.available': 'Available',
|
||
'integrations.active': 'Active',
|
||
'integrations.coming_soon': 'Coming Soon',
|
||
'integrations.category': 'Category',
|
||
'integrations.status': 'Status',
|
||
'integrations.search': 'Search integrations...',
|
||
'integrations.empty': 'No integrations found.',
|
||
'integrations.activate': 'Activate',
|
||
'integrations.deactivate': 'Deactivate',
|
||
'integrations.load_error': 'Failed to load integrations',
|
||
'integrations.status_active': 'Active',
|
||
'integrations.status_available': 'Available',
|
||
'integrations.status_coming_soon': 'Coming Soon',
|
||
|
||
// Memory
|
||
'memory.title': 'Memory Store',
|
||
'memory.memory_title': 'Memory',
|
||
'memory.search': 'Search memory...',
|
||
'memory.search_placeholder': 'Search memory entries...',
|
||
'memory.add': 'Store Memory',
|
||
'memory.add_memory': 'Add Memory',
|
||
'memory.add_modal_title': 'Add Memory',
|
||
'memory.delete': 'Delete',
|
||
'memory.key': 'Key',
|
||
'memory.key_required': 'Key',
|
||
'memory.content': 'Content',
|
||
'memory.content_required': 'Content',
|
||
'memory.category': 'Category',
|
||
'memory.category_optional': 'Category (optional)',
|
||
'memory.timestamp': 'Timestamp',
|
||
'memory.session': 'Session',
|
||
'memory.score': 'Score',
|
||
'memory.empty': 'No memory entries found.',
|
||
'memory.confirm_delete': 'Are you sure you want to delete this memory entry?',
|
||
'memory.all_categories': 'All Categories',
|
||
'memory.search_button': 'Search',
|
||
'memory.load_error': 'Failed to load memory',
|
||
'memory.saving': 'Saving...',
|
||
'memory.validation_error': 'Key and content are required.',
|
||
'memory.store_error': 'Failed to store memory',
|
||
'memory.delete_error': 'Failed to delete memory',
|
||
'memory.delete_confirm': 'Delete?',
|
||
'memory.yes': 'Yes',
|
||
'memory.no': 'No',
|
||
'memory.cancel': 'Cancel',
|
||
|
||
// Config
|
||
'config.title': 'Configuration',
|
||
'config.save': 'Save',
|
||
'config.saving': 'Saving...',
|
||
'config.reset': 'Reset',
|
||
'config.saved': 'Configuration saved successfully.',
|
||
'config.error': 'Failed to save configuration.',
|
||
'config.loading': 'Loading configuration...',
|
||
'config.editor_placeholder': 'TOML configuration...',
|
||
'config.configuration_title': 'Configuration',
|
||
'config.sensitive_title': 'Sensitive fields are masked',
|
||
'config.sensitive_hint': 'API keys, tokens, and passwords are hidden for security. To update a masked field, replace the entire masked value with your new value.',
|
||
'config.save_success': 'Configuration saved successfully.',
|
||
'config.save_error': 'Failed to save configuration',
|
||
'config.toml_label': 'TOML Configuration',
|
||
'config.lines': 'lines',
|
||
|
||
// Cost
|
||
'cost.title': 'Cost Tracker',
|
||
'cost.session': 'Session Cost',
|
||
'cost.daily': 'Daily Cost',
|
||
'cost.monthly': 'Monthly Cost',
|
||
'cost.total_tokens': 'Total Tokens',
|
||
'cost.request_count': 'Requests',
|
||
'cost.by_model': 'Cost by Model',
|
||
'cost.model': 'Model',
|
||
'cost.tokens': 'Tokens',
|
||
'cost.requests': 'Requests',
|
||
'cost.usd': 'Cost (USD)',
|
||
'cost.load_error': 'Failed to load cost data',
|
||
'cost.session_cost': 'Session Cost',
|
||
'cost.daily_cost': 'Daily Cost',
|
||
'cost.monthly_cost': 'Monthly Cost',
|
||
'cost.total_requests': 'Total Requests',
|
||
'cost.token_statistics': 'Token Statistics',
|
||
'cost.avg_tokens_per_request': 'Avg Tokens / Request',
|
||
'cost.cost_per_1k_tokens': 'Cost per 1K Tokens',
|
||
'cost.model_breakdown': 'Model Breakdown',
|
||
'cost.no_model_data': 'No model data available.',
|
||
'cost.cost': 'Cost',
|
||
'cost.share': 'Share',
|
||
|
||
// Logs
|
||
'logs.title': 'Live Logs',
|
||
'logs.live_logs': 'Live Logs',
|
||
'logs.clear': 'Clear',
|
||
'logs.pause': 'Pause',
|
||
'logs.resume': 'Resume',
|
||
'logs.filter': 'Filter logs...',
|
||
'logs.filter_label': 'Filter',
|
||
'logs.empty': 'No log entries.',
|
||
'logs.connected': 'Connected',
|
||
'logs.disconnected': 'Disconnected',
|
||
'logs.events': 'events',
|
||
'logs.jump_to_bottom': 'Jump to bottom',
|
||
'logs.paused_hint': 'Log streaming is paused.',
|
||
'logs.waiting_hint': 'Waiting for events...',
|
||
|
||
// Doctor
|
||
'doctor.title': 'System Diagnostics',
|
||
'doctor.diagnostics_title': 'Diagnostics',
|
||
'doctor.run': 'Run Diagnostics',
|
||
'doctor.run_diagnostics': 'Run Diagnostics',
|
||
'doctor.running': 'Running diagnostics...',
|
||
'doctor.running_btn': 'Running...',
|
||
'doctor.running_desc': 'Running diagnostics...',
|
||
'doctor.running_hint': 'This may take a few seconds.',
|
||
'doctor.ok': 'OK',
|
||
'doctor.warn': 'Warning',
|
||
'doctor.error': 'Error',
|
||
'doctor.severity': 'Severity',
|
||
'doctor.category': 'Category',
|
||
'doctor.message': 'Message',
|
||
'doctor.empty': 'No diagnostics have been run yet.',
|
||
'doctor.summary': 'Diagnostic Summary',
|
||
'doctor.issues_found': 'Issues Found',
|
||
'doctor.warnings_summary': 'Warnings',
|
||
'doctor.all_clear': 'All Clear',
|
||
'doctor.system_diagnostics': 'System Diagnostics',
|
||
'doctor.empty_hint': 'Click "Run Diagnostics" to check your ZeroClaw installation.',
|
||
|
||
// Auth / Pairing
|
||
'auth.pair': 'Pair Device',
|
||
'auth.pairing_code': 'Pairing Code',
|
||
'auth.pair_button': 'Pair',
|
||
'auth.logout': 'Logout',
|
||
'auth.pairing_success': 'Pairing successful!',
|
||
'auth.pairing_failed': 'Pairing failed. Please try again.',
|
||
'auth.enter_code': 'Enter your pairing code to connect to the agent.',
|
||
|
||
// Common
|
||
'common.loading': 'Loading...',
|
||
'common.error': 'An error occurred.',
|
||
'common.retry': 'Retry',
|
||
'common.cancel': 'Cancel',
|
||
'common.confirm': 'Confirm',
|
||
'common.save': 'Save',
|
||
'common.delete': 'Delete',
|
||
'common.edit': 'Edit',
|
||
'common.close': 'Close',
|
||
'common.yes': 'Yes',
|
||
'common.no': 'No',
|
||
'common.search': 'Search...',
|
||
'common.no_data': 'No data available.',
|
||
'common.refresh': 'Refresh',
|
||
'common.back': 'Back',
|
||
'common.actions': 'Actions',
|
||
'common.name': 'Name',
|
||
'common.description': 'Description',
|
||
'common.status': 'Status',
|
||
'common.created': 'Created',
|
||
'common.updated': 'Updated',
|
||
|
||
// Health
|
||
'health.title': 'System Health',
|
||
'health.component': 'Component',
|
||
'health.status': 'Status',
|
||
'health.last_ok': 'Last OK',
|
||
'health.last_error': 'Last Error',
|
||
'health.restart_count': 'Restarts',
|
||
'health.pid': 'Process ID',
|
||
'health.uptime': 'Uptime',
|
||
'health.updated_at': 'Last Updated',
|
||
|
||
// Dashboard specific labels
|
||
'dashboard.provider_model': 'Provider / Model',
|
||
'dashboard.since_last_restart': 'Since last restart',
|
||
'dashboard.paired_yes': 'Yes',
|
||
'dashboard.paired_no': 'No',
|
||
'dashboard.cost_overview': 'Cost Overview',
|
||
'dashboard.active_channels': 'Active Channels',
|
||
'dashboard.filter_active': 'Active',
|
||
'dashboard.filter_all': 'All',
|
||
'dashboard.no_active_channels': 'No active channels',
|
||
'dashboard.component_health': 'Component Health',
|
||
'dashboard.load_error': 'Failed to load dashboard',
|
||
'dashboard.session_label': 'Session',
|
||
'dashboard.daily_label': 'Daily',
|
||
'dashboard.monthly_label': 'Monthly',
|
||
'dashboard.total_tokens_label': 'Total Tokens',
|
||
'dashboard.requests_label': 'Requests',
|
||
'dashboard.no_channels': 'No channels configured',
|
||
'dashboard.active': 'Active',
|
||
'dashboard.inactive': 'Inactive',
|
||
'dashboard.no_components': 'No components reporting',
|
||
'dashboard.restarts': 'Restarts',
|
||
},
|
||
|
||
tr: {
|
||
// Navigation
|
||
'nav.dashboard': 'Kontrol Paneli',
|
||
'nav.agent': 'Ajan',
|
||
'nav.tools': 'Araçlar',
|
||
'nav.cron': 'Zamanlanmış Görevler',
|
||
'nav.integrations': 'Entegrasyonlar',
|
||
'nav.memory': 'Hafıza',
|
||
'nav.config': 'Yapılandırma',
|
||
'nav.cost': 'Maliyet Takibi',
|
||
'nav.logs': 'Kayıtlar',
|
||
'nav.doctor': 'Doktor',
|
||
|
||
// Dashboard
|
||
'dashboard.title': 'Kontrol Paneli',
|
||
'dashboard.provider': 'Sağlayıcı',
|
||
'dashboard.model': 'Model',
|
||
'dashboard.uptime': 'Çalışma Süresi',
|
||
'dashboard.temperature': 'Sıcaklık',
|
||
'dashboard.gateway_port': 'Ağ Geçidi Portu',
|
||
'dashboard.locale': 'Dil',
|
||
'dashboard.memory_backend': 'Hafıza Motoru',
|
||
'dashboard.paired': 'Eşleştirilmiş',
|
||
'dashboard.channels': 'Kanallar',
|
||
'dashboard.health': 'Sağlık',
|
||
'dashboard.status': 'Durum',
|
||
'dashboard.overview': 'Genel Bakış',
|
||
'dashboard.system_info': 'Sistem Bilgisi',
|
||
'dashboard.quick_actions': 'Hızlı İşlemler',
|
||
'dashboard.provider_model': 'Sağlayıcı / Model',
|
||
'dashboard.since_last_restart': 'Son Yeniden Başlatmadan Beri',
|
||
'dashboard.paired_yes': 'Evet',
|
||
'dashboard.paired_no': 'Hayır',
|
||
'dashboard.cost_overview': 'Maliyet Genel Bakışı',
|
||
'dashboard.active_channels': 'Aktif Kanallar',
|
||
'dashboard.filter_active': 'Aktif',
|
||
'dashboard.filter_all': 'Tümü',
|
||
'dashboard.no_active_channels': 'Aktif kanal yok',
|
||
'dashboard.component_health': 'Bileşen Sağlığı',
|
||
'dashboard.load_error': 'Kontrol paneli yüklenemedi',
|
||
'dashboard.session_label': 'Oturum',
|
||
'dashboard.daily_label': 'Günlük',
|
||
'dashboard.monthly_label': 'Aylık',
|
||
'dashboard.total_tokens_label': 'Toplam Token',
|
||
'dashboard.requests_label': 'İstekler',
|
||
'dashboard.no_channels': 'Kanal yapılandırılmamış',
|
||
'dashboard.active': 'Aktif',
|
||
'dashboard.inactive': 'Aktif Değil',
|
||
'dashboard.no_components': 'Bileşen raporlamıyor',
|
||
'dashboard.restarts': 'Yeniden Başlatmalar',
|
||
|
||
// Agent / Chat
|
||
'agent.title': 'Ajan Sohbeti',
|
||
'agent.send': 'Gönder',
|
||
'agent.placeholder': 'Bir mesaj yazın...',
|
||
'agent.start_conversation': 'Sohbeti başlatmak için mesaj gönderin',
|
||
'agent.type_message': 'Bir mesaj yazın...',
|
||
'agent.connecting': 'Bağlanıyor...',
|
||
'agent.connected': 'Bağlandı',
|
||
'agent.disconnected': 'Bağlantı kesildi',
|
||
'agent.reconnecting': 'Yeniden bağlanıyor...',
|
||
'agent.thinking': 'Düşünüyor...',
|
||
'agent.tool_call': 'Araç Çağrısı',
|
||
'agent.tool_result': 'Araç Sonucu',
|
||
'agent.connection_error': 'Bağlantı hatası. Yeniden bağlanmaya çalışılıyor...',
|
||
'agent.tool_call_prefix': '[Araç Çağrısı]',
|
||
'agent.tool_result_prefix': '[Araç Sonucu]',
|
||
'agent.error_prefix': '[Hata]',
|
||
'agent.unknown_error': 'Bilinmeyen hata',
|
||
'agent.send_error': 'Mesaj gönderilemedi. Lütfen tekrar deneyin.',
|
||
'agent.copy_message': 'Mesajı kopyala',
|
||
'agent.connected_status': 'Bağlandı',
|
||
'agent.disconnected_status': 'Bağlantı kesildi',
|
||
|
||
// Tools
|
||
'tools.title': 'Mevcut Araçlar',
|
||
'tools.name': 'Ad',
|
||
'tools.description': 'Açıklama',
|
||
'tools.parameters': 'Parametreler',
|
||
'tools.search': 'Araç ara...',
|
||
'tools.empty': 'Araç bulunamadı.',
|
||
'tools.count': 'Toplam araç',
|
||
'tools.agent_tools': 'Ajan Araçları',
|
||
'tools.cli_tools': 'CLI Araçları',
|
||
'tools.parameter_schema': 'Parametre Şeması',
|
||
'tools.path': 'Yol',
|
||
'tools.version': 'Sürüm',
|
||
'tools.category': 'Kategori',
|
||
'tools.load_error': 'Araçlar yüklenemedi',
|
||
|
||
// Cron
|
||
'cron.title': 'Zamanlanmış Görevler',
|
||
'cron.scheduled_tasks': 'Zamanlanmış Görevler',
|
||
'cron.add': 'Görev Ekle',
|
||
'cron.add_job': 'Görev Ekle',
|
||
'cron.add_modal_title': 'Cron Görevi Ekle',
|
||
'cron.delete': 'Sil',
|
||
'cron.enable': 'Etkinleştir',
|
||
'cron.disable': 'Devre Dışı Bırak',
|
||
'cron.name': 'Ad',
|
||
'cron.name_optional': 'Ad (isteğe bağlı)',
|
||
'cron.command': 'Komut',
|
||
'cron.command_required': 'Komut',
|
||
'cron.schedule': 'Zamanlama',
|
||
'cron.schedule_required': 'Zamanlama',
|
||
'cron.next_run': 'Sonraki Çalıştırma',
|
||
'cron.last_run': 'Son Çalıştırma',
|
||
'cron.last_status': 'Son Durum',
|
||
'cron.enabled': 'Etkin',
|
||
'cron.enabled_status': 'Etkin',
|
||
'cron.disabled_status': 'Devre Dışı',
|
||
'cron.empty': 'Zamanlanmış görev bulunamadı.',
|
||
'cron.confirm_delete': 'Bu görevi silmek istediğinizden emin misiniz?',
|
||
'cron.load_error': 'Cron görevleri yüklenemedi',
|
||
'cron.validation_error': 'Zamanlama ve komut gereklidir.',
|
||
'cron.add_error': 'Görev eklenemedi',
|
||
'cron.delete_error': 'Görev silinemedi',
|
||
'cron.cancel': 'İptal',
|
||
'cron.adding': 'Ekleniyor...',
|
||
'cron.id': 'ID',
|
||
'cron.actions': 'İşlemler',
|
||
'cron.loading_run_history': 'Çalıştırma geçmişi yükleniyor...',
|
||
'cron.load_run_history_error': 'Çalıştırma geçmişi yüklenemedi',
|
||
'cron.no_runs': 'Henüz çalıştırma kaydı yok.',
|
||
'cron.recent_runs': 'Son Çalıştırmalar',
|
||
'cron.yes': 'Evet',
|
||
'cron.no': 'Hayır',
|
||
'cron.edit': 'Düzenle',
|
||
'cron.edit_modal_title': 'Cron Görevini Düzenle',
|
||
'cron.edit_error': 'Görev güncellenemedi',
|
||
'cron.saving': 'Kaydediliyor...',
|
||
'cron.save': 'Kaydet',
|
||
|
||
// Integrations
|
||
'integrations.title': 'Entegrasyonlar',
|
||
'integrations.available': 'Mevcut',
|
||
'integrations.active': 'Aktif',
|
||
'integrations.coming_soon': 'Yakında',
|
||
'integrations.category': 'Kategori',
|
||
'integrations.status': 'Durum',
|
||
'integrations.search': 'Entegrasyon ara...',
|
||
'integrations.empty': 'Entegrasyon bulunamadı.',
|
||
'integrations.activate': 'Etkinleştir',
|
||
'integrations.deactivate': 'Devre Dışı Bırak',
|
||
'integrations.load_error': 'Entegrasyonlar yüklenemedi',
|
||
'integrations.all': 'Tümü',
|
||
'integrations.status_active': 'Aktif',
|
||
'integrations.status_available': 'Mevcut',
|
||
'integrations.status_coming_soon': 'Yakında',
|
||
|
||
// Memory
|
||
'memory.title': 'Hafıza Deposu',
|
||
'memory.memory_title': 'Hafıza',
|
||
'memory.search': 'Hafıza ara...',
|
||
'memory.search_placeholder': 'Hafıza girişleri ara...',
|
||
'memory.add': 'Hafıza Ekle',
|
||
'memory.add_memory': 'Hafıza Ekle',
|
||
'memory.add_modal_title': 'Hafıza Ekle',
|
||
'memory.delete': 'Sil',
|
||
'memory.key': 'Anahtar',
|
||
'memory.key_required': 'Anahtar',
|
||
'memory.content': 'İçerik',
|
||
'memory.content_required': 'İçerik',
|
||
'memory.category': 'Kategori',
|
||
'memory.category_optional': 'Kategori (isteğe bağlı)',
|
||
'memory.timestamp': 'Zaman Damgası',
|
||
'memory.session': 'Oturum',
|
||
'memory.score': 'Puan',
|
||
'memory.empty': 'Hafıza girişi bulunamadı.',
|
||
'memory.confirm_delete': 'Bu hafıza girişini silmek istediğinizden emin misiniz?',
|
||
'memory.all_categories': 'Tüm Kategoriler',
|
||
'memory.search_button': 'Ara',
|
||
'memory.load_error': 'Hafıza yüklenemedi',
|
||
'memory.saving': 'Kaydediliyor...',
|
||
'memory.validation_error': 'Anahtar ve içerik gereklidir.',
|
||
'memory.store_error': 'Hafıza kaydedilemedi',
|
||
'memory.delete_error': 'Hafıza silinemedi',
|
||
'memory.delete_confirm': 'Sil?',
|
||
'memory.yes': 'Evet',
|
||
'memory.no': 'Hayır',
|
||
'memory.cancel': 'İptal',
|
||
|
||
// Config
|
||
'config.title': 'Yapılandırma',
|
||
'config.save': 'Kaydet',
|
||
'config.saving': 'Kaydediliyor...',
|
||
'config.reset': 'Sıfırla',
|
||
'config.saved': 'Yapılandırma başarıyla kaydedildi.',
|
||
'config.error': 'Yapılandırma kaydedilemedi.',
|
||
'config.loading': 'Yapılandırma yükleniyor...',
|
||
'config.editor_placeholder': 'TOML yapılandırması...',
|
||
'config.configuration_title': 'Yapılandırma',
|
||
'config.sensitive_title': 'Hassas alanlar gizlendi',
|
||
'config.sensitive_hint': 'API anahtarları, belirteçler ve parolalar güvenlik için gizlendi. Maskeli bir alanı güncellemek için, tüm maskeli değeri yeni değerinizle değiştirin.',
|
||
'config.save_success': 'Yapılandırma başarıyla kaydedildi.',
|
||
'config.save_error': 'Yapılandırma kaydedilemedi',
|
||
'config.toml_label': 'TOML Yapılandırması',
|
||
'config.lines': 'satır',
|
||
|
||
// Cost
|
||
'cost.title': 'Maliyet Takibi',
|
||
'cost.session': 'Oturum Maliyeti',
|
||
'cost.daily': 'Günlük Maliyet',
|
||
'cost.monthly': 'Aylık Maliyet',
|
||
'cost.total_tokens': 'Toplam Token',
|
||
'cost.request_count': 'İstek Sayısı',
|
||
'cost.by_model': 'Modele Göre Maliyet',
|
||
'cost.model': 'Model',
|
||
'cost.tokens': 'Token',
|
||
'cost.requests': 'İstekler',
|
||
'cost.usd': 'Maliyet (USD)',
|
||
'cost.load_error': 'Maliyet verileri yüklenemedi',
|
||
'cost.session_cost': 'Oturum Maliyeti',
|
||
'cost.daily_cost': 'Günlük Maliyet',
|
||
'cost.monthly_cost': 'Aylık Maliyet',
|
||
'cost.total_requests': 'Toplam İstek',
|
||
'cost.token_statistics': 'Token İstatistikleri',
|
||
'cost.avg_tokens_per_request': 'Ortalama Token / İstek',
|
||
'cost.cost_per_1k_tokens': '1K Token Başına Maliyet',
|
||
'cost.model_breakdown': 'Model Detayı',
|
||
'cost.no_model_data': 'Model verisi mevcut değil.',
|
||
'cost.cost': 'Maliyet',
|
||
'cost.share': 'Pay',
|
||
|
||
// Logs
|
||
'logs.title': 'Canlı Kayıtlar',
|
||
'logs.live_logs': 'Canlı Kayıtlar',
|
||
'logs.clear': 'Temizle',
|
||
'logs.pause': 'Duraklat',
|
||
'logs.resume': 'Devam Et',
|
||
'logs.filter': 'Kayıtları filtrele...',
|
||
'logs.filter_label': 'Filtre',
|
||
'logs.empty': 'Kayıt girişi bulunamadı.',
|
||
'logs.connected': 'Bağlandı',
|
||
'logs.disconnected': 'Bağlantı kesildi',
|
||
'logs.events': 'olay',
|
||
'logs.jump_to_bottom': 'En alta atla',
|
||
'logs.paused_hint': 'Kayıt akışı duraklatıldı.',
|
||
'logs.waiting_hint': 'Olay bekleniyor...',
|
||
|
||
// Doctor
|
||
'doctor.title': 'Sistem Tanıları',
|
||
'doctor.diagnostics_title': 'Tanılar',
|
||
'doctor.run': 'Tanı Çalıştır',
|
||
'doctor.run_diagnostics': 'Tanı Çalıştır',
|
||
'doctor.running': 'Tanı çalıştırılıyor...',
|
||
'doctor.running_btn': 'Çalıştırılıyor...',
|
||
'doctor.running_desc': 'Tanı çalıştırılıyor...',
|
||
'doctor.running_hint': 'Bu birkaç saniye sürebilir.',
|
||
'doctor.ok': 'Tamam',
|
||
'doctor.warn': 'Uyarı',
|
||
'doctor.error': 'Hata',
|
||
'doctor.severity': 'Şiddet',
|
||
'doctor.category': 'Kategori',
|
||
'doctor.message': 'Mesaj',
|
||
'doctor.empty': 'Henüz tanı çalıştırılmadı.',
|
||
'doctor.summary': 'Tanı Özeti',
|
||
'doctor.issues_found': 'Sorunlar Bulundu',
|
||
'doctor.warnings_summary': 'Uyarılar',
|
||
'doctor.all_clear': 'Her Şey Yolunda',
|
||
'doctor.system_diagnostics': 'Sistem Tanıları',
|
||
'doctor.empty_hint': 'ZeroClaw kurulumunuzu kontrol etmek için "Tanı Çalıştır" düğmesine tıklayın.',
|
||
|
||
// Auth / Pairing
|
||
'auth.pair': 'Cihaz Eşleştir',
|
||
'auth.pairing_code': 'Eşleştirme Kodu',
|
||
'auth.pair_button': 'Eşleştir',
|
||
'auth.logout': 'Çıkış Yap',
|
||
'auth.pairing_success': 'Eşleştirme başarılı!',
|
||
'auth.pairing_failed': 'Eşleştirme başarısız. Lütfen tekrar deneyin.',
|
||
'auth.enter_code': 'Akıllı birine bağlanmak için eşleştirme kodunuzu girin.',
|
||
|
||
// Common
|
||
'common.loading': 'Yükleniyor...',
|
||
'common.error': 'Bir hata oluştu.',
|
||
'common.retry': 'Tekrar Dene',
|
||
'common.cancel': 'İptal',
|
||
'common.confirm': 'Onayla',
|
||
'common.save': 'Kaydet',
|
||
'common.delete': 'Sil',
|
||
'common.edit': 'Düzenle',
|
||
'common.close': 'Kapat',
|
||
'common.yes': 'Evet',
|
||
'common.no': 'Hayır',
|
||
'common.search': 'Ara...',
|
||
'common.no_data': 'Veri mevcut değil.',
|
||
'common.refresh': 'Yenile',
|
||
'common.back': 'Geri',
|
||
'common.actions': 'İşlemler',
|
||
'common.name': 'Ad',
|
||
'common.description': 'Açıklama',
|
||
'common.status': 'Durum',
|
||
'common.created': 'Oluşturulma',
|
||
'common.updated': 'Güncellenme',
|
||
|
||
// Health
|
||
'health.title': 'Sistem Sağlığı',
|
||
'health.component': 'Bileşen',
|
||
'health.status': 'Durum',
|
||
'health.last_ok': 'Son Başarılı',
|
||
'health.last_error': 'Son Hata',
|
||
'health.restart_count': 'Yeniden Başlatmalar',
|
||
'health.pid': 'İşlem Kimliği',
|
||
'health.uptime': 'Çalışma Süresi',
|
||
'health.updated_at': 'Son Güncelleme',
|
||
},
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Current locale state
|
||
// ---------------------------------------------------------------------------
|
||
|
||
let currentLocale: Locale = 'en';
|
||
|
||
export function getLocale(): Locale {
|
||
return currentLocale;
|
||
}
|
||
|
||
export function setLocale(locale: Locale): void {
|
||
currentLocale = locale;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Translation function
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Translate a key using the current locale. Returns the key itself if no
|
||
* translation is found.
|
||
*/
|
||
export function t(key: string): string {
|
||
return translations[currentLocale]?.[key] ?? translations.en[key] ?? key;
|
||
}
|
||
|
||
/**
|
||
* Get the translation for a specific locale. Falls back to English, then to the
|
||
* raw key.
|
||
*/
|
||
export function tLocale(key: string, locale: Locale): string {
|
||
return translations[locale]?.[key] ?? translations.en[key] ?? key;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// React hook
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/**
|
||
* React hook that fetches the locale from /api/status on mount and keeps the
|
||
* i18n module in sync. Returns the current locale and a `t` helper bound to it.
|
||
*/
|
||
export function useLocale(): { locale: Locale; t: (key: string) => string } {
|
||
const [locale, setLocaleState] = useState<Locale>(currentLocale);
|
||
|
||
useEffect(() => {
|
||
let cancelled = false;
|
||
|
||
getStatus()
|
||
.then((status) => {
|
||
if (cancelled) return;
|
||
const localeStr = status.locale?.toLowerCase() ?? '';
|
||
let detected: Locale;
|
||
if (localeStr.startsWith('zh')) {
|
||
detected = 'zh';
|
||
} else if (localeStr.startsWith('tr')) {
|
||
detected = 'tr';
|
||
} else {
|
||
detected = 'en';
|
||
}
|
||
setLocale(detected);
|
||
setLocaleState(detected);
|
||
})
|
||
.catch(() => {
|
||
// Keep default locale on error
|
||
});
|
||
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, []);
|
||
|
||
return {
|
||
locale,
|
||
t: (key: string) => tLocale(key, locale),
|
||
};
|
||
}
|