戒酒的李白

The newly added crawler visualization settings and the process visualization orc…

…hestration module have been adapted to support bilingual switching.
  1 +// 爬虫控制面板翻译资源文件
  2 +// 包含中文(zh-CN)和英文(en-US)的翻译
  3 +
  4 +const spiderI18nResources = {
  5 + 'zh-CN': {
  6 + translation: {
  7 + // 页面标题和导航
  8 + 'page-title': '爬虫控制面板',
  9 +
  10 + // 卡片标题
  11 + 'topic-selection': '选择话题类型',
  12 + 'spider-parameters': '爬虫参数配置',
  13 + 'content-filters': '内容筛选配置',
  14 + 'account-config': '账号配置',
  15 + 'parallel-config': '并行配置',
  16 + 'db-config': '数据库配置',
  17 + 'ai-assistant': 'AI配置助手',
  18 + 'spider-status': '爬虫状态',
  19 +
  20 + // 话题选择部分
  21 + 'add-custom-topic': '添加自定义话题',
  22 + 'custom-topic-placeholder': '输入自定义话题',
  23 + 'btn-add': '添加',
  24 + 'selected-topics': '已选择的话题:',
  25 +
  26 + // 爬虫参数部分
  27 + 'crawl-depth': '爬取深度',
  28 + 'crawl-depth-hint': '每个话题爬取的页数(1-10)',
  29 + 'interval': '爬取间隔(秒)',
  30 + 'interval-hint': '每次请求之间的间隔时间',
  31 + 'max-retries': '最大重试次数',
  32 + 'timeout': '请求超时时间(秒)',
  33 +
  34 + // 筛选器部分
  35 + 'help': '帮助',
  36 + 'filter-conditions': '筛选条件说明:',
  37 + 'filter-condition-1': '数值条件:设置大于某个值进行筛选,如点赞数>1000',
  38 + 'filter-condition-2': '正则匹配:使用正则表达式匹配内容,如包含特定关键词',
  39 + 'filter-condition-3': '多个条件之间是"与"的关系,即同时满足才会保留',
  40 + 'filter-tip': '提示:合理设置筛选条件可以提高数据质量',
  41 + 'interaction-filter': '互动数据筛选',
  42 + 'likes-gt': '点赞数大于',
  43 + 'comments-gt': '评论数大于',
  44 + 'reposts-gt': '转发数大于',
  45 + 'reads-gt': '阅读数大于',
  46 + 'regex-filter': '内容正则筛选',
  47 + 'add-regex-filter': '添加正则筛选',
  48 + 'advanced-options': '高级选项',
  49 + 'only-original': '仅爬取原创内容',
  50 + 'must-have-media': '必须包含图片或视频',
  51 + 'only-verified': '仅认证用户的内容',
  52 +
  53 + // 账号配置部分
  54 + 'btn-add-account': '添加账号',
  55 + 'how-to-get-cookie': '如何获取Cookie?',
  56 + 'cookie-step-1': '登录微博网页版',
  57 + 'cookie-step-2': '按F12打开开发者工具',
  58 + 'cookie-step-3': '切换到Network标签页',
  59 + 'cookie-step-4': '刷新页面,找到请求头中的Cookie值',
  60 + 'cookie-warning': '注意:请勿泄露您的Cookie信息!',
  61 + 'account-tip': '提示:添加多个账号可以提高爬取效率,系统会自动在账号间轮换。',
  62 + 'no-account-warning': '请至少添加一个账号',
  63 + 'username': '用户名',
  64 + 'username-placeholder': '微博用户名',
  65 + 'password': '密码',
  66 + 'password-placeholder': '微博密码',
  67 + 'cookie': 'Cookie',
  68 + 'cookie-placeholder': '请输入微博Cookie',
  69 + 'save-cookie': '保存Cookie(加密存储)',
  70 + 'status-pending': '状态:待验证',
  71 + 'btn-validate-account': '验证账号',
  72 + 'status-validating': '状态:验证中...',
  73 + 'status-success': '状态:验证成功',
  74 + 'status-failed': '状态:验证失败 - ',
  75 + 'error-empty-cookie': 'Cookie不能为空',
  76 +
  77 + // 正则筛选器
  78 + 'regex-pattern': '正则表达式',
  79 + 'regex-pattern-placeholder': '输入正则表达式',
  80 + 'match-target': '匹配目标',
  81 + 'target-content': '微博内容',
  82 + 'target-author': '作者名',
  83 + 'target-location': '发布位置',
  84 + 'inverse-match': '反向匹配(不包含匹配项)',
  85 +
  86 + // 并行配置
  87 + 'max-concurrent': '最大并行数',
  88 + 'max-concurrent-hint': '同时进行爬取的最大话题数(1-5)',
  89 + 'requests-per-minute': '每分钟请求数限制',
  90 + 'requests-per-minute-hint': '避免请求过于频繁(30-120)',
  91 +
  92 + // 数据库配置
  93 + 'db-type': '数据库类型',
  94 + 'host': '主机地址',
  95 + 'port': '端口',
  96 + 'db-name': '数据库名',
  97 + 'username-db': '用户名',
  98 + 'password-db': '密码',
  99 + 'btn-test-connection': '测试连接',
  100 + 'db-connect-success': '数据库连接测试成功!',
  101 + 'db-connect-fail': '数据库连接测试失败:',
  102 + 'db-connect-error': '测试连接时发生错误:',
  103 +
  104 + // AI配置助手
  105 + 'ai-prompt-label': '用自然语言描述您的爬虫需求',
  106 + 'ai-prompt-placeholder': '例如:我想爬取最近一周关于人工智能的热门微博,重点关注转发量超过1000的内容,每个话题爬取前5页内容。',
  107 + 'btn-generate-config': '生成配置',
  108 + 'auto-apply': '自动应用生成的配置',
  109 + 'ai-suggestion': 'AI助手建议:',
  110 + 'ai-config-applied': 'AI配置已自动应用',
  111 + 'ai-config-error': '生成配置时出错:',
  112 + 'empty-prompt-error': '请输入您的爬虫需求描述!',
  113 +
  114 + // 操作按钮
  115 + 'btn-start': '开始爬取',
  116 + 'btn-save-config': '保存配置',
  117 + 'config-saved': '配置已保存!',
  118 + 'save-failed': '保存失败:',
  119 + 'save-error': '保存出错:',
  120 +
  121 + // 爬虫状态
  122 + 'task-started': '爬虫任务已启动...',
  123 + 'start-failed': '启动失败:',
  124 + 'error': '错误:',
  125 +
  126 + // 验证错误提示
  127 + 'select-topic-error': '请至少选择一个话题!',
  128 + 'invalid-regex-error': '正则表达式 "{0}" 格式无效!',
  129 + 'need-account-error': '请至少添加一个账号!',
  130 + 'empty-cookie-error': '存在未配置Cookie的账号,请检查!',
  131 + 'concurrent-limit-error': '最大并行数必须在1-5之间!',
  132 + 'request-limit-error': '每分钟请求数必须在30-120之间!',
  133 + 'db-config-error': '请完整填写数据库配置信息!'
  134 + }
  135 + },
  136 + 'en-US': {
  137 + translation: {
  138 + // Page title and navigation
  139 + 'page-title': 'Spider Control Panel',
  140 +
  141 + // Card titles
  142 + 'topic-selection': 'Select Topic Types',
  143 + 'spider-parameters': 'Spider Parameters',
  144 + 'content-filters': 'Content Filters',
  145 + 'account-config': 'Account Configuration',
  146 + 'parallel-config': 'Parallel Configuration',
  147 + 'db-config': 'Database Configuration',
  148 + 'ai-assistant': 'AI Configuration Assistant',
  149 + 'spider-status': 'Spider Status',
  150 +
  151 + // Topic selection section
  152 + 'add-custom-topic': 'Add Custom Topic',
  153 + 'custom-topic-placeholder': 'Enter custom topic',
  154 + 'btn-add': 'Add',
  155 + 'selected-topics': 'Selected Topics:',
  156 +
  157 + // Spider parameters section
  158 + 'crawl-depth': 'Crawl Depth',
  159 + 'crawl-depth-hint': 'Number of pages to crawl for each topic (1-10)',
  160 + 'interval': 'Interval (seconds)',
  161 + 'interval-hint': 'Time between requests',
  162 + 'max-retries': 'Maximum Retries',
  163 + 'timeout': 'Request Timeout (seconds)',
  164 +
  165 + // Filters section
  166 + 'help': 'Help',
  167 + 'filter-conditions': 'Filter conditions:',
  168 + 'filter-condition-1': 'Numeric conditions: Set values to filter by, e.g., likes > 1000',
  169 + 'filter-condition-2': 'Regex matching: Use regular expressions to match content, e.g., contain specific keywords',
  170 + 'filter-condition-3': 'Multiple conditions are combined with AND logic',
  171 + 'filter-tip': 'Tip: Setting proper filters can improve data quality',
  172 + 'interaction-filter': 'Interaction Data Filters',
  173 + 'likes-gt': 'Likes greater than',
  174 + 'comments-gt': 'Comments greater than',
  175 + 'reposts-gt': 'Reposts greater than',
  176 + 'reads-gt': 'Reads greater than',
  177 + 'regex-filter': 'Content Regex Filters',
  178 + 'add-regex-filter': 'Add Regex Filter',
  179 + 'advanced-options': 'Advanced Options',
  180 + 'only-original': 'Only crawl original content',
  181 + 'must-have-media': 'Must contain images or videos',
  182 + 'only-verified': 'Only content from verified users',
  183 +
  184 + // Account configuration section
  185 + 'btn-add-account': 'Add Account',
  186 + 'how-to-get-cookie': 'How to get the Cookie?',
  187 + 'cookie-step-1': 'Login to Weibo web version',
  188 + 'cookie-step-2': 'Press F12 to open developer tools',
  189 + 'cookie-step-3': 'Switch to Network tab',
  190 + 'cookie-step-4': 'Refresh page and find Cookie value in request headers',
  191 + 'cookie-warning': 'Warning: Do not expose your Cookie information!',
  192 + 'account-tip': 'Tip: Adding multiple accounts can improve crawling efficiency, the system will automatically rotate between accounts.',
  193 + 'no-account-warning': 'Please add at least one account',
  194 + 'username': 'Username',
  195 + 'username-placeholder': 'Weibo username',
  196 + 'password': 'Password',
  197 + 'password-placeholder': 'Weibo password',
  198 + 'cookie': 'Cookie',
  199 + 'cookie-placeholder': 'Please enter Weibo Cookie',
  200 + 'save-cookie': 'Save Cookie (encrypted storage)',
  201 + 'status-pending': 'Status: Pending verification',
  202 + 'btn-validate-account': 'Validate Account',
  203 + 'status-validating': 'Status: Validating...',
  204 + 'status-success': 'Status: Validation successful',
  205 + 'status-failed': 'Status: Validation failed - ',
  206 + 'error-empty-cookie': 'Cookie cannot be empty',
  207 +
  208 + // Regex filters
  209 + 'regex-pattern': 'Regular Expression',
  210 + 'regex-pattern-placeholder': 'Enter regular expression',
  211 + 'match-target': 'Match Target',
  212 + 'target-content': 'Weibo content',
  213 + 'target-author': 'Author name',
  214 + 'target-location': 'Posting location',
  215 + 'inverse-match': 'Inverse match (exclude matches)',
  216 +
  217 + // Parallel configuration
  218 + 'max-concurrent': 'Maximum Concurrent Tasks',
  219 + 'max-concurrent-hint': 'Maximum number of topics to crawl simultaneously (1-5)',
  220 + 'requests-per-minute': 'Requests Per Minute Limit',
  221 + 'requests-per-minute-hint': 'Avoid too frequent requests (30-120)',
  222 +
  223 + // Database configuration
  224 + 'db-type': 'Database Type',
  225 + 'host': 'Host',
  226 + 'port': 'Port',
  227 + 'db-name': 'Database Name',
  228 + 'username-db': 'Username',
  229 + 'password-db': 'Password',
  230 + 'btn-test-connection': 'Test Connection',
  231 + 'db-connect-success': 'Database connection test successful!',
  232 + 'db-connect-fail': 'Database connection test failed: ',
  233 + 'db-connect-error': 'Error while testing connection: ',
  234 +
  235 + // AI assistant
  236 + 'ai-prompt-label': 'Describe your crawling requirements in natural language',
  237 + 'ai-prompt-placeholder': 'For example: I want to crawl trending Weibo posts about AI from the past week, focusing on content with more than 1000 reposts, crawling the first 5 pages for each topic.',
  238 + 'btn-generate-config': 'Generate Configuration',
  239 + 'auto-apply': 'Auto-apply generated configuration',
  240 + 'ai-suggestion': 'AI Assistant Suggestion:',
  241 + 'ai-config-applied': 'AI configuration applied automatically',
  242 + 'ai-config-error': 'Error generating configuration: ',
  243 + 'empty-prompt-error': 'Please enter your crawler requirements!',
  244 +
  245 + // Action buttons
  246 + 'btn-start': 'Start Crawling',
  247 + 'btn-save-config': 'Save Configuration',
  248 + 'config-saved': 'Configuration saved!',
  249 + 'save-failed': 'Save failed: ',
  250 + 'save-error': 'Error saving: ',
  251 +
  252 + // Spider status
  253 + 'task-started': 'Crawler task started...',
  254 + 'start-failed': 'Start failed: ',
  255 + 'error': 'Error: ',
  256 +
  257 + // Validation error messages
  258 + 'select-topic-error': 'Please select at least one topic!',
  259 + 'invalid-regex-error': 'Regular expression "{0}" is invalid!',
  260 + 'need-account-error': 'Please add at least one account!',
  261 + 'empty-cookie-error': 'There are accounts without Cookie configuration, please check!',
  262 + 'concurrent-limit-error': 'Maximum concurrent tasks must be between 1-5!',
  263 + 'request-limit-error': 'Requests per minute must be between 30-120!',
  264 + 'db-config-error': 'Please complete all database configuration fields!'
  265 + }
  266 + }
  267 +};
  1 +// 翻译资源文件
  2 +// 包含中文(zh-CN)和英文(en-US)的翻译
  3 +
  4 +const i18nResources = {
  5 + 'zh-CN': {
  6 + translation: {
  7 + // 页面标题
  8 + 'page-title': '工作流编辑器 - 微博舆情分析系统',
  9 + 'navbar-brand': '工作流编辑器',
  10 +
  11 + // 导航菜单
  12 + 'nav-visual-editor': '可视化编辑',
  13 + 'nav-template-mgmt': '模板管理',
  14 + 'nav-task-list': '任务列表',
  15 +
  16 + // 按钮
  17 + 'btn-save': '保存',
  18 + 'btn-run': '运行',
  19 + 'btn-cancel': '取消',
  20 + 'btn-close': '关闭',
  21 + 'btn-create-new': '新建',
  22 + 'btn-validate': '验证',
  23 + 'btn-undo': '撤销',
  24 + 'btn-redo': '重做',
  25 + 'btn-zoom-in': '放大',
  26 + 'btn-zoom-out': '缩小',
  27 + 'btn-fit-view': '适应视图',
  28 + 'btn-export': '导出工作流',
  29 + 'btn-import': '导入工作流',
  30 + 'btn-cancel-task': '取消任务',
  31 + 'btn-view-full-result': '查看完整结果',
  32 +
  33 + // 选项卡
  34 + 'tab-components': '组件',
  35 + 'tab-templates': '模板',
  36 +
  37 + // 组件类别
  38 + 'comp-data-source': '数据源',
  39 + 'comp-data-processing': '数据处理',
  40 + 'comp-model-analysis': '模型分析',
  41 + 'comp-visualization': '可视化',
  42 +
  43 + // 组件
  44 + 'comp-database': '数据库',
  45 + 'comp-file': '文件',
  46 + 'comp-crawler': '爬虫',
  47 + 'comp-filter': '过滤',
  48 + 'comp-sort': '排序',
  49 + 'comp-aggregate': '聚合',
  50 + 'comp-sentiment': '情感分析',
  51 + 'comp-topic': '话题分类',
  52 + 'comp-keywords': '关键词提取',
  53 + 'comp-summarize': '文本摘要',
  54 + 'comp-chart': '图表',
  55 + 'comp-table': '表格',
  56 + 'comp-wordcloud': '词云',
  57 +
  58 + // 模板相关
  59 + 'templates-crawler': '爬虫模板',
  60 + 'templates-analysis': '分析流程模板',
  61 + 'modal-save-template': '保存为模板',
  62 + 'template-name': '模板名称',
  63 + 'template-description': '描述',
  64 + 'template-icon': '图标',
  65 +
  66 + // 图标名称
  67 + 'icon-chart': '图表',
  68 + 'icon-filter': '过滤',
  69 + 'icon-crawler': '爬虫',
  70 + 'icon-ai': 'AI分析',
  71 + 'icon-database': '数据库',
  72 + 'icon-wordcloud': '词云',
  73 +
  74 + // 属性面板
  75 + 'properties-title': '组件属性',
  76 +
  77 + // 工作流状态
  78 + 'workflow-status-message': '工作流就绪。拖拽左侧组件到画布创建节点。',
  79 + 'nodes': '节点',
  80 + 'connections': '连接',
  81 +
  82 + // 运行工作流
  83 + 'modal-run-workflow': '运行工作流',
  84 + 'run-workflow-confirm': '确认要运行当前工作流吗?',
  85 + 'save-before-run': '运行前保存工作流',
  86 +
  87 + // 任务状态
  88 + 'modal-task-status': '任务执行状态',
  89 + 'task-progress': '进度',
  90 + 'task-status-info': '状态信息',
  91 + 'task-waiting': '等待中',
  92 + 'task-id': '任务ID:',
  93 + 'task-status': '状态:',
  94 + 'task-start-time': '开始时间:',
  95 + 'task-complete-time': '完成时间:',
  96 + 'task-current-step': '当前步骤:',
  97 + 'waiting-to-start': '等待开始',
  98 + 'task-elapsed-time': '耗时:',
  99 + 'task-result-preview': '结果预览',
  100 + 'refresh-preview': '刷新预览',
  101 + 'loading': '加载中...',
  102 + 'task-running-preparing': '任务运行中,正在准备预览数据...',
  103 + 'preview-after-task': '任务完成后将显示结果预览...',
  104 + 'preview-error': '加载预览时发生错误'
  105 + }
  106 + },
  107 + 'en-US': {
  108 + translation: {
  109 + // Page title
  110 + 'page-title': 'Workflow Editor - Weibo Public Opinion Analysis System',
  111 + 'navbar-brand': 'Workflow Editor',
  112 +
  113 + // Navigation menu
  114 + 'nav-visual-editor': 'Visual Editor',
  115 + 'nav-template-mgmt': 'Template Management',
  116 + 'nav-task-list': 'Task List',
  117 +
  118 + // Buttons
  119 + 'btn-save': 'Save',
  120 + 'btn-run': 'Run',
  121 + 'btn-cancel': 'Cancel',
  122 + 'btn-close': 'Close',
  123 + 'btn-create-new': 'Create New',
  124 + 'btn-validate': 'Validate',
  125 + 'btn-undo': 'Undo',
  126 + 'btn-redo': 'Redo',
  127 + 'btn-zoom-in': 'Zoom In',
  128 + 'btn-zoom-out': 'Zoom Out',
  129 + 'btn-fit-view': 'Fit View',
  130 + 'btn-export': 'Export Workflow',
  131 + 'btn-import': 'Import Workflow',
  132 + 'btn-cancel-task': 'Cancel Task',
  133 + 'btn-view-full-result': 'View Full Results',
  134 +
  135 + // Tabs
  136 + 'tab-components': 'Components',
  137 + 'tab-templates': 'Templates',
  138 +
  139 + // Component categories
  140 + 'comp-data-source': 'Data Sources',
  141 + 'comp-data-processing': 'Data Processing',
  142 + 'comp-model-analysis': 'Model Analysis',
  143 + 'comp-visualization': 'Visualization',
  144 +
  145 + // Components
  146 + 'comp-database': 'Database',
  147 + 'comp-file': 'File',
  148 + 'comp-crawler': 'Crawler',
  149 + 'comp-filter': 'Filter',
  150 + 'comp-sort': 'Sort',
  151 + 'comp-aggregate': 'Aggregate',
  152 + 'comp-sentiment': 'Sentiment Analysis',
  153 + 'comp-topic': 'Topic Classification',
  154 + 'comp-keywords': 'Keyword Extraction',
  155 + 'comp-summarize': 'Text Summarization',
  156 + 'comp-chart': 'Chart',
  157 + 'comp-table': 'Table',
  158 + 'comp-wordcloud': 'Word Cloud',
  159 +
  160 + // Template related
  161 + 'templates-crawler': 'Crawler Templates',
  162 + 'templates-analysis': 'Analysis Flow Templates',
  163 + 'modal-save-template': 'Save as Template',
  164 + 'template-name': 'Template Name',
  165 + 'template-description': 'Description',
  166 + 'template-icon': 'Icon',
  167 +
  168 + // Icon names
  169 + 'icon-chart': 'Chart',
  170 + 'icon-filter': 'Filter',
  171 + 'icon-crawler': 'Crawler',
  172 + 'icon-ai': 'AI Analysis',
  173 + 'icon-database': 'Database',
  174 + 'icon-wordcloud': 'Word Cloud',
  175 +
  176 + // Properties panel
  177 + 'properties-title': 'Component Properties',
  178 +
  179 + // Workflow status
  180 + 'workflow-status-message': 'Workflow ready. Drag components from the left panel to create nodes.',
  181 + 'nodes': 'Nodes',
  182 + 'connections': 'Connections',
  183 +
  184 + // Run workflow
  185 + 'modal-run-workflow': 'Run Workflow',
  186 + 'run-workflow-confirm': 'Are you sure you want to run the current workflow?',
  187 + 'save-before-run': 'Save workflow before running',
  188 +
  189 + // Task status
  190 + 'modal-task-status': 'Task Execution Status',
  191 + 'task-progress': 'Progress',
  192 + 'task-status-info': 'Status Information',
  193 + 'task-waiting': 'Waiting',
  194 + 'task-id': 'Task ID:',
  195 + 'task-status': 'Status:',
  196 + 'task-start-time': 'Start Time:',
  197 + 'task-complete-time': 'Complete Time:',
  198 + 'task-current-step': 'Current Step:',
  199 + 'waiting-to-start': 'Waiting to start',
  200 + 'task-elapsed-time': 'Elapsed Time:',
  201 + 'task-result-preview': 'Result Preview',
  202 + 'refresh-preview': 'Refresh Preview',
  203 + 'loading': 'Loading...',
  204 + 'task-running-preparing': 'Task is running, preparing preview data...',
  205 + 'preview-after-task': 'Results preview will be displayed after the task is completed...',
  206 + 'preview-error': 'Error loading preview'
  207 + }
  208 + }
  209 +};
1 let workflowEditorInitialized = false; 1 let workflowEditorInitialized = false;
2 2
  3 +// 初始化i18next多语言支持
  4 +function initializeI18n() {
  5 + // 获取浏览器语言,默认为中文
  6 + const browserLang = navigator.language || 'zh-CN';
  7 + const defaultLang = browserLang.startsWith('zh') ? 'zh-CN' : 'en-US';
  8 +
  9 + // 初始化i18next
  10 + i18next.init({
  11 + lng: localStorage.getItem('preferred_language') || defaultLang,
  12 + resources: i18nResources,
  13 + fallbackLng: 'zh-CN',
  14 + }).then(function(t) {
  15 + // 更新当前语言显示
  16 + updateLanguageDisplay();
  17 +
  18 + // 应用翻译到所有元素
  19 + applyTranslations();
  20 + });
  21 +}
  22 +
  23 +// 更新语言显示
  24 +function updateLanguageDisplay() {
  25 + const currentLang = i18next.language;
  26 + const displayName = currentLang === 'zh-CN' ? '中文' : 'English';
  27 + document.getElementById('currentLanguage').textContent = displayName;
  28 +}
  29 +
  30 +// 应用翻译到所有元素
  31 +function applyTranslations() {
  32 + // 翻译data-i18n属性的元素
  33 + document.querySelectorAll('[data-i18n]').forEach(element => {
  34 + const key = element.getAttribute('data-i18n');
  35 + element.textContent = i18next.t(key);
  36 + });
  37 +
  38 + // 翻译title属性
  39 + document.querySelectorAll('[data-i18n-title]').forEach(element => {
  40 + const key = element.getAttribute('data-i18n-title');
  41 + element.title = i18next.t(key);
  42 + });
  43 +
  44 + // 更新页面标题
  45 + document.title = i18next.t('page-title');
  46 +}
  47 +
  48 +// 切换语言
  49 +function switchLanguage(lang) {
  50 + // 保存语言偏好到本地存储
  51 + localStorage.setItem('preferred_language', lang);
  52 +
  53 + // 更改i18next语言
  54 + i18next.changeLanguage(lang).then(() => {
  55 + // 更新语言显示
  56 + updateLanguageDisplay();
  57 +
  58 + // 应用翻译
  59 + applyTranslations();
  60 + });
  61 +}
  62 +
3 document.addEventListener('DOMContentLoaded', function() { 63 document.addEventListener('DOMContentLoaded', function() {
4 // 检查是否已初始化,防止多次执行 64 // 检查是否已初始化,防止多次执行
5 if (workflowEditorInitialized) { 65 if (workflowEditorInitialized) {
@@ -8,6 +68,17 @@ document.addEventListener('DOMContentLoaded', function() { @@ -8,6 +68,17 @@ document.addEventListener('DOMContentLoaded', function() {
8 } 68 }
9 workflowEditorInitialized = true; 69 workflowEditorInitialized = true;
10 70
  71 + // 初始化多语言支持
  72 + initializeI18n();
  73 +
  74 + // 添加语言切换事件
  75 + document.querySelectorAll('.language-option').forEach(option => {
  76 + option.addEventListener('click', function() {
  77 + const lang = this.getAttribute('data-lang');
  78 + switchLanguage(lang);
  79 + });
  80 + });
  81 +
11 // 工作流编辑器的主要元素 82 // 工作流编辑器的主要元素
12 const workflowCanvas = document.getElementById('workflowCanvas'); 83 const workflowCanvas = document.getElementById('workflowCanvas');
13 const connectionsSvg = document.getElementById('connectionsSvg'); 84 const connectionsSvg = document.getElementById('connectionsSvg');
@@ -384,6 +384,7 @@ @@ -384,6 +384,7 @@
384 384
385 // 初始化页面 385 // 初始化页面
386 window.onload = function() { 386 window.onload = function() {
  387 + updateLanguage(); // 设置初始语言
387 loadPredefinedTopics(); 388 loadPredefinedTopics();
388 }; 389 };
389 390
@@ -807,12 +808,12 @@ @@ -807,12 +808,12 @@
807 808
808 const data = await response.json(); 809 const data = await response.json();
809 if (data.success) { 810 if (data.success) {
810 - alert('数据库连接测试成功!'); 811 + alert(currentLang === 'zh' ? '数据库连接测试成功!' : 'Database connection test successful!');
811 } else { 812 } else {
812 - alert('数据库连接测试失败:' + data.message); 813 + alert((currentLang === 'zh' ? '数据库连接测试失败:' : 'Database connection test failed: ') + data.message);
813 } 814 }
814 } catch (error) { 815 } catch (error) {
815 - alert('测试连接时发生错误:' + error.message); 816 + alert((currentLang === 'zh' ? '测试连接时发生错误:' : 'Error during connection test: ') + error.message);
816 } 817 }
817 } 818 }
818 819
@@ -857,13 +858,13 @@ @@ -857,13 +858,13 @@
857 .then(response => response.json()) 858 .then(response => response.json())
858 .then(data => { 859 .then(data => {
859 if (data.success) { 860 if (data.success) {
860 - alert('配置已保存!'); 861 + alert(currentLang === 'zh' ? '配置已保存!' : 'Configuration saved!');
861 } else { 862 } else {
862 - alert('保存失败:' + data.message); 863 + alert((currentLang === 'zh' ? '保存失败:' : 'Save failed: ') + data.message);
863 } 864 }
864 }) 865 })
865 .catch(error => { 866 .catch(error => {
866 - alert('保存出错:' + error.message); 867 + alert((currentLang === 'zh' ? '保存出错:' : 'Error saving: ') + error.message);
867 }); 868 });
868 } 869 }
869 870
@@ -891,7 +892,7 @@ @@ -891,7 +892,7 @@
891 async function generateConfig() { 892 async function generateConfig() {
892 const prompt = document.getElementById('aiPrompt').value.trim(); 893 const prompt = document.getElementById('aiPrompt').value.trim();
893 if (!prompt) { 894 if (!prompt) {
894 - alert('请输入您的爬虫需求描述!'); 895 + alert(currentLang === 'zh' ? '请输入您的爬虫需求描述!' : 'Please enter your crawler requirements!');
895 return; 896 return;
896 } 897 }
897 898
@@ -933,13 +934,13 @@ @@ -933,13 +934,13 @@
933 updateSelectedTopicsList(); 934 updateSelectedTopicsList();
934 935
935 // 添加提示 936 // 添加提示
936 - updateCrawlLog('AI配置已自动应用'); 937 + updateCrawlLog(currentLang === 'zh' ? 'AI配置已自动应用' : 'AI configuration automatically applied');
937 } 938 }
938 } else { 939 } else {
939 throw new Error(data.message); 940 throw new Error(data.message);
940 } 941 }
941 } catch (error) { 942 } catch (error) {
942 - aiSuggestion.textContent = '生成配置时出错:' + error.message; 943 + aiSuggestion.textContent = (currentLang === 'zh' ? '生成配置时出错:' : 'Error generating configuration: ') + error.message;
943 aiResponse.style.display = 'block'; 944 aiResponse.style.display = 'block';
944 } 945 }
945 } 946 }
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 <head> 3 <head>
4 <meta charset="UTF-8"> 4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 - <title>工作流编辑器 - 微博舆情分析系统</title> 6 + <title data-i18n="page-title">工作流编辑器 - 微博舆情分析系统</title>
7 <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet"> 7 <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
8 <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.2.0/css/all.min.css" rel="stylesheet"> 8 <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.2.0/css/all.min.css" rel="stylesheet">
9 <link href="https://cdn.jsdelivr.net/npm/jsoneditor@9.5.0/dist/jsoneditor.min.css" rel="stylesheet"> 9 <link href="https://cdn.jsdelivr.net/npm/jsoneditor@9.5.0/dist/jsoneditor.min.css" rel="stylesheet">
@@ -359,7 +359,7 @@ @@ -359,7 +359,7 @@
359 <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> 359 <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
360 <div class="container-fluid"> 360 <div class="container-fluid">
361 <a class="navbar-brand" href="#"> 361 <a class="navbar-brand" href="#">
362 - <i class="fas fa-project-diagram me-2"></i>工作流编辑器 362 + <i class="fas fa-project-diagram me-2"></i><span data-i18n="navbar-brand">工作流编辑器</span>
363 </a> 363 </a>
364 <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> 364 <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
365 <span class="navbar-toggler-icon"></span> 365 <span class="navbar-toggler-icon"></span>
@@ -367,21 +367,31 @@ @@ -367,21 +367,31 @@
367 <div class="collapse navbar-collapse" id="navbarNav"> 367 <div class="collapse navbar-collapse" id="navbarNav">
368 <ul class="navbar-nav me-auto"> 368 <ul class="navbar-nav me-auto">
369 <li class="nav-item"> 369 <li class="nav-item">
370 - <a class="nav-link active" href="#">可视化编辑</a> 370 + <a class="nav-link active" href="#"><span data-i18n="nav-visual-editor">可视化编辑</span></a>
371 </li> 371 </li>
372 <li class="nav-item"> 372 <li class="nav-item">
373 - <a class="nav-link" href="#">模板管理</a> 373 + <a class="nav-link" href="#"><span data-i18n="nav-template-mgmt">模板管理</span></a>
374 </li> 374 </li>
375 <li class="nav-item"> 375 <li class="nav-item">
376 - <a class="nav-link" href="#">任务列表</a> 376 + <a class="nav-link" href="#"><span data-i18n="nav-task-list">任务列表</span></a>
377 </li> 377 </li>
378 </ul> 378 </ul>
379 <div class="d-flex"> 379 <div class="d-flex">
  380 + <!-- 添加语言切换按钮 -->
  381 + <div class="dropdown me-2">
  382 + <button class="btn btn-outline-light dropdown-toggle" type="button" id="languageDropdown" data-bs-toggle="dropdown" aria-expanded="false">
  383 + <i class="fas fa-language me-1"></i><span id="currentLanguage">中文</span>
  384 + </button>
  385 + <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="languageDropdown">
  386 + <li><button class="dropdown-item language-option" data-lang="zh-CN">中文</button></li>
  387 + <li><button class="dropdown-item language-option" data-lang="en-US">English</button></li>
  388 + </ul>
  389 + </div>
380 <button id="saveWorkflowBtn" class="btn btn-success me-2"> 390 <button id="saveWorkflowBtn" class="btn btn-success me-2">
381 - <i class="fas fa-save me-1"></i>保存 391 + <i class="fas fa-save me-1"></i><span data-i18n="btn-save">保存</span>
382 </button> 392 </button>
383 <button id="runWorkflowBtn" class="btn btn-primary"> 393 <button id="runWorkflowBtn" class="btn btn-primary">
384 - <i class="fas fa-play me-1"></i>运行 394 + <i class="fas fa-play me-1"></i><span data-i18n="btn-run">运行</span>
385 </button> 395 </button>
386 </div> 396 </div>
387 </div> 397 </div>
@@ -394,72 +404,72 @@ @@ -394,72 +404,72 @@
394 <div class="col-md-3 col-lg-2 d-md-block sidebar"> 404 <div class="col-md-3 col-lg-2 d-md-block sidebar">
395 <div class="d-flex justify-content-center mb-4"> 405 <div class="d-flex justify-content-center mb-4">
396 <div class="btn-group"> 406 <div class="btn-group">
397 - <button class="btn btn-outline-primary active" id="componentsTabBtn">组件</button>  
398 - <button class="btn btn-outline-primary" id="templatesTabBtn">模板</button> 407 + <button class="btn btn-outline-primary active" id="componentsTabBtn"><span data-i18n="tab-components">组件</span></button>
  408 + <button class="btn btn-outline-primary" id="templatesTabBtn"><span data-i18n="tab-templates">模板</span></button>
399 </div> 409 </div>
400 </div> 410 </div>
401 411
402 <!-- 组件面板保持不变 --> 412 <!-- 组件面板保持不变 -->
403 <div id="componentsPanel"> 413 <div id="componentsPanel">
404 <div class="component-container"> 414 <div class="component-container">
405 - <h6><i class="fas fa-database me-2"></i>数据源</h6> 415 + <h6><i class="fas fa-database me-2"></i><span data-i18n="comp-data-source">数据源</span></h6>
406 <div class="component-list"> 416 <div class="component-list">
407 <div class="component-item" data-type="data_source" data-subtype="database"> 417 <div class="component-item" data-type="data_source" data-subtype="database">
408 - <i class="fas fa-table me-2"></i>数据库 418 + <i class="fas fa-table me-2"></i><span data-i18n="comp-database">数据库</span>
409 </div> 419 </div>
410 <div class="component-item" data-type="data_source" data-subtype="file"> 420 <div class="component-item" data-type="data_source" data-subtype="file">
411 - <i class="fas fa-file-alt me-2"></i>文件 421 + <i class="fas fa-file-alt me-2"></i><span data-i18n="comp-file">文件</span>
412 </div> 422 </div>
413 <div class="component-item" data-type="data_source" data-subtype="crawler"> 423 <div class="component-item" data-type="data_source" data-subtype="crawler">
414 - <i class="fas fa-spider me-2"></i>爬虫 424 + <i class="fas fa-spider me-2"></i><span data-i18n="comp-crawler">爬虫</span>
415 </div> 425 </div>
416 </div> 426 </div>
417 </div> 427 </div>
418 428
419 <div class="component-container"> 429 <div class="component-container">
420 - <h6><i class="fas fa-filter me-2"></i>数据处理</h6> 430 + <h6><i class="fas fa-filter me-2"></i><span data-i18n="comp-data-processing">数据处理</span></h6>
421 <div class="component-list"> 431 <div class="component-list">
422 <div class="component-item" data-type="preprocessing" data-subtype="filter"> 432 <div class="component-item" data-type="preprocessing" data-subtype="filter">
423 - <i class="fas fa-filter me-2"></i>过滤 433 + <i class="fas fa-filter me-2"></i><span data-i18n="comp-filter">过滤</span>
424 </div> 434 </div>
425 <div class="component-item" data-type="preprocessing" data-subtype="sort"> 435 <div class="component-item" data-type="preprocessing" data-subtype="sort">
426 - <i class="fas fa-sort me-2"></i>排序 436 + <i class="fas fa-sort me-2"></i><span data-i18n="comp-sort">排序</span>
427 </div> 437 </div>
428 <div class="component-item" data-type="preprocessing" data-subtype="aggregate"> 438 <div class="component-item" data-type="preprocessing" data-subtype="aggregate">
429 - <i class="fas fa-layer-group me-2"></i>聚合 439 + <i class="fas fa-layer-group me-2"></i><span data-i18n="comp-aggregate">聚合</span>
430 </div> 440 </div>
431 </div> 441 </div>
432 </div> 442 </div>
433 443
434 <div class="component-container"> 444 <div class="component-container">
435 - <h6><i class="fas fa-brain me-2"></i>模型分析</h6> 445 + <h6><i class="fas fa-brain me-2"></i><span data-i18n="comp-model-analysis">模型分析</span></h6>
436 <div class="component-list"> 446 <div class="component-list">
437 <div class="component-item" data-type="model" data-subtype="sentiment"> 447 <div class="component-item" data-type="model" data-subtype="sentiment">
438 - <i class="fas fa-smile me-2"></i>情感分析 448 + <i class="fas fa-smile me-2"></i><span data-i18n="comp-sentiment">情感分析</span>
439 </div> 449 </div>
440 <div class="component-item" data-type="model" data-subtype="topic"> 450 <div class="component-item" data-type="model" data-subtype="topic">
441 - <i class="fas fa-tags me-2"></i>话题分类 451 + <i class="fas fa-tags me-2"></i><span data-i18n="comp-topic">话题分类</span>
442 </div> 452 </div>
443 <div class="component-item" data-type="model" data-subtype="keywords"> 453 <div class="component-item" data-type="model" data-subtype="keywords">
444 - <i class="fas fa-key me-2"></i>关键词提取 454 + <i class="fas fa-key me-2"></i><span data-i18n="comp-keywords">关键词提取</span>
445 </div> 455 </div>
446 <div class="component-item" data-type="model" data-subtype="summarize"> 456 <div class="component-item" data-type="model" data-subtype="summarize">
447 - <i class="fas fa-compress-alt me-2"></i>文本摘要 457 + <i class="fas fa-compress-alt me-2"></i><span data-i18n="comp-summarize">文本摘要</span>
448 </div> 458 </div>
449 </div> 459 </div>
450 </div> 460 </div>
451 461
452 <div class="component-container"> 462 <div class="component-container">
453 - <h6><i class="fas fa-chart-bar me-2"></i>可视化</h6> 463 + <h6><i class="fas fa-chart-bar me-2"></i><span data-i18n="comp-visualization">可视化</span></h6>
454 <div class="component-list"> 464 <div class="component-list">
455 <div class="component-item" data-type="visualization" data-subtype="chart"> 465 <div class="component-item" data-type="visualization" data-subtype="chart">
456 - <i class="fas fa-chart-line me-2"></i>图表 466 + <i class="fas fa-chart-line me-2"></i><span data-i18n="comp-chart">图表</span>
457 </div> 467 </div>
458 <div class="component-item" data-type="visualization" data-subtype="table"> 468 <div class="component-item" data-type="visualization" data-subtype="table">
459 - <i class="fas fa-table me-2"></i>表格 469 + <i class="fas fa-table me-2"></i><span data-i18n="comp-table">表格</span>
460 </div> 470 </div>
461 <div class="component-item" data-type="visualization" data-subtype="wordcloud"> 471 <div class="component-item" data-type="visualization" data-subtype="wordcloud">
462 - <i class="fas fa-cloud me-2"></i>词云 472 + <i class="fas fa-cloud me-2"></i><span data-i18n="comp-wordcloud">词云</span>
463 </div> 473 </div>
464 </div> 474 </div>
465 </div> 475 </div>
@@ -469,9 +479,9 @@ @@ -469,9 +479,9 @@
469 <div id="templatesPanel" style="display: none;"> 479 <div id="templatesPanel" style="display: none;">
470 <div class="mb-4"> 480 <div class="mb-4">
471 <div class="d-flex justify-content-between align-items-center mb-3"> 481 <div class="d-flex justify-content-between align-items-center mb-3">
472 - <h6 class="mb-0">爬虫模板</h6> 482 + <h6 class="mb-0"><span data-i18n="templates-crawler">爬虫模板</span></h6>
473 <button class="btn btn-sm btn-outline-primary"> 483 <button class="btn btn-sm btn-outline-primary">
474 - <i class="fas fa-plus"></i> 新建 484 + <i class="fas fa-plus"></i> <span data-i18n="btn-create-new">新建</span>
475 </button> 485 </button>
476 </div> 486 </div>
477 <div class="templates-wrapper"> 487 <div class="templates-wrapper">
@@ -483,9 +493,9 @@ @@ -483,9 +493,9 @@
483 493
484 <div class="mb-4"> 494 <div class="mb-4">
485 <div class="d-flex justify-content-between align-items-center mb-3"> 495 <div class="d-flex justify-content-between align-items-center mb-3">
486 - <h6 class="mb-0">分析流程模板</h6> 496 + <h6 class="mb-0"><span data-i18n="templates-analysis">分析流程模板</span></h6>
487 <button class="btn btn-sm btn-outline-primary"> 497 <button class="btn btn-sm btn-outline-primary">
488 - <i class="fas fa-plus"></i> 新建 498 + <i class="fas fa-plus"></i> <span data-i18n="btn-create-new">新建</span>
489 </button> 499 </button>
490 </div> 500 </div>
491 <div class="templates-wrapper"> 501 <div class="templates-wrapper">
@@ -502,30 +512,30 @@ @@ -502,30 +512,30 @@
502 <!-- 添加工作流工具栏 --> 512 <!-- 添加工作流工具栏 -->
503 <div class="d-flex justify-content-between align-items-center mb-3" id="workflowToolbar"> 513 <div class="d-flex justify-content-between align-items-center mb-3" id="workflowToolbar">
504 <div class="btn-group"> 514 <div class="btn-group">
505 - <button id="undoBtn" class="btn btn-sm btn-outline-secondary" title="撤销"> 515 + <button id="undoBtn" class="btn btn-sm btn-outline-secondary" title="撤销" data-i18n-title="btn-undo">
506 <i class="fas fa-undo"></i> 516 <i class="fas fa-undo"></i>
507 </button> 517 </button>
508 - <button id="redoBtn" class="btn btn-sm btn-outline-secondary" title="重做"> 518 + <button id="redoBtn" class="btn btn-sm btn-outline-secondary" title="重做" data-i18n-title="btn-redo">
509 <i class="fas fa-redo"></i> 519 <i class="fas fa-redo"></i>
510 </button> 520 </button>
511 - <button id="zoomInBtn" class="btn btn-sm btn-outline-secondary" title="放大"> 521 + <button id="zoomInBtn" class="btn btn-sm btn-outline-secondary" title="放大" data-i18n-title="btn-zoom-in">
512 <i class="fas fa-search-plus"></i> 522 <i class="fas fa-search-plus"></i>
513 </button> 523 </button>
514 - <button id="zoomOutBtn" class="btn btn-sm btn-outline-secondary" title="缩小"> 524 + <button id="zoomOutBtn" class="btn btn-sm btn-outline-secondary" title="缩小" data-i18n-title="btn-zoom-out">
515 <i class="fas fa-search-minus"></i> 525 <i class="fas fa-search-minus"></i>
516 </button> 526 </button>
517 - <button id="fitViewBtn" class="btn btn-sm btn-outline-secondary" title="适应视图"> 527 + <button id="fitViewBtn" class="btn btn-sm btn-outline-secondary" title="适应视图" data-i18n-title="btn-fit-view">
518 <i class="fas fa-expand"></i> 528 <i class="fas fa-expand"></i>
519 </button> 529 </button>
520 </div> 530 </div>
521 <div> 531 <div>
522 - <button id="validateWorkflowBtn" class="btn btn-sm btn-outline-primary" title="验证工作流">  
523 - <i class="fas fa-check-circle"></i> 验证 532 + <button id="validateWorkflowBtn" class="btn btn-sm btn-outline-primary" title="验证工作流" data-i18n-title="btn-validate">
  533 + <i class="fas fa-check-circle"></i> <span data-i18n="btn-validate">验证</span>
524 </button> 534 </button>
525 - <button id="exportWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导出工作流"> 535 + <button id="exportWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导出工作流" data-i18n-title="btn-export">
526 <i class="fas fa-file-export"></i> 536 <i class="fas fa-file-export"></i>
527 </button> 537 </button>
528 - <button id="importWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导入工作流"> 538 + <button id="importWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导入工作流" data-i18n-title="btn-import">
529 <i class="fas fa-file-import"></i> 539 <i class="fas fa-file-import"></i>
530 </button> 540 </button>
531 </div> 541 </div>
@@ -540,12 +550,12 @@ @@ -540,12 +550,12 @@
540 550
541 <!-- 添加工作流状态栏 --> 551 <!-- 添加工作流状态栏 -->
542 <div class="d-flex justify-content-between align-items-center p-2 bg-light rounded mt-3" id="workflowStatusBar" style="display: none !important;"> 552 <div class="d-flex justify-content-between align-items-center p-2 bg-light rounded mt-3" id="workflowStatusBar" style="display: none !important;">
543 - <div id="workflowStatusMessage" class="text-muted"> 553 + <div id="workflowStatusMessage" class="text-muted" data-i18n="workflow-status-message">
544 工作流就绪。拖拽左侧组件到画布创建节点。 554 工作流就绪。拖拽左侧组件到画布创建节点。
545 </div> 555 </div>
546 <div class="d-flex"> 556 <div class="d-flex">
547 - <div class="me-3">节点: <span id="nodeCount">0</span></div>  
548 - <div>连接: <span id="connectionCount">0</span></div> 557 + <div class="me-3"><span data-i18n="nodes">节点</span>: <span id="nodeCount">0</span></div>
  558 + <div><span data-i18n="connections">连接</span>: <span id="connectionCount">0</span></div>
549 </div> 559 </div>
550 </div> 560 </div>
551 </div> 561 </div>
@@ -555,7 +565,7 @@ @@ -555,7 +565,7 @@
555 <!-- 属性面板 --> 565 <!-- 属性面板 -->
556 <div class="properties-panel" id="propertiesPanel"> 566 <div class="properties-panel" id="propertiesPanel">
557 <div class="d-flex justify-content-between align-items-center mb-3"> 567 <div class="d-flex justify-content-between align-items-center mb-3">
558 - <h5 class="mb-0">组件属性</h5> 568 + <h5 class="mb-0"><span data-i18n="properties-title">组件属性</span></h5>
559 <button class="btn-close" id="closePropertiesBtn"></button> 569 <button class="btn-close" id="closePropertiesBtn"></button>
560 </div> 570 </div>
561 <div id="propertiesContent"> 571 <div id="propertiesContent">
@@ -568,35 +578,35 @@ @@ -568,35 +578,35 @@
568 <div class="modal-dialog"> 578 <div class="modal-dialog">
569 <div class="modal-content"> 579 <div class="modal-content">
570 <div class="modal-header"> 580 <div class="modal-header">
571 - <h5 class="modal-title">保存为模板</h5> 581 + <h5 class="modal-title" data-i18n="modal-save-template">保存为模板</h5>
572 <button type="button" class="btn-close" data-bs-dismiss="modal"></button> 582 <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
573 </div> 583 </div>
574 <div class="modal-body"> 584 <div class="modal-body">
575 <form id="saveTemplateForm"> 585 <form id="saveTemplateForm">
576 <div class="mb-3"> 586 <div class="mb-3">
577 - <label for="templateName" class="form-label">模板名称</label> 587 + <label for="templateName" class="form-label" data-i18n="template-name">模板名称</label>
578 <input type="text" class="form-control" id="templateName" required> 588 <input type="text" class="form-control" id="templateName" required>
579 </div> 589 </div>
580 <div class="mb-3"> 590 <div class="mb-3">
581 - <label for="templateDescription" class="form-label">描述</label> 591 + <label for="templateDescription" class="form-label" data-i18n="template-description">描述</label>
582 <textarea class="form-control" id="templateDescription" rows="3"></textarea> 592 <textarea class="form-control" id="templateDescription" rows="3"></textarea>
583 </div> 593 </div>
584 <div class="mb-3"> 594 <div class="mb-3">
585 - <label for="templateIcon" class="form-label">图标</label> 595 + <label for="templateIcon" class="form-label" data-i18n="template-icon">图标</label>
586 <select class="form-select" id="templateIcon"> 596 <select class="form-select" id="templateIcon">
587 - <option value="chart-line">📊 图表</option>  
588 - <option value="filter">🔍 过滤</option>  
589 - <option value="spider">🕸️ 爬虫</option>  
590 - <option value="brain">🧠 AI分析</option>  
591 - <option value="database">💾 数据库</option>  
592 - <option value="cloud">☁️ 词云</option> 597 + <option value="chart-line">📊 <span data-i18n="icon-chart">图表</span></option>
  598 + <option value="filter">🔍 <span data-i18n="icon-filter">过滤</span></option>
  599 + <option value="spider">🕸️ <span data-i18n="icon-crawler">爬虫</span></option>
  600 + <option value="brain">🧠 <span data-i18n="icon-ai">AI分析</span></option>
  601 + <option value="database">💾 <span data-i18n="icon-database">数据库</span></option>
  602 + <option value="cloud">☁️ <span data-i18n="icon-wordcloud">词云</span></option>
593 </select> 603 </select>
594 </div> 604 </div>
595 </form> 605 </form>
596 </div> 606 </div>
597 <div class="modal-footer"> 607 <div class="modal-footer">
598 - <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>  
599 - <button type="button" class="btn btn-primary" id="saveTemplateBtn">保存</button> 608 + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="btn-cancel">取消</button>
  609 + <button type="button" class="btn btn-primary" id="saveTemplateBtn" data-i18n="btn-save">保存</button>
600 </div> 610 </div>
601 </div> 611 </div>
602 </div> 612 </div>
@@ -606,21 +616,21 @@ @@ -606,21 +616,21 @@
606 <div class="modal-dialog"> 616 <div class="modal-dialog">
607 <div class="modal-content"> 617 <div class="modal-content">
608 <div class="modal-header"> 618 <div class="modal-header">
609 - <h5 class="modal-title">运行工作流</h5> 619 + <h5 class="modal-title" data-i18n="modal-run-workflow">运行工作流</h5>
610 <button type="button" class="btn-close" data-bs-dismiss="modal"></button> 620 <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
611 </div> 621 </div>
612 <div class="modal-body"> 622 <div class="modal-body">
613 - <p>确认要运行当前工作流吗?</p> 623 + <p data-i18n="run-workflow-confirm">确认要运行当前工作流吗?</p>
614 <div class="form-check mb-3"> 624 <div class="form-check mb-3">
615 <input class="form-check-input" type="checkbox" id="saveBeforeRun" checked> 625 <input class="form-check-input" type="checkbox" id="saveBeforeRun" checked>
616 - <label class="form-check-label" for="saveBeforeRun"> 626 + <label class="form-check-label" for="saveBeforeRun" data-i18n="save-before-run">
617 运行前保存工作流 627 运行前保存工作流
618 </label> 628 </label>
619 </div> 629 </div>
620 </div> 630 </div>
621 <div class="modal-footer"> 631 <div class="modal-footer">
622 - <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>  
623 - <button type="button" class="btn btn-primary" id="confirmRunBtn">运行</button> 632 + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="btn-cancel">取消</button>
  633 + <button type="button" class="btn btn-primary" id="confirmRunBtn" data-i18n="btn-run">运行</button>
624 </div> 634 </div>
625 </div> 635 </div>
626 </div> 636 </div>
@@ -630,13 +640,13 @@ @@ -630,13 +640,13 @@
630 <div class="modal-dialog modal-lg"> 640 <div class="modal-dialog modal-lg">
631 <div class="modal-content"> 641 <div class="modal-content">
632 <div class="modal-header"> 642 <div class="modal-header">
633 - <h5 class="modal-title">任务执行状态</h5> 643 + <h5 class="modal-title" data-i18n="modal-task-status">任务执行状态</h5>
634 <button type="button" class="btn-close" data-bs-dismiss="modal"></button> 644 <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
635 </div> 645 </div>
636 <div class="modal-body"> 646 <div class="modal-body">
637 <div class="mb-3"> 647 <div class="mb-3">
638 <h6 class="d-flex align-items-center"> 648 <h6 class="d-flex align-items-center">
639 - <i class="fas fa-tasks me-2"></i>进度 649 + <i class="fas fa-tasks me-2"></i><span data-i18n="task-progress">进度</span>
640 <div class="ms-auto" id="taskProgressPercentage">0%</div> 650 <div class="ms-auto" id="taskProgressPercentage">0%</div>
641 </h6> 651 </h6>
642 <div class="progress" style="height: 10px;"> 652 <div class="progress" style="height: 10px;">
@@ -645,46 +655,46 @@ @@ -645,46 +655,46 @@
645 </div> 655 </div>
646 <div class="mb-3"> 656 <div class="mb-3">
647 <h6 class="d-flex align-items-center"> 657 <h6 class="d-flex align-items-center">
648 - <i class="fas fa-info-circle me-2"></i>状态信息  
649 - <span id="taskStatusBadge" class="ms-2 badge bg-info">等待中</span> 658 + <i class="fas fa-info-circle me-2"></i><span data-i18n="task-status-info">状态信息</span>
  659 + <span id="taskStatusBadge" class="ms-2 badge bg-info" data-i18n="task-waiting">等待中</span>
650 </h6> 660 </h6>
651 <div id="taskStatusInfo" class="p-3 bg-light rounded border"> 661 <div id="taskStatusInfo" class="p-3 bg-light rounded border">
652 <div class="row g-2"> 662 <div class="row g-2">
653 <div class="col-md-6"> 663 <div class="col-md-6">
654 - <p class="mb-1"><strong>任务ID:</strong> <span id="taskIdDisplay">-</span></p>  
655 - <p class="mb-1"><strong>状态:</strong> <span id="taskStatusDisplay">-</span></p> 664 + <p class="mb-1"><strong data-i18n="task-id">任务ID:</strong> <span id="taskIdDisplay">-</span></p>
  665 + <p class="mb-1"><strong data-i18n="task-status">状态:</strong> <span id="taskStatusDisplay">-</span></p>
656 </div> 666 </div>
657 <div class="col-md-6"> 667 <div class="col-md-6">
658 - <p class="mb-1"><strong>开始时间:</strong> <span id="taskStartTimeDisplay">-</span></p>  
659 - <p class="mb-1"><strong>完成时间:</strong> <span id="taskCompleteTimeDisplay">-</span></p> 668 + <p class="mb-1"><strong data-i18n="task-start-time">开始时间:</strong> <span id="taskStartTimeDisplay">-</span></p>
  669 + <p class="mb-1"><strong data-i18n="task-complete-time">完成时间:</strong> <span id="taskCompleteTimeDisplay">-</span></p>
660 </div> 670 </div>
661 </div> 671 </div>
662 <div class="mt-2" id="taskDetailsContainer"> 672 <div class="mt-2" id="taskDetailsContainer">
663 - <p class="mb-1"><strong>当前步骤:</strong> <span id="taskCurrentStepDisplay">等待开始</span></p>  
664 - <p class="mb-0"><strong>耗时:</strong> <span id="taskElapsedTimeDisplay">0秒</span></p> 673 + <p class="mb-1"><strong data-i18n="task-current-step">当前步骤:</strong> <span id="taskCurrentStepDisplay" data-i18n="waiting-to-start">等待开始</span></p>
  674 + <p class="mb-0"><strong data-i18n="task-elapsed-time">耗时:</strong> <span id="taskElapsedTimeDisplay">0秒</span></p>
665 </div> 675 </div>
666 </div> 676 </div>
667 </div> 677 </div>
668 <div> 678 <div>
669 <h6 class="d-flex align-items-center"> 679 <h6 class="d-flex align-items-center">
670 - <i class="fas fa-chart-bar me-2"></i>结果预览  
671 - <button class="btn btn-sm btn-outline-secondary ms-auto" id="refreshPreviewBtn" title="刷新预览"> 680 + <i class="fas fa-chart-bar me-2"></i><span data-i18n="task-result-preview">结果预览</span>
  681 + <button class="btn btn-sm btn-outline-secondary ms-auto" id="refreshPreviewBtn" title="刷新预览" data-i18n-title="refresh-preview">
672 <i class="fas fa-sync-alt"></i> 682 <i class="fas fa-sync-alt"></i>
673 </button> 683 </button>
674 </h6> 684 </h6>
675 <div id="taskResultPreview" class="p-3 bg-light rounded border" style="max-height: 300px; overflow: auto;"> 685 <div id="taskResultPreview" class="p-3 bg-light rounded border" style="max-height: 300px; overflow: auto;">
676 <div class="text-center py-4" id="previewLoadingIndicator"> 686 <div class="text-center py-4" id="previewLoadingIndicator">
677 <div class="spinner-border text-primary" role="status"> 687 <div class="spinner-border text-primary" role="status">
678 - <span class="visually-hidden">加载中...</span> 688 + <span class="visually-hidden" data-i18n="loading">加载中...</span>
679 </div> 689 </div>
680 - <p class="text-muted mt-2">任务运行中,正在准备预览数据...</p> 690 + <p class="text-muted mt-2" data-i18n="task-running-preparing">任务运行中,正在准备预览数据...</p>
681 </div> 691 </div>
682 <div id="previewContent" style="display: none;"> 692 <div id="previewContent" style="display: none;">
683 - <p class="text-muted">任务完成后将显示结果预览...</p> 693 + <p class="text-muted" data-i18n="preview-after-task">任务完成后将显示结果预览...</p>
684 </div> 694 </div>
685 <div id="previewError" class="alert alert-danger" style="display: none;"> 695 <div id="previewError" class="alert alert-danger" style="display: none;">
686 <i class="fas fa-exclamation-triangle me-2"></i> 696 <i class="fas fa-exclamation-triangle me-2"></i>
687 - <span id="errorMessage">加载预览时发生错误</span> 697 + <span id="errorMessage" data-i18n="preview-error">加载预览时发生错误</span>
688 </div> 698 </div>
689 </div> 699 </div>
690 <div class="mt-2 text-end"> 700 <div class="mt-2 text-end">
@@ -695,13 +705,13 @@ @@ -695,13 +705,13 @@
695 <div class="modal-footer d-flex justify-content-between"> 705 <div class="modal-footer d-flex justify-content-between">
696 <div> 706 <div>
697 <button type="button" class="btn btn-danger" id="cancelTaskBtn"> 707 <button type="button" class="btn btn-danger" id="cancelTaskBtn">
698 - <i class="fas fa-stop-circle me-1"></i>取消任务 708 + <i class="fas fa-stop-circle me-1"></i><span data-i18n="btn-cancel-task">取消任务</span>
699 </button> 709 </button>
700 </div> 710 </div>
701 <div> 711 <div>
702 - <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button> 712 + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="btn-close">关闭</button>
703 <button type="button" class="btn btn-primary" id="viewResultBtn"> 713 <button type="button" class="btn btn-primary" id="viewResultBtn">
704 - <i class="fas fa-external-link-alt me-1"></i>查看完整结果 714 + <i class="fas fa-external-link-alt me-1"></i><span data-i18n="btn-view-full-result">查看完整结果</span>
705 </button> 715 </button>
706 </div> 716 </div>
707 </div> 717 </div>
@@ -715,6 +725,10 @@ @@ -715,6 +725,10 @@
715 <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script> 725 <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
716 <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script> 726 <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
717 <script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.5.0/dist/jsoneditor.min.js"></script> 727 <script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.5.0/dist/jsoneditor.min.js"></script>
  728 + <!-- 添加i18next库 -->
  729 + <script src="https://cdn.jsdelivr.net/npm/i18next@21.8.10/i18next.min.js"></script>
  730 + <script src="https://cdn.jsdelivr.net/npm/jquery-i18next@1.2.1/jquery-i18next.min.js"></script>
  731 + <script src="\static\js\i18n\translations.js"></script>
718 <script src="\static\js\workflow_editor.js"></script> 732 <script src="\static\js\workflow_editor.js"></script>
719 </body> 733 </body>
720 </html> 734 </html>