diff --git a/background.js b/background.js index 02ef4c82..43a96f15 100644 --- a/background.js +++ b/background.js @@ -1276,7 +1276,7 @@ function setupDeclarativeNetRequestRules() { } // ============================================================ -// 状态管理(chrome.storage.session + chrome.storage.local) +// State management (chrome.storage.session + chrome.storage.local) // ============================================================ const PERSISTED_SETTING_DEFAULTS = { @@ -1518,14 +1518,14 @@ const DEFAULT_STATE = { nodeStatuses: { ...DEFAULT_NODE_STATUSES }, runtimeState: runtimeStateHelpers?.buildDefaultRuntimeState?.() || null, ...CONTRIBUTION_RUNTIME_DEFAULTS, - accounts: [], // 已生成账号记录:{ email, password, createdAt }。 - accountRunHistory: [], // 账号运行历史快照,实际持久化在 chrome.storage.local。 + accounts: [], // Generated account records: { email, password, createdAt }. + accountRunHistory: [], // Account run-history snapshot; actually persisted in chrome.storage.local. manualAliasUsage: {}, preservedAliases: {}, icloudAliasCache: [], icloudAliasCacheAt: 0, - logs: [], // 侧边栏展示的运行日志。 - ...PERSISTED_SETTING_DEFAULTS, // 合并 chrome.storage.local 中持久化保存的用户配置。 + logs: [], // Run logs shown in the side panel. + ...PERSISTED_SETTING_DEFAULTS, // Merge user config persisted in chrome.storage.local. luckmailApiKey: '', luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL, luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE, @@ -1539,14 +1539,14 @@ const DEFAULT_STATE = { heroSmsLastPriceUserLimit: '', heroSmsLastPriceAt: 0, pendingPhoneActivationConfirmation: null, - autoRunning: false, // 当前是否处于自动运行中。 - autoRunPhase: 'idle', // 当前自动运行阶段。 - autoRunCurrentRun: 0, // 自动运行当前执行到第几轮。 - autoRunTotalRuns: 1, // 自动运行计划总轮数。 - autoRunAttemptRun: 0, // 当前轮次的重试序号。 + autoRunning: false, // Whether auto-run is currently active. + autoRunPhase: 'idle', // Current auto-run phase. + autoRunCurrentRun: 0, // The current round number in auto-run. + autoRunTotalRuns: 1, // Total planned auto-run rounds. + autoRunAttemptRun: 0, // Retry sequence number within the current round. autoRunSessionId: 0, - autoRunRoundSummaries: [], // 自动运行轮次摘要。 - autoRunTimerPlan: null, // 自动运行可恢复计时计划快照。 + autoRunRoundSummaries: [], // Auto-run round summaries. + autoRunTimerPlan: null, // Resumable auto-run timer plan snapshot. autoRunCountdownAt: null, autoRunCountdownTitle: '', autoRunCountdownNote: '', @@ -15327,7 +15327,7 @@ async function executeStep8(state) { } // ============================================================ -// Step 9: 完成 OAuth(自动点击 + localhost 回调监听) +// Step 9: Complete OAuth (auto-click + localhost callback listener) // ============================================================ let webNavListener = null; @@ -15957,7 +15957,7 @@ async function executeStep9(state) { } // ============================================================ -// Step 10: 平台回调验证 +// Step 10: Platform callback verification // ============================================================ async function executeContributionStep10(state) { diff --git a/background/account-run-history.js b/background/account-run-history.js index c647eae0..2aa6921b 100644 --- a/background/account-run-history.js +++ b/background/account-run-history.js @@ -208,33 +208,33 @@ function buildFailureLabel(finalStatus, failedNodeId = '', failedStep = null, failureDetail = '', state = {}) { if (finalStatus === 'success') { - return '流程完成'; + return 'Flow completed'; } if (finalStatus === 'running') { - return '正在运行'; + return 'Running'; } if (finalStatus === 'stopped') { if (failedNodeId) { - return `节点 ${getNodeDisplayName(failedNodeId, state)} 停止`; + return `Node ${getNodeDisplayName(failedNodeId, state)} stopped`; } if (Number.isInteger(failedStep) && failedStep > 0) { - return `步骤 ${failedStep} 停止`; + return `Step ${failedStep} stopped`; } - return '流程已停止'; + return 'Flow stopped'; } if (finalStatus !== 'failed') { - return '无'; + return 'None'; } if (isPhoneVerificationFailure(failureDetail)) { - return '出现手机号验证'; + return 'Phone verification required'; } if (failedNodeId) { - return `节点 ${getNodeDisplayName(failedNodeId, state)} 失败`; + return `Node ${getNodeDisplayName(failedNodeId, state)} failed`; } if (Number.isInteger(failedStep) && failedStep > 0) { - return `步骤 ${failedStep} 失败`; + return `Step ${failedStep} failed`; } - return '流程失败'; + return 'Flow failed'; } function normalizeAccountIdentifierType(value = '') { @@ -674,11 +674,11 @@ try { payload = await response.json(); } catch (err) { - throw new Error(`账号记录快照同步失败:本地 helper 返回了无法解析的响应(${getErrorMessage(err)})`); + throw new Error(`Account run snapshot sync failed: local helper returned unparseable response (${getErrorMessage(err)})`); } if (!response.ok || payload?.ok === false) { - throw new Error(`账号记录快照同步失败:${payload?.error || `HTTP ${response.status}`}`); + throw new Error(`Account run snapshot sync failed: ${payload?.error || `HTTP ${response.status}`}`); } return payload?.filePath || ''; @@ -695,7 +695,7 @@ const history = await getPersistedAccountRunHistory(); const filePath = await syncAccountRunHistorySnapshot(history, state); if (filePath) { - await addLog(`账号记录快照已同步到本地:${filePath}`, 'info'); + await addLog(`Account run snapshot synced locally: ${filePath}`, 'info'); } } catch (err) { await addLog(getErrorMessage(err), 'warn'); @@ -712,7 +712,7 @@ try { const filePath = await syncAccountRunHistorySnapshot([], state); if (filePath) { - await addLog(`账号记录快照已同步到本地:${filePath}`, 'info'); + await addLog(`Account run snapshot synced locally: ${filePath}`, 'info'); } } catch (err) { await addLog(getErrorMessage(err), 'warn'); @@ -740,7 +740,7 @@ try { const filePath = await syncAccountRunHistorySnapshot(nextHistory, state); if (filePath) { - await addLog(`账号记录快照已同步到本地:${filePath}`, 'info'); + await addLog(`Account run snapshot synced locally: ${filePath}`, 'info'); } } catch (err) { await addLog(getErrorMessage(err), 'warn'); diff --git a/background/auto-run-controller.js b/background/auto-run-controller.js index 3fb405c8..a9fe971b 100644 --- a/background/auto-run-controller.js +++ b/background/auto-run-controller.js @@ -76,7 +76,7 @@ if (typeof runAutoSequenceFromNode === 'function') { return runAutoSequenceFromNode(startNodeId, context); } - throw new Error('自动运行节点执行器未接入。'); + throw new Error('Auto-run node executor is not connected.'); } function buildFreshStartStateSnapshot(state = {}) { @@ -302,18 +302,18 @@ function formatAutoRunFailureReasons(reasons = []) { if (!Array.isArray(reasons) || !reasons.length) { - return '未知错误'; + return 'Unknown error'; } const counts = new Map(); for (const reason of reasons) { - const normalized = String(reason || '').trim() || '未知错误'; + const normalized = String(reason || '').trim() || 'Unknown error'; counts.set(normalized, (counts.get(normalized) || 0) + 1); } return Array.from(counts.entries()) - .map(([reason, count]) => (count > 1 ? `${reason}(${count}次)` : reason)) - .join(';'); + .map(([reason, count]) => (count > 1 ? `${reason} (${count} times)` : reason)) + .join('; '); } function isPhoneNumberSupplyExhaustedFailure(errorLike) { @@ -360,40 +360,40 @@ const failedRounds = summaries.filter((item) => item.status === 'failed'); const pendingRounds = summaries.filter((item) => item.status === 'pending'); - await addLog('=== 自动运行汇总 ===', failedRounds.length ? 'warn' : 'ok'); + await addLog('=== Auto-run summary ===', failedRounds.length ? 'warn' : 'ok'); await addLog( - `总轮数:${totalRuns};成功:${successRounds.length};失败:${failedRounds.length};未完成:${pendingRounds.length}`, + `Total rounds: ${totalRuns}; Success: ${successRounds.length}; Failed: ${failedRounds.length}; Pending: ${pendingRounds.length}`, failedRounds.length ? 'warn' : 'ok' ); if (successRounds.length) { await addLog( - `成功轮次:${successRounds - .map((item) => `第 ${item.round} 轮(重试 ${getAutoRunRoundRetryCount(item)} 次)`) - .join(';')}`, + `Successful rounds: ${successRounds + .map((item) => `Round ${item.round} (retries: ${getAutoRunRoundRetryCount(item)})`) + .join('; ')}`, 'ok' ); } if (failedRounds.length) { await addLog( - `失败轮次:${failedRounds + `Failed rounds: ${failedRounds .map((item) => { const retryCount = getAutoRunRoundRetryCount(item); - const finalReason = item.finalFailureReason || item.failureReasons[item.failureReasons.length - 1] || '未知错误'; + const finalReason = item.finalFailureReason || item.failureReasons[item.failureReasons.length - 1] || 'Unknown error'; const reasonSummary = formatAutoRunFailureReasons(item.failureReasons); return !reasonSummary || reasonSummary === finalReason - ? `第 ${item.round} 轮(重试 ${retryCount} 次,最终原因:${finalReason})` - : `第 ${item.round} 轮(重试 ${retryCount} 次,最终原因:${finalReason};失败记录:${reasonSummary})`; + ? `Round ${item.round} (retries: ${retryCount}, final reason: ${finalReason})` + : `Round ${item.round} (retries: ${retryCount}, final reason: ${finalReason}; failure history: ${reasonSummary})`; }) - .join(';')}`, + .join('; ')}`, 'error' ); } if (pendingRounds.length) { await addLog( - `未完成轮次:${pendingRounds.map((item) => `第 ${item.round} 轮`).join(';')}`, + `Pending rounds: ${pendingRounds.map((item) => `Round ${item.round}`).join('; ')}`, 'warn' ); } @@ -428,9 +428,9 @@ } const currentRuntime = runtime.get(); - const statusLabel = roundSummary?.status === 'failed' ? '失败' : '完成'; + const statusLabel = roundSummary?.status === 'failed' ? 'failed' : 'completed'; await addLog( - `线程间隔:第 ${targetRun}/${totalRuns} 轮已${statusLabel},等待 ${fallbackThreadIntervalMinutes} 分钟后开始下一轮。`, + `Thread interval: Round ${targetRun}/${totalRuns} ${statusLabel}, waiting ${fallbackThreadIntervalMinutes} minutes before next round.`, 'info' ); await persistAutoRunTimerPlan({ @@ -442,8 +442,8 @@ autoRunSessionId: currentRuntime.autoRunSessionId, autoRunSkipFailures, roundSummaries, - countdownTitle: '线程间隔中', - countdownNote: `第 ${Math.min(targetRun + 1, totalRuns)}/${totalRuns} 轮即将开始`, + countdownTitle: 'Thread interval in progress', + countdownNote: `Round ${Math.min(targetRun + 1, totalRuns)}/${totalRuns} about to start`, }, { autoRunSkipFailures, autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), @@ -462,7 +462,7 @@ } await addLog( - `线程间隔:等待 ${fallbackThreadIntervalMinutes} 分钟后开始第 ${targetRun}/${totalRuns} 轮第 ${nextAttemptRun} 次尝试。`, + `Thread interval: Waiting ${fallbackThreadIntervalMinutes} minutes before starting Round ${targetRun}/${totalRuns} attempt ${nextAttemptRun}.`, 'info' ); await persistAutoRunTimerPlan({ @@ -474,8 +474,8 @@ autoRunSessionId: runtime.get().autoRunSessionId, autoRunSkipFailures, roundSummaries, - countdownTitle: '线程间隔中', - countdownNote: `第 ${targetRun}/${totalRuns} 轮第 ${nextAttemptRun} 次尝试即将开始`, + countdownTitle: 'Thread interval in progress', + countdownNote: `Round ${targetRun}/${totalRuns} attempt ${nextAttemptRun} about to start`, }, { autoRunSkipFailures, autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), @@ -488,7 +488,7 @@ const currentRuntime = runtime.get(); console.error('Auto run loop crashed:', error); if (!isStopError(error)) { - await addLog(`自动运行异常终止:${getErrorMessage(error) || '未知错误'}`, 'error'); + await addLog(`Auto-run terminated abnormally: ${getErrorMessage(error) || 'Unknown error'}`, 'error'); } runtime.set({ autoRunActive: false, autoRunSessionId: 0 }); @@ -513,7 +513,7 @@ async function autoRunLoop(totalRuns, options = {}) { let currentRuntime = runtime.get(); if (currentRuntime.autoRunActive) { - await addLog('自动运行已在进行中', 'warn'); + await addLog('Auto-run is already in progress', 'warn'); return; } @@ -618,7 +618,7 @@ startNodeId = resumeNodeId; useExistingProgress = true; } else if (hasSavedWorkflowProgress(currentState)) { - await addLog('检测到当前流程已处理完成,本轮将改为从首个节点重新开始。', 'info'); + await addLog('Current flow already processed — this round will restart from the first node.', 'info'); } } @@ -653,7 +653,7 @@ } if (forceFreshTabsNextRun) { - await addLog(`上一轮尝试已放弃,当前开始第 ${targetRun}/${totalRuns} 轮第 ${attemptRun} 次尝试。`, 'warn'); + await addLog(`Previous attempt abandoned, now starting Round ${targetRun}/${totalRuns} attempt ${attemptRun}.`, 'warn'); forceFreshTabsNextRun = false; } @@ -705,13 +705,13 @@ await setState({ autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), }); - await addLog(`=== 第 ${targetRun}/${totalRuns} 轮完成(第 ${attemptRun} 次尝试成功)===`, 'ok'); + await addLog(`=== Round ${targetRun}/${totalRuns} completed (attempt ${attemptRun} succeeded) ===`, 'ok'); break; } catch (err) { if (isStopError(err)) { stoppedEarly = true; await appendRoundRecordIfNeeded('stopped', getErrorMessage(err), err); - await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} stopped by user`, 'warn'); await broadcastAutoRunStatus('stopped', { currentRun: targetRun, totalRuns, @@ -764,11 +764,11 @@ autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), }); await appendRoundRecordIfNeeded('failed', reason, err); - cancelPendingCommands('当前轮因认证流程进入 add-phone 已终止。'); + cancelPendingCommands('Current round aborted due to add-phone authentication.'); await broadcastStopToContentScripts(); if (!autoRunSkipFailures) { await addLog( - `第 ${targetRun}/${totalRuns} 轮触发 add-phone/手机号页,自动重试未开启,当前自动运行将停止。`, + `Round ${targetRun}/${totalRuns} hit add-phone/phone page, auto-retry not enabled, auto-run will stop.`, 'warn' ); stoppedEarly = true; @@ -781,11 +781,11 @@ break; } - await addLog(`第 ${targetRun}/${totalRuns} 轮触发 add-phone/手机号页,本轮将直接失败并跳过剩余重试。`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} hit add-phone/phone page, this round will fail directly and skip remaining retries.`, 'warn'); await addLog( targetRun < totalRuns - ? `第 ${targetRun}/${totalRuns} 轮因 add-phone/手机号页提前结束,自动流程将继续下一轮。` - : `第 ${targetRun}/${totalRuns} 轮因 add-phone/手机号页提前结束,已无后续轮次,本次自动运行结束。`, + ? `Round ${targetRun}/${totalRuns} ended early due to add-phone/phone page, auto-run will continue to next round.` + : `Round ${targetRun}/${totalRuns} ended early due to add-phone/phone page, no remaining rounds, auto-run finished.`, 'warn' ); forceFreshTabsNextRun = true; @@ -799,11 +799,11 @@ autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), }); await appendRoundRecordIfNeeded('failed', reason, err); - cancelPendingCommands('当前轮因接码号池暂无可用号码已终止。'); + cancelPendingCommands('Current round aborted due to no available SMS numbers.'); await broadcastStopToContentScripts(); if (!autoRunSkipFailures) { await addLog( - `第 ${targetRun}/${totalRuns} 轮接码号池暂无可用号码,自动重试未开启,当前自动运行将停止。`, + `Round ${targetRun}/${totalRuns} no SMS numbers available, auto-retry not enabled, auto-run will stop.`, 'warn' ); stoppedEarly = true; @@ -816,11 +816,11 @@ break; } - await addLog(`第 ${targetRun}/${totalRuns} 轮接码号池暂无可用号码,本轮将直接失败并跳过剩余重试。`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} no SMS numbers available, this round will fail directly and skip remaining retries.`, 'warn'); await addLog( targetRun < totalRuns - ? `第 ${targetRun}/${totalRuns} 轮因接码号池暂无可用号码提前结束,自动流程将继续下一轮。` - : `第 ${targetRun}/${totalRuns} 轮因接码号池暂无可用号码提前结束,已无后续轮次,本次自动运行结束。`, + ? `Round ${targetRun}/${totalRuns} ended early due to no SMS numbers, auto-run will continue to next round.` + : `Round ${targetRun}/${totalRuns} ended early due to no SMS numbers, no remaining rounds, auto-run finished.`, 'warn' ); forceFreshTabsNextRun = true; @@ -834,11 +834,11 @@ autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), }); await appendRoundRecordIfNeeded('failed', reason, err); - cancelPendingCommands('当前轮因 Plus 免费试用资格不可用已终止。'); + cancelPendingCommands('Current round aborted because Plus free trial eligibility is unavailable.'); await broadcastStopToContentScripts(); if (!autoRunSkipFailures) { await addLog( - `第 ${targetRun}/${totalRuns} 轮检测到 Plus 今日应付金额非 0,自动重试未开启,当前自动运行将停止。`, + `Round ${targetRun}/${totalRuns} detected non-zero Plus charge today, auto-retry not enabled, auto-run will stop.`, 'warn' ); stoppedEarly = true; @@ -851,11 +851,11 @@ break; } - await addLog(`第 ${targetRun}/${totalRuns} 轮没有 Plus 免费试用资格,本轮将直接失败并跳过剩余重试。`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} has no Plus free trial eligibility, this round will fail directly and skip remaining retries.`, 'warn'); await addLog( targetRun < totalRuns - ? `第 ${targetRun}/${totalRuns} 轮因 Plus 今日应付金额非 0 提前结束,自动流程将继续下一轮。` - : `第 ${targetRun}/${totalRuns} 轮因 Plus 今日应付金额非 0 提前结束,已无后续轮次,本次自动运行结束。`, + ? `Round ${targetRun}/${totalRuns} ended early because Plus charge today is non-zero, auto-run will continue to next round.` + : `Round ${targetRun}/${totalRuns} ended early because Plus charge today is non-zero, no remaining rounds, auto-run finished.`, 'warn' ); forceFreshTabsNextRun = true; @@ -869,11 +869,11 @@ autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), }); await appendRoundRecordIfNeeded('failed', reason, err); - cancelPendingCommands('当前轮因 GPC 任务已结束。'); + cancelPendingCommands('Current round aborted because GPC task ended.'); await broadcastStopToContentScripts(); if (!autoRunSkipFailures) { await addLog( - `第 ${targetRun}/${totalRuns} 轮 GPC 任务已结束,自动重试未开启,当前自动运行将停止。`, + `Round ${targetRun}/${totalRuns} GPC task ended, auto-retry not enabled, auto-run will stop.`, 'warn' ); stoppedEarly = true; @@ -886,11 +886,11 @@ break; } - await addLog(`第 ${targetRun}/${totalRuns} 轮 GPC 任务已结束,本轮将直接失败并跳过剩余重试。`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} GPC task ended, this round will fail directly and skip remaining retries.`, 'warn'); await addLog( targetRun < totalRuns - ? `第 ${targetRun}/${totalRuns} 轮因 GPC 任务结束提前结束,自动流程将继续下一轮。` - : `第 ${targetRun}/${totalRuns} 轮因 GPC 任务结束提前结束,已无后续轮次,本次自动运行结束。`, + ? `Round ${targetRun}/${totalRuns} ended early because GPC task ended, auto-run will continue to next round.` + : `Round ${targetRun}/${totalRuns} ended early because GPC task ended, no remaining rounds, auto-run finished.`, 'warn' ); forceFreshTabsNextRun = true; @@ -904,11 +904,11 @@ autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), }); await appendRoundRecordIfNeeded('failed', reason, err); - cancelPendingCommands('当前轮因 user_already_exists 已终止。'); + cancelPendingCommands('Current round aborted because of user_already_exists.'); await broadcastStopToContentScripts(); if (!autoRunSkipFailures) { await addLog( - `第 ${targetRun}/${totalRuns} 轮触发 user_already_exists/用户已存在,自动重试未开启,当前自动运行将停止。`, + `Round ${targetRun}/${totalRuns} hit user_already_exists/user already exists, auto-retry not enabled, auto-run will stop.`, 'warn' ); stoppedEarly = true; @@ -921,11 +921,11 @@ break; } - await addLog(`第 ${targetRun}/${totalRuns} 轮触发 user_already_exists/用户已存在,本轮将直接失败并跳过剩余重试。`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} hit user_already_exists/user already exists, this round will fail directly and skip remaining retries.`, 'warn'); await addLog( targetRun < totalRuns - ? `第 ${targetRun}/${totalRuns} 轮因 user_already_exists/用户已存在提前结束,自动流程将继续下一轮。` - : `第 ${targetRun}/${totalRuns} 轮因 user_already_exists/用户已存在提前结束,已无后续轮次,本次自动运行结束。`, + ? `Round ${targetRun}/${totalRuns} ended early due to user_already_exists/user already exists, auto-run will continue to next round.` + : `Round ${targetRun}/${totalRuns} ended early due to user_already_exists/user already exists, no remaining rounds, auto-run finished.`, 'warn' ); forceFreshTabsNextRun = true; @@ -939,11 +939,11 @@ autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), }); await appendRoundRecordIfNeeded('failed', reason, err); - cancelPendingCommands('当前轮因步骤 4 连续 405 错误已终止。'); + cancelPendingCommands('Current round aborted due to Step 4 consecutive 405 errors.'); await broadcastStopToContentScripts(); if (!autoRunSkipFailures) { await addLog( - `第 ${targetRun}/${totalRuns} 轮步骤 4 连续 405 恢复失败,自动重试未开启,当前自动运行将停止。`, + `Round ${targetRun}/${totalRuns} Step 4 consecutive 405 recovery failed, auto-retry not enabled, auto-run will stop.`, 'warn' ); stoppedEarly = true; @@ -956,11 +956,11 @@ break; } - await addLog(`第 ${targetRun}/${totalRuns} 轮步骤 4 连续 405 恢复失败,本轮将直接失败并跳过剩余重试。`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} Step 4 consecutive 405 recovery failed, this round will fail directly and skip remaining retries.`, 'warn'); await addLog( targetRun < totalRuns - ? `第 ${targetRun}/${totalRuns} 轮因步骤 4 连续 405 提前结束,自动流程将继续下一轮。` - : `第 ${targetRun}/${totalRuns} 轮因步骤 4 连续 405 提前结束,已无后续轮次,本次自动运行结束。`, + ? `Round ${targetRun}/${totalRuns} ended early due to Step 4 consecutive 405, auto-run will continue to next round.` + : `Round ${targetRun}/${totalRuns} ended early due to Step 4 consecutive 405, no remaining rounds, auto-run finished.`, 'warn' ); forceFreshTabsNextRun = true; @@ -974,10 +974,10 @@ autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), }); await appendRoundRecordIfNeeded('failed', reason, err); - cancelPendingCommands('当前轮检测到 Kiro 代理异常页,已停止自动运行,等待用户切换代理。'); + cancelPendingCommands('Current round detected Kiro proxy error page, auto-run stopped, waiting for user to switch proxy.'); await broadcastStopToContentScripts(); - await addLog(`第 ${targetRun}/${totalRuns} 轮检测到 Kiro 代理异常页:${reason}`, 'error'); - await addLog('当前代理可能不可用,请先切换代理后再继续。自动运行已停止。', 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} detected Kiro proxy error page: ${reason}`, 'error'); + await addLog('Current proxy may be unavailable. Please switch proxy before continuing. Auto-run stopped.', 'warn'); stoppedEarly = true; await broadcastAutoRunStatus('stopped', { currentRun: targetRun, @@ -991,11 +991,11 @@ if (canRetry) { const retryIndex = attemptRun; if (isRestartCurrentAttemptError(err)) { - await addLog(`第 ${targetRun}/${totalRuns} 轮第 ${attemptRun} 次尝试需要整轮重开:${reason}`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} attempt ${attemptRun} requires full restart: ${reason}`, 'warn'); } else { - await addLog(`第 ${targetRun}/${totalRuns} 轮第 ${attemptRun} 次尝试失败:${reason}`, 'error'); + await addLog(`Round ${targetRun}/${totalRuns} attempt ${attemptRun} failed: ${reason}`, 'error'); } - cancelPendingCommands('当前尝试已放弃。'); + cancelPendingCommands('Current attempt abandoned.'); await broadcastStopToContentScripts(); await broadcastAutoRunStatus('retrying', { currentRun: targetRun, @@ -1006,8 +1006,8 @@ forceFreshTabsNextRun = true; await addLog( keepSameEmailUntilAddPhone - ? `自动重试:${Math.round(AUTO_RUN_RETRY_DELAY_MS / 1000)} 秒后继续使用当前邮箱,开始第 ${targetRun}/${totalRuns} 轮第 ${attemptRun + 1} 次尝试。` - : `自动重试:${Math.round(AUTO_RUN_RETRY_DELAY_MS / 1000)} 秒后开始第 ${targetRun}/${totalRuns} 轮第 ${attemptRun + 1} 次尝试(第 ${retryIndex}/${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试)。`, + ? `Auto-retry: continuing with current email after ${Math.round(AUTO_RUN_RETRY_DELAY_MS / 1000)} seconds, starting Round ${targetRun}/${totalRuns} attempt ${attemptRun + 1}.` + : `Auto-retry: starting Round ${targetRun}/${totalRuns} attempt ${attemptRun + 1} (retry ${retryIndex}/${AUTO_RUN_MAX_RETRIES_PER_ROUND}) after ${Math.round(AUTO_RUN_RETRY_DELAY_MS / 1000)} seconds.`, 'warn' ); try { @@ -1016,7 +1016,7 @@ if (isStopError(sleepError)) { stoppedEarly = true; await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError), sleepError); - await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} stopped by user`, 'warn'); await broadcastAutoRunStatus('stopped', { currentRun: targetRun, totalRuns, @@ -1040,7 +1040,7 @@ if (isStopError(sleepError)) { stoppedEarly = true; await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError), sleepError); - await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} stopped by user`, 'warn'); await broadcastAutoRunStatus('stopped', { currentRun: targetRun, totalRuns, @@ -1063,9 +1063,9 @@ }); await appendRoundRecordIfNeeded('failed', reason, err); if (!autoRunSkipFailures) { - cancelPendingCommands('当前轮执行失败。'); + cancelPendingCommands('Current round execution failed.'); await broadcastStopToContentScripts(); - await addLog('自动重试未开启,自动运行将在当前失败后停止。', 'warn'); + await addLog('Auto-retry not enabled, auto-run will stop on this failure.', 'warn'); stoppedEarly = true; await broadcastAutoRunStatus('stopped', { currentRun: targetRun, @@ -1075,14 +1075,14 @@ }); break; } - await addLog(`第 ${targetRun}/${totalRuns} 轮最终失败:${reason}`, 'error'); + await addLog(`Round ${targetRun}/${totalRuns} final failure: ${reason}`, 'error'); await addLog( targetRun < totalRuns - ? `第 ${targetRun}/${totalRuns} 轮已达到 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试上限,继续下一轮。` - : `第 ${targetRun}/${totalRuns} 轮已达到 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试上限,本次自动运行结束。`, + ? `Round ${targetRun}/${totalRuns} reached retry limit of ${AUTO_RUN_MAX_RETRIES_PER_ROUND}, continuing to next round.` + : `Round ${targetRun}/${totalRuns} reached retry limit of ${AUTO_RUN_MAX_RETRIES_PER_ROUND}, auto-run finished.`, 'warn' ); - cancelPendingCommands('当前轮已达到重试上限。'); + cancelPendingCommands('Current round reached retry limit.'); await broadcastStopToContentScripts(); forceFreshTabsNextRun = true; break; @@ -1108,7 +1108,7 @@ } catch (sleepError) { if (isStopError(sleepError)) { stoppedEarly = true; - await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn'); + await addLog(`Round ${targetRun}/${totalRuns} stopped by user`, 'warn'); await broadcastAutoRunStatus('stopped', { currentRun: targetRun, totalRuns, @@ -1134,7 +1134,7 @@ const finalRuntime = runtime.get(); if (deps.getStopRequested() || stoppedEarly) { - await addLog(`=== 已停止,完成 ${successfulRuns}/${finalRuntime.autoRunTotalRuns} 轮 ===`, 'warn'); + await addLog(`=== Stopped, completed ${successfulRuns}/${finalRuntime.autoRunTotalRuns} rounds ===`, 'warn'); await broadcastAutoRunStatus('stopped', { currentRun: finalRuntime.autoRunCurrentRun, totalRuns: finalRuntime.autoRunTotalRuns, @@ -1142,7 +1142,7 @@ sessionId: 0, }); } else { - await addLog(`=== 全部 ${finalRuntime.autoRunTotalRuns} 轮已执行完成,成功 ${successfulRuns} 轮 ===`, 'ok'); + await addLog(`=== All ${finalRuntime.autoRunTotalRuns} rounds completed, ${successfulRuns} succeeded ===`, 'ok'); await broadcastAutoRunStatus('complete', { currentRun: finalRuntime.autoRunTotalRuns, totalRuns: finalRuntime.autoRunTotalRuns, diff --git a/background/cloudmail-provider.js b/background/cloudmail-provider.js index 175d8f72..35a96aa9 100644 --- a/background/cloudmail-provider.js +++ b/background/cloudmail-provider.js @@ -73,23 +73,23 @@ const { requireToken = false, requireCredentials = false, requireDomain = false } = options; const config = getCloudMailConfig(state); if (!config.baseUrl) { - throw new Error('Cloud Mail 服务地址为空或格式无效。'); + throw new Error('Cloud Mail service URL is empty or invalid.'); } if (requireCredentials && (!config.adminEmail || !config.adminPassword)) { - throw new Error('Cloud Mail 缺少管理员邮箱或密码。'); + throw new Error('Cloud Mail admin email or password missing.'); } if (requireToken && !config.token) { - throw new Error('Cloud Mail 尚未获取到身份令牌,请先生成 Token。'); + throw new Error('Cloud Mail has no auth token. Please generate a token first.'); } if (requireDomain && !config.domain) { - throw new Error('Cloud Mail 域名为空或格式无效。'); + throw new Error('Cloud Mail domain is empty or invalid.'); } return config; } async function requestCloudMailJson(config, path, options = {}) { if (!fetchImpl) { - throw new Error('Cloud Mail 当前运行环境不支持 fetch。'); + throw new Error('Cloud Mail: fetch not supported in current environment.'); } const { method = 'POST', @@ -113,8 +113,8 @@ }); } catch (err) { const errorMessage = err?.name === 'AbortError' - ? `Cloud Mail 请求超时(>${Math.round(timeoutMs / 1000)} 秒)` - : `Cloud Mail 请求失败:${err.message}`; + ? `Cloud Mail request timed out (>${Math.round(timeoutMs / 1000)} seconds)` + : `Cloud Mail request failed: ${err.message}`; throw new Error(errorMessage); } finally { clearTimeout(timeoutId); @@ -130,10 +130,10 @@ const payloadError = typeof parsed === 'object' && parsed ? (parsed.message || parsed.error || parsed.msg) : ''; - throw new Error(`Cloud Mail 请求失败:${payloadError || text || `HTTP ${response.status}`}`); + throw new Error(`Cloud Mail request failed: ${payloadError || text || `HTTP ${response.status}`}`); } if (parsed && typeof parsed === 'object' && 'code' in parsed && Number(parsed.code) !== 200) { - throw new Error(`Cloud Mail 业务错误:${parsed.message || parsed.msg || `code=${parsed.code}`}`); + throw new Error(`Cloud Mail business error: ${parsed.message || parsed.msg || `code=${parsed.code}`}`); } return parsed; } @@ -153,7 +153,7 @@ }); const token = getCloudMailTokenFromResponse(result); if (!token) { - throw new Error('Cloud Mail 未返回可用 Token。'); + throw new Error('Cloud Mail did not return a usable token.'); } await setPersistentSettings({ cloudMailToken: token }); return { config: { ...config, token }, token }; @@ -198,7 +198,7 @@ source: 'generated:cloudmail', preserveAccountIdentity: Boolean(options?.preserveAccountIdentity), }); - await addLog(`Cloud Mail:已生成 ${address}`, 'ok'); + await addLog(`Cloud Mail: Generated ${address}`, 'ok'); return address; } @@ -212,11 +212,11 @@ }) .slice(0, 3) .map((message) => { - const receivedAt = message?.receivedDateTime || '未知时间'; - const sender = message?.from?.emailAddress?.address || '未知发件人'; - const subject = message?.subject || '(无主题)'; + const receivedAt = message?.receivedDateTime || 'unknown time'; + const sender = message?.from?.emailAddress?.address || 'unknown sender'; + const subject = message?.subject || '(no subject)'; const preview = String(message?.bodyPreview || '').replace(/\s+/g, ' ').trim().slice(0, 80); - const address = message?.address || '未知地址'; + const address = message?.address || 'unknown address'; return `[${address}] ${receivedAt} | ${sender} | ${subject} | ${preview}`; }) .join(' || '); @@ -263,12 +263,12 @@ const targetEmail = resolveCloudMailPollTargetEmail(latestState, pollPayload, config); const registrationEmail = normalizeCloudMailReceiveMailbox(latestState.email); if (!targetEmail) { - throw new Error('Cloud Mail 轮询前缺少目标邮箱地址,请先填写注册邮箱或"邮件接收"邮箱。'); + throw new Error('Cloud Mail polling missing target email address. Please fill in registration email or "Receive Mail" mailbox.'); } if (registrationEmail && registrationEmail !== targetEmail) { - await addLog(`步骤 ${step}:正在轮询 Cloud Mail 收件邮箱(${targetEmail}),注册邮箱为 ${registrationEmail}...`, 'info'); + await addLog(`Step ${step}: Polling Cloud Mail receive mailbox (${targetEmail}), registration email is ${registrationEmail}...`, 'info'); } else { - await addLog(`步骤 ${step}:正在轮询 Cloud Mail 邮件(${targetEmail})...`, 'info'); + await addLog(`Step ${step}: Polling Cloud Mail messages (${targetEmail})...`, 'info'); } const maxAttempts = Number(pollPayload.maxAttempts) || 5; const intervalMs = Number(pollPayload.intervalMs) || 3000; @@ -290,8 +290,8 @@ const match = matchResult.match; if (match?.code) { if (matchResult.usedRelaxedFilters) { - const fallbackLabel = matchResult.usedTimeFallback ? '宽松匹配 + 时间回退' : '宽松匹配'; - await addLog(`步骤 ${step}:严格规则未命中,已改用 ${fallbackLabel} 并命中 Cloud Mail 验证码。`, 'warn'); + const fallbackLabel = matchResult.usedTimeFallback ? 'relaxed match + time fallback' : 'relaxed match'; + await addLog(`Step ${step}: Strict rules did not match, using ${fallbackLabel} and matched Cloud Mail verification code.`, 'warn'); } return { ok: true, @@ -300,21 +300,21 @@ mailId: match.message?.id || '', }; } - lastError = new Error(`步骤 ${step}:暂未在 Cloud Mail 中找到匹配验证码(${attempt}/${maxAttempts})。`); + lastError = new Error(`Step ${step}: No matching verification code found yet in Cloud Mail (${attempt}/${maxAttempts}).`); await addLog(lastError.message, attempt === maxAttempts ? 'warn' : 'info'); const sample = summarizeCloudMailMessagesForLog(messages); if (sample) { - await addLog(`步骤 ${step}:最近邮件样本:${sample}`, 'info'); + await addLog(`Step ${step}: Recent mail sample: ${sample}`, 'info'); } } catch (err) { lastError = err; - await addLog(`步骤 ${step}:Cloud Mail 轮询失败:${err.message}`, 'warn'); + await addLog(`Step ${step}: Cloud Mail polling failed: ${err.message}`, 'warn'); } if (attempt < maxAttempts) { await sleepWithStop(intervalMs); } } - throw lastError || new Error(`步骤 ${step}:未在 Cloud Mail 中找到新的匹配验证码。`); + throw lastError || new Error(`Step ${step}: No new matching verification code found in Cloud Mail.`); } return { diff --git a/background/contribution-oauth.js b/background/contribution-oauth.js index 92135fc9..bd300f69 100644 --- a/background/contribution-oauth.js +++ b/background/contribution-oauth.js @@ -13,7 +13,7 @@ contributionAdapterId: '', flowContributionRuntime: {}, contributionSource: 'sub2api', - contributionTargetGroupName: 'codex号池', + contributionTargetGroupName: 'codex pool', contributionNickname: '', contributionQq: '', contributionSessionId: '', @@ -117,23 +117,23 @@ function getStatusLabel(status = '') { switch (normalizeContributionStatus(status)) { case 'started': - return '已生成登录地址'; + return 'Login URL generated'; case 'waiting': - return '等待提交回调'; + return 'Waiting for callback submission'; case 'processing': - return '已提交回调,等待 CPA 确认'; + return 'Callback submitted, waiting for CPA confirmation'; case 'auto_approved': - return '贡献成功,CPA 已确认'; + return 'Contribution succeeded, CPA confirmed'; case 'auto_rejected': - return '贡献未通过确认'; + return 'Contribution failed confirmation'; case 'manual_review_required': - return '已提交,等待人工处理'; + return 'Submitted, waiting for manual review'; case 'expired': - return '贡献会话已超时'; + return 'Contribution session expired'; case 'error': - return '贡献流程失败'; + return 'Contribution flow failed'; default: - return '等待开始贡献'; + return 'Waiting to start contribution'; } } @@ -141,17 +141,17 @@ switch (normalizeContributionCallbackStatus(status)) { case 'waiting': case 'idle': - return '等待回调'; + return 'Waiting for callback'; case 'captured': - return '已捕获回调地址'; + return 'Callback URL captured'; case 'submitting': - return '正在提交回调'; + return 'Submitting callback'; case 'submitted': - return '已提交回调'; + return 'Callback submitted'; case 'failed': - return '回调提交失败'; + return 'Callback submission failed'; default: - return '等待回调'; + return 'Waiting for callback'; } } @@ -181,7 +181,7 @@ return details; } - return `贡献服务请求失败(HTTP ${responseStatus})。`; + return `Contribution service request failed (HTTP ${responseStatus}).`; } async function fetchContributionJson(endpoint, options = {}) { @@ -218,7 +218,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error('贡献服务请求超时,请稍后重试。'); + throw new Error('Contribution service request timed out. Please retry later.'); } throw error; } finally { @@ -285,7 +285,7 @@ return { source: currentSource, targetGroupName: currentSource === 'sub2api' - ? (normalizeString(state?.contributionTargetGroupName) || 'codex号池') + ? (normalizeString(state?.contributionTargetGroupName) || 'codex pool') : '', }; } @@ -294,7 +294,7 @@ source: 'sub2api', targetGroupName: isPlusModeState(state) ? 'openai-plus' - : (normalizeString(state?.contributionTargetGroupName) || 'codex号池'), + : (normalizeString(state?.contributionTargetGroupName) || 'codex pool'), }; } @@ -314,7 +314,7 @@ return label; } - return `${label}:${details}`; + return `${label}: ${details}`; } function buildCallbackMessage(status, payload = {}) { @@ -333,7 +333,7 @@ return label; } - return `${label}:${details}`; + return `${label}: ${details}`; } function deriveCallbackState(payload = {}, state = {}) { @@ -425,7 +425,7 @@ async function openContributionAuthUrl(authUrl, options = {}) { const normalizedUrl = normalizeString(authUrl); if (!normalizedUrl) { - throw new Error('贡献服务未返回有效的登录地址。'); + throw new Error('Contribution service did not return a valid login URL.'); } const currentState = options.stateOverride || await getState(); @@ -459,7 +459,7 @@ return await fetchContributionJson(`/result?session_id=${encodeURIComponent(sessionId)}`); } catch (error) { if (typeof addLog === 'function') { - await addLog(`贡献模式:获取最终结果失败:${error.message}`, 'warn'); + await addLog(`Contribution mode: Failed to fetch final result: ${error.message}`, 'warn'); } return null; } @@ -516,11 +516,11 @@ await applyRuntimeUpdates({ contributionCallbackUrl: normalizedUrl, contributionCallbackStatus: 'failed', - contributionCallbackMessage: `回调提交失败:${error.message}`, + contributionCallbackMessage: `Callback submission failed: ${error.message}`, }); if (typeof addLog === 'function') { - await addLog(`贡献模式:回调提交失败:${error.message}`, 'warn'); + await addLog(`Contribution mode: Callback submission failed: ${error.message}`, 'warn'); } throw error; @@ -567,7 +567,7 @@ }); if (typeof addLog === 'function') { - await addLog(`贡献模式:已捕获回调地址(${metadata.source || 'unknown'})。`, 'info'); + await addLog(`Contribution mode: Captured callback URL (${metadata.source || 'unknown'}).`, 'info'); } try { @@ -648,7 +648,7 @@ const currentState = options.stateOverride || await getState(); const shouldOpenAuthTab = options.openAuthTab !== false; if (!currentState.accountContributionEnabled) { - throw new Error('请先进入贡献模式。'); + throw new Error('Please enable contribution mode first.'); } const currentSessionId = normalizeString(currentState.contributionSessionId); @@ -681,7 +681,7 @@ const authUrl = normalizeString(payload.auth_url || payload.authUrl); const authState = normalizeString(payload.state || payload.auth_state || payload.authState) || extractAuthStateFromUrl(authUrl); if (!sessionId || !authUrl) { - throw new Error('贡献服务未返回有效的 session_id 或 auth_url。'); + throw new Error('Contribution service did not return a valid session_id or auth_url.'); } await applyRuntimeUpdates({ diff --git a/background/contribution/adapters/kiro-builder-id.js b/background/contribution/adapters/kiro-builder-id.js index 2d31fc5b..ad5b536d 100644 --- a/background/contribution/adapters/kiro-builder-id.js +++ b/background/contribution/adapters/kiro-builder-id.js @@ -16,7 +16,7 @@ } function getErrorMessage(error) { - return error instanceof Error ? error.message : String(error ?? '未知错误'); + return error instanceof Error ? error.message : String(error ?? 'Unknown error'); } function normalizeFlowId(state = {}) { @@ -82,7 +82,7 @@ async function submitContributionArtifact(artifact = {}, options = {}) { const fetchImpl = options.fetchImpl; if (typeof fetchImpl !== 'function') { - throw new Error('账号贡献提交能力缺少 fetch。'); + throw new Error('Account contribution submission capability is missing fetch.'); } const endpoint = normalizeContributionApiUrl(options.apiUrl); const payload = { @@ -106,13 +106,13 @@ }); const body = await readJsonResponse(response); if (!response.ok || body?.ok === false) { - throw new Error(cleanString(body?.message || body?.detail || body?.error) || `贡献服务请求失败(HTTP ${response.status})。`); + throw new Error(cleanString(body?.message || body?.detail || body?.error) || `Contribution service request failed (HTTP ${response.status}).`); } return { ok: true, status: response.status, contributionId: cleanString(body?.contribution_id || body?.contributionId || body?.id), - message: cleanString(body?.message) || '贡献提交成功', + message: cleanString(body?.message) || 'Contribution submitted successfully', raw: body, }; } @@ -164,7 +164,7 @@ lastMessage: message, updatedAt: Date.now(), }); - await log(`Kiro 账号贡献已跳过:${message}`, 'warn', nodeId); + await log(`Kiro account contribution skipped: ${message}`, 'warn', nodeId); return { ok: false, skipped: true, @@ -177,12 +177,12 @@ enabled: true, status: 'submitting', error: '', - lastMessage: '正在提交 Kiro Builder ID 贡献', + lastMessage: 'Submitting Kiro Builder ID contribution', updatedAt: Date.now(), }); const safeSummary = artifactApi.buildSafeArtifactSummary?.(artifact) || {}; - await log(`Kiro 账号贡献:正在提交 Builder ID,邮箱 ${safeSummary.email || '未知'}。`, 'info', nodeId); + await log(`Kiro account contribution: Submitting Builder ID, email ${safeSummary.email || 'unknown'}.`, 'info', nodeId); try { const result = await submitContributionArtifact(artifact, { @@ -200,7 +200,7 @@ submittedAt: Date.now(), updatedAt: Date.now(), }); - await log(`Kiro 账号贡献:提交完成,${result.message}。`, 'ok', nodeId); + await log(`Kiro account contribution: Submission complete, ${result.message}.`, 'ok', nodeId); return result; } catch (error) { const message = redactKnownArtifactSecrets(getErrorMessage(error), artifact); @@ -211,7 +211,7 @@ lastMessage: message, updatedAt: Date.now(), }); - await log(`Kiro 账号贡献提交失败:${message}`, 'warn', nodeId); + await log(`Kiro account contribution submission failed: ${message}`, 'warn', nodeId); return { ok: false, skipped: false, diff --git a/background/cpa-api.js b/background/cpa-api.js index 7cfd6b2c..09c5b7b5 100644 --- a/background/cpa-api.js +++ b/background/cpa-api.js @@ -41,13 +41,13 @@ function deriveCpaManagementOrigin(vpsUrl) { const normalizedUrl = normalizeString(vpsUrl); if (!normalizedUrl) { - throw new Error('尚未配置 CPA 地址,请先在侧边栏填写。'); + throw new Error('CPA URL is not configured. Please fill it in the side panel.'); } let parsed; try { parsed = new URL(normalizedUrl); } catch { - throw new Error('CPA 地址格式无效,请先在侧边栏检查。'); + throw new Error('CPA URL format is invalid. Please check in the side panel.'); } return parsed.origin; } @@ -60,7 +60,7 @@ payload?.reason, ]; const message = candidates.map(normalizeString).find(Boolean); - return message || `CPA 管理接口请求失败(HTTP ${responseStatus})。`; + return message || `CPA management API request failed (HTTP ${responseStatus}).`; } async function fetchCpaManagementJson(origin, path, options = {}) { @@ -100,7 +100,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error('CPA 管理接口请求超时,请稍后重试。'); + throw new Error('CPA management API request timed out. Please retry later.'); } throw error; } finally { @@ -292,7 +292,7 @@ const session = isPlainObject(state?.session) ? state.session : {}; const accessToken = normalizeString(state?.accessToken || session?.accessToken); if (!accessToken) { - throw new Error('未读取到可导入的 ChatGPT accessToken。'); + throw new Error('No importable ChatGPT accessToken read.'); } const inputIdToken = firstNonEmpty( @@ -401,7 +401,7 @@ async function requestOAuthUrl(state, options = {}) { const managementKey = normalizeString(state?.vpsPassword); if (!managementKey) { - throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。'); + throw new Error('CPA management key is not configured. Please fill it in the side panel.'); } const origin = deriveCpaManagementOrigin(state?.vpsUrl); const result = await fetchCpaManagementJson(origin, '/v0/management/codex-auth-url', { @@ -428,7 +428,7 @@ ); if (!oauthUrl || !oauthUrl.startsWith('http')) { - throw new Error('CPA 管理接口未返回有效的 auth_url。'); + throw new Error('CPA management API did not return a valid auth_url.'); } return { @@ -441,7 +441,7 @@ async function submitOAuthCallback(state, callbackUrl, options = {}) { const managementKey = normalizeString(state?.vpsPassword); if (!managementKey) { - throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。'); + throw new Error('CPA management key is not configured. Please fill it in the side panel.'); } const origin = normalizeString(state?.cpaManagementOrigin) || deriveCpaManagementOrigin(state?.vpsUrl); const result = await fetchCpaManagementJson(origin, '/v0/management/oauth-callback', { @@ -455,22 +455,22 @@ }); return { localhostUrl: normalizeString(callbackUrl), - verifiedStatus: firstNonEmpty(result?.message, result?.status_message, 'CPA 已通过接口提交回调'), + verifiedStatus: firstNonEmpty(result?.message, result?.status_message, 'CPA submitted callback via API'), }; } async function importCurrentChatGptSession(state = {}, options = {}) { - const logLabel = normalizeString(options.logLabel) || 'CPA 会话导入'; + const logLabel = normalizeString(options.logLabel) || 'CPA session import'; const managementKey = normalizeString(state?.vpsPassword); if (!managementKey) { - throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。'); + throw new Error('CPA management key is not configured. Please fill it in the side panel.'); } const origin = deriveCpaManagementOrigin(state?.vpsUrl); const sessionAuth = buildCpaSessionAuthJson(state, options); - await logWithOptions(`${logLabel}:正在通过 CPA 管理接口导入当前 ChatGPT 会话...`, 'info', options); + await logWithOptions(`${logLabel}: Importing current ChatGPT session via CPA management API...`, 'info', options); if (!sessionAuth.hasRefreshToken) { - await logWithOptions(`${logLabel}:未包含 refresh_token,access_token 过期后无法自动续期。`, 'warn', options); + await logWithOptions(`${logLabel}: Missing refresh_token, cannot auto-renew after access_token expires.`, 'warn', options); } await fetchCpaManagementJson(origin, `/v0/management/auth-files?name=${encodeURIComponent(sessionAuth.fileName)}`, { @@ -481,8 +481,8 @@ }); const verifiedStatus = sessionAuth.email - ? `CPA 会话导入完成:${sessionAuth.email}` - : `CPA 会话导入完成:${sessionAuth.fileName}`; + ? `CPA session import complete: ${sessionAuth.email}` + : `CPA session import complete: ${sessionAuth.fileName}`; await logWithOptions(verifiedStatus, 'ok', options); return { verifiedStatus, diff --git a/background/flow-mail-polling.js b/background/flow-mail-polling.js index 820115b0..f5f18207 100644 --- a/background/flow-mail-polling.js +++ b/background/flow-mail-polling.js @@ -158,7 +158,7 @@ throw new Error(result.error); } if (!result?.code) { - throw new Error(notFoundMessage || '邮箱轮询结束,但未获取到验证码。'); + throw new Error(notFoundMessage || 'Mailbox polling finished but no verification code obtained.'); } return result; } @@ -173,11 +173,11 @@ return null; } if (typeof handler.poll !== 'function') { - throw new Error(`${handler.label} 邮箱轮询能力未接入,无法继续执行。`); + throw new Error(`${handler.label} mailbox polling capability not connected, cannot continue.`); } await log( - `步骤 ${step}:正在通过 ${mail.label || handler.label} 轮询${options.actionLabel || '验证码'}...`, + `Step ${step}: Polling ${options.actionLabel || 'verification code'} via ${mail.label || handler.label}...`, 'info', options.logOptions ); @@ -188,21 +188,21 @@ async function ensureBrowserMailSession(step, state, mail, options) { if (isIcloudMail(mail) && typeof ensureIcloudMailSession === 'function') { await log( - `步骤 ${step}:正在确认 ${mail.label || 'iCloud 邮箱'} 登录状态...`, + `Step ${step}: Confirming ${mail.label || 'iCloud Mail'} login state...`, 'info', options.logOptions ); await ensureIcloudMailSession({ state, step, - actionLabel: `步骤 ${step}:确认 iCloud 邮箱登录状态`, + actionLabel: `Step ${step}: Confirm iCloud Mail login state`, }); return; } if (isMail2925Provider(mail) && typeof ensureMail2925MailboxSession === 'function') { await log( - `步骤 ${step}:正在确认 ${mail.label || '2925 邮箱'} 登录状态...`, + `Step ${step}: Confirming ${mail.label || '2925 Mail'} login state...`, 'info', options.logOptions ); @@ -211,12 +211,12 @@ forceRelogin: false, allowLoginWhenOnLoginPage: Boolean(state?.mail2925UseAccountPool), expectedMailboxEmail: getExpectedMail2925MailboxEmail(state), - actionLabel: `步骤 ${step}:确认 2925 邮箱登录状态`, + actionLabel: `Step ${step}: Confirm 2925 Mail login state`, }); return; } - await log(`步骤 ${step}:正在打开 ${mail.label || '邮箱'}...`, 'info', options.logOptions); + await log(`Step ${step}: Opening ${mail.label || 'mailbox'}...`, 'info', options.logOptions); await focusOrOpenMailTab(mail); } @@ -224,7 +224,7 @@ await ensureBrowserMailSession(step, state, mail, options); if (typeof sendToMailContentScriptResilient !== 'function') { - throw new Error(options.missingContentScriptMessage || '当前验证码步骤缺少邮箱内容脚本通信能力,无法继续执行。'); + throw new Error(options.missingContentScriptMessage || 'Current verification code step lacks mailbox content script communication, cannot continue.'); } const timeoutWindow = resolveMailPollingTimeouts(mail, pollPayload); @@ -264,12 +264,12 @@ async function pollFlowVerificationCode(options = {}) { const { - actionLabel = '验证码', + actionLabel = 'verification code', filterAfterTimestamp, flowId = '', logStep, logStepKey = '', - missingCapabilityMessage = '当前验证码步骤缺少共享邮件轮询能力,无法继续执行。', + missingCapabilityMessage = 'Current verification code step lacks shared mail polling capability, cannot continue.', nodeId = '', notFoundMessage = '', payloadOverrides = {}, @@ -278,7 +278,7 @@ } = options; if (typeof getMailConfig !== 'function') { - throw new Error('当前验证码步骤缺少邮箱配置能力,无法继续执行。'); + throw new Error('Current verification code step lacks mailbox configuration capability, cannot continue.'); } if (typeof buildVerificationPollPayloadForNode !== 'function') { throw new Error(missingCapabilityMessage); @@ -332,7 +332,7 @@ logStepKey: logStepKey || nodeId, missingContentScriptMessage: missingCapabilityMessage, nodeId, - notFoundMessage: notFoundMessage || `步骤 ${normalizedStep}:邮箱轮询结束,但未获取到验证码。`, + notFoundMessage: notFoundMessage || `Step ${normalizedStep}: Mailbox polling finished but no verification code obtained.`, }); } diff --git a/background/generated-email-helpers.js b/background/generated-email-helpers.js index 81367d5c..fceb0c36 100644 --- a/background/generated-email-helpers.js +++ b/background/generated-email-helpers.js @@ -63,7 +63,7 @@ const latestState = state || await getState(); const domain = normalizeCloudflareDomain(latestState.cloudflareDomain); if (!domain) { - throw new Error('Cloudflare 域名为空或格式无效。'); + throw new Error('Cloudflare domain is empty or invalid.'); } const localPart = String(options.localPart || '').trim().toLowerCase() || generateCloudflareAliasLocalPart(); @@ -73,7 +73,7 @@ source: 'generated:cloudflare', preserveAccountIdentity: Boolean(options?.preserveAccountIdentity), }); - await addLog(`Cloudflare 邮箱:已生成 ${aliasEmail}`, 'ok'); + await addLog(`Cloudflare email: Generated ${aliasEmail}`, 'ok'); return aliasEmail; } @@ -84,13 +84,13 @@ } = options; const config = getCloudflareTempEmailConfig(state); if (!config.baseUrl) { - throw new Error('Cloudflare Temp Email 服务地址为空或格式无效。'); + throw new Error('Cloudflare Temp Email service URL is empty or invalid.'); } if (requireAdminAuth && !config.adminAuth) { - throw new Error('Cloudflare Temp Email 缺少 Admin Auth。'); + throw new Error('Cloudflare Temp Email missing Admin Auth.'); } if (requireDomain && !config.domain) { - throw new Error('Cloudflare Temp Email 域名为空或格式无效。'); + throw new Error('Cloudflare Temp Email domain is empty or invalid.'); } return config; } @@ -126,8 +126,8 @@ }); } catch (err) { const errorMessage = err?.name === 'AbortError' - ? `Cloudflare Temp Email 请求超时(>${Math.round(timeoutMs / 1000)} 秒)` - : `Cloudflare Temp Email 请求失败:${err.message}`; + ? `Cloudflare Temp Email request timed out (>${Math.round(timeoutMs / 1000)} seconds)` + : `Cloudflare Temp Email request failed: ${err.message}`; throw new Error(errorMessage); } finally { clearTimeout(timeoutId); @@ -145,7 +145,7 @@ const payloadError = typeof parsed === 'object' && parsed ? (parsed.message || parsed.error || parsed.msg) : ''; - throw new Error(`Cloudflare Temp Email 请求失败:${payloadError || text || `HTTP ${response.status}`}`); + throw new Error(`Cloudflare Temp Email request failed: ${payloadError || text || `HTTP ${response.status}`}`); } return parsed; @@ -171,14 +171,14 @@ }); const address = normalizeCloudflareTempEmailAddress(getCloudflareTempEmailAddressFromResponse(result)); if (!address) { - throw new Error('Cloudflare Temp Email 未返回可用邮箱地址。'); + throw new Error('Cloudflare Temp Email did not return a usable email address.'); } await persistResolvedEmailState(latestState, address, { source: 'generated:cloudflare-temp-email', preserveAccountIdentity: Boolean(options?.preserveAccountIdentity), }); - await addLog(`Cloudflare Temp Email:已生成 ${address}`, 'ok'); + await addLog(`Cloudflare Temp Email: Generated ${address}`, 'ok'); return address; } @@ -194,7 +194,7 @@ state = null, } = options; - await addLog(`Duck 邮箱:正在打开自动填充设置(${generateNew ? '生成新地址' : '复用当前地址'})...`); + await addLog(`Duck Mail: Opening autofill settings (${generateNew ? 'generate new address' : 'reuse current address'})...`); await reuseOrCreateTab('duck-mail', DUCK_AUTOFILL_URL); const result = await sendToContentScript('duck-mail', { @@ -210,14 +210,14 @@ throw new Error(result.error); } if (!result?.email) { - throw new Error('未返回 Duck 邮箱地址。'); + throw new Error('No Duck Mail address returned.'); } await persistResolvedEmailState(state, result.email, { source: 'generated:duck', preserveAccountIdentity: Boolean(options?.preserveAccountIdentity), }); - await addLog(`Duck 邮箱:${result.generated ? '已生成' : '已读取'} ${result.email}`, 'ok'); + await addLog(`Duck Mail: ${result.generated ? 'Generated' : 'Read'} ${result.email}`, 'ok'); return result.email; } @@ -229,8 +229,8 @@ if (!email) { throw new Error( requestedIndex > 0 - ? `自定义邮箱池第 ${requestedIndex + 1} 个邮箱不存在,请检查邮箱池配置。` - : '自定义邮箱池为空,请先至少填写 1 个邮箱。' + ? `Email pool entry ${requestedIndex + 1} does not exist. Please check the email pool configuration.` + : 'Custom email pool is empty. Please fill in at least 1 email.' ); } @@ -238,7 +238,7 @@ source: 'generated:custom-pool', preserveAccountIdentity: Boolean(options?.preserveAccountIdentity), }); - await addLog(`自定义邮箱池:已取用 ${email}`, 'ok'); + await addLog(`Custom email pool: Using ${email}`, 'ok'); return email; } @@ -280,7 +280,7 @@ source: `generated:${provider || 'alias'}`, preserveAccountIdentity: Boolean(options?.preserveAccountIdentity), }); - await addLog(`${provider === 'gmail' ? 'Gmail +tag' : '2925'}:已生成 ${email}`, 'ok'); + await addLog(`${provider === 'gmail' ? 'Gmail +tag' : '2925'}: Generated ${email}`, 'ok'); return email; } @@ -307,7 +307,7 @@ mergedState.customEmailPool = options.customEmailPool; } if (generator === 'custom') { - throw new Error('当前邮箱生成方式为自定义邮箱,请直接填写注册邮箱。'); + throw new Error('Current email generation mode is custom email — please fill in the registration email directly.'); } if (generator === CUSTOM_EMAIL_POOL_GENERATOR) { return fetchCustomEmailPoolEmail(mergedState, options); diff --git a/background/ip-proxy-core.js b/background/ip-proxy-core.js index 24643298..0fee628c 100644 --- a/background/ip-proxy-core.js +++ b/background/ip-proxy-core.js @@ -1,4 +1,4 @@ -// background/ip-proxy-core.js — IP代理核心(解析/应用/切换/探测) +// background/ip-proxy-core.js — IP proxy core (parse/apply/switch/probe) let ipProxyAuthListenerInstalled = false; let ipProxyErrorListenerInstalled = false; let currentIpProxyAuthEntry = null; @@ -40,8 +40,8 @@ function normalizeAutomationWindowId(value) { } function buildAutomationWindowUnavailableError(error) { - const suffix = error?.message ? ` 原因:${error.message}` : ''; - return new Error(`自动任务窗口已不可用,请在目标 Chrome 窗口重新打开侧边栏并启动任务。${suffix}`); + const suffix = error?.message ? ` Reason: ${error.message}` : ''; + return new Error(`Automation window is no longer available. Please reopen the side panel in the target Chrome window and restart the task.${suffix}`); } async function createAutomationScopedTab(createProperties = {}, options = {}) { @@ -104,11 +104,11 @@ const IP_PROXY_EXIT_PROBE_ENDPOINTS = [ 'https://ident.me', ]; const IP_PROXY_EXIT_PROBE_ENDPOINTS_711_STICKY = [ - // 与 curl 口径对齐,保持单端点,避免多站点探测引入额外波动与耗时。 + // Align with curl semantics — keep a single endpoint to avoid extra variance/latency from multi-site probing. 'https://ipinfo.io/json', ]; const IP_PROXY_TARGET_REACHABILITY_ENDPOINTS = [ - // Step 1 的真实目标站点;出口 IP 可用不代表该站点的 CONNECT/TLS 链路可用。 + // Step 1 real target site — a usable exit IP doesn't guarantee its CONNECT/TLS link works. 'https://chatgpt.com/', ]; const IP_PROXY_TARGET_REACHABILITY_TIMEOUT_MS = 8000; @@ -278,8 +278,8 @@ async function maybeResolveProxyHostVariantForAuthSwitch(entry = {}, options = { } const provider = normalizeIpProxyProviderValue(entry?.provider || DEFAULT_IP_PROXY_SERVICE); const allow711ResolvedIp = Boolean(options?.allow711ResolvedIp); - // 711 默认仍保持域名路由;仅在“多账号列表切换”场景下允许解析 IP 变体, - // 用于强制打断同 host:port 连接复用,提升触发 407 挑战的概率。 + // 711 keeps domain routing by default; only allow IP variant resolution in "multi-account list switch" + // scenarios to force breaking same host:port connection reuse and improve odds of triggering 407 challenge. if (provider === '711proxy' && !allow711ResolvedIp) { return entry; } @@ -350,7 +350,7 @@ function buildEffectiveProxyEntryForApply(entry = {}, options = {}) { next.host = buildHostVariantForProxy(originalHost, ipProxyAuthHostVariantToggle); return next; } - // 单账号场景下保持 host 稳定,避免不必要的链路扰动。 + // Keep host stable in single-account scenarios to avoid unnecessary link disturbance. next.host = stripProxyHostTrailingDot(originalHost); return next; } @@ -557,7 +557,7 @@ function normalizeIpProxyAccountList(value = '') { if (!line) { return false; } - // 允许用注释快速停用账号列表行,避免“看似注释、实际仍生效”。 + // Allow comments to quickly disable account list rows, avoiding "looks like comment, actually still active". if (/^(?:#|\/\/|;)/.test(line)) { return false; } @@ -616,7 +616,7 @@ function shouldApplyConfiguredRegionFallbackToEntry(entry = {}, context = {}) { if (hostRegion) { return true; } - // 711 固定账号在 zone-custom 混播场景下,不使用旧的配置地区兜底,避免误报“卡在 AF”。 + // 711 fixed account in zone-custom mixed-broadcast scenarios: do not use legacy configured region fallback to avoid false "stuck at AF" reports. return false; } @@ -981,7 +981,7 @@ function normalizeProxyPoolEntries(value = [], provider = DEFAULT_IP_PROXY_SERVI contiguousEntries.forEach((entry) => append(entry)); } if (!result.length) { - // Lumi API 在 lb 参数异常时,可能返回无分隔的 host:port 串,这里做兜底拆分。 + // Lumi API may return host:port stream without separators when lb param is invalid — fallback split here. const contiguousMatches = rawText.match(/\d{1,3}(?:\.\d{1,3}){3}:\d{2,5}/g) || []; contiguousMatches.forEach((token) => { append(parseIpProxyLine(token, provider)); @@ -1140,15 +1140,15 @@ function getAccountListParseFailureHint(state = {}, provider = DEFAULT_IP_PROXY_ return ''; } if (normalizedProvider === '711proxy') { - return '账号列表已填写,但未解析出有效条目。请按每行 host:port:username:password 填写。'; + return 'Account list filled but no valid entries parsed. Please fill in host:port:username:password per line.'; } - return '账号列表已填写,但未解析出有效条目。请检查列表格式。'; + return 'Account list filled but no valid entries parsed. Please check list format.'; } function resolveIpProxyPoolTargetCountForMode(state = {}, mode = normalizeIpProxyMode(state?.ipProxyMode)) { const normalizedMode = normalizeIpProxyMode(mode); - // `ipProxyPoolTargetCount` 语义是“任务成功轮次阈值”, - // 拉取/切换代理池条数不再复用该字段,避免语义混淆。 + // `ipProxyPoolTargetCount` semantics is "task success round threshold"; + // proxy pool fetch/switch count no longer reuses this field to avoid semantic confusion. if (normalizedMode === 'account') { return 500; } @@ -1169,7 +1169,7 @@ function buildProxyPoolSummary(pool = [], preferredIndex = 0) { count: 0, index: 0, current: null, - display: '暂无可用代理', + display: 'No available proxy', }; } const index = normalizeIpProxyCurrentIndex(preferredIndex, 0) % normalizedPool.length; @@ -1250,7 +1250,7 @@ function buildIpProxyRuntimeStatePatch(mode = DEFAULT_IP_PROXY_MODE, runtime = { [fields.poolKey]: pool, [fields.indexKey]: summary.index, [fields.currentKey]: current, - // 兼容旧状态字段,保持当前激活模式的摘要可见。 + // Compatibility with legacy state fields — keep the current active mode's summary visible. ipProxyPool: pool, ipProxyCurrentIndex: summary.index, ipProxyCurrent: current, @@ -1391,8 +1391,8 @@ function shouldEnableIpProxyLeakGuardForStatus(status = {}) { return false; } const reason = String(status?.reason || '').trim().toLowerCase(); - // connectivity_failed 表示 PAC/代理接管已经下发,只是探测或目标站点失败。 - // 此时继续启用 DNR 会把 chatgpt.com 变成 ERR_BLOCKED_BY_CLIENT,掩盖真实代理链路结果。 + // connectivity_failed means PAC/proxy takeover was already applied, just the probe or target site failed. + // Continuing to enable DNR here would turn chatgpt.com into ERR_BLOCKED_BY_CLIENT, hiding the real proxy link result. return reason !== 'connectivity_failed'; } @@ -1604,34 +1604,34 @@ function applyExitRegionExpectation(status = {}, expectedRegion = '') { const hasErrorPageFailure = /Frame with ID 0 is showing error page|Cannot access contents of the page/i.test(exitError); const netErrorCode = exitError.match(/net::ERR_[A-Z_]+/i)?.[0] || ''; const navigationHint = netErrorCode - ? `导航错误:${netErrorCode}。` + ? `Navigation error: ${netErrorCode}.` : ''; const tunnelFailedWithoutChallenge = missingProxyChallenge && /ERR_TUNNEL_CONNECTION_FAILED/i.test(netErrorCode || exitError); const humanizedError = hasErrorPageFailure ? ( hasAuth - ? '探测页已进入浏览器错误页(常见于代理鉴权失败、代理节点不可用或网络被拦截)。请先检查代理账号/密码与节点可用性,再重试检测。' - : '探测页已进入浏览器错误页(常见于代理节点不可用、代理协议不匹配或网络被拦截)。请先检查代理节点、端口与协议配置,再重试检测。' + ? 'Probe page entered browser error page (common with proxy auth failures, unavailable proxy nodes, or blocked networks). Please check proxy username/password and node availability, then retry.' + : 'Probe page entered browser error page (common with unavailable proxy nodes, mismatched protocols, or blocked networks). Please check proxy node, port, and protocol settings, then retry.' ) : ''; const authDiagnostics = exitError.match(/probe:auth\([^)]+\)/i)?.[0] || ''; const challengeHint = missingProxyChallenge - ? '当前链路未收到 407 代理鉴权挑战(可能是代理端返回非标准拒绝,例如 630 no Proxy-Authorization),浏览器无法触发扩展鉴权回填。建议切换 API 模式,或更换会返回标准 407 的账号节点。' + ? 'Current link did not receive 407 proxy auth challenge (proxy may have returned non-standard rejection like 630 no Proxy-Authorization), browser cannot trigger extension auth fill-back. Suggestion: switch to API mode or use an account node that returns standard 407.' : ''; const compatibilityHint = tunnelFailedWithoutChallenge - ? '当前账号模式节点可能采用非标准代理鉴权链路(未发起 407 挑战),该链路在 Chrome 扩展代理 API 下无法自动回填凭据。' + ? 'Current account-mode node may use non-standard proxy auth link (no 407 challenge issued) — Chrome extension proxy API cannot auto-fill credentials on this link.' : ''; const mergedErrorParts = [ hasBackgroundAbortStorm ? ( hasBackgroundAbortFallback - ? '后台探测请求连续超时中断(代理隧道未在时限内建立),且已自动回退页面探测但仍未拿到可用出口。' - : '后台探测请求连续超时中断(代理隧道未在时限内建立)。这通常是代理节点链路不可用或被上游丢弃,并非探测页面本身异常。' + ? 'Background probe requests aborted consecutively (proxy tunnel not established in time), and page probe fallback also failed to get a usable exit.' + : 'Background probe requests aborted consecutively (proxy tunnel not established in time). Usually means proxy node link is unavailable or dropped upstream, not a probe page issue.' ) : '', hasProxyRuntimeError - ? '已捕获浏览器代理栈错误事件(chrome.proxy.onProxyError),请优先依据诊断中的 proxy_error 字段排查节点/协议兼容性。' + ? 'Captured browser proxy stack error event (chrome.proxy.onProxyError). Please troubleshoot node/protocol compatibility using proxy_error field in diagnostics.' : '', humanizedError, navigationHint, @@ -1639,13 +1639,13 @@ function applyExitRegionExpectation(status = {}, expectedRegion = '') { challengeHint, ].filter(Boolean); const mergedError = mergedErrorParts.length - ? `${mergedErrorParts.join(' ')}${authDiagnostics ? ` 诊断:${authDiagnostics}` : ''}` + ? `${mergedErrorParts.join(' ')}${authDiagnostics ? ` Diagnostics: ${authDiagnostics}` : ''}` : ''; const mergedWithDiagnostics = mergedError ? ( exitError && !mergedError.includes(exitError) - ? `${mergedError} 诊断:${exitError}` + ? `${mergedError} Diagnostics: ${exitError}` : mergedError ) : ''; @@ -1653,7 +1653,7 @@ function applyExitRegionExpectation(status = {}, expectedRegion = '') { ...status, applied: false, reason: 'connectivity_failed', - error: mergedWithDiagnostics || exitError || '未检测到代理出口 IP,无法确认代理已生效。', + error: mergedWithDiagnostics || exitError || 'No proxy exit IP detected. Cannot confirm proxy is in effect.', }; } @@ -1676,7 +1676,7 @@ function applyExitRegionExpectation(status = {}, expectedRegion = '') { ...status, applied: false, reason: 'connectivity_failed', - error: `已检测到出口 IP ${exitIp},但地区探测未返回国家/地区代码,暂无法校验期望地区 ${expected}。`, + error: `Detected exit IP ${exitIp}, but region probe did not return a country/region code. Cannot verify expected region ${expected}.`, }; } @@ -1684,28 +1684,28 @@ function applyExitRegionExpectation(status = {}, expectedRegion = '') { return status; } const sourceHint = entrySource === 'account_list' - ? '(来源:账号列表)' - : (entrySource === 'fixed_account' ? '(来源:固定账号)' : ''); + ? ' (source: account list)' + : (entrySource === 'fixed_account' ? ' (source: fixed account)' : ''); const usernameRegion = inferRegionFromProxyUsername(status?.username || ''); const usernameHint = usernameRegion ? ( hasProxyExitRegionMismatch(expected, usernameRegion) - ? ` 当前账号用户名地区标记为 ${usernameRegion},与期望不一致。` - : ` 当前账号用户名地区标记为 ${usernameRegion}。` + ? ` Current account username region marker is ${usernameRegion}, inconsistent with expected.` + : ` Current account username region marker is ${usernameRegion}.` ) : ''; const missingAuthChallengeHint = Boolean(status?.hasAuth) && parsedAuthSummary && parsedAuthSummary.challenge <= 0 && parsedAuthSummary.provided <= 0 - ? ' 当前链路未触发代理鉴权挑战(407),可能复用了历史代理连接/鉴权缓存,或代理端未返回标准 407 导致账号参数未被浏览器回填(可能走匿名链路)。可先关闭再开启代理,或切换到下一条节点后重试。' + ? ' Current link did not trigger proxy auth challenge (407). May be reusing historical proxy connection/auth cache, or proxy did not return standard 407 causing account params not filled back by browser (may use anonymous link). Try toggling proxy off and on, or switch to next node and retry.' : ''; - const authHint = authSummary ? ` 诊断:probe:${authSummary}` : ''; - const mismatchMessage = `代理出口地区与预期不一致:期望 ${expected},实际 ${detected || 'unknown'}${sourceHint}。${usernameHint}${missingAuthChallengeHint}${authHint}`.trim(); + const authHint = authSummary ? ` Diagnostics: probe:${authSummary}` : ''; + const mismatchMessage = `Proxy exit region inconsistent with expectation: expected ${expected}, actual ${detected || 'unknown'}${sourceHint}.${usernameHint}${missingAuthChallengeHint}${authHint}`.trim(); if (normalizedProvider === '711proxy') { const warningPrefix = missingAuthChallenge - ? '地区校验未通过且未触发代理鉴权挑战,疑似匿名链路;先保留代理接管并给出强告警:' - : '地区校验未通过,但已保留代理接管:'; + ? 'Region check failed and no proxy auth challenge triggered, suspected anonymous link; keeping proxy takeover with strong warning: ' + : 'Region check failed, but proxy takeover preserved: '; return { ...status, applied: true, @@ -1731,7 +1731,7 @@ function applyExitBaselineExpectation(status = {}) { ...status, applied: false, reason: 'connectivity_failed', - error: `已检测到出口 IP ${exitIp},但系统基线网络探测失败,无法确认是否经过插件代理链路。`, + error: `Detected exit IP ${exitIp}, but system baseline network probe failed. Cannot confirm whether traffic went through plugin proxy link.`, }; } if (!exitIp || !baselineIp) { @@ -1744,7 +1744,7 @@ function applyExitBaselineExpectation(status = {}) { ...status, applied: false, reason: 'connectivity_failed', - error: `检测到出口 IP ${exitIp} 与系统基线网络一致,疑似未经过插件代理链路。`, + error: `Detected exit IP ${exitIp} matches system baseline network — suspect not going through plugin proxy link.`, }; } @@ -1764,10 +1764,10 @@ function buildTargetReachabilityFailureMessage(status = {}, reachability = {}) { const endpoint = String(reachability?.endpoint || reachability?.url || IP_PROXY_TARGET_REACHABILITY_ENDPOINTS[0] || '').trim(); const targetHost = extractProbeHostFromTabUrl(endpoint) || endpoint || 'chatgpt.com'; const diagnostic = String(reachability?.error || reachability?.diagnostics || '').trim(); - const diagnosticSuffix = diagnostic ? ` 诊断:${diagnostic}` : ''; + const diagnosticSuffix = diagnostic ? ` Diagnostics: ${diagnostic}` : ''; const exitRegionSuffix = exitRegion ? ` [${exitRegion}]` : ''; - return `已检测到出口 IP ${exitIp}${exitRegionSuffix},但真实目标 ${targetHost} 不可达。` - + `这说明代理只通过了 IP 探测,无法打开步骤 1 的 ChatGPT 页面;请更换支持 chatgpt.com CONNECT/TLS 的节点。` + return `Detected exit IP ${exitIp}${exitRegionSuffix}, but real target ${targetHost} unreachable.` + + ` This means proxy only passed IP probe — cannot open Step 1 ChatGPT page. Please switch to a node supporting chatgpt.com CONNECT/TLS.` + diagnosticSuffix; } @@ -2746,16 +2746,16 @@ async function detectProxyExitInfo(options = {}) { async function pullIpProxyPoolFromApi(state = {}, options = {}) { const apiUrl = String(state?.ipProxyApiUrl || '').trim(); if (!apiUrl) { - throw new Error('代理 API 不能为空。'); + throw new Error('Proxy API cannot be empty.'); } let parsedUrl; try { parsedUrl = new URL(apiUrl); } catch { - throw new Error('代理 API 不是有效 URL。'); + throw new Error('Proxy API is not a valid URL.'); } if (!['http:', 'https:'].includes(parsedUrl.protocol)) { - throw new Error('代理 API 仅支持 http/https。'); + throw new Error('Proxy API only supports http/https.'); } const provider = normalizeIpProxyProviderValue(state?.ipProxyService); @@ -2766,7 +2766,7 @@ async function pullIpProxyPoolFromApi(state = {}, options = {}) { headers: { Accept: 'application/json, text/plain, */*' }, }, timeoutMs); if (!response.ok) { - throw new Error(`代理 API 请求失败(HTTP ${response.status})。`); + throw new Error(`Proxy API request failed (HTTP ${response.status}).`); } const rawText = await response.text(); @@ -2859,7 +2859,7 @@ function installIpProxyErrorListener() { function callChromeProxySettings(method, details) { const proxySettings = chrome.proxy?.settings; if (!proxySettings || typeof proxySettings[method] !== 'function') { - return Promise.reject(new Error('当前浏览器不支持扩展代理 API')); + return Promise.reject(new Error('Current browser does not support extension proxy API')); } return new Promise((resolve, reject) => { proxySettings[method](details, () => { @@ -2876,7 +2876,7 @@ function callChromeProxySettings(method, details) { function getChromeProxySettings(details = { incognito: false }) { const proxySettings = chrome.proxy?.settings; if (!proxySettings || typeof proxySettings.get !== 'function') { - return Promise.reject(new Error('当前浏览器不支持扩展代理 API')); + return Promise.reject(new Error('Current browser does not support extension proxy API')); } return new Promise((resolve, reject) => { proxySettings.get(details, (value) => { @@ -2895,7 +2895,7 @@ function validateProxyControlAfterApply(details, entry) { if (level && level !== 'controlled_by_this_extension') { return { ok: false, - message: `代理控制权不在当前扩展(levelOfControl=${level || 'unknown'})`, + message: `Proxy control not held by current extension (levelOfControl=${level || 'unknown'})`, }; } @@ -2903,7 +2903,7 @@ function validateProxyControlAfterApply(details, entry) { if (mode !== 'pac_script') { return { ok: false, - message: `代理模式不是 pac_script(当前为 ${mode || 'unknown'})`, + message: `Proxy mode is not pac_script (current: ${mode || 'unknown'})`, }; } @@ -2912,7 +2912,7 @@ function validateProxyControlAfterApply(details, entry) { if (pacData && !pacData.includes(endpoint)) { return { ok: false, - message: `PAC 未包含当前代理节点 ${endpoint},疑似被其他代理配置覆盖`, + message: `PAC does not contain current proxy node ${endpoint}, suspected override by other proxy configuration`, }; } @@ -3118,7 +3118,7 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { applied: false, reason: 'proxy_api_unavailable', provider: normalizeIpProxyProviderValue(resolvedState?.ipProxyService), - error: '当前浏览器不支持扩展代理 API。', + error: 'Current browser does not support extension proxy API.', exitDetecting: false, exitIp: '', exitRegion: '', @@ -3149,7 +3149,7 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { applied: false, reason: 'missing_proxy_entry', provider, - error: 'API 模式已启用,但代理 API 为空。', + error: 'API mode is enabled but proxy API is empty.', exitDetecting: false, exitIp: '', exitRegion: '', @@ -3208,8 +3208,8 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { effectiveEntry = await maybeResolveProxyHostVariantForAuthSwitch(effectiveEntry, { force: true, timeoutMs: 3500, - // 仅多节点账号列表场景允许解析 IP 变体; - // 单账号显式重绑仅使用 host 字面量变体,避免解析 IP 后链路不稳定。 + // Only allow IP variant resolution in multi-node account list scenarios; + // single-account explicit rebind uses host literal variants only to avoid link instability after IP resolution. allow711ResolvedIp: hasMultipleAccountEntries, }).catch(() => effectiveEntry); } @@ -3226,7 +3226,7 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { region: entry.region, hasAuth: Boolean(entry.username || entry.password), provider: normalizeIpProxyProviderValue(entry.provider || resolvedState?.ipProxyService), - error: '代理配置不完整,无法生成 PAC 规则。', + error: 'Proxy configuration is incomplete — cannot generate PAC rules.', exitDetecting: false, exitIp: '', exitRegion: '', @@ -3252,12 +3252,12 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { if (typeof addLog === 'function') { await addLog( explicitForceAuthRebind - ? '正在执行代理鉴权重绑:重置代理连接并切换主机变体,避免复用旧鉴权缓存。' - : '检测到同节点切换代理账号,正在重置代理连接并切换主机变体,避免复用旧鉴权缓存。', + ? 'Performing proxy auth rebind: resetting proxy connection and switching host variant to avoid reusing old auth cache.' + : 'Detected same-node proxy account switch — resetting proxy connection and switching host variant to avoid reusing old auth cache.', 'info' ).catch(() => {}); if (String(effectiveEntry?.host || '').trim() !== String(entry?.host || '').trim()) { - await addLog(`代理账号切换:已启用主机缓存隔离(${entry.host} -> ${effectiveEntry.host})。`, 'info').catch(() => {}); + await addLog(`Proxy account switch: enabled host cache isolation (${entry.host} -> ${effectiveEntry.host}).`, 'info').catch(() => {}); } } await setIpProxyLeakGuardEnabled(true); @@ -3291,7 +3291,7 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { mode: 'pac_script', pacScript: { data: pacScript, - // 官方文档建议启用 mandatory,避免 PAC 失效时静默回落到 DIRECT。 + // Chrome docs recommend enabling mandatory to avoid silent fallback to DIRECT when PAC fails. mandatory: true, }, }, @@ -3300,7 +3300,7 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { const proxySettings = await getChromeProxySettings({ incognito: false }).catch(() => null); const takeoverCheck = validateProxyControlAfterApply(proxySettings || {}, effectiveEntry); if (!takeoverCheck.ok) { - throw new Error(takeoverCheck.message || '代理接管校验失败。'); + throw new Error(takeoverCheck.message || 'Proxy takeover validation failed.'); } await setIpProxyLeakGuardEnabled(false); lastAppliedIpProxyEntrySignature = entrySignature; @@ -3322,7 +3322,7 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { region: entry.region, hasAuth: Boolean(entry.username || entry.password), provider: normalizeIpProxyProviderValue(entry.provider || resolvedState?.ipProxyService), - error: error?.message || String(error || '代理设置失败'), + error: error?.message || String(error || 'Proxy settings failed'), exitDetecting: false, exitIp: '', exitRegion: '', @@ -3353,7 +3353,8 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { await updateIpProxyRuntimeStatus(status); if (shouldForceDrain) { - // 同节点切换账号后给网络栈一点时间建立新隧道,避免立即探测命中旧连接。 + // After switching accounts on the same node, give the network stack a moment to establish a new tunnel, + // avoiding immediate probes that may hit the old connection. await new Promise((resolve) => setTimeout(resolve, 900)); } @@ -3369,8 +3370,8 @@ async function applyIpProxySettingsFromState(state = {}, options = {}) { errors: diagnostics, provider: status?.provider || provider, username: status?.username || entry?.username || '', - // 优先使用页面上下文探测,确保探测链路与真实浏览器页面一致; - // 后台探测仅作为补充兜底,避免单一路径误判。 + // Prefer page-context probe to ensure probe link is consistent with real browser pages; + // background probe is only a supplementary fallback to avoid single-path misjudgment. preferPageContext: true, allowBackgroundFallback: true, state, @@ -3451,7 +3452,7 @@ async function refreshIpProxyPool(options = {}) { if (parseFailureHint) { throw new Error(parseFailureHint); } - throw new Error('账号密码模式没有可用代理,请先填写代理列表,或填写 Host/Port。'); + throw new Error('Username/password mode has no usable proxies. Please fill in the proxy list or Host/Port.'); } } else { pool = await pullIpProxyPoolFromApi(state, { @@ -3459,7 +3460,7 @@ async function refreshIpProxyPool(options = {}) { timeoutMs: options.timeoutMs, }); if (!pool.length) { - throw new Error('代理列表为空,请检查 API 返回。'); + throw new Error('Proxy list is empty. Please check the API response.'); } } @@ -3514,7 +3515,7 @@ async function switchIpProxy(direction = 'next', options = {}) { const state = options.state || await getState(); const mode = normalizeIpProxyMode(options.mode || state?.ipProxyMode); if (mode === 'api' && !isApiModeProxyConfigAvailable(state)) { - throw new Error('API 模式代理 URL 为空,请先填写代理 API 地址。'); + throw new Error('API mode proxy URL is empty. Please fill in the proxy API address first.'); } const maxItems = Math.max( 1, @@ -3538,8 +3539,8 @@ async function switchIpProxy(direction = 'next', options = {}) { }); } throw new Error(mode === 'account' - ? '账号密码模式没有可切换的代理,请先填写代理列表或 Host/Port。' - : '当前没有可切换代理,请先点击“拉取”获取 IP 列表。'); + ? 'Username/password mode has no proxies to switch. Please fill in the proxy list or Host/Port.' + : 'No switchable proxy available. Please click "Pull" to fetch the IP list.'); } const nextIndex = getNextIpProxyPoolIndex(runtime.index, pool.length, direction); @@ -3592,29 +3593,29 @@ async function switchIpProxy(direction = 'next', options = {}) { async function changeIpProxyExit(options = {}) { const state = options.state || await getState(); if (!state?.ipProxyEnabled) { - throw new Error('请先启用 IP 代理。'); + throw new Error('Please enable IP proxy first.'); } const mode = normalizeIpProxyMode(options.mode || state?.ipProxyMode); const provider = normalizeIpProxyProviderValue(state?.ipProxyService); if (mode !== 'account') { - throw new Error('Change 仅支持账号密码模式。'); + throw new Error('Change only supports username/password mode.'); } if (provider !== '711proxy') { - throw new Error('Change 当前仅支持 711Proxy。'); + throw new Error('Change currently only supports 711Proxy.'); } const entry = getIpProxyCurrentEntryFromState(state); if (!entry || !entry.host || !entry.port) { - throw new Error('当前没有可用代理条目,无法执行 Change。'); + throw new Error('No available proxy entry — cannot execute Change.'); } const username = String(entry?.username || '').trim(); if (!has711SessionToken(username)) { - throw new Error('当前账号未配置 session,无法执行 Change。请先在账号中追加 session 参数。'); + throw new Error('Current account has no session configured — cannot execute Change. Please add session parameter to the account first.'); } if (typeof addLog === 'function') { - await addLog('正在执行 Change:保持当前 session,重绑代理链路并刷新出口。', 'info').catch(() => {}); + await addLog('Executing Change: keeping current session, rebinding proxy link and refreshing exit.', 'info').catch(() => {}); } const proxyRouting = await applyIpProxySettingsFromState(state, { @@ -3678,10 +3679,10 @@ async function tryRecoverApiProxyByRotation(options = {}) { const nodeText = Number.isInteger(switched?.index) && Number.isInteger(switched?.count) && switched.count > 0 - ? `(第 ${switched.index + 1}/${switched.count} 个节点)` + ? `(node ${switched.index + 1}/${switched.count})` : ''; - const refreshText = refreshedPool ? '(已自动刷新 IP 池)' : ''; - await addLog(`IP 代理自动恢复成功:API 模式已切换到可用节点${nodeText}${refreshText}。`, 'ok').catch(() => {}); + const refreshText = refreshedPool ? ' (auto-refreshed IP pool)' : ''; + await addLog(`IP proxy auto-recovery succeeded: API mode switched to available node ${nodeText}${refreshText}.`, 'ok').catch(() => {}); } return true; } @@ -3712,8 +3713,8 @@ async function tryRecoverApiProxyByRotation(options = {}) { if (latestStatus && attempts > 0) { const suffix = refreshedPool - ? `已自动轮换 ${attempts} 个 API 节点,并刷新 IP 池后重试,仍不可用。` - : `已自动轮换 ${attempts} 个 API 节点但仍不可用。`; + ? `Auto-rotated ${attempts} API nodes and refreshed IP pool, still unavailable.` + : `Auto-rotated ${attempts} API nodes but still unavailable.`; const currentError = String(latestStatus.error || '').trim(); latestStatus = { ...latestStatus, @@ -3827,7 +3828,7 @@ async function probeIpProxyExit(options = {}) { exitError: '', exitSource: '', }; - // 上一轮连通性失败会启用 fail-close 规则,手动检测前先解除,避免自阻断影响探测。 + // The previous round's connectivity failure may have enabled the fail-close rule; clear it before manual probe to avoid self-blocking. await setIpProxyLeakGuardEnabled(false); await updateIpProxyRuntimeStatus(probingStatus); @@ -3839,7 +3840,7 @@ async function probeIpProxyExit(options = {}) { errors: diagnostics, provider: statusSeed?.provider || probingStatus?.provider || provider, username: statusSeed?.username || probingStatus?.username || '', - // 手动探测与自动应用保持一致:先页面上下文,再后台补充。 + // Manual probe stays consistent with auto-apply: page context first, then background as backup. preferPageContext: true, allowBackgroundFallback: true, state, @@ -3847,7 +3848,7 @@ async function probeIpProxyExit(options = {}) { if (!exit?.ip && Boolean(statusSeed?.hasAuth)) { appendIpProxyAuthDiagnosticsToErrors(diagnostics); if (typeof addLog === 'function') { - await addLog(`代理出口探测失败:${buildProbeDiagnosticsSummary(diagnostics, IP_PROXY_DIAGNOSTICS_SUMMARY_MAX_ITEMS)}`, 'warn').catch(() => {}); + await addLog(`Proxy exit probe failed: ${buildProbeDiagnosticsSummary(diagnostics, IP_PROXY_DIAGNOSTICS_SUMMARY_MAX_ITEMS)}`, 'warn').catch(() => {}); } } const finalStatus = { @@ -3902,8 +3903,8 @@ async function probeIpProxyExit(options = {}) { const maxRebindAttempts = Math.max(1, Math.min(3, Number(options?.authRebindMaxAttempts) || 2)); for (let attempt = 0; attempt < maxRebindAttempts; attempt += 1) { if (typeof addLog === 'function') { - const hint = attempt === 0 ? '首次重绑复测' : `第 ${attempt + 1} 次重绑复测`; - await addLog(`检测到代理链路未触发 407 挑战,正在执行${hint}。`, 'info').catch(() => {}); + const hint = attempt === 0 ? 'initial rebind retest' : `rebind retest #${attempt + 1}`; + await addLog(`Detected proxy link did not trigger 407 challenge — performing ${hint}.`, 'info').catch(() => {}); } const latestBeforeRebind = await getState(); const reboundStatus = await applyIpProxySettingsFromState(latestBeforeRebind, { diff --git a/background/ip-proxy-provider-711proxy.js b/background/ip-proxy-provider-711proxy.js index 898bf22c..e13037c7 100644 --- a/background/ip-proxy-provider-711proxy.js +++ b/background/ip-proxy-provider-711proxy.js @@ -1,4 +1,4 @@ -// background/ip-proxy-provider-711proxy.js — 711Proxy 参数与账号规则 +// background/ip-proxy-provider-711proxy.js — 711Proxy parameters and account rules (function register711ProxyProvider(root) { function normalizeCountryCode(value = '') { const raw = String(value || '').trim().toUpperCase().replace(/[^A-Z]/g, ''); @@ -78,7 +78,7 @@ } } - // 账号列表模式按每行原样生效,不叠加固定账号区的 session/sessTime。 + // Account list mode honors each line as-is — do not overlay fixed account region's session/sessTime. if (hasAccountList) { return nextEntry; } diff --git a/background/logging-status.js b/background/logging-status.js index b3de7631..5c0ac32a 100644 --- a/background/logging-status.js +++ b/background/logging-status.js @@ -19,27 +19,27 @@ return sourceRegistry.getSourceLabel(source); } const labels = { - 'openai-auth': '认证页', - 'gmail-mail': 'Gmail 邮箱', - 'sidepanel': '侧边栏', - 'vps-panel': 'CPA 面板', - 'sub2api-panel': 'SUB2API 后台', - 'codex2api-panel': 'Codex2API 后台', - 'qq-mail': 'QQ 邮箱', - 'mail-163': '163 邮箱', - 'mail-2925': '2925 邮箱', - 'inbucket-mail': 'Inbucket 邮箱', - 'duck-mail': 'Duck 邮箱', - 'hotmail-api': 'Hotmail(API对接/本地助手)', - 'luckmail-api': 'LuckMail(API 购邮)', + 'openai-auth': 'Auth page', + 'gmail-mail': 'Gmail', + 'sidepanel': 'Side panel', + 'vps-panel': 'CPA panel', + 'sub2api-panel': 'SUB2API panel', + 'codex2api-panel': 'Codex2API panel', + 'qq-mail': 'QQ Mail', + 'mail-163': '163 Mail', + 'mail-2925': '2925 Mail', + 'inbucket-mail': 'Inbucket Mail', + 'duck-mail': 'Duck Mail', + 'hotmail-api': 'Hotmail (API/local helper)', + 'luckmail-api': 'LuckMail (API purchase)', 'cloudflare-temp-email': 'Cloudflare Temp Email', 'cloudmail': 'Cloud Mail', 'plus-checkout': 'Plus Checkout', - 'paypal-flow': 'PayPal 授权页', - 'gopay-flow': 'GoPay 授权页', - 'unknown-source': '未知来源', + 'paypal-flow': 'PayPal authorization page', + 'gopay-flow': 'GoPay authorization page', + 'unknown-source': 'Unknown source', }; - return labels[source] || source || '未知来源'; + return labels[source] || source || 'Unknown source'; } function normalizeLogStep(value) { @@ -75,7 +75,7 @@ async function setNodeStatus(nodeId, status) { const normalizedNodeId = String(nodeId || '').trim(); if (!normalizedNodeId) { - throw new Error('setNodeStatus 缺少 nodeId。'); + throw new Error('setNodeStatus missing nodeId.'); } const state = await getState(); const nodeStatuses = { ...(state.nodeStatuses || {}) }; @@ -112,23 +112,23 @@ function getLoginAuthStateLabel(state) { switch (state) { case 'verification_page': - return '登录验证码页'; + return 'Login verification code page'; case 'password_page': - return '密码页'; + return 'Password page'; case 'email_page': - return '邮箱输入页'; + return 'Email entry page'; case 'login_timeout_error_page': - return '登录超时报错页'; + return 'Login timeout error page'; case 'oauth_consent_page': - return 'OAuth 授权页'; + return 'OAuth consent page'; case 'add_phone_page': - return '手机号页'; + return 'Phone number page'; case 'add_email_page': - return '添加邮箱页'; + return 'Add email page'; case 'phone_verification_page': - return '手机验证码页'; + return 'Phone verification code page'; default: - return '未知页面'; + return 'Unknown page'; } } diff --git a/background/mail-2925-session.js b/background/mail-2925-session.js index d4a0ce91..ac8d9927 100644 --- a/background/mail-2925-session.js +++ b/background/mail-2925-session.js @@ -53,7 +53,7 @@ provider: '2925', source: MAIL2925_SOURCE, url: MAIL2925_URL, - label: '2925 邮箱', + label: '2925 Mail', inject: MAIL2925_INJECT, injectSource: MAIL2925_INJECT_SOURCE, }; @@ -81,7 +81,7 @@ } await requestStop({ - logMessage: errorMessage || '2925 登录失败,已按手动停止逻辑暂停自动流程。', + logMessage: errorMessage || '2925 login failed; paused auto-run via manual-stop logic.', }); return true; } @@ -90,7 +90,9 @@ const message = getErrorMessage(error); return message.startsWith(MAIL2925_LIMIT_ERROR_PREFIX) || message.includes('子邮箱已达上限') - || message.includes('已达上限邮箱'); + || message.includes('已达上限邮箱') + || message.includes('sub-mailbox limit reached') + || message.includes('mailbox limit reached'); } function isMail2925ThreadTerminatedError(error) { @@ -188,7 +190,7 @@ const accounts = normalizeMail2925Accounts(state.mail2925Accounts); const account = findMail2925Account(accounts, accountId); if (!account) { - throw new Error('未找到对应的 2925 账号。'); + throw new Error('Corresponding 2925 account not found.'); } let nextAccount = account; @@ -214,7 +216,7 @@ const accounts = normalizeMail2925Accounts(state.mail2925Accounts); const account = findMail2925Account(accounts, accountId); if (!account) { - throw new Error('未找到对应的 2925 账号。'); + throw new Error('Corresponding 2925 account not found.'); } const nextAccount = normalizeMail2925Account({ @@ -293,17 +295,17 @@ } if (!account) { - throw new Error('没有可用的 2925 账号。请先在侧边栏添加至少一个带密码的 2925 账号。'); + throw new Error('No available 2925 account. Please add at least one 2925 account with password in the side panel first.'); } if (!account.password) { - throw new Error(`2925 账号 ${account.email || account.id} 缺少密码,无法自动登录。`); + throw new Error(`2925 account ${account.email || account.id} is missing password — cannot auto-login.`); } if (!isMail2925AccountAvailable(account, now)) { const disabledUntil = Number(account.disabledUntil || 0); if (disabledUntil > now) { - throw new Error(`2925 账号 ${account.email || account.id} 当前处于冷却期,将在 ${new Date(disabledUntil).toLocaleString('zh-CN', { hour12: false })} 后恢复。`); + throw new Error(`2925 account ${account.email || account.id} is currently in cooldown — will recover after ${new Date(disabledUntil).toLocaleString('zh-CN', { hour12: false })}.`); } - throw new Error(`2925 账号 ${account.email || account.id} 当前不可用。`); + throw new Error(`2925 account ${account.email || account.id} is currently unavailable.`); } return setCurrentMail2925Account(account.id, { updateLastUsedAt: markUsed }); @@ -417,7 +419,7 @@ const currentUrl = (await getMail2925TabUrlById(numericTabId)) || await getMail2925CurrentTabUrl(); await addLog( - `2925:登录提交后页面发生跳转或重载,正在等待当前标签页恢复后继续确认登录态。当前地址:${currentUrl || 'unknown'}`, + `2925: Login submission triggered navigation or reload — waiting for the current tab to recover before re-confirming session. Current URL: ${currentUrl || 'unknown'}`, 'warn' ); @@ -427,7 +429,7 @@ retryDelayMs: 300, }); await addLog( - `2925:登录跳转等待结束,当前标签地址:${String(completedTab?.url || '').trim() || 'unknown'}`, + `2925: Login navigation wait ended, current tab URL: ${String(completedTab?.url || '').trim() || 'unknown'}`, completedTab?.url ? 'info' : 'warn' ); } @@ -438,19 +440,19 @@ injectSource: MAIL2925_INJECT_SOURCE, timeoutMs: MAIL2925_LOGIN_PAGE_RECOVERY_TIMEOUT_MS, retryDelayMs: 800, - logMessage: '步骤 0:2925 登录后页面仍在跳转,正在等待邮箱页重新就绪...', + logMessage: 'Step 0: 2925 page is still navigating after login — waiting for mailbox page to be ready again...', }); } const recoveredUrl = (await getMail2925TabUrlById(numericTabId)) || await getMail2925CurrentTabUrl(); - await addLog(`2925:登录跳转恢复后当前标签地址:${recoveredUrl || 'unknown'}`, 'info'); + await addLog(`2925: After login navigation recovery, current tab URL: ${recoveredUrl || 'unknown'}`, 'info'); } async function ensureMail2925MailboxSession(options = {}) { const { accountId = null, forceRelogin = false, - actionLabel = '确保 2925 邮箱登录态', + actionLabel = 'Ensure 2925 mailbox session', allowLoginWhenOnLoginPage = true, expectedMailboxEmail = '', } = options; @@ -488,18 +490,18 @@ }); const failMailboxSession = async (message) => { - const stopped = await stopAutoRunForMail2925LoginFailure(`${message}已按手动停止逻辑暂停自动流程。`); + const stopped = await stopAutoRunForMail2925LoginFailure(`${message} Paused auto-run via manual-stop logic.`); if (stopped) { - throw new Error('流程已被用户停止。'); + throw new Error('Flow stopped by user.'); } throw new Error(message); }; if (forceRelogin) { const removedCount = await clearMail2925SessionCookies(); - await addLog(`2925:已清理 ${removedCount} 个登录相关 cookie,准备使用 ${account.email} 重新登录。`, 'info'); + await addLog(`2925: Cleared ${removedCount} login-related cookies, preparing to login with ${account.email}.`, 'info'); if (typeof sleepWithStop === 'function') { - await addLog('2925:清理 cookie 后等待 3 秒,再打开登录页...', 'info'); + await addLog('2925: Waiting 3 seconds after cookie cleanup before opening login page...', 'info'); await sleepWithStop(3000); } } @@ -508,8 +510,8 @@ const targetUrl = forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL; await addLog( forceRelogin - ? `2925:准备打开登录页 ${MAIL2925_LOGIN_URL}(强制重登录)` - : `2925:准备打开邮箱页 ${MAIL2925_URL}(登录页自动登录=${allowLoginWhenOnLoginPage ? '开启' : '关闭'})`, + ? `2925: Preparing to open login page ${MAIL2925_LOGIN_URL} (force relogin)` + : `2925: Preparing to open mailbox page ${MAIL2925_URL} (auto-login on login page = ${allowLoginWhenOnLoginPage ? 'enabled' : 'disabled'})`, 'info' ); const tabId = await reuseOrCreateTab(MAIL2925_SOURCE, targetUrl, { @@ -521,7 +523,7 @@ if (!openedUrl) { openedUrl = await getMail2925CurrentTabUrl(); } - await addLog(`2925:打开页后当前标签地址:${openedUrl || 'unknown'}`, 'info'); + await addLog(`2925: After opening, current tab URL: ${openedUrl || 'unknown'}`, 'info'); if (forceRelogin && typeof waitForTabUrlMatch === 'function') { const matchedLoginTab = await waitForTabUrlMatch( @@ -529,7 +531,7 @@ (url) => isMail2925LoginUrl(url), { timeoutMs: 15000, retryDelayMs: 300 } ); - await addLog(`2925:等待最终落到登录页结果:${matchedLoginTab?.url || '超时'}`, matchedLoginTab ? 'info' : 'warn'); + await addLog(`2925: Wait for login page result: ${matchedLoginTab?.url || 'timeout'}`, matchedLoginTab ? 'info' : 'warn'); if (matchedLoginTab?.url) { openedUrl = String(matchedLoginTab.url || '').trim(); } @@ -541,17 +543,17 @@ injectSource: MAIL2925_INJECT_SOURCE, timeoutMs: 20000, retryDelayMs: 800, - logMessage: '步骤 0:2925 登录页内容脚本未就绪,正在等待页面稳定后继续登录...', + logMessage: 'Step 0: 2925 login page content script not ready — waiting for page to stabilize before continuing login...', }); } if (!forceRelogin && !isMail2925LoginUrl(openedUrl) && !normalizedExpectedMailboxEmail) { - await addLog('2925:当前邮箱页未跳转到登录页,将直接复用已登录会话。', 'info'); + await addLog('2925: Current mailbox page did not navigate to login page — reusing existing session.', 'info'); return buildSuccessPayload(); } if (!forceRelogin && isMail2925LoginUrl(openedUrl) && !allowLoginWhenOnLoginPage) { - await failMailboxSession(`2925:${actionLabel}失败,当前页面已跳转到登录页,且当前未启用 2925 账号池,不执行自动登录。`); + await failMailboxSession(`2925: ${actionLabel} failed — current page has navigated to login page, but 2925 account pool is not enabled, so auto-login will not run.`); } if (!account && (forceRelogin || allowLoginWhenOnLoginPage)) { @@ -562,14 +564,14 @@ } if (forceRelogin && typeof sleepWithStop === 'function') { - await addLog('2925:登录页已打开,等待 3 秒后开始检查输入框并执行登录...', 'info'); + await addLog('2925: Login page opened — waiting 3 seconds before checking inputs and executing login...', 'info'); await sleepWithStop(3000); } let result; const sendEnsureSessionRequest = async () => { const beforeSendUrl = (await getMail2925TabUrlById(tabId)) || await getMail2925CurrentTabUrl(); - await addLog(`2925:发送 ENSURE_MAIL2925_SESSION 前当前地址:${beforeSendUrl || 'unknown'}`, 'info'); + await addLog(`2925: URL before sending ENSURE_MAIL2925_SESSION: ${beforeSendUrl || 'unknown'}`, 'info'); return sendLoginMessage( MAIL2925_SOURCE, { @@ -587,7 +589,7 @@ timeoutMs: MAIL2925_LOGIN_MESSAGE_RETRY_WINDOW_MS, retryDelayMs: 800, responseTimeoutMs: MAIL2925_LOGIN_RESPONSE_TIMEOUT_MS, - logMessage: '步骤 0:2925 登录页通信异常,正在等待页面恢复...', + logMessage: 'Step 0: 2925 login page communication anomaly — waiting for page recovery...', } ); }; @@ -597,7 +599,7 @@ if (isRetryableMail2925TransportError(err)) { try { await recoverMail2925LoginPageAfterTransportError(tabId); - await addLog('2925:页面恢复完成,正在重新确认登录态...', 'info'); + await addLog('2925: Page recovery complete — re-confirming login state...', 'info'); result = await sendEnsureSessionRequest(); } catch (recoveryErr) { err = recoveryErr; @@ -605,26 +607,26 @@ } if (!result) { - const message = `2925:${actionLabel}失败(${getErrorMessage(err) || '登录结果确认超时'})。`; - const stopped = await stopAutoRunForMail2925LoginFailure(`${message}已按手动停止逻辑暂停自动流程。`); + const message = `2925: ${actionLabel} failed (${getErrorMessage(err) || 'login result confirmation timed out'}).`; + const stopped = await stopAutoRunForMail2925LoginFailure(`${message} Paused auto-run via manual-stop logic.`); if (stopped) { - throw new Error('流程已被用户停止。'); + throw new Error('Flow stopped by user.'); } throw err; } } if (result?.error) { - await failMailboxSession(`2925:${actionLabel}失败(${result.error})。`); + await failMailboxSession(`2925: ${actionLabel} failed (${result.error}).`); } if (result?.limitReached) { - throw new Error(`${MAIL2925_LIMIT_ERROR_PREFIX}${result.limitMessage || '子邮箱已达上限邮箱'}`); + throw new Error(`${MAIL2925_LIMIT_ERROR_PREFIX}${result.limitMessage || 'Sub-mailbox limit reached.'}`); } const actualMailboxEmail = normalizeMailboxEmail(result?.mailboxEmail || ''); if (normalizedExpectedMailboxEmail && actualMailboxEmail && actualMailboxEmail !== normalizedExpectedMailboxEmail) { if (allowLoginWhenOnLoginPage) { await addLog( - `2925:当前邮箱页显示账号 ${actualMailboxEmail},与目标账号 ${normalizedExpectedMailboxEmail} 不一致,准备登出当前账号并登录目标账号。`, + `2925: Current mailbox page shows account ${actualMailboxEmail}, which does not match target account ${normalizedExpectedMailboxEmail}. Preparing to log out current account and log in target account.`, 'warn' ); return ensureMail2925MailboxSession({ @@ -636,18 +638,18 @@ }); } await failMailboxSession( - `2925:${actionLabel}失败,当前邮箱页显示账号 ${actualMailboxEmail},与目标账号 ${normalizedExpectedMailboxEmail} 不一致,且当前未启用 2925 账号池。` + `2925: ${actionLabel} failed — current mailbox page shows account ${actualMailboxEmail}, which does not match target account ${normalizedExpectedMailboxEmail}, and 2925 account pool is not enabled.` ); } if (normalizedExpectedMailboxEmail && !actualMailboxEmail && result?.currentView === 'mailbox') { - await addLog('2925:未能识别当前邮箱页顶部邮箱地址,已跳过邮箱一致性校验。', 'warn'); + await addLog('2925: Could not detect current mailbox email at top of page — skipped email consistency check.', 'warn'); } if (!result?.loggedIn) { - await failMailboxSession(`2925:${actionLabel}失败,登录后仍未进入收件箱。`); + await failMailboxSession(`2925: ${actionLabel} failed — did not enter inbox after login.`); } if (!account) { - await addLog('2925:未触发自动登录,继续复用当前已登录会话。', 'info'); + await addLog('2925: Auto-login not triggered — reusing existing logged-in session.', 'info'); return { account: null, mail: getMail2925MailConfig(), @@ -666,7 +668,7 @@ broadcastDataUpdate({ currentMail2925AccountId: account.id }); const finalUrl = (await getMail2925TabUrlById(tabId)) || await getMail2925CurrentTabUrl(); - await addLog(`2925:登录态确认成功,当前地址=${finalUrl || 'unknown'}`, 'ok'); + await addLog(`2925: Login state confirmed, current URL=${finalUrl || 'unknown'}`, 'ok'); return { account: await ensureMail2925AccountForFlow({ @@ -680,7 +682,7 @@ async function handleMail2925LimitReachedError(step, error) { const reason = getErrorMessage(error).replace(MAIL2925_LIMIT_ERROR_PREFIX, '').trim() - || '子邮箱已达上限邮箱'; + || 'Sub-mailbox limit reached.'; const state = await getState(); const currentAccount = getCurrentMail2925Account(state); const poolEnabled = Boolean(state?.mail2925UseAccountPool); @@ -688,19 +690,19 @@ if (!poolEnabled) { if (typeof requestStop === 'function') { await requestStop({ - logMessage: `步骤 ${step}:2925 检测到“${reason}”,当前未启用账号池,已按手动停止逻辑暂停自动流程。`, + logMessage: `Step ${step}: 2925 detected "${reason}", account pool not enabled — paused auto-run via manual-stop logic.`, }); } - return new Error('流程已被用户停止。'); + return new Error('Flow stopped by user.'); } if (!currentAccount) { if (typeof requestStop === 'function') { await requestStop({ - logMessage: `步骤 ${step}:2925 检测到“${reason}”,但当前没有可识别的账号可供切换。`, + logMessage: `Step ${step}: 2925 detected "${reason}", but no identifiable account is available to switch.`, }); } - return new Error('流程已被用户停止。'); + return new Error('Flow stopped by user.'); } const disabledUntil = Date.now() + Math.max(1, Number(MAIL2925_LIMIT_COOLDOWN_MS) || (24 * 60 * 60 * 1000)); @@ -710,7 +712,7 @@ lastError: reason, }); await addLog( - `步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”,已禁用到 ${new Date(disabledUntil).toLocaleString('zh-CN', { hour12: false })}。`, + `Step ${step}: 2925 account ${currentAccount.email} hit "${reason}", disabled until ${new Date(disabledUntil).toLocaleString('zh-CN', { hour12: false })}.`, 'warn' ); @@ -726,10 +728,10 @@ broadcastDataUpdate({ currentMail2925AccountId: null }); if (typeof requestStop === 'function') { await requestStop({ - logMessage: `步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”,但当前没有可切换的下一个账号。`, + logMessage: `Step ${step}: 2925 account ${currentAccount.email} hit "${reason}", and there is no next account available to switch.`, }); } - return new Error('流程已被用户停止。'); + return new Error('Flow stopped by user.'); } await setCurrentMail2925Account(nextAccount.id); @@ -737,11 +739,11 @@ accountId: nextAccount.id, forceRelogin: true, allowLoginWhenOnLoginPage: true, - actionLabel: `步骤 ${step}:切换 2925 账号`, + actionLabel: `Step ${step}: Switch 2925 account`, }); - await addLog(`步骤 ${step}:2925 已切换到下一个账号 ${nextAccount.email}。`, 'warn'); + await addLog(`Step ${step}: 2925 switched to next account ${nextAccount.email}.`, 'warn'); return buildMail2925ThreadTerminatedError( - `步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”,已切换到 ${nextAccount.email},当前尝试结束,等待下一轮重试。` + `Step ${step}: 2925 account ${currentAccount.email} hit "${reason}", switched to ${nextAccount.email}. Current attempt ended, waiting for next retry.` ); } diff --git a/background/mail-rule-registry.js b/background/mail-rule-registry.js index 22b4fcc1..64ece0e2 100644 --- a/background/mail-rule-registry.js +++ b/background/mail-rule-registry.js @@ -21,7 +21,7 @@ const flowId = resolveFlowId(state); const flowBuilder = getFlowBuilder(flowId); if (!flowBuilder || typeof flowBuilder.getRuleDefinition !== 'function') { - throw new Error(`未找到 flow=${flowId} 的邮件规则定义。`); + throw new Error(`Mail rule definition not found for flow=${flowId}.`); } return flowBuilder.getRuleDefinition(step, state); } @@ -30,7 +30,7 @@ const flowId = resolveFlowId(state); const flowBuilder = getFlowBuilder(flowId); if (!flowBuilder) { - throw new Error(`未找到 flow=${flowId} 的邮件规则定义。`); + throw new Error(`Mail rule definition not found for flow=${flowId}.`); } if (typeof flowBuilder.getRuleDefinitionForNode === 'function') { return flowBuilder.getRuleDefinitionForNode(nodeId, state); @@ -38,14 +38,14 @@ if (typeof flowBuilder.getRuleDefinition === 'function') { return flowBuilder.getRuleDefinition({ nodeId }, state); } - throw new Error(`未找到 flow=${flowId} 的邮件规则定义。`); + throw new Error(`Mail rule definition not found for flow=${flowId}.`); } function buildVerificationPollPayload(step, state = {}, overrides = {}) { const flowId = resolveFlowId(state); const flowBuilder = getFlowBuilder(flowId); if (!flowBuilder || typeof flowBuilder.buildVerificationPollPayload !== 'function') { - throw new Error(`未找到 flow=${flowId} 的邮件轮询规则构造器。`); + throw new Error(`Mail polling rule builder not found for flow=${flowId}.`); } return flowBuilder.buildVerificationPollPayload(step, state, overrides); } @@ -54,7 +54,7 @@ const flowId = resolveFlowId(state); const flowBuilder = getFlowBuilder(flowId); if (!flowBuilder) { - throw new Error(`未找到 flow=${flowId} 的邮件轮询规则构造器。`); + throw new Error(`Mail polling rule builder not found for flow=${flowId}.`); } if (typeof flowBuilder.buildVerificationPollPayloadForNode === 'function') { return flowBuilder.buildVerificationPollPayloadForNode(nodeId, state, overrides); @@ -62,7 +62,7 @@ if (typeof flowBuilder.buildVerificationPollPayload === 'function') { return flowBuilder.buildVerificationPollPayload({ nodeId }, state, overrides); } - throw new Error(`未找到 flow=${flowId} 的邮件轮询规则构造器。`); + throw new Error(`Mail polling rule builder not found for flow=${flowId}.`); } return { diff --git a/background/message-router.js b/background/message-router.js index 1fadb689..f8b9e641 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -346,12 +346,12 @@ const nodeId = String(message?.payload?.nodeId || message?.nodeId || '').trim(); if (!nodeId) { - throw new Error(`${type} 缺少 nodeId。`); + throw new Error(`${type} missing nodeId.`); } const state = await getState(); const step = findStepByNodeId(nodeId, state); if (!step) { - throw new Error(`当前 flow 中未找到节点:${nodeId}`); + throw new Error(`Node not found in current flow: ${nodeId}`); } const payload = { @@ -549,7 +549,7 @@ async function setNodeStatusByStep(step, status, state = {}) { const nodeId = getStepKeyForState(step, state); if (!nodeId) { - throw new Error(`未找到步骤 ${step} 对应节点。`); + throw new Error(`Step ${step} has no matching node.`); } await setNodeStatus(nodeId, status); return nodeId; @@ -572,10 +572,10 @@ function getPlusPaymentMethodLabel(value = '') { const method = normalizePlusPaymentMethodForDisplay(value); if (method === 'none') { - return '无需支付'; + return 'No payment'; } if (method === 'paypal-hosted') { - return 'PayPal 无卡直绑'; + return 'PayPal cardless direct bind'; } if (method === 'gpc-helper') { return 'GPC'; @@ -596,7 +596,7 @@ function getPlusAccountAccessStrategyLabel(value = '') { return normalizePlusAccountAccessStrategyForDisplay(value) === 'sub2api_codex_session' - ? '导入当前 ChatGPT 会话到 SUB2API' + ? 'Import current ChatGPT session into SUB2API' : 'OAuth'; } @@ -604,19 +604,19 @@ const strategy = normalizePlusAccountAccessStrategyForDisplay(value); const normalizedTargetId = String(targetId || '').trim().toLowerCase(); if (strategy === 'sub2api_codex_session') { - return '导入当前 ChatGPT 会话到 SUB2API'; + return 'Import current ChatGPT session into SUB2API'; } if (strategy === 'cpa_codex_session') { - return '导入当前 ChatGPT 会话到 CPA'; + return 'Import current ChatGPT session into CPA'; } if (normalizedTargetId === 'cpa') { - return '通过 OAuth 回调创建 CPA 账号'; + return 'Create CPA account via OAuth callback'; } if (normalizedTargetId === 'sub2api') { - return '通过 OAuth 回调创建 SUB2API 账号'; + return 'Create SUB2API account via OAuth callback'; } if (normalizedTargetId === 'codex2api') { - return '通过 OAuth 回调创建 Codex2API 账号'; + return 'Create Codex2API account via OAuth callback'; } return 'OAuth'; } @@ -628,7 +628,7 @@ const latestState = await getState(); if (typeof markCurrentRegistrationAccountUsed === 'function') { await markCurrentRegistrationAccountUsed(latestState, { - logPrefix: '流程完成', + logPrefix: 'Flow completed', level: 'ok', }); } else if (latestState.currentHotmailAccountId && isHotmailProvider(latestState)) { @@ -636,23 +636,23 @@ used: true, lastUsedAt: Date.now(), }); - await addLog('当前 Hotmail 账号已自动标记为已用。', 'ok'); + await addLog('Current Hotmail account automatically marked as used.', 'ok'); } if (typeof markCurrentRegistrationAccountUsed !== 'function' && String(latestState.mailProvider || '').trim().toLowerCase() === '2925' && latestState.currentMail2925AccountId) { await patchMail2925Account(latestState.currentMail2925AccountId, { lastUsedAt: Date.now(), lastError: '', }); - await addLog('当前 2925 账号已记录最近使用时间。', 'ok'); + await addLog('Current 2925 account: last used time recorded.', 'ok'); } if (typeof markCurrentRegistrationAccountUsed !== 'function' && isLuckmailProvider(latestState)) { const currentPurchase = getCurrentLuckmailPurchase(latestState); if (currentPurchase?.id) { await setLuckmailPurchaseUsedState(currentPurchase.id, true); - await addLog(`当前 LuckMail 邮箱 ${currentPurchase.email_address} 已在本地标记为已用。`, 'ok'); + await addLog(`Current LuckMail email ${currentPurchase.email_address} marked as used locally.`, 'ok'); } await clearLuckmailRuntimeState({ clearEmail: true }); - await addLog('当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。', 'ok'); + await addLog('Current LuckMail email runtime state cleared. Next round will prefer reusing unused emails or purchasing new ones.', 'ok'); } if ( typeof markCurrentRegistrationAccountUsed !== 'function' @@ -660,7 +660,7 @@ && typeof clearYydsMailRuntimeState === 'function' ) { await clearYydsMailRuntimeState({ clearEmail: true }); - await addLog('当前 YYDS Mail 邮箱运行态已清空,下轮将重新创建邮箱。', 'ok'); + await addLog('Current YYDS Mail runtime state cleared. Next round will create new mailbox.', 'ok'); } const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl); if (localhostPrefix) { @@ -721,7 +721,7 @@ const currentStatus = getNodeStatusByStep(loginCodeStep, latestState); if (!isStepProtectedFromAutoSkip(currentStatus)) { await setNodeStatusByStep(loginCodeStep, 'skipped', latestState); - await addLog(`认证页已直接进入 OAuth 授权页,已自动跳过步骤 ${loginCodeStep} 的登录验证码。`, 'warn', { + await addLog(`Auth page directly entered OAuth consent page — auto-skipped Step ${loginCodeStep} login verification code.`, 'warn', { step, stepKey: 'oauth-login', }); @@ -786,7 +786,7 @@ if (stepKey === 'confirm-oauth') { if (payload.localhostUrl) { if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) { - throw new Error(`步骤 ${step} 返回了无效的 localhost OAuth 回调地址。`); + throw new Error(`Step ${step} returned invalid localhost OAuth callback URL.`); } await setState({ localhostUrl: payload.localhostUrl }); broadcastDataUpdate({ localhostUrl: payload.localhostUrl }); @@ -832,7 +832,7 @@ } await setNodeStatusByStep(skippedStep, 'skipped', latestState); } - await addLog('步骤 2:检测到当前已登录会话,已自动跳过步骤 3/4/5,流程将直接进入步骤 6。', 'warn'); + await addLog('Step 2: Existing logged-in session detected — auto-skipped Steps 3/4/5, flow will go directly to Step 6.', 'warn'); break; } if (payload.skippedPasswordStep) { @@ -840,8 +840,8 @@ const step3Status = getNodeStatusByStep(3, latestState); if (step3Status !== 'running' && step3Status !== 'completed' && step3Status !== 'manual_completed') { await setNodeStatusByStep(3, 'skipped', latestState); - const identityLabel = payload.accountIdentifierType === 'phone' ? '手机号' : '邮箱'; - await addLog(`步骤 2:提交${identityLabel}后页面直接进入验证码页,已自动跳过步骤 3。`, 'warn'); + const identityLabel = payload.accountIdentifierType === 'phone' ? 'phone number' : 'email'; + await addLog(`Step 2: After submitting ${identityLabel}, page went directly to verification code page — auto-skipped Step 3.`, 'warn'); } } break; @@ -855,7 +855,7 @@ const step5Status = getNodeStatusByStep(5, latestState); if (step5Status !== 'running' && step5Status !== 'completed' && step5Status !== 'manual_completed') { await setNodeStatusByStep(5, 'skipped', latestState); - await addLog('步骤 3:页面已直接进入已登录态,已自动跳过步骤 5。', 'warn'); + await addLog('Step 3: Page directly entered logged-in state — auto-skipped Step 5.', 'warn'); } } if (payload.loginVerificationRequestedAt) { @@ -879,9 +879,9 @@ if (step5Status !== 'running' && step5Status !== 'completed' && step5Status !== 'manual_completed') { await setNodeStatusByStep(5, 'skipped', latestState); if (payload.skipProfileStepReason === 'combined_verification_profile') { - await addLog('步骤 4:当前验证码页已内嵌完成注册资料提交,已自动跳过步骤 5。', 'warn'); + await addLog('Step 4: Current verification code page embedded registration profile submission — auto-skipped Step 5.', 'warn'); } else { - await addLog('步骤 4:检测到账号已直接进入已登录态,已自动跳过步骤 5。', 'warn'); + await addLog('Step 4: Account directly entered logged-in state — auto-skipped Step 5.', 'warn'); } } } @@ -907,7 +907,7 @@ case 9: if (payload.localhostUrl) { if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) { - throw new Error('步骤 9 返回了无效的 localhost OAuth 回调地址。'); + throw new Error('Step 9 returned invalid localhost OAuth callback URL.'); } await setState({ localhostUrl: payload.localhostUrl }); broadcastDataUpdate({ localhostUrl: payload.localhostUrl }); @@ -926,7 +926,7 @@ if (tabId && message.source) { await registerTab(message.source, tabId); flushCommand(message.source, tabId); - await addLog(`内容脚本已就绪:${getSourceLabel(message.source)}(标签页 ${tabId})`); + await addLog(`Content script ready: ${getSourceLabel(message.source)} (tab ${tabId})`); } return { ok: true }; } @@ -950,12 +950,12 @@ const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim(); const resolvedStep = findStepByNodeId(nodeId, currentStateForNode); if (!nodeId || !resolvedStep) { - throw new Error('NODE_COMPLETE 缺少 nodeId。'); + throw new Error('NODE_COMPLETE missing nodeId.'); } const currentState = await getState(); if (isStaleAutoRunNodeMessage(nodeId, currentState)) { await addLog( - `自动运行:忽略过期的节点 ${nodeId} 完成消息,当前流程已在节点 ${currentState.currentNodeId || '未知'}。`, + `Auto-run: Ignoring stale completion message for node ${nodeId}, current flow already at node ${currentState.currentNodeId || 'unknown'}.`, 'warn', { nodeId } ); @@ -963,8 +963,8 @@ } if (getStopRequested()) { await setNodeStatus(nodeId, 'stopped'); - await appendManualAccountRunRecordIfNeeded(`node:${nodeId}:stopped`, null, '流程已被用户停止。'); - notifyNodeError(nodeId, '流程已被用户停止。'); + await appendManualAccountRunRecordIfNeeded(`node:${nodeId}:stopped`, null, 'Flow stopped by user.'); + notifyNodeError(nodeId, 'Flow stopped by user.'); return { ok: true }; } try { @@ -976,12 +976,12 @@ const userMessage = typeof handleCloudflareSecurityBlocked === 'function' ? await handleCloudflareSecurityBlocked(error) : (error?.message || String(error || '')); - notifyNodeError(nodeId, '流程已被用户停止。'); + notifyNodeError(nodeId, 'Flow stopped by user.'); return { ok: true, error: userMessage }; } - const errorMessage = error?.message || String(error || '步骤 3 提交后确认失败'); + const errorMessage = error?.message || String(error || 'Step 3 post-submit confirmation failed'); await setNodeStatus(nodeId, 'failed'); - await addLog(`失败:${errorMessage}`, 'error', { + await addLog(`Failed: ${errorMessage}`, 'error', { nodeId, }); await appendManualAccountRunRecordIfNeeded(`node:${nodeId}:failed`, null, errorMessage); @@ -997,9 +997,9 @@ const completionState = isFinalNode ? completionStateCandidate : null; if (!deferCompletionUntilBackgroundValidation) { await setNodeStatus(nodeId, 'completed'); - await addLog('已完成', 'ok', { nodeId }); + await addLog('Completed', 'ok', { nodeId }); } else { - await addLog('步骤 5:已收到资料页完成信号,等待后台最终复核后再标记完成。', 'info', { + await addLog('Step 5: Profile page completion signal received — waiting for background final review before marking as complete.', 'info', { step: 5, stepKey: nodeId, }); @@ -1017,12 +1017,12 @@ const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim(); const resolvedStep = findStepByNodeId(nodeId, stateForNode); if (!nodeId || !resolvedStep) { - throw new Error('NODE_ERROR 缺少 nodeId。'); + throw new Error('NODE_ERROR missing nodeId.'); } const staleCheckState = await getState(); if (isStaleAutoRunNodeMessage(nodeId, staleCheckState)) { await addLog( - `自动运行:忽略过期的节点 ${nodeId} 失败消息,当前流程已在节点 ${staleCheckState.currentNodeId || '未知'}。原始错误:${message.error || '未知错误'}`, + `Auto-run: Ignoring stale failure message for node ${nodeId}, current flow already at node ${staleCheckState.currentNodeId || 'unknown'}. Original error: ${message.error || 'unknown error'}`, 'warn', { nodeId } ); @@ -1032,7 +1032,7 @@ const userMessage = typeof handleCloudflareSecurityBlocked === 'function' ? await handleCloudflareSecurityBlocked(message.error) : (typeof message.error === 'string' ? message.error : String(message.error || '')); - notifyNodeError(nodeId, '流程已被用户停止。'); + notifyNodeError(nodeId, 'Flow stopped by user.'); return { ok: true, error: userMessage }; } const currentState = await getState(); @@ -1040,13 +1040,13 @@ const isSignupPhonePasswordMismatch = /SIGNUP_PHONE_PASSWORD_MISMATCH::/i.test(String(message.error || '')); if (isStopError(message.error)) { await setNodeStatus(nodeId, 'stopped'); - await addLog('已被用户停止', 'warn', { nodeId }); + await addLog('Stopped by user', 'warn', { nodeId }); await appendManualAccountRunRecordIfNeeded(`node:${nodeId}:stopped`, null, message.error); notifyNodeError(nodeId, message.error); } else { if (!(isSignupPhonePasswordMismatch && currentNodeStatus === 'failed')) { await setNodeStatus(nodeId, 'failed'); - await addLog(`失败:${message.error}`, 'error', { + await addLog(`Failed: ${message.error}`, 'error', { nodeId, }); await appendManualAccountRunRecordIfNeeded(`node:${nodeId}:failed`, null, message.error); @@ -1084,7 +1084,7 @@ if (isGpcOtp && confirmed) { const otp = String(message.payload?.otp || message.payload?.code || '').trim().replace(/[^\d]/g, ''); if (!otp) { - throw new Error('请输入 GPC OTP 验证码。'); + throw new Error('Please enter the GPC OTP verification code.'); } const otpUpdates = { ...clearManualConfirmationState, @@ -1094,7 +1094,7 @@ if (typeof broadcastDataUpdate === 'function') { broadcastDataUpdate(otpUpdates); } - await addLog(`步骤 ${step}:已收到 GPC OTP,准备提交验证。`, 'ok'); + await addLog(`Step ${step}: Received GPC OTP, ready to submit verification.`, 'ok'); return { ok: true }; } @@ -1104,8 +1104,8 @@ } if (confirmed) { - const methodLabel = method === 'gopay' ? 'GoPay' : '手动'; - await addLog(`步骤 ${step}:已确认${methodLabel}订阅完成,准备继续下一步。`, 'ok'); + const methodLabel = method === 'gopay' ? 'GoPay' : 'Manual'; + await addLog(`Step ${step}: Confirmed ${methodLabel} subscription complete, ready to continue.`, 'ok'); await completeNodeFromBackground(confirmationNodeId, { plusManualConfirmationMethod: currentState?.plusManualConfirmationMethod || '', plusManualConfirmedAt: Date.now(), @@ -1114,10 +1114,10 @@ } const cancelMessage = method === 'gopay' - ? '已取消 GoPay 订阅确认' - : (isGpcOtp ? '已取消 GPC OTP 输入' : '已取消当前手动确认'); + ? 'GoPay subscription confirmation cancelled' + : (isGpcOtp ? 'GPC OTP input cancelled' : 'Current manual confirmation cancelled'); await setNodeStatus(confirmationNodeId, 'failed'); - await addLog(`步骤 ${step}:${cancelMessage}。`, 'warn'); + await addLog(`Step ${step}: ${cancelMessage}.`, 'warn'); await appendManualAccountRunRecordIfNeeded( confirmationNodeId ? `node:${confirmationNodeId}:failed` : 'failed', null, @@ -1135,39 +1135,39 @@ clearStopRequest(); await clearAutoRunTimerAlarm(); await resetState(); - await addLog('流程已重置', 'info'); + await addLog('Flow has been reset', 'info'); return { ok: true }; } case 'CLEAR_FREE_REUSABLE_PHONE': { if (typeof clearFreeReusablePhoneActivation !== 'function') { - throw new Error('白嫖复用手机号清除能力未接入。'); + throw new Error('Free reusable phone clearing capability not connected.'); } return await clearFreeReusablePhoneActivation(); } case 'CLEAR_GROK_SSO_COOKIES': { if (typeof clearGrokSsoCookies !== 'function') { - throw new Error('Grok SSO 清空能力未接入。'); + throw new Error('Grok SSO clearing capability not connected.'); } return await clearGrokSsoCookies(); } case 'SET_FREE_REUSABLE_PHONE': { if (typeof setFreeReusablePhoneActivation !== 'function') { - throw new Error('白嫖复用手机号记录能力未接入。'); + throw new Error('Free reusable phone recording capability not connected.'); } return await setFreeReusablePhoneActivation(message.payload || {}); } case 'SET_ACCOUNT_CONTRIBUTION_MODE': { const enabled = Boolean(message.payload?.enabled); - const state = await ensureManualInteractionAllowed(enabled ? '进入账号贡献' : '退出账号贡献'); + const state = await ensureManualInteractionAllowed(enabled ? 'Enter account contribution' : 'Exit account contribution'); if (Object.values(state.nodeStatuses || {}).some((status) => status === 'running')) { - throw new Error(enabled ? '当前有步骤正在执行,无法进入账号贡献。' : '当前有步骤正在执行,无法退出账号贡献。'); + throw new Error(enabled ? 'A step is currently running — cannot enter account contribution.' : 'A step is currently running — cannot exit account contribution.'); } if (typeof setAccountContributionMode !== 'function') { - throw new Error('账号贡献切换能力未接入。'); + throw new Error('Account contribution toggle capability not connected.'); } return { ok: true, @@ -1179,15 +1179,15 @@ } case 'START_FLOW_CONTRIBUTION': { - const state = await ensureManualInteractionAllowed('开始贡献'); + const state = await ensureManualInteractionAllowed('Start contribution'); if (Object.values(state.nodeStatuses || {}).some((status) => status === 'running')) { - throw new Error('当前有步骤正在执行,无法开始贡献流程。'); + throw new Error('A step is currently running — cannot start contribution flow.'); } if (!state?.accountContributionEnabled) { - throw new Error('请先进入账号贡献。'); + throw new Error('Please enter account contribution first.'); } if (typeof startFlowContribution !== 'function') { - throw new Error('贡献 OAuth 流程尚未接入。'); + throw new Error('Contribution OAuth flow not connected.'); } return { ok: true, @@ -1201,10 +1201,10 @@ case 'SUBMIT_FLOW_CONTRIBUTION': { const state = await getState(); if (!state?.accountContributionEnabled) { - throw new Error('请先进入账号贡献。'); + throw new Error('Please enter account contribution first.'); } if (typeof submitFlowContribution !== 'function') { - throw new Error('贡献提交能力尚未接入。'); + throw new Error('Contribution submission capability not connected.'); } return { ok: true, @@ -1216,7 +1216,7 @@ case 'POLL_FLOW_CONTRIBUTION_STATUS': { if (typeof pollContributionStatus !== 'function') { - throw new Error('贡献状态轮询能力尚未接入。'); + throw new Error('Contribution status polling capability not connected.'); } return { ok: true, @@ -1229,7 +1229,7 @@ case 'CLEAR_ACCOUNT_RUN_HISTORY': { const state = await getState(); if (isAutoRunLockedState(state)) { - throw new Error('自动流程运行中,当前不能清理邮箱记录。'); + throw new Error('Auto flow is running — cannot clear mailbox records currently.'); } if (typeof clearAccountRunHistory !== 'function') { return { ok: true, clearedCount: 0 }; @@ -1241,7 +1241,7 @@ case 'DELETE_ACCOUNT_RUN_HISTORY_RECORDS': { const state = await getState(); if (isAutoRunLockedState(state)) { - throw new Error('自动流程运行中,当前不能删除邮箱记录。'); + throw new Error('Auto flow is running — cannot delete mailbox records currently.'); } if (typeof deleteAccountRunHistoryRecords !== 'function') { return { ok: true, deletedCount: 0, remainingCount: 0 }; @@ -1257,17 +1257,17 @@ const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim(); const resolvedStep = findStepByNodeId(nodeId, requestState); if (!nodeId || !resolvedStep) { - throw new Error('EXECUTE_NODE 缺少 nodeId。'); + throw new Error('EXECUTE_NODE missing nodeId.'); } if (message.source === 'sidepanel') { await lockAutomationWindowFromMessage(message, sender); - await ensureManualInteractionAllowed('手动执行节点'); + await ensureManualInteractionAllowed('Manual node execution'); } if (typeof assertNodeExecutionAllowedForState === 'function') { - assertNodeExecutionAllowedForState(nodeId, requestState, '手动执行节点'); + assertNodeExecutionAllowedForState(nodeId, requestState, 'Manual node execution'); } if (message.source === 'sidepanel') { - await invalidateDownstreamAfterStepRestart(resolvedStep, { logLabel: `节点 ${nodeId} 重新执行` }); + await invalidateDownstreamAfterStepRestart(resolvedStep, { logLabel: `Node ${nodeId} re-execution` }); } if (message.payload.email) { await setEmailState(message.payload.email); @@ -1318,10 +1318,10 @@ state, }); if (autoRunStartValidation?.ok === false) { - throw new Error(autoRunStartValidation.errors?.[0]?.message || '当前设置不支持启动自动流程。'); + throw new Error(autoRunStartValidation.errors?.[0]?.message || 'Current settings do not support starting the auto flow.'); } if (getPendingAutoRunTimerPlan(state)) { - throw new Error('已有线程间隔等待,请先停止或立即继续。'); + throw new Error('A thread interval is already waiting. Please stop or continue immediately first.'); } const totalRuns = normalizeRunCount(message.payload?.totalRuns || 1); const autoRunSkipFailures = Boolean(message.payload?.autoRunSkipFailures); @@ -1338,7 +1338,7 @@ } const skipped = await skipAutoRunCountdown(); if (!skipped) { - throw new Error('当前没有可立即开始的倒计时。'); + throw new Error('No countdown available to start immediately.'); } return { ok: true }; } @@ -1358,15 +1358,15 @@ } case 'TAKEOVER_AUTO_RUN': { - await requestStop({ logMessage: '已确认手动接管,正在停止自动流程并切换为手动控制...' }); - await addLog('自动流程已切换为手动控制。', 'warn'); + await requestStop({ logMessage: 'Manual takeover confirmed — stopping auto flow and switching to manual control...' }); + await addLog('Auto flow has been switched to manual control.', 'warn'); return { ok: true }; } case 'SKIP_NODE': { const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim(); if (!nodeId) { - throw new Error('SKIP_NODE 缺少 nodeId。'); + throw new Error('SKIP_NODE missing nodeId.'); } return await skipNode(nodeId); } @@ -1501,8 +1501,8 @@ const nextIpProxyEnabled = hasIpProxyEnabledUpdate ? Boolean(updates.ipProxyEnabled) : previousIpProxyEnabled; - // 仅在“手动开关代理”时自动应用。 - // 其他字段改动(host/账号/地区/session 等)需由“同步/下一条/检测出口/Change”显式触发。 + // Automatically apply only when "manually toggling proxy". + // Other field changes (host/account/region/session etc.) require explicit triggers via "Sync/Next/Detect Exit/Change". const shouldApplyIpProxyOnSave = hasIpProxyUpdates && hasIpProxyEnabledUpdate && previousIpProxyEnabled !== nextIpProxyEnabled; @@ -1510,8 +1510,8 @@ if (shouldApplyIpProxyOnSave && typeof applyIpProxySettingsFromState === 'function') { const isEnablingProxy = !previousIpProxyEnabled && nextIpProxyEnabled; proxyRouting = await applyIpProxySettingsFromState(mergedState, { - // 手动开启时自动应用一次代理,不做出口探测; - // 出口探测由“同步/检测出口”按钮显式触发,避免开启即误判为失败。 + // When manually enabling, apply proxy once automatically without exit probing; + // Exit probing is triggered explicitly by "Sync/Detect Exit" button to avoid false failures on enable. skipExitProbe: true, resetNetworkState: false, forceAuthRebind: false, @@ -1519,7 +1519,7 @@ }).catch((error) => ({ applied: false, reason: 'apply_failed', - error: error?.message || String(error || '代理应用失败'), + error: error?.message || String(error || 'Proxy apply failed'), })); } if (Boolean(currentState?.accountContributionEnabled) && typeof setAccountContributionMode === 'function') { @@ -1543,15 +1543,15 @@ ); await addLog( Boolean(updates.plusModeEnabled) - ? `Plus 模式已开启,已切换为 Plus Checkout 步骤,当前支付方式:${selectedPlusPaymentMethod},账号接入策略:${selectedPlusAccountAccessStrategy}。` - : 'Plus 模式已关闭,已恢复普通注册授权步骤。', + ? `Plus mode enabled — switched to Plus Checkout steps. Current payment method: ${selectedPlusPaymentMethod}, account access strategy: ${selectedPlusAccountAccessStrategy}.` + : 'Plus mode disabled — restored to regular registration authorization steps.', 'info' ); } else if (plusPaymentChanged && nextPlusModeEnabled) { const selectedPlusPaymentMethod = getPlusPaymentMethodLabel( stateUpdates.plusPaymentMethod ?? currentState?.plusPaymentMethod ?? 'paypal' ); - await addLog(`Plus 支付方式已切换为 ${selectedPlusPaymentMethod},已更新对应的 Plus 步骤。`, 'info'); + await addLog(`Plus payment method switched to ${selectedPlusPaymentMethod}. Updated corresponding Plus steps.`, 'info'); } else if (plusAccountAccessStrategyChanged && nextPlusModeEnabled) { const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel( stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth', @@ -1559,7 +1559,7 @@ ?? currentState?.targetId ?? 'cpa' ); - await addLog(`Plus 账号接入策略已切换为 ${selectedPlusAccountAccessStrategy},已更新对应的 Plus 尾链。`, 'info'); + await addLog(`Plus account access strategy switched to ${selectedPlusAccountAccessStrategy}. Updated corresponding Plus tail chain.`, 'info'); } return { ok: true, @@ -1571,7 +1571,7 @@ case 'REFRESH_GPC_CARD_BALANCE': { if (typeof refreshGpcCardBalance !== 'function') { - throw new Error('GPC API Key 余额查询能力尚未接入。'); + throw new Error('GPC API Key balance query capability not connected.'); } const state = await getState(); const result = await refreshGpcCardBalance({ @@ -1585,7 +1585,7 @@ case 'CHECK_KIRO_RS_CONNECTION': { if (typeof testKiroRsConnection !== 'function') { - throw new Error('kiro.rs 连接测试能力尚未接入。'); + throw new Error('kiro.rs connection test capability not connected.'); } const currentState = await getState(); const activeFlowId = normalizeMessageFlowId( @@ -1623,7 +1623,7 @@ case 'RUN_IP_PROXY_AUTO_SYNC_NOW': { if (typeof runIpProxyAutoSync !== 'function') { - throw new Error('IP 代理自动同步能力尚未接入。'); + throw new Error('IP proxy auto-sync capability not connected.'); } const result = await runIpProxyAutoSync('manual'); return { ok: true, ...result }; @@ -1631,7 +1631,7 @@ case 'REFRESH_IP_PROXY_POOL': { if (typeof refreshIpProxyPool !== 'function') { - throw new Error('IP 代理池能力尚未接入。'); + throw new Error('IP proxy pool capability not connected.'); } const result = await refreshIpProxyPool({ maxItems: message.payload?.maxItems, @@ -1643,7 +1643,7 @@ case 'SWITCH_IP_PROXY': { if (typeof switchIpProxy !== 'function') { - throw new Error('IP 代理切换能力尚未接入。'); + throw new Error('IP proxy switching capability not connected.'); } const result = await switchIpProxy(message.payload?.direction || 'next', { maxItems: message.payload?.maxItems, @@ -1656,7 +1656,7 @@ case 'CHANGE_IP_PROXY_EXIT': { if (typeof changeIpProxyExit !== 'function') { - throw new Error('IP 代理 Change 能力尚未接入。'); + throw new Error('IP proxy Change capability not connected.'); } const result = await changeIpProxyExit({ mode: message.payload?.mode, @@ -1670,7 +1670,7 @@ await lockAutomationWindowFromMessage(message, sender); } if (typeof probeIpProxyExit !== 'function') { - throw new Error('IP 代理出口检测能力尚未接入。'); + throw new Error('IP proxy exit detection capability not connected.'); } const probeState = await getState(); const mode = typeof normalizeIpProxyMode === 'function' @@ -1682,7 +1682,7 @@ const is711AccountMode = mode === 'account' && provider === '711proxy'; const previousReason = String(probeState?.ipProxyAppliedReason || '').trim().toLowerCase(); const previousExitError = String(probeState?.ipProxyAppliedExitError || '').trim(); - const hadMissingAuthChallenge = /challenge=0|provided=0|未触发代理鉴权挑战|未收到 407/i.test(previousExitError); + const hadMissingAuthChallenge = /challenge=0|provided=0|did not trigger proxy auth challenge|did not receive 407|未触发代理鉴权挑战|未收到 407/i.test(previousExitError); const shouldPreRebindBeforeProbe = Boolean( probeState?.ipProxyEnabled && is711AccountMode @@ -1692,7 +1692,7 @@ ? Number(message.payload.timeoutMs) : (is711AccountMode ? (shouldPreRebindBeforeProbe ? 15000 : 12000) : undefined); - // 手动“检测出口”前先轻量应用当前配置,避免读取到旧代理链路状态。 + // Before manual "Detect Exit", lightly apply the current config so we don't read stale proxy link state. if (probeState?.ipProxyEnabled && typeof applyIpProxySettingsFromState === 'function') { await applyIpProxySettingsFromState(probeState, { skipExitProbe: true, @@ -1765,7 +1765,7 @@ try { const result = await verifyHotmailAccount(accountId); await setCurrentHotmailAccount(result.account.id, { markUsed: false, syncEmail: true }); - await addLog(`Hotmail 账号 ${result.account.email} 校验通过,可直接用于收信。`, 'ok'); + await addLog(`Hotmail account ${result.account.email} verified — ready for receiving mail.`, 'ok'); return { ok: true, account: result.account, messageCount: result.messageCount }; } catch (err) { const state = await getState(); @@ -1821,12 +1821,12 @@ updateLastUsedAt: false, }); if (typeof deps.ensureMail2925MailboxSession !== 'function') { - throw new Error('2925 登录能力尚未接入。'); + throw new Error('2925 login capability not connected.'); } await deps.ensureMail2925MailboxSession({ accountId: account.id, forceRelogin: Boolean(message.payload?.forceRelogin), - actionLabel: '侧边栏手动登录 2925 账号', + actionLabel: 'Side panel manual login of 2925 account', }); return { ok: true, account }; } @@ -1869,7 +1869,7 @@ case 'SET_EMAIL_STATE': { const state = await getState(); if (isAutoRunLockedState(state)) { - throw new Error('自动流程运行中,当前不能手动修改邮箱。'); + throw new Error('Auto flow is running — cannot manually modify email at this time.'); } const email = String(message.payload?.email || '').trim() || null; await setEmailStateSilently(email, { source: 'manual' }); @@ -1879,7 +1879,7 @@ case 'SAVE_EMAIL': { const state = await getState(); if (isAutoRunLockedState(state)) { - throw new Error('自动流程运行中,当前不能手动修改邮箱。'); + throw new Error('Auto flow is running — cannot manually modify email at this time.'); } await setEmailState(message.payload.email, { source: 'manual' }); await resumeAutoRun(); @@ -1889,7 +1889,7 @@ case 'SET_SIGNUP_PHONE_STATE': { const state = await getState(); if (isAutoRunLockedState(state)) { - throw new Error('自动流程运行中,当前不能手动修改注册手机号。'); + throw new Error('Auto flow is running — cannot manually modify signup phone number at this time.'); } const phoneNumber = resolveSignupPhonePayload(message.payload) || null; await setSignupPhoneStateSilently(phoneNumber); @@ -1899,7 +1899,7 @@ case 'SAVE_SIGNUP_PHONE': { const state = await getState(); if (isAutoRunLockedState(state)) { - throw new Error('自动流程运行中,当前不能手动修改注册手机号。'); + throw new Error('Auto flow is running — cannot manually modify signup phone number at this time.'); } const phoneNumber = resolveSignupPhonePayload(message.payload) || null; await setSignupPhoneState(phoneNumber); @@ -1910,7 +1910,7 @@ clearStopRequest(); const state = await getState(); if (isAutoRunLockedState(state)) { - throw new Error('自动流程运行中,当前不能手动获取邮箱。'); + throw new Error('Auto flow is running — cannot manually fetch email at this time.'); } const email = await fetchGeneratedEmail(state, message.payload || {}); await resumeAutoRun(); @@ -1921,7 +1921,7 @@ clearStopRequest(); const state = await getState(); if (isAutoRunLockedState(state)) { - throw new Error('自动流程运行中,当前不能手动获取邮箱。'); + throw new Error('Auto flow is running — cannot manually fetch email at this time.'); } const email = await fetchGeneratedEmail(state, { ...(message.payload || {}), generator: 'duck' }); await resumeAutoRun(); diff --git a/background/panel-bridge.js b/background/panel-bridge.js index dd553fa7..fe6383a5 100644 --- a/background/panel-bridge.js +++ b/background/panel-bridge.js @@ -28,7 +28,7 @@ const factory = deps.createSub2ApiApi || self.MultiPageBackgroundSub2ApiApi?.createSub2ApiApi; if (typeof factory !== 'function') { - throw new Error('SUB2API 直连接口模块未加载,无法生成 OAuth 链接。'); + throw new Error('SUB2API direct API module is not loaded — cannot generate OAuth link.'); } sub2ApiApi = factory({ addLog, @@ -60,19 +60,19 @@ const message = candidates .map((value) => String(value || '').trim()) .find(Boolean); - return message || `Codex2API 请求失败(HTTP ${responseStatus})。`; + return message || `Codex2API request failed (HTTP ${responseStatus}).`; } function deriveCpaManagementOrigin(vpsUrl) { const normalizedUrl = String(vpsUrl || '').trim(); if (!normalizedUrl) { - throw new Error('尚未配置 CPA 地址,请先在侧边栏填写。'); + throw new Error('CPA URL is not configured. Please fill it in the side panel first.'); } let parsed; try { parsed = new URL(normalizedUrl); } catch { - throw new Error('CPA 地址格式无效,请先在侧边栏检查。'); + throw new Error('CPA URL format is invalid. Please check the side panel.'); } return parsed.origin; } @@ -87,7 +87,7 @@ const message = candidates .map((value) => String(value || '').trim()) .find(Boolean); - return message || `CPA 管理接口请求失败(HTTP ${responseStatus})。`; + return message || `CPA management API request failed (HTTP ${responseStatus}).`; } async function fetchCpaManagementJson(origin, path, options = {}) { @@ -127,7 +127,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error('CPA 管理接口请求超时,请稍后重试。'); + throw new Error('CPA management API request timed out. Please retry later.'); } throw error; } finally { @@ -166,7 +166,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error('Codex2API 请求超时,请稍后重试。'); + throw new Error('Codex2API request timed out. Please retry later.'); } throw error; } finally { @@ -185,18 +185,18 @@ } async function requestCpaOAuthUrl(state, options = {}) { - const { logLabel = 'OAuth 刷新' } = options; + const { logLabel = 'OAuth refresh' } = options; if (!state.vpsUrl) { - throw new Error('尚未配置 CPA 地址,请先在侧边栏填写。'); + throw new Error('CPA URL is not configured. Please fill it in the side panel first.'); } const managementKey = String(state.vpsPassword || '').trim(); if (!managementKey) { - throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。'); + throw new Error('CPA management key is not configured. Please fill it in the side panel first.'); } const origin = deriveCpaManagementOrigin(state.vpsUrl); - await addLog(`${logLabel}:正在通过 CPA 管理接口获取 OAuth 授权链接...`); + await addLog(`${logLabel}: Fetching OAuth authorization link via CPA management API...`); const result = await fetchCpaManagementJson(origin, '/v0/management/codex-auth-url', { method: 'GET', managementKey, @@ -223,7 +223,7 @@ || extractStateFromAuthUrl(oauthUrl); if (!oauthUrl || !oauthUrl.startsWith('http')) { - throw new Error('CPA 管理接口未返回有效的 auth_url。'); + throw new Error('CPA management API did not return a valid auth_url.'); } return { @@ -234,16 +234,16 @@ } async function requestCodex2ApiOAuthUrl(state, options = {}) { - const { logLabel = 'OAuth 刷新' } = options; + const { logLabel = 'OAuth refresh' } = options; const codex2apiUrl = normalizeCodex2ApiUrl(state.codex2apiUrl); const adminKey = normalizeAdminKey(state.codex2apiAdminKey); if (!adminKey) { - throw new Error('尚未配置 Codex2API 管理密钥,请先在侧边栏填写。'); + throw new Error('Codex2API admin key is not configured. Please fill it in the side panel first.'); } const origin = new URL(codex2apiUrl).origin; - await addLog(`${logLabel}:正在通过 Codex2API 协议生成 OAuth 授权链接...`); + await addLog(`${logLabel}: Generating OAuth authorization link via Codex2API protocol...`); const result = await fetchCodex2ApiJson(origin, '/api/admin/oauth/generate-auth-url', { adminKey, @@ -256,7 +256,7 @@ const oauthState = extractStateFromAuthUrl(oauthUrl); if (!oauthUrl || !sessionId) { - throw new Error('Codex2API 未返回有效的 auth_url 或 session_id。'); + throw new Error('Codex2API did not return a valid auth_url or session_id.'); } return { @@ -267,17 +267,17 @@ } async function requestSub2ApiOAuthUrl(state, options = {}) { - const { logLabel = 'OAuth 刷新' } = options; + const { logLabel = 'OAuth refresh' } = options; const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl); if (!sub2apiUrl) { throw new Error('SUB2API URL is not configured. Please fill it in the side panel first.'); } if (!state.sub2apiEmail) { - throw new Error('尚未配置 SUB2API 登录邮箱,请先在侧边栏填写。'); + throw new Error('SUB2API login email is not configured. Please fill it in the side panel first.'); } if (!state.sub2apiPassword) { - throw new Error('尚未配置 SUB2API 登录密码,请先在侧边栏填写。'); + throw new Error('SUB2API login password is not configured. Please fill it in the side panel first.'); } const api = getSub2ApiApi(); diff --git a/background/paypal-account-store.js b/background/paypal-account-store.js index 6d6c8893..0b730508 100644 --- a/background/paypal-account-store.js +++ b/background/paypal-account-store.js @@ -89,7 +89,7 @@ const accounts = normalizePayPalAccounts(state.paypalAccounts); const account = findPayPalAccount(accounts, accountId); if (!account) { - throw new Error('未找到对应的 PayPal 账号。'); + throw new Error('Corresponding PayPal account not found.'); } await syncSelectedPayPalAccountState(account); diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index 08141efd..16bd3021 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -411,7 +411,7 @@ function assertFiveSimMaxPriceCompatibleWithOperator(operator, maxPriceLimit) { const normalizedOperator = normalizeFiveSimCountryCode(operator, DEFAULT_FIVE_SIM_OPERATOR); if (maxPriceLimit !== null && maxPriceLimit !== undefined && normalizedOperator !== DEFAULT_FIVE_SIM_OPERATOR) { - throw new Error('5sim 价格上限仅支持运营商为 "any" 时使用;请清空价格上限,或先把运营商切换为 any。'); + throw new Error('5sim price cap is only supported when operator is "any". Please clear the price cap or switch operator to any first.'); } } @@ -941,30 +941,30 @@ function formatStep9Reason(reason = '') { const text = String(reason || '').trim(); if (!text) { - return '未知'; + return 'Unknown'; } const normalized = text.toLowerCase(); const reasonMap = { - returned_to_add_phone_loop: '反复返回添加手机号页', - phone_number_used: '手机号已被使用', - sms_not_received: '未收到短信', - sms_timeout: '短信超时', - resend_throttled: '重发短信被限流', - code_rejected: '验证码被拒绝', - add_phone_rejected: '添加手机号被拒绝', - activation_not_found: '接码订单不存在或已失效', - resend_phone_banned: 'OpenAI 无法向该号码发送短信', - phone_max_usage_exceeded: '手机号达到使用上限', - resend_server_error: '重发短信后进入服务器错误页', - whatsapp_resend_channel: '页面重发入口切换为 WhatsApp 通道', - unknown: '未知', + returned_to_add_phone_loop: 'Repeatedly returned to add phone page', + phone_number_used: 'Phone number already used', + sms_not_received: 'SMS not received', + sms_timeout: 'SMS timeout', + resend_throttled: 'SMS resend throttled', + code_rejected: 'Verification code rejected', + add_phone_rejected: 'Add phone rejected', + activation_not_found: 'SMS order does not exist or expired', + resend_phone_banned: 'OpenAI cannot send SMS to this number', + phone_max_usage_exceeded: 'Phone number reached usage limit', + resend_server_error: 'Entered server error page after SMS resend', + whatsapp_resend_channel: 'Page resend entry switched to WhatsApp channel', + unknown: 'Unknown', }; if (reasonMap[normalized]) { return reasonMap[normalized]; } const timeoutWindowMatch = text.match(/^sms_timeout_after_(\d+)_windows$/i); if (timeoutWindowMatch) { - return `连续 ${timeoutWindowMatch[1]} 轮等待后仍未收到短信`; + return `Still no SMS received after ${timeoutWindowMatch[1]} wait rounds`; } return text; } @@ -972,49 +972,49 @@ function formatPhoneSmsApiFailureReason(reason = '') { const text = String(reason || '').trim(); if (!text) { - return '未知错误'; + return 'Unknown error'; } if (/\bBAD_KEY\b|\bWRONG_KEY\b|\bINVALID_KEY\b/i.test(text)) { - return 'API Key 无效(BAD_KEY)'; + return 'Invalid API Key (BAD_KEY)'; } if (/\bNO_BALANCE\b|\bNOT_ENOUGH_BALANCE\b/i.test(text)) { - return '余额不足'; + return 'Insufficient balance'; } if (/\bBANNED\b|\bACCOUNT_BANNED\b/i.test(text)) { - return '账号已被封禁'; + return 'Account banned'; } if (/\bNO_NUMBERS\b/i.test(text)) { - return '暂无可用号码(NO_NUMBERS)'; + return 'No available numbers (NO_NUMBERS)'; } if (/no\s+free\s+phones|numbers?\s+not\s+found|no\s+numbers\s+available|no\s+numbers\s+within|暂无可用号码|均无可用号码|无可用号码/i.test(text)) { - return '暂无可用号码'; + return 'No available numbers'; } const wrongMaxPrice = text.match(/\bWRONG_MAX_PRICE(?::|\s+requires\s+)?(\d+(?:\.\d+)?)?\b/i); if (wrongMaxPrice) { return wrongMaxPrice[1] - ? `价格上限过低,平台要求至少 ${wrongMaxPrice[1]}(WRONG_MAX_PRICE)` - : '价格上限不符合平台要求(WRONG_MAX_PRICE)'; + ? `Price cap too low; platform requires at least ${wrongMaxPrice[1]} (WRONG_MAX_PRICE)` + : 'Price cap does not meet platform requirements (WRONG_MAX_PRICE)'; } if (/rate\s*limit|too\s+many\s+requests|限流/i.test(text)) { - return '请求限流'; + return 'Request throttled'; } if (/unauthorized|forbidden|invalid\s+token|bad\s+key|wrong\s+key/i.test(text)) { - return 'API Key 无效'; + return 'Invalid API Key'; } if (/order\s+not\s+found|activation\s+not\s+found|no\s+such\s+order/i.test(text)) { - return '订单不存在或已失效'; + return 'Order does not exist or expired'; } if (/timed\s*out|timeout/i.test(text)) { - return '请求超时'; + return 'Request timed out'; } if (/failed\s+to\s+fetch|networkerror|load\s+failed/i.test(text)) { - return '网络请求失败'; + return 'Network request failed'; } if (/empty\s+response/i.test(text)) { - return '空响应'; + return 'Empty response'; } if (/unknown\s+terminal\s+error/i.test(text)) { - return '未知终止错误'; + return 'Unknown termination error'; } return text; } @@ -1022,24 +1022,24 @@ function formatHeroSmsActionName(action = '') { const normalized = String(action || '').trim().toLowerCase(); if (normalized === 'getnumber' || normalized === 'getnumberv2') { - return '获取手机号'; + return 'Acquire phone number'; } if (normalized === 'getstatus' || normalized === 'getstatusv2') { - return '查询短信状态'; + return 'Query SMS status'; } if (normalized === 'setstatus') { - return '更新订单状态'; + return 'Update order status'; } if (normalized === 'getprices' || normalized === 'getpricesextended') { - return '查询价格'; + return 'Query price'; } - return action ? `${action} 请求` : '请求'; + return action ? `${action} request` : 'request'; } function formatPhoneSmsActionLabel(actionLabel = '') { const text = String(actionLabel || '').trim(); if (!text) { - return '接码平台请求'; + return 'SMS platform request'; } const normalized = text.toLowerCase(); const heroMatch = text.match(/^HeroSMS\s+(.+)$/i); @@ -1047,40 +1047,40 @@ return `HeroSMS ${formatHeroSmsActionName(heroMatch[1])}`; } if (normalized === '5sim guest prices') { - return '5sim 查询游客价格'; + return '5sim query guest price'; } if (normalized === '5sim user prices') { - return '5sim 查询账号价格'; + return '5sim query account price'; } if (normalized === '5sim buy activation') { - return '5sim 购买手机号'; + return '5sim purchase phone number'; } if (normalized === '5sim check activation') { - return '5sim 查询短信状态'; + return '5sim query SMS status'; } if (normalized === '5sim reuse activation') { - return '5sim 复用手机号'; + return '5sim reuse phone number'; } if (normalized === 'nexsms getcountrybyservice') { - return 'NexSMS 查询服务国家'; + return 'NexSMS query service countries'; } if (normalized === 'nexsms price lookup') { - return 'NexSMS 查询价格'; + return 'NexSMS query price'; } if (normalized === 'nexsms purchase') { - return 'NexSMS 购买手机号'; + return 'NexSMS purchase phone number'; } if (normalized === 'nexsms close activation') { - return 'NexSMS 关闭订单'; + return 'NexSMS close order'; } if (normalized === 'nexsms get sms messages') { - return 'NexSMS 查询短信'; + return 'NexSMS query SMS'; } return text; } function createPhoneSmsActionFailureError(actionLabel, reason = '', payload = null, status = 0) { - const message = `${formatPhoneSmsActionLabel(actionLabel)}失败:${formatPhoneSmsApiFailureReason(reason || status)}`; + const message = `${formatPhoneSmsActionLabel(actionLabel)} failed: ${formatPhoneSmsApiFailureReason(reason || status)}`; const error = new Error(message); if (payload !== null && payload !== undefined) { error.payload = payload; @@ -1106,7 +1106,7 @@ function createHeroSmsActionFailureError(action, reason = '') { const normalizedReason = stripRepeatedHeroSmsFailurePrefix(action, describeHeroSmsPayload(reason)); - const error = new Error(`HeroSMS ${formatHeroSmsActionName(action)}失败:${formatPhoneSmsApiFailureReason(normalizedReason)}`); + const error = new Error(`HeroSMS ${formatHeroSmsActionName(action)} failed: ${formatPhoneSmsApiFailureReason(normalizedReason)}`); error.localizedPhoneSmsFailure = true; return error; } @@ -1115,34 +1115,34 @@ const providerLabel = getPhoneSmsProviderLabel(providerId); let text = String(message || '').trim(); if (!text) { - return '未知错误'; + return 'Unknown error'; } text = text.replace(/^Step\s+\d+\s*[::]\s*/i, '').trim(); const heroFailureMatch = text.match(/^HeroSMS\s+([A-Za-z0-9]+)\s+failed\s*:\s*(.+)$/i); if (heroFailureMatch) { - return `${formatHeroSmsActionName(heroFailureMatch[1])}失败:${formatPhoneSmsApiFailureReason(stripRepeatedHeroSmsFailurePrefix(heroFailureMatch[1], heroFailureMatch[2]))}`; + return `${formatHeroSmsActionName(heroFailureMatch[1])} failed: ${formatPhoneSmsApiFailureReason(stripRepeatedHeroSmsFailurePrefix(heroFailureMatch[1], heroFailureMatch[2]))}`; } - if (normalizePhoneSmsProvider(providerId) === PHONE_SMS_PROVIDER_HERO && /^HeroSMS\s+.+失败:/.test(text)) { + if (normalizePhoneSmsProvider(providerId) === PHONE_SMS_PROVIDER_HERO && /^HeroSMS\s+.+(?:failed|失败)[::]/.test(text)) { return text.replace(/^HeroSMS\s+/, '').trim(); } if (/countries\s+are\s+empty|未选择国家/i.test(text)) { - return '未选择国家,请先在接码设置中至少选择 1 个国家'; + return 'No country selected. Please select at least 1 country in SMS settings.'; } if (/failed\s+to\s+acquire\s+(?:a\s+)?phone(?:\s+number|\s+activation)?/i.test(text)) { - return '获取手机号失败'; + return 'Failed to acquire phone number'; } if (/no\s+numbers\s+available\s+across|no\s+free\s+phones|numbers?\s+not\s+found|no\s+numbers\s+within|暂无可用号码|均无可用号码|无可用号码|\bNO_NUMBERS\b/i.test(text)) { return formatPhoneSmsApiFailureReason(text); } if (/buy activation failed|purchase failed|price lookup failed|check activation failed/i.test(text)) { return text - .replace(/^5sim\s+buy activation failed\s*:\s*/i, '购买手机号失败:') - .replace(/^5sim\s+check activation failed\s*:\s*/i, '查询短信状态失败:') - .replace(/^NexSMS\s+purchase failed\s*:\s*/i, '购买手机号失败:') - .replace(/^NexSMS\s+price lookup failed\s*:\s*/i, '查询价格失败:'); + .replace(/^5sim\s+buy activation failed\s*:\s*/i, 'Purchase phone number failed: ') + .replace(/^5sim\s+check activation failed\s*:\s*/i, 'Query SMS status failed: ') + .replace(/^NexSMS\s+purchase failed\s*:\s*/i, 'Purchase phone number failed: ') + .replace(/^NexSMS\s+price lookup failed\s*:\s*/i, 'Query price failed: '); } - if (providerLabel && text.startsWith(`${providerLabel}:`)) { - return text.slice(providerLabel.length + 1).trim() || text; + if (providerLabel && text.startsWith(`${providerLabel}: `)) { + return text.slice(providerLabel.length + 2).trim() || text; } return text; } @@ -1178,7 +1178,7 @@ return normalized || fallback; } - function normalizeFiveSimCountryLabel(value = '', fallback = '英国 (England)') { + function normalizeFiveSimCountryLabel(value = '', fallback = 'England') { const rootScope = typeof self !== 'undefined' ? self : globalThis; if (rootScope.PhoneSmsFiveSimProvider?.normalizeFiveSimCountryLabel) { return rootScope.PhoneSmsFiveSimProvider.normalizeFiveSimCountryLabel(value, fallback); @@ -1628,8 +1628,8 @@ } function buildPhoneCodeTimeoutError(lastResponse = '') { - const suffix = lastResponse ? ` HeroSMS 最后状态:${lastResponse}` : ''; - return new Error(`${PHONE_CODE_TIMEOUT_ERROR_PREFIX}等待手机验证码超时。${suffix}`); + const suffix = lastResponse ? ` HeroSMS last status: ${lastResponse}` : ''; + return new Error(`${PHONE_CODE_TIMEOUT_ERROR_PREFIX}Phone verification code wait timed out.${suffix}`); } function isSignupEmailVerificationPageState(pageState = {}) { @@ -1643,7 +1643,7 @@ function buildSignupPhoneStaleEmailVerificationError(pageState = {}) { const url = String(pageState?.url || pageState?.href || '').trim(); - const message = `步骤 4:OpenAI 在手机短信验证码提交前已切到邮箱验证${url ? `(URL: ${url})` : ''}。这通常表示当前手机号已关联现有账号或登录路径,请更换手机号后重新开始注册。`; + const message = `Step 4: OpenAI switched to email verification before the phone SMS code was submitted${url ? ` (URL: ${url})` : ''}. This usually means the current phone number is associated with an existing account or login path; please replace the phone number and start the signup over.`; const error = new Error(message); error.code = PHONE_STALE_SIGNUP_EMAIL_VERIFICATION_ERROR_CODE; error.stalePhoneSignupEmailVerification = true; @@ -1703,7 +1703,7 @@ if (message.startsWith(PHONE_RESEND_SERVER_ERROR_PREFIX)) { return new Error(message); } - return new Error(`${PHONE_RESEND_SERVER_ERROR_PREFIX}${message || 'OpenAI contact-verification 页面在重发短信后返回 HTTP ERROR 500。'}`); + return new Error(`${PHONE_RESEND_SERVER_ERROR_PREFIX}${message || 'OpenAI contact-verification page returned HTTP ERROR 500 after SMS resend.'}`); } function getPhoneResendServerErrorFromSnapshot(snapshot = {}) { @@ -1721,7 +1721,7 @@ .trim(); const titleText = String(snapshot?.title || '').replace(/\s+/g, ' ').trim(); if (!bodyText) { - return isPhoneResendServerError(titleText) ? (titleText || 'OpenAI contact-verification 页面在重发短信后返回 HTTP ERROR 500。') : ''; + return isPhoneResendServerError(titleText) ? (titleText || 'OpenAI contact-verification page returned HTTP ERROR 500 after SMS resend.') : ''; } const combined = [ bodyText, @@ -1734,7 +1734,7 @@ if (!isPhoneResendServerError(combined)) { return ''; } - return combined || 'OpenAI contact-verification 页面在重发短信后返回 HTTP ERROR 500。'; + return combined || 'OpenAI contact-verification page returned HTTP ERROR 500 after SMS resend.'; } async function readPhoneResendServerErrorFromAuthTab(tabId) { @@ -1760,11 +1760,11 @@ } function buildHighRiskResendThrottledError(message = '') { - return new Error(`${PHONE_RESEND_THROTTLED_ERROR_PREFIX}${message || 'OpenAI 重发短信被限流,且当前配置会按高概率封禁手机号处理。'}`); + return new Error(`${PHONE_RESEND_THROTTLED_ERROR_PREFIX}${message || 'OpenAI SMS resend was throttled, and current config treats this as high-probability number ban.'}`); } function buildPhoneMaxUsageExceededError(message = '') { - return new Error(`PHONE_MAX_USAGE_EXCEEDED::${message || 'OpenAI 返回 phone_max_usage_exceeded,当前手机号已达到使用上限。'}`); + return new Error(`PHONE_MAX_USAGE_EXCEEDED::${message || 'OpenAI returned phone_max_usage_exceeded. Current phone has reached usage limit.'}`); } function isPhoneMaxUsageExceededFlowError(error) { @@ -1800,8 +1800,8 @@ if (!message) { return false; } - return message === '流程已被用户停止。' - || /已被用户停止/.test(message) + return message === 'Flow stopped by user.' + || /Stopped by user/.test(message) || /flow\s+was\s+stopped|stopped\s+by\s+user/i.test(message); } @@ -1811,9 +1811,9 @@ } function buildPhoneRestartStep7Error(phoneNumber = '') { - const suffix = phoneNumber ? ` 当前号码:${phoneNumber}。` : ''; + const suffix = phoneNumber ? ` Current number: ${phoneNumber}.` : ''; return new Error( - `${PHONE_RESTART_STEP7_ERROR_PREFIX}手机验证重发后仍未收到短信,请从步骤 7 重新获取新号码。${suffix}` + `${PHONE_RESTART_STEP7_ERROR_PREFIX}SMS not received after phone verification resend. Please get a new number from Step 7.${suffix}` ); } @@ -1821,7 +1821,7 @@ const safeMax = Math.max(0, Math.floor(Number(maxNumberReplacementAttempts) || 0)); const safeReason = String(reason || 'unknown').trim() || 'unknown'; return new Error( - `步骤 9:更换 ${safeMax} 次号码后手机号验证仍未成功。最后原因:${formatStep9Reason(safeReason)}。` + `Step 9: Phone verification still failed after replacing the number ${safeMax} times. Last reason: ${formatStep9Reason(safeReason)}.` ); } @@ -1830,7 +1830,7 @@ if (!message.startsWith(PHONE_CODE_TIMEOUT_ERROR_PREFIX)) { return error; } - return new Error(message.slice(PHONE_CODE_TIMEOUT_ERROR_PREFIX.length).trim() || '等待手机验证码超时。'); + return new Error(message.slice(PHONE_CODE_TIMEOUT_ERROR_PREFIX.length).trim() || 'Phone verification code wait timed out.'); } function sanitizePhoneRestartStep7Error(error) { @@ -1840,7 +1840,7 @@ } return new Error( message.slice(PHONE_RESTART_STEP7_ERROR_PREFIX.length).trim() - || '手机验证重发后仍未收到短信,请从步骤 7 重新获取新号码。' + || 'SMS not received after phone verification resend. Please get a new number from Step 7.' ); } @@ -1872,7 +1872,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error(`${formatPhoneSmsActionLabel(actionLabel)}超时。`); + throw new Error(`${formatPhoneSmsActionLabel(actionLabel)} timed out.`); } throw error; } finally { @@ -1949,7 +1949,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error(`${formatPhoneSmsActionLabel(actionLabel)}超时。`); + throw new Error(`${formatPhoneSmsActionLabel(actionLabel)} timed out.`); } throw error; } finally { @@ -2044,7 +2044,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error(`${formatPhoneSmsActionLabel(actionLabel)}超时。`); + throw new Error(`${formatPhoneSmsActionLabel(actionLabel)} timed out.`); } throw error; } finally { @@ -2059,7 +2059,7 @@ if (provider === PHONE_SMS_PROVIDER_5SIM) { const apiKey = normalizeApiKey(state.fiveSimApiKey || state.heroSmsApiKey); if (!apiKey) { - throw new Error('5sim API Key 缺失,请先在侧边栏保存接码 API Key。'); + throw new Error('5sim API Key missing. Please save SMS API Key in the side panel first.'); } const configuredMaxPrice = normalizeHeroSmsPriceLimit(state.fiveSimMaxPrice); const operator = normalizeFiveSimCountryCode(state.fiveSimOperator, DEFAULT_FIVE_SIM_OPERATOR); @@ -2081,7 +2081,7 @@ if (provider === PHONE_SMS_PROVIDER_NEXSMS) { const apiKey = normalizeApiKey(state.nexSmsApiKey || state.heroSmsApiKey); if (!apiKey) { - throw new Error('NexSMS API Key 缺失,请先在侧边栏保存接码 API Key。'); + throw new Error('NexSMS API Key missing. Please save SMS API Key in the side panel first.'); } return { provider, @@ -2094,7 +2094,7 @@ const apiKey = normalizeApiKey(state.heroSmsApiKey); if (!apiKey) { - throw new Error('HeroSMS API Key 缺失,请先在侧边栏保存接码 API Key。'); + throw new Error('HeroSMS API Key missing. Please save SMS API Key in the side panel first.'); } return { provider, @@ -2107,7 +2107,7 @@ function resolveHeroSmsPhoneConfig(state = {}) { const apiKey = normalizeApiKey(state.heroSmsApiKey); if (!apiKey) { - throw new Error('HeroSMS API Key 缺失,请先在侧边栏保存接码 API Key。'); + throw new Error('HeroSMS API Key missing. Please save SMS API Key in the side panel first.'); } return { provider: PHONE_SMS_PROVIDER_HERO, @@ -2370,35 +2370,35 @@ const hasMinPrice = Number.isFinite(minPrice) && minPrice > 0; const hasMaxPrice = Number.isFinite(maxPrice) && maxPrice > 0; if (context?.priceRangeInvalid) { - suggestions.push('先修正价格区间(最低购买价不能高于价格上限)'); + suggestions.push('Fix the price range first (min purchase price cannot exceed price cap)'); } else if (hasMinPrice && hasMaxPrice) { - suggestions.push(`先适当放宽价格区间(当前 ${context.priceRangeText || `${minPrice}~${maxPrice}`})`); + suggestions.push(`Widen the price range a bit (currently ${context.priceRangeText || `${minPrice}~${maxPrice}`})`); } else if (hasMinPrice) { - suggestions.push(`可适当降低最低购买价(当前 ${context.priceRangeText || `${minPrice}~`})`); + suggestions.push(`Consider lowering the min purchase price (currently ${context.priceRangeText || `${minPrice}~`})`); } else if (!hasMaxPrice) { - suggestions.push('先设置价格上限(建议 >= 0.12)'); + suggestions.push('Set a price cap first (suggested >= 0.12)'); } else if (maxPrice < 0.12) { - suggestions.push('先提高价格上限(当前偏低)'); + suggestions.push('Raise the price cap (currently too low)'); } if ((context?.heroCountryCount || 0) <= 1) { - suggestions.push('HeroSMS 增加国家回退'); + suggestions.push('Add country fallbacks for HeroSMS'); } if ((context?.fiveSimCountryCount || 0) <= 0) { - suggestions.push('5sim 至少选择 1 个国家'); + suggestions.push('Select at least 1 country for 5sim'); } if ((context?.nexSmsCountryCount || 0) <= 0) { - suggestions.push('NexSMS 至少选择 1 个国家'); + suggestions.push('Select at least 1 country for NexSMS'); } if (String(context?.acquirePriority || '') === HERO_SMS_ACQUIRE_PRIORITY_COUNTRY) { - suggestions.push('可尝试切到“价格优先”'); + suggestions.push('Try switching to "price-first"'); } const unique = Array.from(new Set(suggestions)); if (!unique.length) { - return '优先提高价格上限,并调整服务商/国家优先级后重试'; + return 'Raise the price cap first, then adjust provider/country priority and retry'; } - return unique.slice(0, 3).join(';'); + return unique.slice(0, 3).join('; '); } async function resetPhoneNoSupplyFailureStreak(state = {}) { @@ -2438,13 +2438,13 @@ latestState && typeof latestState === 'object' ? latestState : state, providerOrder ); - const minPriceText = context.minPrice === null ? '未设置' : String(context.minPrice); - const maxPriceText = context.maxPrice === null ? '未设置' : String(context.maxPrice); + const minPriceText = context.minPrice === null ? 'not set' : String(context.minPrice); + const maxPriceText = context.maxPrice === null ? 'not set' : String(context.maxPrice); const priceRangeText = context.priceRangeText || formatPhonePriceRangeText(context.minPrice, context.maxPrice); const providerOrderText = context.order.join(' > '); const suggestion = formatNoSupplySuggestion(context); await addLog( - `步骤 9 诊断:无号连续失败 ${nextStreak} 次;价格区间=${priceRangeText};最低价=${minPriceText};最高价=${maxPriceText};平台顺序=${providerOrderText};国家数 HeroSMS=${context.heroCountryCount}, 5sim=${context.fiveSimCountryCount}, NexSMS=${context.nexSmsCountryCount}。建议:${suggestion}。`, + `Step 9 diagnostics: No-supply failed ${nextStreak} times in a row; price range=${priceRangeText}; min=${minPriceText}; max=${maxPriceText}; provider order=${providerOrderText}; country counts HeroSMS=${context.heroCountryCount}, 5sim=${context.fiveSimCountryCount}, NexSMS=${context.nexSmsCountryCount}. Suggestion: ${suggestion}.`, nextStreak >= 2 ? 'warn' : 'info' ); return true; @@ -2569,12 +2569,12 @@ ) { if (userLimit !== null && updatedMaxPrice > userLimit) { throw new Error( - `HeroSMS ${formatHeroSmsActionName(action)}失败:价格上限过低,平台要求至少 ${updatedMaxPrice},已超过当前配置的价格上限 ${userLimit}。` + `HeroSMS ${formatHeroSmsActionName(action)} failed: Price cap is too low; platform requires at least ${updatedMaxPrice}, which exceeds the currently configured price cap ${userLimit}.` ); } if (userMinLimit !== null && updatedMaxPrice < userMinLimit) { throw new Error( - `HeroSMS ${formatHeroSmsActionName(action)}失败:平台要求价格 ${updatedMaxPrice} 低于当前配置的最低购买价 ${userMinLimit}。` + `HeroSMS ${formatHeroSmsActionName(action)} failed: Platform required price ${updatedMaxPrice} is below the currently configured min purchase price ${userMinLimit}.` ); } nextMaxPrice = updatedMaxPrice; @@ -2691,9 +2691,9 @@ function buildFiveSimRateLimitError(details = []) { const suffix = Array.isArray(details) && details.length - ? `:${details.join(' | ')}。` - : '。'; - return new Error(`${FIVE_SIM_RATE_LIMIT_ERROR_PREFIX}5sim 购买接口触发限流,请稍后再试${suffix}`); + ? `: ${details.join(' | ')}.` + : '.'; + return new Error(`${FIVE_SIM_RATE_LIMIT_ERROR_PREFIX}5sim purchase API was rate-limited; please retry later${suffix}`); } function isFiveSimTerminalError(payloadOrMessage, status = 0) { @@ -2779,7 +2779,7 @@ ? config.countryCandidates : []; if (!allCountryCandidates.length) { - throw new Error(`步骤 ${getActivePhoneVerificationVisibleStep()}:5sim 未选择国家,请先在接码设置中至少选择 1 个国家。`); + throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: 5sim has no country selected. Please select at least 1 country in SMS settings.`); } const blockedCountryIds = new Set( (Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : []) @@ -2793,7 +2793,7 @@ countryCandidates = allCountryCandidates; if (blockedCountryIds.size) { await addLog( - '步骤 9:已选国家均达到临时收码失败跳过阈值,本轮解除跳过并重新尝试。', + 'Step 9: All selected countries reached temporary SMS failure skip threshold. Lifting skip this round and retrying.', 'warn' ); } @@ -2802,7 +2802,7 @@ const priceRange = resolvePhonePriceRange(state, PHONE_SMS_PROVIDER_5SIM); if (priceRange.invalidRange) { throw new Error( - `5sim 价格区间无效:最低购买价 ${priceRange.minPriceLimit} 高于价格上限 ${priceRange.maxPriceLimit}。` + `5sim price range is invalid: min purchase price ${priceRange.minPriceLimit} is greater than price cap ${priceRange.maxPriceLimit}.` ); } const maxPriceLimit = priceRange.maxPriceLimit; @@ -2825,7 +2825,7 @@ for (let round = 1; round <= maxAcquireRounds; round += 1) { if (maxAcquireRounds > 1) { await addLog( - `步骤 9:5sim 正在获取手机号(第 ${round}/${maxAcquireRounds} 轮)...`, + `Step 9: 5sim is acquiring phone number (round ${round}/${maxAcquireRounds})...`, 'info' ); } @@ -2871,10 +2871,10 @@ const countryLabel = String(entry.countryConfig.label || countryCode).trim() || countryCode; return Number.isFinite(entry.lowestPrice) ? `${countryLabel}:${entry.lowestPrice}` - : `${countryLabel}:无`; + : `${countryLabel}:none`; }) .join(' | '); - await addLog(`步骤 9:5sim 价格优先排序:${rankedSummary}`, 'info'); + await addLog(`Step 9: 5sim price priority ranking: ${rankedSummary}`, 'info'); } for (const countryConfig of orderedCountryCandidates) { @@ -2988,7 +2988,7 @@ && rawPriceCandidates.length ) { noNumbersByCountry.push( - `${countryLabel}: 价格区间 ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)} 内暂无可用号码;可见档位=${rawPriceCandidates.join(', ')}` + `${countryLabel}: No numbers available in price range ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)}; visible tiers=${rawPriceCandidates.join(', ')}` ); } else if ( maxPriceLimit !== null @@ -2996,18 +2996,18 @@ && Number(lowestCatalog) > Number(maxPriceLimit) ) { noNumbersByCountry.push( - `${countryLabel}: 价格上限 ${maxPriceLimit} 内暂无可用号码;平台最低价=${lowestCatalog}` + `${countryLabel}: No numbers available within price cap ${maxPriceLimit}; platform lowest price=${lowestCatalog}` ); } else if (countryPriceFloor !== null && rangeFilteredPriceCandidates.length) { noNumbersByCountry.push( - `${countryLabel}: 当前回退尝试没有高于 ${countryPriceFloor} 的价格档位` + `${countryLabel}: Current fallback attempt has no price tier above ${countryPriceFloor}` ); } else if (rawPriceCandidates.length) { const tierText = rawPriceCandidates.join(', '); - noNumbersByCountry.push(`${countryLabel}: 可见价格档位均不可用(${tierText})`); + noNumbersByCountry.push(`${countryLabel}: All visible price tiers unavailable (${tierText})`); retryableNoNumberCountries.push(countryLabel); } else { - noNumbersByCountry.push(`${countryLabel}: 暂无可用号码`); + noNumbersByCountry.push(`${countryLabel}: No numbers available`); retryableNoNumberCountries.push(countryLabel); } continue; @@ -3045,7 +3045,7 @@ continue; } if (isFiveSimNoNumbersError(payload)) { - countryNoNumbersText = payloadText || countryNoNumbersText || '暂无可用号码'; + countryNoNumbersText = payloadText || countryNoNumbersText || 'No numbers available'; continue; } if (isFiveSimTerminalError(payload)) { @@ -3061,7 +3061,7 @@ throw createPhoneSmsActionFailureError('5sim buy activation', describeFiveSimPayload(error?.payload || error?.message) || 'unknown terminal error', error?.payload, error?.status); } if (isFiveSimNoNumbersError(error?.payload || error?.message)) { - countryNoNumbersText = describeFiveSimPayload(error?.payload || error?.message) || countryNoNumbersText || '暂无可用号码'; + countryNoNumbersText = describeFiveSimPayload(error?.payload || error?.message) || countryNoNumbersText || 'No numbers available'; continue; } lastError = error; @@ -3081,16 +3081,16 @@ && rawPriceCandidates.length ) { noNumbersByCountry.push( - `${countryLabel}: 价格区间 ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)} 内暂无可用号码;可见档位=${rawPriceCandidates.join(', ')}` + `${countryLabel}: No numbers available in price range ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)}; visible tiers=${rawPriceCandidates.join(', ')}` ); } else if (maxPriceLimit !== null && lowestCatalogPrice !== null && Number(lowestCatalogPrice) > Number(maxPriceLimit)) { noNumbersByCountry.push( - `${countryLabel}: 价格上限 ${maxPriceLimit} 内暂无可用号码;平台最低价=${lowestCatalogPrice}` + `${countryLabel}: No numbers available within price cap ${maxPriceLimit}; platform lowest price=${lowestCatalogPrice}` ); } else if (isFiveSimRateLimitError(countryNoNumbersText)) { rateLimitByCountry.push(`${countryLabel}: ${countryNoNumbersText || 'rate limit'}`); } else { - noNumbersByCountry.push(`${countryLabel}: ${countryNoNumbersText || '暂无可用号码'}`); + noNumbersByCountry.push(`${countryLabel}: ${countryNoNumbersText || 'No numbers available'}`); retryableNoNumberCountries.push(countryLabel); } continue; @@ -3109,14 +3109,14 @@ : lowestRangePrice; if (minPriceLimit !== null && lowestRangePrice === null) { noNumbersByCountry.push( - `${countryLabel}: 价格区间 ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)} 内暂无可用号码` + `${countryLabel}: No numbers available in price range ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)}` ); } else if (maxPriceLimit !== null && lowestCatalogPrice !== null && lowestCatalogPrice > maxPriceLimit) { noNumbersByCountry.push( - `${countryLabel}: 价格上限 ${maxPriceLimit} 内暂无可用号码;平台最低价=${lowestCatalogPrice}` + `${countryLabel}: No numbers available within price cap ${maxPriceLimit}; platform lowest price=${lowestCatalogPrice}` ); } else { - noNumbersByCountry.push(`${countryLabel}: ${describeFiveSimPayload(error?.payload || error?.message) || '暂无可用号码'}`); + noNumbersByCountry.push(`${countryLabel}: ${describeFiveSimPayload(error?.payload || error?.message) || 'No numbers available'}`); retryableNoNumberCountries.push(countryLabel); } continue; @@ -3139,7 +3139,7 @@ && retryableNoNumberCountries.length > 0 ) { await addLog( - `步骤 9:5sim 暂无可用号码(第 ${round}/${maxAcquireRounds} 轮);${Math.ceil(retryDelayMs / 1000)} 秒后重试。国家:${retryableNoNumberCountries.join(', ')}。`, + `Step 9: 5sim has no available numbers (round ${round}/${maxAcquireRounds}). Retrying in ${Math.ceil(retryDelayMs / 1000)} seconds. Countries: ${retryableNoNumberCountries.join(', ')}.`, 'warn' ); await sleepWithStop(retryDelayMs); @@ -3151,7 +3151,7 @@ if (finalNoNumbersByCountry.length) { throw new Error( - `5sim 已尝试 ${countryCandidates.length} 个候选国家,均无可用号码:${finalNoNumbersByCountry.join(' | ')}。` + `5sim tried ${countryCandidates.length} candidate countries; none had available numbers: ${finalNoNumbersByCountry.join(' | ')}.` ); } if (finalRateLimitByCountry.length) { @@ -3160,7 +3160,7 @@ if (finalLastError) { throw finalLastError; } - throw new Error('5sim 获取手机号失败。'); + throw new Error('5sim failed to acquire phone number.'); } function isNexSmsNoNumbersError(payloadOrMessage) { @@ -3210,7 +3210,7 @@ async function resolveNexSmsCountryPricePlan(config, countryConfig, state = {}) { const countryId = normalizeNexSmsCountryId(countryConfig?.id, -1); if (countryId < 0) { - throw new Error(`NexSMS 国家 ID 无效:${countryConfig?.id}`); + throw new Error(`NexSMS country ID is invalid: ${countryConfig?.id}`); } const payload = await fetchNexSmsPayload( config, @@ -3308,7 +3308,7 @@ ? config.countryCandidates : resolveNexSmsCountryCandidates(state); if (!allCountryCandidates.length) { - throw new Error(`步骤 ${getActivePhoneVerificationVisibleStep()}:NexSMS 未选择国家,请先在接码设置中至少选择 1 个国家。`); + throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: NexSMS has no country selected. Please select at least 1 country in SMS settings.`); } const blockedCountryIds = new Set( (Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : []) @@ -3323,7 +3323,7 @@ countryCandidates = allCountryCandidates; if (blockedCountryIds.size) { await addLog( - '步骤 9:已选国家均达到临时收码失败跳过阈值,本轮解除跳过并重新尝试。', + 'Step 9: All selected countries reached temporary SMS failure skip threshold. Lifting skip this round and retrying.', 'warn' ); } @@ -3333,7 +3333,7 @@ const priceRange = resolvePhonePriceRange(state, PHONE_SMS_PROVIDER_NEXSMS); if (priceRange.invalidRange) { throw new Error( - `NexSMS 价格区间无效:最低购买价 ${priceRange.minPriceLimit} 高于价格上限 ${priceRange.maxPriceLimit}。` + `NexSMS price range is invalid: min purchase price ${priceRange.minPriceLimit} is greater than price cap ${priceRange.maxPriceLimit}.` ); } const minPriceLimit = priceRange.minPriceLimit; @@ -3353,7 +3353,7 @@ for (let round = 1; round <= maxAcquireRounds; round += 1) { if (maxAcquireRounds > 1) { await addLog( - `步骤 9:NexSMS 正在获取手机号(第 ${round}/${maxAcquireRounds} 轮)...`, + `Step 9: NexSMS is acquiring phone number (round ${round}/${maxAcquireRounds})...`, 'info' ); } @@ -3407,9 +3407,9 @@ const label = String(attempt.countryConfig.label || `Country #${id}`).trim() || `Country #${id}`; return Number.isFinite(attempt.orderingPrice) ? `${label}:${attempt.orderingPrice}` - : `${label}:无`; + : `${label}:none`; }).join(' | '); - await addLog(`步骤 9:NexSMS 价格优先排序:${rankingSummary}`, 'info'); + await addLog(`Step 9: NexSMS price priority ranking: ${rankingSummary}`, 'info'); } const noNumbersByCountry = []; @@ -3440,10 +3440,10 @@ && pricePlan.minCatalogPrice > pricePlan.userLimit ) { noNumbersByCountry.push( - `${countryLabel}: 价格上限 ${pricePlan.userLimit} 内暂无可用号码;平台最低价=${pricePlan.minCatalogPrice}` + `${countryLabel}: No numbers available within price cap ${pricePlan.userLimit}; platform lowest price=${pricePlan.minCatalogPrice}` ); } else { - const reason = describeNexSmsPayload(pricePlan.rawPayload) || '无可用价格档位'; + const reason = describeNexSmsPayload(pricePlan.rawPayload) || 'No available price tiers'; noNumbersByCountry.push(`${countryLabel}: ${reason}`); retryableNoNumberCountries.push(countryLabel); } @@ -3481,7 +3481,7 @@ if (!pricesToTry.length) { if (priceRange.hasMinPriceLimit && !rangeFilteredPrices.length) { noNumbersByCountry.push( - `${countryLabel}: 价格区间 ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)} 内暂无可用号码` + `${countryLabel}: No numbers available in price range ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)}` ); continue; } @@ -3491,10 +3491,10 @@ && pricePlan.prices.length > 0 ) { noNumbersByCountry.push( - `${countryLabel}: 当前回退尝试没有高于 ${countryPriceFloor} 的价格档位` + `${countryLabel}: Current fallback attempt has no price tier above ${countryPriceFloor}` ); } else { - noNumbersByCountry.push(`${countryLabel}: ${describeNexSmsPayload(pricePlan.rawPayload) || '暂无可用号码'}`); + noNumbersByCountry.push(`${countryLabel}: ${describeNexSmsPayload(pricePlan.rawPayload) || 'No numbers available'}`); retryableNoNumberCountries.push(countryLabel); } continue; @@ -3531,7 +3531,7 @@ serviceCode: config.serviceCode, }); if (!activation) { - lastError = new Error('NexSMS 购买成功,但未返回手机号。'); + lastError = new Error('NexSMS purchase succeeded but did not return a phone number.'); continue; } const numericPrice = Number(price); @@ -3548,7 +3548,7 @@ } } - const fallbackReason = describeNexSmsPayload(pricePlan.rawPayload) || '暂无可用号码'; + const fallbackReason = describeNexSmsPayload(pricePlan.rawPayload) || 'No numbers available'; noNumbersByCountry.push(`${countryLabel}: ${fallbackReason}`); retryableNoNumberCountries.push(countryLabel); } @@ -3562,7 +3562,7 @@ && retryableNoNumberCountries.length > 0 ) { await addLog( - `步骤 9:NexSMS 暂无可用号码(第 ${round}/${maxAcquireRounds} 轮);${Math.ceil(retryDelayMs / 1000)} 秒后重试。国家:${retryableNoNumberCountries.join(', ')}。`, + `Step 9: NexSMS has no available numbers (round ${round}/${maxAcquireRounds}); retrying in ${Math.ceil(retryDelayMs / 1000)} seconds. Countries: ${retryableNoNumberCountries.join(', ')}.`, 'warn' ); await sleepWithStop(retryDelayMs); @@ -3574,13 +3574,13 @@ if (finalNoNumbersByCountry.length) { throw new Error( - `NexSMS 已尝试 ${countryCandidates.length} 个候选国家,均无可用号码:${finalNoNumbersByCountry.join(' | ')}。` + `NexSMS tried ${countryCandidates.length} candidate countries; none had available numbers: ${finalNoNumbersByCountry.join(' | ')}.` ); } if (finalLastError) { throw finalLastError; } - throw new Error('NexSMS 获取手机号失败。'); + throw new Error('NexSMS failed to acquire phone number.'); } async function requestPhoneActivation(state = {}, options = {}) { @@ -3601,7 +3601,7 @@ ? config.countryCandidates : resolveCountryCandidates(state); if (!allCountryCandidates.length) { - throw new Error(`步骤 ${getActivePhoneVerificationVisibleStep()}:HeroSMS 未选择国家,请先在接码设置中至少选择 1 个国家。`); + throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: HeroSMS has no country selected. Please select at least 1 country in SMS settings.`); } const blockedCountryIds = new Set( (Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : []) @@ -3615,7 +3615,7 @@ countryCandidates = allCountryCandidates; if (blockedCountryIds.size) { await addLog( - '步骤 9:已选国家均达到临时收码失败跳过阈值,本轮解除跳过并重新尝试。', + 'Step 9: All selected countries reached temporary SMS failure skip threshold. Lifting skip this round and retrying.', 'warn' ); } @@ -3624,7 +3624,7 @@ const priceRange = resolvePhonePriceRange(state, PHONE_SMS_PROVIDER_HERO); if (priceRange.invalidRange) { throw new Error( - `HeroSMS 价格区间无效:最低购买价 ${priceRange.minPriceLimit} 高于价格上限 ${priceRange.maxPriceLimit}。` + `HeroSMS price range is invalid: min purchase price ${priceRange.minPriceLimit} is greater than price cap ${priceRange.maxPriceLimit}.` ); } const minPriceLimit = priceRange.minPriceLimit; @@ -3651,7 +3651,7 @@ for (let round = 1; round <= maxAcquireRounds; round += 1) { if (maxAcquireRounds > 1) { await addLog( - `步骤 9:HeroSMS 正在获取手机号(第 ${round}/${maxAcquireRounds} 轮)...`, + `Step 9: HeroSMS is acquiring phone number (round ${round}/${maxAcquireRounds})...`, 'info' ); } @@ -3759,26 +3759,26 @@ : (floorFilteredPrices.length ? floorFilteredPrices : candidatePrices); const rawTierText = Array.isArray(pricePlan?.prices) && pricePlan.prices.length ? pricePlan.prices - .map((value) => (value === null || value === undefined ? '自动' : String(value))) + .map((value) => (value === null || value === undefined ? 'auto' : String(value))) .join(', ') - : '无'; + : 'none'; await addLog( - `步骤 9:HeroSMS ${countryConfig.label} 价格方案:档位=[${rawTierText}],用户上限=${pricePlan?.userLimit ?? '未设置'},目录最低价=${pricePlan?.minCatalogPrice ?? '未知'}。`, + `Step 9: HeroSMS ${countryConfig.label} price plan: tiers=[${rawTierText}], user cap=${pricePlan?.userLimit ?? 'not set'}, catalog lowest=${pricePlan?.minCatalogPrice ?? 'unknown'}.`, 'info' ); if (pricesToTry.length > 1 || countryPriceFloor !== null) { const tierText = pricesToTry - .map((value) => (value === null || value === undefined ? '自动' : String(value))) + .map((value) => (value === null || value === undefined ? 'auto' : String(value))) .join(', '); await addLog( - `步骤 9:HeroSMS ${countryConfig.label} 本轮候选价格:${tierText}${countryPriceFloor !== null ? `(高于 ${countryPriceFloor})` : ''}。`, + `Step 9: HeroSMS ${countryConfig.label} candidate prices this round: ${tierText}${countryPriceFloor !== null ? ` (above ${countryPriceFloor})` : ''}.`, 'info' ); } if (!pricesToTry.length) { if (priceRange.hasMinPriceLimit && !rangeFilteredPrices.length) { noNumbersByCountry.push( - `${countryConfig.label}: 价格区间 ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)} 内暂无可用号码` + `${countryConfig.label}: No numbers available in price range ${formatPhonePriceRangeText(minPriceLimit, maxPriceLimit)}` ); continue; } @@ -3788,7 +3788,7 @@ && pricePlan.prices.length > 0 ) { noNumbersByCountry.push( - `${countryConfig.label}: 当前回退尝试没有高于 ${countryPriceFloor} 的价格档位` + `${countryConfig.label}: Current fallback attempt has no price tier above ${countryPriceFloor}` ); continue; } @@ -3798,7 +3798,7 @@ && pricePlan.minCatalogPrice > pricePlan.userLimit ) { noNumbersByCountry.push( - `${countryConfig.label}: 价格上限 ${pricePlan.userLimit} 内暂无可用号码;平台最低价=${pricePlan.minCatalogPrice}` + `${countryConfig.label}: No numbers available within price cap ${pricePlan.userLimit}; platform lowest price=${pricePlan.minCatalogPrice}` ); } else { noNumbersByCountry.push( @@ -3813,7 +3813,7 @@ try { const fixedPrice = !Boolean(pricePlan.syntheticUserLimitProbe); await addLog( - `步骤 9:HeroSMS ${countryConfig.label} 正在尝试${formatHeroSmsActionName(requestAction)},价格档位 ${maxPrice === null || maxPrice === undefined ? '自动' : maxPrice}。`, + `Step 9: HeroSMS ${countryConfig.label} is trying ${formatHeroSmsActionName(requestAction)} at price tier ${maxPrice === null || maxPrice === undefined ? 'auto' : maxPrice}.`, 'info' ); const payload = await requestPhoneActivationWithPrice( @@ -3868,7 +3868,7 @@ if (noNumbersObservedInCountry) { const tiersTriedText = pricesToTry - .map((value) => (value === null || value === undefined ? '自动' : String(value))) + .map((value) => (value === null || value === undefined ? 'auto' : String(value))) .join(', '); if ( pricePlan.userLimit !== null @@ -3876,11 +3876,11 @@ && pricePlan.minCatalogPrice > pricePlan.userLimit ) { noNumbersByCountry.push( - `${countryConfig.label}: 价格上限 ${pricePlan.userLimit} 内暂无可用号码;平台最低价=${pricePlan.minCatalogPrice}` + `${countryConfig.label}: No numbers available within price cap ${pricePlan.userLimit}; platform lowest price=${pricePlan.minCatalogPrice}` ); } else { noNumbersByCountry.push( - `${countryConfig.label}: ${lastFailureText || 'NO_NUMBERS'}${tiersTriedText ? `(已尝试档位:${tiersTriedText})` : ''}` + `${countryConfig.label}: ${lastFailureText || 'NO_NUMBERS'}${tiersTriedText ? ` (tiers tried: ${tiersTriedText})` : ''}` ); retryableNoNumberCountries.push(countryConfig.label); } @@ -3898,11 +3898,11 @@ && retryableNoNumberCountries.length > 0 ) { await addLog( - `步骤 9:HeroSMS 暂无可用号码(第 ${round}/${maxAcquireRounds} 轮);${Math.ceil(retryDelayMs / 1000)} 秒后重试。国家:${retryableNoNumberCountries.join(', ')}。`, + `Step 9: HeroSMS has no available numbers (round ${round}/${maxAcquireRounds}). Retrying in ${Math.ceil(retryDelayMs / 1000)} seconds. Countries: ${retryableNoNumberCountries.join(', ')}.`, 'warn' ); await addLog( - `步骤 9:HeroSMS 暂无可用号码(第 ${round}/${maxAcquireRounds} 轮),${Math.ceil(retryDelayMs / 1000)} 秒后重试。国家:${retryableNoNumberCountries.join(', ')}。`, + `Step 9: HeroSMS has no available numbers (round ${round}/${maxAcquireRounds}). Retrying in ${Math.ceil(retryDelayMs / 1000)} seconds. Countries: ${retryableNoNumberCountries.join(', ')}.`, 'warn' ); await sleepWithStop(retryDelayMs); @@ -3914,19 +3914,19 @@ if (finalNoNumbersByCountry.length) { throw new Error( - `HeroSMS 已尝试 ${countryCandidates.length} 个候选国家,均无可用号码:${finalNoNumbersByCountry.join(' | ')}。` + `HeroSMS tried ${countryCandidates.length} candidate countries; none had available numbers: ${finalNoNumbersByCountry.join(' | ')}.` ); } if (finalLastError) { throw finalLastError; } - throw new Error(`HeroSMS 获取手机号失败,最后状态:${finalLastFailureText || '未知'}。`); + throw new Error(`HeroSMS failed to acquire phone number; last status: ${finalLastFailureText || 'unknown'}.`); } async function reactivatePhoneActivation(state = {}, activation) { const normalizedActivation = normalizeActivation(activation); if (!normalizedActivation) { - throw new Error('缺少可复用的手机号接码订单。'); + throw new Error('No reusable SMS phone order available.'); } if (getActivationProviderId(normalizedActivation, state) === PHONE_SMS_PROVIDER_FIVE_SIM) { const provider = getFiveSimProviderForState(state); @@ -3949,7 +3949,7 @@ }; } if (config.provider === PHONE_SMS_PROVIDER_NEXSMS) { - throw new Error('NexSMS 当前流程不支持复用手机号订单。'); + throw new Error('NexSMS current flow does not support reusing phone orders.'); } const payload = await fetchHeroSmsPayload(config, { action: 'reactivate', @@ -3958,7 +3958,7 @@ const nextActivation = parseActivationPayload(payload, normalizedActivation); if (!nextActivation) { const text = describeHeroSmsPayload(payload); - throw new Error(`HeroSMS 复用手机号失败:${text || '空响应'}`); + throw new Error(`HeroSMS reuse phone number failed: ${text || 'empty response'}`); } return nextActivation; } @@ -3975,7 +3975,7 @@ ) { const identifier = normalizedActivation.phoneNumber || normalizedActivation.activationId || 'current activation'; await addLog( - `步骤 9:白嫖复用模式仅请求短信,跳过 ${identifier} 的 setStatus(${normalizedStatus})。`, + `Step 9: Free-reuse mode only requests SMS; skipping setStatus(${normalizedStatus}) for ${identifier}.`, 'info' ); return `free reuse setStatus(${normalizedStatus}) skipped`; @@ -4021,7 +4021,7 @@ const normalizedActivation = normalizeActivation(activation); const identifier = normalizedActivation?.phoneNumber || normalizedActivation?.activationId || 'current activation'; await addLog( - `步骤 9:白嫖复用模式仅请求短信,跳过 ${identifier} 的接码完成状态。`, + `Step 9: Free-reuse mode only requests SMS; skipping SMS completion status for ${identifier}.`, 'info' ); return; @@ -4042,7 +4042,7 @@ if (shouldSkipTerminalStatusForFreeReuse(state, activation)) { const identifier = normalizedActivation?.phoneNumber || normalizedActivation?.activationId || 'current activation'; await addLog( - `步骤 9:白嫖复用模式仅请求短信,跳过 ${identifier} 的接码取消状态。`, + `Step 9: Free-reuse mode only requests SMS; skipping SMS cancel status for ${identifier}.`, 'info' ); return; @@ -4062,7 +4062,7 @@ async function retireFreeReusableActivation(reason = '') { const suffix = reason ? ` ${reason}` : ''; - await addLog(`步骤 9:已清除白嫖复用手机号记录。${suffix}`, 'warn'); + await addLog(`Step 9: Cleared free-reuse phone number record.${suffix}`, 'warn'); await clearFreeReusableActivation(); } @@ -4095,7 +4095,7 @@ if (Object.keys(updates).length) { await setPhoneRuntimeState(updates); await addLog( - `步骤 9:已从复用记录中移除手机号 ${rejectedPhoneNumber}。${reason || '目标站拒绝该号码。'}`, + `Step 9: Removed phone number ${rejectedPhoneNumber} from reuse records.${reason || ' Target site rejected this number.'}`, 'warn' ); } @@ -4143,7 +4143,7 @@ const normalizedActivation = normalizeActivation(activation); const identifier = normalizedActivation?.phoneNumber || normalizedActivation?.activationId || 'current activation'; await addLog( - `步骤 9:白嫖复用模式仅请求短信,跳过 ${identifier} 的接码封禁状态。`, + `Step 9: Free-reuse mode only requests SMS; skipping SMS ban status for ${identifier}.`, 'info' ); return; @@ -4195,7 +4195,7 @@ return { ok: false, reason: 'missing_free_reusable_activation', - message: '免费复用手机号激活记录缺失。', + message: 'Free-reuse phone activation record missing.', }; } if (normalizedActivation.provider === PHONE_SMS_PROVIDER_5SIM) { @@ -4208,7 +4208,7 @@ return { ok: false, reason: 'five_sim_reuse_check_failed', - message: error.message || '5sim 复用手机号基线检查失败。', + message: error.message || '5sim phone reuse baseline check failed.', }; } return { @@ -4231,7 +4231,7 @@ return { ok: false, reason: 'missing_activation_id', - message: '已保存的免费复用手机号缺少 HeroSMS 激活 ID,无法自动重新激活。', + message: 'Saved free-reuse phone number has no HeroSMS activation ID; cannot reactivate automatically.', }; } @@ -4253,20 +4253,20 @@ { ...state, phoneSmsProvider: PHONE_SMS_PROVIDER_HERO }, normalizedActivation, 3, - 'HeroSMS 自动复用设置订单状态' + 'HeroSMS auto-reuse set order status' ); } catch (error) { return { ok: false, reason: 'set_status_failed', - message: error.message || 'HeroSMS 更新订单状态失败。', + message: error.message || 'HeroSMS update order status failed.', lastStatus, prepareRound, }; } await addLog( - `步骤 9:自动白嫖复用已刷新 ${normalizedActivation.phoneNumber},${Math.ceil(FREE_PHONE_REUSE_PREPARE_INTERVAL_MS / 1000)} 秒后检查等待状态(${prepareRound}/${FREE_PHONE_REUSE_PREPARE_MAX_ROUNDS})。`, + `Step 9: Auto free-reuse refreshed ${normalizedActivation.phoneNumber}; will check waiting status in ${Math.ceil(FREE_PHONE_REUSE_PREPARE_INTERVAL_MS / 1000)} seconds (${prepareRound}/${FREE_PHONE_REUSE_PREPARE_MAX_ROUNDS}).`, 'info' ); await sleepWithStop(FREE_PHONE_REUSE_PREPARE_INTERVAL_MS); @@ -4275,11 +4275,11 @@ const payload = await fetchHeroSmsPayload(config, { action: statusAction, id: normalizedActivation.activationId, - }, `HeroSMS 自动复用${statusAction}`); + }, `HeroSMS auto-reuse ${statusAction}`); const statusText = describeHeroSmsPayload(payload); lastStatus = statusText; await addLog( - `步骤 9:自动白嫖复用号码 ${normalizedActivation.phoneNumber} 状态:${statusText || '空响应'}(${prepareRound}/${FREE_PHONE_REUSE_PREPARE_MAX_ROUNDS})。`, + `Step 9: Auto free-reuse number ${normalizedActivation.phoneNumber} status: ${statusText || 'empty response'} (${prepareRound}/${FREE_PHONE_REUSE_PREPARE_MAX_ROUNDS}).`, 'info' ); @@ -4300,7 +4300,7 @@ } if (/^STATUS_OK:/i.test(statusText)) { await addLog( - `步骤 9:自动白嫖复用仍看到旧验证码,将再次刷新等待短信状态。`, + `Step 9: Auto free-reuse still sees the old verification code; will refresh again and wait for SMS status.`, 'warn' ); continue; @@ -4309,7 +4309,7 @@ return { ok: false, reason: 'activation_cancelled', - message: 'HeroSMS 订单在自动白嫖复用前已被取消。', + message: 'HeroSMS order was canceled before auto free-reuse.', lastStatus, prepareRound, }; @@ -4318,7 +4318,7 @@ return { ok: false, reason: 'get_status_failed', - message: error.message || 'HeroSMS 查询短信状态失败。', + message: error.message || 'HeroSMS query SMS status failed.', lastStatus, prepareRound, }; @@ -4328,7 +4328,7 @@ return { ok: false, reason: 'prepare_timeout', - message: `等待已保存手机号进入短信等待状态超时。最后状态:${lastStatus || '未知'}。`, + message: `Timed out waiting for the saved phone number to enter the SMS-waiting state. Last status: ${lastStatus || 'unknown'}.`, lastStatus, prepareRound, }; @@ -4337,7 +4337,7 @@ async function pollPhoneActivationCode(state = {}, activation, options = {}) { const normalizedActivation = normalizeActivation(activation); if (!normalizedActivation) { - throw new Error('缺少手机号接码订单。'); + throw new Error('Missing phone SMS order.'); } if (getActivationProviderId(normalizedActivation, state) === PHONE_SMS_PROVIDER_FIVE_SIM) { const provider = getFiveSimProviderForState(state); @@ -4435,7 +4435,7 @@ } if (/^(CANCELED|CANCELLED|BANNED|FINISHED|EXPIRED|TIMEOUT)$/i.test(statusText)) { - throw new Error(`5sim 订单在收到短信前已结束:${statusText}`); + throw new Error(`5sim order ended before SMS arrived: ${statusText}`); } throw createPhoneSmsActionFailureError('5sim check activation', text || statusText || 'empty response'); @@ -4560,7 +4560,7 @@ } if (/^STATUS_CANCEL$/i.test(text)) { - throw new Error('HeroSMS 订单在短信到达前已被取消。'); + throw new Error('HeroSMS order was cancelled before SMS arrived.'); } throw createHeroSmsActionFailureError(statusAction, text || 'empty response'); @@ -4575,13 +4575,13 @@ let timeoutId = null; const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { - reject(new Error(`步骤 ${visibleStep}:等待认证页状态检查超时。`)); + reject(new Error(`Step ${visibleStep}: Timed out waiting for auth page status check.`)); }, deadlineMs); }); const readPromise = (async () => { await ensureStep8SignupPageReady(tabId, { timeoutMs: deadlineMs, - logMessage: '步骤 9:等待认证页脚本恢复后继续手机号验证。', + logMessage: 'Step 9: Waiting for auth page script recovery before continuing phone verification.', visibleStep, logStepKey: 'phone-verification', }); @@ -4593,7 +4593,7 @@ timeoutMs: deadlineMs, responseTimeoutMs: deadlineMs, retryDelayMs: 600, - logMessage: '步骤 9:认证页正在切换,等待后重新检查手机号验证状态...', + logMessage: 'Step 9: Auth page is switching. Waiting to re-check phone verification status...', logStep: visibleStep, logStepKey: 'phone-verification', }); @@ -4674,7 +4674,7 @@ const countryConfig = resolveCountryConfigFromActivation(activation, state); const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9; const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' - ? await getOAuthFlowStepTimeoutMs(30000, { step: visibleStep, actionLabel: '提交添加手机号' }) + ? await getOAuthFlowStepTimeoutMs(30000, { step: visibleStep, actionLabel: 'Submit add phone' }) : 30000; const result = await sendToContentScriptResilient('openai-auth', { type: 'SUBMIT_PHONE_NUMBER', @@ -4688,7 +4688,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 600, - logMessage: '步骤 9:等待添加手机号页面就绪...', + logMessage: 'Step 9: Waiting for add phone page to be ready...', logStep: visibleStep, logStepKey: 'phone-verification', }); @@ -4721,7 +4721,7 @@ })() : null; const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' - ? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交手机验证码' }) + ? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: 'Submit phone verification code' }) : 45000; const result = await sendToContentScriptResilient('openai-auth', { type: 'SUBMIT_PHONE_VERIFICATION_CODE', @@ -4734,7 +4734,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 600, - logMessage: '步骤 9:等待手机验证码页面就绪后填写短信验证码...', + logMessage: 'Step 9: Waiting for phone verification code page to be ready before filling SMS code...', logStep: visibleStep, logStepKey: 'phone-verification', }); @@ -4764,7 +4764,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 600, - logMessage: '步骤 9:等待手机验证码重发按钮出现...', + logMessage: 'Step 9: Waiting for phone verification code resend button to appear...', logStep: visibleStep, logStepKey: 'phone-verification', }); @@ -4778,7 +4778,7 @@ async function submitSignupPhoneVerificationCode(tabId, code, options = {}) { const visibleStep = 4; const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' - ? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交注册手机验证码' }) + ? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: 'Submit signup phone verification code' }) : 45000; const result = await sendToContentScriptResilient('openai-auth', { type: 'SUBMIT_PHONE_VERIFICATION_CODE', @@ -4793,7 +4793,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 600, - logMessage: '步骤 4:等待注册手机验证码页面就绪后填写短信验证码...', + logMessage: 'Step 4: Waiting for signup phone verification code page to be ready before filling SMS code...', logStep: visibleStep, logStepKey: 'fetch-signup-code', }); @@ -4807,7 +4807,7 @@ async function resendSignupPhoneVerificationCode(tabId) { const visibleStep = 4; const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' - ? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: '重新发送注册手机验证码' }) + ? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: 'Resend signup phone verification code' }) : 65000; const result = await sendToContentScriptResilient('openai-auth', { type: 'RESEND_VERIFICATION_CODE', @@ -4818,7 +4818,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 600, - logMessage: '步骤 4:等待注册手机验证码重发按钮出现...', + logMessage: 'Step 4: Waiting for signup phone verification code resend button to appear...', logStep: visibleStep, logStepKey: 'fetch-signup-code', }); @@ -4842,7 +4842,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 600, - logMessage: '步骤 9:返回添加手机号页面以更换号码...', + logMessage: 'Step 9: Returning to add phone page to change number...', logStep: visibleStep, logStepKey: 'phone-verification', }); @@ -4911,7 +4911,7 @@ message: error.message, }; } - await addLog(`步骤 9:检查手机重发错误时遇到暂时性问题,已忽略。${error.message}`, 'warn'); + await addLog(`Step 9: Hit a transient issue while checking the phone resend error; ignored. ${error.message}`, 'warn'); return { hasError: false, reason: '', @@ -5007,7 +5007,7 @@ if (freeReusableActivation.successfulUses >= freeReusableActivation.maxUses) { await retireFreeReusableActivation( - `保存的手机号 ${freeReusableActivation.phoneNumber} 已达到 ${freeReusableActivation.successfulUses}/${freeReusableActivation.maxUses} 次。` + `Saved phone number ${freeReusableActivation.phoneNumber} reached ${freeReusableActivation.successfulUses}/${freeReusableActivation.maxUses} uses.` ); return null; } @@ -5018,20 +5018,20 @@ if (canPrepareAutomaticFreeReuse) { await addLog( - `步骤 9:准备自动白嫖复用已保存手机号 ${freeReusableActivation.phoneNumber}(${freeReusableActivation.successfulUses + 1}/${freeReusableActivation.maxUses})。`, + `Step 9: Preparing auto free-reuse of saved phone number ${freeReusableActivation.phoneNumber} (${freeReusableActivation.successfulUses + 1}/${freeReusableActivation.maxUses}).`, 'info' ); const prepared = await prepareFreeReusablePhoneActivation(state, freeReusableActivation); if (!prepared.ok) { const reason = prepared.message || prepared.reason || 'unknown error'; - const stopMessage = `自动白嫖复用准备失败:${freeReusableActivation.phoneNumber} 未确认进入等待短信状态,本次不购买新 HeroSMS 号码。原因:${reason}`; + const stopMessage = `Auto free-reuse preparation failed: ${freeReusableActivation.phoneNumber} did not reach SMS-waiting state. Not purchasing a new HeroSMS number this run. Reason: ${reason}`; await addLog( - `步骤 9:自动白嫖复用准备失败,停止本次接码且不购买新 HeroSMS 号码。${reason}`, + `Step 9: Auto free-reuse preparation failed; stopping this SMS attempt and not purchasing a new HeroSMS number. ${reason}`, 'error' ); if (prepared.reason === 'activation_cancelled') { await retireFreeReusableActivation( - `自动白嫖复用号码 ${freeReusableActivation.phoneNumber} 已被 HeroSMS 取消。` + `Auto free-reuse number ${freeReusableActivation.phoneNumber} was canceled by HeroSMS.` ); } if (typeof requestStop === 'function') { @@ -5045,8 +5045,8 @@ const fillResult = await submitPhoneNumber(tabId, freeReusableActivation.phoneNumber, freeReusableActivation); await clearCurrentActivation(); - const message = `开始手动复用手机 ${freeReusableActivation.phoneNumber},请到 SMS 上刷新。`; - await addLog(`步骤 9:${message}`, 'warn'); + const message = `Starting manual reuse of phone ${freeReusableActivation.phoneNumber}. Please refresh on SMS.`; + await addLog(`Step 9: ${message}`, 'warn'); if (typeof requestStop === 'function') { await requestStop({ logMessage: message }); } @@ -5108,7 +5108,7 @@ (provider === PHONE_SMS_PROVIDER_5SIM || provider === PHONE_SMS_PROVIDER_NEXSMS) && !countryCandidates.length ) { - throw new Error(`步骤 ${getActivePhoneVerificationVisibleStep()}:${provider === PHONE_SMS_PROVIDER_5SIM ? '5sim' : 'NexSMS'} 未选择国家,请先在接码设置中至少选择 1 个国家。`); + throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: ${provider === PHONE_SMS_PROVIDER_5SIM ? '5sim' : 'NexSMS'} has no country selected. Please select at least 1 country in SMS settings.`); } const normalizeCountryKey = (value) => ( provider === PHONE_SMS_PROVIDER_5SIM @@ -5172,7 +5172,7 @@ try { const reactivated = await reactivatePhoneActivation(state, preferredActivation); await addLog( - `步骤 9:优先复用手动选择号码 ${reactivated.phoneNumber}${reactivated.countryId ? `(${resolveCountryLabelById(reactivated.countryId)})` : ''}。`, + `Step 9: Reusing manually selected number ${reactivated.phoneNumber}${reactivated.countryId ? ` (${resolveCountryLabelById(reactivated.countryId)})` : ''}.`, 'info' ); await resetPhoneNoSupplyFailureStreak(state); @@ -5181,7 +5181,7 @@ failedPreferredActivation = preferredActivation; await removeReusableActivationFromPool(preferredActivation, { state }).catch(() => {}); await addLog( - `步骤 9:手动选择号码 ${preferredActivation.phoneNumber} 不可用,将改为获取新号码。${error.message}`, + `Step 9: Manually selected number ${preferredActivation.phoneNumber} is unavailable; will get a new number instead. ${error.message}`, 'warn' ); } @@ -5226,13 +5226,13 @@ try { const reactivated = await reactivatePhoneActivation(state, candidateActivation); await addLog( - `步骤 9:复用 ${resolveCountryLabelById(reactivated.countryId)} 号码 ${reactivated.phoneNumber}(第 ${reactivated.successfulUses + 1}/${reactivated.maxUses} 次)。`, + `Step 9: Reusing ${resolveCountryLabelById(reactivated.countryId)} number ${reactivated.phoneNumber} (use ${reactivated.successfulUses + 1}/${reactivated.maxUses}).`, 'info' ); await resetPhoneNoSupplyFailureStreak(state); return reactivated; } catch (error) { - await addLog(`步骤 9:复用号码 ${candidateActivation.phoneNumber} 失败,将改为获取新号码。${error.message}`, 'warn'); + await addLog(`Step 9: Failed to reuse number ${candidateActivation.phoneNumber}; will get a new number instead. ${error.message}`, 'warn'); await removeReusableActivationFromPool(candidateActivation, { state }).catch(() => {}); if (isSameActivation(reusableActivation, candidateActivation)) { await clearReusableActivation(); @@ -5269,12 +5269,12 @@ : String(activation?.countryLabel || activation?.countryId || '').trim(); if (providerCandidate !== provider) { await addLog( - `步骤 9:主接码平台 ${getPhoneSmsProviderLabel(provider)} 暂无可用号码,已回退到 ${providerLabel}${providerCountryLabel ? ` / ${providerCountryLabel}` : ''}。`, + `Step 9: Primary SMS provider ${getPhoneSmsProviderLabel(provider)} has no available numbers; falling back to ${providerLabel}${providerCountryLabel ? ` / ${providerCountryLabel}` : ''}.`, 'warn' ); } await addLog( - `步骤 9:已从 ${providerLabel}${providerCountryLabel ? ` / ${providerCountryLabel}` : ''} 获取号码 ${activation.phoneNumber}。`, + `Step 9: Got number ${activation.phoneNumber} from ${providerLabel}${providerCountryLabel ? ` / ${providerCountryLabel}` : ''}.`, 'info' ); await resetPhoneNoSupplyFailureStreak(state); @@ -5289,37 +5289,37 @@ providerCandidate !== provider && /(?:step|步骤)\s*9\s*[::]\s*(?:5sim|nexsms).*(?:countries\s+are\s+empty|未选择国家)/i.test(providerErrorMessage) ) { - skippedFallbackProviders.push(`${providerLabel}:未选择国家`); + skippedFallbackProviders.push(`${providerLabel}: no country selected`); await addLog( - `步骤 9:跳过回退接码平台 ${providerLabel},因为接码设置中未选择国家。`, + `Step 9: Skipping fallback SMS provider ${providerLabel} because no country is selected in SMS settings.`, 'warn' ); continue; } lastProviderError = error; - providerErrors.push(`${providerLabel}:${formatProviderAcquireFailure(providerCandidate, providerErrorMessage)}`); + providerErrors.push(`${providerLabel}: ${formatProviderAcquireFailure(providerCandidate, providerErrorMessage)}`); } } if (providerErrors.length) { await logNoSupplyDiagnostics(state, providerOrder, providerErrors); const skippedSuffix = skippedFallbackProviders.length - ? `;已跳过回退平台:${skippedFallbackProviders.join(';')}` + ? `; skipped fallback providers: ${skippedFallbackProviders.join('; ')}` : ''; - throw new Error(`步骤 ${getActivePhoneVerificationVisibleStep()}:所有接码平台候选均未获取到手机号。${providerErrors.join(';')}${skippedSuffix}`); + throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: All SMS provider candidates failed to acquire a phone number. ${providerErrors.join('; ')}${skippedSuffix}`); } - throw lastProviderError || new Error(`步骤 ${getActivePhoneVerificationVisibleStep()}:获取手机号订单失败。`); + throw lastProviderError || new Error(`Step ${getActivePhoneVerificationVisibleStep()}: Failed to acquire phone number order.`); } async function prepareSignupPhoneActivation(state = {}, options = {}) { return withPhoneVerificationLogContext({ step: 2, stepKey: 'submit-signup-email' }, async () => { const activation = await acquirePhoneActivation(state, { ...options, - logLabel: options?.logLabel || '步骤 2', + logLabel: options?.logLabel || 'Step 2', }); const normalizedActivation = normalizeActivation(activation); if (!normalizedActivation) { - throw new Error('步骤 2:接码平台返回的手机号订单无效。'); + throw new Error('Step 2: SMS provider returned an invalid phone activation.'); } const countryConfig = resolveCountryConfigFromActivation(normalizedActivation, state); const signupActivation = normalizeActivation({ @@ -5476,7 +5476,7 @@ recordedAt: Date.now(), }); await addLog( - `步骤 9:收到有效短信后已保存白嫖复用手机号 ${normalizedActivation.phoneNumber}。`, + `Step 9: Saved free-reuse phone number ${normalizedActivation.phoneNumber} after receiving a valid SMS.`, 'info' ); } @@ -5506,7 +5506,7 @@ if (successfulUses >= maxUses) { await clearFreeReusableActivation(); await addLog( - `步骤 9:自动白嫖复用手机号 ${savedActivation.phoneNumber} 已达到 ${successfulUses}/${maxUses} 次,已清除本地记录。`, + `Step 9: Auto free-reuse phone number ${savedActivation.phoneNumber} reached ${successfulUses}/${maxUses} uses; cleared local record.`, 'info' ); return; @@ -5519,7 +5519,7 @@ maxUses, }); await addLog( - `步骤 9:自动白嫖复用手机号 ${savedActivation.phoneNumber} 成功(${successfulUses}/${maxUses}),保留供后续注册使用。`, + `Step 9: Auto free-reuse phone number ${savedActivation.phoneNumber} succeeded (${successfulUses}/${maxUses}); keeping it for later signups.`, 'info' ); } @@ -5562,7 +5562,7 @@ if (successfulUses >= maxUses) { await clearFreeReusableActivation(); await addLog( - `步骤 9:白嫖复用手机号 ${savedActivation.phoneNumber} 已达到 ${successfulUses}/${maxUses} 次,已清除本地记录。`, + `Step 9: Free-reuse phone number ${savedActivation.phoneNumber} reached ${successfulUses}/${maxUses} uses; cleared local record.`, 'info' ); return; @@ -5581,7 +5581,7 @@ async function waitForPhoneCodeOrRotateNumber(tabId, state, activation) { const normalizedActivation = normalizeActivation(activation); if (!normalizedActivation) { - throw new Error('缺少手机号接码订单。'); + throw new Error('Missing phone SMS order.'); } const providerLabel = normalizedActivation.provider === PHONE_SMS_PROVIDER_5SIM ? '5sim' @@ -5601,14 +5601,14 @@ for (let windowIndex = 1; windowIndex <= timeoutWindows; windowIndex += 1) { await setPhoneRuntimeCountdown(normalizedActivation, waitSeconds, windowIndex, timeoutWindows); await addLog( - `步骤 9:等待号码 ${normalizedActivation.phoneNumber} 接收短信(等待窗口 ${windowIndex}/${timeoutWindows},最长 ${waitSeconds} 秒,每 ${pollIntervalSeconds} 秒轮询一次,最多 ${pollMaxRounds} 次轮询)。`, + `Step 9: Waiting for number ${normalizedActivation.phoneNumber} to receive SMS (window ${windowIndex}/${timeoutWindows}, up to ${waitSeconds} seconds, polling every ${pollIntervalSeconds} seconds, max ${pollMaxRounds} polls).`, 'info' ); try { const code = await pollPhoneActivationCode(state, normalizedActivation, { actionLabel: windowIndex === 1 - ? '从接码平台轮询手机验证码' - : '从接码平台轮询重发后的手机验证码', + ? 'Poll phone verification code from SMS provider' + : 'Poll phone verification code from SMS provider after resend', timeoutMs: waitSeconds * 1000, intervalMs: pollIntervalSeconds * 1000, maxRounds: pollMaxRounds, @@ -5616,7 +5616,7 @@ if (/^STATUS_(WAIT_CODE|WAIT_RETRY|WAIT_RESEND)(?::.+)?$/i.test(String(statusText || '').trim())) { const pageError = await checkPhoneResendPageError(tabId, state); if (pageError?.reason === 'resend_phone_banned') { - throw new Error(`${PHONE_RESEND_BANNED_NUMBER_ERROR_PREFIX}${pageError.message || 'OpenAI 无法向此手机号发送短信。'}`); + throw new Error(`${PHONE_RESEND_BANNED_NUMBER_ERROR_PREFIX}${pageError.message || 'OpenAI cannot send SMS to this phone number.'}`); } if (pageError?.reason === 'phone_max_usage_exceeded') { throw buildPhoneMaxUsageExceededError(pageError.message); @@ -5629,13 +5629,13 @@ throw buildHighRiskResendThrottledError(pageError.message); } await addLog( - `步骤 9:检测到号码 ${normalizedActivation.phoneNumber} 重发限流,但未启用“按疑似封禁处理”,继续等待短信。${pageError.message || ''}`.trim(), + `Step 9: Number ${normalizedActivation.phoneNumber} resend was rate-limited, but "treat as suspected ban" is not enabled; continuing to wait for SMS. ${pageError.message || ''}`.trim(), 'warn' ); } } await addLog( - `步骤 9:${getPhoneSmsProviderLabel(normalizedActivation.provider)} 号码 ${normalizedActivation.phoneNumber} 状态:${statusText}(已等待 ${Math.ceil(elapsedMs / 1000)} 秒,第 ${pollCount}/${pollMaxRounds} 次轮询)。`, + `Step 9: ${getPhoneSmsProviderLabel(normalizedActivation.provider)} number ${normalizedActivation.phoneNumber} status: ${statusText} (waited ${Math.ceil(elapsedMs / 1000)} seconds, poll ${pollCount}/${pollMaxRounds}).`, 'info' ); }, @@ -5649,7 +5649,7 @@ if (!isPhoneCodeTimeoutError(error)) { if (isPhoneResendBannedNumberError(error)) { await addLog( - `步骤 9:OpenAI 无法向号码 ${normalizedActivation.phoneNumber} 发送短信,立即更换号码。${error.message}`, + `Step 9: OpenAI cannot send SMS to number ${normalizedActivation.phoneNumber}; replacing the number immediately. ${error.message}`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5661,7 +5661,7 @@ } if (isPhoneMaxUsageExceededFlowError(error)) { await addLog( - `步骤 9:OpenAI 提示号码 ${normalizedActivation.phoneNumber} 达到使用上限,立即更换号码。${error.message}`, + `Step 9: OpenAI reports number ${normalizedActivation.phoneNumber} reached its usage limit; replacing the number immediately. ${error.message}`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5673,7 +5673,7 @@ } if (isPhoneResendServerError(error)) { await addLog( - `步骤 9:重发短信后进入 contact-verification 500 页面,立即更换号码。${error.message}`, + `Step 9: Page hit contact-verification 500 after SMS resend; replacing the number immediately. ${error.message}`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5686,7 +5686,7 @@ if (isPhoneResendThrottledError(error)) { if (shouldTreatResendThrottledAsBanned(state)) { await addLog( - `步骤 9:号码 ${normalizedActivation.phoneNumber} 重发限流且配置为高风险封禁,立即更换号码。${error.message}`, + `Step 9: Number ${normalizedActivation.phoneNumber} resend was rate-limited and is configured as high-risk banned; replacing the number immediately. ${error.message}`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5697,7 +5697,7 @@ }; } await addLog( - `步骤 9:号码 ${normalizedActivation.phoneNumber} 重发限流,但未启用高风险换号,继续原等待逻辑。${error.message}`, + `Step 9: Number ${normalizedActivation.phoneNumber} resend was rate-limited; high-risk replacement not enabled, keeping original wait logic. ${error.message}`, 'warn' ); await sleepWithStop(pollIntervalSeconds * 1000); @@ -5705,7 +5705,7 @@ } if (isPhoneActivationOrderMissingError(error, normalizedActivation.provider)) { await addLog( - `步骤 9:${providerLabel} 号码 ${normalizedActivation.phoneNumber} 的接码订单已失效(${error.message || error}),立即更换号码。`, + `Step 9: ${providerLabel} SMS order for number ${normalizedActivation.phoneNumber} has expired (${error.message || error}); replacing the number immediately.`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5720,19 +5720,19 @@ if (windowIndex < timeoutWindows) { await addLog( - `步骤 9:号码 ${normalizedActivation.phoneNumber} 在 ${waitSeconds} 秒内未收到短信,正在请求再次发送。`, + `Step 9: Number ${normalizedActivation.phoneNumber} did not receive SMS within ${waitSeconds} seconds; requesting resend.`, 'warn' ); if (!usePageResend) { await addLog( - `步骤 9:${providerLabel} 保持当前验证码页会话并跳过页面重发,避免触发 405 或重发限流;继续轮询当前号码。`, + `Step 9: ${providerLabel} keeps the current verification page session and skips the page resend to avoid triggering 405 or rate limiting; continuing to poll the current number.`, 'warn' ); continue; } if (resendTriggeredForCurrentNumber) { await addLog( - `步骤 9:号码 ${normalizedActivation.phoneNumber} 已触发过一次页面重发;为避免限流,将继续轮询不再点击重发。`, + `Step 9: Number ${normalizedActivation.phoneNumber} has already triggered one page resend; to avoid rate limiting, will keep polling without clicking resend again.`, 'warn' ); continue; @@ -5741,7 +5741,7 @@ const resendProbeResult = await resendPhoneVerificationCode(tabId, { probeOnly: true }); if (isWhatsAppPhoneResendResult(resendProbeResult)) { await addLog( - `步骤 9:页面重发入口显示 WhatsApp 通道(${resendProbeResult.channelText || resendProbeResult.text || 'WhatsApp'}),当前接码平台无法读取 WhatsApp 消息,立即更换号码。`, + `Step 9: Page resend entry shows WhatsApp channel (${resendProbeResult.channelText || resendProbeResult.text || 'WhatsApp'}); the SMS provider cannot read WhatsApp messages, so replacing the number immediately.`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5756,7 +5756,7 @@ const resendResult = await resendPhoneVerificationCode(tabId); if (isWhatsAppPhoneResendResult(resendResult)) { await addLog( - `步骤 9:页面重发入口切换为 WhatsApp 通道(${resendResult.channelText || resendResult.text || 'WhatsApp'}),当前接码平台无法读取 WhatsApp 消息,立即更换号码。`, + `Step 9: Page resend entry switched to WhatsApp channel (${resendResult.channelText || resendResult.text || 'WhatsApp'}); the SMS provider cannot read WhatsApp messages, so replacing the number immediately.`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5768,14 +5768,14 @@ } } resendTriggeredForCurrentNumber = true; - await addLog('步骤 9:已点击手机验证码页面的“重新发送短信”。', 'info'); + await addLog('Step 9: Clicked "Resend SMS" on the phone verification code page.', 'info'); } catch (resendError) { if (isStopRequestedError(resendError)) { throw resendError; } if (isPhoneResendBannedNumberError(resendError)) { await addLog( - `步骤 9:OpenAI 无法向号码 ${normalizedActivation.phoneNumber} 发送短信,立即更换号码。${resendError.message}`, + `Step 9: OpenAI cannot send SMS to number ${normalizedActivation.phoneNumber}; replacing the number immediately. ${resendError.message}`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5787,7 +5787,7 @@ } if (isPhoneResendThrottledError(resendError)) { await addLog( - `步骤 9:号码 ${normalizedActivation.phoneNumber} 重发短信被限流,立即更换号码。${resendError.message}`, + `Step 9: Number ${normalizedActivation.phoneNumber} SMS resend was rate-limited; replacing the number immediately. ${resendError.message}`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5801,7 +5801,7 @@ } if (isPhoneResendServerError(resendError)) { await addLog( - `步骤 9:重发短信后进入 contact-verification 500 页面,立即更换号码。${resendError.message}`, + `Step 9: Page hit contact-verification 500 after SMS resend; replacing the number immediately. ${resendError.message}`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5811,13 +5811,13 @@ reason: 'resend_server_error', }; } - await addLog(`步骤 9:点击手机验证码页面重发按钮失败。${resendError.message}`, 'warn'); + await addLog(`Step 9: Failed to click the resend button on the phone verification code page. ${resendError.message}`, 'warn'); } continue; } await addLog( - `步骤 9:号码 ${normalizedActivation.phoneNumber} 连续 ${timeoutWindows} 轮未收到短信,将在步骤 9 内更换号码。`, + `Step 9: Number ${normalizedActivation.phoneNumber} did not receive SMS in ${timeoutWindows} consecutive windows; will replace the number within step 9.`, 'warn' ); await clearPhoneRuntimeCountdown(); @@ -5829,7 +5829,7 @@ } } - throw new Error('手机号验证未完成。'); + throw new Error('Phone verification incomplete.'); } function buildCompletedActivationSnapshot(activation) { @@ -5851,7 +5851,7 @@ const actionLabelPrefix = String(options?.actionLabelPrefix || 'signup phone verification').trim() || 'phone verification'; const onPollStatus = typeof options?.onPollStatus === 'function' ? options.onPollStatus : null; if (!normalizedActivation) { - throw new Error(options?.missingActivationMessage || `步骤 ${visibleStep}:手机号激活记录缺失,请重新执行前置步骤。`); + throw new Error(options?.missingActivationMessage || `Step ${visibleStep}: Phone activation record missing. Please re-execute prerequisite steps.`); } return withPhoneVerificationLogContext({ step: visibleStep, stepKey }, async () => { @@ -5876,7 +5876,7 @@ [PHONE_RUNTIME_COUNTDOWN_WINDOW_TOTAL_KEY]: timeoutWindows, }); await addLog( - `步骤 ${visibleStep}:正在等待 ${normalizedActivation.phoneNumber} 的短信验证码(等待窗口 ${windowIndex}/${timeoutWindows},最长 ${waitSeconds} 秒,每 ${pollIntervalSeconds} 秒轮询一次,最多 ${pollMaxRounds} 次轮询)。`, + `Step ${visibleStep}: Waiting for the SMS code on ${normalizedActivation.phoneNumber} (window ${windowIndex}/${timeoutWindows}, up to ${waitSeconds} seconds, polling every ${pollIntervalSeconds} seconds, max ${pollMaxRounds} polls).`, 'info', { step: visibleStep, stepKey } ); @@ -5890,7 +5890,7 @@ maxRounds: pollMaxRounds, onStatus: async ({ elapsedMs, pollCount, statusText }) => { await addLog( - `步骤 ${visibleStep}:${providerLabel} 状态 ${normalizedActivation.phoneNumber}: ${statusText}(已等待 ${Math.ceil(elapsedMs / 1000)} 秒,第 ${pollCount}/${pollMaxRounds} 次轮询)。`, + `Step ${visibleStep}: ${providerLabel} status ${normalizedActivation.phoneNumber}: ${statusText} (waited ${Math.ceil(elapsedMs / 1000)} seconds, poll ${pollCount}/${pollMaxRounds}).`, 'info', { step: visibleStep, stepKey } ); @@ -5910,14 +5910,14 @@ } catch (error) { if (!isPhoneCodeTimeoutError(error)) { if (isPhoneActivationOrderMissingError(error, normalizedActivation.provider)) { - throw new Error(`步骤 ${visibleStep}:当前手机号激活已失效,请重新执行前置步骤获取新短信。${error.message || error}`); + throw new Error(`Step ${visibleStep}: Current phone activation has expired. Please re-execute prerequisite steps to get new SMS.${error.message || error}`); } throw error; } if (windowIndex < timeoutWindows) { await addLog( - `步骤 ${visibleStep}:${normalizedActivation.phoneNumber} 在 ${waitSeconds} 秒内未收到短信,准备请求重发。`, + `Step ${visibleStep}: ${normalizedActivation.phoneNumber} did not receive SMS within ${waitSeconds} seconds; preparing to request a resend.`, 'warn', { step: visibleStep, stepKey } ); @@ -5937,7 +5937,7 @@ } } - throw new Error(`步骤 ${visibleStep}:手机验证码未能成功获取。`); + throw new Error(`Step ${visibleStep}: Phone verification code could not be acquired successfully.`); }); } @@ -5948,7 +5948,7 @@ stepKey: 'fetch-signup-code', purpose: 'signup', actionLabelPrefix: 'signup phone verification', - missingActivationMessage: '步骤 4:注册手机号激活记录缺失,请重新执行步骤 2。', + missingActivationMessage: 'Step 4: Signup phone activation record missing. Please re-execute Step 2.', }); } @@ -5960,7 +5960,7 @@ stepKey: 'fetch-login-code', purpose: 'login', actionLabelPrefix: 'login phone verification', - missingActivationMessage: `步骤 ${visibleStep}:登录手机号激活记录缺失,请重新执行步骤 ${visibleStep >= 11 ? 10 : 7}。`, + missingActivationMessage: `Step ${visibleStep}: Login phone activation record missing. Please re-execute Step ${visibleStep >= 11 ? 10 : 7}.`, }); } @@ -5995,7 +5995,7 @@ const activation = normalizeActivation(options?.activation || state?.signupPhoneActivation); const pageStateCheckTimeoutMs = Math.max(1, Math.floor(Number(options?.pageStateCheckTimeoutMs) || 5000)); if (!activation) { - throw new Error('步骤 4:未找到当前注册手机号激活记录,请重新执行步骤 2。'); + throw new Error('Step 4: Current signup phone activation record not found. Please re-execute Step 2.'); } const assertSignupPhoneStillApplicable = async (phaseLabel) => { @@ -6011,7 +6011,7 @@ } await throwPhoneResendServerErrorIfAuthTabShowsIt(tabId); await addLog( - `步骤 4:检查注册手机号页面状态(${phaseLabel})失败,将继续等待短信。${error.message}`, + `Step 4: Failed to check the signup phone page state (${phaseLabel}); continuing to wait for SMS. ${error.message}`, 'warn', { step: 4, @@ -6035,7 +6035,7 @@ onTimeoutWindow: async () => { try { await resendSignupPhoneVerificationCode(tabId); - await addLog('步骤 4:已点击注册手机验证码页面的“重新发送”。', 'info', { + await addLog('Step 4: Clicked "Resend" on the signup phone verification code page.', 'info', { step: 4, stepKey: 'fetch-signup-code', }); @@ -6047,7 +6047,7 @@ throw buildPhoneResendServerError(resendError); } await throwPhoneResendServerErrorIfAuthTabShowsIt(tabId); - await addLog(`步骤 4:注册手机验证码页面重发失败,将继续轮询短信。${resendError.message}`, 'warn', { + await addLog(`Step 4: Failed to resend on the signup phone verification page; continuing to poll for SMS. ${resendError.message}`, 'warn', { step: 4, stepKey: 'fetch-signup-code', }); @@ -6062,7 +6062,7 @@ signupPhoneVerificationRequestedAt: Date.now(), signupPhoneVerificationPurpose: 'signup', }); - await addLog(`步骤 4:已获取手机验证码 ${code}。`, 'info', { + await addLog(`Step 4: Acquired phone verification code ${code}.`, 'info', { step: 4, stepKey: 'fetch-signup-code', }); @@ -6072,9 +6072,9 @@ }); if (submitResult.invalidCode) { - const invalidErrorText = String(submitResult.errorText || submitResult.url || '未知错误').trim(); + const invalidErrorText = String(submitResult.errorText || submitResult.url || 'unknown error').trim(); if (attempt >= DEFAULT_PHONE_SUBMIT_ATTEMPTS) { - throw new Error(`步骤 4:手机验证码连续 ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} 次被拒绝:${invalidErrorText}`); + throw new Error(`Step 4: Phone verification code was rejected ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} times in a row: ${invalidErrorText}`); } await requestAdditionalPhoneSms(state, activation); @@ -6088,13 +6088,13 @@ throw buildPhoneResendServerError(resendError); } await throwPhoneResendServerErrorIfAuthTabShowsIt(tabId); - await addLog(`步骤 4:验证码被拒后点击重发失败。${resendError.message}`, 'warn', { + await addLog(`Step 4: Failed to click resend after code rejection. ${resendError.message}`, 'warn', { step: 4, stepKey: 'fetch-signup-code', }); } await addLog( - `步骤 4:手机验证码被拒绝,已请求新短信(${attempt + 1}/${DEFAULT_PHONE_SUBMIT_ATTEMPTS})。`, + `Step 4: Phone verification code was rejected; requested a new SMS (${attempt + 1}/${DEFAULT_PHONE_SUBMIT_ATTEMPTS}).`, 'warn', { step: 4, stepKey: 'fetch-signup-code' } ); @@ -6108,14 +6108,14 @@ signupPhoneVerificationRequestedAt: null, signupPhoneVerificationPurpose: '', }); - await addLog('步骤 4:手机验证码已通过,继续进入资料填写。', 'ok', { + await addLog('Step 4: Phone verification code passed. Continuing to profile entry.', 'ok', { step: 4, stepKey: 'fetch-signup-code', }); return submitResult || {}; } - throw new Error('步骤 4:手机验证码未能成功提交。'); + throw new Error('Step 4: Phone verification code could not be submitted successfully.'); } catch (error) { if (shouldCancelActivation && activation) { await cancelSignupPhoneActivation(state, activation).catch(() => {}); @@ -6133,7 +6133,7 @@ async function submitLoginPhoneVerificationCode(tabId, code, options = {}) { const visibleStep = normalizeLogStep(options?.visibleStep || options?.step) || 8; const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' - ? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交登录手机验证码' }) + ? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: 'Submit login phone verification code' }) : 45000; const result = await sendToContentScriptResilient('openai-auth', { type: 'SUBMIT_PHONE_VERIFICATION_CODE', @@ -6148,7 +6148,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 600, - logMessage: `步骤 ${visibleStep}:等待登录手机验证码页面就绪后填写短信验证码...`, + logMessage: `Step ${visibleStep}: Waiting for the login phone verification code page to be ready before filling the SMS code...`, logStep: visibleStep, logStepKey: 'fetch-login-code', }); @@ -6162,7 +6162,7 @@ async function resendLoginPhoneVerificationCode(tabId, options = {}) { const visibleStep = normalizeLogStep(options?.visibleStep || options?.step) || 8; const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' - ? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: '重新发送登录手机验证码' }) + ? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: 'Resend login phone verification code' }) : 65000; const result = await sendToContentScriptResilient('openai-auth', { type: 'RESEND_VERIFICATION_CODE', @@ -6173,7 +6173,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 600, - logMessage: `步骤 ${visibleStep}:等待登录手机验证码重发按钮出现...`, + logMessage: `Step ${visibleStep}: Waiting for the login phone verification code resend button to appear...`, logStep: visibleStep, logStepKey: 'fetch-login-code', }); @@ -6193,7 +6193,7 @@ || state?.signupPhoneActivation ); if (!preferredActivation) { - throw new Error(`步骤 ${visibleStep}:缺少已注册手机号激活记录,无法继续手机号登录验证码流程。`); + throw new Error(`Step ${visibleStep}: Missing registered phone activation record; cannot continue the phone-login verification flow.`); } const activeActivation = normalizeActivation(state?.signupPhoneActivation); @@ -6208,7 +6208,7 @@ const reactivated = await reactivatePhoneActivation(state, preferredActivation); const normalizedActivation = normalizeActivation(reactivated); if (!normalizedActivation) { - throw new Error(`步骤 ${visibleStep}:无法复用当前注册手机号,请重新执行步骤 ${visibleStep >= 11 ? 10 : 7}。`); + throw new Error(`Step ${visibleStep}: Could not reuse the current signup phone number. Please re-execute Step ${visibleStep >= 11 ? 10 : 7}.`); } await setPhoneRuntimeState({ @@ -6264,7 +6264,7 @@ || state?.signupPhoneActivation ); if (!baseActivation) { - throw new Error(`步骤 ${visibleStep}:未找到当前登录手机号激活记录,请重新执行步骤 ${visibleStep >= 11 ? 10 : 7}。`); + throw new Error(`Step ${visibleStep}: Current login phone activation record not found. Please re-execute Step ${visibleStep >= 11 ? 10 : 7}.`); } let activation = await prepareLoginPhoneActivation(state, { @@ -6282,7 +6282,7 @@ onTimeoutWindow: async () => { try { await resendLoginPhoneVerificationCode(tabId, { visibleStep }); - await addLog(`步骤 ${visibleStep}:已点击登录手机验证码页面的“重新发送”。`, 'info', { + await addLog(`Step ${visibleStep}: Clicked "Resend" on the login phone verification code page.`, 'info', { step: visibleStep, stepKey: 'fetch-login-code', }); @@ -6293,7 +6293,7 @@ if (isPhoneResendServerError(resendError)) { throw buildPhoneResendServerError(resendError); } - await addLog(`步骤 ${visibleStep}:登录手机验证码页面重发失败,将继续轮询短信。${resendError.message}`, 'warn', { + await addLog(`Step ${visibleStep}: Failed to resend on the login phone verification page; continuing to poll for SMS. ${resendError.message}`, 'warn', { step: visibleStep, stepKey: 'fetch-login-code', }); @@ -6306,7 +6306,7 @@ signupPhoneVerificationRequestedAt: Date.now(), signupPhoneVerificationPurpose: 'login', }); - await addLog(`步骤 ${visibleStep}:已获取登录手机验证码 ${code}。`, 'info', { + await addLog(`Step ${visibleStep}: Got login phone verification code ${code}.`, 'info', { step: visibleStep, stepKey: 'fetch-login-code', }); @@ -6316,9 +6316,9 @@ }); if (submitResult.invalidCode) { - const invalidErrorText = String(submitResult.errorText || submitResult.url || '未知错误').trim(); + const invalidErrorText = String(submitResult.errorText || submitResult.url || 'unknown error').trim(); if (attempt >= DEFAULT_PHONE_SUBMIT_ATTEMPTS) { - throw new Error(`步骤 ${visibleStep}:登录手机验证码连续 ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} 次被拒绝:${invalidErrorText}`); + throw new Error(`Step ${visibleStep}: Login phone verification code was rejected ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} times in a row: ${invalidErrorText}`); } await requestAdditionalPhoneSms(state, activation); @@ -6331,13 +6331,13 @@ if (isPhoneResendServerError(resendError)) { throw buildPhoneResendServerError(resendError); } - await addLog(`步骤 ${visibleStep}:登录手机验证码被拒后点击重发失败。${resendError.message}`, 'warn', { + await addLog(`Step ${visibleStep}: Failed to click resend after the login phone verification code was rejected. ${resendError.message}`, 'warn', { step: visibleStep, stepKey: 'fetch-login-code', }); } await addLog( - `步骤 ${visibleStep}:登录手机验证码被拒绝,已请求新短信(${attempt + 1}/${DEFAULT_PHONE_SUBMIT_ATTEMPTS})。`, + `Step ${visibleStep}: Login phone verification code was rejected; requested a new SMS (${attempt + 1}/${DEFAULT_PHONE_SUBMIT_ATTEMPTS}).`, 'warn', { step: visibleStep, stepKey: 'fetch-login-code' } ); @@ -6346,14 +6346,14 @@ await finalizeLoginPhoneActivationAfterSuccess(state, activation, { visibleStep }); shouldCancelActivation = false; - await addLog(`步骤 ${visibleStep}:登录手机验证码已通过,继续进入后续授权流程。`, 'ok', { + await addLog(`Step ${visibleStep}: Login phone verification code passed; continuing to the next authorization step.`, 'ok', { step: visibleStep, stepKey: 'fetch-login-code', }); return submitResult || {}; } - throw new Error(`步骤 ${visibleStep}:登录手机验证码未能成功提交。`); + throw new Error(`Step ${visibleStep}: Login phone verification code could not be submitted successfully.`); } catch (error) { if (shouldCancelActivation && activation) { await cancelPhoneActivation(state, activation).catch(() => {}); @@ -6452,7 +6452,7 @@ const result = await navigateAuthTabToAddPhone(tabId, { visibleStep, timeoutMs: 30000, - logMessage: '步骤 9:认证页已失联,直接打开添加手机号页面后等待脚本恢复。', + logMessage: 'Step 9: Auth page is unreachable; opening the add-phone page directly and waiting for the script to recover.', logStepKey: 'phone-verification', attemptLabel, }); @@ -6476,7 +6476,7 @@ } catch (error) { snapshotError = error; await addLog( - `步骤 9:检查认证页状态失败(${attemptLabel})。${error.message}`, + `Step 9: Failed to check auth page state (${attemptLabel}). ${error.message}`, 'warn' ); snapshot = null; @@ -6499,7 +6499,7 @@ } catch (error) { returnError = error; await addLog( - `步骤 9:返回添加手机号页面失败(${attemptLabel})。${error.message}`, + `Step 9: Failed to return to the add-phone page (${attemptLabel}). ${error.message}`, 'warn' ); } @@ -6531,7 +6531,7 @@ } if (!latest?.addPhonePage) { throw new Error( - `步骤 9:提交手机号前认证页未停留在添加手机号页面(${attemptLabel})。URL: ${latest?.url || 'unknown'}` + `Step 9: Before submitting phone number, the auth page did not stay on the add-phone page (${attemptLabel}). URL: ${latest?.url || 'unknown'}` ); } return latest; @@ -6562,7 +6562,7 @@ if (nextCount >= PHONE_SMS_FAILURE_SKIP_THRESHOLD) { const countryLabel = resolveCountryLabelByFailureKey(countryKey, providerId); await addLog( - `步骤 9:${countryLabel} 已累计 ${nextCount} 次短信失败(${formatStep9Reason(reason)});下次获取号码会优先尝试其它已选国家。`, + `Step 9: ${countryLabel} accumulated ${nextCount} SMS failures (${formatStep9Reason(reason)}); the next phone number acquisition will prioritize other selected countries.`, 'warn' ); } @@ -6644,7 +6644,7 @@ countryPriceFloorByKey.set(countryKey, normalizedFloor); const countryLabel = resolveCountryLabelByFailureKey(countryKey, normalizedActivation.provider); await addLog( - `步骤 9:${countryLabel} 因 ${formatStep9Reason(reason || 'sms_timeout')} 将尝试更高价格档位(> ${normalizedFloor})。`, + `Step 9: ${countryLabel} will try a higher price tier (> ${normalizedFloor}) due to ${formatStep9Reason(reason || 'sms_timeout')}.`, 'warn' ); }; @@ -6662,7 +6662,7 @@ } preferredActivationExhausted = true; await addLog( - `步骤 9:优先号码 ${activation.phoneNumber} 失败(${formatStep9Reason(reason || 'unknown')}),将改为获取新号码。`, + `Step 9: Preferred number ${activation.phoneNumber} failed (${formatStep9Reason(reason || 'unknown')}); will get a new number instead.`, 'warn' ); }; @@ -6674,7 +6674,7 @@ throw buildPhoneReplacementLimitError(maxNumberReplacementAttempts, failureCode || 'add_phone_rejected'); } await addLog( - `步骤 9:添加手机号失败后正在更换号码(${formatStep9Reason(failureReason)},${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts})。`, + `Step 9: Replacing the number after add-phone failed (${formatStep9Reason(failureReason)}, ${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts}).`, 'warn' ); if (shouldCancelActivation && activation) { @@ -6701,7 +6701,7 @@ }; } catch (returnError) { await addLog( - `步骤 9:号码被拒后返回添加手机号页面失败,将用当前可用状态继续。${returnError.message}`, + `Step 9: Failed to return to the add-phone page after the number was rejected; continuing with the current available state. ${returnError.message}`, 'warn' ); } @@ -6715,7 +6715,7 @@ }; } catch (verifyError) { await addLog( - `步骤 9:号码被拒后确认添加手机号页面状态失败。${verifyError.message}`, + `Step 9: Failed to confirm the add-phone page state after the number was rejected. ${verifyError.message}`, 'warn' ); } @@ -6759,12 +6759,12 @@ throw buildPhoneReplacementLimitError(maxNumberReplacementAttempts, 'returned_to_add_phone_loop'); } await addLog( - `步骤 9:当前号码 ${activation.phoneNumber} 反复返回添加手机号页,正在更换号码(${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts})。`, + `Step 9: Current number ${activation.phoneNumber} keeps returning to the add-phone page; replacing the number (${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts}).`, 'warn' ); if (shouldRetireFreeReusableActivationOnFailure(await getState(), activation)) { await retireFreeReusableActivation( - `自动白嫖复用号码 ${activation.phoneNumber} 反复返回添加手机号页。` + `Auto free-reuse number ${activation.phoneNumber} keeps returning to the add-phone page.` ); } if (shouldCancelActivation && activation) { @@ -6783,7 +6783,7 @@ continue; } await addLog( - `步骤 9:页面返回添加手机号,将先重新提交当前号码 ${activation.phoneNumber},暂不获取新号码。`, + `Step 9: Page returned to add-phone; will resubmit the current number ${activation.phoneNumber} first instead of acquiring a new one.`, 'warn' ); } @@ -6809,22 +6809,22 @@ usedNumberReplacementAttempts += 1; if (usedNumberReplacementAttempts > maxNumberReplacementAttempts) { throw new Error( - `步骤 9:更换 ${maxNumberReplacementAttempts} 次号码后手机号验证仍未成功。最后原因:${formatStep9Reason('phone_number_used')}。` + `Step 9: Phone verification still failed after replacing the number ${maxNumberReplacementAttempts} times. Last reason: ${formatStep9Reason('phone_number_used')}.` ); } await addLog( - `步骤 9:添加手机号页面提示 ${activation.phoneNumber} 已被使用(${addPhoneRejectText}),正在更换号码(${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts})。`, + `Step 9: Add-phone page reports ${activation.phoneNumber} is already in use (${addPhoneRejectText}); replacing the number (${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts}).`, 'warn' ); await discardPhoneActivationFromReuse( - `目标站拒绝该号码(${addPhoneRejectText})。`, + `Target site rejected this number (${addPhoneRejectText}).`, activation, await getState() ); if (shouldRetireFreeReusableActivationOnFailure(await getState(), activation)) { await retireFreeReusableActivation( - `自动白嫖复用号码 ${activation.phoneNumber} 被目标站拒绝。` + `Auto free-reuse number ${activation.phoneNumber} was rejected by the target site.` ); } if (shouldCancelActivation && activation) { @@ -6853,7 +6853,7 @@ } await addLog( - `步骤 9:添加手机号页面拒绝当前号码,但未明确提示已使用(${addPhoneRejectText}),将用同一号码再试一次。`, + `Step 9: Add-phone page rejected the current number, but did not explicitly report "already in use" (${addPhoneRejectText}); will retry once with the same number.`, 'warn' ); let retrySubmitError = null; @@ -6884,12 +6884,12 @@ continue; } throw new Error( - `步骤 9:添加手机号页面持续拒绝当前号码,但没有明确“已使用”状态:${submitResult.errorText || submitResult.url || '未知错误'}。` + `Step 9: Add-phone page keeps rejecting the current number with no clear "already used" status: ${submitResult.errorText || submitResult.url || 'unknown error'}.` ); } } - await addLog('步骤 9:已在添加手机号页面提交号码。', 'info'); + await addLog('Step 9: Submitted the phone number on the add-phone page.', 'info'); pageState = { ...pageState, ...submitResult, @@ -6909,7 +6909,7 @@ } if (!activation) { - throw new Error('认证页面正在等待手机验证码,但当前运行没有保存手机号接码订单。'); + throw new Error('Auth page is waiting for the phone verification code, but the current run has no saved phone SMS order.'); } let shouldReplaceNumber = false; @@ -6935,12 +6935,12 @@ [PHONE_VERIFICATION_CODE_STATE_KEY]: String(codeResult.code || '').trim(), }); await markFreeReusableActivationAfterCode(state, activation); - await addLog(`步骤 9:已收到手机验证码 ${codeResult.code}。`, 'info'); + await addLog(`Step 9: Received phone verification code ${codeResult.code}.`, 'info'); const submitResult = await submitPhoneVerificationCode(tabId, codeResult.code); if (submitResult.returnedToAddPhone) { await addLog( - '步骤 9:提交验证码后返回添加手机号页面,将先重试当前号码。', + 'Step 9: After submitting the verification code the page returned to add-phone; will retry the current number first.', 'warn' ); preferReuseExistingActivationOnAddPhone = true; @@ -6959,13 +6959,13 @@ shouldReplaceNumber = true; replaceReason = 'phone_number_used'; await discardPhoneActivationFromReuse( - `目标站拒绝该号码(${invalidErrorText})。`, + `Target site rejected this number (${invalidErrorText}).`, activation, await getState() ); if (shouldRetireFreeReusableActivationOnFailure(await getState(), activation)) { await retireFreeReusableActivation( - `自动白嫖复用号码 ${activation.phoneNumber} 被目标站拒绝。` + `Auto free-reuse number ${activation.phoneNumber} was rejected by the target site.` ); } if (shouldCancelActivation && activation) { @@ -6973,7 +6973,7 @@ shouldCancelActivation = false; } await addLog( - `步骤 9:手机号被提示已使用(${invalidErrorText}),立即更换新号码。`, + `Step 9: Phone number reported as already used (${invalidErrorText}); switching to a new number immediately.`, 'warn' ); break; @@ -6987,7 +6987,7 @@ shouldCancelActivation = false; } await addLog( - `步骤 9:手机验证码连续 ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} 次被拒(${invalidErrorText}),将更换号码。`, + `Step 9: Phone verification code was rejected ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} times in a row (${invalidErrorText}); replacing the number.`, 'warn' ); break; @@ -7001,7 +7001,7 @@ shouldReplaceNumber = true; replaceReason = 'whatsapp_resend_channel'; await addLog( - `步骤 9:验证码被拒后的重发入口显示 WhatsApp 通道(${resendProbeResult.channelText || resendProbeResult.text || 'WhatsApp'}),当前接码平台无法读取 WhatsApp 消息,将更换号码。`, + `Step 9: After the verification code was rejected, the resend entry shows WhatsApp channel (${resendProbeResult.channelText || resendProbeResult.text || 'WhatsApp'}); the SMS provider cannot read WhatsApp messages, so switching numbers.`, 'warn' ); break; @@ -7013,26 +7013,26 @@ shouldReplaceNumber = true; replaceReason = 'whatsapp_resend_channel'; await addLog( - `步骤 9:验证码被拒后的重发入口切换为 WhatsApp 通道(${resendResult.channelText || resendResult.text || 'WhatsApp'}),当前接码平台无法读取 WhatsApp 消息,将更换号码。`, + `Step 9: After the verification code was rejected, the resend entry switched to WhatsApp channel (${resendResult.channelText || resendResult.text || 'WhatsApp'}); the SMS provider cannot read WhatsApp messages, so switching numbers.`, 'warn' ); break; } } - await addLog('步骤 9:手机验证码被拒后已点击“重新发送短信”。', 'info'); + await addLog('Step 9: Clicked "Resend SMS" after the verification code was rejected.', 'info'); } catch (resendError) { - await addLog(`步骤 9:验证码被拒后点击重发失败。${resendError.message}`, 'warn'); + await addLog(`Step 9: Failed to click resend after the verification code was rejected. ${resendError.message}`, 'warn'); } if (shouldReplaceNumber) { break; } await addLog( - `步骤 9:手机验证码被拒,已请求再次发送短信(剩余 ${remainingResendRequests} 次重发)。`, + `Step 9: Phone verification code was rejected; requested another SMS (${remainingResendRequests} resends remaining).`, 'warn' ); } else { await addLog( - '步骤 9:手机验证码被拒,配置的重发次数已用完,将在当前接码窗口内继续重试。', + 'Step 9: Phone verification code was rejected and the configured resend count is used up; will keep retrying in the current SMS window.', 'warn' ); } @@ -7043,7 +7043,7 @@ if (shouldSkipTerminalStatusForFreeReuse(latestSuccessState, activation)) { const terminalProviderLabel = getPhoneSmsProviderLabel(activation.provider); await addLog( - `步骤 9:已跳过 ${terminalProviderLabel} 完成状态,保留 ${activation.phoneNumber} 供白嫖复用。`, + `Step 9: Skipped ${terminalProviderLabel} completion status to keep ${activation.phoneNumber} for free reuse.`, 'info' ); await markFreeReusableActivationAfterInitialSuccess(latestSuccessState, activation); @@ -7061,7 +7061,7 @@ phoneNumber: activation.phoneNumber, }); addPhoneReentryWithSameActivation = 0; - await addLog('步骤 9:手机号验证已完成,等待 OAuth 授权页。', 'ok'); + await addLog('Step 9: Phone verification complete; waiting for the OAuth authorization page.', 'ok'); return submitResult; } @@ -7069,7 +7069,7 @@ if (pageState?.addPhonePage) { continue; } - throw new Error('手机号验证未完成。'); + throw new Error('Phone verification incomplete.'); } if ( @@ -7095,12 +7095,12 @@ } if (shouldRetireFreeReusableActivationOnFailure(await getState(), activation)) { await retireFreeReusableActivation( - `自动白嫖复用号码 ${activation.phoneNumber} 在失败后被更换。` + `Auto free-reuse number ${activation.phoneNumber} was replaced after failing.` ); } if (isPhoneNumberUsedError(replaceReason)) { await discardPhoneActivationFromReuse( - `目标站拒绝该号码(${replaceReason})。`, + `Target site rejected this number (${replaceReason}).`, activation, await getState() ); @@ -7114,7 +7114,7 @@ try { returnResult = await returnToAddPhone(tabId); } catch (returnError) { - await addLog(`步骤 9:更换号码前返回添加手机号页面失败。${returnError.message}`, 'warn'); + await addLog(`Step 9: Failed to return to the add-phone page before replacing the number. ${returnError.message}`, 'warn'); } if (!returnResult?.addPhonePage) { @@ -7144,7 +7144,7 @@ }; await addLog( - `步骤 9:正在更换号码并在步骤 9 内重试(${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts})。`, + `Step 9: Replacing the number and retrying within step 9 (${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts}).`, 'warn' ); pageState = { @@ -7164,7 +7164,7 @@ } if (shouldRetireFreeReusableActivationOnFailure(await getState(), activation)) { await retireFreeReusableActivation( - `自动白嫖复用号码 ${activation.phoneNumber} 执行失败:${errorMessage || 'unknown error'}。` + `Auto free-reuse number ${activation.phoneNumber} failed during execution: ${errorMessage || 'unknown error'}.` ); } if (shouldCancelActivation && activation) { diff --git a/background/signup-flow-helpers.js b/background/signup-flow-helpers.js index 75d20705..212b34b6 100644 --- a/background/signup-flow-helpers.js +++ b/background/signup-flow-helpers.js @@ -42,7 +42,7 @@ if (typeof addLog === 'function') { await addLog( - `步骤 ${step}:注册页已打开,正在等待页面加载完成并额外稳定 3 秒...`, + `Step ${step}: Signup page opened, waiting for it to finish loading and remain stable for 3 more seconds...`, 'info', { step, stepKey: 'signup-entry' } ); @@ -69,7 +69,7 @@ injectSource: 'openai-auth', timeoutMs: 45000, retryDelayMs: 900, - logMessage: `步骤 ${step}:ChatGPT 官网仍在加载,正在重试连接内容脚本...`, + logMessage: `Step ${step}: ChatGPT site is still loading, retrying content-script connection...`, }); return tabId; @@ -85,7 +85,7 @@ }, { timeoutMs: 20000, retryDelayMs: 700, - logMessage: `步骤 ${step}:官网注册入口正在切换,等待页面恢复...`, + logMessage: `Step ${step}: Signup entry is switching, waiting for the page to recover...`, }); if (result?.error) { @@ -149,7 +149,7 @@ retryDelayMs: 300, }); if (!matchedTab) { - throw new Error('等待注册身份提交后的页面跳转超时,请检查页面是否仍停留在输入页。'); + throw new Error('Timed out waiting for page navigation after signup identity submission. Check whether the page is still stuck on the input page.'); } landingUrl = matchedTab.url || ''; @@ -167,7 +167,7 @@ } if (!landingState) { - throw new Error(`注册身份提交后未能识别当前页面,既不是密码页、验证码页,也不是资料页。URL: ${landingUrl || 'unknown'}`); + throw new Error(`Could not identify the current page after signup identity submission — it is not the password page, verification page, or profile page. URL: ${landingUrl || 'unknown'}`); } if (landingState !== 'password_page' && typeof waitForTabStableComplete === 'function') { @@ -192,8 +192,8 @@ timeoutMs: 45000, retryDelayMs: 900, logMessage: landingState === 'password_page' - ? `步骤 ${step}:密码页仍在加载,正在重试连接内容脚本...` - : `步骤 ${step}:注册后续页面仍在加载,正在等待页面恢复...`, + ? `Step ${step}: Password page is still loading, retrying content-script connection...` + : `Step ${step}: Signup follow-up page is still loading, waiting for it to recover...`, }); if (landingState !== 'password_page') { @@ -212,7 +212,7 @@ }, { timeoutMs: 20000, retryDelayMs: 700, - logMessage: `步骤 ${step}:认证页正在切换,等待密码页重新就绪...`, + logMessage: `Step ${step}: Auth page is switching, waiting for the password page to become ready again...`, }); if (result?.error) { @@ -234,14 +234,14 @@ async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) { const result = await ensureSignupPostEmailPageReadyInTab(tabId, step, options); if (result.state !== 'password_page') { - throw new Error(`当前页面不是密码页,实际落地为 ${result.state || 'unknown'}。URL: ${result.url || 'unknown'}`); + throw new Error(`Current page is not the password page; it actually landed on ${result.state || 'unknown'}. URL: ${result.url || 'unknown'}`); } return result; } async function finalizeSignupPasswordSubmitInTab(tabId, password = '', step = 3) { if (!Number.isInteger(tabId)) { - throw new Error(`认证页面标签页已关闭,无法完成步骤 ${step} 的提交后确认。`); + throw new Error(`Auth page tab was closed; cannot complete the post-submit confirmation for step ${step}.`); } await ensureContentScriptReadyOnTab('openai-auth', tabId, { @@ -249,7 +249,7 @@ injectSource: 'openai-auth', timeoutMs: 45000, retryDelayMs: 900, - logMessage: `步骤 ${step}:认证页仍在切换,正在等待页面恢复后继续确认提交流程...`, + logMessage: `Step ${step}: Auth page is still switching, waiting for the page to recover before confirming the submission flow...`, }); let result; @@ -261,16 +261,16 @@ payload: { password: password || '', prepareSource: 'step3_finalize', - prepareLogLabel: '步骤 3 收尾', + prepareLogLabel: 'Step 3 finalize', }, }, { timeoutMs: 30000, retryDelayMs: 700, - logMessage: `步骤 ${step}:密码已提交,正在确认是否进入下一页面,必要时自动恢复重试页...`, + logMessage: `Step ${step}: Password submitted, confirming whether the page advanced to the next page; will auto-recover the retry page if necessary...`, }); } catch (error) { if (isRetryableContentScriptTransportError(error)) { - const message = `步骤 ${step}:认证页在提交后切换过程中页面通信超时,未能重新就绪,暂时无法确认是否进入下一页面。请重试当前轮。`; + const message = `Step ${step}: Page communication timed out while the auth page was switching after submission; could not become ready in time, so it cannot confirm whether the page advanced to the next page. Please retry the current round.`; if (typeof addLog === 'function') { await addLog(message, 'warn'); } @@ -371,7 +371,7 @@ } if (!resolvedEmail) { - throw new Error('缺少邮箱地址,请先在侧边栏粘贴邮箱。'); + throw new Error('Missing email address. Please paste an email in the side panel first.'); } if (!generatedEmailAlreadyPersisted || options?.preserveAccountIdentity) { diff --git a/background/sub2api-api.js b/background/sub2api-api.js index 5b518256..f1a9056b 100644 --- a/background/sub2api-api.js +++ b/background/sub2api-api.js @@ -34,7 +34,7 @@ parsed.pathname = '/auth/callback'; } if (parsed.pathname !== '/auth/callback') { - throw new Error('SUB2API 回调地址必须是 /auth/callback,例如 http://localhost:1455/auth/callback'); + throw new Error('SUB2API callback path must be /auth/callback, e.g. http://localhost:1455/auth/callback'); } return parsed.toString(); } @@ -47,7 +47,7 @@ try { return new URL(sub2apiUrl).origin; } catch { - throw new Error('SUB2API URL 格式无效,请先在侧边栏检查。'); + throw new Error('SUB2API URL format is invalid. Please check it in the side panel first.'); } } @@ -59,7 +59,7 @@ payload?.reason, ]; const message = candidates.map(normalizeString).find(Boolean); - return message || `SUB2API 请求失败(HTTP ${responseStatus}):${path}`; + return message || `SUB2API request failed (HTTP ${responseStatus}): ${path}`; } async function requestJson(origin, path, options = {}) { @@ -102,7 +102,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error(`SUB2API 请求超时:${path}`); + throw new Error(`SUB2API request timed out: ${path}`); } throw error; } finally { @@ -116,10 +116,10 @@ const origin = getSub2ApiOrigin(state.sub2apiUrl); if (!email) { - throw new Error('尚未配置 SUB2API 登录邮箱,请先在侧边栏填写。'); + throw new Error('SUB2API login email is not configured. Please fill it in the side panel first.'); } if (!password) { - throw new Error('尚未配置 SUB2API 登录密码,请先在侧边栏填写。'); + throw new Error('SUB2API login password is not configured. Please fill it in the side panel first.'); } const loginData = await requestJson(origin, '/api/v1/auth/login', { @@ -130,7 +130,7 @@ const token = normalizeString(loginData?.access_token || loginData?.accessToken); if (!token) { - throw new Error('SUB2API 登录返回缺少 access_token。'); + throw new Error('SUB2API login response is missing access_token.'); } return { @@ -181,7 +181,7 @@ } if (missing.length) { - throw new Error(`SUB2API 中未找到以下 openai 分组:${missing.join('、')}。`); + throw new Error(`The following openai groups were not found in SUB2API: ${missing.join(', ')}.`); } return matched; @@ -205,7 +205,7 @@ } const numeric = Number(rawValue); if (!Number.isSafeInteger(numeric) || numeric < 1) { - throw new Error('SUB2API 账号优先级必须是大于等于 1 的整数。'); + throw new Error('SUB2API account priority must be an integer greater than or equal to 1.'); } return numeric; } @@ -229,7 +229,7 @@ const port = proxy.port === undefined || proxy.port === null ? '' : normalizeString(proxy.port); const address = protocol && host && port ? `${protocol}://${host}:${port}` : ''; return [ - name || '(未命名代理)', + name || '(unnamed proxy)', id ? `#${id}` : '', address, ].filter(Boolean).join(' '); @@ -308,7 +308,7 @@ timeoutMs: options.timeoutMs, }); if (!Array.isArray(proxies)) { - throw new Error('SUB2API 代理列表返回格式异常,无法自动选择代理。'); + throw new Error('SUB2API proxy list returned an unexpected format; cannot automatically select a proxy.'); } const { proxy, reason, candidates } = findSub2ApiProxy(proxies, preference); @@ -316,24 +316,24 @@ return proxy; } - const configured = normalizeSub2ApiProxyPreference(preference) || '(未配置)'; + const configured = normalizeSub2ApiProxyPreference(preference) || '(not configured)'; const available = (candidates || []) .slice(0, 8) .map(buildProxyDisplayName) - .join(';') || '无可用代理'; + .join('; ') || 'no available proxies'; if (reason === 'ambiguous-name' || reason === 'ambiguous-fuzzy') { - throw new Error(`SUB2API 默认代理“${configured}”匹配到多个代理,请改填代理 ID。候选:${available}`); + throw new Error(`SUB2API default proxy "${configured}" matched multiple proxies; please specify the proxy ID instead. Candidates: ${available}`); } if (reason === 'missing-id') { - throw new Error(`SUB2API 默认代理 ID “${configured}”不存在或未启用。可用代理:${available}`); + throw new Error(`SUB2API default proxy ID "${configured}" does not exist or is not enabled. Available proxies: ${available}`); } if (reason === 'missing-name') { - throw new Error(`SUB2API 默认代理“${configured}”不存在或未启用。可用代理:${available}`); + throw new Error(`SUB2API default proxy "${configured}" does not exist or is not enabled. Available proxies: ${available}`); } if (reason === 'no-preference') { - throw new Error(`SUB2API 存在多个可用代理,请在侧边栏填写默认代理名称或 ID;留空则不使用代理。可用代理:${available}`); + throw new Error(`SUB2API has multiple available proxies; please fill in the default proxy name or ID in the side panel, or leave it blank to skip the proxy. Available proxies: ${available}`); } - throw new Error('SUB2API 没有可用代理;请检查默认代理配置,或将其留空以禁用代理。'); + throw new Error('SUB2API has no available proxies. Please check the default proxy config, or leave it blank to disable the proxy.'); } function buildDraftAccountName(groupName) { @@ -429,7 +429,7 @@ return normalizedAccessToken; } - throw new Error('未读取到可导入的 ChatGPT 会话或 accessToken。'); + throw new Error('No importable ChatGPT session or accessToken was found.'); } function resolveCodexSessionImportExpiresAt(session) { @@ -470,7 +470,7 @@ function buildCodexSessionImportSummary(result) { const normalized = normalizeCodexSessionImportResult(result); - return `SUB2API 会话导入完成:新建 ${normalized.created},更新 ${normalized.updated},跳过 ${normalized.skipped},失败 ${normalized.failed}`; + return `SUB2API session import complete: created ${normalized.created}, updated ${normalized.updated}, skipped ${normalized.skipped}, failed ${normalized.failed}`; } function getCodexSessionImportFailureMessage(result) { @@ -481,7 +481,7 @@ .map((item) => normalizeString(item?.message)) .find(Boolean) || buildCodexSessionImportSummary(normalized); - return detail || 'SUB2API 会话导入失败。'; + return detail || 'SUB2API session import failed.'; } function parseLocalhostCallback(rawUrl, visibleStep = 10) { @@ -489,23 +489,23 @@ try { parsed = new URL(rawUrl); } catch { - throw new Error(`步骤 ${visibleStep} 捕获到的 localhost OAuth 回调地址格式无效。`); + throw new Error(`Step ${visibleStep}: The captured localhost OAuth callback URL has an invalid format.`); } if (!['http:', 'https:'].includes(parsed.protocol)) { - throw new Error('回调 URL 协议不正确。'); + throw new Error('Callback URL protocol is incorrect.'); } if (!['localhost', '127.0.0.1'].includes(parsed.hostname)) { - throw new Error(`步骤 ${visibleStep} 只接受 localhost / 127.0.0.1 回调地址。`); + throw new Error(`Step ${visibleStep} only accepts localhost / 127.0.0.1 callback URLs.`); } if (parsed.pathname !== '/auth/callback') { - throw new Error('回调 URL 路径必须是 /auth/callback。'); + throw new Error('Callback URL path must be /auth/callback.'); } const code = normalizeString(parsed.searchParams.get('code')); const oauthState = normalizeString(parsed.searchParams.get('state')); if (!code || !oauthState) { - throw new Error('回调 URL 中缺少 code 或 state。'); + throw new Error('Callback URL is missing code or state.'); } return { @@ -537,7 +537,7 @@ } if (!credentials.access_token) { - throw new Error('SUB2API 交换授权码后未返回 access_token。'); + throw new Error('SUB2API did not return access_token after exchanging the authorization code.'); } return credentials; @@ -561,12 +561,12 @@ } async function generateOpenAiAuthUrl(state = {}, options = {}) { - const logLabel = normalizeString(options.logLabel) || 'OAuth 刷新'; + const logLabel = normalizeString(options.logLabel) || 'OAuth refresh'; const redirectUri = normalizeRedirectUri(options.redirectUri || DEFAULT_REDIRECT_URI); const groupNames = normalizeSub2ApiGroupNames(state.sub2apiGroupName || DEFAULT_SUB2API_GROUP_NAME); const groupName = groupNames[0] || DEFAULT_SUB2API_GROUP_NAME; - await logWithOptions(`${logLabel}:正在通过 SUB2API 管理接口登录并生成 OpenAI Auth 链接...`, 'info', options); + await logWithOptions(`${logLabel}: Logging in via the SUB2API admin interface and generating the OpenAI Auth URL...`, 'info', options); const { origin, token } = await loginSub2Api(state, options); const groups = await getGroupsByNames(origin, token, groupNames, options); const group = groups[0]; @@ -574,13 +574,13 @@ const proxy = proxyPreference ? await resolveSub2ApiProxy(origin, token, proxyPreference, options) : null; const proxyId = normalizeProxyId(proxy?.id); const draftName = buildDraftAccountName(group.name || groupName); - const groupLabel = groups.map((item) => `${item.name}(#${item.id})`).join('、'); + const groupLabel = groups.map((item) => `${item.name} (#${item.id})`).join(', '); - await logWithOptions(`${logLabel}:已登录 SUB2API,使用分组 ${groupLabel}。`, 'info', options); + await logWithOptions(`${logLabel}: Logged in to SUB2API, using group ${groupLabel}.`, 'info', options); if (proxy) { - await logWithOptions(`${logLabel}:已选择 SUB2API 默认代理 ${buildProxyDisplayName(proxy)}。`, 'info', options); + await logWithOptions(`${logLabel}: Selected SUB2API default proxy ${buildProxyDisplayName(proxy)}.`, 'info', options); } else { - await logWithOptions(`${logLabel}:未配置 SUB2API 默认代理,本次将不使用代理。`, 'info', options); + await logWithOptions(`${logLabel}: No SUB2API default proxy configured; this run will not use a proxy.`, 'info', options); } const authRequestBody = { redirect_uri: redirectUri }; @@ -600,10 +600,10 @@ const oauthState = normalizeString(authData?.state || extractStateFromAuthUrl(oauthUrl)); if (!oauthUrl || !sessionId) { - throw new Error('SUB2API 未返回完整的 auth_url / session_id。'); + throw new Error('SUB2API did not return a complete auth_url / session_id.'); } - await logWithOptions(`${logLabel}:已获取 SUB2API OAuth 链接:${oauthUrl.slice(0, 96)}...`, 'ok', options); + await logWithOptions(`${logLabel}: Got SUB2API OAuth URL: ${oauthUrl.slice(0, 96)}...`, 'ok', options); return { oauthUrl, sub2apiSessionId: sessionId, @@ -621,13 +621,13 @@ const flowEmail = normalizeString(state.email); const sessionId = normalizeString(state.sub2apiSessionId); const expectedState = normalizeString(state.sub2apiOAuthState); - const logLabel = normalizeString(options.logLabel) || `步骤 ${visibleStep}`; + const logLabel = normalizeString(options.logLabel) || `Step ${visibleStep}`; if (!sessionId) { - throw new Error('缺少 SUB2API session_id,请重新执行步骤 1。'); + throw new Error('Missing SUB2API session_id. Please rerun step 1.'); } if (expectedState && expectedState !== callback.state) { - throw new Error('本次 localhost 回调中的 state 与步骤 1 生成的 state 不一致,请重新执行步骤 1。'); + throw new Error('The state in this localhost callback does not match the state generated in step 1. Please rerun step 1.'); } const { origin, token } = await loginSub2Api(state, options); @@ -647,11 +647,11 @@ ? [{ id: state.sub2apiGroupId, name: state.sub2apiGroupName || DEFAULT_SUB2API_GROUP_NAME }] : await getGroupsByNames(origin, token, state.sub2apiGroupName || DEFAULT_SUB2API_GROUP_NAME, options)); - await logWithOptions(`${logLabel}:正在通过 SUB2API 管理接口交换 OpenAI 授权码...`, 'info', options); + await logWithOptions(`${logLabel}: Exchanging the OpenAI authorization code via the SUB2API admin interface...`, 'info', options); if (proxy) { - await logWithOptions(`${logLabel}:使用 SUB2API 默认代理 ${buildProxyDisplayName(proxy)}。`, 'info', options); + await logWithOptions(`${logLabel}: Using SUB2API default proxy ${buildProxyDisplayName(proxy)}.`, 'info', options); } else { - await logWithOptions(`${logLabel}:未配置 SUB2API 默认代理,本次将不使用代理。`, 'info', options); + await logWithOptions(`${logLabel}: No SUB2API default proxy configured; this run will not use a proxy.`, 'info', options); } const exchangeRequestBody = { @@ -677,7 +677,7 @@ .map((group) => Number(group.id)) .filter((id) => Number.isFinite(id) && id > 0); if (!groupIds.length) { - throw new Error('SUB2API 返回的目标分组 ID 无效。'); + throw new Error('SUB2API returned an invalid target group ID.'); } const accountName = resolvedEmail @@ -703,7 +703,7 @@ createPayload.extra = extra; } - await logWithOptions(`${logLabel}:授权码交换成功,正在创建 SUB2API 账号(名称:${accountName})...`, 'info', options); + await logWithOptions(`${logLabel}: Authorization code exchange succeeded, creating SUB2API account (name: ${accountName})...`, 'info', options); const createdAccount = await requestJson(origin, '/api/v1/admin/accounts', { method: 'POST', token, @@ -711,7 +711,7 @@ body: createPayload, }); - const verifiedStatus = `SUB2API 已创建账号 #${createdAccount?.id || 'unknown'}`; + const verifiedStatus = `SUB2API account created #${createdAccount?.id || 'unknown'}`; await logWithOptions(verifiedStatus, 'ok', options); return { localhostUrl: callback.url, @@ -720,7 +720,7 @@ } async function importCurrentChatGptSession(state = {}, options = {}) { - const logLabel = normalizeString(options.logLabel) || 'SUB2API 会话导入'; + const logLabel = normalizeString(options.logLabel) || 'SUB2API session import'; const session = normalizeCodexSessionObject(state?.session); const accessToken = normalizeString( state?.accessToken @@ -730,21 +730,21 @@ const importExpiresAt = resolveCodexSessionImportExpiresAt(session); const preferredAccountName = resolveCodexSessionImportAccountName(state, session, accessToken); - await logWithOptions(`${logLabel}:正在通过 SUB2API 管理接口登录并准备导入当前 ChatGPT 会话...`, 'info', options); + await logWithOptions(`${logLabel}: Logging in via the SUB2API admin interface and preparing to import the current ChatGPT session...`, 'info', options); const { origin, token } = await loginSub2Api(state, options); const groupNames = state.sub2apiGroupName || DEFAULT_SUB2API_GROUP_NAME; const groups = await getGroupsByNames(origin, token, groupNames, options); - const groupLabel = groups.map((item) => `${item.name}(${item.id})`).join('、'); + const groupLabel = groups.map((item) => `${item.name} (${item.id})`).join(', '); const proxyPreference = resolveSub2ApiProxyPreference(state); const proxy = proxyPreference ? await resolveSub2ApiProxy(origin, token, proxyPreference, options) : null; const proxyId = normalizeProxyId(proxy?.id); const accountPriority = resolveSub2ApiAccountPriority(state); - await logWithOptions(`${logLabel}:已登录 SUB2API,使用分组 ${groupLabel}。`, 'info', options); + await logWithOptions(`${logLabel}: Logged in to SUB2API, using group ${groupLabel}.`, 'info', options); if (proxy) { - await logWithOptions(`${logLabel}:已选择 SUB2API 默认代理 ${buildProxyDisplayName(proxy)}。`, 'info', options); + await logWithOptions(`${logLabel}: Selected SUB2API default proxy ${buildProxyDisplayName(proxy)}.`, 'info', options); } else { - await logWithOptions(`${logLabel}:未配置 SUB2API 默认代理,本次将不使用代理。`, 'info', options); + await logWithOptions(`${logLabel}: No SUB2API default proxy configured; this run will not use a proxy.`, 'info', options); } const importPayload = { @@ -758,7 +758,7 @@ update_existing: true, }; if (!importPayload.group_ids.length) { - throw new Error('SUB2API 返回的目标分组 ID 无效。'); + throw new Error('SUB2API returned an invalid target group ID.'); } if (proxyId) { importPayload.proxy_id = proxyId; @@ -767,7 +767,7 @@ importPayload.expires_at = importExpiresAt; } - await logWithOptions(`${logLabel}:正在导入当前 ChatGPT 会话到 SUB2API...`, 'info', options); + await logWithOptions(`${logLabel}: Importing the current ChatGPT session into SUB2API...`, 'info', options); const importResult = normalizeCodexSessionImportResult(await requestJson(origin, '/api/v1/admin/accounts/import/codex-session', { method: 'POST', token, @@ -776,7 +776,7 @@ })); for (const warning of importResult.warnings) { - await logWithOptions(`${logLabel}:${warning.message}`, 'warn', options); + await logWithOptions(`${logLabel}: ${warning.message}`, 'warn', options); } if (importResult.failed > 0) { diff --git a/background/verification-flow.js b/background/verification-flow.js index c417724c..605f6fa0 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -87,7 +87,7 @@ } function getVerificationCodeLabel(step) { - return step === 4 ? '注册' : '登录'; + return step === 4 ? 'signup' : 'login'; } function isIcloudMail(mail) { @@ -248,7 +248,7 @@ timeoutMs: requestTimeoutMs, responseTimeoutMs: requestTimeoutMs, retryDelayMs: 400, - logMessage: `步骤 ${step}:验证码提交后页面正在切换,等待页面恢复并确认授权状态...`, + logMessage: `Step ${step}: Page is switching after verification code submission, waiting for it to recover and confirm authorization state...`, } ) : await sendToContentScript('openai-auth', request, { @@ -391,23 +391,23 @@ const completionStep = getCompletionStep(step, options); const promptStep = getCompletionStep(step, { completionStep: options.promptStep ?? completionStep }); const verificationLabel = getVerificationCodeLabel(step); - await addLog(`步骤 ${completionStep}:当前为自定义邮箱模式,请手动在页面中输入${verificationLabel}验证码并进入下一页面。`, 'warn'); + await addLog(`Step ${completionStep}: Currently in custom email mode. Please enter the ${verificationLabel} verification code on the page manually and proceed to the next page.`, 'warn'); let response = null; try { response = await confirmCustomVerificationStepBypassRequest(promptStep); } catch { - throw new Error(`步骤 ${completionStep}:无法打开确认弹窗,请先保持侧边栏打开后重试。`); + throw new Error(`Step ${completionStep}: Could not open the confirmation dialog. Please keep the side panel open and retry.`); } if (response?.error) { throw new Error(response.error); } if (step === 8 && response?.addPhoneDetected) { - throw new Error(`步骤 ${completionStep}:验证码提交后页面进入手机号页面,当前流程无法继续自动授权。 URL: https://auth.openai.com/add-phone`); + throw new Error(`Step ${completionStep}: After verification code submission, the page entered the phone-number page; the current flow cannot continue automatic authorization. URL: https://auth.openai.com/add-phone`); } if (!response?.confirmed) { - throw new Error(`步骤 ${completionStep}:已取消手动${verificationLabel}验证码确认。`); + throw new Error(`Step ${completionStep}: Manual ${verificationLabel} verification code confirmation was canceled.`); } await setState({ @@ -417,10 +417,10 @@ }); const completionNodeId = await getNodeIdForStep(completionStep); if (!completionNodeId) { - throw new Error(`步骤 ${completionStep} 未映射到验证码节点。`); + throw new Error(`Step ${completionStep} is not mapped to a verification code node.`); } await setNodeStatus(completionNodeId, 'skipped'); - await addLog(`步骤 ${completionStep}:已确认手动完成${verificationLabel}验证码输入,当前步骤已跳过。`, 'warn'); + await addLog(`Step ${completionStep}: Confirmed manual ${verificationLabel} verification code entry; the current step has been skipped.`, 'warn'); } function getVerificationPollPayload(step, state, overrides = {}) { @@ -518,7 +518,7 @@ throwIfStopped(); const signupTabId = await getTabId('openai-auth'); if (!signupTabId) { - throw new Error('认证页面标签页已关闭,无法重新请求验证码。'); + throw new Error('Auth page tab was closed; cannot request a new verification code.'); } throwIfStopped(); @@ -535,7 +535,7 @@ step, options, 30000, - `重新发送${getVerificationCodeLabel(step)}验证码` + `Resend ${getVerificationCodeLabel(step)} verification code` ), }); @@ -543,7 +543,7 @@ throw new Error(result.error); } - await addLog(`步骤 ${step}:已请求新的${getVerificationCodeLabel(step)}验证码。`, 'warn'); + await addLog(`Step ${step}: Requested a new ${getVerificationCodeLabel(step)} verification code.`, 'warn'); const requestedAt = Date.now(); if (step === 4) { @@ -558,7 +558,7 @@ const mailTabId = await getTabId('mail-2925'); if (mailTabId) { await chrome.tabs.update(mailTabId, { active: true }); - await addLog(`步骤 ${step}:已切换到 2925 邮箱标签页等待新邮件。`, 'info'); + await addLog(`Step ${step}: Switched to the 2925 mailbox tab to wait for new mail.`, 'info'); } } @@ -579,14 +579,14 @@ } throwIfStopped(); - await addLog(`步骤 ${step}:开始刷新 2925 邮箱前先清空全部邮件,避免读取旧验证码邮件。`, 'warn'); + await addLog(`Step ${step}: Clearing all emails before refreshing the 2925 mailbox to avoid reading old verification code emails.`, 'warn'); try { const responseTimeoutMs = await getResponseTimeoutMsForStep( step, options, 15000, - '清空 2925 邮箱历史邮件' + 'Clear 2925 mailbox history' ); const result = await sendToMailContentScriptResilient( mail, @@ -610,16 +610,16 @@ } if (result?.deleted === false) { - await addLog(`步骤 ${step}:未能确认 2925 邮箱已清空,将继续刷新等待新邮件。`, 'warn'); + await addLog(`Step ${step}: Could not confirm that the 2925 mailbox was cleared; will keep refreshing and wait for new mail.`, 'warn'); return; } - await addLog(`步骤 ${step}:2925 邮箱已预先清空,开始刷新等待新邮件。`, 'info'); + await addLog(`Step ${step}: 2925 mailbox cleared in advance; refreshing and waiting for new mail.`, 'info'); } catch (err) { if (isStopError(err)) { throw err; } - await addLog(`步骤 ${step}:预清空 2925 邮箱失败,将继续刷新等待新邮件:${err.message}`, 'warn'); + await addLog(`Step ${step}: Failed to pre-clear the 2925 mailbox; will keep refreshing and wait for new mail: ${err.message}`, 'warn'); } } @@ -634,7 +634,7 @@ if (Number.isInteger(tabId)) { await chrome.tabs.remove(tabId).catch(() => {}); - await addLog(`步骤 ${step}:已关闭 iCloud 邮箱标签页,避免长期累积。`, 'info'); + await addLog(`Step ${step}: Closed the iCloud mailbox tab to avoid long-term accumulation.`, 'info'); return; } @@ -723,7 +723,7 @@ if (round === 1 && initialRoundNoResendUntil > 0) { const waitSeconds = Math.max(1, Math.ceil((initialRoundNoResendUntil - Date.now()) / 1000)); await addLog( - `步骤 ${step}:首次进入验证码轮询,先等待 ${waitSeconds} 秒观察新邮件,避免过早重复重发。`, + `Step ${step}: Entering verification code polling for the first time; will wait ${waitSeconds} seconds to watch for new mail and avoid resending too early.`, 'info' ); } @@ -758,7 +758,7 @@ step, payload, pollOverrides, - `轮询${getVerificationCodeLabel(step)}验证码邮箱` + `Poll ${getVerificationCodeLabel(step)} verification code mailbox` ); const timeoutWindow = resolveMailPollingTimeouts(mail, timedPoll); const result = await sendToMailContentScriptResilient( @@ -783,11 +783,11 @@ } if (!result || !result.code) { - throw new Error(`步骤 ${step}:邮箱轮询结束,但未获取到验证码。`); + throw new Error(`Step ${step}: Mailbox polling ended but no verification code was found.`); } if (rejectedCodes.has(result.code)) { - throw new Error(`步骤 ${step}:再次收到了相同的${getVerificationCodeLabel(step)}验证码:${result.code}`); + throw new Error(`Step ${step}: Received the same ${getVerificationCodeLabel(step)} verification code again: ${result.code}`); } transportErrorStreak = 0; @@ -811,10 +811,10 @@ if (isTransportError) { transportErrorStreak += 1; lastError = err; - await addLog(`步骤 ${step}:${err.message}`, 'warn'); + await addLog(`Step ${step}: ${err.message}`, 'warn'); if (transportErrorStreak >= maxTransportErrorStreak) { throw new Error( - `步骤 ${step}:${mail?.label || '邮箱'}页面通信异常连续 ${transportErrorStreak} 次,已停止当前轮询以避免重复重发验证码。最后错误:${err.message}` + `Step ${step}: ${mail?.label || 'Mailbox'} page communication failed ${transportErrorStreak} times in a row. Stopped the current polling to avoid resending the verification code repeatedly. Last error: ${err.message}` ); } const fallbackIntervalMs = Math.max( @@ -831,14 +831,14 @@ } transportErrorStreak = 0; lastError = err; - await addLog(`步骤 ${step}:${err.message}`, 'warn'); + await addLog(`Step ${step}: ${err.message}`, 'warn'); } if (mail?.source === 'icloud-mail' && maxIcloudNoResendRounds > 0) { pollOnlyNoResendRounds += 1; if (pollOnlyNoResendRounds >= maxIcloudNoResendRounds) { throw new Error( - `步骤 ${step}:iCloud 邮箱连续 ${pollOnlyNoResendRounds} 轮轮询均未拿到验证码且未触发重发,已停止当前链路以避免空轮询循环,请刷新邮箱页后重试。` + `Step ${step}: The iCloud mailbox failed to produce a verification code in ${pollOnlyNoResendRounds} consecutive polling rounds without triggering a resend. Stopped the current chain to avoid an empty polling loop. Please refresh the mailbox page and retry.` ); } } @@ -852,7 +852,7 @@ const effectiveCooldownMs = Math.max(remainingBeforeResendMs, initialCooldownMs); if (effectiveCooldownMs > 0) { await addLog( - `步骤 ${step}:距离下次重新发送验证码还差 ${Math.ceil(effectiveCooldownMs / 1000)} 秒,继续刷新邮箱(第 ${round}/${maxRounds} 轮)...`, + `Step ${step}: ${Math.ceil(effectiveCooldownMs / 1000)} seconds remain until the next verification code resend; continuing to refresh the mailbox (round ${round}/${maxRounds})...`, 'info' ); const configuredIntervalMs = Math.max( @@ -870,13 +870,13 @@ } if (round < maxRounds) { - await addLog(`步骤 ${step}:已到 25 秒重发间隔,准备重新发送验证码(第 ${round + 1}/${maxRounds} 轮)...`, 'warn'); + await addLog(`Step ${step}: Reached the 25-second resend interval; preparing to resend the verification code (round ${round + 1}/${maxRounds})...`, 'warn'); } break; } } - throw lastError || new Error(`步骤 ${step}:无法获取新的${getVerificationCodeLabel(step)}验证码。`); + throw lastError || new Error(`Step ${step}: Could not get a new ${getVerificationCodeLabel(step)} verification code.`); } function shouldRequestLuckmailResendBeforeRetry(error) { @@ -885,7 +885,7 @@ return true; } - return !/没有可用 token|token 对应邮箱与当前邮箱不一致/i.test(message); + return !/没有可用 token|token 对应邮箱与当前邮箱不一致|no available token|token does not match current email/i.test(message); } async function pollLuckmailVerificationCodeWithResend(step, state, pollOverrides = {}) { @@ -931,16 +931,16 @@ if (isStopError(resendError)) { throw resendError; } - await addLog(`步骤 ${step}:LuckMail 点击重新发送验证码失败:${resendError.message},仍将在 ${Math.ceil(intervalMs / 1000)} 秒后继续轮询 /code 接口。`, 'warn'); + await addLog(`Step ${step}: LuckMail failed to click resend verification code: ${resendError.message}. Will continue polling the /code endpoint in ${Math.ceil(intervalMs / 1000)} seconds.`, 'warn'); } } - await addLog(`步骤 ${step}:LuckMail 暂未获取到新的${getVerificationCodeLabel(step)}验证码,等待 ${Math.ceil(intervalMs / 1000)} 秒后继续轮询 /code 接口(${attempt + 1}/${maxAttempts})...`, 'warn'); + await addLog(`Step ${step}: LuckMail has not yet returned a new ${getVerificationCodeLabel(step)} verification code; waiting ${Math.ceil(intervalMs / 1000)} seconds before continuing to poll the /code endpoint (${attempt + 1}/${maxAttempts})...`, 'warn'); await sleepWithStop(intervalMs); } } - throw lastError || new Error(`步骤 ${step}:无法获取新的${getVerificationCodeLabel(step)}验证码。`); + throw lastError || new Error(`Step ${step}: Could not get a new ${getVerificationCodeLabel(step)} verification code.`); } async function pollFreshVerificationCode(step, state, mail, pollOverrides = {}) { @@ -959,14 +959,14 @@ ...getVerificationPollPayload(step, state), ...hotmailPollConfig, ...cleanPollOverrides, - }, cleanPollOverrides, `轮询${getVerificationCodeLabel(step)}验证码邮箱`); + }, cleanPollOverrides, `Poll ${getVerificationCodeLabel(step)} verification code mailbox`); return pollHotmailVerificationCode(step, state, timedPoll.payload); } if (mail.provider === LUCKMAIL_PROVIDER) { const timedPoll = await applyMailPollingTimeBudget(step, { ...getVerificationPollPayload(step, state), ...cleanPollOverrides, - }, cleanPollOverrides, `轮询${getVerificationCodeLabel(step)}验证码邮箱`); + }, cleanPollOverrides, `Poll ${getVerificationCodeLabel(step)} verification code mailbox`); return pollLuckmailVerificationCodeWithResend(step, state, { ...cleanPollOverrides, ...timedPoll.payload, @@ -977,21 +977,21 @@ const timedPoll = await applyMailPollingTimeBudget(step, { ...getVerificationPollPayload(step, state), ...cleanPollOverrides, - }, cleanPollOverrides, `轮询${getVerificationCodeLabel(step)}验证码邮箱`); + }, cleanPollOverrides, `Poll ${getVerificationCodeLabel(step)} verification code mailbox`); return pollCloudflareTempEmailVerificationCode(step, state, timedPoll.payload); } if (mail.provider === CLOUD_MAIL_PROVIDER) { const timedPoll = await applyMailPollingTimeBudget(step, { ...getVerificationPollPayload(step, state), ...cleanPollOverrides, - }, cleanPollOverrides, `轮询${getVerificationCodeLabel(step)}验证码邮箱`); + }, cleanPollOverrides, `Poll ${getVerificationCodeLabel(step)} verification code mailbox`); return pollCloudMailVerificationCode(step, state, timedPoll.payload); } if (mail.provider === YYDS_MAIL_PROVIDER) { const timedPoll = await applyMailPollingTimeBudget(step, { ...getVerificationPollPayload(step, state), ...cleanPollOverrides, - }, cleanPollOverrides, `轮询${getVerificationCodeLabel(step)}验证码邮箱`); + }, cleanPollOverrides, `Poll ${getVerificationCodeLabel(step)} verification code mailbox`); return pollYydsMailVerificationCode(step, state, timedPoll.payload); } @@ -1051,7 +1051,7 @@ step, payload, pollOverrides, - `轮询${getVerificationCodeLabel(step)}验证码邮箱` + `Poll ${getVerificationCodeLabel(step)} verification code mailbox` ); const timeoutWindow = resolveMailPollingTimeouts(mail, timedPoll); const result = await sendToMailContentScriptResilient( @@ -1076,11 +1076,11 @@ } if (!result || !result.code) { - throw new Error(`步骤 ${step}:邮箱轮询结束,但未获取到验证码。`); + throw new Error(`Step ${step}: Mailbox polling ended but no verification code was found.`); } if (rejectedCodes.has(result.code)) { - throw new Error(`步骤 ${step}:再次收到了相同的${getVerificationCodeLabel(step)}验证码:${result.code}`); + throw new Error(`Step ${step}: Received the same ${getVerificationCodeLabel(step)} verification code again: ${result.code}`); } return { @@ -1098,14 +1098,14 @@ throw err; } lastError = err; - await addLog(`步骤 ${step}:${err.message}`, 'warn'); + await addLog(`Step ${step}: ${err.message}`, 'warn'); if (round < maxRounds) { - await addLog(`步骤 ${step}:将重新发送验证码后重试(${round + 1}/${maxRounds})...`, 'warn'); + await addLog(`Step ${step}: Will resend the verification code and retry (${round + 1}/${maxRounds})...`, 'warn'); } } } - throw lastError || new Error(`步骤 ${step}:无法获取新的${getVerificationCodeLabel(step)}验证码。`); + throw lastError || new Error(`Step ${step}: Could not get a new ${getVerificationCodeLabel(step)} verification code.`); } async function submitVerificationCode(step, code, options = {}) { @@ -1113,7 +1113,7 @@ const authLoginStep = completionStep >= 11 ? 10 : 7; const signupTabId = await getTabId('openai-auth'); if (!signupTabId) { - throw new Error('认证页面标签页已关闭,无法填写验证码。'); + throw new Error('Auth page tab was closed; cannot fill the verification code.'); } await chrome.tabs.update(signupTabId, { active: true }); @@ -1127,7 +1127,7 @@ } : options, step === 7 ? 45000 : 30000, - `填写${getVerificationCodeLabel(step)}验证码` + `Fill ${getVerificationCodeLabel(step)} verification code` ); const message = { type: 'FILL_CODE', @@ -1147,7 +1147,7 @@ timeoutMs: Math.max(baseResponseTimeoutMs + 15000, 30000), retryDelayMs: 700, responseTimeoutMs: baseResponseTimeoutMs, - logMessage: '认证页正在切换,等待页面重新就绪后继续确认验证码提交结果...', + logMessage: 'Auth page is switching, waiting for it to become ready again before confirming the verification code submission result...', logStep: completionStep, logStepKey: step === 4 ? 'fetch-signup-code' : 'fetch-login-code', }); @@ -1159,9 +1159,9 @@ }); if (fallback.success) { const fallbackLabel = fallback.reason === 'chatgpt_home' - ? 'ChatGPT 已登录首页' - : '注册资料页'; - await addLog(`步骤 4:验证码提交后页面已切换到${fallbackLabel},按提交成功继续。`, 'warn'); + ? 'ChatGPT logged-in home' + : 'signup profile page'; + await addLog(`Step 4: After verification code submission, the page switched to ${fallbackLabel}; treating as success and continuing.`, 'warn'); return { success: true, assumed: true, @@ -1179,12 +1179,12 @@ }); if (fallback.success) { if (fallback.addPhonePage) { - await addLog('验证码提交后通信中断,但页面已进入手机号验证页,按提交成功继续。', 'warn', { + await addLog('Communication was interrupted after verification code submission, but the page already entered the phone verification page; treating as success and continuing.', 'warn', { step: completionStep, stepKey: 'fetch-login-code', }); } else { - await addLog('验证码提交后通信中断,但页面已进入 OAuth 授权页,按提交成功继续。', 'warn', { + await addLog('Communication was interrupted after verification code submission, but the page already entered the OAuth consent page; treating as success and continuing.', 'warn', { step: completionStep, stepKey: 'fetch-login-code', }); @@ -1199,7 +1199,7 @@ } if (fallback.restartStep7) { const urlPart = fallback.url ? ` URL: ${fallback.url}` : ''; - throw new Error(`STEP8_RESTART_STEP7::步骤 ${completionStep}:验证码提交后认证页进入登录超时报错页,请回到步骤 ${authLoginStep} 重新开始。${urlPart}`.trim()); + throw new Error(`STEP8_RESTART_STEP7::Step ${completionStep}: After verification code submission, the auth page entered the login timeout error page. Please go back to step ${authLoginStep} and start again.${urlPart}`.trim()); } } throw err; @@ -1211,7 +1211,7 @@ }); } catch (err) { if (isRetryableVerificationTransportError(err)) { - await addLog('认证页正在切换,等待页面重新就绪后继续确认验证码提交结果...', 'warn', { + await addLog('Auth page is switching, waiting for it to become ready again before confirming the verification code submission result...', 'warn', { step: completionStep, stepKey: 'fetch-login-code', }); @@ -1223,18 +1223,18 @@ if (fallback.invalidCode) { return { invalidCode: true, - errorText: fallback.errorText || '验证码被拒绝。', + errorText: fallback.errorText || 'Verification code was rejected.', url: fallback.url || '', }; } if (fallback.success) { if (fallback.addPhonePage) { - await addLog('验证码提交后通信中断,但页面已进入手机号验证页,按提交成功继续。', 'warn', { + await addLog('Communication was interrupted after verification code submission, but the page already entered the phone verification page; treating as success and continuing.', 'warn', { step: completionStep, stepKey: 'fetch-login-code', }); } else { - await addLog('验证码提交后通信中断,但页面已进入 OAuth 授权页,按提交成功继续。', 'warn', { + await addLog('Communication was interrupted after verification code submission, but the page already entered the OAuth consent page; treating as success and continuing.', 'warn', { step: completionStep, stepKey: 'fetch-login-code', }); @@ -1249,7 +1249,7 @@ } if (fallback.restartStep7) { const urlPart = fallback.url ? ` URL: ${fallback.url}` : ''; - throw new Error(`STEP8_RESTART_STEP7::步骤 ${completionStep}:验证码提交后认证页进入登录超时报错页,请回到步骤 ${authLoginStep} 重新开始。${urlPart}`.trim()); + throw new Error(`STEP8_RESTART_STEP7::Step ${completionStep}: After verification code submission, the auth page entered the login timeout error page. Please go back to step ${authLoginStep} and start again.${urlPart}`.trim()); } } throw err; @@ -1320,18 +1320,18 @@ if (requestFreshCodeFirst) { if (remainingAutomaticResendCount <= 0) { - await addLog(`步骤 ${step}:当前自动重新发送验证码次数为 0,将直接使用当前时间窗口轮询邮箱。`, 'info'); + await addLog(`Step ${step}: Current auto-resend verification code count is 0; will poll the mailbox using the current time window directly.`, 'info'); } else { try { lastResendAt = await requestVerificationCodeResend(step, options); remainingAutomaticResendCount -= 1; await updateFilterAfterTimestampForVerificationStep(lastResendAt); - await addLog(`步骤 ${step}:已先请求一封新的${getVerificationCodeLabel(step)}验证码,再开始轮询邮箱。`, 'warn'); + await addLog(`Step ${step}: Requested a new ${getVerificationCodeLabel(step)} verification code first before polling the mailbox.`, 'warn'); } catch (err) { if (isStopError(err)) { throw err; } - await addLog(`步骤 ${step}:首次重新获取验证码失败:${err.message},将继续使用当前时间窗口轮询。`, 'warn'); + await addLog(`Step ${step}: First refresh of the verification code failed: ${err.message}. Will keep polling the current time window.`, 'warn'); } } } @@ -1342,12 +1342,12 @@ const remainingMs = await getRemainingTimeBudgetMs( step, options, - `等待${getVerificationCodeLabel(step)}验证码邮件到达` + `Wait for ${getVerificationCodeLabel(step)} verification code email to arrive` ); const delayMs = remainingMs === null ? initialDelayMs : Math.min(initialDelayMs, Math.max(0, remainingMs)); - await addLog(`步骤 ${step}:等待 ${Math.round(initialDelayMs / 1000)} 秒,让 Hotmail 验证码邮件先到达...`, 'info'); + await addLog(`Step ${step}: Waiting ${Math.round(initialDelayMs / 1000)} seconds so the Hotmail verification code email can arrive first...`, 'info'); await sleepWithStop(delayMs); } } @@ -1379,7 +1379,7 @@ ); throwIfStopped(); - await addLog(`步骤 ${step}:已获取${getVerificationCodeLabel(step)}验证码:${result.code}`); + await addLog(`Step ${step}: Got ${getVerificationCodeLabel(step)} verification code: ${result.code}`); if (beforeSubmit) { await beforeSubmit(result, { attempt, @@ -1393,27 +1393,27 @@ if (submitResult.invalidCode) { rejectedCodes.add(result.code); - await addLog(`步骤 ${step}:验证码被页面拒绝:${submitResult.errorText || result.code}`, 'warn'); + await addLog(`Step ${step}: Verification code was rejected by the page: ${submitResult.errorText || result.code}`, 'warn'); if (attempt >= maxSubmitAttempts) { - throw new Error(`步骤 ${step}:验证码连续失败,已达到 ${maxSubmitAttempts} 次重试上限。`); + throw new Error(`Step ${step}: Verification code failed repeatedly and reached the retry limit of ${maxSubmitAttempts}.`); } if (mail.provider === LUCKMAIL_PROVIDER) { - await addLog(`步骤 ${step}:LuckMail 验证码提交失败,等待 15 秒后重新轮询 /code 接口(${attempt + 1}/${maxSubmitAttempts})...`, 'warn'); + await addLog(`Step ${step}: LuckMail verification code submission failed; waiting 15 seconds before polling the /code endpoint again (${attempt + 1}/${maxSubmitAttempts})...`, 'warn'); await sleepWithStop(15000); continue; } if (remainingAutomaticResendCount <= 0) { - await addLog(`步骤 ${step}:已达到自动重新发送验证码次数上限,将排除已拒绝验证码并继续轮询新邮件。`, 'warn'); + await addLog(`Step ${step}: Reached the auto-resend verification code limit; will exclude the rejected codes and continue polling for new mail.`, 'warn'); continue; } lastResendAt = await requestVerificationCodeResend(step, options); remainingAutomaticResendCount -= 1; await updateFilterAfterTimestampForVerificationStep(lastResendAt); - await addLog(`步骤 ${step}:提交失败后已请求新验证码(${attempt + 1}/${maxSubmitAttempts})...`, 'warn'); + await addLog(`Step ${step}: Requested a new verification code after submission failure (${attempt + 1}/${maxSubmitAttempts})...`, 'warn'); continue; } @@ -1423,7 +1423,7 @@ }); if (!completionNodeId) { - throw new Error(`步骤 ${completionStep} 未映射到验证码节点。`); + throw new Error(`Step ${completionStep} is not mapped to a verification code node.`); } await completeNodeFromBackground(completionNodeId, { emailTimestamp: result.emailTimestamp, diff --git a/background/yyds-mail-provider.js b/background/yyds-mail-provider.js index caed454f..8c73a4f2 100644 --- a/background/yyds-mail-provider.js +++ b/background/yyds-mail-provider.js @@ -45,20 +45,20 @@ const { requireApiKey = false, requireInbox = false } = options; const config = getYydsMailConfig(state); if (!config.baseUrl) { - throw new Error('YYDS Mail API 地址为空或格式无效。'); + throw new Error('YYDS Mail API base URL is empty or invalid.'); } if (requireApiKey && !config.apiKey) { - throw new Error('YYDS Mail API Key 为空,请先在侧边栏填写。'); + throw new Error('YYDS Mail API key is empty. Please fill it in the side panel first.'); } if (requireInbox && (!config.currentInbox?.address || !config.currentInbox?.token)) { - throw new Error('YYDS Mail 当前没有可用邮箱,请先获取邮箱。'); + throw new Error('YYDS Mail has no available mailbox right now. Please request one first.'); } return config; } async function requestYydsMailJson(config, path, options = {}) { if (!fetchImpl) { - throw new Error('YYDS Mail 当前运行环境不支持 fetch。'); + throw new Error('YYDS Mail is not supported in this runtime (no fetch).'); } const { method = 'GET', @@ -85,8 +85,8 @@ }); } catch (err) { const errorMessage = err?.name === 'AbortError' - ? `YYDS Mail 请求超时(>${Math.round(timeoutMs / 1000)} 秒)` - : `YYDS Mail 请求失败:${err.message}`; + ? `YYDS Mail request timed out (>${Math.round(timeoutMs / 1000)} seconds)` + : `YYDS Mail request failed: ${err.message}`; throw new Error(errorMessage); } finally { clearTimeout(timeoutId); @@ -104,11 +104,11 @@ const payloadError = parsed && typeof parsed === 'object' ? (parsed.error || parsed.message || parsed.msg || parsed.errorCode) : ''; - throw new Error(`YYDS Mail 请求失败:${payloadError || text || `HTTP ${response.status}`}`); + throw new Error(`YYDS Mail request failed: ${payloadError || text || `HTTP ${response.status}`}`); } if (parsed && typeof parsed === 'object' && parsed.success === false) { - throw new Error(`YYDS Mail 业务错误:${parsed.error || parsed.message || parsed.errorCode || 'unknown_error'}`); + throw new Error(`YYDS Mail business error: ${parsed.error || parsed.message || parsed.errorCode || 'unknown_error'}`); } if (parsed && typeof parsed === 'object' && Object.prototype.hasOwnProperty.call(parsed, 'data')) { @@ -143,7 +143,7 @@ }); const inbox = normalizeYydsMailInbox(data); if (!inbox.address || !inbox.token) { - throw new Error('YYDS Mail 创建邮箱成功,但未返回可用 address/token。'); + throw new Error('YYDS Mail mailbox creation succeeded but did not return a usable address/token.'); } await setState({ currentYydsMailInbox: inbox }); @@ -151,7 +151,7 @@ source: `generated:${YYDS_MAIL_PROVIDER}`, preserveAccountIdentity: Boolean(options?.preserveAccountIdentity), }); - await addLog(`YYDS Mail:已创建邮箱 ${inbox.address}`, 'ok'); + await addLog(`YYDS Mail: Created mailbox ${inbox.address}`, 'ok'); return inbox.address; } @@ -217,9 +217,9 @@ }) .slice(0, 3) .map((message) => { - const receivedAt = message?.receivedDateTime || '未知时间'; - const sender = message?.from?.emailAddress?.address || '未知发件人'; - const subject = message?.subject || '(无主题)'; + const receivedAt = message?.receivedDateTime || 'unknown time'; + const sender = message?.from?.emailAddress?.address || 'unknown sender'; + const subject = message?.subject || '(no subject)'; const preview = String(message?.bodyPreview || '').replace(/\s+/g, ' ').trim().slice(0, 80); return `${receivedAt} | ${sender} | ${subject} | ${preview}`; }) @@ -237,7 +237,7 @@ try { details.push(await getYydsMailMessageDetail(state, message.id, { address })); } catch (err) { - await addLog(`YYDS Mail:读取邮件详情 ${message.id} 失败:${err.message}`, 'warn'); + await addLog(`YYDS Mail: Failed to read message detail ${message.id}: ${err.message}`, 'warn'); details.push(message); } } @@ -248,10 +248,10 @@ const latestState = state || await getState(); const targetEmail = resolveYydsMailPollTargetEmail(latestState, pollPayload); if (!targetEmail) { - throw new Error('YYDS Mail 轮询前缺少目标邮箱地址,请先获取邮箱。'); + throw new Error('YYDS Mail is missing a target email address before polling. Please request a mailbox first.'); } - await addLog(`步骤 ${step}:正在轮询 YYDS Mail 邮件(${targetEmail})...`, 'info'); + await addLog(`Step ${step}: Polling YYDS Mail messages (${targetEmail})...`, 'info'); const maxAttempts = Number(pollPayload.maxAttempts) || 5; const intervalMs = Number(pollPayload.intervalMs) || 3000; let lastError = null; @@ -275,8 +275,8 @@ const match = matchResult.match; if (match?.code) { if (matchResult.usedRelaxedFilters) { - const fallbackLabel = matchResult.usedTimeFallback ? '宽松匹配 + 时间回退' : '宽松匹配'; - await addLog(`步骤 ${step}:严格规则未命中,已改用 ${fallbackLabel} 并命中 YYDS Mail 验证码。`, 'warn'); + const fallbackLabel = matchResult.usedTimeFallback ? 'relaxed match + time fallback' : 'relaxed match'; + await addLog(`Step ${step}: Strict rules did not match; switched to ${fallbackLabel} and matched a YYDS Mail verification code.`, 'warn'); } return { ok: true, @@ -286,22 +286,22 @@ }; } - lastError = new Error(`步骤 ${step}:暂未在 YYDS Mail 中找到匹配验证码(${attempt}/${maxAttempts})。`); + lastError = new Error(`Step ${step}: No matching verification code found in YYDS Mail yet (${attempt}/${maxAttempts}).`); await addLog(lastError.message, attempt === maxAttempts ? 'warn' : 'info'); const sample = summarizeYydsMailMessagesForLog(detailedMessages.length ? detailedMessages : messages); if (sample) { - await addLog(`步骤 ${step}:最近邮件样本:${sample}`, 'info'); + await addLog(`Step ${step}: Recent message sample: ${sample}`, 'info'); } } catch (err) { lastError = err; - await addLog(`步骤 ${step}:YYDS Mail 轮询失败:${err.message}`, 'warn'); + await addLog(`Step ${step}: YYDS Mail polling failed: ${err.message}`, 'warn'); } if (attempt < maxAttempts) { await sleepWithStop(intervalMs); } } - throw lastError || new Error(`步骤 ${step}:未在 YYDS Mail 中找到新的匹配验证码。`); + throw lastError || new Error(`Step ${step}: No new matching verification code found in YYDS Mail.`); } async function clearYydsMailRuntimeState(options = {}) { diff --git a/content/duck-mail.js b/content/duck-mail.js index 3a40cda5..ef5aa709 100644 --- a/content/duck-mail.js +++ b/content/duck-mail.js @@ -10,7 +10,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { sendResponse(result); }).catch((err) => { if (isStopError(err)) { - log('Duck 邮箱:已被用户停止。', 'warn'); + log('Duck Mail: Stopped by user.', 'warn'); sendResponse({ stopped: true, error: err.message }); return; } @@ -26,7 +26,7 @@ async function fetchDuckEmail(payload = {}) { baselineEmail = '', } = payload; - log(`Duck 邮箱:正在${generateNew ? '生成' : '读取'}私有地址...`); + log(`Duck Mail: ${generateNew ? 'Generating' : 'Reading'} private address...`); await waitForElement( 'input.AutofillSettingsPanel__PrivateDuckAddressValue, button.AutofillSettingsPanel__GeneratorButton', @@ -140,7 +140,7 @@ async function fetchDuckEmail(payload = {}) { } await sleep(150); } - throw new Error('等待 Duck 地址变化超时。'); + throw new Error('Timed out waiting for Duck address to change.'); }; const knownBaselineEmail = normalizeDuckEmail(baselineEmail); @@ -150,25 +150,25 @@ async function fetchDuckEmail(payload = {}) { } if (currentEmail && !generateNew) { - log(`Duck 邮箱:已发现现有地址 ${currentEmail}`); + log(`Duck Mail: Found existing address ${currentEmail}`); return { email: currentEmail, generated: false }; } const generatorButton = getGeneratorButton(); if (!generatorButton) { if (generateNew) { - throw new Error('未找到“生成新 Duck 地址”按钮(可能是页面结构、文案或登录状态发生变化)。'); + throw new Error('"Generate new Duck address" button not found (page structure, copy, or login state may have changed).'); } if (currentEmail) { - log(`Duck 邮箱:未找到生成按钮,复用现有地址 ${currentEmail}`, 'warn'); + log(`Duck Mail: Generate button not found, reusing existing address ${currentEmail}`, 'warn'); return { email: currentEmail, generated: false }; } - throw new Error('未找到 Duck 私有地址生成按钮。'); + throw new Error('Duck private address generate button not found.'); } const comparisonEmails = [currentEmail, knownBaselineEmail].filter(Boolean); if (!currentEmail && knownBaselineEmail) { - log(`Duck 邮箱:当前地址尚未显示,改用已知基线 ${knownBaselineEmail} 作为对比基线。`, 'warn'); + log(`Duck Mail: Current address not yet displayed, using known baseline ${knownBaselineEmail} as comparison baseline.`, 'warn'); } for (let attempt = 1; attempt <= 2; attempt++) { @@ -187,20 +187,20 @@ async function fetchDuckEmail(payload = {}) { } } ); - log(`Duck 邮箱:已点击“生成 Duck 私有地址”按钮(${attempt}/2)`); + log(`Duck Mail: Clicked "Generate Duck Private Address" button (${attempt}/2)`); try { const nextEmail = await waitForEmailValue(comparisonEmails); - log(`Duck 邮箱:地址已就绪 ${nextEmail}`, 'ok'); + log(`Duck Mail: Address ready ${nextEmail}`, 'ok'); return { email: nextEmail, generated: true }; } catch (err) { if (attempt >= 2) { throw err; } - log('Duck 邮箱:首次生成后地址未变化,准备再试一次...', 'warn'); + log('Duck Mail: Address did not change after first generation, retrying...', 'warn'); await sleep(800); } } - throw new Error('Duck 地址生成失败。'); + throw new Error('Failed to generate Duck address.'); } diff --git a/content/gmail-mail.js b/content/gmail-mail.js index f9eb84c2..a33c220c 100644 --- a/content/gmail-mail.js +++ b/content/gmail-mail.js @@ -43,11 +43,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { sendResponse(result); }).catch((err) => { if (isStopError(err)) { - log(`步骤 ${message.step}:已被用户停止。`, 'warn'); + log(`Step ${message.step}: Stopped by user.`, 'warn'); sendResponse({ stopped: true, error: err.message }); return; } - log(`步骤 ${message.step}:Gmail 轮询失败:${err.message}`, 'warn'); + log(`Step ${message.step}: Gmail polling failed: ${err.message}`, 'warn'); sendResponse({ error: err.message }); }); return true; @@ -327,13 +327,13 @@ async function activateCategoryTab(step, categoryKey) { const refreshed = collectCategoryTabs().find((item) => item.key === categoryKey); if (refreshed?.selected) { await sleep(500); - log(`步骤 ${step}:已切换到 Gmail 分类 ${refreshed.label}。`); + log(`Step ${step}: Switched to Gmail category ${refreshed.label}.`); return { key: refreshed.key, label: refreshed.label, switched: true }; } } await sleep(600); - log(`步骤 ${step}:已尝试切换到 Gmail 分类 ${target.label}。`, 'info'); + log(`Step ${step}: Attempted to switch to Gmail category ${target.label}.`, 'info'); return { key: target.key, label: target.label, switched: true }; } @@ -499,7 +499,7 @@ async function ensureInboxReady(step) { if (inboxLink) { simulateClick(inboxLink); await sleep(800); - log(`步骤 ${step}:已切回 Gmail 收件箱。`); + log(`Step ${step}: Switched back to Gmail inbox.`); } else { location.hash = '#inbox'; await sleep(800); @@ -521,7 +521,7 @@ async function refreshInbox(step) { const refreshButton = findRefreshButton(); if (refreshButton) { simulateClick(refreshButton); - log(`步骤 ${step}:已点击 Gmail 刷新。`); + log(`Step ${step}: Clicked Gmail refresh.`); await sleep(1500); return; } @@ -529,13 +529,13 @@ async function refreshInbox(step) { const inboxLink = findInboxLink(); if (inboxLink) { simulateClick(inboxLink); - log(`步骤 ${step}:未找到刷新按钮,已重新进入收件箱。`); + log(`Step ${step}: Refresh button not found, re-entered inbox.`); await sleep(1200); return; } location.reload(); - log(`步骤 ${step}:未找到刷新按钮,已直接刷新页面。`); + log(`Step ${step}: Refresh button not found, reloaded page directly.`); await sleep(2500); } @@ -592,9 +592,9 @@ async function handlePollEmail(step, payload) { const excludedCodeSet = new Set(excludeCodes.filter(Boolean)); const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0); - log(`步骤 ${step}:开始轮询 Gmail(最多 ${maxAttempts} 次)`); + log(`Step ${step}: Starting Gmail polling (max ${maxAttempts} attempts)`); if (filterAfterMinute) { - log(`步骤 ${step}:仅尝试 ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} 及之后时间的邮件。`); + log(`Step ${step}: Only attempting emails from ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} onward.`); } let initialRows = await ensureInboxReady(step); @@ -604,7 +604,7 @@ async function handlePollEmail(step, payload) { } if (!initialRows.length) { - throw new Error('Gmail 收件箱列表未加载完成,请确认当前已打开 Gmail 收件箱。'); + throw new Error('Gmail inbox list did not finish loading. Please confirm Gmail inbox is open.'); } const categoryOrder = getCategoryScanOrder(); @@ -614,11 +614,11 @@ async function handlePollEmail(step, payload) { const activeCategory = await activateCategoryTab(step, category.key); const rows = collectThreadRows(); existingMailIdsByCategory.set(activeCategory.key, getCurrentMailIds(rows)); - log(`步骤 ${step}:已记录 Gmail 分类 ${activeCategory.label} 的 ${rows.length} 封旧邮件快照`); + log(`Step ${step}: Recorded snapshot of ${rows.length} old emails in Gmail category ${activeCategory.label}`); } for (let attempt = 1; attempt <= maxAttempts; attempt++) { - log(`步骤 ${step}:正在轮询 Gmail,第 ${attempt}/${maxAttempts} 次`); + log(`Step ${step}: Polling Gmail, attempt ${attempt}/${maxAttempts}`); if (attempt > 1) { await refreshInbox(step); @@ -658,19 +658,19 @@ async function handlePollEmail(step, payload) { }); if (previewCode) { if (excludedCodeSet.has(previewCode)) { - log(`步骤 ${step}:跳过排除的验证码:${previewCode}`, 'info'); + log(`Step ${step}: Skipping excluded verification code: ${previewCode}`, 'info'); continue; } if (seenCodes.has(previewCode)) { - log(`步骤 ${step}:跳过已处理过的验证码:${previewCode}`, 'info'); + log(`Step ${step}: Skipping already processed verification code: ${previewCode}`, 'info'); continue; } seenCodes.add(previewCode); persistSeenCodes(); - const source = useFallback && existingMailIds.has(rowId) ? '回退匹配邮件' : '新邮件'; - const timeLabel = rowTimestamp ? `,时间:${new Date(rowTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; - const targetLabel = previewTargetState.matches ? ',目标邮箱命中' : ''; - log(`步骤 ${step}:已在 Gmail ${activeCategory.label} 分类找到验证码:${previewCode}(来源:${source}${timeLabel}${targetLabel})`, 'ok'); + const source = useFallback && existingMailIds.has(rowId) ? 'fallback-matched email' : 'new email'; + const timeLabel = rowTimestamp ? `, time: ${new Date(rowTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; + const targetLabel = previewTargetState.matches ? ', target email hit' : ''; + log(`Step ${step}: Found verification code in Gmail ${activeCategory.label} category: ${previewCode} (source: ${source}${timeLabel}${targetLabel})`, 'ok'); return { ok: true, code: previewCode, @@ -688,19 +688,19 @@ async function handlePollEmail(step, payload) { continue; } if (excludedCodeSet.has(bodyCode)) { - log(`步骤 ${step}:跳过排除的验证码:${bodyCode}`, 'info'); + log(`Step ${step}: Skipping excluded verification code: ${bodyCode}`, 'info'); continue; } if (seenCodes.has(bodyCode)) { - log(`步骤 ${step}:跳过已处理过的验证码:${bodyCode}`, 'info'); + log(`Step ${step}: Skipping already processed verification code: ${bodyCode}`, 'info'); continue; } seenCodes.add(bodyCode); persistSeenCodes(); - const source = useFallback && existingMailIds.has(rowId) ? '回退匹配邮件正文' : '新邮件正文'; - const timeLabel = rowTimestamp ? `,时间:${new Date(rowTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; - const targetLabel = openedTargetState.matches ? ',目标邮箱命中' : ''; - log(`步骤 ${step}:已在 Gmail ${activeCategory.label} 分类正文中找到验证码:${bodyCode}(来源:${source}${timeLabel}${targetLabel})`, 'ok'); + const source = useFallback && existingMailIds.has(rowId) ? 'fallback-matched email body' : 'new email body'; + const timeLabel = rowTimestamp ? `, time: ${new Date(rowTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; + const targetLabel = openedTargetState.matches ? ', target email hit' : ''; + log(`Step ${step}: Found verification code in Gmail ${activeCategory.label} category body: ${bodyCode} (source: ${source}${timeLabel}${targetLabel})`, 'ok'); return { ok: true, code: bodyCode, @@ -711,7 +711,7 @@ async function handlePollEmail(step, payload) { } if (attempt === GMAIL_FALLBACK_AFTER + 1) { - log(`步骤 ${step}:连续 ${GMAIL_FALLBACK_AFTER} 次未发现新邮件,开始回退到首封匹配邮件`, 'warn'); + log(`Step ${step}: ${GMAIL_FALLBACK_AFTER} consecutive attempts with no new emails, starting fallback to first matched email`, 'warn'); } if (attempt < maxAttempts) { @@ -720,7 +720,7 @@ async function handlePollEmail(step, payload) { } throw new Error( - `${(maxAttempts * intervalMs / 1000).toFixed(0)} 秒后仍未在 Gmail 中找到匹配邮件。请手动检查 Gmail 收件箱。` + `No matching email found in Gmail after ${(maxAttempts * intervalMs / 1000).toFixed(0)} seconds. Please manually check Gmail inbox.` ); } diff --git a/content/icloud-mail.js b/content/icloud-mail.js index a3e5ab37..17fe166e 100644 --- a/content/icloud-mail.js +++ b/content/icloud-mail.js @@ -28,11 +28,11 @@ if (shouldHandlePollEmailInCurrentFrame) { sendResponse(result); }).catch((err) => { if (isStopError(err)) { - log(`步骤 ${message.step}:已被用户停止。`, 'warn'); + log(`Step ${message.step}: Stopped by user.`, 'warn'); sendResponse({ stopped: true, error: err.message }); return; } - log(`步骤 ${message.step}:iCloud 邮箱轮询失败:${err.message}`, 'warn'); + log(`Step ${message.step}: iCloud mail polling failed: ${err.message}`, 'warn'); sendResponse({ error: err.message }); }); return true; @@ -215,7 +215,7 @@ if (shouldHandlePollEmailInCurrentFrame) { } await sleep(100); } - throw new Error('打开邮件后未找到详情区域,请确认邮件内容已加载。'); + throw new Error('Did not find email detail area after opening mail. Please confirm email content has loaded.'); } async function openMailItemAndRead(item) { @@ -326,7 +326,7 @@ if (shouldHandlePollEmailInCurrentFrame) { const normalizedSenderFilters = senderFilters.map((filter) => String(filter || '').toLowerCase()).filter(Boolean); const normalizedSubjectFilters = subjectFilters.map((filter) => String(filter || '').toLowerCase()).filter(Boolean); - log(`步骤 ${step}:开始轮询 iCloud 邮箱(最多 ${maxAttempts} 次)`); + log(`Step ${step}: Starting iCloud mail polling (max ${maxAttempts} attempts)`); await waitForElement('.content-container', 10000); await sleep(1500); const currentItems = collectThreadItems(); @@ -334,13 +334,13 @@ if (shouldHandlePollEmailInCurrentFrame) { const existingSignatures = sessionBaseline.signatures; let fallbackCarry = sessionBaseline.fallbackCarry; if (sessionBaseline.fromCache) { - log(`步骤 ${step}:已复用当前会话旧邮件快照(${existingSignatures.size} 封)。`); + log(`Step ${step}: Reusing current session's old email snapshot (${existingSignatures.size} emails).`); } else { - log(`步骤 ${step}:已记录当前 ${existingSignatures.size} 封旧邮件快照`); + log(`Step ${step}: Recorded snapshot of current ${existingSignatures.size} old emails`); } for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { - log(`步骤 ${step}:正在轮询 iCloud 邮箱,第 ${attempt}/${maxAttempts} 次`); + log(`Step ${step}: Polling iCloud mail, attempt ${attempt}/${maxAttempts}`); if (attempt > 1) { await refreshInbox(); @@ -389,12 +389,12 @@ if (shouldHandlePollEmailInCurrentFrame) { continue; } if (excludedCodeSet.has(code)) { - log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info'); + log(`Step ${step}: Skipping excluded verification code: ${code}`, 'info'); continue; } - const source = useFallback && existingSignatures.has(signature) ? '回退匹配邮件' : '新邮件'; - log(`步骤 ${step}:已找到验证码:${code}(来源:${source})`, 'ok'); + const source = useFallback && existingSignatures.has(signature) ? 'fallback-matched email' : 'new email'; + log(`Step ${step}: Found verification code: ${code} (source: ${source})`, 'ok'); persistPollSessionBaseline( pollSessionKey, new Set(collectThreadItems().map(buildItemSignature)), @@ -409,7 +409,7 @@ if (shouldHandlePollEmailInCurrentFrame) { } if (attempt === FALLBACK_AFTER + 1) { - log(`步骤 ${step}:连续 ${FALLBACK_AFTER} 次未发现新邮件,开始回退到首封匹配邮件`, 'warn'); + log(`Step ${step}: ${FALLBACK_AFTER} consecutive attempts with no new emails, starting fallback to first matched email`, 'warn'); } if (attempt < maxAttempts) { @@ -425,7 +425,7 @@ if (shouldHandlePollEmailInCurrentFrame) { ); throw new Error( - `${Math.round((maxAttempts * intervalMs) / 1000)} 秒后仍未在 iCloud 邮箱中找到新的匹配邮件。请手动检查收件箱。` + `No new matching email found in iCloud mail after ${Math.round((maxAttempts * intervalMs) / 1000)} seconds. Please manually check the inbox.` ); } } diff --git a/content/inbucket-mail.js b/content/inbucket-mail.js index accdc5be..c6baa691 100644 --- a/content/inbucket-mail.js +++ b/content/inbucket-mail.js @@ -45,11 +45,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { sendResponse(result); }).catch(err => { if (isStopError(err)) { - log(`步骤 ${message.step}:已被用户停止。`, 'warn'); + log(`Step ${message.step}: Stopped by user.`, 'warn'); sendResponse({ stopped: true, error: err.message }); return; } - log(`步骤 ${message.step}:邮箱轮询失败:${err.message}`, 'warn'); + log(`Step ${message.step}: Mailbox polling failed: ${err.message}`, 'warn'); sendResponse({ error: err.message }); }); return true; @@ -208,10 +208,10 @@ async function deleteCurrentMailboxMessage(step) { try { const deleteButton = await waitForElement('.button-bar button.danger', 5000); simulateClick(deleteButton); - log(`步骤 ${step}:已删除邮箱消息`, 'ok'); + log(`Step ${step}: Deleted mailbox message`, 'ok'); await sleep(1200); } catch (err) { - log(`步骤 ${step}:删除邮箱消息失败:${err.message}`, 'warn'); + log(`Step ${step}: Failed to delete mailbox message: ${err.message}`, 'warn'); } } @@ -227,22 +227,22 @@ async function handleMailboxPollEmail(step, payload) { } = payload || {}; const excludedCodeSet = new Set(excludeCodes.filter(Boolean)); - log(`步骤 ${step}:开始轮询 Inbucket 邮箱页面(最多 ${maxAttempts} 次)`); + log(`Step ${step}: Starting Inbucket mailbox page polling (max ${maxAttempts} attempts)`); try { await waitForElement('.message-list, .message-list-entry', 15000); - log(`步骤 ${step}:邮箱页面已加载`); + log(`Step ${step}: Mailbox page loaded`); } catch { - throw new Error('Inbucket 邮箱页面未加载完成,请确认已打开 /m// 页面。'); + throw new Error('Inbucket mailbox page did not finish loading. Please confirm /m// page is open.'); } const existingMailIds = getCurrentMailboxIds(); - log(`步骤 ${step}:已记录当前 ${existingMailIds.size} 封旧消息快照`); + log(`Step ${step}: Recorded snapshot of current ${existingMailIds.size} old messages`); const FALLBACK_AFTER = 3; for (let attempt = 1; attempt <= maxAttempts; attempt++) { - log(`步骤 ${step}:正在轮询 Inbucket 邮箱,第 ${attempt}/${maxAttempts} 次`); + log(`Step ${step}: Polling Inbucket mailbox, attempt ${attempt}/${maxAttempts}`); if (attempt > 1) { await refreshMailbox(); @@ -272,7 +272,7 @@ async function handleMailboxPollEmail(step, payload) { }); if (!code) continue; if (excludedCodeSet.has(code)) { - log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info'); + log(`Step ${step}: Skipping excluded verification code: ${code}`, 'info'); continue; } @@ -282,9 +282,9 @@ async function handleMailboxPollEmail(step, payload) { seenMailIds.add(mail.mailId); await persistSeenMailIds(); - const source = existingMailIds.has(mail.mailId) ? '回退匹配邮件' : '新邮件'; + const source = existingMailIds.has(mail.mailId) ? 'fallback-matched email' : 'new email'; log( - `步骤 ${step}:已找到验证码:${code}(来源:${source},发件人:${mail.sender || '未知'},主题:${(mail.subject || '').slice(0, 60)})`, + `Step ${step}: Found verification code: ${code} (source: ${source}, sender: ${mail.sender || 'unknown'}, subject: ${(mail.subject || '').slice(0, 60)})`, 'ok' ); @@ -297,7 +297,7 @@ async function handleMailboxPollEmail(step, payload) { } if (attempt === FALLBACK_AFTER + 1) { - log(`步骤 ${step}:暂未发现新消息,开始回退到较早的匹配邮件`, 'warn'); + log(`Step ${step}: No new messages yet, starting fallback to earlier matched emails`, 'warn'); } if (attempt < maxAttempts) { @@ -306,14 +306,14 @@ async function handleMailboxPollEmail(step, payload) { } throw new Error( - `${(maxAttempts * intervalMs / 1000).toFixed(0)} 秒后仍未在 Inbucket 邮箱中找到匹配的验证码邮件。` + - '请手动检查邮箱页面。' + `No matching verification code email found in Inbucket mailbox after ${(maxAttempts * intervalMs / 1000).toFixed(0)} seconds. ` + + 'Please manually check the mailbox page.' ); } async function handlePollEmail(step, payload) { if (!location.pathname.startsWith('/m/')) { - throw new Error('当前 Inbucket 仅支持 /m// 这种邮箱页面。'); + throw new Error('Current Inbucket only supports the /m// mailbox page.'); } return handleMailboxPollEmail(step, payload); } diff --git a/content/mail-163.js b/content/mail-163.js index 95a4bf47..4ffac673 100644 --- a/content/mail-163.js +++ b/content/mail-163.js @@ -2,9 +2,9 @@ // Injected on: mail.163.com // // DOM structure: -// Mail item: div[sign="letter"] with aria-label="你的 ChatGPT 代码为 479637 发件人 : OpenAI ..." +// Mail item: div[sign="letter"] with aria-label="Your ChatGPT code is 479637 sender: OpenAI ..." // Sender: .nui-user (e.g., "OpenAI") -// Subject: span.da0 (e.g., "你的 ChatGPT 代码为 479637") +// Subject: span.da0 (e.g., "Your ChatGPT code is 479637") // Delete actions: hover trash icon on the row, or checkbox + toolbar delete button const MAIL163_PREFIX = '[MultiPage:mail-163]'; @@ -54,11 +54,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { sendResponse(result); }).catch(err => { if (isStopError(err)) { - log(`步骤 ${message.step}:已被用户停止。`, 'warn'); + log(`Step ${message.step}: Stopped by user.`, 'warn'); sendResponse({ stopped: true, error: err.message }); return; } - log(`步骤 ${message.step}:邮箱轮询失败:${err.message}`, 'warn'); + log(`Step ${message.step}: Mailbox polling failed: ${err.message}`, 'warn'); sendResponse({ error: err.message }); }); return true; @@ -112,13 +112,13 @@ function getNetEaseMailLabel(hostname) { ).toLowerCase(); if (currentHostname === 'mail.126.com' || currentHostname.endsWith('.mail.126.com')) { - return '126 邮箱'; + return '126 Mail'; } if (currentHostname === 'webmail.vip.163.com') { - return '163 VIP 邮箱'; + return '163 VIP Mail'; } - return '163 邮箱'; + return '163 Mail'; } function isVisibleNode(node) { @@ -537,23 +537,23 @@ async function handlePollEmail(step, payload) { const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0); const mailLabel = getNetEaseMailLabel(); - log(`步骤 ${step}:开始轮询 ${mailLabel}(最多 ${maxAttempts} 次)`); + log(`Step ${step}: Starting polling of ${mailLabel} (max ${maxAttempts} attempts)`); if (filterAfterMinute) { - log(`步骤 ${step}:仅尝试 ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} 及之后时间的邮件。`); + log(`Step ${step}: Only attempting emails from ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} onward.`); } // Click inbox in sidebar to ensure we're in inbox view - log(`步骤 ${step}:正在等待侧边栏加载...`); + log(`Step ${step}: Waiting for sidebar to load...`); try { const inboxLink = await waitForElement('.nui-tree-item-text[title="收件箱"]', 5000); inboxLink.click(); - log(`步骤 ${step}:已点击收件箱`); + log(`Step ${step}: Clicked inbox`); } catch { - log(`步骤 ${step}:未找到收件箱入口,继续尝试后续流程...`, 'warn'); + log(`Step ${step}: Inbox entry not found, continuing with subsequent flow...`, 'warn'); } // Wait for mail list to appear - log(`步骤 ${step}:正在等待邮件列表加载...`); + log(`Step ${step}: Waiting for mail list to load...`); let items = []; for (let i = 0; i < 20; i++) { items = findMailItems(); @@ -568,19 +568,19 @@ async function handlePollEmail(step, payload) { } if (items.length === 0) { - throw new Error(`${mailLabel}列表未加载完成,请确认当前已打开收件箱。`); + throw new Error(`${mailLabel} list did not finish loading. Please confirm inbox is open.`); } - log(`步骤 ${step}:邮件列表已加载,共 ${items.length} 封邮件`); + log(`Step ${step}: Mail list loaded, ${items.length} emails total`); // Snapshot existing mail IDs const existingMailIds = getCurrentMailIds(items); - log(`步骤 ${step}:已记录当前 ${existingMailIds.size} 封旧邮件快照`); + log(`Step ${step}: Recorded snapshot of current ${existingMailIds.size} old emails`); const FALLBACK_AFTER = 3; for (let attempt = 1; attempt <= maxAttempts; attempt++) { - log(`步骤 ${step}:正在轮询 ${mailLabel},第 ${attempt}/${maxAttempts} 次`); + log(`Step ${step}: Polling ${mailLabel}, attempt ${attempt}/${maxAttempts}`); if (attempt > 1) { await refreshInbox(); @@ -612,7 +612,7 @@ async function handlePollEmail(step, payload) { const combinedText = normalizeText([subject, ariaLabel, rowText].filter(Boolean).join(' ')); if (!mailTimestamp) { - log(`步骤 ${step}:邮件 ${id.slice(0, 60)} 未读取到时间,已跳过时间窗口校验后的文本匹配阶段。`, 'info'); + log(`Step ${step}: Email ${id.slice(0, 60)} did not yield a timestamp, skipped text matching phase after time window check.`, 'info'); } const senderMatch = senderFilters.some((filter) => { @@ -626,37 +626,37 @@ async function handlePollEmail(step, payload) { if (senderMatch || subjectMatch) { let code = extractVerificationCode(combinedText, { codePatterns }); - let codeSource = '邮件列表'; + let codeSource = 'mail list'; if (!code) { const openedText = await openMailAndGetMessageText(item, { codePatterns }); code = extractVerificationCode(openedText, { codePatterns }); if (code) { - codeSource = '邮件正文'; + codeSource = 'mail body'; } } if (code && excludedCodeSet.has(code)) { - log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info'); + log(`Step ${step}: Skipping excluded verification code: ${code}`, 'info'); } else if (code && !seenCodes.has(code)) { seenCodes.add(code); persistSeenCodes(); - const source = useFallback && existingMailIds.has(id) ? `回退匹配${codeSource}` : `新邮件${codeSource}`; - const timeLabel = mailTimestamp ? `,时间:${new Date(mailTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; - log(`步骤 ${step}:已找到验证码:${code}(来源:${source}${timeLabel},主题:${subject.slice(0, 40)})`, 'ok'); + const source = useFallback && existingMailIds.has(id) ? `fallback-matched ${codeSource}` : `new email ${codeSource}`; + const timeLabel = mailTimestamp ? `, time: ${new Date(mailTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; + log(`Step ${step}: Found verification code: ${code} (source: ${source}${timeLabel}, subject: ${subject.slice(0, 40)})`, 'ok'); // Trigger cleanup only as a best-effort side effect. scheduleEmailCleanup(item, step); return { ok: true, code, emailTimestamp: Date.now(), mailId: id }; } else if (code && seenCodes.has(code)) { - log(`步骤 ${step}:跳过已处理过的验证码:${code}`, 'info'); + log(`Step ${step}: Skipping already processed verification code: ${code}`, 'info'); } } } if (attempt === FALLBACK_AFTER + 1) { - log(`步骤 ${step}:连续 ${FALLBACK_AFTER} 次未发现新邮件,开始回退到首封匹配邮件`, 'warn'); + log(`Step ${step}: ${FALLBACK_AFTER} consecutive attempts with no new emails, starting fallback to first matched email`, 'warn'); } if (attempt < maxAttempts) { @@ -665,8 +665,8 @@ async function handlePollEmail(step, payload) { } throw new Error( - `${(maxAttempts * intervalMs / 1000).toFixed(0)} 秒后仍未在 ${mailLabel}中找到新的匹配邮件。` + - '请手动检查收件箱。' + `No new matching email found in ${mailLabel} after ${(maxAttempts * intervalMs / 1000).toFixed(0)} seconds. ` + + 'Please manually check the inbox.' ); } @@ -676,7 +676,7 @@ async function handlePollEmail(step, payload) { async function deleteEmail(item, step) { try { - log(`步骤 ${step}:正在删除邮件...`); + log(`Step ${step}: Deleting email...`); // Strategy 1: Click the trash icon inside the mail item // Each mail item has: @@ -688,21 +688,21 @@ async function deleteEmail(item, step) { const trashIcon = item.querySelector('[sign="trash"], .nui-ico-delete, [title="删除邮件"]'); if (trashIcon) { trashIcon.click(); - log(`步骤 ${step}:已点击删除图标`, 'ok'); + log(`Step ${step}: Clicked delete icon`, 'ok'); await sleep(1500); // Check if item disappeared (confirm deletion) const stillExists = document.getElementById(item.id); if (!stillExists || stillExists.style.display === 'none') { - log(`步骤 ${step}:邮件已成功删除`); + log(`Step ${step}: Email deleted successfully`); } else { - log(`步骤 ${step}:邮件可能尚未删除,列表中仍可见`, 'warn'); + log(`Step ${step}: Email may not be deleted, still visible in list`, 'warn'); } return; } // Strategy 2: Select checkbox then click toolbar delete button - log(`步骤 ${step}:未找到删除图标,尝试使用复选框加工具栏删除...`); + log(`Step ${step}: Delete icon not found, trying checkbox + toolbar delete...`); const checkbox = item.querySelector('[sign="checkbox"], .nui-chk'); if (checkbox) { checkbox.click(); @@ -713,16 +713,16 @@ async function deleteEmail(item, step) { for (const btn of toolbarBtns) { if (btn.textContent.replace(/\s/g, '').includes('删除')) { btn.closest('.nui-btn').click(); - log(`步骤 ${step}:已点击工具栏删除`, 'ok'); + log(`Step ${step}: Clicked toolbar delete`, 'ok'); await sleep(1500); return; } } } - log(`步骤 ${step}:无法删除邮件(未找到删除按钮)`, 'warn'); + log(`Step ${step}: Unable to delete email (delete button not found)`, 'warn'); } catch (err) { - log(`步骤 ${step}:删除邮件失败:${err.message}`, 'warn'); + log(`Step ${step}: Failed to delete email: ${err.message}`, 'warn'); } } @@ -731,7 +731,7 @@ async function deleteEmail(item, step) { // ============================================================ async function refreshInbox() { - // Try toolbar "刷 新" button + // Try toolbar "Refresh" button const toolbarBtns = document.querySelectorAll('.nui-btn .nui-btn-text'); for (const btn of toolbarBtns) { if (btn.textContent.replace(/\s/g, '') === '刷新') { @@ -742,7 +742,7 @@ async function refreshInbox() { } } - // Fallback: click sidebar "收 信" + // Fallback: click sidebar "Receive Mail" const shouXinBtns = document.querySelectorAll('.ra0'); for (const btn of shouXinBtns) { if (btn.textContent.replace(/\s/g, '').includes('收信')) { diff --git a/content/mail-2925.js b/content/mail-2925.js index 8d481fac..5b0ac9bb 100644 --- a/content/mail-2925.js +++ b/content/mail-2925.js @@ -86,7 +86,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { ensureMail2925Session(message.payload).then((result) => { sendResponse(result); }).catch((err) => { - sendResponse({ error: err?.message || String(err || '2925 登录失败') }); + sendResponse({ error: err?.message || String(err || '2925 login failed') }); }); return true; } @@ -97,12 +97,12 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { sendResponse(result); }).catch((err) => { if (isStopError(err)) { - log(`步骤 ${message.step}:已被用户停止。`, 'warn'); + log(`Step ${message.step}: Stopped by user.`, 'warn'); sendResponse({ stopped: true, error: err.message }); return; } - log(`步骤 ${message.step}:邮箱轮询失败:${err.message}`, 'warn'); + log(`Step ${message.step}: Mailbox polling failed: ${err.message}`, 'warn'); sendResponse({ error: err.message }); }); return true; @@ -112,7 +112,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { Promise.resolve(deleteAllMailboxEmails(message.step)).then((deleted) => { sendResponse({ ok: true, deleted }); }).catch((err) => { - sendResponse({ ok: false, error: err?.message || String(err || '删除邮件失败') }); + sendResponse({ ok: false, error: err?.message || String(err || 'Failed to delete email') }); }); return true; } @@ -1172,12 +1172,12 @@ async function ensureMail2925Session(payload = {}) { const password = String(payload?.password || ''); const forceLogin = Boolean(payload?.forceLogin); const allowLoginWhenOnLoginPage = payload?.allowLoginWhenOnLoginPage !== false; - log(`步骤 0:2925 登录态检查开始,当前地址 ${location.href},forceLogin=${forceLogin ? 'true' : 'false'}`, 'info'); + log(`Step 0: 2925 login state check started, current URL ${location.href}, forceLogin=${forceLogin ? 'true' : 'false'}`, 'info'); for (let attempt = 0; attempt < 10; attempt += 1) { throwIfStopped(); const currentState = detectMail2925ViewState(); - log(`步骤 0:2925 登录页状态探测,第 ${attempt + 1}/10 次,状态=${currentState.view},地址=${location.href}`, 'info'); + log(`Step 0: 2925 login page state detection, attempt ${attempt + 1}/10, state=${currentState.view}, URL=${location.href}`, 'info'); if (currentState.view === 'limit') { return { ok: false, @@ -1211,7 +1211,7 @@ async function ensureMail2925Session(payload = {}) { } const loginState = detectMail2925ViewState(); - log(`步骤 0:2925 准备执行登录,当前状态=${loginState.view},地址=${location.href}`, 'info'); + log(`Step 0: 2925 preparing to perform login, current state=${loginState.view}, URL=${location.href}`, 'info'); if (loginState.view === 'mailbox') { return { ok: true, @@ -1243,10 +1243,10 @@ async function ensureMail2925Session(payload = {}) { const passwordInput = findMail2925LoginPasswordInput(); const loginButton = findLoginButton(); if (!emailInput || !passwordInput || !loginButton) { - throw new Error('2925:未识别到可用的登录表单,请确认当前页面处于 2925 登录页。'); + throw new Error('2925: No usable login form recognized. Please confirm current page is the 2925 login page.'); } if (!email || !password) { - throw new Error('2925:当前账号缺少邮箱或密码,无法自动登录。'); + throw new Error('2925: Current account is missing email or password, cannot auto-login.'); } await ensureAgreementChecked(); @@ -1259,16 +1259,16 @@ async function ensureMail2925Session(payload = {}) { }); await sleep(200); await sleep(1000); - log(`步骤 0:2925 已定位到登录表单,准备点击“登录”,当前地址 ${location.href}`, 'info'); + log(`Step 0: 2925 located login form, ready to click "Login", current URL ${location.href}`, 'info'); await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'submit', label: 'mail2925-login-submit' }, async () => { simulateClick(loginButton); }); - log(`步骤 0:2925 已点击“登录”,点击后地址 ${location.href}`, 'info'); + log(`Step 0: 2925 clicked "Login", URL after click ${location.href}`, 'info'); const finalState = await waitForMail2925View('mailbox', 40000); - log(`步骤 0:2925 登录等待结束,状态=${finalState.view},地址=${location.href}`, 'info'); + log(`Step 0: 2925 login wait finished, state=${finalState.view}, URL=${location.href}`, 'info'); if (finalState.view !== 'mailbox') { - throw new Error('2925:提交账号密码后未进入收件箱。'); + throw new Error('2925: Did not enter inbox after submitting credentials.'); } return { @@ -1300,7 +1300,7 @@ async function handlePollEmail(step, payload) { throwIfMail2925LimitReached(); } - log(`步骤 ${step}:开始轮询 2925 邮箱(最多 ${maxAttempts} 次)`); + log(`Step ${step}: Starting 2925 mailbox polling (max ${maxAttempts} attempts)`); let initialItems = []; let initialLoadUsedRefresh = false; @@ -1316,18 +1316,18 @@ async function handlePollEmail(step, payload) { throwIfMail2925LimitReached(); } if (!refreshedMailbox.ready) { - throw new Error('2925 邮箱列表未加载完成,请确认当前已打开收件箱。'); + throw new Error('2925 mailbox list did not finish loading. Please confirm inbox is open.'); } initialItems = refreshedMailbox.items; } - log(`步骤 ${step}:邮件列表已加载,共 ${initialItems.length} 封邮件`); + log(`Step ${step}: Mail list loaded, ${initialItems.length} emails total`); for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { if (typeof throwIfMail2925LimitReached === 'function') { throwIfMail2925LimitReached(); } - log(`步骤 ${step}:正在轮询 2925 邮箱,第 ${attempt}/${maxAttempts} 次`); + log(`Step ${step}: Polling 2925 mailbox, attempt ${attempt}/${maxAttempts}`); if (attempt > 1 || !initialLoadUsedRefresh) { await returnToInbox(); @@ -1337,7 +1337,7 @@ async function handlePollEmail(step, payload) { const mailbox = await waitForMailboxReady(3000); if (!mailbox.ready) { - throw new Error('2925 邮箱列表未加载完成,请确认当前已打开收件箱。'); + throw new Error('2925 mailbox list did not finish loading. Please confirm inbox is open.'); } const items = mailbox.items; if (items.length > 0) { @@ -1381,19 +1381,19 @@ async function handlePollEmail(step, payload) { } if (excludedCodeSet.has(candidateCode)) { - log(`步骤 ${step}:跳过排除的验证码:${candidateCode}`, 'info'); + log(`Step ${step}: Skipping excluded verification code: ${candidateCode}`, 'info'); continue; } if (seenCodes.has(candidateCode)) { - log(`步骤 ${step}:跳过已处理过的验证码:${candidateCode}`, 'info'); + log(`Step ${step}: Skipping already processed verification code: ${candidateCode}`, 'info'); continue; } seenCodes.add(candidateCode); persistSeenCodes(); - const source = bodyCode ? '邮件正文' : '邮件预览'; - const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; - log(`步骤 ${step}:已找到验证码:${candidateCode}(来源:${source}${timeLabel})`, 'ok'); + const source = bodyCode ? 'mail body' : 'mail preview'; + const timeLabel = itemTimestamp ? `, time: ${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; + log(`Step ${step}: Found verification code: ${candidateCode} (source: ${source}${timeLabel})`, 'ok'); return { ok: true, code: candidateCode, emailTimestamp: Date.now() }; } } @@ -1404,7 +1404,7 @@ async function handlePollEmail(step, payload) { } throw new Error( - `${(maxAttempts * intervalMs / 1000).toFixed(0)} 秒后仍未在 2925 邮箱中找到新的匹配邮件。请手动检查收件箱。` + `No new matching email found in 2925 mailbox after ${(maxAttempts * intervalMs / 1000).toFixed(0)} seconds. Please manually check the inbox.` ); } diff --git a/content/qq-mail.js b/content/qq-mail.js index 73b9eba6..d9a49d85 100644 --- a/content/qq-mail.js +++ b/content/qq-mail.js @@ -27,11 +27,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { sendResponse(result); }).catch(err => { if (isStopError(err)) { - log(`步骤 ${message.step}:已被用户停止。`, 'warn'); + log(`Step ${message.step}: Stopped by user.`, 'warn'); sendResponse({ stopped: true, error: err.message }); return; } - log(`步骤 ${message.step}:邮箱轮询失败:${err.message}`, 'warn'); + log(`Step ${message.step}: Mailbox polling failed: ${err.message}`, 'warn'); sendResponse({ error: err.message }); }); return true; // async response @@ -98,26 +98,26 @@ async function handlePollEmail(step, payload) { } = payload; const excludedCodeSet = new Set(excludeCodes.filter(Boolean)); - log(`步骤 ${step}:开始轮询邮箱(最多 ${maxAttempts} 次,每 ${intervalMs / 1000} 秒一次)`); + log(`Step ${step}: Starting mailbox polling (max ${maxAttempts} attempts, every ${intervalMs / 1000} seconds)`); // Wait for mail list to load try { await waitForElement('.mail-list-page-item', 10000); - log(`步骤 ${step}:邮件列表已加载`); + log(`Step ${step}: Mail list loaded`); } catch { - throw new Error('邮件列表未加载完成,请确认 QQ 邮箱已打开收件箱。'); + throw new Error('Mail list not loaded. Please confirm QQ Mail inbox is open.'); } // Step 1: Snapshot existing mail IDs BEFORE we start waiting for new email const existingMailIds = getCurrentMailIds(); - log(`步骤 ${step}:已将当前 ${existingMailIds.size} 封邮件标记为旧邮件快照`); + log(`Step ${step}: Marked current ${existingMailIds.size} emails as old email snapshot`); // Fallback after just 3 attempts (~10s). In practice, the email is usually // already in the list but has the same mailid (page was already open). const FALLBACK_AFTER = 3; for (let attempt = 1; attempt <= maxAttempts; attempt++) { - log(`步骤 ${step}:正在轮询 QQ 邮箱,第 ${attempt}/${maxAttempts} 次`); + log(`Step ${step}: Polling QQ Mail, attempt ${attempt}/${maxAttempts}`); // Refresh inbox (skip on first attempt, list is fresh) if (attempt > 1) { @@ -148,18 +148,18 @@ async function handlePollEmail(step, payload) { }); if (code) { if (excludedCodeSet.has(code)) { - log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info'); + log(`Step ${step}: Skipping excluded verification code: ${code}`, 'info'); continue; } - const source = useFallback && existingMailIds.has(mailId) ? '回退首封匹配邮件' : '新邮件'; - log(`步骤 ${step}:已找到验证码:${code}(来源:${source},主题:${subject.slice(0, 40)})`, 'ok'); + const source = useFallback && existingMailIds.has(mailId) ? 'fallback-matched email' : 'new email'; + log(`Step ${step}: Found verification code: ${code} (source: ${source}, subject: ${subject.slice(0, 40)})`, 'ok'); return { ok: true, code, emailTimestamp: Date.now(), mailId }; } } } if (attempt === FALLBACK_AFTER + 1) { - log(`步骤 ${step}:连续 ${FALLBACK_AFTER} 次未发现新邮件,开始回退到首封匹配邮件`, 'warn'); + log(`Step ${step}: ${FALLBACK_AFTER} consecutive attempts with no new emails, starting fallback to first matched email`, 'warn'); } if (attempt < maxAttempts) { @@ -168,8 +168,8 @@ async function handlePollEmail(step, payload) { } throw new Error( - `${(maxAttempts * intervalMs / 1000).toFixed(0)} 秒后仍未找到新的匹配邮件。` + - '请手动检查 QQ 邮箱,邮件可能延迟到达或进入垃圾箱。' + `No new matching email found after ${(maxAttempts * intervalMs / 1000).toFixed(0)} seconds. ` + + 'Please manually check QQ Mail. Email may be delayed or in spam folder.' ); } diff --git a/content/utils.js b/content/utils.js index 2c439d13..7bb479b4 100644 --- a/content/utils.js +++ b/content/utils.js @@ -46,7 +46,7 @@ function getRuntimeScriptSource() { } const LOG_PREFIX = `[MultiPage:${SCRIPT_SOURCE}]`; -const STOP_ERROR_MESSAGE = '流程已被用户停止。'; +const STOP_ERROR_MESSAGE = 'Flow stopped by user.'; let flowStopped = false; if (!window.__MULTIPAGE_UTILS_LISTENER_READY__) { @@ -96,14 +96,14 @@ function waitForElement(selector, timeout = 10000) { const existing = document.querySelector(selector); if (existing) { - console.log(LOG_PREFIX, `立即找到元素: ${selector}`); - log(`已找到元素:${selector}`); + console.log(LOG_PREFIX, `Found element immediately: ${selector}`); + log(`Element found: ${selector}`); resolve(existing); return; } - console.log(LOG_PREFIX, `等待元素: ${selector}(超时 ${timeout}ms)`); - log(`正在等待选择器:${selector}...`); + console.log(LOG_PREFIX, `Waiting for element: ${selector} (timeout ${timeout}ms)`); + log(`Waiting for selector: ${selector}...`); let settled = false; let stopTimer = null; @@ -124,8 +124,8 @@ function waitForElement(selector, timeout = 10000) { const el = document.querySelector(selector); if (el) { cleanup(); - console.log(LOG_PREFIX, `等待后找到元素: ${selector}`); - log(`已找到元素:${selector}`); + console.log(LOG_PREFIX, `Element found after wait: ${selector}`); + log(`Element found: ${selector}`); resolve(el); } }); @@ -137,7 +137,7 @@ function waitForElement(selector, timeout = 10000) { const timer = setTimeout(() => { cleanup(); - const msg = `在 ${location.href} 等待 ${selector} 超时,已超过 ${timeout}ms`; + const msg = `Timed out waiting for ${selector} at ${location.href}, exceeded ${timeout}ms`; console.error(LOG_PREFIX, msg); reject(new Error(msg)); }, timeout); @@ -178,14 +178,14 @@ function waitForElementByText(containerSelector, textPattern, timeout = 10000) { const existing = search(); if (existing) { - console.log(LOG_PREFIX, `立即按文本找到元素: ${containerSelector} 匹配 ${textPattern}`); - log(`已按文本找到元素:${textPattern}`); + console.log(LOG_PREFIX, `Element found immediately by text: ${containerSelector} matches ${textPattern}`); + log(`Element found by text: ${textPattern}`); resolve(existing); return; } - console.log(LOG_PREFIX, `等待文本匹配: ${containerSelector} / ${textPattern}`); - log(`正在等待包含文本的元素:${textPattern}...`); + console.log(LOG_PREFIX, `Waiting for text match: ${containerSelector} / ${textPattern}`); + log(`Waiting for element containing text: ${textPattern}...`); let settled = false; let stopTimer = null; @@ -206,8 +206,8 @@ function waitForElementByText(containerSelector, textPattern, timeout = 10000) { const el = search(); if (el) { cleanup(); - console.log(LOG_PREFIX, `等待后按文本找到元素: ${textPattern}`); - log(`已按文本找到元素:${textPattern}`); + console.log(LOG_PREFIX, `Element found by text after wait: ${textPattern}`); + log(`Element found by text: ${textPattern}`); resolve(el); } }); @@ -219,7 +219,7 @@ function waitForElementByText(containerSelector, textPattern, timeout = 10000) { const timer = setTimeout(() => { cleanup(); - const msg = `在 ${location.href} 的 ${containerSelector} 中等待文本 "${textPattern}" 超时,已超过 ${timeout}ms`; + const msg = `Timed out waiting for text "${textPattern}" in ${containerSelector} at ${location.href}, exceeded ${timeout}ms`; console.error(LOG_PREFIX, msg); reject(new Error(msg)); }, timeout); @@ -252,8 +252,8 @@ function fillInput(el, value) { nativeInputValueSetter.call(el, value); el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); - console.log(LOG_PREFIX, `已填写输入框 ${el.name || el.id || el.type}: ${value}`); - log(`已填写输入框 [${el.name || el.id || el.type || '未知'}]`); + console.log(LOG_PREFIX, `Filled input ${el.name || el.id || el.type}: ${value}`); + log(`Filled input [${el.name || el.id || el.type || 'unknown'}]`); } /** @@ -265,8 +265,8 @@ function fillSelect(el, value) { throwIfStopped(); el.value = value; el.dispatchEvent(new Event('change', { bubbles: true })); - console.log(LOG_PREFIX, `已在 ${el.name || el.id} 中选择值: ${value}`); - log(`已选择 [${el.name || el.id || '未知'}] = ${value}`); + console.log(LOG_PREFIX, `Selected value in ${el.name || el.id}: ${value}`); + log(`Selected [${el.name || el.id || 'unknown'}] = ${value}`); } function normalizeLogStep(value) { @@ -338,7 +338,7 @@ function reportReady() { console.warn(LOG_PREFIX, 'skip CONTENT_SCRIPT_READY for unknown source'); return; } - console.log(LOG_PREFIX, '内容脚本已就绪'); + console.log(LOG_PREFIX, 'Content script ready'); const message = { type: 'CONTENT_SCRIPT_READY', source: getRuntimeScriptSource(), @@ -363,8 +363,8 @@ function reportReady() { function reportComplete(stepOrNodeId, data = {}) { const nodeId = resolveReportNodeId(stepOrNodeId, data); const step = normalizeLogStep(stepOrNodeId || data?.step || data?.visibleStep); - console.log(LOG_PREFIX, `节点 ${nodeId || stepOrNodeId} 已完成`, data); - log('已成功完成', 'ok', { step, stepKey: nodeId }); + console.log(LOG_PREFIX, `Node ${nodeId || stepOrNodeId} completed`, data); + log('Completed successfully', 'ok', { step, stepKey: nodeId }); const message = { type: 'NODE_COMPLETE', source: getRuntimeScriptSource(), @@ -394,7 +394,7 @@ function reportComplete(stepOrNodeId, data = {}) { function reportNodeComplete(nodeId, data = {}) { const normalizedNodeId = String(nodeId || '').trim(); - console.log(LOG_PREFIX, `节点 ${normalizedNodeId} 已完成`, data); + console.log(LOG_PREFIX, `Node ${normalizedNodeId} completed`, data); const message = { type: 'NODE_COMPLETE', source: getRuntimeScriptSource(), @@ -429,7 +429,7 @@ function reportNodeComplete(nodeId, data = {}) { function reportError(stepOrNodeId, errorMessage) { const nodeId = resolveReportNodeId(stepOrNodeId); const step = normalizeLogStep(stepOrNodeId); - console.error(LOG_PREFIX, `节点 ${nodeId || stepOrNodeId} 失败: ${errorMessage}`); + console.error(LOG_PREFIX, `Node ${nodeId || stepOrNodeId} failed: ${errorMessage}`); const message = { type: 'NODE_ERROR', source: getRuntimeScriptSource(), @@ -458,7 +458,7 @@ function reportError(stepOrNodeId, errorMessage) { function reportNodeError(nodeId, errorMessage) { const normalizedNodeId = String(nodeId || '').trim(); - console.error(LOG_PREFIX, `节点 ${normalizedNodeId} 失败: ${errorMessage}`); + console.error(LOG_PREFIX, `Node ${normalizedNodeId} failed: ${errorMessage}`); const message = { type: 'NODE_ERROR', source: getRuntimeScriptSource(), @@ -491,7 +491,7 @@ function reportNodeError(nodeId, errorMessage) { function simulateClick(el) { throwIfStopped(); if (!el) { - throw new Error('无法点击空元素。'); + throw new Error('Cannot click null element.'); } const form = el.form || el.closest?.('form') || null; @@ -517,8 +517,8 @@ function simulateClick(el) { el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); } - console.log(LOG_PREFIX, `已点击(${method}): ${el.tagName} ${textBeforeClick.slice(0, 30)}`); - log(`已点击(${method}) [${el.tagName}] "${textBeforeClick.trim().slice(0, 30) || ''}"`); + console.log(LOG_PREFIX, `Clicked (${method}): ${el.tagName} ${textBeforeClick.slice(0, 30)}`); + log(`Clicked (${method}) [${el.tagName}] "${textBeforeClick.trim().slice(0, 30) || ''}"`); } /** diff --git a/core/flow-kernel/flow-capabilities.js b/core/flow-kernel/flow-capabilities.js index 9bdbb2e8..7c3dfe1f 100644 --- a/core/flow-kernel/flow-capabilities.js +++ b/core/flow-kernel/flow-capabilities.js @@ -35,7 +35,7 @@ supportsLuckmail: false, canSwitchFlow: true, stepDefinitionMode: 'default', - targetSelectorLabel: '来源', + targetSelectorLabel: 'Source', }); const FLOW_CAPABILITIES = Object.freeze( @@ -424,36 +424,36 @@ if (!flowState.supportsPhoneSignup) { return { code: 'phone_signup_flow_unsupported', - message: '当前 flow 不支持手机号注册。', + message: 'The current flow does not support phone number signup.', }; } if (!targetState.supportsPhoneSignup) { return { code: 'phone_signup_panel_unsupported', - message: `当前来源 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 不支持手机号注册。`, + message: `The current source ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} does not support phone number signup.`, }; } if (!runtimeLocks.phoneVerificationEnabled) { return { code: 'phone_signup_phone_verification_disabled', - message: '请先开启接码设置后再使用手机号注册。', + message: 'Please enable SMS verification settings before using phone number signup.', }; } if (runtimeLocks.plusModeEnabled) { return { code: 'phone_signup_plus_mode_locked', - message: 'Plus 模式开启时不能使用手机号注册。', + message: 'Phone number signup cannot be used while Plus mode is enabled.', }; } if (runtimeLocks.accountContribution) { return { code: 'phone_signup_contribution_mode_locked', - message: '贡献模式开启时不能使用手机号注册。', + message: 'Phone number signup cannot be used while contribution mode is enabled.', }; } return { code: 'phone_signup_unavailable', - message: '当前设置暂不支持手机号注册。', + message: 'The current settings do not support phone number signup.', }; } @@ -469,21 +469,21 @@ ) { errors.push({ code: 'panel_mode_unsupported', - message: `当前 flow 不支持 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 来源。`, + message: `The current flow does not support the ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} source.`, }); } if (Boolean(state?.plusModeEnabled) && !capabilityState.flowCapabilities?.supportsPlusMode) { errors.push({ code: 'plus_mode_unsupported', - message: '当前 flow 不支持 Plus 模式。', + message: 'The current flow does not support Plus mode.', }); } if (Boolean(state?.accountContributionEnabled) && !capabilityState.flowCapabilities?.supportsAccountContribution) { errors.push({ code: 'contribution_mode_unsupported', - message: '当前 flow 不支持贡献模式。', + message: 'The current flow does not support contribution mode.', }); } @@ -525,7 +525,7 @@ normalizedUpdates.targetId = capabilityState.effectiveTargetId; errors.push({ code: 'panel_mode_unsupported', - message: `当前 flow 不支持 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 来源。`, + message: `The current flow does not support the ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} source.`, }); } @@ -533,7 +533,7 @@ normalizedUpdates.plusModeEnabled = false; errors.push({ code: 'plus_mode_unsupported', - message: '当前 flow 不支持 Plus 模式。', + message: 'The current flow does not support Plus mode.', }); } @@ -545,7 +545,7 @@ normalizedUpdates.accountContributionEnabled = false; errors.push({ code: 'contribution_mode_unsupported', - message: '当前 flow 不支持贡献模式。', + message: 'The current flow does not support contribution mode.', }); } @@ -557,7 +557,7 @@ normalizedUpdates.phoneVerificationEnabled = false; errors.push({ code: 'phone_verification_unsupported', - message: '当前 flow 不支持接码设置。', + message: 'The current flow does not support SMS verification settings.', }); } diff --git a/core/flow-kernel/logging-status.js b/core/flow-kernel/logging-status.js index b3de7631..defe02fa 100644 --- a/core/flow-kernel/logging-status.js +++ b/core/flow-kernel/logging-status.js @@ -19,27 +19,27 @@ return sourceRegistry.getSourceLabel(source); } const labels = { - 'openai-auth': '认证页', - 'gmail-mail': 'Gmail 邮箱', - 'sidepanel': '侧边栏', - 'vps-panel': 'CPA 面板', - 'sub2api-panel': 'SUB2API 后台', - 'codex2api-panel': 'Codex2API 后台', - 'qq-mail': 'QQ 邮箱', - 'mail-163': '163 邮箱', - 'mail-2925': '2925 邮箱', - 'inbucket-mail': 'Inbucket 邮箱', - 'duck-mail': 'Duck 邮箱', - 'hotmail-api': 'Hotmail(API对接/本地助手)', - 'luckmail-api': 'LuckMail(API 购邮)', + 'openai-auth': 'Auth Page', + 'gmail-mail': 'Gmail Mailbox', + 'sidepanel': 'Side Panel', + 'vps-panel': 'CPA Panel', + 'sub2api-panel': 'SUB2API Backend', + 'codex2api-panel': 'Codex2API Backend', + 'qq-mail': 'QQ Mailbox', + 'mail-163': '163 Mailbox', + 'mail-2925': '2925 Mailbox', + 'inbucket-mail': 'Inbucket Mailbox', + 'duck-mail': 'Duck Mailbox', + 'hotmail-api': 'Hotmail (API/Local Helper)', + 'luckmail-api': 'LuckMail (API Purchase)', 'cloudflare-temp-email': 'Cloudflare Temp Email', 'cloudmail': 'Cloud Mail', 'plus-checkout': 'Plus Checkout', - 'paypal-flow': 'PayPal 授权页', - 'gopay-flow': 'GoPay 授权页', - 'unknown-source': '未知来源', + 'paypal-flow': 'PayPal Auth Page', + 'gopay-flow': 'GoPay Auth Page', + 'unknown-source': 'Unknown Source', }; - return labels[source] || source || '未知来源'; + return labels[source] || source || 'Unknown Source'; } function normalizeLogStep(value) { @@ -75,7 +75,7 @@ async function setNodeStatus(nodeId, status) { const normalizedNodeId = String(nodeId || '').trim(); if (!normalizedNodeId) { - throw new Error('setNodeStatus 缺少 nodeId。'); + throw new Error('setNodeStatus is missing nodeId.'); } const state = await getState(); const nodeStatuses = { ...(state.nodeStatuses || {}) }; @@ -112,23 +112,23 @@ function getLoginAuthStateLabel(state) { switch (state) { case 'verification_page': - return '登录验证码页'; + return 'Login Verification Code Page'; case 'password_page': - return '密码页'; + return 'Password Page'; case 'email_page': - return '邮箱输入页'; + return 'Email Input Page'; case 'login_timeout_error_page': - return '登录超时报错页'; + return 'Login Timeout Error Page'; case 'oauth_consent_page': - return 'OAuth 授权页'; + return 'OAuth Consent Page'; case 'add_phone_page': - return '手机号页'; + return 'Phone Number Page'; case 'add_email_page': - return '添加邮箱页'; + return 'Add Email Page'; case 'phone_verification_page': - return '手机验证码页'; + return 'Phone Verification Code Page'; default: - return '未知页面'; + return 'Unknown Page'; } } diff --git a/core/flow-kernel/tab-runtime.js b/core/flow-kernel/tab-runtime.js index cba669b0..96e42af9 100644 --- a/core/flow-kernel/tab-runtime.js +++ b/core/flow-kernel/tab-runtime.js @@ -88,8 +88,8 @@ } function buildAutomationWindowUnavailableError(error) { - const suffix = error?.message ? ` 原因:${error.message}` : ''; - return new Error(`自动任务窗口已不可用,请在目标 Chrome 窗口重新打开侧边栏并启动任务。${suffix}`); + const suffix = error?.message ? ` Reason: ${error.message}` : ''; + return new Error(`The automation task window is no longer available. Please reopen the side panel in the target Chrome window and start the task again.${suffix}`); } async function getAutomationWindowId(options = {}) { @@ -319,7 +319,7 @@ await setState({ tabRegistry: registry }); } - await addLog(`已关闭 ${matchedIds.length} 个旧的${getSourceLabel(source)}标签页。`, 'info'); + await addLog(`Closed ${matchedIds.length} stale ${getSourceLabel(source)} tabs.`, 'info'); } function isLocalhostOAuthCallbackTabMatch(callbackUrl, candidateUrl) { @@ -357,7 +357,7 @@ await setState({ tabRegistry: registry }); } - await addLog(`已关闭 ${matchedIds.length} 个匹配当前 OAuth callback 的 localhost 残留标签页。`, 'info'); + await addLog(`Closed ${matchedIds.length} localhost residual tabs matching the current OAuth callback.`, 'info'); return matchedIds.length; } @@ -387,7 +387,7 @@ if (!matchedIds.length) return 0; await chrome.tabs.remove(matchedIds).catch(() => { }); - await addLog(`已关闭 ${matchedIds.length} 个匹配 ${prefix} 的 localhost 残留标签页。`, 'info'); + await addLog(`Closed ${matchedIds.length} localhost residual tabs matching ${prefix}.`, 'info'); return matchedIds.length; } @@ -540,7 +540,7 @@ } if (!inject || !inject.length) { - throw new Error(`${getSourceLabel(source)} 内容脚本未就绪,且未提供可用的注入文件。`); + throw new Error(`${getSourceLabel(source)} content script is not ready and no injectable files were provided.`); } let registry = await getTabRegistry(); @@ -592,7 +592,7 @@ await sleepOrStop(retryDelayMs); } - throw lastError || new Error(`${getSourceLabel(source)} 内容脚本长时间未就绪。`); + throw lastError || new Error(`${getSourceLabel(source)} content script did not become ready in time.`); } function getContentScriptResponseTimeoutMs(message) { @@ -624,10 +624,10 @@ const rawMessage = error?.message || String(error || ''); if (isRetryableContentScriptTransportError(error)) { return new Error( - `${getSourceLabel(source)} 页面刚完成跳转或刷新,内容脚本还没有重新接回;扩展已自动重试,但仍未恢复。请重试当前步骤。` + `${getSourceLabel(source)} page just navigated or reloaded and the content script has not re-attached; the extension auto-retried but it still has not recovered. Please retry the current step.` ); } - return new Error(rawMessage || `${getSourceLabel(source)} 页面通信失败。`); + return new Error(rawMessage || `${getSourceLabel(source)} page communication failed.`); } function getMessageDebugLabel(source, message, tabId = null) { @@ -664,7 +664,7 @@ settled = true; const seconds = Math.ceil(responseTimeoutMs / 1000); console.warn(LOG_PREFIX, `[sendTabMessageWithTimeout] timeout ${debugLabel} after ${Date.now() - startedAt}ms`); - reject(new Error(`${getSourceLabel(source)} 内容脚本 ${seconds} 秒内未响应,请刷新页面后重试。`)); + reject(new Error(`${getSourceLabel(source)} content script did not respond within ${seconds}s. Please refresh the page and retry.`)); }, responseTimeoutMs); chrome.tabs.sendMessage(tabId, message) @@ -692,7 +692,7 @@ const commandKey = getSourceCommandKey(source); const timer = setTimeout(() => { pendingCommands.delete(commandKey); - reject(new Error(`${getSourceLabel(source)} 内容脚本 ${timeout / 1000} 秒内未响应,请刷新页面后重试。`)); + reject(new Error(`${getSourceLabel(source)} content script did not respond within ${timeout / 1000}s. Please refresh the page and retry.`)); }, timeout); pendingCommands.set(commandKey, { message, @@ -957,7 +957,7 @@ if (lastError && isRetryableContentScriptTransportError(lastError)) { throw buildRetryableTransportTimeoutError(source, lastError); } - throw lastError || new Error(`等待 ${getSourceLabel(source)} 重新就绪超时。`); + throw lastError || new Error(`Timed out waiting for ${getSourceLabel(source)} to become ready again.`); } async function sendToMailContentScriptResilient(mail, message, options = {}) { @@ -995,7 +995,7 @@ lastError = err; if (!logged) { - await addLog(`${mail.label} 页面通信异常,正在尝试让邮箱页重新就绪...`, 'warn', { + await addLog(`${mail.label} page communication failed; trying to bring the mailbox page back to ready state...`, 'warn', { step: logStep, stepKey: logStepKey, }); @@ -1016,7 +1016,7 @@ } } - throw lastError || new Error(`${mail.label} 页面未能重新就绪。`); + throw lastError || new Error(`${mail.label} page failed to become ready again.`); } return { diff --git a/flows/grok/background/publisher-webchat2api.js b/flows/grok/background/publisher-webchat2api.js index 1c963efe..52dfd6e2 100644 --- a/flows/grok/background/publisher-webchat2api.js +++ b/flows/grok/background/publisher-webchat2api.js @@ -45,7 +45,7 @@ } function getErrorMessage(error) { - return error instanceof Error ? error.message : cleanString(error) || '未知错误'; + return error instanceof Error ? error.message : cleanString(error) || 'Unknown error'; } async function readResponse(response) { @@ -71,17 +71,17 @@ function normalizeWebchat2ApiBaseUrl(value = '') { const rawUrl = cleanString(value); if (!rawUrl) { - throw new Error('缺少 webchat2api 地址。'); + throw new Error('Missing webchat2api URL.'); } const withProtocol = /^https?:\/\//i.test(rawUrl) ? rawUrl : `http://${rawUrl}`; let parsed = null; try { parsed = new URL(withProtocol); } catch (_error) { - throw new Error('webchat2api 地址格式无效,请检查配置。'); + throw new Error('Invalid webchat2api URL format, please check the config.'); } if (!/^https?:$/.test(parsed.protocol)) { - throw new Error('webchat2api 地址只支持 http 或 https。'); + throw new Error('webchat2api URL only supports http or https.'); } return parsed.origin; } @@ -146,7 +146,7 @@ function buildGrokSsoInjectPayload(ssoCookie = '') { const normalizedCookie = cleanString(ssoCookie); if (!normalizedCookie) { - throw new Error('缺少 Grok SSO Cookie,请先完成步骤 5。'); + throw new Error('Missing Grok SSO Cookie, please complete step 5 first.'); } return { accounts: [{ @@ -165,7 +165,7 @@ const endpointUrl = buildWebchat2ApiInjectUrl(baseUrl); const normalizedApiKey = normalizeWebchat2ApiAdminKey(apiKey); if (!normalizedApiKey) { - throw new Error('缺少 webchat2api 管理密钥。'); + throw new Error('Missing webchat2api admin key.'); } const response = await fetchImpl(endpointUrl, { @@ -180,15 +180,15 @@ const body = await readResponse(response); if (!response.ok) { const message = readWebchat2ApiResponseMessage(body, response.statusText) || `HTTP ${response.status}`; - throw new Error(`webchat2api SSO 上传失败:${message}`); + throw new Error(`webchat2api SSO upload failed: ${message}`); } if (isPlainObject(body.json) && Object.prototype.hasOwnProperty.call(body.json, 'code') && Number(body.json.code) !== 0) { const message = readWebchat2ApiResponseMessage(body, `code=${body.json.code}`); - throw new Error(`webchat2api SSO 上传失败:${message}`); + throw new Error(`webchat2api SSO upload failed: ${message}`); } return { endpointUrl, - message: readWebchat2ApiResponseMessage(body, '') || '上传成功', + message: readWebchat2ApiResponseMessage(body, '') || 'Upload succeeded', raw: body.json, }; } @@ -248,11 +248,11 @@ failureTargetUrl = endpointUrl; const apiKey = normalizeWebchat2ApiAdminKey(targetConfig.apiKey); if (!apiKey) { - throw new Error('缺少 webchat2api 管理密钥。'); + throw new Error('Missing webchat2api admin key.'); } const ssoCookie = resolveGrokSsoCookie(currentState); if (!ssoCookie) { - throw new Error('缺少 Grok SSO Cookie,请先完成步骤 5。'); + throw new Error('Missing Grok SSO Cookie, please complete step 5 first.'); } await applyRuntimeState(currentState, { @@ -267,7 +267,7 @@ }, }); - await log('步骤 6:正在上传 Grok SSO 到 webchat2api...', 'info', nodeId); + await log('Step 6: uploading Grok SSO to webchat2api...', 'info', nodeId); const uploadResult = await uploadGrokSsoToWebchat2Api( targetConfig.baseUrl, apiKey, @@ -282,16 +282,16 @@ upload: { status: 'uploaded', uploadedAt, - message: uploadResult.message || '上传成功', + message: uploadResult.message || 'Upload succeeded', targetUrl: uploadResult.endpointUrl, }, }); - await log(`步骤 6:Grok SSO 已上传到 webchat2api,状态:${uploadResult.message || '上传成功'}。`, 'ok', nodeId); + await log(`Step 6: Grok SSO uploaded to webchat2api, status: ${uploadResult.message || 'Upload succeeded'}.`, 'ok', nodeId); await completeNodeFromBackground(nodeId, payload); } catch (error) { const message = getErrorMessage(error); await persistFailure(currentState, message, failureTargetUrl); - await log(`步骤 6:${message}`, 'error', nodeId); + await log(`Step 6: ${message}`, 'error', nodeId); throw error; } } diff --git a/flows/grok/background/register-runner.js b/flows/grok/background/register-runner.js index d8f30144..e4ea6d6b 100644 --- a/flows/grok/background/register-runner.js +++ b/flows/grok/background/register-runner.js @@ -20,7 +20,7 @@ } function getErrorMessage(error) { - return error instanceof Error ? error.message : cleanString(error) || '未知错误'; + return error instanceof Error ? error.message : cleanString(error) || 'Unknown error'; } function createGeneratedPassword() { @@ -135,7 +135,7 @@ } if (!options.openIfMissing) { - throw new Error(options.missingMessage || '缺少 Grok 注册页,请先执行步骤 1。'); + throw new Error(options.missingMessage || 'Missing Grok registration page, please run step 1 first.'); } const openedTabId = await reuseOrCreateTab(GROK_REGISTER_PAGE_SOURCE_ID, GROK_SIGNUP_URL, { @@ -143,7 +143,7 @@ injectSource: GROK_REGISTER_PAGE_SOURCE_ID, }); if (!Number.isInteger(openedTabId)) { - throw new Error('无法打开 Grok 注册页。'); + throw new Error('Unable to open Grok registration page.'); } await registerTab(GROK_REGISTER_PAGE_SOURCE_ID, openedTabId); return openedTabId; @@ -151,7 +151,7 @@ async function ensureContentReady(tabId, options = {}) { if (!Number.isInteger(tabId)) { - throw new Error('缺少 Grok 注册页标签页,无法连接内容脚本。'); + throw new Error('Missing Grok registration page tab, cannot connect content script.'); } if (typeof waitForTabStableComplete === 'function') { await waitForTabStableComplete(tabId, { @@ -167,14 +167,14 @@ injectSource: GROK_REGISTER_PAGE_SOURCE_ID, timeoutMs: options.timeoutMs || DEFAULT_GROK_PAGE_TIMEOUT_MS, retryDelayMs: 700, - logMessage: options.logMessage || 'Grok 注册页内容脚本未就绪,正在等待页面恢复...', + logMessage: options.logMessage || 'Grok registration page content script not ready, waiting for page recovery...', }); } } async function sendGrokCommand(nodeId, payload = {}, options = {}) { if (typeof sendToContentScriptResilient !== 'function') { - throw new Error('Grok 注册页通信能力不可用。'); + throw new Error('Grok registration page communication unavailable.'); } const result = await sendToContentScriptResilient(GROK_REGISTER_PAGE_SOURCE_ID, { type: 'EXECUTE_NODE', @@ -233,8 +233,8 @@ const stateLabel = cleanString(lastState?.state) || 'unknown'; const urlLabel = cleanString(lastState?.url); - const errorLabel = lastError ? `,最后通信错误:${lastError}` : ''; - throw new Error(`Grok 邮箱提交后尚未进入验证码页面,当前状态:${stateLabel}${urlLabel ? `,URL:${urlLabel}` : ''}${errorLabel}。`); + const errorLabel = lastError ? `, last communication error: ${lastError}` : ''; + throw new Error(`Grok email submission did not reach verification-code page. Current state: ${stateLabel}${urlLabel ? `, URL: ${urlLabel}` : ''}${errorLabel}.`); } function shouldClearGrokCookie(cookie = {}) { @@ -252,7 +252,7 @@ async function clearGrokCookiesBeforeStep1() { if (!chrome?.cookies?.getAll || !chrome.cookies?.remove) { - await log('步骤 1:当前浏览器不支持 cookies API,跳过 Grok Cookie 清理。', 'warn', 'grok-open-signup-page'); + await log('Step 1: cookies API not supported by this browser, skipping Grok cookie cleanup.', 'warn', 'grok-open-signup-page'); return; } @@ -304,7 +304,7 @@ } } } - await log(`步骤 1:已清理 Grok/xAI Cookie ${removedCount} 个。`, removedCount ? 'ok' : 'info', 'grok-open-signup-page'); + await log(`Step 1: cleared ${removedCount} Grok/xAI cookies.`, removedCount ? 'ok' : 'info', 'grok-open-signup-page'); } function resolveProfile(currentState = {}) { @@ -376,9 +376,9 @@ const result = await sendGrokCommand(nodeId, {}, { step: 1, timeoutMs: DEFAULT_GROK_PAGE_TIMEOUT_MS, - logMessage: '步骤 1:正在打开 Grok 邮箱注册入口...', + logMessage: 'Step 1: opening Grok email signup page...', }); - await log('步骤 1:已打开 Grok 邮箱注册页。', 'ok', nodeId); + await log('Step 1: opened Grok email signup page.', 'ok', nodeId); await completeNode(nodeId, { grokRegisterTabId: tabId, grokPageState: result.state || 'email_signup_ready', @@ -403,7 +403,7 @@ lastError: message, }, })); - await log(`步骤 1:${message}`, 'error', nodeId); + await log(`Step 1: ${message}`, 'error', nodeId); throw error; } } @@ -413,7 +413,7 @@ const currentState = await getExecutionState(state); try { if (typeof resolveSignupEmailForFlow !== 'function') { - throw new Error('Grok 邮箱步骤缺少公共邮箱解析能力,无法继续执行。'); + throw new Error('Grok email step missing shared email resolver, cannot continue.'); } const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false }); await activateTab(tabId); @@ -423,7 +423,7 @@ }); const email = cleanString(resolvedEmail).toLowerCase(); if (!email) { - throw new Error('Grok 注册邮箱为空,无法继续执行。'); + throw new Error('Grok registration email is empty, cannot continue.'); } const requestedAt = Date.now(); await persistState({ @@ -442,12 +442,12 @@ const result = await sendGrokCommand(nodeId, { email }, { step: 2, timeoutMs: GROK_VERIFICATION_READY_TIMEOUT_MS + 15000, - logMessage: '步骤 2:正在提交 Grok 注册邮箱...', + logMessage: 'Step 2: submitting Grok registration email...', }); if (result.state !== GROK_VERIFICATION_PAGE_STATE) { - throw new Error(`Grok 邮箱提交后尚未进入验证码页面,当前状态:${cleanString(result.state) || 'unknown'}${cleanString(result.url) ? `,URL:${cleanString(result.url)}` : ''}。`); + throw new Error(`Grok email submission did not reach verification-code page. Current state: ${cleanString(result.state) || 'unknown'}${cleanString(result.url) ? `, URL: ${cleanString(result.url)}` : ''}.`); } - await log(`步骤 2:已提交 Grok 注册邮箱 ${email}。`, 'ok', nodeId); + await log(`Step 2: submitted Grok registration email ${email}.`, 'ok', nodeId); await completeNode(nodeId, { grokEmail: email, grokVerificationRequestedAt: requestedAt, @@ -479,7 +479,7 @@ status: 'error', }, })); - await log(`步骤 2:${message}`, 'error', nodeId); + await log(`Step 2: ${message}`, 'error', nodeId); throw error; } } @@ -489,7 +489,7 @@ const currentState = await getExecutionState(state); try { if (typeof pollFlowVerificationCode !== 'function') { - throw new Error('Grok 验证码步骤缺少共享邮件轮询能力,无法继续执行。'); + throw new Error('Grok verification-code step missing shared mail polling, cannot continue.'); } const requestedAt = Math.max( 0, @@ -510,7 +510,7 @@ await activateTab(tabId); const readyState = await waitForGrokVerificationPageReady(tabId, { step: 3, - logMessage: '步骤 3:正在等待 Grok 验证码页面就绪...', + logMessage: 'Step 3: waiting for Grok verification-code page to be ready...', }); await persistState({ grokPageState: readyState.state || '', @@ -524,13 +524,13 @@ }), }); const pollResult = await pollFlowVerificationCode({ - actionLabel: 'Grok 验证码', + actionLabel: 'Grok verification code', filterAfterTimestamp, flowId: 'grok', logStep: 3, logStepKey: nodeId, nodeId, - notFoundMessage: '步骤 3:邮箱轮询结束,但未获取到 xAI 验证码。', + notFoundMessage: 'Step 3: mailbox polling ended without obtaining xAI verification code.', state: { ...currentState, activeFlowId: 'grok', @@ -543,15 +543,15 @@ }); const code = normalizeGrokVerificationCode(pollResult?.code); if (!code) { - throw new Error('未能获取到 xAI 邮箱验证码。'); + throw new Error('Failed to obtain xAI email verification code.'); } await activateTab(tabId); await ensureContentReady(tabId); const result = await sendGrokCommand(nodeId, { code }, { step: 3, - logMessage: '步骤 3:正在填写 xAI 邮箱验证码...', + logMessage: 'Step 3: filling in xAI email verification code...', }); - await log(`步骤 3:已提交 xAI 邮箱验证码,当前页面状态:${result.state || 'unknown'}。`, 'ok', nodeId); + await log(`Step 3: submitted xAI email verification code, current page state: ${result.state || 'unknown'}.`, 'ok', nodeId); await completeNode(nodeId, { grokVerificationCode: code, grokVerificationRawCode: cleanString(pollResult?.code), @@ -580,7 +580,7 @@ status: 'error', }, })); - await log(`步骤 3:${message}`, 'error', nodeId); + await log(`Step 3: ${message}`, 'error', nodeId); throw error; } } @@ -616,12 +616,12 @@ password, }, { step: 4, - logMessage: '步骤 4:正在填写 xAI 注册资料...', + logMessage: 'Step 4: filling in xAI registration profile...', }); - await log(`步骤 4:已提交 Grok 注册资料,等待 ${Math.floor(GROK_POST_PROFILE_CF_WAIT_MS / 1000)} 秒完成注册验证...`, 'info', nodeId); + await log(`Step 4: submitted Grok registration profile, waiting ${Math.floor(GROK_POST_PROFILE_CF_WAIT_MS / 1000)} seconds for registration verification...`, 'info', nodeId); await sleepWithStop(GROK_POST_PROFILE_CF_WAIT_MS); await ensureContentReady(tabId, { timeoutMs: DEFAULT_GROK_PAGE_TIMEOUT_MS }); - await log('步骤 4:已提交 Grok 注册资料并完成等待。', 'ok', nodeId); + await log('Step 4: submitted Grok registration profile and finished waiting.', 'ok', nodeId); await completeNode(nodeId, { grokFirstName: profile.firstName, grokLastName: profile.lastName, @@ -652,7 +652,7 @@ status: 'error', }, })); - await log(`步骤 4:${message}`, 'error', nodeId); + await log(`Step 4: ${message}`, 'error', nodeId); throw error; } } @@ -663,7 +663,7 @@ try { const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false }); await activateTab(tabId); - await log(`步骤 5:等待 ${Math.floor(GROK_PRE_SSO_EXTRACT_WAIT_MS / 1000)} 秒后提取 Grok SSO...`, 'info', nodeId); + await log(`Step 5: waiting ${Math.floor(GROK_PRE_SSO_EXTRACT_WAIT_MS / 1000)} seconds before extracting Grok SSO...`, 'info', nodeId); await sleepWithStop(GROK_PRE_SSO_EXTRACT_WAIT_MS); let ssoCookie = await readSsoCookieFromChrome(); @@ -671,12 +671,12 @@ await ensureContentReady(tabId); const result = await sendGrokCommand(nodeId, {}, { step: 5, - logMessage: '步骤 5:正在从 Grok 注册页读取 sso Cookie...', + logMessage: 'Step 5: reading sso cookie from Grok registration page...', }); ssoCookie = cleanString(result?.ssoCookie); } if (!ssoCookie) { - throw new Error('未找到 x.ai/grok sso Cookie。'); + throw new Error('x.ai/grok sso cookie not found.'); } const completedAt = Date.now(); @@ -716,11 +716,11 @@ ...currentState, ...completionPatch, }, { - logPrefix: 'Grok 注册成功', + logPrefix: 'Grok registration succeeded', level: 'ok', }); } - await log('步骤 5:已提取 Grok SSO Cookie。', 'ok', nodeId); + await log('Step 5: extracted Grok SSO cookie.', 'ok', nodeId); await completeNode(nodeId, completionPatch); } catch (error) { const message = getErrorMessage(error); @@ -732,7 +732,7 @@ status: 'error', }, })); - await log(`步骤 5:${message}`, 'error', nodeId); + await log(`Step 5: ${message}`, 'error', nodeId); throw error; } } diff --git a/flows/grok/content/register-page.js b/flows/grok/content/register-page.js index eac3204a..6287cb89 100644 --- a/flows/grok/content/register-page.js +++ b/flows/grok/content/register-page.js @@ -45,7 +45,7 @@ function findGrokClickableByText(pattern) { function simulateGrokClick(element) { throwIfStopped(); if (!element) { - throw new Error('无法点击空元素。'); + throw new Error('Cannot click empty element.'); } const rect = element.getBoundingClientRect(); const clientX = Math.max(0, Math.floor(rect.left + Math.min(rect.width - 1, Math.max(1, rect.width / 2)))); @@ -152,7 +152,7 @@ async function openGrokSignupPage() { const emailButton = await waitForGrok(() => ( findGrokClickableByText(GROK_EMAIL_SIGNUP_TEXT_PATTERN) || findGrokEmailInput() ), { timeoutMs: 30000 }); - if (!emailButton) throw new Error('未找到 x.ai 邮箱注册入口。'); + if (!emailButton) throw new Error('x.ai email signup entry not found.'); if (!(emailButton instanceof HTMLInputElement)) { simulateGrokClick(emailButton); await sleep(500); @@ -195,18 +195,18 @@ async function waitForGrokVerificationPageAfterEmailSubmit() { throw new Error(errorText); } const finalState = getGrokPageState(); - throw new Error(`提交 Grok 注册邮箱后未进入验证码页面,当前页面状态:${finalState || 'unknown'}。请确认页面已跳转到“验证您的邮箱”后再继续。`); + throw new Error(`Did not reach verification-code page after submitting Grok registration email. Current page state: ${finalState || 'unknown'}. Please confirm the page has navigated to "Verify your email" before continuing.`); } async function submitGrokEmail(payload = {}) { const email = String(payload.email || '').trim(); - if (!email) throw new Error('缺少 Grok 注册邮箱。'); + if (!email) throw new Error('Missing Grok registration email.'); const input = await waitForGrok(findGrokEmailInput, { timeoutMs: 45000 }); - if (!input) throw new Error('未找到 x.ai 邮箱输入框。'); + if (!input) throw new Error('x.ai email input not found.'); fillInput(input, email); await sleep(200); const button = findGrokSubmitButton(); - if (!button) throw new Error('未找到 x.ai 邮箱提交按钮。'); + if (!button) throw new Error('x.ai email submit button not found.'); simulateGrokClick(button); await sleep(1200); const errorText = getGrokEmailErrorText(); @@ -234,9 +234,9 @@ function getGrokVerificationErrorText() { async function submitGrokVerificationCode(payload = {}) { const normalizedCode = String(payload.code || '').replace(/[^A-Za-z0-9]/g, '').trim(); - if (!normalizedCode) throw new Error('缺少 xAI 验证码。'); + if (!normalizedCode) throw new Error('Missing xAI verification code.'); const inputs = await waitForGrok(() => findGrokOtpInputs(), { timeoutMs: 45000 }); - if (!inputs?.length) throw new Error('未找到 xAI 验证码输入框。'); + if (!inputs?.length) throw new Error('xAI verification-code input not found.'); if (inputs.length === 1) { fillInput(inputs[0], normalizedCode); } else { @@ -258,10 +258,10 @@ async function submitGrokVerificationCode(payload = {}) { throw new Error(settledState.error); } if (finalState === 'email_entry') { - throw new Error('x.ai 验证码提交后回到邮箱注册页,可能是验证码无效、会话过期或注册风控重置。'); + throw new Error('x.ai verification-code submission returned to the email signup page. The code may be invalid, the session expired, or registration risk control was reset.'); } if (!['profile_entry', 'signed_in'].includes(finalState)) { - throw new Error(`x.ai 验证码提交后进入未知页面状态:${finalState || 'unknown'}。`); + throw new Error(`x.ai verification-code submission entered an unknown page state: ${finalState || 'unknown'}.`); } return { submitted: true, state: finalState, url: location.href }; } @@ -270,20 +270,20 @@ async function submitGrokProfile(payload = {}) { const firstName = String(payload.firstName || '').trim(); const lastName = String(payload.lastName || '').trim(); const password = String(payload.password || ''); - if (!firstName || !lastName || !password) throw new Error('缺少 Grok 注册资料。'); + if (!firstName || !lastName || !password) throw new Error('Missing Grok registration profile.'); const ready = await waitForGrok(() => { const firstInput = findGrokProfileInput(['givenName', 'firstName', 'given-name']); const lastInput = findGrokProfileInput(['familyName', 'lastName', 'family-name']); const passwordInputs = findGrokPasswordInputs(); return firstInput && lastInput && passwordInputs.length ? { firstInput, lastInput, passwordInputs } : null; }, { timeoutMs: 45000 }); - if (!ready) throw new Error('未找到 x.ai 资料或密码表单。'); + if (!ready) throw new Error('x.ai profile or password form not found.'); fillInput(ready.firstInput, firstName); fillInput(ready.lastInput, lastName); ready.passwordInputs.forEach((input) => fillInput(input, password)); await sleep(GROK_PROFILE_SUBMIT_PRE_CLICK_DELAY_MS); const button = findGrokSubmitButton(); - if (!button) throw new Error('未找到 x.ai 资料提交按钮。'); + if (!button) throw new Error('x.ai profile submit button not found.'); simulateGrokClick(button); return { submitted: true, state: 'profile_submitted', url: location.href }; } @@ -313,7 +313,7 @@ async function executeGrokCommand(command, payload = {}) { case 'GET_PAGE_STATE': return { state: getGrokPageState(), url: location.href }; default: - throw new Error(`未知 Grok 注册命令:${command}`); + throw new Error(`Unknown Grok registration command: ${command}`); } } diff --git a/flows/grok/index.js b/flows/grok/index.js index ed2aba16..c7877624 100644 --- a/flows/grok/index.js +++ b/flows/grok/index.js @@ -32,7 +32,7 @@ supportsLuckmail: false, canSwitchFlow: true, stepDefinitionMode: 'grok', - targetSelectorLabel: '来源', + targetSelectorLabel: 'Source', }, baseGroups: ['grok-runtime-status', 'shared-auto-run', 'shared-settings-actions'], targets: { @@ -53,7 +53,7 @@ 'grok-register-page': { flowId: 'grok', kind: 'flow-page', - label: 'Grok 注册页', + label: 'Grok signup page', readyPolicy: 'top-frame-only', family: 'grok-register-page-family', driverId: 'flows/grok/content/register-page', @@ -144,7 +144,7 @@ }, 'grok-runtime-status': { id: 'grok-runtime-status', - label: 'Grok 运行态', + label: 'Grok runtime status', rowIds: [ 'row-grok-register-status', 'row-grok-sso-status', diff --git a/flows/grok/mail-rules.js b/flows/grok/mail-rules.js index aa2b501a..ef40b209 100644 --- a/flows/grok/mail-rules.js +++ b/flows/grok/mail-rules.js @@ -118,7 +118,7 @@ function getRuleDefinitionForNode(nodeId, state = {}) { const normalizedNodeId = cleanString(nodeId); if (normalizedNodeId && normalizedNodeId !== SUBMIT_VERIFICATION_CODE_NODE_ID) { - throw new Error(`Grok 邮件规则不支持节点:${normalizedNodeId}`); + throw new Error(`Grok mail rules do not support node: ${normalizedNodeId}`); } return getRuleDefinition({ nodeId: SUBMIT_VERIFICATION_CODE_NODE_ID }, state); } diff --git a/flows/grok/workflow.js b/flows/grok/workflow.js index 41a66cd1..ee3c987d 100644 --- a/flows/grok/workflow.js +++ b/flows/grok/workflow.js @@ -17,7 +17,7 @@ id: 1, order: 10, key: 'grok-open-signup-page', - title: '打开 Grok 注册页', + title: 'Open Grok signup page', sourceId: 'grok-register-page', driverId: 'flows/grok/background/register-runner', command: 'grok-open-signup-page', @@ -27,7 +27,7 @@ id: 2, order: 20, key: 'grok-submit-email', - title: '获取邮箱并继续', + title: 'Get email and continue', sourceId: 'grok-register-page', driverId: 'flows/grok/background/register-runner', command: 'grok-submit-email', @@ -37,7 +37,7 @@ id: 3, order: 30, key: 'grok-submit-verification-code', - title: '获取验证码并继续', + title: 'Get verification code and continue', sourceId: 'grok-register-page', driverId: 'flows/grok/background/register-runner', command: 'grok-submit-verification-code', @@ -48,7 +48,7 @@ id: 4, order: 40, key: 'grok-submit-profile', - title: '填写资料并继续', + title: 'Fill in profile and continue', sourceId: 'grok-register-page', driverId: 'flows/grok/background/register-runner', command: 'grok-submit-profile', @@ -58,7 +58,7 @@ id: 5, order: 50, key: 'grok-extract-sso-cookie', - title: '提取 SSO Cookie', + title: 'Extract SSO Cookie', sourceId: 'grok-register-page', driverId: 'flows/grok/background/register-runner', command: 'grok-extract-sso-cookie', @@ -68,7 +68,7 @@ id: 6, order: 60, key: 'grok-upload-sso-to-webchat2api', - title: '上传 SSO 到 webchat2api', + title: 'Upload SSO to webchat2api', sourceId: 'grok-webchat2api', driverId: 'flows/grok/background/publisher-webchat2api', command: 'grok-upload-sso-to-webchat2api', diff --git a/flows/kiro/background/credential-artifact.js b/flows/kiro/background/credential-artifact.js index 73cbe650..889d7e41 100644 --- a/flows/kiro/background/credential-artifact.js +++ b/flows/kiro/background/credential-artifact.js @@ -80,10 +80,10 @@ const clientId = cleanString(desktopAuth.clientId); const clientSecret = String(desktopAuth.clientSecret || ''); - assertRequiredField('refreshToken', refreshToken, '缺少桌面授权 refreshToken,无法提交 Kiro Builder ID 贡献。'); - assertRequiredField('clientId', clientId, '缺少桌面授权 clientId,无法提交 Kiro Builder ID 贡献。'); - assertRequiredField('clientSecret', clientSecret, '缺少桌面授权 clientSecret,无法提交 Kiro Builder ID 贡献。'); - assertRequiredField('email', email, '缺少注册邮箱,无法提交 Kiro Builder ID 贡献。'); + assertRequiredField('refreshToken', refreshToken, 'Missing desktop authorization refreshToken, cannot submit Kiro Builder ID contribution.'); + assertRequiredField('clientId', clientId, 'Missing desktop authorization clientId, cannot submit Kiro Builder ID contribution.'); + assertRequiredField('clientSecret', clientSecret, 'Missing desktop authorization clientSecret, cannot submit Kiro Builder ID contribution.'); + assertRequiredField('email', email, 'Missing registration email, cannot submit Kiro Builder ID contribution.'); return { schemaVersion: 1, diff --git a/flows/kiro/background/desktop-authorize-runner.js b/flows/kiro/background/desktop-authorize-runner.js index 3d7117ba..b612b8c2 100644 --- a/flows/kiro/background/desktop-authorize-runner.js +++ b/flows/kiro/background/desktop-authorize-runner.js @@ -133,7 +133,7 @@ } function getErrorMessage(error) { - return error instanceof Error ? error.message : String(error ?? '未知错误'); + return error instanceof Error ? error.message : String(error ?? 'Unknown error'); } function isKiroWebUrl(rawUrl = '') { @@ -178,7 +178,7 @@ return { url: normalizedUrl, state: stateValue, - error: `回调 state 不匹配:expected=${cleanString(expectedState)} actual=${stateValue}`, + error: `Callback state mismatch: expected=${cleanString(expectedState)} actual=${stateValue}`, }; } const error = cleanString(parsed.searchParams.get('error_description') || parsed.searchParams.get('error')); @@ -253,7 +253,7 @@ installListeners(); const expectedState = cleanString(params.expectedState); if (!expectedState) { - throw new Error('缺少桌面授权 state,无法注册回调监听。'); + throw new Error('Missing desktop authorization state, cannot register callback listener.'); } const existingResolved = resolvedSessions.get(expectedState); const existingPending = pendingSessions.get(expectedState); @@ -286,7 +286,7 @@ } const session = pendingSessions.get(stateKey); if (!session) { - return Promise.reject(new Error(`未注册桌面授权回调监听:${stateKey}`)); + return Promise.reject(new Error(`Desktop authorization callback listener not registered: ${stateKey}`)); } return new Promise((resolve, reject) => { const timer = setTimeout(() => { @@ -295,7 +295,7 @@ nextSession.waiters = (nextSession.waiters || []).filter((entry) => entry.reject !== reject); pendingSessions.set(stateKey, nextSession); } - reject(new Error('等待桌面授权回调超时。')); + reject(new Error('Timed out waiting for desktop authorization callback.')); }, Math.max(1000, Math.floor(Number(timeoutMs) || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS))); session.waiters.push({ resolve: (result) => { @@ -318,7 +318,7 @@ } const session = pendingSessions.get(stateKey); if (session && Array.isArray(session.waiters)) { - session.waiters.forEach(({ reject }) => reject(new Error('桌面授权回调监听已清理。'))); + session.waiters.forEach(({ reject }) => reject(new Error('Desktop authorization callback listener cleared.'))); } pendingSessions.delete(stateKey); resolvedSessions.delete(stateKey); @@ -414,12 +414,12 @@ async function finalizeDesktopAuthorizeCallback(currentState = {}, runtimeState = {}, resolvedCallback = {}, nodeId = '') { if (resolvedCallback?.error) { - throw new Error(`桌面授权回调失败:${resolvedCallback.error}`); + throw new Error(`Desktop authorization callback failed: ${resolvedCallback.error}`); } const authorizationCode = cleanString(resolvedCallback?.code); if (!authorizationCode) { - throw new Error('桌面授权回调缺少 authorization code。'); + throw new Error('Desktop authorization callback missing authorization code.'); } const tokenResult = await desktopClientApi.exchangeDesktopAuthorizationCode({ @@ -449,7 +449,7 @@ error: '', }, }); - await log('步骤 8:桌面授权回调已捕获,Token 换取成功。', 'ok', nodeId); + await log('Step 8: desktop authorization callback captured, token exchange succeeded.', 'ok', nodeId); await completeNodeFromBackground(nodeId, payload); return payload; } @@ -465,11 +465,11 @@ return tabId; } if (!authorizeUrl) { - throw new Error(options.missingUrlMessage || '缺少桌面授权地址,请先执行步骤 7。'); + throw new Error(options.missingUrlMessage || 'Missing desktop authorization URL, please run step 7 first.'); } tabId = await reuseOrCreateTab(KIRO_DESKTOP_SOURCE_ID, authorizeUrl); if (!Number.isInteger(tabId)) { - throw new Error(options.openFailedMessage || '无法打开桌面授权页,请重试步骤 7。'); + throw new Error(options.openFailedMessage || 'Unable to open desktop authorization page, please retry step 7.'); } await registerTab(KIRO_DESKTOP_SOURCE_ID, tabId); await setState(mergeRuntimePatch(state, { @@ -488,7 +488,7 @@ async function reattachDesktopAuthorizePage(tabId, options = {}) { if (!Number.isInteger(tabId)) { - throw new Error('缺少 Kiro 桌面授权页标签页,无法重新连接内容脚本。'); + throw new Error('Missing Kiro desktop authorization page tab, cannot reconnect content script.'); } const timeoutBudget = resolveTimeoutBudget(options); if (typeof waitForTabStableComplete === 'function') { @@ -505,7 +505,7 @@ injectSource: KIRO_DESKTOP_SOURCE_ID, timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, - logMessage: options.injectLogMessage || 'Kiro 桌面授权页已跳转,正在重新连接内容脚本...', + logMessage: options.injectLogMessage || 'Kiro desktop authorization page navigated, reconnecting content script...', }); } } @@ -522,14 +522,14 @@ timeoutBudget: createTimeoutBudget(remainingTimeoutMs), stableMs: Number(options.recoveryStableMs) || Number(options.stableMs) || 1200, initialDelayMs: Number(options.recoveryInitialDelayMs) || 120, - injectLogMessage: options.recoveryInjectLogMessage || options.injectLogMessage || 'Kiro 桌面授权页已跳转,正在重新连接内容脚本...', + injectLogMessage: options.recoveryInjectLogMessage || options.injectLogMessage || 'Kiro desktop authorization page navigated, reconnecting content script...', }); }; } async function getDesktopAuthorizePageState(tabId, options = {}) { if (!Number.isInteger(tabId)) { - throw new Error('缺少 Kiro 桌面授权页标签页,无法继续执行。'); + throw new Error('Missing Kiro desktop authorization page tab, cannot continue.'); } const pageLoadTimeoutMs = normalizeKiroPageLoadTimeoutMs( options.pageTimeoutMs, @@ -550,7 +550,7 @@ injectSource: KIRO_DESKTOP_SOURCE_ID, timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, - logMessage: options.injectLogMessage || 'Kiro 桌面授权页内容脚本未就绪,正在等待页面恢复...', + logMessage: options.injectLogMessage || 'Kiro desktop authorization page content script not ready, waiting for page recovery...', }); } const stateWaitTimeoutMs = timeoutBudget.getRemainingMs(1000); @@ -565,7 +565,7 @@ ...options, timeoutBudget, }), - logMessage: options.readyLogMessage || '正在读取 Kiro 桌面授权页状态...', + logMessage: options.readyLogMessage || 'Reading Kiro desktop authorization page state...', }); if (result?.error) { throw new Error(result.error); @@ -590,7 +590,7 @@ ...options, timeoutBudget, }), - logMessage: options.logMessage || '正在执行 Kiro 桌面授权动作...', + logMessage: options.logMessage || 'Executing Kiro desktop authorization action...', }); if (result?.error) { throw new Error(result.error); @@ -601,7 +601,7 @@ function resolveDesktopLoginPassword(state = {}) { const password = String(state?.customPassword || state?.password || ''); if (!password) { - throw new Error('缺少已注册账号密码,无法完成桌面授权重登。'); + throw new Error('Missing registered account password, cannot complete desktop authorization re-login.'); } return password; } @@ -657,7 +657,7 @@ }); } if (!Number.isInteger(tabId)) { - throw new Error('无法打开 Kiro 账号页,请手动打开 app.kiro.dev/settings/account 后重试步骤 7。'); + throw new Error('Unable to open Kiro account page, please manually open app.kiro.dev/settings/account and retry step 7.'); } await registerTab(KIRO_REGISTER_PAGE_SOURCE_ID, tabId); return { @@ -682,7 +682,7 @@ injectSource: KIRO_REGISTER_PAGE_SOURCE_ID, timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, - logMessage: options.injectLogMessage || '步骤 7:正在连接已登录的 Kiro Web 页面...', + logMessage: options.injectLogMessage || 'Step 7: connecting to logged-in Kiro Web page...', }); } if (typeof sendToContentScriptResilient !== 'function') { @@ -697,7 +697,7 @@ timeoutMs: stateWaitTimeoutMs, retryDelayMs: 700, responseTimeoutMs: Math.min(stateWaitTimeoutMs, 10000), - logMessage: '步骤 7:正在读取 Kiro Web 登录态...', + logMessage: 'Step 7: reading Kiro Web sign-in state...', }); if (result?.error) { throw new Error(result.error); @@ -730,7 +730,7 @@ try { const pageState = await readKiroWebSessionStateFromTab(tabId, { timeoutMs: DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, - injectLogMessage: '步骤 7:Kiro Web 页面内容脚本未就绪,正在等待页面恢复...', + injectLogMessage: 'Step 7: Kiro Web page content script not ready, waiting for page recovery...', }); if (pageState?.state !== 'kiro_web_signed_in') { return null; @@ -772,7 +772,7 @@ ...currentState, ...payload, }; - await log(`步骤 7:检测到已有 Kiro Web 登录态,已恢复账号 ${detectedEmail},继续启动桌面授权。`, 'ok', nodeId); + await log(`Step 7: detected existing Kiro Web sign-in, restored account ${detectedEmail}, continuing with desktop authorization.`, 'ok', nodeId); return { currentState: nextState, runtimeState: readKiroRuntime(nextState), @@ -797,7 +797,7 @@ } } - await log('步骤 7:未能从已打开页面确认 Kiro Web 登录态,正在打开 Kiro 账号页重新确认...', 'info', nodeId); + await log('Step 7: could not confirm Kiro Web sign-in from open tabs, opening Kiro account page to re-confirm...', 'info', nodeId); const accountTab = await openKiroWebAccountSessionTab(); const restoredSession = await tryRestoreFromTab(accountTab); if (restoredSession) { @@ -805,15 +805,15 @@ } if (detectedSignedInWithoutEmail) { - throw new Error('已检测到 Kiro Web 登录态,但未能识别账号邮箱。请打开 Kiro 账号设置页后重试步骤 7。'); + throw new Error('Detected Kiro Web sign-in, but could not identify account email. Please open the Kiro account settings page and retry step 7.'); } - const detail = lastRecoveryError ? `最后一次检测错误:${lastRecoveryError}` : ''; - throw new Error(`Kiro Web 登录态尚未建立。请在自动打开的 Kiro 账号页登录后,从步骤 7 继续。${detail}`); + const detail = lastRecoveryError ? `Last detection error: ${lastRecoveryError}` : ''; + throw new Error(`Kiro Web sign-in not yet established. After signing in on the auto-opened Kiro account page, resume from step 7. ${detail}`); } async function pollDesktopOtpCode(step, state = {}, nodeId = '') { if (typeof pollFlowVerificationCode !== 'function') { - throw new Error('Kiro 桌面授权验证码步骤缺少共享邮件轮询能力,无法继续执行。'); + throw new Error('Kiro desktop authorization OTP step missing shared mail polling, cannot continue.'); } const runtimeState = readKiroRuntime(state); @@ -824,14 +824,14 @@ : requestedAt; return pollFlowVerificationCode({ - actionLabel: '桌面授权验证码', + actionLabel: 'Desktop authorization OTP', filterAfterTimestamp, flowId: 'kiro', logStep: step, logStepKey: 'kiro-complete-desktop-authorize', - missingCapabilityMessage: 'Kiro 桌面授权验证码步骤缺少共享邮件轮询能力,无法继续执行。', + missingCapabilityMessage: 'Kiro desktop authorization OTP step missing shared mail polling, cannot continue.', nodeId: 'kiro-complete-desktop-authorize', - notFoundMessage: `步骤 ${step}:邮箱轮询结束,但未获取到桌面授权验证码。`, + notFoundMessage: `Step ${step}: mailbox polling ended without obtaining desktop authorization OTP.`, state: { ...state, activeFlowId: 'kiro', @@ -873,7 +873,7 @@ const tabId = await reuseOrCreateTab(KIRO_DESKTOP_SOURCE_ID, authorizeUrl); if (!Number.isInteger(tabId)) { - throw new Error('无法打开 Kiro 桌面授权页,请重试步骤 7。'); + throw new Error('Unable to open Kiro desktop authorization page, please retry step 7.'); } await registerTab(KIRO_DESKTOP_SOURCE_ID, tabId); callbackTracker.registerPending({ @@ -919,7 +919,7 @@ }, }); await activateTab(tabId); - await log('步骤 7:Kiro 桌面授权页已打开,下一步将继续完成授权并抓取回调。', 'ok', nodeId); + await log('Step 7: Kiro desktop authorization page opened. Next step will complete authorization and capture the callback.', 'ok', nodeId); await completeNodeFromBackground(nodeId, payload); } catch (error) { const message = getErrorMessage(error); @@ -935,16 +935,16 @@ const desktopState = cleanString(runtimeState.desktopAuth?.state); try { if (!desktopState) { - throw new Error('缺少桌面授权 state,请先执行步骤 7。'); + throw new Error('Missing desktop authorization state, please run step 7 first.'); } if (!cleanString(runtimeState.desktopAuth?.clientId) || !cleanString(runtimeState.desktopAuth?.clientSecret)) { - throw new Error('缺少桌面授权客户端凭据,请先执行步骤 7。'); + throw new Error('Missing desktop authorization client credentials, please run step 7 first.'); } if (!cleanString(runtimeState.desktopAuth?.redirectUri) || !runtimeState.desktopAuth?.redirectPort) { - throw new Error('缺少桌面授权回调地址,请先执行步骤 7。'); + throw new Error('Missing desktop authorization callback URL, please run step 7 first.'); } if (!cleanString(runtimeState.desktopAuth?.codeVerifier)) { - throw new Error('缺少桌面授权 PKCE verifier,请先执行步骤 7。'); + throw new Error('Missing desktop authorization PKCE verifier, please run step 7 first.'); } callbackTracker.registerPending({ @@ -1017,8 +1017,8 @@ } } else { tabId = await activateDesktopAuthorizeTab(currentState, { - missingUrlMessage: '缺少桌面授权地址,请先执行步骤 7。', - openFailedMessage: '无法恢复桌面授权页,请重新执行步骤 7。', + missingUrlMessage: 'Missing desktop authorization URL, please run step 7 first.', + openFailedMessage: 'Unable to restore desktop authorization page, please re-run step 7.', }); } @@ -1027,8 +1027,8 @@ pageState = await getDesktopAuthorizePageState(tabId, { step: 8, timeoutBudget, - injectLogMessage: '步骤 8:Kiro 桌面授权页内容脚本未就绪,正在等待页面恢复...', - readyLogMessage: '步骤 8:正在读取 Kiro 桌面授权页当前状态...', + injectLogMessage: 'Step 8: Kiro desktop authorization page content script not ready, waiting for page recovery...', + readyLogMessage: 'Step 8: reading current state of Kiro desktop authorization page...', }); } catch (error) { if (awaitingCallbackAfterConsent && isMissingTabError(error)) { @@ -1048,11 +1048,11 @@ if (pageState.state === 'relogin_email') { const email = cleanString(runtimeState.register?.email || currentState?.email); - await log(`步骤 8:桌面授权页要求重新输入邮箱,正在填写 ${email}...`, 'info', nodeId); + await log(`Step 8: desktop authorization page requires email re-entry, filling in ${email}...`, 'info', nodeId); await executeDesktopAction(tabId, 'submit-email', { email }, { step: 8, timeoutBudget, - logMessage: '步骤 8:正在向桌面授权页提交邮箱...', + logMessage: 'Step 8: submitting email to desktop authorization page...', }); await sleepWithStop(1200); continue; @@ -1060,11 +1060,11 @@ if (pageState.state === 'relogin_password') { const password = resolveDesktopLoginPassword(currentState); - await log('步骤 8:桌面授权页要求重新输入密码,正在填写密码...', 'info', nodeId); + await log('Step 8: desktop authorization page requires password re-entry, filling in password...', 'info', nodeId); await executeDesktopAction(tabId, 'submit-password', { password }, { step: 8, timeoutBudget, - logMessage: '步骤 8:正在向桌面授权页提交密码...', + logMessage: 'Step 8: submitting password to desktop authorization page...', }); await sleepWithStop(1200); continue; @@ -1082,24 +1082,24 @@ const codeResult = await pollDesktopOtpCode(8, currentState, nodeId); const code = cleanString(codeResult?.code); if (!code) { - throw new Error('未获取到桌面授权验证码。'); + throw new Error('Failed to obtain desktop authorization OTP.'); } - await log(`步骤 8:已获取桌面授权验证码 ${code},正在提交...`, 'info', nodeId); + await log(`Step 8: obtained desktop authorization OTP ${code}, submitting...`, 'info', nodeId); await executeDesktopAction(tabId, 'submit-otp', { code }, { step: 8, timeoutBudget, - logMessage: '步骤 8:正在向桌面授权页提交验证码...', + logMessage: 'Step 8: submitting OTP to desktop authorization page...', }); await sleepWithStop(1200); continue; } if (pageState.state === 'consent_page') { - await log('步骤 8:正在确认 Kiro 桌面授权访问...', 'info', nodeId); + await log('Step 8: confirming Kiro desktop authorization access...', 'info', nodeId); await executeDesktopAction(tabId, 'confirm-consent', {}, { step: 8, timeoutBudget, - logMessage: '步骤 8:正在确认桌面授权访问...', + logMessage: 'Step 8: confirming desktop authorization access...', }); awaitingCallbackAfterConsent = true; await sleepWithStop(1200); @@ -1126,7 +1126,7 @@ return; } - throw new Error('等待桌面授权回调超时。'); + throw new Error('Timed out waiting for desktop authorization callback.'); } catch (error) { callbackTracker.clear(desktopState); const message = getErrorMessage(error); diff --git a/flows/kiro/background/desktop-client.js b/flows/kiro/background/desktop-client.js index c10b27ca..52f26ff8 100644 --- a/flows/kiro/background/desktop-client.js +++ b/flows/kiro/background/desktop-client.js @@ -80,7 +80,7 @@ function buildRedirectUri(port) { const normalizedPort = Math.max(1, Math.floor(Number(port) || 0)); if (!normalizedPort) { - throw new Error('缺少桌面授权回调端口。'); + throw new Error('Missing desktop authorization callback port.'); } return `http://127.0.0.1:${normalizedPort}/oauth/callback`; } @@ -107,13 +107,13 @@ }); const body = await readResponse(response); if (!response.ok) { - throw new Error(`Kiro 桌面客户端注册失败:${cleanString(body.text || response.statusText) || response.status}`); + throw new Error(`Kiro desktop client registration failed: ${cleanString(body.text || response.statusText) || response.status}`); } const clientId = cleanString(body.json?.clientId); const clientSecret = String(body.json?.clientSecret || ''); if (!clientId || !clientSecret) { - throw new Error('Kiro 桌面客户端注册响应缺少 clientId 或 clientSecret。'); + throw new Error('Kiro desktop client registration response missing clientId or clientSecret.'); } return { @@ -164,13 +164,13 @@ }); const body = await readResponse(response); if (!response.ok) { - throw new Error(`Kiro 桌面授权换取 Token 失败:${cleanString(body.text || response.statusText) || response.status}`); + throw new Error(`Kiro desktop authorization token exchange failed: ${cleanString(body.text || response.statusText) || response.status}`); } const accessToken = String(body.json?.accessToken || ''); const refreshToken = String(body.json?.refreshToken || ''); if (!accessToken || !refreshToken) { - throw new Error('Kiro 桌面授权换取 Token 响应缺少 accessToken 或 refreshToken。'); + throw new Error('Kiro desktop authorization token exchange response missing accessToken or refreshToken.'); } return { diff --git a/flows/kiro/background/publisher-kiro-rs.js b/flows/kiro/background/publisher-kiro-rs.js index 5ea10029..27d08636 100644 --- a/flows/kiro/background/publisher-kiro-rs.js +++ b/flows/kiro/background/publisher-kiro-rs.js @@ -70,7 +70,7 @@ function normalizeKiroRsBaseUrl(value = '') { const normalized = cleanString(value).replace(/\/+$/, ''); if (!normalized) { - throw new Error('缺少 kiro.rs 管理后台地址。'); + throw new Error('Missing kiro.rs admin URL.'); } return normalized.endsWith('/admin') ? normalized.slice(0, -'/admin'.length) @@ -80,18 +80,18 @@ function normalizeKiroUploadMessage(value = '') { const rawValue = cleanString(value); if (!rawValue) { - return '上传成功'; + return 'Upload succeeded'; } const normalizedValue = rawValue.toLowerCase(); if (normalizedValue === 'uploaded' || normalizedValue === 'credential uploaded.') { - return '上传成功'; + return 'Upload succeeded'; } return rawValue; } function getErrorMessage(error) { - return error instanceof Error ? error.message : String(error ?? '未知错误'); + return error instanceof Error ? error.message : String(error ?? 'Unknown error'); } async function readResponse(response) { @@ -152,7 +152,7 @@ function resolveKiroTargetConfig(state = {}, targetId = DEFAULT_TARGET_ID) { if (targetId !== DEFAULT_TARGET_ID) { - throw new Error(`暂不支持 Kiro 发布目标:${targetId}`); + throw new Error(`Kiro publish target not supported: ${targetId}`); } const nestedConfig = state?.settingsState?.flows?.kiro?.targets?.[targetId] || {}; return { @@ -193,7 +193,7 @@ async function buildMachineId(refreshToken = '') { const normalizedRefreshToken = cleanString(refreshToken); if (!normalizedRefreshToken) { - throw new Error('缺少 refreshToken,无法生成 machineId。'); + throw new Error('Missing refreshToken, cannot generate machineId.'); } return sha256Hex(`KotlinNativeAPI/${normalizedRefreshToken}`); } @@ -214,13 +214,13 @@ const email = cleanString(register.email || state?.email); if (!refreshToken) { - throw new Error('缺少桌面授权 refreshToken,请先完成步骤 8。'); + throw new Error('Missing desktop authorization refreshToken, please complete step 8 first.'); } if (!clientId || !clientSecret) { - throw new Error('缺少桌面授权 clientId 或 clientSecret,请先完成步骤 7-8。'); + throw new Error('Missing desktop authorization clientId or clientSecret, please complete steps 7-8 first.'); } if (!email) { - throw new Error('缺少注册邮箱,无法上传到 kiro.rs。'); + throw new Error('Missing registration email, cannot upload to kiro.rs.'); } return { @@ -253,34 +253,34 @@ return { ok: true, status: response.status, - message: `kiro.rs 连接正常(HTTP ${response.status})`, + message: `kiro.rs connection OK (HTTP ${response.status})`, }; } if (response.status === 405) { return { ok: true, status: response.status, - message: 'kiro.rs 上传接口可访问。', + message: 'kiro.rs upload endpoint is reachable.', }; } if (response.status === 401 || response.status === 403) { return { ok: false, status: response.status, - message: `kiro.rs API Key 被拒绝(HTTP ${response.status}${detail ? `:${detail}` : ''})`, + message: `kiro.rs API key rejected (HTTP ${response.status}${detail ? `: ${detail}` : ''})`, }; } if (response.status === 404) { return { ok: false, status: response.status, - message: `未找到 kiro.rs 管理接口(HTTP 404${detail ? `:${detail}` : ''})`, + message: `kiro.rs admin endpoint not found (HTTP 404${detail ? `: ${detail}` : ''})`, }; } return { ok: false, status: response.status, - message: detail || `kiro.rs 连接失败(HTTP ${response.status})`, + message: detail || `kiro.rs connection failed (HTTP ${response.status})`, }; } @@ -298,7 +298,7 @@ const body = await readResponse(response); if (!response.ok) { const message = readKiroRsResponseMessage(body, response.statusText) || `HTTP ${response.status}`; - throw new Error(`kiro.rs 凭据上传失败:${message}`); + throw new Error(`kiro.rs credential upload failed: ${message}`); } return { @@ -374,13 +374,13 @@ }, }); - await log('步骤 9:正在上传 Builder ID 到贡献池...', 'info', nodeId); + await log('Step 9: uploading Builder ID to contribution pool...', 'info', nodeId); const contributionResult = await maybeSubmitFlowContribution(currentState, { nodeId, trigger: 'kiro-step-9', }); if (!contributionResult?.ok || contributionResult?.skipped) { - throw new Error(contributionResult?.message || 'Kiro 贡献上传失败。'); + throw new Error(contributionResult?.message || 'Kiro contribution upload failed.'); } const uploadedAt = Date.now(); @@ -394,11 +394,11 @@ status: 'uploaded', error: '', credentialId: contributionResult.contributionId || '', - lastMessage: contributionResult.message || '贡献上传成功', + lastMessage: contributionResult.message || 'Contribution uploaded', lastUploadedAt: uploadedAt, }, }); - await log(`步骤 9:贡献上传完成,状态:${contributionResult.message || '贡献上传成功'}`, 'ok', nodeId); + await log(`Step 9: contribution upload complete, status: ${contributionResult.message || 'Contribution uploaded'}`, 'ok', nodeId); await completeNodeFromBackground(nodeId, payload); return; } @@ -408,7 +408,7 @@ const baseUrl = normalizeKiroRsBaseUrl(targetConfig.baseUrl); const apiKey = String(targetConfig.apiKey || ''); if (!apiKey) { - throw new Error('缺少 kiro.rs API Key。'); + throw new Error('Missing kiro.rs API key.'); } const uploadInput = buildUploadPayload(currentState); @@ -427,7 +427,7 @@ }, }); - await log('步骤 9:正在上传 Builder ID 凭据到 kiro.rs...', 'info', nodeId); + await log('Step 9: uploading Builder ID credential to kiro.rs...', 'info', nodeId); const connection = await checkKiroRsConnection(baseUrl, apiKey, fetchImpl); if (!connection.ok) { @@ -461,11 +461,11 @@ status: 'uploaded', error: '', credentialId: uploadResult.credentialId, - lastMessage: uploadResult.message || '上传成功', + lastMessage: uploadResult.message || 'Upload succeeded', lastUploadedAt: uploadedAt, }, }); - await log(`步骤 9:kiro.rs 上传完成,状态:${uploadResult.message || '上传成功'}`, 'ok', nodeId); + await log(`Step 9: kiro.rs upload complete, status: ${uploadResult.message || 'Upload succeeded'}`, 'ok', nodeId); await completeNodeFromBackground(nodeId, payload); } catch (error) { const message = getErrorMessage(error); diff --git a/flows/kiro/background/register-runner.js b/flows/kiro/background/register-runner.js index 5e345a10..d2d0da65 100644 --- a/flows/kiro/background/register-runner.js +++ b/flows/kiro/background/register-runner.js @@ -194,7 +194,7 @@ } function getErrorMessage(error) { - return error instanceof Error ? error.message : String(error ?? '未知错误'); + return error instanceof Error ? error.message : String(error ?? 'Unknown error'); } function normalizeKiroCookieDomain(domain = '') { @@ -477,11 +477,11 @@ async function clearKiroCookiesBeforeStep1() { if (!chrome?.cookies?.getAll || !chrome.cookies?.remove) { - await log('步骤 1:当前浏览器不支持 cookies API,跳过打开 Kiro 注册页前的 cookie 清理。', 'warn'); + await log('Step 1: cookies API not supported by this browser, skipping cookie cleanup before opening Kiro registration page.', 'warn'); return; } - await log('步骤 1:打开 Kiro 注册页前清理 AWS Builder ID 相关 cookies...', 'info'); + await log('Step 1: clearing AWS Builder ID related cookies before opening Kiro registration page...', 'info'); const cookies = await collectKiroStep1Cookies(chrome); let removedCount = 0; for (const cookie of cookies) { @@ -497,11 +497,11 @@ origins: KIRO_STEP1_COOKIE_CLEAR_ORIGINS, }); } catch (error) { - await log(`步骤 1:browsingData 补扫 cookies 失败:${getErrorMessage(error)}`, 'warn'); + await log(`Step 1: browsingData cookie sweep failed: ${getErrorMessage(error)}`, 'warn'); } } - await log(`步骤 1:已清理 ${removedCount} 个 AWS Builder ID 相关 cookies。`, 'ok'); + await log(`Step 1: cleared ${removedCount} AWS Builder ID related cookies.`, 'ok'); } async function ensureKiroRegisterTab(state = {}, options = {}) { @@ -528,12 +528,12 @@ } if (!loginUrl) { - throw new Error(options.missingUrlMessage || '缺少 Kiro 注册页地址,请先执行步骤 1。'); + throw new Error(options.missingUrlMessage || 'Missing Kiro registration page URL, please run step 1 first.'); } tabId = await reuseOrCreateTab(KIRO_REGISTER_PAGE_SOURCE_ID, loginUrl); if (!Number.isInteger(tabId)) { - throw new Error(options.openFailedMessage || '无法打开 Kiro 注册页,请重试步骤 1。'); + throw new Error(options.openFailedMessage || 'Unable to open Kiro registration page, please retry step 1.'); } await registerTab(KIRO_REGISTER_PAGE_SOURCE_ID, tabId); await setState(mergeRuntimePatch(state, { @@ -591,11 +591,11 @@ ...options, timeoutBudget, }), - logMessage: options.readyLogMessage || '正在等待 Kiro 页面进入下一状态...', + logMessage: options.readyLogMessage || 'Waiting for Kiro page to advance to the next state...', }); if (!hasKiroRegisterPageState(result) && !result?.error) { - await log('Kiro 注册页通用脚本已响应,但专用页面识别脚本未返回状态,正在重新注入 Kiro 注册页识别脚本...', 'warn'); + await log('Kiro registration page common script responded, but the page-specific detection script did not return a state. Re-injecting Kiro registration page detection script...', 'warn'); await injectKiroRegisterContentScripts(tabId); result = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, message, { timeoutMs: timeoutBudget?.getRemainingMs?.(1000) || timeoutMs, @@ -604,7 +604,7 @@ ...options, timeoutBudget, }), - logMessage: options.readyLogMessage || '正在等待 Kiro 页面进入下一状态...', + logMessage: options.readyLogMessage || 'Waiting for Kiro page to advance to the next state...', }); } @@ -612,14 +612,14 @@ throw new Error(result.error); } if (!hasKiroRegisterPageState(result)) { - throw new Error('Kiro 注册页专用页面识别脚本未返回页面状态,请刷新当前 AWS 页面或重新执行当前步骤。'); + throw new Error('Kiro registration page detection script did not return a page state. Please refresh the current AWS page or re-run the current step.'); } return result; } async function reattachKiroRegisterPage(tabId, options = {}) { if (!Number.isInteger(tabId)) { - throw new Error('缺少 Kiro 注册页标签页,无法重新连接内容脚本。'); + throw new Error('Missing Kiro registration page tab, cannot reconnect content script.'); } const timeoutBudget = resolveTimeoutBudget(options); if (typeof waitForTabStableComplete === 'function') { @@ -636,7 +636,7 @@ injectSource: KIRO_REGISTER_PAGE_SOURCE_ID, timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, - logMessage: options.injectLogMessage || 'Kiro 注册页已跳转,正在重新连接内容脚本...', + logMessage: options.injectLogMessage || 'Kiro registration page navigated, reconnecting content script...', }); } } @@ -653,14 +653,14 @@ timeoutBudget: createTimeoutBudget(remainingTimeoutMs), stableMs: Number(options.recoveryStableMs) || Number(options.stableMs) || 1200, initialDelayMs: Number(options.recoveryInitialDelayMs) || 120, - injectLogMessage: options.recoveryInjectLogMessage || options.injectLogMessage || 'Kiro 注册页已跳转,正在重新连接内容脚本...', + injectLogMessage: options.recoveryInjectLogMessage || options.injectLogMessage || 'Kiro registration page navigated, reconnecting content script...', }); }; } async function ensureKiroPageState(tabId, options = {}) { if (!Number.isInteger(tabId)) { - throw new Error('缺少 Kiro 注册页标签页,无法继续执行。'); + throw new Error('Missing Kiro registration page tab, cannot continue.'); } const pageLoadTimeoutMs = normalizeKiroPageLoadTimeoutMs( options.pageTimeoutMs, @@ -681,7 +681,7 @@ injectSource: KIRO_REGISTER_PAGE_SOURCE_ID, timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, - logMessage: options.injectLogMessage || 'Kiro 注册页内容脚本未就绪,正在等待页面恢复...', + logMessage: options.injectLogMessage || 'Kiro registration page content script not ready, waiting for page recovery...', }); } if (typeof sendToContentScriptResilient !== 'function') { @@ -712,7 +712,7 @@ async function waitForKiroPageChange(tabId, options = {}) { if (!Number.isInteger(tabId)) { - throw new Error('缺少 Kiro 注册页标签页,无法继续执行。'); + throw new Error('Missing Kiro registration page tab, cannot continue.'); } const pageLoadTimeoutMs = normalizeKiroPageLoadTimeoutMs( options.pageTimeoutMs, @@ -733,7 +733,7 @@ injectSource: KIRO_REGISTER_PAGE_SOURCE_ID, timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, - logMessage: options.injectLogMessage || 'Kiro 注册页切换中,正在等待页面恢复...', + logMessage: options.injectLogMessage || 'Kiro registration page is switching, waiting for page recovery...', }); } if (typeof sendToContentScriptResilient !== 'function') { @@ -756,7 +756,7 @@ ...options, timeoutBudget, timeoutMs: stateWaitTimeoutMs, - readyLogMessage: options.readyLogMessage || '正在等待 Kiro 页面完成跳转...', + readyLogMessage: options.readyLogMessage || 'Waiting for Kiro page navigation to complete...', }); } @@ -784,7 +784,7 @@ const email = resolveKiroRegisterEmail(currentState, pageState, fallbackEmail); const emailText = email ? ` ${email}` : ''; return new Error( - `步骤 ${step}:邮箱${emailText} 已进入 AWS Builder ID 登录页,说明该邮箱已存在或被 AWS 判定为已有账号;Kiro 注册流程只处理新账号注册,已停止,请换新邮箱重试。` + `Step ${step}: email${emailText} reached the AWS Builder ID sign-in page, meaning this email already exists or is treated as an existing account by AWS. The Kiro registration flow only handles new account sign-ups and has stopped. Please retry with a new email.` ); } @@ -868,7 +868,7 @@ return cachedName; } if (typeof generateRandomName !== 'function') { - throw new Error('Kiro 姓名步骤缺少随机姓名能力,无法继续执行。'); + throw new Error('Kiro name step missing random-name capability, cannot continue.'); } const generated = generateRandomName(); if (typeof generated === 'string') { @@ -881,7 +881,7 @@ const lastName = cleanString(generated?.lastName); const fullName = cleanString(`${firstName} ${lastName}`); if (!fullName) { - throw new Error('Kiro 姓名步骤未生成有效姓名。'); + throw new Error('Kiro name step did not produce a valid name.'); } return fullName; } @@ -895,7 +895,7 @@ }; } if (typeof generatePassword !== 'function') { - throw new Error('Kiro 密码步骤缺少公共密码生成能力,无法继续执行。'); + throw new Error('Kiro password step missing shared password generator, cannot continue.'); } return { password: String(generatePassword() || ''), @@ -905,7 +905,7 @@ async function pollKiroVerificationCode(step, state = {}, nodeId = '') { if (typeof pollFlowVerificationCode !== 'function') { - throw new Error('Kiro 验证码步骤缺少共享邮件轮询能力,无法继续执行。'); + throw new Error('Kiro verification-code step missing shared mail polling, cannot continue.'); } const runtimeState = readKiroRuntime(state); @@ -917,14 +917,14 @@ : requestedAt; return pollFlowVerificationCode({ - actionLabel: 'Kiro 验证码', + actionLabel: 'Kiro verification code', filterAfterTimestamp, flowId: 'kiro', logStep: step, logStepKey: 'kiro-submit-verification-code', - missingCapabilityMessage: 'Kiro 验证码步骤缺少共享邮件轮询能力,无法继续执行。', + missingCapabilityMessage: 'Kiro verification-code step missing shared mail polling, cannot continue.', nodeId: 'kiro-submit-verification-code', - notFoundMessage: `步骤 ${step}:邮箱轮询结束,但未获取到 Kiro 验证码。`, + notFoundMessage: `Step ${step}: mailbox polling ended without obtaining Kiro verification code.`, state: { ...state, activeFlowId: 'kiro', @@ -943,7 +943,7 @@ const loginUrl = KIRO_SIGNIN_URL; const tabId = await reuseOrCreateTab(KIRO_REGISTER_PAGE_SOURCE_ID, loginUrl); if (!Number.isInteger(tabId)) { - throw new Error('无法打开 Kiro 注册页,请重试步骤 1。'); + throw new Error('Unable to open Kiro registration page, please retry step 1.'); } await registerTab(KIRO_REGISTER_PAGE_SOURCE_ID, tabId); await activateTab(tabId); @@ -953,12 +953,12 @@ targetStates: ['kiro_signin_page', 'email_entry'], stableMs: 2500, initialDelayMs: 300, - injectLogMessage: '步骤 1:Kiro 注册页内容脚本未就绪,正在等待页面恢复...', - readyLogMessage: '步骤 1:正在等待 Kiro 官方登录页加载完成...', + injectLogMessage: 'Step 1: Kiro registration page content script not ready, waiting for page recovery...', + readyLogMessage: 'Step 1: waiting for the Kiro official sign-in page to finish loading...', }); if (landingResult?.state === 'kiro_signin_page') { - await log('步骤 1:正在选择 AWS Builder ID 登录方式...', 'info', nodeId); + await log('Step 1: selecting AWS Builder ID sign-in option...', 'info', nodeId); const selectResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { type: 'EXECUTE_NODE', nodeId: 'kiro-open-register-page', @@ -969,7 +969,7 @@ timeoutMs: 30000, retryDelayMs: 700, onRetryableError: buildKiroRetryRecovery(tabId, {}), - logMessage: '步骤 1:正在点击 Kiro 官方登录页的 Builder ID...', + logMessage: 'Step 1: clicking Builder ID on the Kiro official sign-in page...', }); if (selectResult?.error) { throw new Error(selectResult.error); @@ -979,9 +979,9 @@ targetStates: ['email_entry'], stableMs: 2500, initialDelayMs: 300, - injectLogMessage: '步骤 1:选择 Builder ID 后页面跳转中,正在等待 Kiro 注册页恢复...', - readyLogMessage: '步骤 1:正在等待 Builder ID 邮箱输入框加载完成...', - timeoutMessage: '选择 Builder ID 后未进入邮箱页,请检查当前 Kiro 登录页或代理状态。', + injectLogMessage: 'Step 1: page navigating after Builder ID selection, waiting for Kiro registration page recovery...', + readyLogMessage: 'Step 1: waiting for the Builder ID email input to finish loading...', + timeoutMessage: 'After selecting Builder ID, the email page did not load. Please check the current Kiro sign-in page or proxy state.', }); } @@ -1039,7 +1039,7 @@ }; const payload = await applyRuntimeState(currentState, nextPatch); - await log('Kiro 注册页已就绪,已进入 Builder ID 邮箱页,请在下一步中获取邮箱并继续。', 'ok', nodeId); + await log('Kiro registration page is ready and on the Builder ID email page. Get the email and continue in the next step.', 'ok', nodeId); await completeNodeFromBackground(nodeId, payload); } catch (error) { const message = getErrorMessage(error); @@ -1053,15 +1053,15 @@ const currentState = await getExecutionState(state); try { const tabId = await activateKiroRegisterTab(currentState, { - missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。', - openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。', + missingUrlMessage: 'Missing Kiro registration page URL, please run step 1 first.', + openFailedMessage: 'Unable to restore Kiro registration page, please re-run step 1.', }); const currentPageState = await readKiroRegisterPageState(tabId, { step: 2, stableMs: 2500, initialDelayMs: 300, - injectLogMessage: '步骤 2:Kiro 注册页内容脚本未就绪,正在等待页面恢复...', - readyLogMessage: '步骤 2:正在读取 Kiro 注册页当前状态...', + injectLogMessage: 'Step 2: Kiro registration page content script not ready, waiting for page recovery...', + readyLogMessage: 'Step 2: reading current state of Kiro registration page...', }); assertKiroRegistrationOnlyState(currentPageState, currentState, 2); @@ -1069,7 +1069,7 @@ const runtimeState = readKiroRuntime(currentState); const adoptedEmail = resolveKiroRegisterEmail(currentState, currentPageState); if (!adoptedEmail) { - throw new Error('步骤 2:当前已不在邮箱页,但无法识别注册邮箱,请回到邮箱页重新提交或在配置中填入注册邮箱。'); + throw new Error('Step 2: no longer on the email page but could not identify the registration email. Please return to the email page and resubmit, or set the registration email in config.'); } const status = getKiroRegisterStatusForPageState(currentPageState.state); await adoptKiroRegisterPageState(currentState, currentPageState, nodeId, { @@ -1079,22 +1079,22 @@ ? runtimeState.register?.verificationRequestedAt || 0 : undefined, }); - await log(`步骤 2:检测到当前已进入 ${currentPageState.state},已收养注册进度并继续。`, 'ok', nodeId); + await log(`Step 2: already at ${currentPageState.state}, adopted registration progress and continuing.`, 'ok', nodeId); return; } if (currentPageState?.state !== 'email_entry') { - throw new Error(`步骤 2:当前页面状态为 ${currentPageState?.state || 'unknown'},不是 Kiro 注册邮箱页,请先执行步骤 1 或回到邮箱输入页。`); + throw new Error(`Step 2: current page state is ${currentPageState?.state || 'unknown'}, not the Kiro registration email page. Please run step 1 first or return to the email input page.`); } if (typeof resolveSignupEmailForFlow !== 'function') { - throw new Error('Kiro 邮箱步骤缺少公共邮箱解析能力,无法继续执行。'); + throw new Error('Kiro email step missing shared email resolver, cannot continue.'); } const resolvedEmail = await resolveSignupEmailForFlow(currentState, { preserveAccountIdentity: true, }); - await log(`步骤 2:已获取邮箱 ${resolvedEmail},正在提交到 Kiro 注册页...`, 'info', nodeId); + await log(`Step 2: obtained email ${resolvedEmail}, submitting to Kiro registration page...`, 'info', nodeId); await activateTab(tabId); const submitResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { @@ -1109,7 +1109,7 @@ timeoutMs: 30000, retryDelayMs: 700, onRetryableError: buildKiroRetryRecovery(tabId, {}), - logMessage: '步骤 2:正在向 Kiro 注册页提交邮箱...', + logMessage: 'Step 2: submitting email to Kiro registration page...', }); if (submitResult?.error) { throw new Error(submitResult.error); @@ -1120,13 +1120,13 @@ fromStates: ['email_entry'], stableMs: 1500, initialDelayMs: 150, - injectLogMessage: '步骤 2:邮箱提交后页面切换中,正在等待 Kiro 注册页恢复...', - readyLogMessage: '步骤 2:邮箱已提交,正在等待 Kiro 注册链路进入下一页...', - timeoutMessage: '邮箱提交后页面没有离开邮箱页,请检查邮箱是否被拒绝、页面是否异常或代理是否卡住。', + injectLogMessage: 'Step 2: page switching after email submission, waiting for Kiro registration page recovery...', + readyLogMessage: 'Step 2: email submitted, waiting for the Kiro registration flow to reach the next page...', + timeoutMessage: 'After email submission, the page did not leave the email page. Check whether the email was rejected, the page is broken, or the proxy is stuck.', }); assertKiroRegistrationOnlyState(landingResult, currentState, 2, resolvedEmail); if (!KIRO_REGISTER_AFTER_EMAIL_STATES.includes(landingResult?.state)) { - throw new Error(`步骤 2:邮箱提交后进入了无法继续注册的页面状态:${landingResult?.state || 'unknown'}。`); + throw new Error(`Step 2: after email submission the page entered a state that cannot continue registration: ${landingResult?.state || 'unknown'}.`); } const landedStatus = getKiroRegisterStatusForPageState(landingResult.state); @@ -1152,7 +1152,7 @@ error: '', }, }); - await log(`步骤 2:邮箱 ${resolvedEmail} 已提交,当前页面状态:${landingResult?.state || 'unknown'}。`, 'ok', nodeId); + await log(`Step 2: email ${resolvedEmail} submitted, current page state: ${landingResult?.state || 'unknown'}.`, 'ok', nodeId); await completeNodeFromBackground(nodeId, { ...payload, email: resolvedEmail, @@ -1173,21 +1173,21 @@ const runtimeState = readKiroRuntime(currentState); const tabId = await activateKiroRegisterTab(currentState, { - missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。', - openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。', + missingUrlMessage: 'Missing Kiro registration page URL, please run step 1 first.', + openFailedMessage: 'Unable to restore Kiro registration page, please re-run step 1.', }); const currentPageState = await readKiroRegisterPageState(tabId, { step: 3, stableMs: 1500, initialDelayMs: 150, - injectLogMessage: '步骤 3:Kiro 姓名页内容脚本未就绪,正在等待页面恢复...', - readyLogMessage: '步骤 3:正在读取 Kiro 注册页当前状态...', + injectLogMessage: 'Step 3: Kiro name page content script not ready, waiting for page recovery...', + readyLogMessage: 'Step 3: reading current state of Kiro registration page...', }); assertKiroRegistrationOnlyState(currentPageState, currentState, 3); const currentEmail = resolveKiroRegisterEmail(currentState, currentPageState); if (!currentEmail) { - throw new Error('步骤 3:缺少 Kiro 注册邮箱,请先完成步骤 2。'); + throw new Error('Step 3: missing Kiro registration email, please complete step 2 first.'); } if (KIRO_REGISTER_AFTER_NAME_STATES.includes(currentPageState?.state)) { @@ -1199,17 +1199,17 @@ ? (runtimeState.register?.verificationRequestedAt || 0) : undefined, }); - await log(`步骤 3:检测到当前已进入 ${currentPageState.state},已收养注册进度并继续。`, 'ok', nodeId); + await log(`Step 3: already at ${currentPageState.state}, adopted registration progress and continuing.`, 'ok', nodeId); return; } if (currentPageState?.state !== 'name_entry') { - throw new Error(`步骤 3:当前页面状态为 ${currentPageState?.state || 'unknown'},不是 Kiro 注册姓名页,请先完成步骤 2。`); + throw new Error(`Step 3: current page state is ${currentPageState?.state || 'unknown'}, not the Kiro registration name page. Please complete step 2 first.`); } const fullName = resolveKiroFullName(currentState); const verificationRequestedAt = Date.now(); - await log(`步骤 3:正在填写姓名 ${fullName} 并继续...`, 'info', nodeId); + await log(`Step 3: filling in name ${fullName} and continuing...`, 'info', nodeId); const submitResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { type: 'EXECUTE_NODE', @@ -1223,7 +1223,7 @@ timeoutMs: 30000, retryDelayMs: 700, onRetryableError: buildKiroRetryRecovery(tabId, {}), - logMessage: '步骤 3:正在向 Kiro 姓名页提交姓名...', + logMessage: 'Step 3: submitting name to Kiro name page...', }); if (submitResult?.error) { throw new Error(submitResult.error); @@ -1234,13 +1234,13 @@ fromStates: ['name_entry'], stableMs: 1500, initialDelayMs: 150, - injectLogMessage: '步骤 3:姓名提交后页面切换中,正在等待 Kiro 注册页恢复...', - readyLogMessage: '步骤 3:姓名已提交,正在等待 Kiro 注册链路进入下一页...', - timeoutMessage: '姓名提交后未进入验证码页,请检查当前页面状态。', + injectLogMessage: 'Step 3: page switching after name submission, waiting for Kiro registration page recovery...', + readyLogMessage: 'Step 3: name submitted, waiting for the Kiro registration flow to reach the next page...', + timeoutMessage: 'After name submission, the verification-code page did not load. Please check the current page state.', }); assertKiroRegistrationOnlyState(landingResult, currentState, 3, currentEmail); if (!KIRO_REGISTER_AFTER_NAME_STATES.includes(landingResult?.state)) { - throw new Error(`步骤 3:姓名提交后进入了无法继续注册的页面状态:${landingResult?.state || 'unknown'}。`); + throw new Error(`Step 3: after name submission the page entered a state that cannot continue registration: ${landingResult?.state || 'unknown'}.`); } const landedStatus = getKiroRegisterStatusForPageState(landingResult.state); @@ -1266,7 +1266,7 @@ error: '', }, }); - await log(`步骤 3:姓名已提交,当前页面状态:${landingResult?.state || 'unknown'}。`, 'ok', nodeId); + await log(`Step 3: name submitted, current page state: ${landingResult?.state || 'unknown'}.`, 'ok', nodeId); await completeNodeFromBackground(nodeId, payload); } catch (error) { const message = getErrorMessage(error); @@ -1280,21 +1280,21 @@ const currentState = await getExecutionState(state); try { const tabId = await activateKiroRegisterTab(currentState, { - missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。', - openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。', + missingUrlMessage: 'Missing Kiro registration page URL, please run step 1 first.', + openFailedMessage: 'Unable to restore Kiro registration page, please re-run step 1.', }); const currentPageState = await readKiroRegisterPageState(tabId, { step: 4, stableMs: 1500, initialDelayMs: 150, - injectLogMessage: '步骤 4:Kiro 验证码页内容脚本未就绪,正在等待页面恢复...', - readyLogMessage: '步骤 4:正在读取 Kiro 注册页当前状态...', + injectLogMessage: 'Step 4: Kiro verification-code page content script not ready, waiting for page recovery...', + readyLogMessage: 'Step 4: reading current state of Kiro registration page...', }); assertKiroRegistrationOnlyState(currentPageState, currentState, 4); const currentEmail = resolveKiroRegisterEmail(currentState, currentPageState); if (!currentEmail) { - throw new Error('步骤 4:缺少 Kiro 注册邮箱,请先完成步骤 2,或在当前验证码页显示注册邮箱后重试。'); + throw new Error('Step 4: missing Kiro registration email. Please complete step 2 first, or retry after the registration email appears on the current verification-code page.'); } if (KIRO_REGISTER_AFTER_OTP_STATES.includes(currentPageState?.state)) { @@ -1303,12 +1303,12 @@ email: currentEmail, status, }); - await log(`步骤 4:检测到当前已进入 ${currentPageState.state},已收养注册进度并继续。`, 'ok', nodeId); + await log(`Step 4: already at ${currentPageState.state}, adopted registration progress and continuing.`, 'ok', nodeId); return; } if (currentPageState?.state !== 'register_otp_page') { - throw new Error(`步骤 4:当前页面状态为 ${currentPageState?.state || 'unknown'},不是 Kiro 注册验证码页,请先完成前置注册步骤。`); + throw new Error(`Step 4: current page state is ${currentPageState?.state || 'unknown'}, not the Kiro registration verification-code page. Please complete the earlier registration steps first.`); } const pollingState = { @@ -1323,9 +1323,9 @@ const codeResult = await pollKiroVerificationCode(4, pollingState, nodeId); const code = cleanString(codeResult?.code); if (!code) { - throw new Error('未能获取到 Kiro 邮箱验证码。'); + throw new Error('Failed to obtain Kiro email verification code.'); } - await log(`步骤 4:已获取验证码 ${code},正在返回 Kiro 注册页提交...`, 'info', nodeId); + await log(`Step 4: obtained verification code ${code}, returning to Kiro registration page to submit...`, 'info', nodeId); await activateTab(tabId); const submitResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { @@ -1340,7 +1340,7 @@ timeoutMs: 30000, retryDelayMs: 700, onRetryableError: buildKiroRetryRecovery(tabId, {}), - logMessage: '步骤 4:正在向 Kiro 验证码页提交验证码...', + logMessage: 'Step 4: submitting verification code to Kiro verification-code page...', }); if (submitResult?.error) { throw new Error(submitResult.error); @@ -1351,17 +1351,17 @@ fromStates: ['register_otp_page'], stableMs: 1500, initialDelayMs: 150, - injectLogMessage: '步骤 4:验证码提交后页面切换中,正在等待 Kiro 注册页恢复...', - readyLogMessage: '步骤 4:验证码已提交,正在等待 Kiro 密码页加载完成...', + injectLogMessage: 'Step 4: page switching after verification-code submission, waiting for Kiro registration page recovery...', + readyLogMessage: 'Step 4: verification code submitted, waiting for the Kiro password page to finish loading...', returnOnCodeInvalid: true, - timeoutMessage: '验证码提交后未进入密码页,请检查验证码是否失效或页面是否异常。', + timeoutMessage: 'After verification-code submission, the password page did not load. Please check whether the code expired or the page is broken.', }); assertKiroRegistrationOnlyState(landingResult, currentState, 4, currentEmail); if (landingResult?.state === 'register_otp_page' && landingResult?.codeInvalid) { - throw new Error('步骤 4:Kiro 提示验证码无效或已过期,已停止当前注册;请重新获取验证码或换邮箱重试。'); + throw new Error('Step 4: Kiro reported the verification code is invalid or expired and stopped the current registration. Please obtain a new verification code or retry with a different email.'); } if (!KIRO_REGISTER_AFTER_OTP_STATES.includes(landingResult?.state)) { - throw new Error(`步骤 4:验证码提交后进入了无法继续注册的页面状态:${landingResult?.state || 'unknown'}。`); + throw new Error(`Step 4: after verification-code submission the page entered a state that cannot continue registration: ${landingResult?.state || 'unknown'}.`); } const landedStatus = getKiroRegisterStatusForPageState(landingResult.state); @@ -1383,7 +1383,7 @@ error: '', }, }); - await log(`步骤 4:验证码已提交,当前页面状态:${landingResult?.state || 'unknown'}。`, 'ok', nodeId); + await log(`Step 4: verification code submitted, current page state: ${landingResult?.state || 'unknown'}.`, 'ok', nodeId); await completeNodeFromBackground(nodeId, { ...payload, code, @@ -1402,15 +1402,15 @@ const currentState = await getExecutionState(state); try { const tabId = await activateKiroRegisterTab(currentState, { - missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。', - openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。', + missingUrlMessage: 'Missing Kiro registration page URL, please run step 1 first.', + openFailedMessage: 'Unable to restore Kiro registration page, please re-run step 1.', }); const currentPageState = await readKiroRegisterPageState(tabId, { step: 5, stableMs: 1500, initialDelayMs: 150, - injectLogMessage: '步骤 5:Kiro 密码页内容脚本未就绪,正在等待页面恢复...', - readyLogMessage: '步骤 5:正在读取 Kiro 注册页当前状态...', + injectLogMessage: 'Step 5: Kiro password page content script not ready, waiting for page recovery...', + readyLogMessage: 'Step 5: reading current state of Kiro registration page...', }); assertKiroRegistrationOnlyState(currentPageState, currentState, 5); @@ -1421,18 +1421,18 @@ email: currentEmail, status, }); - await log(`步骤 5:检测到当前已进入 ${currentPageState.state},已收养注册进度并继续。`, 'ok', nodeId); + await log(`Step 5: already at ${currentPageState.state}, adopted registration progress and continuing.`, 'ok', nodeId); return; } if (currentPageState?.state !== 'create_password_page') { - throw new Error(`步骤 5:当前页面状态为 ${currentPageState?.state || 'unknown'},不是 Kiro 注册密码页,请先完成前置注册步骤。`); + throw new Error(`Step 5: current page state is ${currentPageState?.state || 'unknown'}, not the Kiro registration password page. Please complete the earlier registration steps first.`); } const passwordResolution = resolveKiroPassword(currentState); const password = passwordResolution.password; if (!password) { - throw new Error('未生成有效的 Kiro 账户密码。'); + throw new Error('Failed to generate a valid Kiro account password.'); } if (typeof setPasswordState === 'function') { await setPasswordState(password); @@ -1441,9 +1441,9 @@ } const passwordModeLabel = passwordResolution.mode === 'custom' - ? '自定义密码' - : (passwordResolution.mode === 'reused' ? '复用现有密码' : '自动生成密码'); - await log(`步骤 5:正在填写 Kiro 账户密码(${passwordModeLabel},${password.length} 位)...`, 'info', nodeId); + ? 'custom password' + : (passwordResolution.mode === 'reused' ? 'reusing existing password' : 'auto-generated password'); + await log(`Step 5: filling in Kiro account password (${passwordModeLabel}, ${password.length} chars)...`, 'info', nodeId); const submitResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { type: 'EXECUTE_NODE', @@ -1457,7 +1457,7 @@ timeoutMs: 30000, retryDelayMs: 700, onRetryableError: buildKiroRetryRecovery(tabId, {}), - logMessage: '步骤 5:正在向 Kiro 密码页提交密码...', + logMessage: 'Step 5: submitting password to Kiro password page...', }); if (submitResult?.error) { throw new Error(submitResult.error); @@ -1468,13 +1468,13 @@ fromStates: ['create_password_page'], stableMs: 1200, initialDelayMs: 120, - injectLogMessage: '步骤 5:密码提交后页面切换中,正在等待 Kiro 注册页恢复...', - readyLogMessage: '步骤 5:密码已提交,正在等待 Kiro 注册页完成跳转...', - timeoutMessage: '密码提交后页面未离开密码页,请检查密码规则或当前页面提示。', + injectLogMessage: 'Step 5: page switching after password submission, waiting for Kiro registration page recovery...', + readyLogMessage: 'Step 5: password submitted, waiting for the Kiro registration page navigation to complete...', + timeoutMessage: 'After password submission, the page did not leave the password page. Please check the password rules or the current page hint.', }); assertKiroRegistrationOnlyState(landingResult, currentState, 5, currentEmail); if (!KIRO_REGISTER_AFTER_PASSWORD_STATES.includes(landingResult?.state)) { - throw new Error(`步骤 5:密码提交后进入了无法继续注册的页面状态:${landingResult?.state || 'unknown'}。`); + throw new Error(`Step 5: after password submission the page entered a state that cannot continue registration: ${landingResult?.state || 'unknown'}.`); } const nextRegisterStatus = getKiroRegisterStatusForPageState(landingResult?.state); @@ -1495,7 +1495,7 @@ error: '', }, }); - await log(`步骤 5:密码已提交,当前页面状态:${landingResult?.state || 'unknown'}。`, 'ok', nodeId); + await log(`Step 5: password submitted, current page state: ${landingResult?.state || 'unknown'}.`, 'ok', nodeId); await completeNodeFromBackground(nodeId, payload); } catch (error) { const message = getErrorMessage(error); @@ -1509,25 +1509,25 @@ const currentState = await getExecutionState(state); try { const tabId = await activateKiroRegisterTab(currentState, { - missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。', - openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。', + missingUrlMessage: 'Missing Kiro registration page URL, please run step 1 first.', + openFailedMessage: 'Unable to restore Kiro registration page, please re-run step 1.', }); let landingResult = await readKiroRegisterPageState(tabId, { step: 6, stableMs: 1500, initialDelayMs: 150, - injectLogMessage: '步骤 6:Kiro 授权确认页内容脚本未就绪,正在等待页面恢复...', - readyLogMessage: '步骤 6:正在读取 Kiro 注册页当前状态...', - timeoutMessage: '未进入 Kiro 授权确认页,请检查当前页面状态。', + injectLogMessage: 'Step 6: Kiro authorization consent page content script not ready, waiting for page recovery...', + readyLogMessage: 'Step 6: reading current state of Kiro registration page...', + timeoutMessage: 'Did not reach the Kiro authorization consent page. Please check the current page state.', }); assertKiroRegistrationOnlyState(landingResult, currentState, 6); if (!['authorization_page', 'success_page', 'kiro_web_signed_in'].includes(landingResult?.state)) { - throw new Error(`步骤 6:当前页面状态为 ${landingResult?.state || 'unknown'},不是 Kiro 注册授权确认页,请先完成前置注册步骤。`); + throw new Error(`Step 6: current page state is ${landingResult?.state || 'unknown'}, not the Kiro registration authorization consent page. Please complete the earlier registration steps first.`); } if (landingResult?.state === 'authorization_page') { - await log('步骤 6:正在确认访问并完成 Kiro 注册授权...', 'info', nodeId); + await log('Step 6: confirming access and finishing Kiro registration authorization...', 'info', nodeId); const submitResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { type: 'EXECUTE_NODE', nodeId: 'kiro-complete-register-consent', @@ -1540,7 +1540,7 @@ timeoutMs: 60000, retryDelayMs: 700, onRetryableError: buildKiroRetryRecovery(tabId, {}), - logMessage: '步骤 6:正在处理 Kiro 注册授权确认页...', + logMessage: 'Step 6: processing Kiro registration authorization consent page...', }); if (submitResult?.error) { throw new Error(submitResult.error); @@ -1550,9 +1550,9 @@ targetStates: ['success_page', 'kiro_web_signed_in'], stableMs: 2000, initialDelayMs: 300, - injectLogMessage: '步骤 6:授权确认后页面跳转中,正在等待 Kiro Web 登录态恢复...', - readyLogMessage: '步骤 6:授权确认已提交,正在等待回到 Kiro Web...', - timeoutMessage: '授权确认后未回到 Kiro Web 登录完成页,请检查当前页面或代理状态。', + injectLogMessage: 'Step 6: page navigating after authorization consent, waiting for Kiro Web sign-in to be restored...', + readyLogMessage: 'Step 6: authorization consent submitted, waiting for return to Kiro Web...', + timeoutMessage: 'After authorization consent, did not return to the Kiro Web sign-in completion page. Please check the current page or proxy state.', }); assertKiroRegistrationOnlyState(landingResult, currentState, 6); } @@ -1580,7 +1580,7 @@ error: '', }, }); - await log('步骤 6:注册页授权已完成,Kiro Web 登录态已建立。', 'ok', nodeId); + await log('Step 6: registration page authorization complete, Kiro Web sign-in established.', 'ok', nodeId); await completeNodeFromBackground(nodeId, payload); } catch (error) { const message = getErrorMessage(error); diff --git a/flows/kiro/content/desktop-authorize-page.js b/flows/kiro/content/desktop-authorize-page.js index 6a0d6794..519b7057 100644 --- a/flows/kiro/content/desktop-authorize-page.js +++ b/flows/kiro/content/desktop-authorize-page.js @@ -116,14 +116,14 @@ function detectDesktopFatalState(pageText = '', currentUrl = '', pageTitle = '') return { state: 'proxy_error_page', url: currentUrl, - fatalMessage: 'Kiro 桌面授权页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。', + fatalMessage: 'Kiro desktop authorization page encountered an AWS request error, usually caused by the current proxy IP or exit region. Please switch proxy and retry.', }; } if (KIRO_DESKTOP_CLOUDFRONT_403_TEXT_PATTERN.test(combinedText)) { return { state: 'cloudfront_403_page', url: currentUrl, - fatalMessage: 'Kiro 桌面授权页返回 403(CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。', + fatalMessage: 'Kiro desktop authorization page returned 403 (CloudFront denied), usually caused by the current proxy IP or region triggering AWS risk control. Please switch proxy and retry.', }; } return null; @@ -228,10 +228,10 @@ function detectKiroDesktopAuthorizeState() { async function getCurrentDesktopAuthorizeState() { const detected = detectKiroDesktopAuthorizeState(); if (detected.state === 'cloudfront_403_page' || detected.state === 'proxy_error_page') { - throw new Error(detected.fatalMessage || 'Kiro 桌面授权页出现代理异常。'); + throw new Error(detected.fatalMessage || 'Proxy error on Kiro desktop authorization page.'); } if (detected.state === 'callback_error') { - throw new Error(`Kiro 桌面授权回调失败:${detected.error || 'unknown_error'}`); + throw new Error(`Kiro desktop authorization callback failed: ${detected.error || 'unknown_error'}`); } return detected; } @@ -239,11 +239,11 @@ async function getCurrentDesktopAuthorizeState() { async function submitDesktopEmail(payload = {}) { const email = String(payload?.email || '').trim(); if (!email) { - throw new Error('缺少桌面授权邮箱,无法继续。'); + throw new Error('Missing desktop authorization email, cannot continue.'); } const currentState = await getCurrentDesktopAuthorizeState(); if (currentState.state !== 'relogin_email' || !currentState.emailInput || !currentState.continueButton) { - throw new Error(`当前桌面授权页不是邮箱重登状态:${currentState.state}`); + throw new Error(`Current desktop authorization page is not in email re-login state: ${currentState.state}`); } fillInput(currentState.emailInput, email); await sleep(150); @@ -258,11 +258,11 @@ async function submitDesktopEmail(payload = {}) { async function submitDesktopPassword(payload = {}) { const password = String(payload?.password || ''); if (!password) { - throw new Error('缺少桌面授权密码,无法继续。'); + throw new Error('Missing desktop authorization password, cannot continue.'); } const currentState = await getCurrentDesktopAuthorizeState(); if (currentState.state !== 'relogin_password' || !currentState.passwordInput || !currentState.continueButton) { - throw new Error(`当前桌面授权页不是密码重登状态:${currentState.state}`); + throw new Error(`Current desktop authorization page is not in password re-login state: ${currentState.state}`); } fillInput(currentState.passwordInput, password); await sleep(150); @@ -277,11 +277,11 @@ async function submitDesktopPassword(payload = {}) { async function submitDesktopOtp(payload = {}) { const code = String(payload?.code || '').trim(); if (!code) { - throw new Error('缺少桌面授权验证码,无法继续。'); + throw new Error('Missing desktop authorization OTP, cannot continue.'); } const currentState = await getCurrentDesktopAuthorizeState(); if (currentState.state !== 'otp_page' || !currentState.otpInput || !currentState.continueButton) { - throw new Error(`当前桌面授权页不是验证码状态:${currentState.state}`); + throw new Error(`Current desktop authorization page is not in OTP state: ${currentState.state}`); } fillInput(currentState.otpInput, code); await sleep(150); @@ -296,7 +296,7 @@ async function submitDesktopOtp(payload = {}) { async function confirmDesktopConsent() { const currentState = await getCurrentDesktopAuthorizeState(); if (currentState.state !== 'consent_page' || !currentState.actionButton) { - throw new Error(`当前桌面授权页不是授权确认状态:${currentState.state}`); + throw new Error(`Current desktop authorization page is not in consent state: ${currentState.state}`); } simulateClick(currentState.actionButton); return { @@ -325,7 +325,7 @@ async function handleKiroDesktopAuthorizeCommand(message) { if (action === 'confirm-consent') { return confirmDesktopConsent(); } - throw new Error(`desktop-authorize-page.js 不处理动作:${action}`); + throw new Error(`desktop-authorize-page.js does not handle action: ${action}`); } default: return null; @@ -349,7 +349,7 @@ if (document.documentElement.getAttribute(KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTIN sendResponse({ stopped: true, error: error.message }); return; } - sendResponse({ error: error?.message || String(error || '未知错误') }); + sendResponse({ error: error?.message || String(error || 'Unknown error') }); }); return true; } diff --git a/flows/kiro/content/register-page.js b/flows/kiro/content/register-page.js index e1922271..854801e6 100644 --- a/flows/kiro/content/register-page.js +++ b/flows/kiro/content/register-page.js @@ -520,7 +520,7 @@ function detectKiroFatalPageState(pageText = '', currentUrl = '', pageTitle = '' return { state: 'proxy_error_page', url: currentUrl, - fatalMessage: 'Kiro 注册页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。', + fatalMessage: 'Kiro registration page encountered an AWS request error, usually caused by the current proxy IP or exit region. Please switch proxy and retry.', }; } @@ -528,7 +528,7 @@ function detectKiroFatalPageState(pageText = '', currentUrl = '', pageTitle = '' return { state: 'cloudfront_403_page', url: currentUrl, - fatalMessage: 'Kiro 注册页返回 403(CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。', + fatalMessage: 'Kiro registration page returned 403 (CloudFront denied), usually caused by the current proxy IP or region triggering AWS risk control. Please switch proxy and retry.', }; } @@ -546,11 +546,11 @@ function getKiroFatalStateMessage(snapshot = {}) { } switch (snapshot?.state) { case 'cloudfront_403_page': - return 'Kiro 注册页返回 403(CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。'; + return 'Kiro registration page returned 403 (CloudFront denied), usually caused by the current proxy IP or region triggering AWS risk control. Please switch proxy and retry.'; case 'proxy_error_page': - return 'Kiro 注册页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。'; + return 'Kiro registration page encountered an AWS request error, usually caused by the current proxy IP or exit region. Please switch proxy and retry.'; default: - return `Kiro 页面出现异常状态:${snapshot?.state || 'unknown'}`; + return `Kiro page entered an unexpected state: ${snapshot?.state || 'unknown'}`; } } @@ -697,7 +697,7 @@ async function waitForKiroState(predicate, options = {}) { if (isKiroFatalState(finalState.state)) { throw new Error(getKiroFatalStateMessage(finalState)); } - throw new Error(options.timeoutMessage || `等待 Kiro 页面状态超时:${finalState.state}`); + throw new Error(options.timeoutMessage || `Timed out waiting for Kiro page state: ${finalState.state}`); } async function ensureKiroRegisterPageState(payload = {}) { @@ -705,7 +705,7 @@ async function ensureKiroRegisterPageState(payload = {}) { ? payload.targetStates.map((entry) => String(entry || '').trim()).filter(Boolean) : []; if (!targetStates.length) { - throw new Error('缺少 Kiro 目标页面状态。'); + throw new Error('Missing target Kiro page states.'); } return waitForKiroState( @@ -713,7 +713,7 @@ async function ensureKiroRegisterPageState(payload = {}) { { timeoutMs: payload?.timeoutMs, retryDelayMs: payload?.retryDelayMs, - timeoutMessage: payload?.timeoutMessage || `等待 Kiro 页面进入 ${targetStates.join(' / ')} 超时,当前页面:${location.href}`, + timeoutMessage: payload?.timeoutMessage || `Timed out waiting for Kiro page to enter ${targetStates.join(' / ')}, current page: ${location.href}`, } ); } @@ -723,7 +723,7 @@ async function waitForKiroRegisterStateChange(payload = {}) { ? payload.fromStates.map((entry) => String(entry || '').trim()).filter(Boolean) : []; if (!fromStates.length) { - throw new Error('缺少 Kiro 原始页面状态。'); + throw new Error('Missing source Kiro page states.'); } return waitForKiroState( @@ -739,7 +739,7 @@ async function waitForKiroRegisterStateChange(payload = {}) { { timeoutMs: payload?.timeoutMs, retryDelayMs: payload?.retryDelayMs, - timeoutMessage: payload?.timeoutMessage || `等待 Kiro 页面离开 ${fromStates.join(' / ')} 超时,当前页面:${location.href}`, + timeoutMessage: payload?.timeoutMessage || `Timed out waiting for Kiro page to leave ${fromStates.join(' / ')}, current page: ${location.href}`, } ); } @@ -765,7 +765,7 @@ async function waitForKiroAuthorizationAdvance(previousState = {}, options = {}) { timeoutMs: options.timeoutMs, retryDelayMs: options.retryDelayMs, - timeoutMessage: options.timeoutMessage || `等待 Kiro 授权页进入下一步超时:${location.href}`, + timeoutMessage: options.timeoutMessage || `Timed out waiting for Kiro authorization page to advance: ${location.href}`, } ); } @@ -777,7 +777,7 @@ async function selectKiroBuilderId() { retryDelayMs: 250, }); if (!readyState.actionButton) { - throw new Error('Kiro 官方登录页未找到 Builder ID 登录按钮。'); + throw new Error('Builder ID sign-in button not found on the Kiro official sign-in page.'); } simulateClick(readyState.actionButton); return { @@ -791,7 +791,7 @@ async function selectKiroBuilderId() { async function submitKiroEmail(payload = {}) { const email = String(payload?.email || '').trim(); if (!email) { - throw new Error('缺少 Kiro 注册邮箱,无法继续提交。'); + throw new Error('Missing Kiro registration email, cannot continue.'); } const readyState = await ensureKiroRegisterPageState({ @@ -800,7 +800,7 @@ async function submitKiroEmail(payload = {}) { retryDelayMs: payload?.retryDelayMs || 250, }); if (!readyState.emailInput || !readyState.continueButton) { - throw new Error('Kiro 邮箱页未找到可用的输入框或继续按钮。'); + throw new Error('Kiro email page did not provide a usable input or continue button.'); } fillInput(readyState.emailInput, email); @@ -816,7 +816,7 @@ async function submitKiroEmail(payload = {}) { async function submitKiroName(payload = {}) { const fullName = String(payload?.fullName || '').trim(); if (!fullName) { - throw new Error('缺少 Kiro 注册姓名,无法继续提交。'); + throw new Error('Missing Kiro registration name, cannot continue.'); } const readyState = await ensureKiroRegisterPageState({ @@ -825,7 +825,7 @@ async function submitKiroName(payload = {}) { retryDelayMs: payload?.retryDelayMs || 250, }); if (!readyState.nameInput || !readyState.continueButton) { - throw new Error('Kiro 姓名页未找到可用的输入框或继续按钮。'); + throw new Error('Kiro name page did not provide a usable input or continue button.'); } fillInput(readyState.nameInput, fullName); @@ -841,7 +841,7 @@ async function submitKiroName(payload = {}) { async function submitKiroVerificationCode(payload = {}) { const code = String(payload?.code || '').trim(); if (!code) { - throw new Error('缺少 Kiro 邮箱验证码,无法继续提交。'); + throw new Error('Missing Kiro email verification code, cannot continue.'); } const readyState = await ensureKiroRegisterPageState({ @@ -850,7 +850,7 @@ async function submitKiroVerificationCode(payload = {}) { retryDelayMs: payload?.retryDelayMs || 250, }); if (!readyState.otpInput || !readyState.verifyButton) { - throw new Error('Kiro 验证码页未找到可用的输入框或继续按钮。'); + throw new Error('Kiro verification-code page did not provide a usable input or continue button.'); } fillInput(readyState.otpInput, code); @@ -866,7 +866,7 @@ async function submitKiroVerificationCode(payload = {}) { async function submitKiroPassword(payload = {}) { const password = String(payload?.password || ''); if (!password) { - throw new Error('缺少 Kiro 账户密码,无法继续提交。'); + throw new Error('Missing Kiro account password, cannot continue.'); } const readyState = await ensureKiroRegisterPageState({ @@ -875,13 +875,13 @@ async function submitKiroPassword(payload = {}) { retryDelayMs: payload?.retryDelayMs || 250, }); if (!readyState.passwordInput || !readyState.continueButton) { - throw new Error('Kiro 密码页未找到可用的密码框或继续按钮。'); + throw new Error('Kiro password page did not provide a usable password field or continue button.'); } fillInput(readyState.passwordInput, password); await sleep(150); if (String(readyState.passwordInput.value || '') !== password) { - throw new Error('Kiro \u5bc6\u7801\u9875\u4e3b\u5bc6\u7801\u6846\u586b\u5165\u5931\u8d25\uff0c\u5df2\u505c\u6b62\u63d0\u4ea4\uff0c\u8bf7\u68c0\u67e5\u9875\u9762\u5b57\u6bb5\u7ed3\u6784\u3002'); + throw new Error('Failed to fill the primary password field on the Kiro password page; submission stopped. Please check the page field structure.'); } const confirmPasswordInput = readyState.confirmPasswordInput @@ -893,7 +893,7 @@ async function submitKiroPassword(payload = {}) { fillInput(confirmPasswordInput, password); await sleep(150); if (String(confirmPasswordInput.value || '') !== password) { - throw new Error('Kiro \u5bc6\u7801\u9875\u786e\u8ba4\u5bc6\u7801\u6846\u586b\u5165\u5931\u8d25\uff0c\u5df2\u505c\u6b62\u63d0\u4ea4\uff0c\u8bf7\u68c0\u67e5\u9875\u9762\u5b57\u6bb5\u7ed3\u6784\u3002'); + throw new Error('Failed to fill the confirm-password field on the Kiro password page; submission stopped. Please check the page field structure.'); } } @@ -924,7 +924,7 @@ async function confirmKiroRegisterConsent(payload = {}) { const actions = []; while (currentState.state === 'authorization_page' && actions.length < maxActions) { if (!currentState.actionButton) { - throw new Error('Kiro 授权页未找到可用的授权按钮。'); + throw new Error('Kiro authorization page did not provide a usable authorization button.'); } actions.push({ @@ -935,12 +935,12 @@ async function confirmKiroRegisterConsent(payload = {}) { currentState = await waitForKiroAuthorizationAdvance(currentState, { timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, retryDelayMs: payload?.retryDelayMs || 250, - timeoutMessage: 'Kiro 授权按钮点击后页面未继续,请检查当前授权页状态。', + timeoutMessage: 'Page did not advance after clicking the Kiro authorization button. Please check the current authorization page state.', }); } if (currentState.state !== 'success_page' && currentState.state !== 'kiro_web_signed_in') { - throw new Error('Kiro 授权页未完成确认访问流程。'); + throw new Error('Kiro authorization page did not complete the confirm-access flow.'); } return { @@ -991,7 +991,7 @@ async function handleKiroRegisterCommand(message) { if (nodeId === 'kiro-complete-register-consent') { return confirmKiroRegisterConsent(message.payload || {}); } - throw new Error(`register-page.js 不处理节点:${nodeId}`); + throw new Error(`register-page.js does not handle node: ${nodeId}`); } default: return null; @@ -1017,7 +1017,7 @@ if (document.documentElement.getAttribute(KIRO_REGISTER_PAGE_LISTENER_SENTINEL) sendResponse({ stopped: true, error: error.message }); return; } - sendResponse({ error: error?.message || String(error || '未知错误') }); + sendResponse({ error: error?.message || String(error || 'Unknown error') }); }); return true; } diff --git a/flows/kiro/index.js b/flows/kiro/index.js index f5a79a88..e65e2582 100644 --- a/flows/kiro/index.js +++ b/flows/kiro/index.js @@ -36,7 +36,7 @@ "supportsLuckmail": false, "canSwitchFlow": true, "stepDefinitionMode": "kiro", - "targetSelectorLabel": "来源" + "targetSelectorLabel": "Source" }, "baseGroups": [ "kiro-runtime-status", @@ -66,7 +66,7 @@ "kiro-register-page": { "flowId": "kiro", "kind": "flow-page", - "label": "Kiro 注册页", + "label": "Kiro signup page", "readyPolicy": "top-frame-only", "family": "kiro-register-page-family", "driverId": "flows/kiro/content/register-page", @@ -121,7 +121,7 @@ "kiro-desktop-authorize": { "flowId": "kiro", "kind": "flow-page", - "label": "Kiro 桌面授权页", + "label": "Kiro desktop authorization page", "readyPolicy": "top-frame-only", "family": "kiro-desktop-authorize-family", "driverId": "flows/kiro/content/desktop-authorize-page", @@ -207,7 +207,7 @@ "settingsGroups": { "kiro-target-kiro-rs": { "id": "kiro-target-kiro-rs", - "label": "kiro.rs 配置", + "label": "kiro.rs Config", "rowIds": [ "row-kiro-rs-url", "row-kiro-rs-key", @@ -216,7 +216,7 @@ }, "kiro-runtime-status": { "id": "kiro-runtime-status", - "label": "Kiro 运行态", + "label": "Kiro runtime status", "rowIds": [ "row-kiro-web-status", "row-kiro-login-url", diff --git a/flows/kiro/workflow.js b/flows/kiro/workflow.js index 145e0b5a..66df1b64 100644 --- a/flows/kiro/workflow.js +++ b/flows/kiro/workflow.js @@ -1,7 +1,7 @@ (function attachMultiPageKiroWorkflow(root, factory) { root.MultiPageKiroWorkflow = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createMultiPageKiroWorkflow() { - const KIRO_CONTRIBUTION_STEP_TITLE = '\u8d21\u732e\u4e0a\u4f20'; + const KIRO_CONTRIBUTION_STEP_TITLE = 'Contribution upload'; function freezeDeep(entry) { if (!entry || typeof entry !== 'object' || Object.isFrozen(entry)) { @@ -19,7 +19,7 @@ "id": 1, "order": 10, "key": "kiro-open-register-page", - "title": "打开注册页", + "title": "Open signup page", "sourceId": "kiro-register-page", "driverId": "flows/kiro/background/register-runner", "command": "kiro-open-register-page", @@ -29,7 +29,7 @@ "id": 2, "order": 20, "key": "kiro-submit-email", - "title": "获取邮箱并继续", + "title": "Get email and continue", "sourceId": "kiro-register-page", "driverId": "flows/kiro/background/register-runner", "command": "kiro-submit-email", @@ -39,7 +39,7 @@ "id": 3, "order": 30, "key": "kiro-submit-name", - "title": "填写姓名并继续", + "title": "Fill in name and continue", "sourceId": "kiro-register-page", "driverId": "flows/kiro/background/register-runner", "command": "kiro-submit-name", @@ -49,7 +49,7 @@ "id": 4, "order": 40, "key": "kiro-submit-verification-code", - "title": "获取验证码并继续", + "title": "Get verification code and continue", "sourceId": "kiro-register-page", "driverId": "flows/kiro/background/register-runner", "command": "kiro-submit-verification-code", @@ -59,7 +59,7 @@ "id": 5, "order": 50, "key": "kiro-submit-password", - "title": "设置密码并继续", + "title": "Set password and continue", "sourceId": "kiro-register-page", "driverId": "flows/kiro/background/register-runner", "command": "kiro-submit-password", @@ -69,7 +69,7 @@ "id": 6, "order": 60, "key": "kiro-complete-register-consent", - "title": "完成注册授权", + "title": "Complete signup authorization", "sourceId": "kiro-register-page", "driverId": "flows/kiro/background/register-runner", "command": "kiro-complete-register-consent", @@ -79,7 +79,7 @@ "id": 7, "order": 70, "key": "kiro-start-desktop-authorize", - "title": "启动桌面授权", + "title": "Start desktop authorization", "sourceId": "kiro-desktop-authorize", "driverId": "flows/kiro/background/desktop-authorize-runner", "command": "kiro-start-desktop-authorize", @@ -89,7 +89,7 @@ "id": 8, "order": 80, "key": "kiro-complete-desktop-authorize", - "title": "完成桌面授权", + "title": "Complete desktop authorization", "sourceId": "kiro-desktop-authorize", "driverId": "flows/kiro/background/desktop-authorize-runner", "command": "kiro-complete-desktop-authorize", @@ -99,7 +99,7 @@ "id": 9, "order": 90, "key": "kiro-upload-credential", - "title": "上传凭据到 kiro.rs", + "title": "Upload credentials to kiro.rs", "sourceId": "kiro-rs-admin", "driverId": "flows/kiro/background/publisher-kiro-rs", "command": "kiro-upload-credential", diff --git a/flows/openai/background/steps/confirm-oauth.js b/flows/openai/background/steps/confirm-oauth.js index 1ba322a2..ca292104 100644 --- a/flows/openai/background/steps/confirm-oauth.js +++ b/flows/openai/background/steps/confirm-oauth.js @@ -69,10 +69,10 @@ if (!activeState.oauthUrl) { const authLoginStep = getAuthLoginStepForState(activeState, visibleStep); - throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${authLoginStep}。`); + throw new Error(`Missing OAuth login link, please complete step ${authLoginStep} first.`); } - await addStepLog(visibleStep, '正在监听 localhost 回调地址...'); + await addStepLog(visibleStep, 'Listening for localhost callback URL...'); let callbackTimeoutMs = LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS; let timeoutRecoveryAttempted = false; @@ -81,7 +81,7 @@ callbackTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS, { step: visibleStep, - actionLabel: 'OAuth localhost 回调', + actionLabel: 'OAuth localhost callback', oauthUrl: activeState?.oauthUrl || '', }) : LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS; @@ -132,7 +132,7 @@ resolved = true; cleanupListener(); - addStepLog(visibleStep, `已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => { + addStepLog(visibleStep, `Captured localhost URL: ${callbackUrl}`, 'ok').then(() => { return completeNodeFromBackground(state?.nodeId || 'confirm-oauth', { localhostUrl: callbackUrl }); }).then(() => { resolve(); @@ -156,7 +156,7 @@ timeoutDeferredLogged = true; await addStepLog( visibleStep, - '检测到认证页仍在安全验证/授权跳转中,暂停本地回调超时判定,继续等待 localhost 回调...', + 'Detected that auth page is still in security verification/authorization redirect, pausing local callback timeout check, continuing to wait for localhost callback...', 'info' ); } @@ -164,7 +164,7 @@ } catch (error) { await addStepLog( visibleStep, - `复核认证页跳转状态失败(${error?.message || error}),继续按原超时规则等待回调。`, + `Failed to verify auth page redirect status (${error?.message || error}), continuing to wait for callback per original timeout rules.`, 'warn' ); return false; @@ -182,7 +182,7 @@ } if (elapsedMs >= LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS) { - rejectStep9(new Error(`${Math.round(LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS / 1000)} 秒内未捕获到 localhost 回调跳转,步骤 ${visibleStep} 的点击可能被拦截了。`)); + rejectStep9(new Error(`Did not capture localhost callback redirect within ${Math.round(LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS / 1000)} seconds, the click in step ${visibleStep} may have been intercepted.`)); return; } @@ -190,7 +190,7 @@ try { await getOAuthFlowRemainingMs({ step: visibleStep, - actionLabel: 'OAuth localhost 回调', + actionLabel: 'OAuth localhost callback', oauthUrl: activeState?.oauthUrl || '', }); } catch (error) { @@ -198,7 +198,7 @@ return; } } else if (elapsedMs >= callbackTimeoutMs) { - rejectStep9(new Error(`${Math.round(callbackTimeoutMs / 1000)} 秒内未捕获到 localhost 回调跳转,步骤 ${visibleStep} 的点击可能被拦截了。`)); + rejectStep9(new Error(`Did not capture localhost callback redirect within ${Math.round(callbackTimeoutMs / 1000)} seconds, the click in step ${visibleStep} may have been intercepted.`)); return; } @@ -237,10 +237,10 @@ if (signupTabId && await isTabAlive('openai-auth')) { await chrome.tabs.update(signupTabId, { active: true }); - await addStepLog(visibleStep, '已切回认证页,正在准备调试器点击...'); + await addStepLog(visibleStep, 'Switched back to auth page, preparing debugger click...'); } else { signupTabId = await reuseOrCreateTab('openai-auth', activeState.oauthUrl); - await addStepLog(visibleStep, '已重新打开认证页,正在准备调试器点击...'); + await addStepLog(visibleStep, 'Reopened auth page, preparing debugger click...'); } throwIfStep8SettledOrStopped(resolved); @@ -251,12 +251,12 @@ timeoutMs: typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(15000, { step: visibleStep, - actionLabel: '等待 OAuth 同意页内容脚本就绪', + actionLabel: 'Wait for OAuth consent page content script to be ready', }) : 15000, visibleStep, logStepKey: 'confirm-oauth', - logMessage: '认证页内容脚本尚未就绪,正在等待页面恢复...', + logMessage: 'Auth page content script not yet ready, waiting for page to recover...', }); for (let round = 1; round <= STEP8_MAX_ROUNDS && !resolved; round++) { @@ -266,7 +266,7 @@ typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(STEP8_READY_WAIT_TIMEOUT_MS, { step: visibleStep, - actionLabel: '等待 OAuth 同意页出现', + actionLabel: 'Wait for OAuth consent page to appear', }) : STEP8_READY_WAIT_TIMEOUT_MS, { visibleStep } @@ -278,13 +278,13 @@ const strategy = STEP8_STRATEGIES[Math.min(round - 1, STEP8_STRATEGIES.length - 1)]; - await addStepLog(visibleStep, `第 ${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label})...`); + await addStepLog(visibleStep, `Round ${round}/${STEP8_MAX_ROUNDS} attempting to click "Continue" (${strategy.label})...`); if (strategy.mode === 'debugger') { const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(15000, { step: visibleStep, - actionLabel: '定位 OAuth 同意页继续按钮', + actionLabel: 'Locate OAuth consent page Continue button', }) : 15000; const clickTarget = await prepareStep8DebuggerClick(signupTabId, { @@ -298,7 +298,7 @@ const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(15000, { step: visibleStep, - actionLabel: '点击 OAuth 同意页继续按钮', + actionLabel: 'Click OAuth consent page Continue button', }) : 15000; await triggerStep8ContentStrategy(signupTabId, strategy.strategy, { @@ -318,7 +318,7 @@ typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(15000, { step: visibleStep, - actionLabel: '等待 OAuth 同意页点击生效', + actionLabel: 'Wait for OAuth consent page click to take effect', }) : 15000, { visibleStep } @@ -328,21 +328,21 @@ } if (effect.progressed) { - await addStepLog(visibleStep, `检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info'); + await addStepLog(visibleStep, `Detected that this click took effect, ${getStep8EffectLabel(effect)}, continuing to wait for localhost callback...`, 'info'); break; } if (round >= STEP8_MAX_ROUNDS) { - throw new Error(`步骤 ${visibleStep}:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`); + throw new Error(`Step ${visibleStep}: page still unresponsive after ${STEP8_MAX_ROUNDS} consecutive rounds of clicking "Continue".`); } - await addStepLog(visibleStep, `${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS})...`, 'warn'); + await addStepLog(visibleStep, `${strategy.label} no page response after this round's click, refreshing auth page and retrying (next round ${round + 1}/${STEP8_MAX_ROUNDS})...`, 'warn'); await reloadStep8ConsentPage( signupTabId, typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(30000, { step: visibleStep, - actionLabel: '刷新 OAuth 同意页', + actionLabel: 'Refresh OAuth consent page', }) : 30000, { visibleStep } diff --git a/flows/openai/background/steps/cpa-session-import.js b/flows/openai/background/steps/cpa-session-import.js index d3fa6be5..54bfdfe1 100644 --- a/flows/openai/background/steps/cpa-session-import.js +++ b/flows/openai/background/steps/cpa-session-import.js @@ -35,7 +35,7 @@ const factory = deps.createCpaApi || self.MultiPageBackgroundCpaApi?.createCpaApi; if (typeof factory !== 'function') { - throw new Error('CPA 接口模块未加载,无法导入当前 ChatGPT 会话。'); + throw new Error('CPA API module not loaded, unable to import current ChatGPT session.'); } cpaApi = factory({ addLog: rawAddLog, @@ -181,16 +181,16 @@ return fallbackTab.id; } - throw new Error('未找到可读取 ChatGPT 会话的标签页,请先打开一个已登录的 ChatGPT / OpenAI 页面,或完成当前 Plus 支付链路。'); + throw new Error('No tab found from which to read a ChatGPT session, please open a logged-in ChatGPT / OpenAI page first, or complete the current Plus payment flow.'); } async function getResolvedSessionTab(tabId, visibleStep) { const tab = await chrome?.tabs?.get?.(tabId).catch(() => null); if (!tab?.id) { - throw new Error(`步骤 ${visibleStep}:ChatGPT 会话标签页不存在或已关闭,无法继续导入 CPA。`); + throw new Error(`Step ${visibleStep}: ChatGPT session tab does not exist or has been closed, unable to continue importing CPA.`); } if (!isSupportedChatGptSessionUrl(tab.url)) { - throw new Error(`步骤 ${visibleStep}:当前标签页不在 ChatGPT / OpenAI 页面,无法读取当前登录会话。`); + throw new Error(`Step ${visibleStep}: current tab is not on a ChatGPT / OpenAI page, unable to read current login session.`); } return tab; } @@ -201,7 +201,7 @@ await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, { inject: PLUS_CHECKOUT_INJECT_FILES, injectSource: PLUS_CHECKOUT_SOURCE, - logMessage: `步骤 ${visibleStep}:正在等待 ChatGPT 会话页完成加载,再继续读取当前登录会话...`, + logMessage: `Step ${visibleStep}: waiting for ChatGPT session page to finish loading, then continuing to read current login session...`, }); const sessionResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, { @@ -224,7 +224,7 @@ || session?.accessToken ); if (!session && !accessToken) { - throw new Error(`步骤 ${visibleStep}:未读取到有效的 ChatGPT 会话或 accessToken,请确认当前标签页仍处于已登录状态。`); + throw new Error(`Step ${visibleStep}: did not read a valid ChatGPT session or accessToken, please confirm the current tab is still logged in.`); } return { @@ -238,14 +238,14 @@ const visibleStep = resolveVisibleStep(state); const api = getCpaApi(); - await addStepLog(visibleStep, '正在定位当前 ChatGPT 会话页并准备导入 CPA...', 'info'); + await addStepLog(visibleStep, 'Locating current ChatGPT session page and preparing to import CPA...', 'info'); const tabId = await resolveSessionTabId(state); const tab = await getResolvedSessionTab(tabId, visibleStep); if (chrome?.tabs?.update) { await chrome.tabs.update(tab.id, { active: true }).catch(() => {}); } - await addStepLog(visibleStep, '正在读取当前 ChatGPT 登录会话...', 'info'); + await addStepLog(visibleStep, 'Reading current ChatGPT login session...', 'info'); const sessionState = await readCurrentChatGptSession(tab.id, visibleStep); throwIfStopped(); @@ -255,7 +255,7 @@ accessToken: sessionState.accessToken, }, { visibleStep, - logLabel: `步骤 ${visibleStep}`, + logLabel: `Step ${visibleStep}`, logOptions: { step: visibleStep, stepKey: 'cpa-session-import' }, timeoutMs: 120000, importTimeoutMs: 120000, diff --git a/flows/openai/background/steps/create-plus-checkout.js b/flows/openai/background/steps/create-plus-checkout.js index a912624a..e4c0aeba 100644 --- a/flows/openai/background/steps/create-plus-checkout.js +++ b/flows/openai/background/steps/create-plus-checkout.js @@ -33,11 +33,11 @@ const PAYPAL_HOSTED_STEP_CREATE_ACCOUNT = 'paypal-hosted-create-account'; const PAYPAL_HOSTED_STEP_REVIEW = 'paypal-hosted-review'; const PAYPAL_HOSTED_STEP_META = Object.freeze({ - [PAYPAL_HOSTED_STEP_OPENAI_CHECKOUT]: { step: 6, label: '创建 PayPal 无卡直绑 Checkout' }, - [PAYPAL_HOSTED_STEP_EMAIL]: { step: 7, label: '无卡直绑 PayPal 邮箱页' }, - [PAYPAL_HOSTED_STEP_CARD]: { step: 8, label: '无卡直绑 PayPal 资料页' }, - [PAYPAL_HOSTED_STEP_CREATE_ACCOUNT]: { step: 9, label: '无卡直绑 PayPal 创建确认页' }, - [PAYPAL_HOSTED_STEP_REVIEW]: { step: 10, label: '无卡直绑 PayPal 授权复核页' }, + [PAYPAL_HOSTED_STEP_OPENAI_CHECKOUT]: { step: 6, label: 'Create PayPal cardless direct binding Checkout' }, + [PAYPAL_HOSTED_STEP_EMAIL]: { step: 7, label: 'Cardless direct binding PayPal email page' }, + [PAYPAL_HOSTED_STEP_CARD]: { step: 8, label: 'Cardless direct binding PayPal profile page' }, + [PAYPAL_HOSTED_STEP_CREATE_ACCOUNT]: { step: 9, label: 'Cardless direct binding PayPal create confirmation page' }, + [PAYPAL_HOSTED_STEP_REVIEW]: { step: 10, label: 'Cardless direct binding PayPal authorization review page' }, }); function createPlusCheckoutCreateExecutor(deps = {}) { @@ -96,12 +96,12 @@ function getCheckoutModeLabel(state = {}) { const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod); if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) { - return 'GPC 订阅页'; + return 'GPC subscription page'; } if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) { - return 'PayPal 无卡直绑'; + return 'PayPal cardless direct binding'; } - return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? 'GoPay 订阅页' : 'Plus Checkout'; + return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? 'GoPay subscription page' : 'Plus Checkout'; } function getPlusPaymentMethodLabel(method = PLUS_PAYMENT_METHOD_PAYPAL) { @@ -110,7 +110,7 @@ return 'GPC'; } if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) { - return 'PayPal 无卡直绑'; + return 'PayPal cardless direct binding'; } return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? 'GoPay' : 'PayPal'; } @@ -121,7 +121,7 @@ : await chrome.tabs.create({ url: PLUS_CHECKOUT_ENTRY_URL, active: true }); const tabId = Number(tab?.id); if (!Number.isInteger(tabId)) { - throw new Error('步骤 6:打开 ChatGPT 页面失败,无法创建订阅页。'); + throw new Error('Step 6: failed to open ChatGPT page, unable to create subscription page.'); } if (typeof registerTab === 'function') { await registerTab(PLUS_CHECKOUT_SOURCE, tabId); @@ -264,11 +264,11 @@ const discoveredTabId = await findOpenHostedCheckoutTabId(); if (discoveredTabId) { - await addHostedStepLog(stepKey, `步骤 ${getHostedStepNumber(stepKey)}:已从当前浏览器标签中发现 PayPal 无卡直绑页面,正在接管继续执行。`, 'info'); + await addHostedStepLog(stepKey, `Step ${getHostedStepNumber(stepKey)}: discovered PayPal cardless direct binding page from current browser tabs, taking over to continue.`, 'info'); return discoveredTabId; } - throw new Error(`步骤 ${getHostedStepNumber(stepKey)}:未找到 PayPal 无卡直绑标签页,请先完成创建 checkout 节点。`); + throw new Error(`Step ${getHostedStepNumber(stepKey)}: PayPal cardless direct binding tab not found, please complete the create checkout node first.`); } async function getHostedCurrentUrl(tabId) { @@ -302,7 +302,7 @@ const config = await getHostedCheckoutRuntimeConfig(state); const shouldWait = Boolean(options.waitBeforeComplete); if (shouldWait && config.oauthDelaySeconds > 0) { - await addHostedStepLog(stepKey, `步骤 ${getHostedStepNumber(stepKey)}:支付成功后等待 ${config.oauthDelaySeconds} 秒,再继续账号接入。`, 'info'); + await addHostedStepLog(stepKey, `Step ${getHostedStepNumber(stepKey)}: wait ${config.oauthDelaySeconds} seconds after payment success before continuing with account integration.`, 'info'); await sleepWithStop(config.oauthDelaySeconds * 1000); } await completeHostedStep(stepKey, tabId, { @@ -367,7 +367,7 @@ body: JSON.stringify({ path: '/', method: 'address' }), }, 30000); if (!response?.ok) { - throw new Error(`获取无卡直绑地址失败(HTTP ${response?.status || 0})。`); + throw new Error(`Failed to get cardless direct binding address (HTTP ${response?.status || 0}).`); } const address = data?.address || data || {}; return { @@ -451,13 +451,13 @@ async function fetchHostedVerificationCode(verificationUrl = '') { const url = String(verificationUrl || '').trim(); if (!url) { - throw new Error('未配置 OpenAI Checkout 验证码接口。'); + throw new Error('OpenAI Checkout verification code endpoint not configured.'); } const fetcher = typeof fetchImpl === 'function' ? fetchImpl : (typeof fetch === 'function' ? fetch.bind(globalThis) : null); if (typeof fetcher !== 'function') { - throw new Error('当前运行环境不支持 fetch,无法获取 OpenAI Checkout 验证码。'); + throw new Error('Current runtime environment does not support fetch, unable to retrieve OpenAI Checkout verification code.'); } const separator = url.includes('?') ? '&' : '?'; const response = await fetcher(`${url}${separator}t=${Date.now()}`, { @@ -473,7 +473,7 @@ } const code = extractHostedVerificationCode(payload); if (!code) { - throw new Error('验证码接口暂未返回有效 6 位验证码。'); + throw new Error('Verification code endpoint did not return a valid 6-digit verification code.'); } return code; } @@ -484,24 +484,24 @@ throwIfStopped(); try { const code = await fetchHostedVerificationCode(verificationUrl); - await addLog(`步骤 6:已获取 OpenAI Checkout 验证码(${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS})。`, 'info'); + await addLog(`Step 6: retrieved OpenAI Checkout verification code (${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS}).`, 'info'); return code; } catch (error) { lastError = error; - await addLog(`步骤 6:OpenAI Checkout 验证码暂不可用(${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS}):${error?.message || error}`, 'warn'); + await addLog(`Step 6: OpenAI Checkout verification code temporarily unavailable (${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS}): ${error?.message || error}`, 'warn'); if (attempt < HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS) { await sleepWithStop(HOSTED_CHECKOUT_VERIFICATION_POLL_INTERVAL_MS); } } } - throw lastError || new Error('OpenAI Checkout 验证码轮询失败。'); + throw lastError || new Error('OpenAI Checkout verification code polling failed.'); } async function runHostedOpenAiCheckout(tabId, profile, config) { await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, { inject: PLUS_CHECKOUT_INJECT_FILES, injectSource: PLUS_CHECKOUT_SOURCE, - logMessage: '步骤 6:正在等待 OpenAI hosted checkout 脚本就绪...', + logMessage: 'Step 6: waiting for OpenAI hosted checkout script to be ready...', }); const firstResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, { type: 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP', @@ -518,7 +518,7 @@ throwIfStopped(); const tab = await chrome?.tabs?.get?.(tabId).catch(() => null); if (!tab) { - throw new Error('步骤 6:无卡直绑 checkout 标签页已关闭。'); + throw new Error('Step 6: cardless direct binding checkout tab has been closed.'); } const currentUrl = String(tab.url || '').trim(); if (isPayPalUrl(currentUrl) || isHostedCheckoutSuccessUrl(currentUrl)) { @@ -550,7 +550,7 @@ } await sleepWithStop(500); } - throw new Error('步骤 6:OpenAI hosted checkout 长时间未跳转到 PayPal 或支付成功页。'); + throw new Error('Step 6: OpenAI hosted checkout did not redirect to PayPal or payment success page for an extended period.'); } async function getHostedPayPalState(tabId) { @@ -558,7 +558,7 @@ await ensureContentScriptReadyOnTabUntilStopped(PAYPAL_SOURCE, tabId, { inject: PAYPAL_INJECT_FILES, injectSource: PAYPAL_SOURCE, - logMessage: '步骤 6:正在等待 PayPal 无卡直绑页面脚本就绪...', + logMessage: 'Step 6: waiting for PayPal cardless direct binding page script to be ready...', }); const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, { type: 'PAYPAL_HOSTED_GET_STATE', @@ -576,7 +576,7 @@ await ensureContentScriptReadyOnTabUntilStopped(PAYPAL_SOURCE, tabId, { inject: PAYPAL_INJECT_FILES, injectSource: PAYPAL_SOURCE, - logMessage: '步骤 6:正在等待 PayPal 无卡直绑页面脚本就绪...', + logMessage: 'Step 6: waiting for PayPal cardless direct binding page script to be ready...', }); const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, { type: 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP', @@ -615,7 +615,7 @@ async function waitForHostedPayPalStage(tabId, predicate, options = {}) { const timeoutMs = Math.max(1000, Number(options.timeoutMs) || HOSTED_CHECKOUT_TRANSITION_TIMEOUT_MS); const intervalMs = Math.max(100, Number(options.intervalMs) || 500); - const label = String(options.label || 'PayPal 无卡直绑页面').trim(); + const label = String(options.label || 'PayPal cardless direct binding page').trim(); const deadline = Date.now() + timeoutMs; let lastStage = ''; while (Date.now() < deadline) { @@ -642,13 +642,13 @@ } await sleepWithStop(intervalMs); } - throw new Error(`${label}等待超时${lastStage ? `(最后状态:${lastStage})` : ''}。`); + throw new Error(`${label} wait timed out${lastStage ? ` (last state: ${lastStage})` : ''}.`); } async function waitForHostedUrlAfterAction(tabId, matcher, options = {}) { const timeoutMs = Math.max(1000, Number(options.timeoutMs) || HOSTED_CHECKOUT_TRANSITION_TIMEOUT_MS); const intervalMs = Math.max(100, Number(options.intervalMs) || 500); - const label = String(options.label || 'PayPal 无卡直绑跳转').trim(); + const label = String(options.label || 'PayPal cardless direct binding redirect').trim(); const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { throwIfStopped(); @@ -660,12 +660,12 @@ } await sleepWithStop(intervalMs); } - throw new Error(`${label}等待超时。`); + throw new Error(`${label} wait timed out.`); } async function runHostedPayPalStepAndWaitForStageChange(tabId, payload = {}, previousStage = '', options = {}) { const normalizedPreviousStage = String(previousStage || payload.expectedStage || '').trim(); - const label = String(options.label || 'PayPal 无卡直绑页面跳转').trim(); + const label = String(options.label || 'PayPal cardless direct binding page redirect').trim(); const predicate = typeof options.predicate === 'function' ? options.predicate : (stateInfo) => stateInfo?.hostedStage && stateInfo.hostedStage !== normalizedPreviousStage; @@ -723,10 +723,10 @@ async function executeHostedCheckoutCreate(tabId, state = {}, result = {}) { const targetCheckoutUrl = resolveCheckoutTargetUrl(result, PLUS_PAYMENT_METHOD_PAYPAL_HOSTED); if (!targetCheckoutUrl) { - throw new Error('步骤 6:PayPal 无卡直绑未返回可用的订阅链接。'); + throw new Error('Step 6: PayPal cardless direct binding did not return a usable subscription link.'); } - await addLog('步骤 6:PayPal 无卡直绑链接已创建,正在打开并提交 OpenAI Checkout 页面...', 'ok'); + await addLog('Step 6: PayPal cardless direct binding link created, opening and submitting OpenAI Checkout page...', 'ok'); await chrome.tabs.update(tabId, { url: targetCheckoutUrl, active: true }); await waitForTabCompleteUntilStopped(tabId); @@ -741,7 +741,7 @@ if (isHostedOpenAiCheckoutUrl(completedUrl)) { const { profile, config } = await ensureHostedGuestProfile(state); - await addLog(`步骤 6:正在提交 OpenAI Checkout,等待跳转到 PayPal 邮箱页(电话使用本地号码 ${profile.phone})。`, 'info'); + await addLog(`Step 6: submitting OpenAI Checkout, waiting for redirect to PayPal email page (phone uses local number ${profile.phone}).`, 'info'); completedUrl = String(await runHostedOpenAiCheckout(tabId, profile, config) || await getHostedCurrentUrl(tabId) || '').trim(); } @@ -760,7 +760,7 @@ plusHostedCheckoutCompleted: isAlreadySuccessful, }); - await addLog(`步骤 6:PayPal 无卡直绑已提交 OpenAI Checkout(${result.country || 'US'} ${result.currency || 'USD'}),准备进入 PayPal 邮箱页。`, 'info'); + await addLog(`Step 6: PayPal cardless direct binding submitted OpenAI Checkout (${result.country || 'US'} ${result.currency || 'USD'}), preparing to enter PayPal email page.`, 'info'); await completeNodeFromBackground('plus-checkout-create', { plusCheckoutCountry: result.country || 'US', @@ -782,7 +782,7 @@ let currentUrl = await getHostedCurrentUrl(tabId); if (isPayPalUrl(currentUrl)) { - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前已在 PayPal 页面,OpenAI Checkout 节点直接完成。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: currently on PayPal page, OpenAI Checkout node completing directly.`, 'info'); await completeHostedStep(stepKey, tabId, { plusCheckoutSource: PLUS_PAYMENT_METHOD_PAYPAL_HOSTED, }); @@ -792,7 +792,7 @@ currentUrl = await waitForHostedUrlAfterAction( tabId, (url) => isHostedOpenAiCheckoutUrl(url) || isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url), - { label: `步骤 ${stepNumber}:等待 OpenAI hosted checkout 页面` } + { label: `Step ${stepNumber}: waiting for OpenAI hosted checkout page` } ); } if (isHostedCheckoutSuccessUrl(currentUrl)) { @@ -810,7 +810,7 @@ } const { profile, config } = await ensureHostedGuestProfile(state); - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在选择 PayPal 并提交 OpenAI hosted checkout(电话使用本地号码 ${profile.phone})。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: selecting PayPal and submitting OpenAI hosted checkout (phone uses local number ${profile.phone}).`, 'info'); const transitionUrl = await runHostedOpenAiCheckout(tabId, profile, config); const completedUrl = String(transitionUrl || await getHostedCurrentUrl(tabId) || '').trim(); await completeHostedStep(stepKey, tabId, { @@ -832,7 +832,7 @@ await waitForHostedUrlAfterAction( tabId, (url) => isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url), - { label: `步骤 ${stepNumber}:等待 PayPal 邮箱页` } + { label: `Step ${stepNumber}: waiting for PayPal email page` } ); if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) { return; @@ -841,23 +841,23 @@ const pageState = await getHostedPayPalState(tabId); if (isHostedStageAtOrAfter(pageState.hostedStage, PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT) && pageState.hostedStage !== PAYPAL_HOSTED_STAGE_LOGIN) { - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前 PayPal 已进入后续页面(${pageState.hostedStage}),邮箱节点直接完成。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: current PayPal already advanced to subsequent page (${pageState.hostedStage}), email node completing directly.`, 'info'); await completeHostedStep(stepKey, tabId, { plusHostedCheckoutLastStage: pageState.hostedStage, }); return; } if (pageState.hostedStage !== PAYPAL_HOSTED_STAGE_LOGIN) { - throw new Error(`步骤 ${stepNumber}:当前不是 PayPal 邮箱页(当前状态:${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN})。`); + throw new Error(`Step ${stepNumber}: current page is not the PayPal email page (current state: ${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN}).`); } - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在填写 PayPal 无卡直绑邮箱。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: filling in PayPal cardless direct binding email.`, 'info'); const { nextState, completedByStageChange } = await runHostedPayPalStepAndWaitForStageChange(tabId, { expectedStage: PAYPAL_HOSTED_STAGE_LOGIN, email: profile.email, - }, PAYPAL_HOSTED_STAGE_LOGIN, { label: `步骤 ${stepNumber}:等待 PayPal 邮箱页跳转` }); + }, PAYPAL_HOSTED_STAGE_LOGIN, { label: `Step ${stepNumber}: waiting for PayPal email page redirect` }); if (completedByStageChange) { - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:已检测到 PayPal 进入后续页面(${nextState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN}),邮箱节点直接完成。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: detected PayPal advanced to subsequent page (${nextState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN}), email node completing directly.`, 'info'); } await completeHostedStep(stepKey, tabId, { plusHostedCheckoutLastStage: nextState.hostedStage || '', @@ -874,7 +874,7 @@ await waitForHostedUrlAfterAction( tabId, (url) => isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url), - { label: `步骤 ${stepNumber}:等待 PayPal 资料页` } + { label: `Step ${stepNumber}: waiting for PayPal profile page` } ); if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) { return; @@ -883,18 +883,18 @@ const pageState = await getHostedPayPalState(tabId); if (isHostedStageAtOrAfter(pageState.hostedStage, PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT) && pageState.hostedStage !== PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT) { - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前 PayPal 已进入后续页面(${pageState.hostedStage}),资料节点直接完成。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: current PayPal already advanced to subsequent page (${pageState.hostedStage}), profile node completing directly.`, 'info'); await completeHostedStep(stepKey, tabId, { plusHostedCheckoutLastStage: pageState.hostedStage, }); return; } if (pageState.hostedStage !== PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT) { - throw new Error(`步骤 ${stepNumber}:当前不是 PayPal 资料页(当前状态:${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN})。`); + throw new Error(`Step ${stepNumber}: current page is not the PayPal profile page (current state: ${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN}).`); } const { profile } = await ensureHostedGuestProfile(state); - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在填写 PayPal 无卡直绑资料,提交前会复查电话是否为 ${profile.phone}。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: filling in PayPal cardless direct binding profile, will verify phone matches ${profile.phone} before submitting.`, 'info'); const cardResult = await runHostedPayPalStep(tabId, { ...profile, expectedStage: PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT, @@ -903,14 +903,14 @@ if (cardResult?.phoneMatched) { await addHostedStepLog( stepKey, - `步骤 ${stepNumber}:PayPal 页面电话复查通过(配置 ${cardResult.payloadPhoneDigits},页面 ${cardResult.renderedPhoneDigits})。`, + `Step ${stepNumber}: PayPal page phone verification passed (configured ${cardResult.payloadPhoneDigits}, page ${cardResult.renderedPhoneDigits}).`, 'info' ); } const nextState = await waitForHostedPayPalStage( tabId, (stateInfo) => stateInfo?.hostedStage && stateInfo.hostedStage !== PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT, - { label: `步骤 ${stepNumber}:等待 PayPal 资料页跳转` } + { label: `Step ${stepNumber}: waiting for PayPal profile page redirect` } ); await completeHostedStep(stepKey, tabId, { plusHostedCheckoutLastStage: nextState.hostedStage || '', @@ -927,7 +927,7 @@ await waitForHostedUrlAfterAction( tabId, (url) => isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url), - { label: `步骤 ${stepNumber}:等待 PayPal 创建确认页` } + { label: `Step ${stepNumber}: waiting for PayPal create confirmation page` } ); if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) { return; @@ -936,24 +936,24 @@ const pageState = await getHostedPayPalState(tabId); if (isHostedStageAtOrAfter(pageState.hostedStage, PAYPAL_HOSTED_STAGE_REVIEW) && pageState.hostedStage !== PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT) { - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前 PayPal 已进入后续页面(${pageState.hostedStage}),创建确认节点直接完成。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: current PayPal already advanced to subsequent page (${pageState.hostedStage}), create confirmation node completing directly.`, 'info'); await completeHostedStep(stepKey, tabId, { plusHostedCheckoutLastStage: pageState.hostedStage, }); return; } if (pageState.hostedStage !== PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT) { - throw new Error(`步骤 ${stepNumber}:当前不是 PayPal 创建确认页(当前状态:${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN})。`); + throw new Error(`Step ${stepNumber}: current page is not the PayPal create confirmation page (current state: ${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN}).`); } - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在确认创建 PayPal 账号。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: confirming PayPal account creation.`, 'info'); await runHostedPayPalStep(tabId, { expectedStage: PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT, }); const nextState = await waitForHostedPayPalStage( tabId, (stateInfo) => stateInfo?.hostedStage && stateInfo.hostedStage !== PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT, - { label: `步骤 ${stepNumber}:等待 PayPal 创建确认页跳转` } + { label: `Step ${stepNumber}: waiting for PayPal create confirmation page redirect` } ); await completeHostedStep(stepKey, tabId, { plusHostedCheckoutLastStage: nextState.hostedStage || '', @@ -970,7 +970,7 @@ await waitForHostedUrlAfterAction( tabId, (url) => isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url), - { label: `步骤 ${stepNumber}:等待 PayPal 授权复核页` } + { label: `Step ${stepNumber}: waiting for PayPal authorization review page` } ); if (await completeHostedStepIfSuccessful(stepKey, tabId, state, { waitBeforeComplete: true })) { return; @@ -978,20 +978,20 @@ const pageState = await getHostedPayPalState(tabId); if (pageState.hostedStage !== PAYPAL_HOSTED_STAGE_REVIEW) { - throw new Error(`步骤 ${stepNumber}:当前不是 PayPal 授权复核页(当前状态:${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN})。`); + throw new Error(`Step ${stepNumber}: current page is not the PayPal authorization review page (current state: ${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN}).`); } - await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在确认 PayPal 授权复核页。`, 'info'); + await addHostedStepLog(stepKey, `Step ${stepNumber}: confirming PayPal authorization review page.`, 'info'); await runHostedPayPalStep(tabId, { expectedStage: PAYPAL_HOSTED_STAGE_REVIEW, }); await waitForHostedUrlAfterAction( tabId, (url) => isHostedCheckoutSuccessUrl(url), - { label: `步骤 ${stepNumber}:等待 PayPal 回到 ChatGPT 支付成功页`, timeoutMs: HOSTED_CHECKOUT_PAYPAL_TIMEOUT_MS } + { label: `Step ${stepNumber}: waiting for PayPal to return to ChatGPT payment success page`, timeoutMs: HOSTED_CHECKOUT_PAYPAL_TIMEOUT_MS } ); if (!await completeHostedStepIfSuccessful(stepKey, tabId, state, { waitBeforeComplete: true })) { - throw new Error(`步骤 ${stepNumber}:PayPal 授权后未检测到 ChatGPT 支付成功页。`); + throw new Error(`Step ${stepNumber}: did not detect ChatGPT payment success page after PayPal authorization.`); } } @@ -1036,7 +1036,7 @@ || '' ).trim(); if (!apiKey) { - throw new Error('创建 GPC 订单失败:缺少 API Key。'); + throw new Error('Failed to create GPC order: missing API Key.'); } return apiKey; } @@ -1172,7 +1172,7 @@ async function assertGpcApiKeyReadyForCreate(state = {}, phoneMode = GPC_HELPER_PHONE_MODE_MANUAL, apiKey = '') { const apiUrl = buildGpcBalanceUrl(state?.gopayHelperApiUrl); if (!apiUrl) { - throw new Error('创建 GPC 订单失败:缺少 API 地址。'); + throw new Error('Failed to create GPC order: missing API URL.'); } const { response, data } = await fetchJsonWithTimeout(apiUrl, { method: 'GET', @@ -1183,19 +1183,19 @@ }, 30000); if (!response?.ok || !isGpcUnifiedResponseOk(data)) { const detail = getGpcResponseErrorDetail(data, response?.status || 0); - throw new Error(`创建 GPC 订单失败:API Key 校验失败:${detail}`); + throw new Error(`Failed to create GPC order: API Key verification failed: ${detail}`); } const balanceData = unwrapGpcResponse(data); const remainingUses = getGpcRemainingUses(balanceData); const status = String(balanceData?.status || balanceData?.card_status || balanceData?.cardStatus || '').trim().toLowerCase(); if (status && status !== 'active') { - throw new Error(`创建 GPC 订单失败:API Key 状态不可用(${status})。`); + throw new Error(`Failed to create GPC order: API Key status unavailable (${status}).`); } if (remainingUses !== null && remainingUses <= 0) { - throw new Error('创建 GPC 订单失败:API Key 剩余次数不足。'); + throw new Error('Failed to create GPC order: API Key has insufficient remaining uses.'); } if (phoneMode === GPC_HELPER_PHONE_MODE_AUTO && isGpcAutoModePermissionDenied(balanceData)) { - throw new Error('创建 GPC 订单失败:当前 GPC API Key 未开通自动模式。'); + throw new Error('Failed to create GPC order: current GPC API Key does not have auto mode enabled.'); } } @@ -1204,13 +1204,13 @@ ? fetchImpl : (typeof fetch === 'function' ? fetch.bind(globalThis) : null); if (typeof fetcher !== 'function') { - throw new Error('当前运行环境不支持 fetch,无法调用 GPC API。'); + throw new Error('Current runtime environment does not support fetch, unable to call GPC API.'); } const controller = typeof AbortController === 'function' ? new AbortController() : null; const effectiveTimeoutMs = Math.max(1000, Number(timeoutMs) || 30000); let didTimeout = false; let timer = null; - const buildTimeoutError = () => new Error(`GPC API 请求超时(>${Math.round(effectiveTimeoutMs / 1000)} 秒):${url}`); + const buildTimeoutError = () => new Error(`GPC API request timed out (>${Math.round(effectiveTimeoutMs / 1000)} seconds): ${url}`); const timeoutPromise = new Promise((_, reject) => { timer = setTimeout(() => { didTimeout = true; @@ -1246,7 +1246,7 @@ await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, { inject: PLUS_CHECKOUT_INJECT_FILES, injectSource: PLUS_CHECKOUT_SOURCE, - logMessage: '步骤 6:正在等待 ChatGPT 页面完成加载,再继续获取 accessToken...', + logMessage: 'Step 6: waiting for ChatGPT page to finish loading, then continuing to retrieve accessToken...', }); const sessionResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, { @@ -1266,11 +1266,11 @@ async function generateGpcCheckoutFromApi(accessToken = '', state = {}) { const token = String(accessToken || '').trim(); if (!token) { - throw new Error('创建 GPC 订单失败:缺少 accessToken。'); + throw new Error('Failed to create GPC order: missing accessToken.'); } const apiUrl = buildGpcTaskCreateUrl(state?.gopayHelperApiUrl); if (!apiUrl) { - throw new Error('创建 GPC 订单失败:缺少 API 地址。'); + throw new Error('Failed to create GPC order: missing API URL.'); } const phoneMode = normalizeGpcHelperPhoneMode(state?.gopayHelperPhoneMode || state?.phoneMode); const isAutoMode = phoneMode === GPC_HELPER_PHONE_MODE_AUTO; @@ -1279,10 +1279,10 @@ const pin = String(state?.gopayHelperPin || '').trim(); const apiKey = resolveGpcHelperApiKey(state); if (!isAutoMode && !phoneNumber) { - throw new Error('创建 GPC 订单失败:手动模式缺少手机号。'); + throw new Error('Failed to create GPC order: manual mode missing phone number.'); } if (!isAutoMode && !pin) { - throw new Error('创建 GPC 订单失败:手动模式缺少 PIN。'); + throw new Error('Failed to create GPC order: manual mode missing PIN.'); } throwIfStopped(); @@ -1315,7 +1315,7 @@ if (!response?.ok || !isGpcUnifiedResponseOk(data) || !taskId) { const detail = getGpcResponseErrorDetail(data, response?.status || 0); - throw new Error(`创建 GPC 订单失败:${detail}`); + throw new Error(`Failed to create GPC order: ${detail}`); } return { @@ -1335,7 +1335,7 @@ async function executeGpcCheckoutCreate(state = {}) { let accessToken = String(state?.contributionAccessToken || state?.accessToken || state?.chatgptAccessToken || '').trim(); if (!accessToken) { - await addLog('步骤 6:正在获取 accessToken...', 'info'); + await addLog('Step 6: retrieving accessToken...', 'info'); const tokenTabId = await openFreshChatGptTabForCheckoutCreate(); try { accessToken = await readAccessTokenFromChatGptSessionTab(tokenTabId); @@ -1346,10 +1346,10 @@ } } if (!accessToken) { - throw new Error('步骤 6:GPC 模式获取 accessToken 失败。'); + throw new Error('Step 6: GPC mode failed to retrieve accessToken.'); } - await addLog('步骤 6:正在调用 GPC 接口创建订单...', 'info'); + await addLog('Step 6: calling GPC API to create order...', 'info'); const result = await generateGpcCheckoutFromApi(accessToken, state); await setState({ plusCheckoutTabId: null, @@ -1374,7 +1374,7 @@ gopayHelperStartPayload: null, gopayHelperOrderCreatedAt: result.orderCreatedAt || Date.now(), }); - await addLog(`步骤 6:GPC ${result.phoneMode === GPC_HELPER_PHONE_MODE_AUTO ? '自动' : '手动'}模式任务已创建(task_id: ${result.taskId}),准备继续下一步。`, 'info'); + await addLog(`Step 6: GPC ${result.phoneMode === GPC_HELPER_PHONE_MODE_AUTO ? 'auto' : 'manual'} mode task created (task_id: ${result.taskId}), preparing to continue to next step.`, 'info'); await completeNodeFromBackground('plus-checkout-create', { plusCheckoutCountry: result.country || 'ID', plusCheckoutCurrency: result.currency || 'IDR', @@ -1391,7 +1391,7 @@ const paymentMethodLabel = getPlusPaymentMethodLabel(paymentMethod); const checkoutModeLabel = getCheckoutModeLabel(state); - await addLog(`步骤 6:正在打开新的 ChatGPT 会话,准备创建${checkoutModeLabel}...`, 'info'); + await addLog(`Step 6: opening new ChatGPT session, preparing to create ${checkoutModeLabel}...`, 'info'); const tabId = await openFreshChatGptTabForCheckoutCreate(); await waitForTabCompleteUntilStopped(tabId); @@ -1399,7 +1399,7 @@ await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, { inject: PLUS_CHECKOUT_INJECT_FILES, injectSource: PLUS_CHECKOUT_SOURCE, - logMessage: '步骤 6:正在等待 ChatGPT 页面完成加载,再继续创建订阅页...', + logMessage: 'Step 6: waiting for ChatGPT page to finish loading, then continuing to create subscription page...', }); const result = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, { @@ -1413,7 +1413,7 @@ } const targetCheckoutUrl = resolveCheckoutTargetUrl(result, paymentMethod); if (!targetCheckoutUrl) { - throw new Error(`步骤 6:${checkoutModeLabel}未返回可用的订阅链接。`); + throw new Error(`Step 6: ${checkoutModeLabel} did not return a usable subscription link.`); } if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) { @@ -1421,14 +1421,14 @@ return; } - await addLog(`步骤 6:${checkoutModeLabel}已创建,正在打开订阅页面...`, 'ok'); + await addLog(`Step 6: ${checkoutModeLabel} created, opening subscription page...`, 'ok'); await chrome.tabs.update(tabId, { url: targetCheckoutUrl, active: true }); await waitForTabCompleteUntilStopped(tabId); await sleepWithStop(1000); await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, { inject: PLUS_CHECKOUT_INJECT_FILES, injectSource: PLUS_CHECKOUT_SOURCE, - logMessage: '步骤 6:正在等待订阅页面完成加载...', + logMessage: 'Step 6: waiting for subscription page to finish loading...', }); await setState({ @@ -1439,7 +1439,7 @@ plusCheckoutSource: '', }); - await addLog(`步骤 6:Plus Checkout 页面已就绪(${paymentMethodLabel} / ${result.country || 'DE'} ${result.currency || 'EUR'}),准备继续下一步。`, 'info'); + await addLog(`Step 6: Plus Checkout page ready (${paymentMethodLabel} / ${result.country || 'DE'} ${result.currency || 'EUR'}), preparing to continue to next step.`, 'info'); await completeNodeFromBackground('plus-checkout-create', { plusCheckoutCountry: result.country || 'DE', diff --git a/flows/openai/background/steps/fetch-login-code.js b/flows/openai/background/steps/fetch-login-code.js index 1fc50829..e7ab0707 100644 --- a/flows/openai/background/steps/fetch-login-code.js +++ b/flows/openai/background/steps/fetch-login-code.js @@ -126,7 +126,7 @@ return async (details = {}) => getOAuthFlowRemainingMs({ step: visibleStep, - actionLabel: details.actionLabel || '登录验证码流程', + actionLabel: details.actionLabel || 'Login verification code flow', oauthUrl: expectedOauthUrl, }); } @@ -143,7 +143,7 @@ || '' ).trim(); if (!email) { - throw new Error(`步骤 ${visibleStep || 0}:缺少绑定邮箱,无法使用邮箱模式重新发起 OAuth 登录。`); + throw new Error(`Step ${visibleStep || 0}: missing bound email, unable to use email mode to re-initiate OAuth login.`); } return email; } @@ -179,7 +179,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 600, - logMessage: options.logMessage || `步骤 ${visibleStep}:认证页正在切换,等待页面重新就绪...`, + logMessage: options.logMessage || `Step ${visibleStep}: auth page is switching, waiting for page to be ready again...`, logStep: visibleStep, logStepKey: options.logStepKey || activeFetchLoginCodeStepKey || 'fetch-login-code', } @@ -199,7 +199,7 @@ ? initialPageState : await getLoginAuthStateFromContent(visibleStep, { timeoutMs: 15000, - logMessage: `步骤 ${visibleStep}:正在确认是否已进入添加邮箱页...`, + logMessage: `Step ${visibleStep}: confirming whether the add-email page has been reached...`, }); if (pageState?.state !== 'add_email_page') { return { state, pageState }; @@ -209,12 +209,12 @@ const resolvedEmail = await resolveSignupEmailForFlow(latestState, { preserveAccountIdentity: true, }); - await addLog(`步骤 ${visibleStep}:检测到添加邮箱页,正在添加邮箱 ${resolvedEmail} 并进入邮箱验证码页...`); + await addLog(`Step ${visibleStep}: detected add-email page, adding email ${resolvedEmail} and entering email verification code page...`); const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(60000, { step: visibleStep, - actionLabel: '添加邮箱并进入验证码页', + actionLabel: 'Add email and enter verification code page', oauthUrl: latestState?.oauthUrl || state?.oauthUrl || '', }) : 60000; @@ -232,7 +232,7 @@ timeoutMs, responseTimeoutMs: timeoutMs, retryDelayMs: 700, - logMessage: `步骤 ${visibleStep}:添加邮箱页面正在切换,等待邮箱验证码页就绪...`, + logMessage: `Step ${visibleStep}: add-email page is switching, waiting for email verification code page to be ready...`, logStep: visibleStep, logStepKey: activeFetchLoginCodeStepKey || 'fetch-login-code', } @@ -284,7 +284,7 @@ const fromRecovery = Boolean(options.fromRecovery); const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code'; await addLog( - `步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页${fromRecovery ? '(轮询失败后复核)' : ''},跳过登录验证码拉取并继续后续流程。`, + `Step ${visibleStep}: current auth page has entered OAuth authorization page${fromRecovery ? ' (post-polling-failure recheck)' : ''}, skipping login verification code fetch and continuing with subsequent flow.`, 'warn', { step: visibleStep, stepKey } ); @@ -304,7 +304,7 @@ }); const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code'; await addLog( - `步骤 ${visibleStep}:当前认证页已进入手机号验证流程,跳过登录邮箱验证码,交给后续“手机号验证”步骤处理。`, + `Step ${visibleStep}: current auth page has entered phone number verification flow, skipping login email verification code, deferring to subsequent "phone number verification" step.`, 'warn', { step: visibleStep, stepKey } ); @@ -324,7 +324,7 @@ loginVerificationRequestedAt: null, }); await addLog( - `步骤 ${visibleStep}:当前认证页已进入添加邮箱页,跳过登录短信验证码,交给后续“绑定邮箱”步骤处理。`, + `Step ${visibleStep}: current auth page has entered the add-email page, skipping login SMS verification code, deferring to subsequent "bind email" step.`, 'warn' ); if (typeof completeNodeFromBackground === 'function') { @@ -350,7 +350,7 @@ allowPhoneVerificationPage: true, allowAddEmailPage: true, timeoutMs: await getStep8ReadyTimeoutMs( - '登录验证码轮询异常后复核认证页状态', + 'Recheck auth page status after login verification code polling exception', currentState?.oauthUrl || '', visibleStep ), @@ -361,7 +361,7 @@ } if (pageState?.state === 'verification_page' || pageState?.state === 'phone_verification_page' || pageState?.state === 'add_email_page') { await addLog( - `步骤 ${visibleStep}:检测到邮箱轮询/页面通信异常,但认证页仍在当前登录后续页面,先在当前链路重试,不回到步骤 ${authLoginStep}。`, + `Step ${visibleStep}: detected mail polling/page-communication anomaly, but the auth page is still on the current post-login page. Retrying on the current path without going back to step ${authLoginStep}.`, 'warn' ); return { outcome: 'retry_without_step7' }; @@ -374,7 +374,7 @@ throw inspectError; } await addLog( - `步骤 ${visibleStep}:轮询失败后复核认证页状态异常:${inspectError?.message || inspectError},将回到步骤 ${authLoginStep} 重试。`, + `Step ${visibleStep}: error while rechecking auth page state after polling failure: ${inspectError?.message || inspectError}. Falling back to step ${authLoginStep}.`, 'warn' ); } @@ -430,10 +430,10 @@ async function executeLoginPhoneCodeStep(state, signupTabId, visibleStep) { if (!Number.isInteger(signupTabId)) { - throw new Error(`步骤 ${visibleStep}:认证页面标签页已关闭,无法继续手机号登录验证码流程。`); + throw new Error(`Step ${visibleStep}: auth page tab has been closed, cannot continue phone-number login verification code flow.`); } if (typeof phoneVerificationHelpers?.completeLoginPhoneVerificationFlow !== 'function') { - throw new Error(`步骤 ${visibleStep}:手机号登录验证码流程不可用,接码模块尚未初始化。`); + throw new Error(`Step ${visibleStep}: phone-number login verification code flow unavailable, SMS verification module not initialized.`); } const result = await phoneVerificationHelpers.completeLoginPhoneVerificationFlow(signupTabId, { @@ -456,14 +456,14 @@ return authTabId; } if (!state?.oauthUrl) { - throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成刷新 OAuth 并登录。`); + throw new Error(`Step ${visibleStep}: missing OAuth login URL, please refresh OAuth and log in first.`); } return reuseOrCreateTab('openai-auth', state.oauthUrl); } async function completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, options = {}) { const stepKey = options.stepKey || 'post-login-phone-verification'; - await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过手机号验证步骤。`, 'warn', { + await addLog(`Step ${visibleStep}: current auth page has entered OAuth authorization page, skipping phone-number verification step.`, 'warn', { step: visibleStep, stepKey, }); @@ -481,8 +481,8 @@ activeFetchLoginCodeStepKey = runtime.stepKey || 'post-login-phone-verification'; const authTabId = await ensureAuthTabForPostLoginStep(state, visibleStep); const pageState = await getLoginAuthStateFromContent(visibleStep, { - timeoutMs: await getStep8ReadyTimeoutMs('确认手机号验证页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep), - logMessage: `步骤 ${visibleStep}:正在确认是否需要手机号验证...`, + timeoutMs: await getStep8ReadyTimeoutMs('Confirm phone-verification page or OAuth authorization page is ready', state?.oauthUrl || '', visibleStep), + logMessage: `Step ${visibleStep}: confirming whether phone-number verification is needed...`, logStepKey: activeFetchLoginCodeStepKey, }); @@ -494,13 +494,13 @@ return; } if (pageState?.state !== 'add_phone_page' && pageState?.state !== 'phone_verification_page') { - throw new Error(`步骤 ${visibleStep}:手机号验证步骤只处理添加手机号页或手机验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: phone-verification step only handles add-phone or phone verification-code pages. Current state: ${pageState?.state || 'unknown'}. URL: ${pageState?.url || ''}`.trim()); } if (!state?.phoneVerificationEnabled) { - throw new Error(`步骤 ${visibleStep}:检测到需要手机号验证,但手机接码未开启。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: phone-number verification is required, but SMS verification is not enabled. URL: ${pageState?.url || ''}`.trim()); } if (typeof phoneVerificationHelpers?.completePhoneVerificationFlow !== 'function') { - throw new Error(`步骤 ${visibleStep}:手机号验证流程不可用,接码模块尚未初始化。`); + throw new Error(`Step ${visibleStep}: phone-number verification flow unavailable, SMS verification module not initialized.`); } const result = await phoneVerificationHelpers.completePhoneVerificationFlow(authTabId, pageState, { @@ -523,12 +523,12 @@ activeFetchLoginCodeStepKey = 'bind-email'; await ensureAuthTabForPostLoginStep(state, visibleStep); const pageState = await getLoginAuthStateFromContent(visibleStep, { - timeoutMs: await getStep8ReadyTimeoutMs('确认添加邮箱页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep), - logMessage: `步骤 ${visibleStep}:正在确认是否需要绑定邮箱...`, + timeoutMs: await getStep8ReadyTimeoutMs('Confirm add-email page or OAuth authorization page is ready', state?.oauthUrl || '', visibleStep), + logMessage: `Step ${visibleStep}: confirming whether email binding is needed...`, }); if (pageState?.state === 'oauth_consent_page') { - await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱步骤。`, 'warn', { + await addLog(`Step ${visibleStep}: current auth page has entered OAuth authorization page, skipping bind-email step.`, 'warn', { step: visibleStep, stepKey: 'bind-email', }); @@ -542,14 +542,14 @@ } if (pageState?.state !== 'add_email_page') { - throw new Error(`步骤 ${visibleStep}:绑定邮箱步骤只处理添加邮箱页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: bind-email step only handles the add-email page. Current state: ${pageState?.state || 'unknown'}. URL: ${pageState?.url || ''}`.trim()); } const addEmailPreparation = await submitAddEmailIfNeeded(state, visibleStep, pageState); const preparedState = addEmailPreparation?.state || state; const nextPageState = addEmailPreparation?.pageState || pageState; if (nextPageState?.state !== 'verification_page') { - throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后必须进入邮箱验证码页,当前状态:${nextPageState?.state || 'unknown'}。URL: ${nextPageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: after bind-email submission, must enter the email verification-code page. Current state: ${nextPageState?.state || 'unknown'}. URL: ${nextPageState?.url || ''}`.trim()); } if (typeof completeNodeFromBackground === 'function') { @@ -589,9 +589,9 @@ step8VerificationTargetEmail: displayedVerificationEmail || '', }); - await addLog(`步骤 ${visibleStep}:邮箱验证码页面已就绪,开始获取验证码。`, 'info'); + await addLog(`Step ${visibleStep}: email verification-code page is ready, starting to fetch the verification code.`, 'info'); if (shouldCompareVerificationEmail && displayedVerificationEmail) { - await addLog(`步骤 ${visibleStep}:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info'); + await addLog(`Step ${visibleStep}: locked the email ${displayedVerificationEmail} shown on the current verification-code page as the matching target.`, 'info'); } if (shouldUseCustomRegistrationEmail(preparedState)) { @@ -603,11 +603,11 @@ } if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') { - await addLog(`步骤 ${visibleStep}:正在确认 iCloud 邮箱登录态...`, 'info'); + await addLog(`Step ${visibleStep}: confirming iCloud mailbox sign-in...`, 'info'); await ensureIcloudMailSession({ state: preparedState, step: 8, - actionLabel: `步骤 ${visibleStep}:确认 iCloud 邮箱登录态`, + actionLabel: `Step ${visibleStep}: confirm iCloud mailbox sign-in`, }); } @@ -618,9 +618,9 @@ || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER || mail.provider === CLOUD_MAIL_PROVIDER ) { - await addLog(`步骤 ${visibleStep}:正在通过 ${mail.label} 轮询验证码...`); + await addLog(`Step ${visibleStep}: polling verification code via ${mail.label}...`); } else { - await addLog(`步骤 ${visibleStep}:正在打开${mail.label}...`); + await addLog(`Step ${visibleStep}: opening ${mail.label}...`); if (mail.provider === '2925' && typeof ensureMail2925MailboxSession === 'function') { await ensureMail2925MailboxSession({ accountId: preparedState.currentMail2925AccountId || null, @@ -633,7 +633,7 @@ await focusOrOpenMailTab(mail); } if (mail.provider === '2925') { - await addLog(`步骤 ${visibleStep}:将直接使用当前已登录的 ${mail.label} 轮询验证码。`, 'info'); + await addLog(`Step ${visibleStep}: will poll verification codes directly from the currently logged-in ${mail.label}.`, 'info'); } } @@ -673,7 +673,7 @@ } async function completeFetchBindEmailCodeSkippedOnOauth(visibleStep, options = {}) { - await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱验证码步骤。`, 'warn', { + await addLog(`Step ${visibleStep}: current auth page has entered OAuth authorization page, skipping bind-email verification code step.`, 'warn', { step: visibleStep, stepKey: 'fetch-bind-email-code', }); @@ -691,22 +691,22 @@ activeFetchLoginCodeStepKey = 'fetch-bind-email-code'; await ensureAuthTabForPostLoginStep(state, visibleStep); const pageState = await getLoginAuthStateFromContent(visibleStep, { - timeoutMs: await getStep8ReadyTimeoutMs('确认绑定邮箱验证码页已就绪', state?.oauthUrl || '', visibleStep), - logMessage: `步骤 ${visibleStep}:正在确认绑定邮箱验证码页...`, + timeoutMs: await getStep8ReadyTimeoutMs('Confirm bind-email verification code page is ready', state?.oauthUrl || '', visibleStep), + logMessage: `Step ${visibleStep}: confirming bind-email verification code page...`, }); if (pageState?.state === 'oauth_consent_page') { if (state?.bindEmailSubmitted) { - throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后不应直接进入 OAuth 授权页,必须先完成邮箱验证码。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: after bind-email submission the page must not jump directly to OAuth authorization. Email verification code is required first. URL: ${pageState?.url || ''}`.trim()); } await completeFetchBindEmailCodeSkippedOnOauth(visibleStep, { nodeId: state?.nodeId }); return; } if (pageState?.state !== 'verification_page') { - throw new Error(`步骤 ${visibleStep}:获取绑定邮箱验证码步骤只处理邮箱验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: fetch-bind-email-code step only handles the email verification-code page. Current state: ${pageState?.state || 'unknown'}. URL: ${pageState?.url || ''}`.trim()); } if (!state?.bindEmailSubmitted) { - throw new Error(`步骤 ${visibleStep}:尚未完成绑定邮箱提交,不能直接获取绑定邮箱验证码。`); + throw new Error(`Step ${visibleStep}: bind-email submission not completed, cannot fetch bind-email verification code directly.`); } return pollEmailVerificationCode(state, pageState, visibleStep, { @@ -725,7 +725,7 @@ await chrome.tabs.update(authTabId, { active: true }); } else { if (!preparedState.oauthUrl) { - throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成绑定邮箱后刷新 OAuth 并登录。`); + throw new Error(`Step ${visibleStep}: missing OAuth login URL. Please complete bind-email first, then refresh OAuth and sign in.`); } await reuseOrCreateTab('openai-auth', preparedState.oauthUrl); } @@ -736,7 +736,7 @@ authLoginStep: Math.max(1, visibleStep - 1), allowPhoneVerificationPage: true, allowAddEmailPage: false, - timeoutMs: await getStep8ReadyTimeoutMs('确认绑定邮箱登录验证码页已就绪', preparedState?.oauthUrl || '', visibleStep), + timeoutMs: await getStep8ReadyTimeoutMs('Confirm bound-email login verification code page is ready', preparedState?.oauthUrl || '', visibleStep), }); if (pageState?.state === 'oauth_consent_page') { @@ -754,10 +754,10 @@ return; } if (pageState?.state === 'add_email_page') { - throw new Error(`步骤 ${visibleStep}:绑定邮箱后邮箱模式登录不应再进入添加邮箱页。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: after binding email, email-mode login should no longer enter the add-email page. URL: ${pageState?.url || ''}`.trim()); } if (pageState?.state !== 'verification_page') { - throw new Error(`步骤 ${visibleStep}:绑定邮箱后获取登录验证码只处理邮箱登录验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: post-bind-email login verification code only handles the email login verification-code page. Current state: ${pageState?.state || 'unknown'}. URL: ${pageState?.url || ''}`.trim()); } return pollEmailVerificationCode(preparedState, pageState, visibleStep, { @@ -782,7 +782,7 @@ await chrome.tabs.update(authTabId, { active: true }); } else { if (!state.oauthUrl) { - throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForState(state, visibleStep)}。`); + throw new Error(`Missing OAuth login URL, please complete step ${getAuthLoginStepForState(state, visibleStep)} first.`); } await reuseOrCreateTab('openai-auth', state.oauthUrl); } @@ -793,7 +793,7 @@ authLoginStep: getAuthLoginStepForState(state, visibleStep), allowPhoneVerificationPage: true, allowAddEmailPage: true, - timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep), + timeoutMs: await getStep8ReadyTimeoutMs('Confirm login verification code page is ready', state?.oauthUrl || '', visibleStep), }); if (pageState?.state === 'oauth_consent_page') { await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: state?.nodeId }); @@ -809,12 +809,12 @@ return; } if (pageState?.state === 'verification_page') { - throw new Error(`步骤 ${visibleStep}:手机号注册模式只允许处理手机登录验证码,当前进入了普通邮箱登录验证码页,不会回落到邮箱 provider。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: in phone-number registration mode, only phone login verification codes are processed, but the page entered the email login verification-code page. It will not fall back to an email provider. URL: ${pageState?.url || ''}`.trim()); } if (pageState?.state === 'add_phone_page') { - throw new Error(`步骤 ${visibleStep}:手机号注册模式不应进入添加手机号页。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: phone-number registration mode should not enter the add-phone page. URL: ${pageState?.url || ''}`.trim()); } - throw new Error(`步骤 ${visibleStep}:手机号注册模式登录验证码步骤进入了不允许的页面:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: in phone-number registration mode, the login verification code step entered a disallowed page: ${pageState?.state || 'unknown'}. URL: ${pageState?.url || ''}`.trim()); } if (pageState?.state === 'add_phone_page' || pageState?.state === 'phone_verification_page') { @@ -822,7 +822,7 @@ return; } if (pageState?.state === 'add_email_page') { - throw new Error(`步骤 ${visibleStep}:邮箱注册模式不应进入添加邮箱页。URL: ${pageState?.url || ''}`.trim()); + throw new Error(`Step ${visibleStep}: email registration mode should not enter the add-email page. URL: ${pageState?.url || ''}`.trim()); } return pollEmailVerificationCode(state, pageState, visibleStep, runtime); @@ -890,11 +890,11 @@ retryWithoutStep7Streak += 1; if (retryWithoutStep7Streak > maxRetryWithoutStep7Streak) { await addLog( - `步骤 ${visibleStep}:邮箱通信异常在当前链路已连续重试 ${retryWithoutStep7Streak} 次,改为回到步骤 ${authLoginStep} 重新发起授权链路,避免空轮询循环。`, + `Step ${visibleStep}: mailbox communication anomaly retried ${retryWithoutStep7Streak} times on the current path. Going back to step ${authLoginStep} to reissue the auth flow to avoid an empty polling loop.`, 'warn' ); await rerunStep7ForStep8Recovery({ - logMessage: `邮箱通信异常持续未恢复,正在回到步骤 ${authLoginStep} 重新发起登录流程...`, + logMessage: `Mailbox communication anomaly persisting, going back to step ${authLoginStep} to restart the login flow...`, logStep: visibleStep, logStepKey: 'fetch-login-code', }); @@ -903,7 +903,7 @@ continue; } await addLog( - `步骤 ${visibleStep}:认证页仍保持在验证码页,将在当前链路直接重试(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS}),不回到步骤 ${authLoginStep}(连续同链路重试 ${retryWithoutStep7Streak}/${maxRetryWithoutStep7Streak})。`, + `Step ${visibleStep}: auth page is still on the verification-code page. Retrying on the current path (${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS}) without going back to step ${authLoginStep} (consecutive same-path retries ${retryWithoutStep7Streak}/${maxRetryWithoutStep7Streak}).`, 'warn' ); const latestState = await getState(); @@ -924,7 +924,7 @@ : 0; if (remainingBeforeRetryMs > 0 && typeof sleepWithStop === 'function') { await addLog( - `步骤 ${visibleStep}:上轮已触发重发验证码,为避免重复重发,先等待 ${Math.ceil(remainingBeforeRetryMs / 1000)} 秒后继续当前链路重试。`, + `Step ${visibleStep}: previous round already triggered a verification-code resend. To avoid duplicate resends, waiting ${Math.ceil(remainingBeforeRetryMs / 1000)} seconds before retrying on the current path.`, 'info' ); await sleepWithStop(Math.min(remainingBeforeRetryMs, 3000)); @@ -934,14 +934,14 @@ retryWithoutStep7Streak = 0; await addLog( isStep8RestartStep7Error(currentError) - ? `步骤 ${visibleStep}:检测到认证页进入重试/超时报错状态,准备从步骤 ${authLoginStep} 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...` - : `步骤 ${visibleStep}:检测到邮箱轮询类失败,准备从步骤 ${authLoginStep} 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`, + ? `Step ${visibleStep}: detected auth page entered a retry/timeout error state. Restarting from step ${authLoginStep} (${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...` + : `Step ${visibleStep}: detected mail polling failure. Restarting from step ${authLoginStep} (${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`, 'warn' ); await rerunStep7ForStep8Recovery({ logMessage: isStep8RestartStep7Error(currentError) - ? `认证页进入重试/超时报错状态,正在回到步骤 ${authLoginStep} 重新发起登录流程...` - : `正在回到步骤 ${authLoginStep},重新发起登录验证码流程...`, + ? `Auth page entered a retry/timeout error state, going back to step ${authLoginStep} to restart the login flow...` + : `Going back to step ${authLoginStep} to restart the login verification code flow...`, logStep: visibleStep, logStepKey: 'fetch-login-code', }); @@ -952,11 +952,11 @@ const visibleStep = getVisibleStep(currentState, 8); if (lastMailPollingError) { throw new Error( - `步骤 ${visibleStep}:登录验证码流程在 ${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS} 轮邮箱轮询恢复后仍未成功。最后一次原因:${lastMailPollingError.message}` + `Step ${visibleStep}: login verification code flow did not succeed after ${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS} rounds of mailbox polling recovery. Last reason: ${lastMailPollingError.message}` ); } - throw new Error(`步骤 ${visibleStep}:登录验证码流程未成功完成。`); + throw new Error(`Step ${visibleStep}: login verification code flow did not complete successfully.`); } return { diff --git a/flows/openai/background/steps/fetch-signup-code.js b/flows/openai/background/steps/fetch-signup-code.js index 4701b8d7..ce8400d9 100644 --- a/flows/openai/background/steps/fetch-signup-code.js +++ b/flows/openai/background/steps/fetch-signup-code.js @@ -70,7 +70,7 @@ async function executeSignupPhoneCodeStep(state, signupTabId) { if (typeof phoneVerificationHelpers?.completeSignupPhoneVerificationFlow !== 'function') { - throw new Error('步骤 4:手机号注册验证码流程不可用,接码模块尚未初始化。'); + throw new Error('Step 4: phone-number registration verification code flow unavailable, SMS verification module not initialized.'); } const signupProfile = buildSignupProfileForVerificationStep(); @@ -106,11 +106,11 @@ : stepStartedAt; if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') { - await addLog('步骤 4:正在确认 iCloud 邮箱登录态...', 'info'); + await addLog('Step 4: confirming iCloud mailbox sign-in...', 'info'); await ensureIcloudMailSession({ state, step: 4, - actionLabel: '步骤 4:确认 iCloud 邮箱登录态', + actionLabel: 'Step 4: confirm iCloud mailbox sign-in', }); } @@ -121,23 +121,23 @@ || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER || mail.provider === CLOUD_MAIL_PROVIDER ) { - await addLog(`步骤 4:正在通过 ${mail.label} 轮询验证码...`); + await addLog(`Step 4: polling verification code via ${mail.label}...`); } else if (mail.provider === '2925') { - await addLog(`步骤 4:正在打开${mail.label}...`); + await addLog(`Step 4: opening ${mail.label}...`); if (typeof ensureMail2925MailboxSession === 'function') { await ensureMail2925MailboxSession({ accountId: state.currentMail2925AccountId || null, forceRelogin: false, allowLoginWhenOnLoginPage: Boolean(state?.mail2925UseAccountPool), expectedMailboxEmail: getExpectedMail2925MailboxEmail(state), - actionLabel: '步骤 4:确认 2925 邮箱登录态', + actionLabel: 'Step 4: confirm 2925 mailbox sign-in', }); } else { await focusOrOpenMailTab(mail); } - await addLog(`步骤 4:将直接使用当前已登录的 ${mail.label} 轮询验证码。`, 'info'); + await addLog(`Step 4: will poll verification codes directly from the currently logged-in ${mail.label}.`, 'info'); } else { - await addLog(`步骤 4:正在打开${mail.label}...`); + await addLog(`Step 4: opening ${mail.label}...`); await focusOrOpenMailTab(mail); } @@ -191,13 +191,13 @@ const signupTabId = await getTabId('openai-auth'); if (!signupTabId) { - throw new Error('认证页面标签页已关闭,无法继续步骤 4。请先执行步骤 1 或步骤 2,重新打开认证页后再试。'); + throw new Error('Auth page tab is closed, cannot continue step 4. Please run step 1 or step 2 first to reopen the auth page and retry.'); } await chrome.tabs.update(signupTabId, { active: true }); throwIfStopped(); if (typeof waitForTabStableComplete === 'function') { - await addLog('步骤 4:等待注册验证码页面完成加载后再继续...', 'info'); + await addLog('Step 4: waiting for signup verification code page to finish loading before continuing...', 'info'); await waitForTabStableComplete(signupTabId, { timeoutMs: 45000, retryDelayMs: 300, @@ -206,7 +206,7 @@ }); } throwIfStopped(); - await addLog('步骤 4:正在确认注册验证码页面是否就绪,必要时自动恢复密码页超时报错...'); + await addLog('Step 4: confirming signup verification code page is ready, recovering from password page timeout if needed...'); const prepareRequest = { type: 'PREPARE_SIGNUP_VERIFICATION', @@ -215,7 +215,7 @@ payload: { password: state.password || state.customPassword || '', prepareSource: 'step4_execute', - prepareLogLabel: '步骤 4 执行', + prepareLogLabel: 'Step 4 execution', }, }; const prepareTimeoutMs = 30000; @@ -235,7 +235,7 @@ timeoutMs: Math.max(1000, prepareTimeoutMs - (Date.now() - prepareStartAt)), responseTimeoutMs: prepareResponseTimeoutMs, retryDelayMs: 700, - logMessage: '步骤 4:认证页正在切换,等待页面重新就绪后继续检测...', + logMessage: 'Step 4: auth page is switching, waiting for page to be ready again before continuing detection...', }); break; } catch (error) { @@ -257,13 +257,13 @@ step: 4, timeoutMs: Math.min(12000, remainingMs), maxClickAttempts: 2, - logLabel: '步骤 4:检测到注册认证重试页,正在点击“重试”恢复', + logLabel: 'Step 4: detected signup auth retry page, clicking "Retry" to recover', }, }, { timeoutMs: Math.min(12000, remainingMs), responseTimeoutMs: Math.min(12000, remainingMs), retryDelayMs: 700, - logMessage: '步骤 4:认证页正在切换,等待页面重新就绪后继续检测...', + logMessage: 'Step 4: auth page is switching, waiting for page to be ready again before continuing detection...', }); if (recoverResult?.error) { @@ -273,7 +273,7 @@ } if (!prepareResult) { - throw new Error('步骤 4:等待注册验证码页面就绪超时,请刷新认证页后重试。'); + throw new Error('Step 4: timed out waiting for signup verification code page to be ready. Please refresh the auth page and retry.'); } if (prepareResult && prepareResult.error) { @@ -287,7 +287,7 @@ if (isPhoneSignupState(state)) { const phoneResult = await executeSignupPhoneCodeStep(state, signupTabId); if (phoneResult?.emailVerificationRequired || phoneResult?.emailVerificationPage) { - await addLog('步骤 4:手机验证码已通过,OpenAI 要求继续邮箱验证,切换到邮箱验证码轮询。', 'info'); + await addLog('Step 4: phone verification code passed, OpenAI requires email verification next. Switching to email verification code polling.', 'info'); return executeSignupEmailVerificationStep(state, stepStartedAt, verificationSessionKey); } return phoneResult; diff --git a/flows/openai/background/steps/fill-password.js b/flows/openai/background/steps/fill-password.js index d2c47b67..b7309a8b 100644 --- a/flows/openai/background/steps/fill-password.js +++ b/flows/openai/background/steps/fill-password.js @@ -66,14 +66,14 @@ const identity = resolveStep3AccountIdentity(state); if (!identity.accountIdentifier) { if (identity.accountIdentifierType === 'phone') { - throw new Error('缺少注册手机号,请先完成步骤 2 或在侧栏填写注册手机号后再执行步骤 3。'); + throw new Error('Missing registration phone number. Please complete step 2 or set the registration phone number in the side panel before running step 3.'); } - throw new Error('缺少注册账号,请先完成步骤 2。'); + throw new Error('Missing registration account, please complete step 2 first.'); } const signupTabId = await getTabId('openai-auth'); if (!signupTabId || !(await isTabAlive('openai-auth'))) { - throw new Error('认证页面标签页已关闭,请先重新完成步骤 2。'); + throw new Error('Auth page tab is closed, please re-run step 2 first.'); } const password = state.customPassword || state.password || generatePassword(); @@ -95,14 +95,14 @@ injectSource: 'openai-auth', timeoutMs: 45000, retryDelayMs: 900, - logMessage: '步骤 3:密码页内容脚本未就绪,正在等待页面恢复...', + logMessage: 'Step 3: password page content script not ready, waiting for page recovery...', }); const identityLabel = identity.accountIdentifierType === 'phone' - ? `注册手机号为 ${identity.accountIdentifier}` - : `邮箱为 ${identity.accountIdentifier}`; + ? `registration phone ${identity.accountIdentifier}` + : `email ${identity.accountIdentifier}`; await addLog( - `步骤 3:正在填写密码,${identityLabel},密码为${state.customPassword ? '自定义' : '自动生成'}(${password.length} 位)` + `Step 3: filling password, ${identityLabel}, password is ${state.customPassword ? 'custom' : 'auto-generated'} (${password.length} chars)` ); await sendToContentScript('openai-auth', { type: 'EXECUTE_NODE', diff --git a/flows/openai/background/steps/fill-plus-checkout.js b/flows/openai/background/steps/fill-plus-checkout.js index cc5737f4..ec2959c6 100644 --- a/flows/openai/background/steps/fill-plus-checkout.js +++ b/flows/openai/background/steps/fill-plus-checkout.js @@ -16,20 +16,20 @@ const GPC_TASK_POLL_INTERVAL_MS = 3000; const GPC_TASK_STALE_STATUS_TIMEOUT_MS = 60000; const GPC_REMOTE_STAGE_LABELS = { - auto_otp_wait: '等待自动 OTP', - checkout_order_start: '创建订单', - checkout_start: '创建订单', - completed: '充值完成', - gopay_validate_pin: '校验 PIN', - otp_ready: '等待 PIN', - otp_submitted_local: 'OTP 已提交', - payment_processing: '支付处理中', - pin_submitted_local: 'PIN 已提交', - sms_otp_wait: '等待短信 OTP', - whatsapp_otp_wait: '等待 WhatsApp OTP', + auto_otp_wait: 'Waiting for auto OTP', + checkout_order_start: 'Create order', + checkout_start: 'Create order', + completed: 'Top-up complete', + gopay_validate_pin: 'Validate PIN', + otp_ready: 'Waiting for PIN', + otp_submitted_local: 'OTP submitted', + payment_processing: 'Payment processing', + pin_submitted_local: 'PIN submitted', + sms_otp_wait: 'Waiting for SMS OTP', + whatsapp_otp_wait: 'Waiting for WhatsApp OTP', }; const GPC_WAITING_FOR_LABELS = { - auto_otp: '自动 OTP', + auto_otp: 'Auto OTP', otp: 'OTP', pin: 'PIN', }; @@ -167,10 +167,10 @@ const remoteStage = String(task?.remote_stage || task?.remoteStage || '').trim(); const stageText = formatGpcRemoteStageLabel(remoteStage); const waitingForText = formatGpcWaitingForLabel(task?.api_waiting_for || task?.apiWaitingFor || ''); - const mainText = stageText || statusText || status || '处理中'; - const parts = [`步骤 7:GPC 任务状态:${mainText}`]; + const mainText = stageText || statusText || status || 'Processing'; + const parts = [`Step 7: GPC task status: ${mainText}`]; if (waitingForText && !mainText.includes(waitingForText)) { - parts.push(`,等待 ${waitingForText}`); + parts.push(`, waiting for ${waitingForText}`); } return parts.join(''); } @@ -208,13 +208,13 @@ ? fetchImpl : (typeof fetch === 'function' ? fetch.bind(globalThis) : null); if (typeof fetcher !== 'function') { - throw new Error('当前运行环境不支持 fetch,无法调用 GPC API。'); + throw new Error('Current runtime does not support fetch, cannot call GPC API.'); } const controller = typeof AbortController === 'function' ? new AbortController() : null; const effectiveTimeoutMs = Math.max(1000, Number(timeoutMs) || 30000); let didTimeout = false; let timer = null; - const buildTimeoutError = () => new Error(`GPC API 请求超时(>${Math.round(effectiveTimeoutMs / 1000)} 秒):${url}`); + const buildTimeoutError = () => new Error(`GPC API request timed out (>${Math.round(effectiveTimeoutMs / 1000)} s): ${url}`); const timeoutPromise = new Promise((_, reject) => { timer = setTimeout(() => { didTimeout = true; @@ -474,7 +474,7 @@ return { ...firstResponse, retried: false, payload: primaryPayload }; } const firstDetail = getGpcResponseErrorDetail(firstResponse?.data, status); - await addLog(`步骤 7:GPC 接口返回 ${status}(${firstDetail}),使用兼容字段重试。`, 'warn'); + await addLog(`Step 7: GPC endpoint returned ${status} (${firstDetail}), retrying with compatible fields.`, 'warn'); const secondResponse = await send(fallbackPayload); return { ...secondResponse, @@ -653,18 +653,18 @@ } lastMessage = String(data?.message || data?.status || '').trim(); } catch (error) { - lastMessage = error?.message || String(error || '未知错误'); + lastMessage = error?.message || String(error || 'Unknown error'); } if (singleAttempt) { break; } await sleepWithStop(pollIntervalSeconds * 1000); } - throw new Error(lastMessage || '本地 SMS Helper 等待 OTP 超时。'); + throw new Error(lastMessage || 'Local SMS Helper timed out waiting for OTP.'); } function buildGpcTaskEndedError(task = {}, fallbackMessage = '') { - const detail = buildGpcTaskTerminalError(task) || fallbackMessage || 'GPC 任务已结束,请重新创建任务。'; + const detail = buildGpcTaskTerminalError(task) || fallbackMessage || 'GPC task ended, please recreate the task.'; return new Error(`GPC_TASK_ENDED::${detail}`); } @@ -673,10 +673,10 @@ return deadlineMs > 0 && Date.now() > deadlineMs; } - function buildGpcInputDeadlineError(task = {}, label = '输入') { + function buildGpcInputDeadlineError(task = {}, label = 'input') { const stage = String(task?.remote_stage || '').trim(); - const detail = `${label}提交已超时,请重新创建任务。`; - return new Error(`GPC_TASK_ENDED::${detail}${stage ? `(${stage})` : ''}`); + const detail = `${label} submission timed out, please recreate the task.`; + return new Error(`GPC_TASK_ENDED::${detail}${stage ? ` (${stage})` : ''}`); } function normalizeSixDigitOtp(value = '') { @@ -690,7 +690,7 @@ } function isGpcOtpFormatConflict(error) { - return /OTP\s*必须是\s*6\s*位数字|OTP.*6.*digit|task_conflict/i.test(error?.message || String(error || '')); + return /OTP must be 6 digits|OTP必须是\s*6\s*位数字|OTP.*6.*digit|task_conflict/i.test(error?.message || String(error || '')); } async function requestGpcOtpInput({ title = '', message = '', taskId = '', lastInputError = '', inputDeadlineAt = '' }) { @@ -711,8 +711,8 @@ plusManualConfirmationRequestId: requestId, plusManualConfirmationStep: 7, plusManualConfirmationMethod: 'gopay-otp', - plusManualConfirmationTitle: title || 'GPC OTP 验证', - plusManualConfirmationMessage: message || '请输入 OTP 验证码', + plusManualConfirmationTitle: title || 'GPC OTP verification', + plusManualConfirmationMessage: message || 'Please enter the OTP verification code', gopayHelperLastInputError: String(lastInputError || '').trim(), gopayHelperApiInputDeadlineAt: String(inputDeadlineAt || '').trim(), gopayHelperResolvedOtp: '', @@ -749,7 +749,7 @@ if (typeof broadcastDataUpdate === 'function') { broadcastDataUpdate(clearPayload); } - reject(new Error('OTP 输入超时')); + reject(new Error('OTP input timed out')); return; } const currentState = await getStateInternal(); @@ -759,7 +759,7 @@ if (resolvedOtp) { resolve(resolvedOtp); } else { - reject(new Error('OTP 输入已取消')); + reject(new Error('OTP input cancelled')); } } } catch (error) { @@ -824,8 +824,8 @@ const label = formatGpcRemoteStageLabel(task?.remote_stage) || task?.status_text || task?.status - || '未知状态'; - return new Error(`GPC_TASK_ENDED::GPC 任务状态超过 ${seconds} 秒无进展(${label}),请重新创建任务。`); + || 'unknown state'; + return new Error(`GPC_TASK_ENDED::GPC task made no progress for ${seconds} seconds (${label}), please recreate the task.`); } function buildGpcTaskTerminalError(task = {}) { @@ -848,19 +848,19 @@ ).trim(); if (detail) { return failureStage && !detail.includes(failureStage) - ? `${detail}(${failureStage})` + ? `${detail} (${failureStage})` : detail; } if (/api_otp_timeout/i.test(remoteStage)) { - return 'GPC OTP 超时,请重新创建任务'; + return 'GPC OTP timed out, please recreate the task'; } if (/api_pin_timeout/i.test(remoteStage)) { - return 'GPC PIN 超时,请重新创建任务'; + return 'GPC PIN timed out, please recreate the task'; } if (failureStage) { - return `GPC 任务失败:${failureStage}`; + return `GPC task failed: ${failureStage}`; } - return `任务状态 ${status || '未知'}`; + return `Task status ${status || 'unknown'}`; } async function stopGpcTaskBestEffort(apiUrl, taskId, apiKey, reason = '') { @@ -869,10 +869,10 @@ } try { const task = await postGpcTaskAction(apiUrl, taskId, 'stop', {}, apiKey, 15000); - const statusText = task?.status_text || task?.status || '已停止'; - await addLog(`步骤 7:已请求停止 GPC 任务(${statusText})。`, 'warn'); + const statusText = task?.status_text || task?.status || 'stopped'; + await addLog(`Step 7: requested to stop GPC task (${statusText}).`, 'warn'); } catch (error) { - await addLog(`步骤 7:停止 GPC 任务失败${reason ? `(${reason})` : ''}:${error?.message || String(error || '未知错误')}`, 'warn'); + await addLog(`Step 7: failed to stop GPC task${reason ? ` (${reason})` : ''}: ${error?.message || String(error || 'Unknown error')}`, 'warn'); } } @@ -924,8 +924,8 @@ try { await addLog( retryCount > 0 || lastInputError - ? `步骤 7:${lastInputError || 'OTP 校验未通过'},正在从本地 OTP Helper 等待新的 GPC OTP...` - : '步骤 7:正在从本地 OTP Helper 等待 GPC OTP...', + ? `Step 7: ${lastInputError || 'OTP validation failed'}, waiting for new GPC OTP from local OTP Helper...` + : 'Step 7: waiting for GPC OTP from local OTP Helper...', 'info' ); otp = await pollLocalSmsHelperOtp(state, taskId, { @@ -934,9 +934,9 @@ requestTimeoutMs: 2000, timeoutSeconds: 2, }); - await addLog('步骤 7:本地 OTP Helper 已读取到 GPC OTP,准备提交验证。', 'ok'); + await addLog('Step 7: local OTP Helper has read the GPC OTP, preparing to submit for verification.', 'ok'); } catch (error) { - await addLog(`步骤 7:本地 OTP Helper 暂未读取到新 OTP:${error?.message || String(error || '未知错误')},继续等待远端任务状态更新。`, 'warn'); + await addLog(`Step 7: local OTP Helper has not yet read a new OTP: ${error?.message || String(error || 'Unknown error')}, continuing to wait for remote task status update.`, 'warn'); } } if (otp) { @@ -945,12 +945,12 @@ if (useLocalSmsHelper) { return ''; } - await addLog('步骤 7:等待用户输入 OTP...', 'info'); + await addLog('Step 7: waiting for user to enter OTP...', 'info'); return requestGpcOtpInput({ - title: 'GPC OTP 验证', + title: 'GPC OTP verification', message: retryCount > 0 || lastInputError - ? `${lastInputError || '上一次 OTP 校验未通过'},请重新输入正确的 OTP 验证码(task_id: ${taskId})` - : `请输入收到的 OTP 验证码(task_id: ${taskId})`, + ? `${lastInputError || 'Previous OTP validation failed'}. Please re-enter the correct OTP (task_id: ${taskId})` + : `Please enter the received OTP (task_id: ${taskId})`, lastInputError, inputDeadlineAt: options?.inputDeadlineAt || options?.input_deadline_at || '', taskId, @@ -983,13 +983,13 @@ const staleStatusTimeoutMs = getGpcTaskStaleStatusTimeoutMs(state); if (!taskId) { - throw new Error('步骤 7:GPC 模式缺少 task_id,请先执行步骤 6。'); + throw new Error('Step 7: GPC mode is missing task_id, please run step 6 first.'); } if (!apiUrl) { - throw new Error('步骤 7:GPC 模式缺少 API 地址。'); + throw new Error('Step 7: GPC mode is missing API URL.'); } if (!apiKey) { - throw new Error('步骤 7:GPC 模式缺少 API Key。'); + throw new Error('Step 7: GPC mode is missing API key.'); } const configuredPhoneMode = normalizeGpcHelperPhoneMode(state?.gopayHelperPhoneMode || state?.phoneMode || GPC_HELPER_PHONE_MODE_MANUAL); @@ -998,14 +998,14 @@ const pin = normalizeSixDigitPin(rawPin); if (configuredPhoneMode === GPC_HELPER_PHONE_MODE_MANUAL && !pin) { if (taskId && apiUrl && apiKey) { - await stopGpcTaskBestEffort(apiUrl, taskId, apiKey, 'PIN 配置错误'); + await stopGpcTaskBestEffort(apiUrl, taskId, apiKey, 'PIN config error'); } throw new Error(pinDigits - ? '步骤 7:GPC PIN 必须是 6 位数字,请检查侧边栏配置。' - : '步骤 7:GPC 手动模式缺少 PIN 配置。'); + ? 'Step 7: GPC PIN must be 6 digits, please check the side-panel config.' + : 'Step 7: GPC manual mode is missing PIN config.'); } - await addLog(`步骤 7:GPC ${configuredPhoneMode === GPC_HELPER_PHONE_MODE_AUTO ? '自动' : '手动'}模式开始轮询任务(task_id: ${taskId})...`, 'info'); + await addLog(`Step 7: GPC ${configuredPhoneMode === GPC_HELPER_PHONE_MODE_AUTO ? 'auto' : 'manual'} mode started polling task (task_id: ${taskId})...`, 'info'); try { while (Date.now() <= deadline) { throwIfStopped(); @@ -1017,7 +1017,7 @@ await setState({ plusCheckoutSource: PLUS_PAYMENT_METHOD_GPC_HELPER, }); - await addLog('步骤 7:GPC 任务已完成,准备继续下一步。', 'ok'); + await addLog('Step 7: GPC task completed, preparing to continue with the next step.', 'ok'); await completeNodeFromBackground('plus-checkout-billing', { plusCheckoutSource: PLUS_PAYMENT_METHOD_GPC_HELPER, }); @@ -1026,7 +1026,7 @@ if (['failed', 'expired', 'discarded'].includes(task.status)) { terminalReached = true; - throw buildGpcTaskEndedError(task, 'GPC 任务已结束,请重新创建任务。'); + throw buildGpcTaskEndedError(task, 'GPC task ended, please recreate the task.'); } if (shouldWatchGpcTaskProgress(task, state)) { @@ -1061,7 +1061,7 @@ } if (task.last_input_error) { await addLog( - `步骤 7:${task.last_input_error}${task.otp_invalid_count ? `(OTP 错误 ${task.otp_invalid_count} 次)` : ''}`, + `Step 7: ${task.last_input_error}${task.otp_invalid_count ? ` (OTP errors ${task.otp_invalid_count})` : ''}`, 'warn' ); } @@ -1074,8 +1074,8 @@ inputDeadlineAt: task.api_input_deadline_at, }); } catch (error) { - if (/OTP\s*输入(?:已取消|超时)/i.test(error?.message || String(error || ''))) { - throw new Error(`GPC_TASK_ENDED::${error?.message || 'OTP 输入已取消'},已结束当前 GPC 任务。`); + if (/OTP input (?:cancelled|timed out)|OTP\s*输入(?:已取消|超时)/i.test(error?.message || String(error || ''))) { + throw new Error(`GPC_TASK_ENDED::${error?.message || 'OTP input cancelled'}, current GPC task ended.`); } throw error; } @@ -1085,16 +1085,16 @@ } const normalizedOtp = normalizeSixDigitOtp(otp); if (!normalizedOtp) { - await addLog('步骤 7:OTP 必须是 6 位数字,等待重新输入。', 'warn'); + await addLog('Step 7: OTP must be 6 digits, waiting for a new input.', 'warn'); await sleepWithStop(GPC_TASK_POLL_INTERVAL_MS); continue; } if (task.last_input_error && lastSubmittedOtp && normalizedOtp === lastSubmittedOtp) { - await addLog('步骤 7:本地 OTP Helper 返回的仍是上次已失败 OTP,等待新的验证码。', 'warn'); + await addLog('Step 7: local OTP Helper still returned the previously failed OTP, waiting for a new code.', 'warn'); await sleepWithStop(GPC_TASK_POLL_INTERVAL_MS); continue; } - await addLog('步骤 7:正在提交 OTP...', 'info'); + await addLog('Step 7: submitting OTP...', 'info'); try { await postGpcTaskAction( apiUrl, @@ -1106,7 +1106,7 @@ ); } catch (error) { if (isGpcOtpFormatConflict(error)) { - await addLog(`步骤 7:OTP 提交被拒绝:${error?.message || String(error || 'OTP 格式错误')},等待重新输入。`, 'warn'); + await addLog(`Step 7: OTP submission was rejected: ${error?.message || String(error || 'OTP format error')}. Waiting for re-entry.`, 'warn'); await sleepWithStop(GPC_TASK_POLL_INTERVAL_MS); continue; } @@ -1115,12 +1115,12 @@ otpSubmitCount += 1; otpLastSubmittedAt = Date.now(); lastSubmittedOtp = normalizedOtp; - await addLog('步骤 7:OTP 已提交,继续等待 GPC 任务状态更新。', 'ok'); + await addLog('Step 7: OTP submitted, continuing to wait for GPC task status updates.', 'ok'); } else if (isGpcTaskPinWait(task, state) && !pinSubmitted) { if (isGpcTaskInputDeadlineExpired(task)) { throw buildGpcInputDeadlineError(task, 'PIN'); } - await addLog('步骤 7:正在提交 PIN...', 'info'); + await addLog('Step 7: Submitting PIN...', 'info'); let pinTask = null; try { pinTask = await postGpcTaskAction( @@ -1132,21 +1132,21 @@ 30000 ); } catch (error) { - throw new Error(`GPC_TASK_ENDED::${error?.message || String(error || 'PIN 提交失败,请重新创建任务。')}`); + throw new Error(`GPC_TASK_ENDED::${error?.message || String(error || 'PIN submission failed. Please recreate the task.')}`); } pinSubmitted = true; await setState({ gopayHelperPinPayload: pinTask, }); - await addLog('步骤 7:PIN 已提交,继续轮询直到任务完成。', 'ok'); + await addLog('Step 7: PIN submitted, continuing to poll until the task completes.', 'ok'); } await sleepWithStop(GPC_TASK_POLL_INTERVAL_MS); } - throw new Error('步骤 7:GPC 任务轮询超时。'); + throw new Error('Step 7: GPC task polling timed out.'); } catch (error) { if (!terminalReached) { - await stopGpcTaskBestEffort(apiUrl, taskId, apiKey, error?.message || '流程中断'); + await stopGpcTaskBestEffort(apiUrl, taskId, apiKey, error?.message || 'flow interrupted'); } if (isGpcTaskEndedError(error)) { await clearGpcTaskRuntimeState(); @@ -1251,7 +1251,7 @@ } const data = await response.json(); if (data?.status !== 'ok') { - throw new Error(data?.message || data?.status || '未知响应'); + throw new Error(data?.message || data?.status || 'Unknown response'); } return buildDirectAddressSeed(countryCode, data.address || {}, fallbackSeed); } @@ -1322,31 +1322,31 @@ const countryCode = countryResolution.countryCode; const requestedCountry = countryResolution.requestedCountry; if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY && countryResolution.source === 'proxy_exit') { - await addLog(`步骤 7:GoPay 账单地址将按当前代理出口地区 ${countryCode} 填写。`, 'info'); + await addLog(`Step 7: The GoPay billing address will be filled based on the current proxy exit region ${countryCode}.`, 'info'); } const localSeed = getLocalAddressSeed(countryCode); const lookupSeed = localSeed || buildMeiguodizhiLookupSeed(countryCode); if (!lookupSeed) { - throw new Error(`步骤 7:无法识别账单国家或地区:${requestedCountry || '空'}`); + throw new Error(`Step 7: Unable to identify the billing country/region: ${requestedCountry || 'empty'}`); } try { const remoteSeed = await fetchMeiguodizhiAddressSeed(countryCode, lookupSeed); if (hasCompleteAddressFallback(remoteSeed)) { await addLog( - `步骤 7:已从 meiguodizhi 接口获取账单地址(${remoteSeed.fallback.city} / ${remoteSeed.fallback.postalCode}),将跳过 Google 地址推荐。`, + `Step 7: Fetched the billing address from the meiguodizhi API (${remoteSeed.fallback.city} / ${remoteSeed.fallback.postalCode}); skipping Google address suggestions.`, 'info' ); return remoteSeed; } - await addLog('步骤 7:meiguodizhi 接口返回的地址字段不完整,回退到本地地址种子。', 'warn'); + await addLog('Step 7: The meiguodizhi API returned incomplete address fields; falling back to the local address seed.', 'warn'); } catch (error) { - await addLog(`步骤 7:meiguodizhi 地址接口不可用,回退到本地地址种子:${error?.message || String(error || '')}`, 'warn'); + await addLog(`Step 7: The meiguodizhi address API is unavailable; falling back to the local address seed: ${error?.message || String(error || '')}`, 'warn'); } if (hasCompleteAddressFallback(localSeed)) { return localSeed; } - throw new Error(`步骤 7:${requestedCountry} 的 meiguodizhi 地址不可用,且没有本地兜底地址。`); + throw new Error(`Step 7: The meiguodizhi address for ${requestedCountry} is unavailable, and no local fallback address exists.`); } async function getAlivePlusCheckoutTabId(tabId) { @@ -1456,7 +1456,7 @@ while (Date.now() - startedAt < PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS) { const tab = await chrome.tabs.get(tabId).catch(() => null); if (!tab) { - throw new Error(`步骤 7:checkout 标签页已关闭,无法继续等待 ${paymentConfig.label} 跳转。`); + throw new Error(`Step 7: The checkout tab has been closed. Cannot continue waiting for the ${paymentConfig.label} redirect.`); } const url = String(tab.url || ''); if (paymentConfig.redirectPattern.test(url) && !isPlusCheckoutUrl(url)) { @@ -1465,7 +1465,7 @@ return true; } if (url && !isPlusCheckoutUrl(url)) { - await addLog(`步骤 7:点击订阅后页面跳转到非 ${paymentConfig.label} 识别地址:${url}`, 'warn'); + await addLog(`Step 7: After clicking Subscribe, the page redirected to a non-${paymentConfig.label} recognized URL: ${url}`, 'warn'); return false; } await sleepWithStop(500); @@ -1569,29 +1569,29 @@ } async function ensureFreeTrialAmount(tabId, state = {}, options = {}) { - const phaseLabel = String(options.phaseLabel || '').trim() || '提交前'; + const phaseLabel = String(options.phaseLabel || '').trim() || 'Before submission'; const amountSummary = await inspectCheckoutAmountSummary(tabId); if (!amountSummary?.hasTodayDue) { - await addLog(`步骤 7:${phaseLabel}未能识别 checkout 的“今日应付金额”,为避免误判将继续执行。`, 'warn'); + await addLog(`Step 7: ${phaseLabel} could not identify checkout's "today due" amount; continuing to avoid a false positive.`, 'warn'); return; } if (amountSummary.isZero) { - await addLog(`步骤 7:${phaseLabel}已确认今日应付金额为 ${amountSummary.rawAmount || '0'},继续执行。`, 'ok'); + await addLog(`Step 7: ${phaseLabel} confirmed today's due amount is ${amountSummary.rawAmount || '0'}; continuing.`, 'ok'); return; } const amountLabel = amountSummary.rawAmount || ( - Number.isFinite(Number(amountSummary.amount)) ? String(amountSummary.amount) : '未知金额' + Number.isFinite(Number(amountSummary.amount)) ? String(amountSummary.amount) : 'unknown amount' ); - await addLog(`步骤 7:${phaseLabel}检测到今日应付金额不是 0(${amountLabel}),说明当前账号没有免费试用资格,将跳过支付提交。`, 'warn'); + await addLog(`Step 7: ${phaseLabel} detected that today's due amount is not 0 (${amountLabel}), which means the current account is not eligible for a free trial; skipping payment submission.`, 'warn'); if (typeof markCurrentRegistrationAccountUsed === 'function') { await markCurrentRegistrationAccountUsed(state, { reason: 'plus-checkout-non-free-trial', - logPrefix: 'Plus Checkout:当前账号没有免费试用资格', + logPrefix: 'Plus Checkout: current account is not eligible for a free trial', }); } - throw new Error(`PLUS_CHECKOUT_NON_FREE_TRIAL::步骤 7:今日应付金额不是 0(${amountLabel}),当前账号没有免费试用资格,已跳过支付提交。`); + throw new Error(`PLUS_CHECKOUT_NON_FREE_TRIAL::Step 7: today's due amount is not 0 (${amountLabel}); the current account is not eligible for a free trial, so payment submission has been skipped.`); } async function getReadyCheckoutFrames(tabId) { @@ -1678,10 +1678,10 @@ } const currentCheckoutTabId = await getCurrentPlusCheckoutTabId(); if (currentCheckoutTabId) { - await addLog('步骤 7:检测到当前已在 Plus Checkout 页面,直接接管当前标签页。', 'info'); + await addLog('Step 7: Detected that we are already on the Plus Checkout page and are taking over the current tab.', 'info'); return currentCheckoutTabId; } - throw new Error('步骤 7:未找到 Plus Checkout 标签页。请先打开 Plus Checkout 页面,或完成步骤 6。'); + throw new Error('Step 7: Plus Checkout tab not found. Open the Plus Checkout page first, or complete Step 6.'); } async function executePlusCheckoutBilling(state = {}) { @@ -1692,36 +1692,36 @@ const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod); const paymentConfig = getPaymentMethodConfig(paymentMethod); const tabId = await getCheckoutTabId(state); - await addLog('步骤 7:正在等待 Plus Checkout 页面加载完成...', 'info'); + await addLog('Step 7: Waiting for the Plus Checkout page to finish loading...', 'info'); await waitForTabCompleteUntilStopped(tabId); await sleepWithStop(1000); await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, { inject: PLUS_CHECKOUT_INJECT_FILES, injectSource: PLUS_CHECKOUT_SOURCE, - logMessage: '步骤 7:Checkout 页面仍在加载,等待账单填写脚本就绪...', + logMessage: 'Step 7: Checkout page is still loading; waiting for the billing-fill script to be ready...', }); const readyFrames = await getReadyCheckoutFrames(tabId); await ensureFreeTrialAmount(tabId, state, { - phaseLabel: 'Checkout 页面加载后', + phaseLabel: 'After checkout page load', }); const paymentFrame = await resolvePaymentFrame(tabId, readyFrames, paymentMethod); if (paymentFrame.frameId === null) { const frameSummary = buildFrameSummary(paymentFrame.inspections); - throw new Error(`步骤 7:未在主页面或 iframe 中发现 ${paymentConfig.label} DOM,无法自动切换付款方式。frame 摘要:${frameSummary}`); + throw new Error(`Step 7: Could not find ${paymentConfig.label} DOM in the main page or any iframe; cannot switch payment method automatically. Frame summary: ${frameSummary}`); } if (!paymentFrame.ready) { - throw new Error(`步骤 7:已定位到 ${paymentConfig.label} 所在 iframe(frameId=${paymentFrame.frameId}),但账单脚本无法注入该 iframe。请提供该 iframe 的控制台结构或截图。`); + throw new Error(`Step 7: Located the iframe containing ${paymentConfig.label} (frameId=${paymentFrame.frameId}), but the billing script cannot be injected into that iframe. Please provide the iframe's console structure or a screenshot.`); } if (paymentFrame.frameId !== 0) { - await addLog(`步骤 7:${paymentConfig.label} 位于 checkout iframe(frameId=${paymentFrame.frameId}),将改为在该 frame 内操作。`, 'info'); + await addLog(`Step 7: ${paymentConfig.label} is in the checkout iframe (frameId=${paymentFrame.frameId}); switching to operate inside that frame.`, 'info'); } const randomName = generateRandomName(); const fullName = [randomName.firstName, randomName.lastName].filter(Boolean).join(' '); - await addLog(`步骤 7:正在切换 ${paymentConfig.label} 付款方式...`, 'info'); + await addLog(`Step 7: Switching the ${paymentConfig.label} payment method...`, 'info'); const paymentResult = await sendFrameMessage(tabId, paymentFrame.frameId, { type: paymentConfig.selectMessageType, source: 'background', @@ -1733,10 +1733,10 @@ const billingFrame = await waitForBillingFrame(tabId); if (!billingFrame.ready) { - throw new Error(`步骤 7:已定位到账单地址 iframe(frameId=${billingFrame.frameId}),但账单脚本无法注入该 iframe。请提供该 iframe 的控制台结构或截图。`); + throw new Error(`Step 7: Located the billing-address iframe (frameId=${billingFrame.frameId}), but the billing script cannot be injected into that iframe. Please provide the iframe's console structure or a screenshot.`); } if (billingFrame.frameId !== paymentFrame.frameId) { - await addLog(`步骤 7:账单地址位于 checkout iframe(frameId=${billingFrame.frameId}),将改为在该 frame 内填写。`, 'info'); + await addLog(`Step 7: The billing address is in the checkout iframe (frameId=${billingFrame.frameId}); switching to fill it inside that frame.`, 'info'); } let billingState = state; @@ -1747,7 +1747,7 @@ || '' ); try { - await addLog('步骤 7:GoPay 账单地址准备按代理出口填写,正在重新检测当前出口地区...', 'info'); + await addLog('Step 7: Preparing to fill the GoPay billing address based on the proxy exit; rechecking the current exit region...', 'info'); const probeResult = await probeIpProxyExit({ state, timeoutMs: 12000, @@ -1769,9 +1769,9 @@ ipProxyAppliedExitIp: probedExitIp, ipProxyAppliedExitSource: probedExitSource, }; - const sourceSuffix = probedExitSource ? `,来源 ${probedExitSource}` : ''; - const endpointSuffix = probeEndpoint ? `,检测地址 ${probeEndpoint}` : ''; - await addLog(`步骤 7:当前代理出口复测结果:${probedExitRegion}${probedExitIp ? ` / ${probedExitIp}` : ''}${sourceSuffix}${endpointSuffix}。`, 'info'); + const sourceSuffix = probedExitSource ? `, source ${probedExitSource}` : ''; + const endpointSuffix = probeEndpoint ? `, endpoint ${probeEndpoint}` : ''; + await addLog(`Step 7: Current proxy exit recheck result: ${probedExitRegion}${probedExitIp ? ` / ${probedExitIp}` : ''}${sourceSuffix}${endpointSuffix}.`, 'info'); } else { billingState = { ...(state || {}), @@ -1781,7 +1781,7 @@ ipProxyAppliedExitSource: probedExitSource, }; await addLog( - `步骤 7:代理出口复测没有返回国家/地区代码,已清空旧出口地区${staleExitRegion ? ` ${staleExitRegion}` : ''},不会继续沿用旧地区。${probeReason ? `状态:${probeReason}。` : ''}${probeError ? `诊断:${probeError}` : ''}`, + `Step 7: The proxy exit recheck did not return a country/region code; the old exit region${staleExitRegion ? ` ${staleExitRegion}` : ''} has been cleared and will not be reused.${probeReason ? ` Status: ${probeReason}.` : ''}${probeError ? ` Diagnosis: ${probeError}` : ''}`, 'warn' ); } @@ -1791,27 +1791,27 @@ ipProxyAppliedExitRegion: '', ipProxyExitRegion: '', }; - await addLog(`步骤 7:代理出口复测失败,已清空旧出口地区${staleExitRegion ? ` ${staleExitRegion}` : ''},不会继续沿用旧地区:${error?.message || String(error || '未知错误')}`, 'warn'); + await addLog(`Step 7: Proxy exit recheck failed; the old exit region${staleExitRegion ? ` ${staleExitRegion}` : ''} has been cleared and will not be reused: ${error?.message || String(error || 'unknown error')}`, 'warn'); } } if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY && typeof probeIpProxyExit === 'function' && !resolveMeiguodizhiCountryCode(billingState?.ipProxyAppliedExitRegion || billingState?.ipProxyExitRegion || '')) { - throw new Error('步骤 7:GoPay 账单地址需要当前代理出口国家/地区,但本次复测没有拿到国家码;已停止填写,避免误用旧的 KR/ID 地区。请先点 IP 代理“检测出口”,确认显示 JP 后再继续。'); + throw new Error('Step 7: The GoPay billing address requires the current proxy exit country/region, but this recheck did not return a country code. Filling has been stopped to avoid reusing the old KR/ID region. Please click IP Proxy "Detect Exit", confirm that JP is shown, and then continue.'); } const addressSeed = await resolveBillingAddressSeed(billingState, billingFrame.countryText, { paymentMethod }); if (!addressSeed) { - throw new Error('步骤 7:未找到可用的本地账单地址种子。'); + throw new Error('Step 7: No usable local billing address seed found.'); } - await addLog(`步骤 7:正在填写账单地址(${addressSeed.countryCode} / ${addressSeed.query})...`, 'info'); + await addLog(`Step 7: Filling the billing address (${addressSeed.countryCode} / ${addressSeed.query})...`, 'info'); const autocompleteFrame = await resolveOptionalFrameByUrl(tabId, isAutocompleteFrameUrl); let result = null; if (!addressSeed.skipAutocomplete && autocompleteFrame?.frame && autocompleteFrame.frame.frameId !== billingFrame.frameId) { if (!autocompleteFrame.ready) { - throw new Error('步骤 7:发现 Google 地址推荐 iframe,但无法注入账单脚本。请提供该 iframe 的控制台结构。'); + throw new Error('Step 7: Found the Google address suggestions iframe, but cannot inject the billing script. Please provide the iframe console structure.'); } - await addLog(`步骤 7:Google 地址推荐位于独立 iframe(frameId=${autocompleteFrame.frame.frameId}),将拆分输入与选择动作。`, 'info'); + await addLog(`Step 7: Google address suggestions are in a separate iframe (frameId=${autocompleteFrame.frame.frameId}); splitting input and selection actions.`, 'info'); const queryResult = await sendFrameMessage(tabId, billingFrame.frameId, { type: 'PLUS_CHECKOUT_FILL_ADDRESS_QUERY', @@ -1834,7 +1834,7 @@ }); const suggestionError = suggestionResult?.error || ''; if (suggestionError) { - await addLog(`步骤 7:Google 地址推荐不可用,将改用本地地址字段兜底:${suggestionError}`, 'warn'); + await addLog(`Step 7: Google address suggestions are unavailable; falling back to local address fields: ${suggestionError}`, 'warn'); } const structuredResult = await sendFrameMessage(tabId, billingFrame.frameId, { @@ -1874,7 +1874,7 @@ plusBillingAddress: result?.structuredAddress || null, }); await ensureFreeTrialAmount(tabId, state, { - phaseLabel: '提交订阅前', + phaseLabel: 'Before subscription submission', }); let redirectedToPayment = false; @@ -1882,12 +1882,12 @@ for (let attempt = 1; attempt <= PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS; attempt += 1) { await addLog( attempt === 1 - ? '步骤 7:账单地址已填写完成,等待 3 秒让 checkout 完成校验...' - : `步骤 7:准备第 ${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS} 次重新检测订阅按钮...`, + ? 'Step 7: Billing address has been filled. Waiting 3 seconds for checkout validation...' + : `Step 7: Preparing to re-check the Subscribe button, attempt ${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS}...`, attempt === 1 ? 'info' : 'warn' ); await sleepWithStop(3000); - await addLog('步骤 7:正在定位订阅按钮...', 'info'); + await addLog('Step 7: Locating the Subscribe button...', 'info'); const subscribeFrame = await waitForSubscribeFrame(tabId, [ { frameId: 0, url: '' }, { frameId: paymentFrame.frameId, url: paymentFrame.frameUrl || '' }, @@ -1903,7 +1903,7 @@ }); if (subscribeResult?.error) { lastSubmitError = subscribeResult.error; - await addLog(`步骤 7:点击订阅失败(${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS}):${lastSubmitError}`, 'warn'); + await addLog(`Step 7: Subscribe click failed (${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS}): ${lastSubmitError}`, 'warn'); continue; } @@ -1911,23 +1911,23 @@ const subscribeButtonText = String(subscribeResult?.subscribeButtonText || '').trim(); const subscribeButtonStatus = String(subscribeResult?.subscribeButtonStatus || '').trim(); if (subscribeClicked) { - await addLog(`步骤 7:已点击订阅按钮,正在等待跳转到 ${paymentConfig.label}(${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS})...`, 'info'); + await addLog(`Step 7: Clicked the Subscribe button; waiting to redirect to ${paymentConfig.label} (${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS})...`, 'info'); } else { const buttonStateLabel = subscribeButtonText || subscribeButtonStatus || 'unknown'; - await addLog(`步骤 7:订阅按钮当前为「${buttonStateLabel}」,本轮未点击,正在等待页面是否跳转到 ${paymentConfig.label}(${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS})...`, 'warn'); + await addLog(`Step 7: The Subscribe button is currently "${buttonStateLabel}"; no click this round, waiting to see whether the page redirects to ${paymentConfig.label} (${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS})...`, 'warn'); } redirectedToPayment = await waitForPaymentRedirectAfterSubmit(tabId, paymentMethod); if (redirectedToPayment) { break; } lastSubmitError = subscribeClicked - ? `点击订阅后 ${Math.round(PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS / 1000)} 秒内未跳转到 ${paymentConfig.label}` - : `订阅按钮当前为「${subscribeButtonText || subscribeButtonStatus || 'unknown'}」,${Math.round(PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS / 1000)} 秒内未跳转到 ${paymentConfig.label}`; - await addLog(`步骤 7:${lastSubmitError},将重新检测订阅按钮。`, 'warn'); + ? `Did not redirect to ${paymentConfig.label} within ${Math.round(PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS / 1000)} seconds after clicking Subscribe` + : `The Subscribe button is currently "${subscribeButtonText || subscribeButtonStatus || 'unknown'}"; did not redirect to ${paymentConfig.label} within ${Math.round(PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS / 1000)} seconds`; + await addLog(`Step 7: ${lastSubmitError}; rechecking the Subscribe button.`, 'warn'); } if (!redirectedToPayment) { - throw new Error(`步骤 7:多次检测订阅按钮后仍未跳转到 ${paymentConfig.label}。${lastSubmitError}`); + throw new Error(`Step 7: After multiple checks, the page still did not redirect to ${paymentConfig.label}. ${lastSubmitError}`); } await completeNodeFromBackground('plus-checkout-billing', { diff --git a/flows/openai/background/steps/fill-profile.js b/flows/openai/background/steps/fill-profile.js index 9bc18886..c91e4cf7 100644 --- a/flows/openai/background/steps/fill-profile.js +++ b/flows/openai/background/steps/fill-profile.js @@ -13,7 +13,7 @@ const { firstName, lastName } = generateRandomName(); const { year, month, day } = generateRandomBirthday(); - await addLog(`步骤 5:已生成姓名 ${firstName} ${lastName},生日 ${year}-${month}-${day}`); + await addLog(`Step 5: Generated name ${firstName} ${lastName}, birthday ${year}-${month}-${day}`); await sendToContentScript('openai-auth', { type: 'EXECUTE_NODE', diff --git a/flows/openai/background/steps/gopay-approve.js b/flows/openai/background/steps/gopay-approve.js index d2d56a38..121045f5 100644 --- a/flows/openai/background/steps/gopay-approve.js +++ b/flows/openai/background/steps/gopay-approve.js @@ -38,9 +38,9 @@ function formatGoPayTerminalError(pageState = {}) { const terminalError = pageState?.terminalError || {}; - const terminalMessage = normalizeText(terminalError.message || 'GoPay 页面显示支付失败或会话已失效。'); + const terminalMessage = normalizeText(terminalError.message || 'The GoPay page shows a payment failure or the session has expired.'); const rawText = normalizeText(terminalError.rawText || pageState?.textPreview || ''); - const rawSuffix = rawText ? ` 页面提示:${rawText.slice(0, 180)}` : ''; + const rawSuffix = rawText ? ` Page hint: ${rawText.slice(0, 180)}` : ''; return `${terminalMessage}${rawSuffix}`; } @@ -76,8 +76,8 @@ } async function restartGoPayCheckoutFromStep6(tabId, reason = '') { - const message = normalizeText(reason || 'GoPay 支付页已失效或点击后没有进入下一步。'); - await addLog(`步骤 8:${message} 正在关闭当前 GoPay/Checkout 页面,并回到步骤 6 重新创建 Plus Checkout。`, 'warn'); + const message = normalizeText(reason || 'The GoPay payment page has expired or clicking did not advance to the next step.'); + await addLog(`Step 8: ${message} Closing the current GoPay/Checkout page and returning to Step 6 to recreate Plus Checkout.`, 'warn'); if (Number.isInteger(tabId) && chrome?.tabs?.remove) { await chrome.tabs.remove(tabId).catch(() => {}); } @@ -88,7 +88,7 @@ plusPaypalApprovedAt: null, plusGoPayApprovedAt: null, }); - throw new Error(`GOPAY_RESTART_FROM_STEP6::步骤 8:${message} 已关闭当前支付页,请从步骤 6 重新创建 Plus Checkout。`); + throw new Error(`GOPAY_RESTART_FROM_STEP6::Step 8: ${message} The current payment page has been closed; please recreate Plus Checkout from Step 6.`); } async function handleGoPayTerminalError(pageState = {}, tabId = null) { @@ -136,7 +136,7 @@ } const discoveredGoPayTabId = await findOpenTabId(isGoPayUrl); if (discoveredGoPayTabId) { - await addLog('步骤 8:已从当前浏览器标签中发现 GoPay 页面,正在接管继续执行。', 'info'); + await addLog('Step 8: Found a GoPay page in the current browser tab and taking over execution.', 'info'); if (typeof registerTab === 'function') { await registerTab(GOPAY_SOURCE, discoveredGoPayTabId); } @@ -156,7 +156,7 @@ } return storedTabId; } - throw new Error('步骤 8:未找到 GoPay 标签页,请先完成步骤 7。'); + throw new Error('Step 8: GoPay tab not found. Please complete Step 7 first.'); } async function ensureGoPayReady(tabId, logMessage = '') { @@ -165,7 +165,7 @@ await ensureContentScriptReadyOnTabUntilStopped(GOPAY_SOURCE, tabId, { inject: GOPAY_INJECT_FILES, injectSource: GOPAY_SOURCE, - logMessage: logMessage || '步骤 8:GoPay 页面仍在加载,等待脚本就绪...', + logMessage: logMessage || 'Step 8: GoPay page is still loading; waiting for the script to be ready...', }); } @@ -621,7 +621,7 @@ await chrome.debugger.attach(target, '1.3'); attached = true; } catch (err) { - throw new Error(`GoPay iframe 调试器附加失败:${err?.message || String(err || 'unknown_error')}`); + throw new Error(`Failed to attach the GoPay iframe debugger: ${err?.message || String(err || 'unknown_error')}`); } try { return await callback(target); @@ -640,7 +640,7 @@ awaitPromise: true, }); if (result?.exceptionDetails) { - throw new Error(result.exceptionDetails?.text || 'GoPay iframe 脚本执行失败'); + throw new Error(result.exceptionDetails?.text || 'GoPay iframe script execution failed'); } return result?.result?.value || {}; }); @@ -657,7 +657,7 @@ async function submitGoPayOtpWithDebuggerTarget(targetId, code = '') { const normalizedCode = normalizeGoPayOtp(code); if (!normalizedCode) { - throw new Error('GoPay WhatsApp 验证码为空。'); + throw new Error('GoPay WhatsApp verification code is empty.'); } return withDebuggerTarget(targetId, async (debuggee) => { const focused = await chrome.debugger.sendCommand(debuggee, 'Runtime.evaluate', { @@ -667,7 +667,7 @@ }); const focusValue = focused?.result?.value || {}; if (!focusValue.focused) { - throw new Error(focusValue.reason || 'GoPay 验证码输入框未找到'); + throw new Error(focusValue.reason || 'GoPay verification code input not found'); } await sleepWithStop(200); await typeDigitsWithDebugger(debuggee, normalizedCode, 160); @@ -691,7 +691,7 @@ async function submitGoPayPinWithDebuggerTarget(targetId, pin = '') { const normalizedPin = normalizeGoPayOtp(pin); if (!normalizedPin) { - throw new Error('GoPay PIN 为空,请先在侧边栏配置。'); + throw new Error('GoPay PIN is empty; please configure it in the side panel first.'); } return withDebuggerTarget(targetId, async (debuggee) => { const focused = await chrome.debugger.sendCommand(debuggee, 'Runtime.evaluate', { @@ -701,7 +701,7 @@ }); const focusValue = focused?.result?.value || {}; if (!focusValue.focused) { - throw new Error(focusValue.reason || 'GoPay PIN 输入框未找到'); + throw new Error(focusValue.reason || 'GoPay PIN input not found'); } await sleepWithStop(250); await typeDigitsWithDebugger(debuggee, normalizedPin, 220); @@ -772,7 +772,7 @@ if (type === 'GOPAY_SUBMIT_PIN') { return submitGoPayPinWithDebuggerTarget(targetId, payload.pin || payload.gopayPin || ''); } - throw new Error(`GoPay iframe 调试器暂不支持命令:${type}`); + throw new Error(`The GoPay iframe debugger does not support the command: ${type}`); } async function findGoPayActionFrame(tabId) { @@ -1021,13 +1021,13 @@ while (Date.now() - startedAt < timeoutMs) { const tab = await chrome.tabs.get(tabId).catch(() => null); if (!tab) { - throw new Error('步骤 8:GoPay 标签页已关闭,无法继续。'); + throw new Error('Step 8: The GoPay tab has been closed; cannot continue.'); } const url = String(tab.url || ''); if (isReturnUrl(url)) { return { returned: true, url }; } - await ensureGoPayReady(tabId, '步骤 8:GoPay 页面正在切换,等待脚本重新就绪...'); + await ensureGoPayReady(tabId, 'Step 8: GoPay page is switching; waiting for the script to become ready again...'); const actionFrame = await findGoPayActionFrame(tabId); const actionFrameId = actionFrame.frameId; const actionTargetId = actionFrame.targetId; @@ -1100,18 +1100,18 @@ async function requestManualGoPayOtp(existingCode = '') { if (typeof requestGoPayOtpInput !== 'function') { - throw new Error('步骤 8:未配置 GoPay 验证码,也无法打开侧边栏输入弹窗。'); + throw new Error('Step 8: No GoPay verification code is configured, and the side panel input dialog cannot be opened.'); } await addLog(existingCode - ? '步骤 8:检测到上次保存的 GoPay 验证码,将弹窗请你确认或改填新验证码。' - : '步骤 8:请在侧边栏弹窗中输入 GoPay 验证码,提交后会继续填写 PIN。', 'info'); + ? 'Step 8: Detected a previously saved GoPay verification code; the dialog will ask you to confirm it or enter a new one.' + : 'Step 8: Enter the GoPay verification code in the side panel dialog; after submission, PIN entry will continue.', 'info'); const response = await requestGoPayOtpInput({ code: existingCode }); if (response?.error) { throw new Error(response.error); } const code = normalizeGoPayOtp(response?.code || ''); if (!code || response?.cancelled) { - throw new Error('步骤 8:GoPay 验证码输入已取消。'); + throw new Error('Step 8: GoPay verification code entry was canceled.'); } return code; } @@ -1119,10 +1119,10 @@ async function executeGoPayApprove(state = {}) { const credentials = resolveGoPayCredentials(state); if (!credentials.phone) { - throw new Error('步骤 8:未配置 GoPay 手机号,请先在侧边栏填写。'); + throw new Error('Step 8: No GoPay phone number is configured; fill it in from the side panel first.'); } if (!credentials.pin) { - throw new Error('步骤 8:未配置 GoPay PIN,请先在侧边栏填写。'); + throw new Error('Step 8: No GoPay PIN is configured; fill it in from the side panel first.'); } const tabId = await resolveGoPayTabId(state); @@ -1154,11 +1154,11 @@ const tab = await chrome.tabs.get(tabId).catch(() => null); const currentUrl = String(tab?.url || ''); if (currentUrl && isReturnUrl(currentUrl)) { - await addLog('步骤 8:GoPay 已跳转回 ChatGPT / OpenAI 页面,准备进入回跳确认。', 'ok'); + await addLog('Step 8: GoPay has returned to the ChatGPT / OpenAI page; preparing for return-check confirmation.', 'ok'); break; } - await ensureGoPayReady(tabId, '步骤 8:GoPay 页面正在切换,等待脚本重新就绪...'); + await ensureGoPayReady(tabId, 'Step 8: GoPay page is switching; waiting for the script to become ready again...'); const actionFrame = await findGoPayActionFrame(tabId); const actionFrameId = actionFrame.frameId; const actionTargetId = actionFrame.targetId; @@ -1174,7 +1174,7 @@ await handleGoPayTerminalError(pageState, tabId); if (pageState.completed) { - await addLog('步骤 8:GoPay 页面已显示完成状态,准备进入回跳确认。', 'ok'); + await addLog('Step 8: GoPay page shows the completed state; preparing for return-check confirmation.', 'ok'); break; } @@ -1189,26 +1189,26 @@ if (!Number.isInteger(actionFrameId) && payNowClickAttempts > 2) { const framePayResult = await clickGoPayPayButtonInAnyFrame(tabId); if (framePayResult?.clicked) { - await addLog(`步骤 8:顶层仍显示 Pay now,但已在 GoPay iframe 中点击 Bayar 按钮:${framePayResult.clickTarget || 'Bayar'}。`, 'info'); + await addLog(`Step 8: The top level still shows Pay now, but the Bayar button has already been clicked in the GoPay iframe: ${framePayResult.clickTarget || 'Bayar'}.`, 'info'); await sleepWithStop(2500); resetTransientStepFlags(); loggedWaiting = false; continue; } - await addLog('步骤 8:顶层 Pay now 已重复出现,暂未识别到 iframe 内 Bayar/PIN;继续等待页面切换,不再自动回退步骤 6。', 'warn'); + await addLog('Step 8: Pay now has reappeared at the top level; Bayar/PIN was not yet detected inside the iframe. Continuing to wait for the page transition and will no longer auto-fall back to Step 6.', 'warn'); await sleepWithStop(3000); loggedWaiting = false; continue; } - const paymentLabel = actionFrame.kind === 'payment' ? '最终 Bayar 确认' : 'Pay now'; - await addLog(`步骤 8:检测到 GoPay ${paymentLabel} 按钮,正在点击完成支付...`, 'info'); + const paymentLabel = actionFrame.kind === 'payment' ? 'final Bayar confirmation' : 'Pay now'; + await addLog(`Step 8: Detected the GoPay ${paymentLabel} button; clicking to complete payment...`, 'info'); const payResult = actionTargetId ? await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_CLICK_PAY_NOW', {}) : (Number.isInteger(actionFrameId) ? await sendGoPayFrameCommand(tabId, actionFrameId, 'GOPAY_CLICK_PAY_NOW', {}) : await sendGoPayCommand(tabId, 'GOPAY_CLICK_PAY_NOW', {})); if (payResult?.clickTarget) { - await addLog(`步骤 8:已点击 GoPay 支付按钮:${payResult.clickTarget}`, 'info'); + await addLog(`Step 8: Clicked the GoPay payment button: ${payResult.clickTarget}`, 'info'); } await sleepWithStop(2500); const decision = await waitForGoPayState(tabId, (nextState) => ( @@ -1218,7 +1218,7 @@ ), { timeoutMs: 15000 }); await handleGoPayTerminalError(decision.pageState, tabId); if (decision.returned) { - await addLog('步骤 8:GoPay 支付后已跳转回 ChatGPT / OpenAI 页面,准备进入回跳确认。', 'ok'); + await addLog('Step 8: After GoPay payment, the page has returned to ChatGPT / OpenAI; preparing for return-check confirmation.', 'ok'); break; } if (decision.pageState) { @@ -1228,7 +1228,7 @@ if (!Number.isInteger(actionFrameId)) { const framePayResult = await clickGoPayPayButtonInAnyFrame(tabId); if (framePayResult?.clicked) { - await addLog(`步骤 8:已在 GoPay iframe 中点击 Bayar 按钮:${framePayResult.clickTarget || 'Bayar'}。`, 'info'); + await addLog(`Step 8: Clicked the Bayar button in the GoPay iframe: ${framePayResult.clickTarget || 'Bayar'}.`, 'info'); await sleepWithStop(2500); resetTransientStepFlags(); loggedWaiting = false; @@ -1237,19 +1237,19 @@ } const debuggerResult = await clickGoPayPayNowWithDebugger(tabId, actionFrameId); if (debuggerResult?.clickTarget) { - await addLog(`步骤 8:已使用 debugger 点击 GoPay 支付按钮:${debuggerResult.clickTarget}`, 'info'); + await addLog(`Step 8: Clicked the GoPay payment button using the debugger: ${debuggerResult.clickTarget}`, 'info'); } await sleepWithStop(2500); if (!Number.isInteger(actionFrameId)) { const lateFramePayResult = await clickGoPayPayButtonInAnyFrame(tabId); if (lateFramePayResult?.clicked) { - await addLog(`步骤 8:顶层 Pay now 点击后,已在 GoPay iframe 中补点 Bayar 按钮:${lateFramePayResult.clickTarget || 'Bayar'}。`, 'info'); + await addLog(`Step 8: After clicking top-level Pay now, also clicked the Bayar button in the GoPay iframe: ${lateFramePayResult.clickTarget || 'Bayar'}.`, 'info'); await sleepWithStop(2500); resetTransientStepFlags(); loggedWaiting = false; continue; } - await addLog('步骤 8:顶层 Pay now 兜底点击后仍未识别到 iframe 内 Bayar/PIN,继续等待,不自动回退步骤 6。', 'warn'); + await addLog('Step 8: After the fallback top-level Pay now click, Bayar/PIN was still not detected in the iframe; continuing to wait and will not auto-fall back to Step 6.', 'warn'); } } resetTransientStepFlags(); @@ -1258,7 +1258,7 @@ } if (pageState.hasPhoneInput && !phoneSubmitted) { - await addLog(`步骤 8:正在切换 GoPay 区号 ${credentials.countryCode} 并填写手机号...`, 'info'); + await addLog(`Step 8: Switching the GoPay country code to ${credentials.countryCode} and filling the phone number...`, 'info'); await sendGoPayCommand(tabId, 'GOPAY_SUBMIT_PHONE', { countryCode: credentials.countryCode, phone: credentials.phone, @@ -1272,7 +1272,7 @@ } if (pageState.hasPinInput && !pinSubmitted) { - await addLog('步骤 8:正在填写 GoPay PIN...', 'info'); + await addLog('Step 8: Filling the GoPay PIN...', 'info'); if (actionTargetId) { await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_SUBMIT_PIN', { pin: credentials.pin }); } else if (Number.isInteger(actionFrameId)) { @@ -1292,11 +1292,11 @@ ), { timeoutMs: 20000 }); await handleGoPayTerminalError(afterPinDecision.pageState, tabId); if (afterPinDecision.returned) { - await addLog('步骤 8:GoPay PIN 后已跳转回 ChatGPT / OpenAI 页面,准备进入回跳确认。', 'ok'); + await addLog('Step 8: After GoPay PIN entry, the page has returned to ChatGPT / OpenAI; preparing for return-check confirmation.', 'ok'); break; } if (afterPinDecision.pageState?.hasPayNowButton) { - await addLog('步骤 8:GoPay PIN 已通过,等待点击 Pay now 完成支付。', 'info'); + await addLog('Step 8: GoPay PIN was accepted; waiting to click Pay now to complete payment.', 'info'); resetTransientStepFlags(); } await sleepWithStop(800); @@ -1306,14 +1306,14 @@ if (pageState.hasOtpInput && !pageState.hasPinInput && !otpSubmitted) { const code = await requestManualGoPayOtp(credentials.otp); credentials.otp = code; - await addLog('步骤 8:正在填写 GoPay 验证码...', 'info'); + await addLog('Step 8: Filling the GoPay verification code...', 'info'); if (actionTargetId) { await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_SUBMIT_OTP', { code }); } else if (Number.isInteger(actionFrameId)) { await ensureGoPayOtpFrameReady(tabId, actionFrameId); await sendGoPayFrameCommand(tabId, actionFrameId, 'GOPAY_SUBMIT_OTP', { code }); } else { - await ensureGoPayReady(tabId, '步骤 8:已获取 GoPay 验证码,等待 GoPay 页面就绪...'); + await ensureGoPayReady(tabId, 'Step 8: Got the GoPay verification code; waiting for the GoPay page to be ready...'); await sendGoPayCommand(tabId, 'GOPAY_SUBMIT_OTP', { code }); } otpSubmitted = true; @@ -1334,7 +1334,7 @@ } if (continueClickAttempts > 2) { const stableBeforeRetrySeconds = Math.round(stableState.stableMs / 1000); - await addLog(`步骤 8:GoPay 确认按钮点击后页面仍未变化,先等待 linking 页面加载/跳转(已稳定 ${stableBeforeRetrySeconds}s)。`, 'warn'); + await addLog(`Step 8: After clicking the GoPay confirm button, the page still has not changed; waiting for the linking page to load/navigate first (stable for ${stableBeforeRetrySeconds}s).`, 'warn'); const decision = await waitForGoPayState(tabId, (nextState) => ( nextState.hasTerminalError || nextState.hasOtpInput @@ -1345,7 +1345,7 @@ ), { timeoutMs: GOPAY_LINKING_RETRY_WAIT_MS }); await handleGoPayTerminalError(decision.pageState, tabId); if (decision.returned) { - await addLog('步骤 8:GoPay 已跳转回 ChatGPT / OpenAI 页面,准备进入回跳确认。', 'ok'); + await addLog('Step 8: GoPay has returned to the ChatGPT / OpenAI page; preparing for return-check confirmation.', 'ok'); break; } if (!decision.timeout) { @@ -1359,17 +1359,17 @@ const refreshedStableState = stableStateTracker.update(refreshedState, currentUrl); const stableSeconds = Math.round(refreshedStableState.stableMs / 1000); if (stableSeconds < Math.round(GOPAY_LINKING_STABLE_WAIT_MS / 1000)) { - await addLog(`步骤 8:GoPay linking 页面还在同一状态(${stableSeconds}s),改用兜底点击 Hubungkan/确认按钮后继续等待。`, 'info'); + await addLog(`Step 8: The GoPay linking page is still in the same state (${stableSeconds}s); using a fallback click on the Hubungkan/confirm button and then continuing to wait.`, 'info'); const retryResult = await clickGoPayContinueBestEffort(tabId); if (retryResult?.clickTarget) { - await addLog(`步骤 8:已兜底点击 GoPay 控件:${retryResult.clickTarget}`, 'info'); + await addLog(`Step 8: Fallback-clicked the GoPay control: ${retryResult.clickTarget}`, 'info'); } continueClickAttempts = 2; await sleepWithStop(2500); loggedWaiting = false; continue; } - await addLog('步骤 8:GoPay linking 页面长时间没有变化,已暂停自动重复点击。请手动点击页面上的 Hubungkan/确认按钮,插件会继续等待后续页面。', 'warn'); + await addLog('Step 8: The GoPay linking page has not changed for a long time, so automatic repeated clicking has been paused. Please click the Hubungkan/confirm button on the page manually; the extension will keep waiting for the next page.', 'warn'); const manualDecision = await waitForGoPayState(tabId, (nextState) => ( nextState.hasTerminalError || nextState.hasOtpInput @@ -1380,7 +1380,7 @@ ), { timeoutMs: GOPAY_WAIT_TIMEOUT_MS }); await handleGoPayTerminalError(manualDecision.pageState, tabId); if (manualDecision.returned) { - await addLog('步骤 8:GoPay 已跳转回 ChatGPT / OpenAI 页面,准备进入回跳确认。', 'ok'); + await addLog('Step 8: GoPay has returned to the ChatGPT / OpenAI page; preparing for return-check confirmation.', 'ok'); break; } if (!manualDecision.timeout) { @@ -1390,9 +1390,9 @@ loggedWaiting = false; continue; } - throw new Error('步骤 8:GoPay linking 页面长时间无变化,请手动点击 Hubungkan/确认按钮后重新执行或继续当前步骤。'); + throw new Error('Step 8: The GoPay linking page has not changed for a long time. Please click the Hubungkan/confirm button manually, then rerun or continue this step.'); } - await addLog(`步骤 8:检测到 GoPay 继续/确认按钮,正在点击${continueClickAttempts > 1 ? `(第 ${continueClickAttempts} 次)` : ''}...`, 'info'); + await addLog(`Step 8: Detected a GoPay Continue/Confirm button; clicking${continueClickAttempts > 1 ? ` (attempt ${continueClickAttempts})` : ''}...`, 'info'); const clickResult = continueClickAttempts === 1 ? (actionTargetId ? await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_CLICK_CONTINUE', {}) @@ -1401,7 +1401,7 @@ : await sendGoPayCommand(tabId, 'GOPAY_CLICK_CONTINUE', {}))) : await clickGoPayContinueWithDebugger(tabId, actionFrameId); if (clickResult?.clickTarget) { - await addLog(`步骤 8:已点击 GoPay 控件:${clickResult.clickTarget}${continueClickAttempts > 1 ? '(debugger 真实鼠标事件)' : ''}`, 'info'); + await addLog(`Step 8: Clicked the GoPay control: ${clickResult.clickTarget}${continueClickAttempts > 1 ? ' (real debugger mouse event)' : ''}`, 'info'); } await sleepWithStop(2000); loggedWaiting = false; @@ -1410,7 +1410,7 @@ if (!loggedWaiting) { loggedWaiting = true; - await addLog('步骤 8:等待 GoPay 手机号、验证码、PIN 或完成页面出现...', 'info'); + await addLog('Step 8: Waiting for the GoPay phone number, verification code, PIN, or completion page to appear...', 'info'); } const decision = await waitForGoPayState(tabId, (nextState) => ( @@ -1424,7 +1424,7 @@ ), { timeoutMs: 10000 }); await handleGoPayTerminalError(decision.pageState, tabId); if (decision.returned) { - await addLog('步骤 8:GoPay 已跳转回 ChatGPT / OpenAI 页面,准备进入回跳确认。', 'ok'); + await addLog('Step 8: GoPay has returned to the ChatGPT / OpenAI page; preparing for return-check confirmation.', 'ok'); break; } if (decision.timeout) { diff --git a/flows/openai/background/steps/gopay-manual-confirm.js b/flows/openai/background/steps/gopay-manual-confirm.js index c7d6f7d4..5d44e3e7 100644 --- a/flows/openai/background/steps/gopay-manual-confirm.js +++ b/flows/openai/background/steps/gopay-manual-confirm.js @@ -5,11 +5,11 @@ const GOPAY_CONFIRM_NODE_ID = 'gopay-subscription-confirm'; const SUB2API_SESSION_IMPORT_NODE_ID = 'sub2api-session-import'; const CPA_SESSION_IMPORT_NODE_ID = 'cpa-session-import'; - const DEFAULT_CONFIRM_TITLE = 'GoPay 订阅确认'; - const OAUTH_CONTINUATION_LABEL = 'OAuth 登录'; - const SUB2API_SESSION_CONTINUATION_LABEL = '导入当前 ChatGPT 会话到 SUB2API'; + const DEFAULT_CONFIRM_TITLE = 'GoPay subscription confirmation'; + const OAUTH_CONTINUATION_LABEL = 'OAuth login'; + const SUB2API_SESSION_CONTINUATION_LABEL = 'Import current ChatGPT session to SUB2API'; - const CPA_SESSION_CONTINUATION_LABEL = '导入当前 ChatGPT 会话到 CPA'; + const CPA_SESSION_CONTINUATION_LABEL = 'Import current ChatGPT session to CPA'; function normalizeString(value = '') { return String(value || '').trim(); @@ -55,7 +55,7 @@ function buildDefaultConfirmMessage(state = {}, options = {}) { const continuationActionLabel = getContinuationActionLabel(state, options); - return `GoPay 订阅页已打开。请先手动完成订阅,完成后确认继续${continuationActionLabel}。`; + return `The GoPay subscription page is open. Please complete the subscription manually first, then confirm to continue ${continuationActionLabel}。`; } function createGoPayManualConfirmExecutor(deps = {}) { @@ -96,11 +96,11 @@ const checkoutUrl = normalizeString(state?.plusCheckoutUrl); if (!checkoutUrl) { - throw new Error('步骤 7:未检测到 GoPay 订阅页,请先执行步骤 6。'); + throw new Error('Step 7: GoPay subscription page not detected. Run Step 6 first.'); } if (!chrome?.tabs?.create) { - throw new Error('步骤 7:无法自动重新打开 GoPay 订阅页。'); + throw new Error('Step 7: Could not automatically reopen the GoPay subscription page.'); } const tab = typeof createAutomationTab === 'function' @@ -108,7 +108,7 @@ : await chrome.tabs.create({ url: checkoutUrl, active: true }); const tabId = Number(tab?.id) || 0; if (!tabId) { - throw new Error('步骤 7:重新打开 GoPay 订阅页失败。'); + throw new Error('Step 7: Failed to reopen the GoPay subscription page.'); } if (typeof registerTab === 'function') { await registerTab(PLUS_CHECKOUT_SOURCE, tabId); @@ -140,7 +140,7 @@ } await addLog( - `步骤 ${visibleStep}:正在等待手动完成 GoPay 订阅,确认后继续${continuationActionLabel}。`, + `Step ${visibleStep}: Waiting for manual GoPay subscription completion. After confirmation, continue ${continuationActionLabel}。`, 'info' ); } diff --git a/flows/openai/background/steps/oauth-login.js b/flows/openai/background/steps/oauth-login.js index b627b9d5..493e511d 100644 --- a/flows/openai/background/steps/oauth-login.js +++ b/flows/openai/background/steps/oauth-login.js @@ -33,12 +33,12 @@ return false; } - const mentionsSecret = /管理密钥|Admin Secret|X-Admin-Key|CPA Key/i.test(message); + const mentionsSecret = /admin key|Admin Secret|X-Admin-Key|CPA Key/i.test(message); if (!mentionsSecret) { return false; } - return /缺少|未配置|请输入|无效|错误|失败|401|认证失败|未授权|unauthorized|invalid/i.test(message); + return /missing|not configured|please enter|invalid|error|failed|401|authentication failed|unauthorized/i.test(message); } function normalizeStep7IdentifierType(value = '') { @@ -188,7 +188,7 @@ if (phoneSignupMode) { if (isStep7AddPhoneResult(result)) { - throw new Error(`步骤 ${completionStepForState(currentState)}:手机号注册模式 OAuth 登录不应进入添加手机号页。URL: ${result?.url || ''}`.trim()); + throw new Error(`Step ${completionStepForState(currentState)}: Phone-signup OAuth login should not enter the add-phone page. URL: ${result?.url || ''}`.trim()); } if (isStep7AddEmailResult(result)) { payload.skipLoginVerificationStep = true; @@ -199,13 +199,13 @@ return payload; } if (isStep7PlainVerificationResult(result)) { - throw new Error(`步骤 ${completionStepForState(currentState)}:手机号注册模式 OAuth 登录进入了普通邮箱登录验证码页,当前流程不会回落到邮箱验证码。URL: ${result?.url || ''}`.trim()); + throw new Error(`Step ${completionStepForState(currentState)}: Phone-signup OAuth login entered the regular email login verification page. This flow will not fall back to email verification. URL: ${result?.url || ''}`.trim()); } - throw new Error(`步骤 ${completionStepForState(currentState)}:手机号注册模式 OAuth 登录进入了不允许的页面:${getLoginAuthStateLabel(result.state)}。URL: ${result?.url || ''}`.trim()); + throw new Error(`Step ${completionStepForState(currentState)}: Phone-signup OAuth login entered a disallowed page: ${getLoginAuthStateLabel(result.state)}。URL: ${result?.url || ''}`.trim()); } if (isStep7AddEmailResult(result)) { - throw new Error(`步骤 ${completionStepForState(currentState)}:邮箱注册模式 OAuth 登录不应进入添加邮箱页。URL: ${result?.url || ''}`.trim()); + throw new Error(`Step ${completionStepForState(currentState)}: Email-signup OAuth login should not enter the add-email page. URL: ${result?.url || ''}`.trim()); } if (isStep7AddPhoneResult(result) || isStep7PhoneVerificationResult(result)) { payload.skipLoginVerificationStep = true; @@ -217,7 +217,7 @@ return payload; } - throw new Error(`步骤 ${completionStepForState(currentState)}:邮箱注册模式 OAuth 登录进入了不允许的页面:${getLoginAuthStateLabel(result.state)}。URL: ${result?.url || ''}`.trim()); + throw new Error(`Step ${completionStepForState(currentState)}: Email-signup OAuth login entered a disallowed page: ${getLoginAuthStateLabel(result.state)}。URL: ${result?.url || ''}`.trim()); } function completionStepForState(state = {}) { @@ -228,7 +228,7 @@ async function completeStep7PostLoginPhoneHandoff(state = {}, err, completionStep) { if (normalizeStep7SignupMethod(state?.resolvedSignupMethod || state?.signupMethod) === 'phone') { throw new Error( - `步骤 ${completionStep}:手机号注册模式 OAuth 登录进入了添加手机号页,当前流程不允许在手机号注册模式补手机号。URL: ${extractAddPhoneUrl(err)}` + `Step ${completionStep}: Phone-signup OAuth login entered the add-phone page. This flow does not allow adding a phone number in phone-signup mode. URL: ${extractAddPhoneUrl(err)}` ); } await completeNodeFromBackground(state?.nodeId || 'oauth-login', { @@ -263,7 +263,7 @@ (resolvedIdentifierType === 'phone' && !phoneNumber) || (resolvedIdentifierType !== 'phone' && !email) ) { - throw new Error('缺少登录账号:请先完成步骤 2,或在侧栏“注册邮箱/注册手机号”中手动填写账号后再执行当前步骤。'); + throw new Error('Missing login account: complete Step 2 first, or manually fill in the account in the side panel under "Signup Email/Signup Phone Number" before running this step.'); } const forceEmailLoginForThisRun = shouldForceStep7EmailLogin(state); @@ -325,18 +325,18 @@ const loginTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(180000, { step: completionStep, - actionLabel: 'OAuth 登录并进入验证码页', + actionLabel: 'OAuth login and enter the verification page', oauthUrl, }) : 180000; if (attempt === 1) { - await addLog('正在打开最新 OAuth 链接并登录...', 'info', { + await addLog('Opening the latest OAuth link and logging in...', 'info', { step: completionStep, stepKey: 'oauth-login', }); } else { - await addLog(`上一轮失败后,正在进行第 ${attempt} 次尝试(最多 ${STEP6_MAX_ATTEMPTS} 次)...`, 'warn', { + await addLog(`Previous round failed. Starting attempt ${attempt} (max ${STEP6_MAX_ATTEMPTS})...`, 'warn', { step: completionStep, stepKey: 'oauth-login', }); @@ -372,7 +372,7 @@ timeoutMs: loginTimeoutMs, responseTimeoutMs: loginTimeoutMs, retryDelayMs: 700, - logMessage: '认证页正在切换,等待页面重新就绪后继续登录...', + logMessage: 'Auth page is switching. Waiting for the page to become ready again before continuing login...', logStep: completionStep, logStepKey: 'oauth-login', } @@ -396,11 +396,11 @@ if (isStep6RecoverableResult(result)) { const reasonMessage = result.message - || `当前停留在${getLoginAuthStateLabel(result.state)},准备重新执行步骤 ${completionStep}。`; + || `Currently staying on ${getLoginAuthStateLabel(result.state)}. Preparing to rerun Step ${completionStep}.`; throw new Error(reasonMessage); } - throw new Error(`步骤 ${completionStep}:认证页未返回可识别的登录结果。`); + throw new Error(`Step ${completionStep}: Auth page did not return a recognizable login result.`); } catch (err) { throwIfStopped(err); if (isAddPhoneAuthFailure(err)) { @@ -416,7 +416,7 @@ } if (isManagementSecretConfigError(err)) { await addLog( - `检测到来源后台管理密钥缺失或错误,不再重试,当前流程停止。原因:${getErrorMessage(err)}`, + `Detected that the source backend admin key is missing or invalid. No more retries. The current flow will stop. Reason: ${getErrorMessage(err)}`, 'error', { step: completionStep, stepKey: 'oauth-login' } ); @@ -427,14 +427,14 @@ break; } - await addLog(`第 ${attempt} 次尝试失败,原因:${getErrorMessage(err)};准备重试...`, 'warn', { + await addLog(`Attempt ${attempt} failed. Reason: ${getErrorMessage(err)}. Preparing to retry...`, 'warn', { step: completionStep, stepKey: 'oauth-login', }); } } - throw new Error(`步骤 ${completionStep}:判断失败后已重试 ${STEP6_MAX_ATTEMPTS - 1} 次,仍未成功。最后原因:${getErrorMessage(lastError)}`); + throw new Error(`Step ${completionStep}: After a failed state judgment, retried ${STEP6_MAX_ATTEMPTS - 1} times and still did not succeed. Final reason: ${getErrorMessage(lastError)}`); } return { executeStep7 }; diff --git a/flows/openai/background/steps/open-chatgpt.js b/flows/openai/background/steps/open-chatgpt.js index 91d03e5c..592431b5 100644 --- a/flows/openai/background/steps/open-chatgpt.js +++ b/flows/openai/background/steps/open-chatgpt.js @@ -40,7 +40,7 @@ } function getStep1ErrorMessage(error) { - return error?.message || String(error || '未知错误'); + return error?.message || String(error || 'Unknown error'); } async function collectStep1Cookies(chromeApi) { @@ -127,12 +127,12 @@ async function clearOpenAiCookiesBeforeStep1() { if (!chromeApi?.cookies?.getAll || !chromeApi.cookies?.remove) { - await addLog('步骤 1:当前浏览器不支持 cookies API,跳过打开官网前 cookie 清理。', 'warn'); + await addLog('Step 1: The current browser does not support the cookies API. Skipping cookie cleanup before opening the website.', 'warn'); return; } const startedAt = Date.now(); - await addLog('步骤 1:打开 ChatGPT 官网前清理 ChatGPT / OpenAI cookies...', 'info'); + await addLog('Step 1: Clearing ChatGPT / OpenAI cookies before opening the ChatGPT website...', 'info'); const cookies = await collectStep1Cookies(chromeApi); let removedCount = 0; for (const cookie of cookies) { @@ -142,12 +142,12 @@ } const elapsedMs = Date.now() - startedAt; - await addLog(`步骤 1:已清理 ${removedCount} 个 ChatGPT / OpenAI cookies(耗时 ${elapsedMs}ms)。`, 'ok'); + await addLog(`Step 1: Cleared ${removedCount} ChatGPT / OpenAI cookies (took ${elapsedMs}ms).`, 'ok'); } async function executeStep1() { await clearOpenAiCookiesBeforeStep1(); - await addLog('步骤 1:正在打开 ChatGPT 官网...'); + await addLog('Step 1: Opening the ChatGPT website...'); await openSignupEntryTab(1); await completeNodeFromBackground('open-chatgpt', {}); } diff --git a/flows/openai/background/steps/paypal-approve.js b/flows/openai/background/steps/paypal-approve.js index e8c23c89..493aba39 100644 --- a/flows/openai/background/steps/paypal-approve.js +++ b/flows/openai/background/steps/paypal-approve.js @@ -30,7 +30,7 @@ } const discoveredPayPalTabId = await findOpenPayPalTabId(); if (discoveredPayPalTabId) { - await addLog('步骤 8:已从当前浏览器标签中发现 PayPal 页面,正在接管继续执行。', 'info'); + await addLog('Step 8: Found the PayPal page in the current browser tabs. Taking over and continuing.', 'info'); return discoveredPayPalTabId; } const checkoutTabId = await getTabId(PLUS_CHECKOUT_SOURCE); @@ -41,7 +41,7 @@ if (storedTabId) { return storedTabId; } - throw new Error('步骤 8:未找到 PayPal 标签页,请先完成步骤 7。'); + throw new Error('Step 8: PayPal tab not found. Complete Step 7 first.'); } async function findOpenPayPalTabId() { @@ -75,7 +75,7 @@ await ensureContentScriptReadyOnTabUntilStopped(PAYPAL_SOURCE, tabId, { inject: PAYPAL_INJECT_FILES, injectSource: PAYPAL_SOURCE, - logMessage: logMessage || '步骤 8:PayPal 页面仍在加载,等待脚本就绪...', + logMessage: logMessage || 'Step 8: PayPal page is still loading. Waiting for the script to become ready...', }); } @@ -118,9 +118,9 @@ async function submitLogin(tabId, state = {}) { const credentials = resolvePayPalCredentials(state); if (!credentials.password) { - throw new Error('步骤 8:未配置可用的 PayPal 账号,请先在侧边栏添加并选择账号。'); + throw new Error('Step 8: No available PayPal account is configured. Add and select one in the side panel first.'); } - await addLog('步骤 8:正在填写 PayPal 登录信息并提交...', 'info'); + await addLog('Step 8: Filling in PayPal login details and submitting...', 'info'); const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, { type: 'PAYPAL_SUBMIT_LOGIN', source: 'background', @@ -152,7 +152,7 @@ while (Date.now() - startedAt < PAYPAL_LOGIN_TRANSITION_TIMEOUT_MS) { const tab = await chrome.tabs.get(tabId).catch(() => null); if (!tab) { - throw new Error('步骤 8:PayPal 标签页已关闭,无法继续识别登录后的页面。'); + throw new Error('Step 8: PayPal tab was closed. Cannot continue identifying the page after login.'); } const currentUrl = tab.url || ''; @@ -175,8 +175,8 @@ await ensurePayPalReady( tabId, phase === 'email_submitted' - ? '步骤 8:PayPal 账号已提交,正在识别下一页...' - : '步骤 8:PayPal 密码已提交,正在识别跳转结果...' + ? 'Step 8: PayPal account submitted. Identifying the next page...' + : 'Step 8: PayPal password submitted. Identifying the navigation result...' ); const pageState = await getPayPalState(tabId); @@ -238,35 +238,35 @@ while (true) { const currentUrl = (await chrome.tabs.get(tabId).catch(() => null))?.url || ''; if (currentUrl && !isPayPalUrl(currentUrl)) { - await addLog('步骤 8:PayPal 已跳转离开授权页,准备进入回跳确认。', 'ok'); + await addLog('Step 8: PayPal has navigated away from the authorization page. Preparing to confirm the return.', 'ok'); break; } - await ensurePayPalReady(tabId, '步骤 8:PayPal 页面正在切换,等待脚本重新就绪...'); + await ensurePayPalReady(tabId, 'Step 8: PayPal page is switching. Waiting for the script to become ready again...'); const pageState = await getPayPalState(tabId); if (pageState.needsLogin) { const submitResult = await submitLogin(tabId, state); const decision = await waitForPayPalPostLoginDecision(tabId, submitResult); if (decision.outcome === 'left_paypal') { - await addLog('步骤 8:PayPal 登录后已跳转离开登录/授权页,继续进入回跳确认。', 'ok'); + await addLog('Step 8: After PayPal login, the page has left the login/authorization view. Continuing to return confirmation.', 'ok'); break; } if (decision.outcome === 'password_ready') { - await addLog('步骤 8:PayPal 账号页提交后已识别到密码页,继续填写密码。', 'info'); + await addLog('Step 8: After submitting the PayPal account page, the password page was detected. Continuing to fill the password.', 'info'); } else if (decision.outcome === 'approve_ready') { - await addLog('步骤 8:PayPal 登录后已识别到授权确认页,继续点击授权。', 'info'); + await addLog('Step 8: After PayPal login, the authorization confirmation page was detected. Continuing to authorize.', 'info'); } else if (decision.outcome === 'prompt') { - await addLog('步骤 8:PayPal 登录后已识别到提示弹窗,继续处理弹窗。', 'info'); + await addLog('Step 8: After PayPal login, a prompt dialog was detected. Continuing to handle it.', 'info'); } else if (decision.outcome === 'timeout') { - await addLog('步骤 8:PayPal 登录动作后暂未识别到新页面,重新检查当前页面状态。', 'warn'); + await addLog('Step 8: No new page was detected after the PayPal login action. Rechecking the current page state.', 'warn'); } loggedWaiting = false; continue; } if (pageState.hasPasskeyPrompt) { - await addLog('步骤 8:检测到 PayPal 通行密钥提示,正在关闭...', 'info'); + await addLog('Step 8: Detected a PayPal passkey prompt. Closing it...', 'info'); await dismissPrompts(tabId); await sleepWithStop(1000); continue; @@ -279,7 +279,7 @@ } if (pageState.approveReady) { - await addLog('步骤 8:正在点击 PayPal“同意并继续”...', 'info'); + await addLog('Step 8: Clicking PayPal "Agree and continue"...', 'info'); const clicked = await clickApprove(tabId); if (clicked) { await setState({ plusPaypalApprovedAt: Date.now() }); @@ -289,7 +289,7 @@ if (!loggedWaiting) { loggedWaiting = true; - await addLog('步骤 8:等待 PayPal 授权按钮或下一步页面出现...', 'info'); + await addLog('Step 8: Waiting for the PayPal authorization button or the next page to appear...', 'info'); } await sleepWithStop(500); } diff --git a/flows/openai/background/steps/platform-verify.js b/flows/openai/background/steps/platform-verify.js index ebd86497..1dc018e5 100644 --- a/flows/openai/background/steps/platform-verify.js +++ b/flows/openai/background/steps/platform-verify.js @@ -33,7 +33,7 @@ const factory = deps.createSub2ApiApi || self.MultiPageBackgroundSub2ApiApi?.createSub2ApiApi; if (typeof factory !== 'function') { - throw new Error('SUB2API 直连接口模块未加载,无法提交回调。'); + throw new Error('SUB2API direct-connect module is not loaded. Cannot submit the callback.'); } sub2ApiApi = factory({ addLog, @@ -90,13 +90,13 @@ try { parsed = new URL(rawUrl); } catch { - throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址格式无效,请重新执行步骤 ${confirmOauthStep}。`); + throw new Error(`Step ${platformVerifyStep}: The captured localhost OAuth callback URL format is invalid. Rerun Step ${confirmOauthStep}.`); } const code = normalizeString(parsed.searchParams.get('code')); const oauthState = normalizeString(parsed.searchParams.get('state')); if (!code || !oauthState) { - throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址缺少 code 或 state,请重新执行步骤 ${confirmOauthStep}。`); + throw new Error(`Step ${platformVerifyStep}: The captured localhost OAuth callback URL is missing code or state. Rerun Step ${confirmOauthStep}.`); } return { @@ -115,19 +115,19 @@ ] .map((value) => normalizeString(value)) .find(Boolean); - return details || `Codex2API 请求失败(HTTP ${responseStatus})。`; + return details || `Codex2API request failed (HTTP ${responseStatus}).`; } function deriveCpaManagementOrigin(vpsUrl) { const normalizedUrl = normalizeString(vpsUrl); if (!normalizedUrl) { - throw new Error('尚未填写 CPA 地址,请先在侧边栏输入。'); + throw new Error('CPA URL is not filled in yet. Enter it in the side panel first.'); } let parsed; try { parsed = new URL(normalizedUrl); } catch { - throw new Error('CPA 地址格式无效,请先在侧边栏检查。'); + throw new Error('CPA URL format is invalid. Check it in the side panel first.'); } return parsed.origin; } @@ -141,7 +141,7 @@ ] .map((value) => normalizeString(value)) .find(Boolean); - return details || `CPA 管理接口请求失败(HTTP ${responseStatus})。`; + return details || `CPA admin API request failed (HTTP ${responseStatus}).`; } async function fetchCpaManagementJson(origin, path, options = {}) { @@ -181,7 +181,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error('CPA 管理接口请求超时,请稍后重试。'); + throw new Error('CPA admin API request timed out. Please try again later.'); } throw error; } finally { @@ -240,7 +240,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error('Codex2API 请求超时,请稍后重试。'); + throw new Error('Codex2API request timed out. Please try again later.'); } throw error; } finally { @@ -263,17 +263,17 @@ const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep, state); const authLoginStep = resolveAuthLoginStep(platformVerifyStep, state); if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) { - throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}。`); + throw new Error(`Step ${confirmOauthStep}: The captured localhost OAuth callback URL is invalid. Rerun Step ${confirmOauthStep}.`); } if (!state.localhostUrl) { - throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}。`); + throw new Error(`Missing localhost callback URL. Complete Step ${confirmOauthStep} first.`); } if (!state.vpsUrl) { - throw new Error('尚未填写 CPA 地址,请先在侧边栏输入。'); + throw new Error('CPA URL is not filled in yet. Enter it in the side panel first.'); } if (shouldBypassStep9ForLocalCpa(state)) { - await addStepLog(platformVerifyStep, '检测到本地 CPA,且当前策略为“跳过平台回调验证”,本轮不再重复提交回调地址。', 'info'); + await addStepLog(platformVerifyStep, 'Detected local CPA, and the current strategy is "skip platform callback verification". This round will not resubmit the callback URL.', 'info'); await completeNodeFromBackground(state?.nodeId || 'platform-verify', { localhostUrl: state.localhostUrl, verifiedStatus: 'local-auto', @@ -284,14 +284,14 @@ const callback = parseLocalhostCallback(state.localhostUrl, platformVerifyStep, state); const expectedState = normalizeString(state.cpaOAuthState); if (expectedState && expectedState !== callback.state) { - throw new Error(`CPA 回调 state 与当前授权会话不匹配,请重新执行步骤 ${authLoginStep}。`); + throw new Error(`CPA callback state does not match the current authorization session. Rerun Step ${authLoginStep}.`); } const managementKey = normalizeString(state.vpsPassword); if (!managementKey) { - throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。'); + throw new Error('CPA admin key is not configured yet. Fill it in the side panel first.'); } - await addStepLog(platformVerifyStep, '正在通过 CPA 管理接口提交回调地址...'); + await addStepLog(platformVerifyStep, 'Submitting the callback URL through the CPA admin API...'); try { const origin = normalizeString(state.cpaManagementOrigin) || deriveCpaManagementOrigin(state.vpsUrl); const result = await fetchCpaManagementJson(origin, '/v0/management/oauth-callback', { @@ -305,7 +305,7 @@ const verifiedStatus = normalizeString(result?.message) || normalizeString(result?.status_message) - || 'CPA 已通过接口提交回调'; + || 'CPA callback submitted through the API'; await addStepLog(platformVerifyStep, verifiedStatus, 'ok'); await completeNodeFromBackground(state?.nodeId || 'platform-verify', { localhostUrl: callback.url, @@ -313,7 +313,7 @@ }); } catch (error) { const reason = normalizeString(error?.message) || 'unknown error'; - await addStepLog(platformVerifyStep, `CPA 接口提交失败:${reason}`, 'error'); + await addStepLog(platformVerifyStep, `CPA API submission failed: ${reason}`, 'error'); throw error; } } @@ -323,28 +323,28 @@ const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep, state); const authLoginStep = resolveAuthLoginStep(platformVerifyStep, state); if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) { - throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}。`); + throw new Error(`Step ${confirmOauthStep}: The captured localhost OAuth callback URL is invalid. Rerun Step ${confirmOauthStep}.`); } if (!state.localhostUrl) { - throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}。`); + throw new Error(`Missing localhost callback URL. Complete Step ${confirmOauthStep} first.`); } if (!state.codex2apiSessionId) { - throw new Error(`缺少 Codex2API 会话信息,请重新执行步骤 ${authLoginStep}。`); + throw new Error(`Missing Codex2API session information. Rerun Step ${authLoginStep}.`); } if (!normalizeString(state.codex2apiAdminKey)) { - throw new Error('尚未配置 Codex2API 管理密钥,请先在侧边栏填写。'); + throw new Error('Codex2API admin key is not configured yet. Fill it in the side panel first.'); } const callback = parseLocalhostCallback(state.localhostUrl, platformVerifyStep, state); const expectedState = normalizeString(state.codex2apiOAuthState); if (expectedState && expectedState !== callback.state) { - throw new Error(`Codex2API 回调 state 与当前授权会话不匹配,请重新执行步骤 ${authLoginStep}。`); + throw new Error(`Codex2API callback state does not match the current authorization session. Rerun Step ${authLoginStep}.`); } const codex2apiUrl = normalizeCodex2ApiUrl(state.codex2apiUrl); const origin = new URL(codex2apiUrl).origin; - await addStepLog(platformVerifyStep, '正在向 Codex2API 提交回调并创建账号...'); + await addStepLog(platformVerifyStep, 'Submitting the callback to Codex2API and creating the account...'); const result = await fetchCodex2ApiJson(origin, '/api/admin/oauth/exchange-code', { adminKey: state.codex2apiAdminKey, method: 'POST', @@ -355,7 +355,7 @@ }, }); - const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功'; + const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth account added successfully'; await addStepLog(platformVerifyStep, verifiedStatus, 'ok'); await completeNodeFromBackground(state?.nodeId || 'platform-verify', { localhostUrl: callback.url, @@ -368,19 +368,19 @@ const visibleStep = platformVerifyStep; const confirmOauthStep = resolveConfirmOauthStep(visibleStep, state); if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) { - throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}。`); + throw new Error(`Step ${confirmOauthStep}: The captured localhost OAuth callback URL is invalid. Rerun Step ${confirmOauthStep}.`); } if (!state.localhostUrl) { - throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}。`); + throw new Error(`Missing localhost callback URL. Complete Step ${confirmOauthStep} first.`); } if (!state.sub2apiSessionId) { - throw new Error('缺少 SUB2API 会话信息,请重新执行步骤 1。'); + throw new Error('Missing SUB2API session information. Rerun Step 1.'); } if (!state.sub2apiEmail) { - throw new Error('尚未配置 SUB2API 登录邮箱,请先在侧边栏填写。'); + throw new Error('SUB2API login email is not configured yet. Fill it in the side panel first.'); } if (!state.sub2apiPassword) { - throw new Error('尚未配置 SUB2API 登录密码,请先在侧边栏填写。'); + throw new Error('SUB2API login password is not configured yet. Fill it in the side panel first.'); } const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl); @@ -398,7 +398,7 @@ sub2apiUrl, }, { visibleStep, - logLabel: `步骤 ${visibleStep}`, + logLabel: `Step ${visibleStep}`, logOptions: { step: visibleStep, stepKey: 'platform-verify' }, timeoutMs: SUB2API_STEP9_RESPONSE_TIMEOUT_MS, }); @@ -410,7 +410,7 @@ throw error; } await addLog( - `SUB2API 回调交换出现临时网络波动(${error.message}),正在重试 ${attempt + 1}/${maxExchangeAttempts}...`, + `SUB2API callback exchange hit a temporary network issue (${error.message}). Retrying ${attempt + 1}/${maxExchangeAttempts}...`, 'warn', { step: visibleStep, stepKey: 'platform-verify' } ); diff --git a/flows/openai/background/steps/plus-return-confirm.js b/flows/openai/background/steps/plus-return-confirm.js index 65164f97..1191da3e 100644 --- a/flows/openai/background/steps/plus-return-confirm.js +++ b/flows/openai/background/steps/plus-return-confirm.js @@ -34,7 +34,7 @@ if (storedTabId) { return storedTabId; } - throw new Error('步骤 9:未找到 Plus / PayPal / GoPay 标签页,无法确认订阅回跳。'); + throw new Error('Step 9: Plus / PayPal / GoPay tab not found. Cannot confirm the subscription return.'); } function isReturnUrl(url = '') { @@ -44,9 +44,9 @@ async function executePlusReturnConfirm(state = {}) { const tabId = await resolveReturnTabId(state); - await addLog('步骤 9:正在等待支付授权后回跳到 ChatGPT / OpenAI 页面...', 'info'); + await addLog('Step 9: Waiting to return to the ChatGPT / OpenAI page after payment authorization...', 'info'); const tab = await waitForTabUrlMatchUntilStopped(tabId, isReturnUrl); - await addLog('步骤 9:已检测到订阅回跳页面,固定等待 20 秒让页面完成加载。', 'info'); + await addLog('Step 9: Subscription return page detected. Waiting a fixed 20 seconds for the page to finish loading.', 'info'); await sleepWithStop(PLUS_RETURN_SETTLE_WAIT_MS); await setState({ diff --git a/flows/openai/background/steps/sub2api-session-import.js b/flows/openai/background/steps/sub2api-session-import.js index a163022f..0d1e5d88 100644 --- a/flows/openai/background/steps/sub2api-session-import.js +++ b/flows/openai/background/steps/sub2api-session-import.js @@ -37,7 +37,7 @@ const factory = deps.createSub2ApiApi || self.MultiPageBackgroundSub2ApiApi?.createSub2ApiApi; if (typeof factory !== 'function') { - throw new Error('SUB2API 接口模块未加载,无法导入当前 ChatGPT 会话。'); + throw new Error('SUB2API module is not loaded. Cannot import the current ChatGPT session.'); } sub2ApiApi = factory({ addLog: rawAddLog, @@ -185,16 +185,16 @@ return fallbackTab.id; } - throw new Error('未找到可读取 ChatGPT 会话的标签页,请先打开一个已登录的 ChatGPT / OpenAI 页面,或完成当前 Plus 支付链路。'); + throw new Error('No tab with a readable ChatGPT session was found. Open a logged-in ChatGPT / OpenAI page first, or complete the current Plus payment flow.'); } async function getResolvedSessionTab(tabId, visibleStep) { const tab = await chrome?.tabs?.get?.(tabId).catch(() => null); if (!tab?.id) { - throw new Error(`步骤 ${visibleStep}:ChatGPT 会话标签页不存在或已关闭,无法继续导入 SUB2API。`); + throw new Error(`Step ${visibleStep}: ChatGPT session tab does not exist or was closed. Cannot continue importing to SUB2API.`); } if (!isSupportedChatGptSessionUrl(tab.url)) { - throw new Error(`步骤 ${visibleStep}:当前标签页不在 ChatGPT / OpenAI 页面,无法读取当前登录会话。`); + throw new Error(`Step ${visibleStep}: The current tab is not on a ChatGPT / OpenAI page. Cannot read the current login session.`); } return tab; } @@ -205,7 +205,7 @@ await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, { inject: PLUS_CHECKOUT_INJECT_FILES, injectSource: PLUS_CHECKOUT_SOURCE, - logMessage: `步骤 ${visibleStep}:正在等待 ChatGPT 会话页完成加载,再继续读取当前登录会话...`, + logMessage: `Step ${visibleStep}: Waiting for the ChatGPT session page to finish loading before continuing to read the current login session...`, }); const sessionResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, { @@ -228,7 +228,7 @@ || session?.accessToken ); if (!session && !accessToken) { - throw new Error(`步骤 ${visibleStep}:未读取到有效的 ChatGPT 会话或 accessToken,请确认当前标签页仍处于已登录状态。`); + throw new Error(`Step ${visibleStep}: No valid ChatGPT session or accessToken was read. Confirm that the current tab is still logged in.`); } return { @@ -242,14 +242,14 @@ const visibleStep = resolveVisibleStep(state); const api = getSub2ApiApi(); - await addStepLog(visibleStep, '正在定位当前 ChatGPT 会话页并准备导入 SUB2API...', 'info'); + await addStepLog(visibleStep, 'Locating the current ChatGPT session page and preparing to import to SUB2API...', 'info'); const tabId = await resolveSessionTabId(state); const tab = await getResolvedSessionTab(tabId, visibleStep); if (chrome?.tabs?.update) { await chrome.tabs.update(tab.id, { active: true }).catch(() => {}); } - await addStepLog(visibleStep, '正在读取当前 ChatGPT 登录会话...', 'info'); + await addStepLog(visibleStep, 'Reading the current ChatGPT login session...', 'info'); const sessionState = await readCurrentChatGptSession(tab.id, visibleStep); throwIfStopped(); @@ -259,7 +259,7 @@ accessToken: sessionState.accessToken, }, { visibleStep, - logLabel: `步骤 ${visibleStep}`, + logLabel: `Step ${visibleStep}`, logOptions: { step: visibleStep, stepKey: 'sub2api-session-import' }, timeoutMs: 120000, importTimeoutMs: 120000, diff --git a/flows/openai/background/steps/submit-signup-email.js b/flows/openai/background/steps/submit-signup-email.js index e8942b70..23aaab41 100644 --- a/flows/openai/background/steps/submit-signup-email.js +++ b/flows/openai/background/steps/submit-signup-email.js @@ -27,17 +27,17 @@ function isSignupEntryUnavailableErrorMessage(errorLike) { const message = getErrorMessage(errorLike); - return /未找到可用的邮箱输入入口|当前页面没有可用的注册入口,也不在邮箱\/密码页/i.test(message); + return /No available email input entry found|当前页面没有可用的注册入口,也不在邮箱\/密码页/i.test(message); } function isSignupPhoneEntryUnavailableErrorMessage(errorLike) { const message = getErrorMessage(errorLike); - return /未找到可用的手机号输入入口|当前页面没有可用的手机号注册入口,也不在密码页/i.test(message); + return /No available phone number input entry found|The current page has no available phone signup entry and is not on the password page/i.test(message); } function isRetryableStep2TransportErrorMessage(errorLike) { const message = getErrorMessage(errorLike); - return /Content script on [\w-]+ did not respond in \d+s|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message); + return /Content script on [\w-]+ did not respond in \d+s|content script\s+\d+(?:\.\d+)?\s*did not respond within seconds|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message); } function isLikelyLoggedInChatgptHomeUrl(rawUrl) { @@ -86,7 +86,7 @@ }, { timeoutMs: 12000, retryDelayMs: 500, - logMessage: '步骤 2:正在检查官网注册入口状态...', + logMessage: 'Step 2: Checking the official website signup entry state...', }); if (result?.error) { return ''; @@ -137,8 +137,8 @@ } const reasonText = getErrorMessage(reasonMessage); - const reasonSuffix = reasonText ? `(触发原因:${reasonText})` : ''; - const message = `步骤 2:检测到当前停留在已登录 ChatGPT 首页,已阻止自动跳过步骤 3/4/5。请先执行步骤 1 清理会话后重试。${reasonSuffix}`; + const reasonSuffix = reasonText ? ` (trigger reason: ${reasonText})` : ''; + const message = `Step 2: Detected that the current page is the logged-in ChatGPT home page. Auto-skip for Steps 3/4/5 has been blocked. Run Step 1 to clear the session, then retry.${reasonSuffix}`; await addLog(message, 'error'); throw new Error(message); } @@ -147,7 +147,7 @@ const { timeoutMs = 35000, retryDelayMs = 700, - logMessage = '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...', + logMessage = 'Step 2: The official website signup entry is switching. Waiting for the page to recover before continuing to enter the email...', } = options; try { @@ -173,7 +173,7 @@ } await addLog( - logMessage || '步骤 2:注册页标签已切换,正在等待页面加载完成并额外稳定 3 秒...', + logMessage || 'Step 2: Switched to the signup tab. Waiting for the page to finish loading and remain stable for another 3 seconds...', 'info', { step: 2, stepKey: 'signup-entry' } ); @@ -195,7 +195,7 @@ async function ensureSignupPhoneEntryReady(tabId) { if (!Number.isInteger(tabId)) { - throw new Error('步骤 2:未找到可用的注册页标签,无法切换到手机号注册入口。'); + throw new Error('Step 2: No available signup tab was found. Cannot switch to the phone signup entry.'); } const result = await sendToContentScriptResilient('openai-auth', { @@ -206,7 +206,7 @@ }, { timeoutMs: 30000, retryDelayMs: 700, - logMessage: '步骤 2:正在打开官网注册入口并切换到手机号注册...', + logMessage: 'Step 2: Opening the official website signup entry and switching to phone signup...', }); if (result?.error) { @@ -226,7 +226,7 @@ countryId: activation?.countryId ?? null, countryLabel: String(activation?.countryLabel || '').trim(), }, { - logMessage: '步骤 2:官网注册入口正在切换,等待手机号注册入口恢复...', + logMessage: 'Step 2: The official website signup entry is switching. Waiting for the phone signup entry to recover...', ...options, }); } @@ -234,21 +234,21 @@ async function ensureSignupTabForStep2() { let signupTabId = await getTabId('openai-auth'); if (!signupTabId || !(await isTabAlive('openai-auth'))) { - await addLog('步骤 2:未发现可用的注册页标签,正在重新打开 ChatGPT 官网...', 'warn'); + await addLog('Step 2: No available signup tab was found. Reopening the ChatGPT website...', 'warn'); signupTabId = (await ensureSignupEntryPageReady(2)).tabId; } else { await chrome.tabs.update(signupTabId, { active: true }); await keepSignupTabWindowInBackgroundForStep2(signupTabId); await waitForStep2SignupTabToSettle( signupTabId, - '步骤 2:已切换到注册页标签,正在等待页面加载完成并额外稳定 3 秒...' + 'Step 2: Switched to the signup tab. Waiting for the page to finish loading and remain stable for another 3 seconds...' ); await ensureContentScriptReadyOnTab('openai-auth', signupTabId, { inject: OPENAI_AUTH_INJECT_FILES, injectSource: 'openai-auth', timeoutMs: 45000, retryDelayMs: 900, - logMessage: '步骤 2:注册入口页内容脚本未就绪,正在等待页面恢复...', + logMessage: 'Step 2: Signup entry page content script is not ready. Waiting for the page to recover...', }); } return signupTabId; @@ -284,7 +284,7 @@ async function resolveSignupPhoneForStep2(state = {}) { const existingActivation = normalizeSignupPhoneActivationForStep2(state?.signupPhoneActivation); if (existingActivation?.phoneNumber) { - await addLog(`步骤 2:复用当前注册手机号 ${existingActivation.phoneNumber},不重新获取号码。`); + await addLog(`Step 2: Reusing the current signup phone number ${existingActivation.phoneNumber}. Not requesting a new number.`); return { phoneNumber: existingActivation.phoneNumber, activation: existingActivation, @@ -293,7 +293,7 @@ const manualPhoneNumber = getSignupPhoneNumberFromState(state); if (manualPhoneNumber) { - await addLog(`步骤 2:使用手动填写的注册手机号 ${manualPhoneNumber},本轮不会重新获取号码。`, 'warn'); + await addLog(`Step 2: Using the manually entered signup phone number ${manualPhoneNumber}. This round will not request a new number.`, 'warn'); return { phoneNumber: manualPhoneNumber, activation: null, @@ -301,7 +301,7 @@ } if (typeof phoneVerificationHelpers?.prepareSignupPhoneActivation !== 'function') { - throw new Error('手机号注册流程不可用:接码模块尚未初始化。'); + throw new Error('Phone signup flow unavailable: the SMS verification module is not initialized yet.'); } const activation = await phoneVerificationHelpers.prepareSignupPhoneActivation(state); return { @@ -313,7 +313,7 @@ async function executeSignupPhoneEntry(state) { let signupTabId = await ensureSignupTabForStep2(); if (await shouldForceAuthEntryRetry(signupTabId)) { - await addLog('步骤 2:检测到当前位于已登录 ChatGPT 首页,先切换认证入口页再提交手机号。', 'warn'); + await addLog('Step 2: Detected that the current page is the logged-in ChatGPT home page. Switching to the auth entry page before submitting the phone number.', 'warn'); try { signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId; } catch (entryError) { @@ -321,7 +321,7 @@ if (await failStep2OnLoggedInSession(signupTabId, entryErrorMessage)) { return; } - await addLog('步骤 2:切换认证入口失败,正在重新打开官网入口并重试提交手机号...', 'warn'); + await addLog('Step 2: Failed to switch the auth entry. Reopening the official entry and retrying phone number submission...', 'warn'); signupTabId = (await ensureSignupEntryPageReady(2)).tabId; } } @@ -338,7 +338,7 @@ || isSignupEntryUnavailableErrorMessage(entryErrorMessage) || isRetryableStep2TransportErrorMessage(entryErrorMessage) ) { - await addLog('步骤 2:手机号注册入口尚未就绪,正在重新打开官网入口后重试一次...', 'warn'); + await addLog('Step 2: Phone signup entry is not ready yet. Reopening the official entry and retrying once...', 'warn'); signupTabId = (await ensureSignupEntryPageReady(2)).tabId; await ensureSignupPhoneEntryReady(signupTabId); } else { @@ -351,7 +351,7 @@ let step2Result = await submitSignupPhone(phoneNumber, activation, { timeoutMs: 45000, retryDelayMs: 700, - logMessage: '步骤 2:官网注册入口正在切换,等待手机号注册入口恢复...', + logMessage: 'Step 2: The official website signup entry is switching. Waiting for the phone signup entry to recover...', }); if (step2Result?.error) { @@ -361,13 +361,13 @@ || isSignupEntryUnavailableErrorMessage(errorMessage) || isRetryableStep2TransportErrorMessage(errorMessage) ) { - await addLog('步骤 2:手机号注册入口不可用或通信超时,正在重新准备手机号注册入口后重试一次...', 'warn'); + await addLog('Step 2: Phone signup entry is unavailable or timed out. Preparing the phone signup entry again and retrying once...', 'warn'); signupTabId = (await ensureSignupEntryPageReady(2)).tabId; await ensureSignupPhoneEntryReady(signupTabId); step2Result = await submitSignupPhone(phoneNumber, activation, { timeoutMs: 45000, retryDelayMs: 700, - logMessage: '步骤 2:手机号注册入口已就绪,正在重新提交手机号...', + logMessage: 'Step 2: Phone signup entry is ready. Resubmitting the phone number...', }); } } @@ -387,7 +387,7 @@ throw new Error(finalErrorMessage); } - await addLog(`步骤 2:手机号 ${phoneNumber} 已提交,正在等待页面加载并确认下一步入口...`); + await addLog(`Step 2: Phone number ${phoneNumber} submitted. Waiting for the page to load and confirm the next entry...`); const landingResult = await ensureSignupPostIdentityPageReadyInTab(signupTabId, 2, { skipUrlWait: Boolean(step2Result?.alreadyOnPasswordPage), }); @@ -409,7 +409,7 @@ let signupTabId = await ensureSignupTabForStep2(); if (await shouldForceAuthEntryRetry(signupTabId)) { - await addLog('步骤 2:检测到当前位于已登录 ChatGPT 首页,先切换认证入口页再提交邮箱。', 'warn'); + await addLog('Step 2: Detected that the current page is the logged-in ChatGPT home page. Switching to the auth entry page before submitting the email.', 'warn'); try { signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId; } catch (entryError) { @@ -417,7 +417,7 @@ if (await failStep2OnLoggedInSession(signupTabId, entryErrorMessage)) { return; } - await addLog('步骤 2:切换认证入口失败,正在重新打开官网入口并重试提交邮箱...', 'warn'); + await addLog('Step 2: Failed to switch the auth entry. Reopening the official entry and retrying email submission...', 'warn'); signupTabId = (await ensureSignupEntryPageReady(2)).tabId; } } @@ -425,18 +425,18 @@ let step2Result = await submitSignupEmail(resolvedEmail, { timeoutMs: 35000, retryDelayMs: 700, - logMessage: '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...', + logMessage: 'Step 2: The official website signup entry is switching. Waiting for the page to recover before continuing to enter the email...', }); if (step2Result?.error) { const errorMessage = getErrorMessage(step2Result.error); if (isSignupEntryUnavailableErrorMessage(errorMessage)) { - await addLog('步骤 2:未找到邮箱输入入口,正在切换认证入口页后重试一次...', 'warn'); + await addLog('Step 2: Email input entry was not found. Switching the auth entry page and retrying once...', 'warn'); signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId; step2Result = await submitSignupEmail(resolvedEmail, { timeoutMs: 35000, retryDelayMs: 700, - logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...', + logMessage: 'Step 2: Auth entry page is open. Resubmitting the email...', }); if (step2Result?.error) { @@ -445,22 +445,22 @@ if (await failStep2OnLoggedInSession(signupTabId, retryErrorMessage)) { return; } - await addLog('步骤 2:认证入口仍不可用,正在重新进入官网注册入口再重试一次...', 'warn'); + await addLog('Step 2: Auth entry is still unavailable. Re-entering the official website signup entry and retrying once...', 'warn'); signupTabId = (await ensureSignupEntryPageReady(2)).tabId; step2Result = await submitSignupEmail(resolvedEmail, { timeoutMs: 35000, retryDelayMs: 700, - logMessage: '步骤 2:重试官网注册入口后正在重新提交邮箱...', + logMessage: 'Step 2: Retrying email submission after retrying the official website signup entry...', }); } } } else if (isRetryableStep2TransportErrorMessage(errorMessage)) { - await addLog('步骤 2:注册入口页通信超时,正在切换认证入口页并重试提交邮箱...', 'warn'); + await addLog('Step 2: Signup entry page communication timed out. Switching the auth entry page and retrying email submission...', 'warn'); signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId; step2Result = await submitSignupEmail(resolvedEmail, { timeoutMs: 45000, retryDelayMs: 700, - logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...', + logMessage: 'Step 2: Auth entry page is open. Resubmitting the email...', }); } } @@ -478,7 +478,7 @@ } if (!step2Result?.alreadyOnPasswordPage) { - await addLog(`步骤 2:邮箱 ${resolvedEmail} 已提交,正在等待页面加载并确认下一步入口...`); + await addLog(`Step 2: Email ${resolvedEmail} submitted. Waiting for the page to load and confirm the next entry...`); } const landingResult = await ensureSignupPostEmailPageReadyInTab(signupTabId, 2, { diff --git a/flows/openai/background/steps/wait-registration-success.js b/flows/openai/background/steps/wait-registration-success.js index f3f407c6..1838b715 100644 --- a/flows/openai/background/steps/wait-registration-success.js +++ b/flows/openai/background/steps/wait-registration-success.js @@ -100,7 +100,7 @@ addLog = async () => {}, chrome: chromeApi = globalThis.chrome, completeNodeFromBackground, - getErrorMessage = (error) => error?.message || String(error || '未知错误'), + getErrorMessage = (error) => error?.message || String(error || 'Unknown error'), registrationSuccessWaitMs = DEFAULT_REGISTRATION_SUCCESS_WAIT_MS, sleepWithStop = async (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0))), } = deps; @@ -110,12 +110,12 @@ return; } if (!chromeApi?.cookies?.getAll || !chromeApi.cookies?.remove) { - await addLog('步骤 6:当前浏览器不支持 cookies API,跳过第六步 Cookies 清理。', 'warn'); + await addLog('Step 6: The current browser does not support the cookies API. Skipping Step 6 cookie cleanup.', 'warn'); return; } try { - await addLog('步骤 6:已开启 Cookies 清理,正在清理 ChatGPT / OpenAI cookies...', 'info'); + await addLog('Step 6: Cookie cleanup is enabled. Clearing ChatGPT / OpenAI cookies...', 'info'); const cookies = await collectStep6Cookies(chromeApi); let removedCount = 0; for (const cookie of cookies) { @@ -131,24 +131,24 @@ origins: STEP6_COOKIE_CLEAR_ORIGINS, }); } catch (error) { - await addLog(`步骤 6:browsingData 补扫 cookies 失败:${getErrorMessage(error)}`, 'warn'); + await addLog(`Step 6: browsingData fallback cookie scan failed: ${getErrorMessage(error)}`, 'warn'); } } - await addLog(`步骤 6:已清理 ${removedCount} 个 ChatGPT / OpenAI cookies。`, 'ok'); + await addLog(`Step 6: Cleared ${removedCount} ChatGPT / OpenAI cookies.`, 'ok'); } catch (error) { - await addLog(`步骤 6:Cookies 清理失败,已跳过并继续后续流程:${getErrorMessage(error)}`, 'warn'); + await addLog(`Step 6: Cookie cleanup failed. Skipped and continuing the remaining flow: ${getErrorMessage(error)}`, 'warn'); } } async function executeStep6(state = {}) { const waitMs = Math.max(0, Math.floor(Number(registrationSuccessWaitMs) || 0)); if (waitMs > 0) { - await addLog(`步骤 6:等待 ${Math.round(waitMs / 1000)} 秒,确认注册成功并让页面稳定...`, 'info'); + await addLog(`Step 6: Waiting ${Math.round(waitMs / 1000)} seconds to confirm signup success and let the page stabilize...`, 'info'); await sleepWithStop(waitMs); } await clearCookiesIfEnabled(state); - await addLog('步骤 6:注册成功等待完成,准备继续获取 OAuth 链接并登录。', 'ok'); + await addLog('Step 6: Signup success wait completed. Preparing to continue with the OAuth link and login.', 'ok'); await completeNodeFromBackground('wait-registration-success'); } diff --git a/flows/openai/content/auth-page-recovery.js b/flows/openai/content/auth-page-recovery.js index 81d54c0a..0567ca31 100644 --- a/flows/openai/content/auth-page-recovery.js +++ b/flows/openai/content/auth-page-recovery.js @@ -158,13 +158,13 @@ if (retryState.maxCheckAttemptsBlocked) { throw new Error( - 'CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器' + 'CF_SECURITY_BLOCKED::Cloudflare security protection was triggered. The flow has been fully stopped. Do not resend verification codes repeatedly in a short time. Continuous refreshes and repeated retry clicks will worsen the risk control state. Close the page and wait 15-30 minutes for the temporary restriction to clear automatically, or switch browsers.' ); } if (retryState.userAlreadyExistsBlocked) { throw new Error( - 'SIGNUP_USER_ALREADY_EXISTS::步骤 4:检测到 user_already_exists,说明当前用户已存在,当前轮将直接停止。' + 'SIGNUP_USER_ALREADY_EXISTS::Step 4: Detected user_already_exists, which means the current user already exists. This round will stop immediately.' ); } @@ -172,8 +172,8 @@ idlePollCount = 0; clickCount += 1; if (typeof log === 'function') { - const prefix = logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`; - log(`${prefix}(第 ${clickCount} 次)...`, 'warn'); + const prefix = logLabel || `Step ${step || '?'}: Retry page detected. Clicking "Try again" to recover`; + log(`${prefix} (attempt ${clickCount})...`, 'warn'); } if (typeof humanPause === 'function') { await humanPause(300, 800); @@ -199,7 +199,7 @@ idlePollCount += 1; if (idlePollCount >= maxIdlePolls) { throw new Error( - `${logLabel || `步骤 ${step || '?'}:重试页恢复`}超时:重试按钮长时间不可点击。URL: ${location.href}` + `${logLabel || `Step ${step || '?'}: Retry page recovery`} timed out: the retry button stayed unclickable for too long. URL: ${location.href}` ); } @@ -217,18 +217,18 @@ if (finalRetryState.maxCheckAttemptsBlocked) { throw new Error( - 'CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器' + 'CF_SECURITY_BLOCKED::Cloudflare security protection was triggered. The flow has been fully stopped. Do not resend verification codes repeatedly in a short time. Continuous refreshes and repeated retry clicks will worsen the risk control state. Close the page and wait 15-30 minutes for the temporary restriction to clear automatically, or switch browsers.' ); } if (finalRetryState.userAlreadyExistsBlocked) { throw new Error( - 'SIGNUP_USER_ALREADY_EXISTS::步骤 4:检测到 user_already_exists,说明当前用户已存在,当前轮将直接停止。' + 'SIGNUP_USER_ALREADY_EXISTS::Step 4: Detected user_already_exists, which means the current user already exists. This round will stop immediately.' ); } throw new Error( - `${logLabel || `步骤 ${step || '?'}:重试页恢复`}失败:已连续点击“重试” ${maxClickAttempts} 次,页面仍未恢复。URL: ${location.href}` + `${logLabel || `Step ${step || '?'}: Retry page recovery`} failed: clicked "Try again" repeatedly ${maxClickAttempts} times and the page still did not recover. URL: ${location.href}` ); } diff --git a/flows/openai/content/gopay-flow.js b/flows/openai/content/gopay-flow.js index 2edde4b4..c1885317 100644 --- a/flows/openai/content/gopay-flow.js +++ b/flows/openai/content/gopay-flow.js @@ -32,7 +32,7 @@ if (document.documentElement.getAttribute(GOPAY_FLOW_LISTENER_SENTINEL) !== '1') } }); } else { - console.log('[MultiPage:gopay-flow] 消息监听已存在,跳过重复注册'); + console.log('[MultiPage:gopay-flow] message listener already exists, skipping duplicate registration'); } async function performGoPayOperationWithDelay(metadata, operation) { @@ -60,7 +60,7 @@ async function handleGoPayCommand(message) { case 'GOPAY_GET_PAY_NOW_TARGET': return getGoPayPayNowTarget(); default: - throw new Error(`gopay-flow.js 不处理消息:${message.type}`); + throw new Error(`gopay-flow.js does not handle message: ${message.type}`); } } @@ -75,7 +75,7 @@ async function waitUntil(predicate, options = {}) { return value; } if (timeoutMs > 0 && Date.now() - startedAt >= timeoutMs) { - throw new Error(options.timeoutMessage || `${options.label || 'GoPay 页面状态'}等待超时`); + throw new Error(options.timeoutMessage || `${options.label || 'GoPay page state'} timed out`); } await sleep(intervalMs); } @@ -97,7 +97,7 @@ async function waitForDocumentComplete(options = {}) { label: 'GoPay DOM', }); } catch (_) { - // GoPay linking 页面有时长时间保持 loading;后续定位控件本身还有等待/重试。 + // GoPay linking page may stay loading for a long time; later control lookup still includes its own waiting/retry logic. } await sleep(settleMs); } @@ -230,7 +230,7 @@ function detectGoPayTerminalError(text = getPageBodyText()) { if (/waktunya\s+habis|ulang(?:i)?\s+prosesnya\s+dari\s+awal|time(?:'s|\s+is)?\s+(?:out|expired)|session\s+expired|expired|kedaluwarsa/i.test(normalizedText)) { return { code: 'expired', - message: 'GoPay 支付会话已超时,需要重新创建 Plus Checkout。', + message: 'GoPay payment session timed out. Recreate Plus Checkout.', rawText: normalizedText.slice(0, 240), }; } @@ -238,7 +238,7 @@ function detectGoPayTerminalError(text = getPageBodyText()) { if (/technical\s+error|don[’']t\s+worry|try\s+again|terjadi\s+kesalahan|error\s+teknis/i.test(normalizedText)) { return { code: 'technical-error', - message: 'GoPay 页面显示技术错误,需要重新发起支付授权。', + message: 'GoPay page shows a technical error. Payment authorization must be started again.', rawText: normalizedText.slice(0, 240), }; } @@ -246,7 +246,7 @@ function detectGoPayTerminalError(text = getPageBodyText()) { if (/payment\s+failed|pembayaran\s+gagal|transaksi\s+gagal|ditolak|declined|failed/i.test(normalizedText)) { return { code: 'failed', - message: 'GoPay 页面显示支付失败,需要重新发起支付授权。', + message: 'GoPay page shows payment failure. Payment authorization must be started again.', rawText: normalizedText.slice(0, 240), }; } @@ -371,7 +371,7 @@ function dispatchPointerMouseSequence(target) { async function humanClickElement(el, options = {}) { if (!el) { - throw new Error('GoPay 页面未找到可点击元素。'); + throw new Error('No clickable element was found on the GoPay page.'); } el.scrollIntoView?.({ block: 'center', inline: 'center' }); await sleep(Math.max(0, Number(options.beforeMs) || 120)); @@ -519,7 +519,7 @@ async function ensureGoPayCountryCode(countryCode = '+86') { const toggle = findCountryCodeToggle(); if (!toggle) { - throw new Error(`GoPay 页面未找到国家区号切换控件,当前识别区号:${selected || '未知'},目标区号:${normalized}`); + throw new Error(`GoPay page did not find the country code switcher. Current detected code: ${selected || 'Unknown'}, target code: ${normalized}`); } robustClick(toggle); await sleep(500); @@ -537,7 +537,7 @@ async function ensureGoPayCountryCode(countryCode = '+86') { } const option = await waitUntil(() => findCountryCodeOption(normalized), { - label: `GoPay 国家区号 ${normalized}`, + label: `GoPay country code ${normalized}`, intervalMs: 250, timeoutMs: 8000, }); @@ -549,7 +549,7 @@ async function ensureGoPayCountryCode(countryCode = '+86') { countryDropdown.style.display = 'none'; } if (nextSelected !== normalized) { - throw new Error(`GoPay 国家区号切换失败:目标 ${normalized},当前 ${nextSelected || '未知'}`); + throw new Error(`Failed to switch GoPay country code: target ${normalized}, current ${nextSelected || 'Unknown'}`); } return { changed: true, @@ -639,10 +639,10 @@ async function submitGoPayPhone(payload = {}) { const countryCode = normalizeGoPayCountryCode(payload.countryCode || payload.gopayCountryCode || '+86'); const phone = normalizeGoPayNationalPhone(payload.phone || payload.gopayPhone || '', countryCode); if (!phone) { - throw new Error('GoPay 手机号为空,请先在侧边栏配置。'); + throw new Error('GoPay phone number is empty. Configure it in the side panel first.'); } const input = await waitUntil(() => findPhoneInput(), { - label: 'GoPay 手机号输入框', + label: 'GoPay phone number input', intervalMs: 250, timeoutMs: 15000, }); @@ -676,11 +676,11 @@ async function submitGoPayOtp(payload = {}) { await waitForDocumentComplete(); const code = normalizeOtp(payload.code || payload.otp || ''); if (!code) { - throw new Error('GoPay WhatsApp 验证码为空。'); + throw new Error('GoPay WhatsApp verification code is empty.'); } const { filled, clickResult } = await delayOperation({ stepKey: 'gopay-approve', kind: 'submit', label: 'submit-otp' }, async () => { const filledOtp = await waitUntil(() => fillVisibleOtpInputs(code), { - label: 'GoPay 验证码输入框', + label: 'GoPay verification code input', intervalMs: 250, timeoutMs: 15000, }); @@ -706,11 +706,11 @@ async function submitGoPayPin(payload = {}) { await waitForDocumentComplete(); const pin = normalizeOtp(payload.pin || payload.gopayPin || ''); if (!pin) { - throw new Error('GoPay PIN 为空,请先在侧边栏配置。'); + throw new Error('GoPay PIN is empty. Configure it in the side panel first.'); } const { filled, clickResult } = await delayOperation({ stepKey: 'gopay-approve', kind: 'submit', label: 'submit-pin' }, async () => { const filledPin = await waitUntil(() => fillVisiblePinInputs(pin), { - label: 'GoPay PIN 输入框', + label: 'GoPay PIN input', intervalMs: 250, timeoutMs: 15000, }); diff --git a/flows/openai/content/openai-auth.js b/flows/openai/content/openai-auth.js index cde7b69b..b31c3925 100644 --- a/flows/openai/content/openai-auth.js +++ b/flows/openai/content/openai-auth.js @@ -49,7 +49,7 @@ if (document.documentElement.getAttribute(OPENAI_AUTH_LISTENER_SENTINEL) !== '1' const reportedNodeId = resolveCommandNodeId(message); if (isStopError(err)) { if (reportedStep) { - log(`步骤 ${reportedStep || 8}:已被用户停止。`, 'warn'); + log(`Step ${reportedStep || 8}: stopped by the user.`, 'warn'); } sendResponse({ stopped: true, error: err.message }); return; @@ -70,7 +70,7 @@ if (document.documentElement.getAttribute(OPENAI_AUTH_LISTENER_SENTINEL) !== '1' } }); } else { - console.log('[MultiPage:openai-auth] 消息监听已存在,跳过重复注册'); + console.log('[MultiPage:openai-auth] message listener already exists, skipping duplicate registration'); } const OPENAI_AUTH_NODE_HANDLERS = Object.freeze({ @@ -122,7 +122,7 @@ async function handleCommand(message) { const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim(); const handler = OPENAI_AUTH_NODE_HANDLERS[nodeId]; if (!handler) { - throw new Error(`openai-auth.js 不处理节点 ${nodeId}`); + throw new Error(`openai-auth.js does not handle node ${nodeId}`); } return await handler(message.payload || {}); } @@ -334,7 +334,7 @@ function buildContactVerificationServerError(errorText = '') { ? PHONE_RESEND_SERVER_ERROR_PREFIX : 'PHONE_RESEND_SERVER_ERROR::'; const resolvedText = String(errorText || '').trim() - || 'OpenAI contact-verification 页面返回 HTTP ERROR 500。'; + || 'OpenAI contact-verification page returned HTTP ERROR 500.'; return new Error( resolvedText.startsWith(serverErrorPrefix) ? resolvedText @@ -380,7 +380,7 @@ async function resendVerificationCode(step, timeout = 45000) { action = findResendVerificationCodeTrigger({ allowDisabled: true }); if (action && isActionEnabled(action)) { - log(`步骤 ${step}:重新发送验证码按钮已可用。`); + log(`Step ${step}: the resend code button is now available.`); await humanPause(350, 900); await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'click', label: 'resend-verification-code' }, async () => { simulateClick(action); @@ -389,7 +389,7 @@ async function resendVerificationCode(step, timeout = 45000) { // After clicking resend, check if 405 error appeared if (is405MethodNotAllowedPage()) { - log(`步骤 ${step}:点击重新发送后出现 405 错误,正在恢复...`, 'warn'); + log(`Step ${step}: clicking resend returned a 405 error, recovering...`, 'warn'); await handle405ResendError(step, timeout - (Date.now() - start)); loggedWaiting = false; continue; @@ -404,14 +404,14 @@ async function resendVerificationCode(step, timeout = 45000) { if (action && !loggedWaiting) { loggedWaiting = true; - log(`步骤 ${step}:正在等待重新发送验证码按钮变为可点击...`); + log(`Step ${step}: waiting for the resend code button to become clickable...`); } await sleep(250); } throwIfContactVerificationServerError(); - throw new Error('无法点击重新发送验证码按钮。URL: ' + location.href); + throw new Error('Unable to click the resend code button. URL: ' + location.href); } function is405MethodNotAllowedPage() { @@ -491,7 +491,7 @@ function clearStep405RecoveryCount(step) { function createStep405RecoveryLimitError(step, count) { const normalizedStep = Number(step) || step || '?'; const limit = getStep405RecoveryLimit(normalizedStep) || count; - const message = `步骤 ${normalizedStep}:检测到 405 错误页面,已连续点击“重试”恢复 ${count}/${limit} 次仍未恢复,当前轮将结束并进入下一轮。URL: ${location.href}`; + const message = `Step ${normalizedStep}: detected a 405 error page, clicked "Retry" ${count}/${limit} times in a row and still did not recover; this round will end and the next round will begin. URL: ${location.href}`; return new Error(`${getStep405RecoveryErrorPrefix(normalizedStep)}${message}`); } @@ -506,8 +506,8 @@ async function handle405ResendError(step, remainingTimeout = 30000) { const maxClickAttempts = Number(step) === 4 ? 1 : 5; await recoverCurrentAuthRetryPage({ logLabel: Number(step) === 4 - ? `步骤 ${step}:检测到 405 错误页面,正在点击“重试”恢复(总计 ${nextCount}/${getStep405RecoveryLimit(step)})` - : `步骤 ${step}:检测到 405 错误页面,正在点击“重试”恢复`, + ? `Step ${step}: detected a 405 error page, clicking "Retry" to recover (total ${nextCount}/${getStep405RecoveryLimit(step)})` + : `Step ${step}: detected a 405 error page, clicking "Retry" to recover`, maxClickAttempts, pathPatterns: [], step, @@ -517,7 +517,7 @@ async function handle405ResendError(step, remainingTimeout = 30000) { throw createStep405RecoveryLimitError(step, nextCount); } if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); - log(`步骤 ${step}:405 错误已恢复,页面已返回验证码页面。`); + log(`Step ${step}: 405 error recovered, page returned to the verification code page.`); } // ============================================================ @@ -1096,7 +1096,7 @@ function getSignupPasswordDiagnostics() { function logSignupPasswordDiagnostics(context, level = 'warn') { try { - log(`${context}:密码页诊断快照:${JSON.stringify(getSignupPasswordDiagnostics())}`, level); + log(`${context}: password page diagnostic snapshot: ${JSON.stringify(getSignupPasswordDiagnostics())}`, level); } catch (error) { console.warn('[MultiPage:openai-auth] failed to build signup password diagnostics:', error?.message || error); } @@ -1132,7 +1132,7 @@ async function waitForSignupEntryState(options = {}) { if (logDiagnostics && snapshot.state !== lastState) { lastState = snapshot.state; - log(`步骤 ${step}:注册入口状态切换为 ${snapshot.state},状态快照:${JSON.stringify(getSignupEntryStateSummary(snapshot))}`); + log(`Step ${step}: sign-up entry state changed to ${snapshot.state}, state snapshot: ${JSON.stringify(getSignupEntryStateSummary(snapshot))}`); } if (snapshot.state === 'password_page' || snapshot.state === 'email_entry') { @@ -1148,21 +1148,21 @@ async function waitForSignupEntryState(options = {}) { lastSwitchToEmailAt = Date.now(); loggedMissingSwitchToEmail = false; if (logDiagnostics) { - log(`步骤 ${step}:检测到手机号输入模式,准备点击切换邮箱入口:"${getActionText(snapshot.switchToEmailTrigger).slice(0, 80)}"`); + log(`Step ${step}: detected phone-number input mode, preparing to click the switch-to-email entry: "${getActionText(snapshot.switchToEmailTrigger).slice(0, 80)}"`); } - log('步骤 2:检测到手机号输入模式,正在切换到邮箱输入模式...'); + log('Step 2: detected phone-number input mode, switching to email input mode...'); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-entry', kind: 'click', label: 'switch-to-signup-email' }, async () => { simulateClick(snapshot.switchToEmailTrigger); }); } else if (!snapshot.switchToEmailTrigger && !loggedMissingSwitchToEmail) { loggedMissingSwitchToEmail = true; - log('步骤 2:检测到手机号输入模式,但暂未识别到“改用邮箱/继续使用电子邮件地址登录”按钮,继续等待界面稳定...', 'warn'); + log('Step 2: detected phone-number input mode, but the "switch to email / continue with email address" button has not been identified yet; keep waiting for the UI to settle...', 'warn'); } if (logDiagnostics && !slowSnapshotLogged && Date.now() - start >= 5000) { slowSnapshotLogged = true; - log(`步骤 ${step}:等待手机号入口切换超过 5 秒,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); + log(`Step ${step}: waiting for the phone-entry switch exceeded 5 seconds, page diagnostics snapshot: ${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } await sleep(250); @@ -1176,23 +1176,23 @@ async function waitForSignupEntryState(options = {}) { if (Date.now() - lastTriggerClickAt >= 1500) { if (clickAttempts >= maxSignupEntryClickAttempts) { - log(`步骤 ${step}:官网注册入口已完成 ${maxSignupEntryClickRetries} 次重试,页面仍未进入邮箱输入页,停止重试。`, 'warn'); + log(`Step ${step}: the official sign-up entry has already been retried ${maxSignupEntryClickRetries} times, but the page still has not reached the email input page; stopping retries.`, 'warn'); return snapshot; } lastTriggerClickAt = Date.now(); clickAttempts += 1; const retryAttempt = clickAttempts - 1; if (logDiagnostics) { - log(`步骤 ${step}:正在点击官网注册入口(第 ${clickAttempts}/${maxSignupEntryClickAttempts} 次):"${getActionText(snapshot.signupTrigger).slice(0, 80)}"`); + log(`Step ${step}: clicking the official sign-up entry (attempt ${clickAttempts}/${maxSignupEntryClickAttempts}): "${getActionText(snapshot.signupTrigger).slice(0, 80)}"`); } log(retryAttempt > 0 - ? `步骤 ${step}:上次点击后仍未进入邮箱输入页,等待 3 秒后重试点击官网注册入口(重试 ${retryAttempt}/${maxSignupEntryClickRetries})...` - : `步骤 ${step}:已找到官网注册入口,等待 3 秒后点击...`); + ? `Step ${step}: still not on the email input page after the last click, waiting 3 seconds before retrying the official sign-up entry click (retry ${retryAttempt}/${maxSignupEntryClickRetries})...` + : `Step ${step}: found the official sign-up entry, waiting 3 seconds before clicking...`); await sleep(3000); throwIfStopped(); const clickTarget = findSignupEntryTrigger({ allowHiddenFallback: false }) || snapshot.signupTrigger; if (!isVisibleElement(clickTarget)) { - log(`步骤 ${step}:注册入口仍处于不可见状态,继续按入口重试节奏尝试恢复点击...`, 'warn'); + log(`Step ${step}: the sign-up entry is still invisible, continuing to recover the click according to the retry rhythm...`, 'warn'); } await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-entry', kind: 'click', label: 'open-signup-entry' }, async () => { @@ -1203,7 +1203,7 @@ async function waitForSignupEntryState(options = {}) { if (logDiagnostics && !slowSnapshotLogged && Date.now() - start >= 5000) { slowSnapshotLogged = true; - log(`步骤 ${step}:等待注册入口超过 5 秒,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); + log(`Step ${step}: waiting for the sign-up entry exceeded 5 seconds, page diagnostics snapshot: ${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } await sleep(250); @@ -1211,7 +1211,7 @@ async function waitForSignupEntryState(options = {}) { const finalSnapshot = inspectSignupEntryState(); if (logDiagnostics) { - log(`步骤 ${step}:等待注册入口状态超时,最终状态快照:${JSON.stringify(getSignupEntryStateSummary(finalSnapshot))}`, 'warn'); + log(`Step ${step}: waiting for the sign-up entry state timed out, final state snapshot: ${JSON.stringify(getSignupEntryStateSummary(finalSnapshot))}`, 'warn'); } return finalSnapshot; } @@ -1226,8 +1226,8 @@ async function ensureSignupEntryReady(timeout = 15000) { }; } - log(`注册入口识别失败,诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); - throw new Error('当前页面没有可用的注册入口,也不在邮箱/密码页。URL: ' + location.href); + log(`Sign-up entry identification failed, diagnostics snapshot: ${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); + throw new Error('The current page has no available sign-up entry, and it is not on the email/password page. URL: ' + location.href); } async function ensureSignupPhoneEntryReady(timeout = 25000) { @@ -1243,8 +1243,8 @@ async function ensureSignupPhoneEntryReady(timeout = 25000) { }; } - log(`手机号注册入口识别失败,诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); - throw new Error('当前页面没有可用的手机号注册入口,也不在密码页。URL: ' + location.href); + log(`Phone-number sign-up entry identification failed, diagnostics snapshot: ${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); + throw new Error('The current page has no available phone-number sign-up entry, and it is not on the password page. URL: ' + location.href); } async function ensureSignupPasswordPageReady(timeout = 20000) { @@ -1263,7 +1263,7 @@ async function ensureSignupPasswordPageReady(timeout = 20000) { await sleep(200); } - throw new Error('等待进入密码页超时。URL: ' + location.href); + throw new Error('Timed out waiting to enter the password page. URL: ' + location.href); } async function fillSignupEmailAndContinue(email, step) { @@ -1274,7 +1274,7 @@ async function fillSignupEmailAndContinue(email, step) { const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; - if (!email) throw new Error(`未提供邮箱地址,步骤 ${step} 无法继续。`); + if (!email) throw new Error(`No email address provided; step ${step} cannot continue.`); const normalizedEmail = String(email || '').trim().toLowerCase(); const snapshot = await waitForSignupEntryState({ @@ -1286,9 +1286,9 @@ async function fillSignupEmailAndContinue(email, step) { if (snapshot.state === 'password_page') { if (snapshot.displayedEmail && snapshot.displayedEmail !== normalizedEmail) { - throw new Error(`步骤 ${step}:当前密码页邮箱为 ${snapshot.displayedEmail},与目标邮箱 ${email} 不一致,请先回到步骤 1 重新开始。`); + throw new Error(`Step ${step}: the email on the current password page is ${snapshot.displayedEmail}, which does not match the target email ${email}; please go back to step 1 and start over.`); } - log(`步骤 ${step}:当前已在密码页,无需重复提交邮箱。`); + log(`Step ${step}: already on the password page, no need to resubmit the email.`); return { alreadyOnPasswordPage: true, url: snapshot.url || location.href, @@ -1297,24 +1297,24 @@ async function fillSignupEmailAndContinue(email, step) { if (snapshot.state !== 'email_entry' || !snapshot.emailInput) { if (step === 2) { - log(`步骤 ${step}:未进入邮箱输入页,最终页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); + log(`Step ${step}: did not enter the email input page, final page diagnostics snapshot: ${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } - throw new Error(`步骤 ${step}:未找到可用的邮箱输入入口。URL: ${location.href}`); + throw new Error(`Step ${step}: no available email input entry was found. URL: ${location.href}`); } - log(`步骤 ${step}:正在填写邮箱:${email}`); + log(`Step ${step}: filling email: ${email}`); await humanPause(500, 1400); await performOperationWithDelay({ stepKey: step === 2 ? 'signup-entry' : 'fill-password', kind: 'fill', label: 'signup-email' }, async () => { fillInput(snapshot.emailInput, email); }); - log(`步骤 ${step}:邮箱已填写`); + log(`Step ${step}: email filled.`); const continueButton = snapshot.continueButton || getSignupEmailContinueButton({ allowDisabled: true }); if (!continueButton || !isActionEnabled(continueButton)) { - throw new Error(`步骤 ${step}:未找到可点击的“继续”按钮。URL: ${location.href}`); + throw new Error(`Step ${step}: no clickable "Continue" button was found. URL: ${location.href}`); } - log(`步骤 ${step}:邮箱已准备提交,正在前往密码页...`); + log(`Step ${step}: email is ready to submit, moving to the password page...`); window.setTimeout(async () => { try { throwIfStopped(); @@ -2452,7 +2452,7 @@ async function waitForSignupPhoneEntryState(options = {}) { const switchToPhone = snapshot.switchToPhoneTrigger || findSignupUsePhoneTrigger(); if (switchToPhone && Date.now() - lastSwitchToPhoneAt >= 1500) { lastSwitchToPhoneAt = Date.now(); - log(`步骤 ${step}:检测到邮箱输入模式,正在切换到手机号注册入口...`); + log(`Step ${step}: detected email input mode, switching to the phone-number sign-up entry...`); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'switch-to-signup-phone' }, async () => { simulateClick(switchToPhone); @@ -2461,14 +2461,14 @@ async function waitForSignupPhoneEntryState(options = {}) { const moreOptionsTrigger = findSignupMoreOptionsTrigger(); if (moreOptionsTrigger && Date.now() - lastMoreOptionsClickAt >= 1500) { lastMoreOptionsClickAt = Date.now(); - log(`步骤 ${step}:手机号入口可能隐藏在更多选项中,正在展开更多选项...`); + log(`Step ${step}: the phone-number entry may be hidden under More options, expanding them now...`); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'signup-phone-more-options' }, async () => { simulateClick(moreOptionsTrigger); }); } else if (!switchToPhone && !slowSnapshotLogged && Date.now() - start >= 5000) { slowSnapshotLogged = true; - log(`步骤 ${step}:尚未找到手机号入口,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); + log(`Step ${step}: phone-number entry not found yet, page diagnostics snapshot: ${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } } await sleep(250); @@ -2478,20 +2478,20 @@ async function waitForSignupPhoneEntryState(options = {}) { if (snapshot.state === 'entry_home' && snapshot.signupTrigger) { if (Date.now() - lastTriggerClickAt >= 1500) { if (clickAttempts >= maxSignupEntryClickAttempts) { - log(`步骤 ${step}:官网注册入口已完成 ${maxSignupEntryClickRetries} 次重试,页面仍未进入手机号输入页,停止重试。`, 'warn'); + log(`Step ${step}: the official sign-up entry has already been retried ${maxSignupEntryClickRetries} times, but the page still has not reached the phone-number input page; stopping retries.`, 'warn'); return snapshot; } lastTriggerClickAt = Date.now(); clickAttempts += 1; const retryAttempt = clickAttempts - 1; log(retryAttempt > 0 - ? `步骤 ${step}:上次点击后仍未进入手机号输入页,等待 3 秒后重试点击官网注册入口(重试 ${retryAttempt}/${maxSignupEntryClickRetries})...` - : `步骤 ${step}:已找到官网注册入口,等待 3 秒后点击...`); + ? `Step ${step}: still not on the phone-number input page after the last click, waiting 3 seconds before retrying the official sign-up entry click (retry ${retryAttempt}/${maxSignupEntryClickRetries})...` + : `Step ${step}: found the official sign-up entry, waiting 3 seconds before clicking...`); await sleep(3000); throwIfStopped(); const clickTarget = findSignupEntryTrigger({ allowHiddenFallback: false }) || snapshot.signupTrigger; if (!isVisibleElement(clickTarget)) { - log(`步骤 ${step}:注册入口仍处于不可见状态,继续按入口重试节奏尝试恢复点击...`, 'warn'); + log(`Step ${step}: the sign-up entry is still invisible, continuing to recover the click according to the retry rhythm...`, 'warn'); } await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'open-signup-entry' }, async () => { @@ -2504,14 +2504,14 @@ async function waitForSignupPhoneEntryState(options = {}) { if (!slowSnapshotLogged && Date.now() - start >= 5000) { slowSnapshotLogged = true; - log(`步骤 ${step}:等待手机号注册入口超过 5 秒,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); + log(`Step ${step}: waiting for the phone-number sign-up entry exceeded 5 seconds, page diagnostics snapshot: ${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } await sleep(250); } const finalSnapshot = inspectSignupEntryState(); - log(`步骤 ${step}:等待手机号注册入口超时,最终状态快照:${JSON.stringify(getSignupEntryStateSummary(finalSnapshot))}`, 'warn'); + log(`Step ${step}: waiting for the phone-number sign-up entry timed out, final state snapshot: ${JSON.stringify(getSignupEntryStateSummary(finalSnapshot))}`, 'warn'); return finalSnapshot; } @@ -2526,12 +2526,12 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) { const phoneNumber = String(payload.phoneNumber || '').trim(); const countryLabel = String(payload.countryLabel || '').trim(); if (!phoneNumber) { - throw new Error('未提供手机号,步骤 2 无法继续。'); + throw new Error('No phone number provided; step 2 cannot continue.'); } const snapshot = await waitForSignupPhoneEntryState({ timeout: 25000, step: 2 }); if (snapshot.state === 'password_page') { - log('步骤 2:当前已在密码页,无需重复提交手机号。'); + log('Step 2: already on the password page, no need to resubmit the phone number.'); return { alreadyOnPasswordPage: true, url: snapshot.url || location.href, @@ -2539,7 +2539,7 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) { } if (snapshot.state !== 'phone_entry' || !snapshot.phoneInput) { - throw new Error(`步骤 2:未找到可用的手机号输入入口。URL: ${location.href}`); + throw new Error(`Step 2: no available phone-number input entry was found. URL: ${location.href}`); } const countrySelection = await ensureSignupPhoneCountrySelected(snapshot.phoneInput, { @@ -2547,12 +2547,12 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) { phoneNumber, }); if (countrySelection.hasCountryControl && !countrySelection.matched) { - const currentCountryText = getSignupPhoneCountryButtonText(snapshot.phoneInput) || '未知'; + const currentCountryText = getSignupPhoneCountryButtonText(snapshot.phoneInput) || 'unknown'; const targetDialCode = resolveSignupPhoneTargetDialCode({ countryLabel, phoneNumber }, countrySelection.selectedOption); const targetLabel = targetDialCode - ? `目标区号 +${targetDialCode}(号码 ${phoneNumber}${countryLabel ? `,国家 ${countryLabel}` : ''})` + ? `Target country code +${targetDialCode} (number ${phoneNumber}${countryLabel ? `, country ${countryLabel}` : ''})` : (countryLabel || phoneNumber); - throw new Error(`步骤 2:手机号国家下拉框未能自动切换到 ${targetLabel},当前显示为 ${currentCountryText},已停止提交以避免区号不匹配。`); + throw new Error(`Step 2: the phone-number country dropdown could not switch to ${targetLabel} automatically; it currently shows ${currentCountryText}, so submission was stopped to avoid a dial-code mismatch.`); } const dialCode = resolveSignupPhoneDialCode(snapshot.phoneInput, { @@ -2562,10 +2562,10 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) { }); const inputValue = toNationalPhoneNumber(phoneNumber, dialCode); if (!inputValue) { - throw new Error('步骤 2:手机号为空,无法填写。'); + throw new Error('Step 2: phone number is empty and cannot be filled.'); } - log(`步骤 2:正在填写手机号:${phoneNumber}`); + log(`Step 2: filling phone number: ${phoneNumber}`); await humanPause(500, 1400); await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'fill', label: 'signup-phone-number' }, async () => { fillInput(snapshot.phoneInput, inputValue); @@ -2577,14 +2577,14 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) { fillInput(hiddenPhoneNumberInput, e164PhoneNumber); }); } - log(`步骤 2:手机号已填写:${phoneNumber}${dialCode ? `(区号 +${dialCode},本地号 ${inputValue})` : ''}`); + log(`Step 2: phone number filled: ${phoneNumber}${dialCode ? ` (dial code +${dialCode}, local number ${inputValue})` : ''}`); const continueButton = getSignupEmailContinueButton({ allowDisabled: true }); if (!continueButton || !isActionEnabled(continueButton)) { - throw new Error(`步骤 2:未找到可点击的“继续”按钮。URL: ${location.href}`); + throw new Error(`Step 2: no clickable "Continue" button was found. URL: ${location.href}`); } - log('步骤 2:手机号已准备提交,正在前往下一页...'); + log('Step 2: phone number is ready to submit, moving to the next page...'); window.setTimeout(async () => { try { throwIfStopped(); @@ -2632,7 +2632,7 @@ async function step3_fillEmailPassword(payload) { return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const { email, password } = payload; - if (!password) throw new Error('未提供密码,步骤 3 需要可用密码。'); + if (!password) throw new Error('No password provided; step 3 requires a usable password.'); const normalizedEmail = String(email || '').trim().toLowerCase(); const accountIdentifierType = String(payload?.accountIdentifierType || '').trim().toLowerCase() === 'phone' ? 'phone' @@ -2641,7 +2641,7 @@ async function step3_fillEmailPassword(payload) { let snapshot = inspectSignupEntryState(); if (snapshot.state === 'entry_home') { - throw new Error('当前仍停留在 ChatGPT 官网首页,请先完成步骤 2。'); + throw new Error('Still on the ChatGPT home page; please complete step 2 first.'); } if ( @@ -2663,7 +2663,7 @@ async function step3_fillEmailPassword(payload) { deferredSubmit: false, ...(snapshot.skipProfileStep ? { skipProfileStep: true } : {}), }; - log('步骤 3:当前页面已进入验证码或后续阶段,密码页按已跳过处理。', 'warn'); + log('Step 3: the current page has already entered the verification-code or later stage, so the password page is treated as skipped.', 'warn'); reportComplete(3, completionPayload); return completionPayload; } @@ -2683,30 +2683,30 @@ async function step3_fillEmailPassword(payload) { } if (snapshot.state !== 'password_page' || !snapshot.passwordInput) { - logSignupPasswordDiagnostics('步骤 3:未能识别可填写的密码输入框'); + logSignupPasswordDiagnostics('Step 3: could not identify a password input box to fill'); } if (snapshot.state !== 'password_page' || !snapshot.passwordInput) { - throw new Error('在密码页未找到密码输入框。URL: ' + location.href); + throw new Error('No password input box was found on the password page. URL: ' + location.href); } if (normalizedEmail && snapshot.displayedEmail && snapshot.displayedEmail !== normalizedEmail) { - throw new Error(`当前密码页邮箱为 ${snapshot.displayedEmail},与目标邮箱 ${email} 不一致,请先回到步骤 1 重新开始。`); + throw new Error(`The email on the current password page is ${snapshot.displayedEmail}, which does not match the target email ${email}; please go back to step 1 and start over.`); } await humanPause(600, 1500); await performOperationWithDelay({ stepKey: 'fill-password', kind: 'fill', label: 'signup-password' }, async () => { fillInput(snapshot.passwordInput, password); }); - log('步骤 3:密码已填写'); + log('Step 3: password filled.'); const submitBtn = snapshot.submitButton || getSignupPasswordSubmitButton({ allowDisabled: true }) || await waitForElementByText('button', /continue|sign\s*up|submit|注册|创建|続行|続ける|次へ|サインアップ|登録|作成|create/i, 5000).catch(() => null); if (!submitBtn) { - logSignupPasswordDiagnostics('步骤 3:未找到可提交的密码页按钮'); + logSignupPasswordDiagnostics('Step 3: no submit button was found on the password page'); } else if (typeof findOneTimeCodeLoginTrigger === 'function' && findOneTimeCodeLoginTrigger()) { - logSignupPasswordDiagnostics('步骤 3:当前密码页同时存在一次性验证码入口', 'info'); + logSignupPasswordDiagnostics('Step 3: the current password page also has a one-time-code login entry', 'info'); } const signupVerificationRequestedAt = submitBtn ? Date.now() : null; @@ -2730,7 +2730,7 @@ async function step3_fillEmailPassword(payload) { await performOperationWithDelay({ stepKey: 'fill-password', kind: 'submit', label: 'submit-signup-password' }, async () => { simulateClick(submitBtn); }); - log('步骤 3:表单已提交'); + log('Step 3: form submitted.'); } catch (error) { if (!isStopError(error)) { console.error('[MultiPage:openai-auth] deferred step 3 submit failed:', error?.message || error); @@ -2817,15 +2817,15 @@ function getVerificationErrorText() { function createSignupUserAlreadyExistsError() { return new Error( - `${SIGNUP_USER_ALREADY_EXISTS_ERROR_PREFIX}步骤 4:检测到 user_already_exists,说明当前用户已存在,当前轮将直接停止。` + `${SIGNUP_USER_ALREADY_EXISTS_ERROR_PREFIX}Step 4: detected user_already_exists, which means the current user already exists; this round will stop immediately.` ); } function createSignupPhonePasswordMismatchError(detailText = '') { const detail = String(detailText || '').replace(/\s+/g, ' ').trim(); - const suffix = detail ? `页面提示:${detail}` : '页面提示注册手机号不可继续使用,需重新开始当前轮。'; + const suffix = detail ? `Page message: ${detail}` : 'Page message: the registration phone number can no longer be used; this round must be restarted.'; return new Error( - `${SIGNUP_PHONE_PASSWORD_MISMATCH_ERROR_PREFIX}步骤 3:检测到注册手机号异常,需要重新开始当前轮。${suffix}` + `${SIGNUP_PHONE_PASSWORD_MISMATCH_ERROR_PREFIX}Step 3: detected an issue with the registration phone number; this round must be restarted. ${suffix}` ); } @@ -3185,7 +3185,7 @@ function isDocumentLoadComplete() { return getDocumentReadyStateSnapshot() === 'complete'; } -async function waitForDocumentLoadComplete(timeout = 15000, label = '页面') { +async function waitForDocumentLoadComplete(timeout = 15000, label = 'page') { const start = Date.now(); while (Date.now() - start < timeout) { @@ -3196,7 +3196,7 @@ async function waitForDocumentLoadComplete(timeout = 15000, label = '页面') { await sleep(150); } - throw new Error(`${label}长时间未完成加载,当前 readyState=${getDocumentReadyStateSnapshot()}。URL: ${location.href}`); + throw new Error(`${label} did not finish loading for a long time; current readyState=${getDocumentReadyStateSnapshot()}. URL: ${location.href}`); } function isSignupVerificationPageInteractiveReady(snapshot = null) { @@ -3301,7 +3301,7 @@ async function submitPhoneVerificationCodeWithProfileFallback(payload = {}) { const signupProfile = payload?.signupProfile || {}; if (!signupProfile.firstName || !signupProfile.lastName) { - throw new Error('手机号验证后进入资料页,但未提供步骤 5 所需的姓名数据。'); + throw new Error('Entered the profile page after phone verification, but no name data required for step 5 was provided.'); } await step5_fillNameBirthday(signupProfile); @@ -3410,13 +3410,13 @@ function findBirthdayReactAriaSelect(labelText) { async function setReactAriaBirthdaySelect(control, value) { if (!control?.nativeSelect) { - throw new Error('未找到可写入的生日下拉框。'); + throw new Error('No writable birthday dropdown was found.'); } const desiredValue = String(value); const option = Array.from(control.nativeSelect.options).find((item) => item.value === desiredValue); if (!option) { - throw new Error(`生日下拉框中不存在值 ${desiredValue}。`); + throw new Error(`The birthday dropdown does not contain value ${desiredValue}.`); } control.nativeSelect.value = desiredValue; @@ -3632,7 +3632,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) { } if (retryState.maxCheckAttemptsBlocked) { - throw new Error('CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器'); + throw new Error('CF_SECURITY_BLOCKED::You have triggered Cloudflare security protection and the flow has been fully stopped. Do not resend codes multiple times in a short period; repeated refreshes and retry clicks will increase risk controls. Close the page and wait 15-30 minutes for the temporary limit to lift automatically, or switch browsers.'); } if (retryState.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); @@ -3640,7 +3640,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) { if (retryState.retryButton && retryState.retryEnabled) { idlePollCount = 0; clickCount += 1; - log(`${logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`}(第 ${clickCount} 次)...`, 'warn'); + log(`${logLabel || `Step ${step || '?'}: detected a retry page, clicking "Retry" to recover`} (attempt ${clickCount})...`, 'warn'); await humanPause(300, 800); await performOperationWithDelay({ stepKey: step === 8 || flow === 'login' ? 'oauth-login' : 'fetch-signup-code', kind: 'click', label: 'auth-retry-click' }, async () => { simulateClick(retryState.retryButton); @@ -3662,7 +3662,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) { idlePollCount += 1; if (idlePollCount >= maxIdlePolls) { - throw new Error(`${logLabel || `步骤 ${step || '?'}:重试页恢复`}超时:重试按钮长时间不可点击。URL: ${location.href}`); + throw new Error(`${logLabel || `Step ${step || '?'}: retry page recovery`} timed out: the Retry button was not clickable for a long time. URL: ${location.href}`); } await sleep(250); @@ -3677,13 +3677,13 @@ async function recoverCurrentAuthRetryPage(payload = {}) { }; } if (finalRetryState.maxCheckAttemptsBlocked) { - throw new Error('CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器'); + throw new Error('CF_SECURITY_BLOCKED::You have triggered Cloudflare security protection and the flow has been fully stopped. Do not resend codes multiple times in a short period; repeated refreshes and retry clicks will increase risk controls. Close the page and wait 15-30 minutes for the temporary limit to lift automatically, or switch browsers.'); } if (finalRetryState.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } - throw new Error(`${logLabel || `步骤 ${step || '?'}:重试页恢复`}失败:已连续点击“重试” ${maxClickAttempts} 次,页面仍未恢复。URL: ${location.href}`); + throw new Error(`${logLabel || `Step ${step || '?'}: retry page recovery`} failed: clicked "Retry" ${maxClickAttempts} times in a row and the page still did not recover. URL: ${location.href}`); } function getSignupPasswordTimeoutErrorPageState() { @@ -4112,9 +4112,9 @@ async function selectCountryForPhoneInput(phoneInput, phoneNumber = '', countryL if (selection.hasCountryControl && targetDialCode) { if (!selection.matched || (displayedDialCode && displayedDialCode !== targetDialCode)) { - const currentCountryText = getSignupPhoneCountryButtonText(phoneInput) || displayedDialCode || '未知'; - const targetLabel = `目标区号 +${targetDialCode}(号码 ${phoneNumber}${countryLabel ? `,国家 ${countryLabel}` : ''})`; - throw new Error(`步骤 ${visibleStep}:手机号登录国家下拉框未能自动切换到 ${targetLabel},当前显示为 ${currentCountryText},已停止提交以避免区号不匹配。`); + const currentCountryText = getSignupPhoneCountryButtonText(phoneInput) || displayedDialCode || 'unknown'; + const targetLabel = `Target country code +${targetDialCode} (number ${phoneNumber}${countryLabel ? `, country ${countryLabel}` : ''})`; + throw new Error(`Step ${visibleStep}: the phone-login country dropdown could not switch to ${targetLabel} automatically; it currently shows ${currentCountryText}, so submission was stopped to avoid a dial-code mismatch.`); } return targetDialCode; } @@ -4337,27 +4337,27 @@ function getLoginAuthStateLabel(snapshot) { const state = snapshot?.state; switch (state) { case 'verification_page': - return '登录验证码页'; + return 'login verification code page'; case 'password_page': - return '密码页'; + return 'password page'; case 'email_page': - return '邮箱输入页'; + return 'email input page'; case 'phone_entry_page': - return '手机号输入页'; + return 'phone-number input page'; case 'phone_verification_page': - return '手机验证码页'; + return 'phone verification code page'; case 'login_timeout_error_page': - return '登录超时报错页'; + return 'login timeout error page'; case 'oauth_consent_page': - return 'OAuth 授权页'; + return 'OAuth consent page'; case 'entry_page': - return '登录入口页'; + return 'login entry page'; case 'add_phone_page': - return '手机号页'; + return 'phone-number page'; case 'add_email_page': - return '添加邮箱页'; + return 'add-email page'; default: - return '未知页面'; + return 'unknown page'; } } @@ -4399,7 +4399,7 @@ async function waitForLoginVerificationPageReady(timeout = 10000, visibleStep = } throw new Error( - `当前未进入登录验证码页面,请先重新完成步骤 ${Number(visibleStep) >= 11 ? 10 : 7}。当前状态:${getLoginAuthStateLabel(snapshot)}。URL: ${snapshot?.url || location.href}` + `The page has not entered the login verification code page yet; please rerun step ${Number(visibleStep) >= 11 ? 10 : 7}. Current state: ${getLoginAuthStateLabel(snapshot)}. URL: ${snapshot?.url || location.href}` ); } @@ -4467,19 +4467,19 @@ async function createStep6LoginTimeoutRecoveryTransition(reason, snapshot, messa try { const recoveryResult = await recoverCurrentAuthRetryPage({ flow: 'login', - logLabel: `步骤 ${visibleStep}:检测到登录超时报错,正在点击“重试”恢复当前页面`, + logLabel: `Step ${visibleStep}: detected a login timeout error page, clicking "Retry" to recover the current page`, step: visibleStep, timeoutMs: 12000, }); recovered = Boolean(recoveryResult?.recovered); if (recovered) { - log('登录超时报错页已点击“重试”,正在按恢复后的页面状态继续当前流程。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log('The login timeout error page has been clicked on "Retry"; continuing the current flow according to the recovered page state.', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); } } catch (error) { if (/CF_SECURITY_BLOCKED::/i.test(String(error?.message || error || ''))) { throw error; } - log(`登录超时报错页自动点击“重试”失败:${error.message}`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log(`Automatic "Retry" click on the login timeout error page failed: ${error.message}`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); } } @@ -4516,17 +4516,17 @@ async function createStep6LoginTimeoutRecoveryTransition(reason, snapshot, messa } if (resolvedSnapshot.state === 'password_page') { - log('登录超时报错页恢复后已进入密码页,继续当前登录流程。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log('After recovery from the login timeout error page, the flow entered the password page and will continue.', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return { action: 'password', snapshot: resolvedSnapshot }; } if (resolvedSnapshot.state === 'phone_entry_page') { - log('登录超时报错页恢复后已进入手机号输入页,继续当前登录流程。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log('After recovery from the login timeout error page, the flow entered the phone-number input page and will continue.', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return { action: 'phone', snapshot: resolvedSnapshot }; } if (resolvedSnapshot.state === 'email_page') { - log('登录超时报错页恢复后已回到邮箱输入页,继续当前登录流程。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log('After recovery from the login timeout error page, the flow returned to the email input page and will continue.', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return { action: 'email', snapshot: resolvedSnapshot }; } @@ -4553,7 +4553,7 @@ async function createStep6LoginTimeoutRecoverableResult(reason, snapshot, messag async function finalizeStep6VerificationReady(options = {}) { const { visibleStep = 7, - logLabel = `步骤 ${visibleStep} 收尾`, + logLabel = `Step ${visibleStep} wrap-up`, loginVerificationRequestedAt = null, timeout = 12000, via = 'verification_page_ready', @@ -4567,7 +4567,7 @@ async function finalizeStep6VerificationReady(options = {}) { while (Date.now() - start < timeout && round < maxRounds) { throwIfStopped(); round += 1; - log(`确认页面是否稳定停留在登录验证码阶段(第 ${round}/${maxRounds} 轮,先等待 3 秒)...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log(`Checking whether the page is stably staying on the login verification code stage (round ${round}/${maxRounds}, wait 3 seconds first)...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await sleep(settleDelayMs); const rawSnapshot = inspectLoginAuthState(); @@ -4575,7 +4575,7 @@ async function finalizeStep6VerificationReady(options = {}) { if (snapshot.state === 'verification_page' || (allowPhoneVerificationPage && snapshot.state === 'phone_verification_page')) { log( - snapshot.state === 'phone_verification_page' ? '登录手机验证码页面已稳定就绪。' : '登录验证码页面已稳定就绪。', + snapshot.state === 'phone_verification_page' ? 'The login phone verification code page is now stable and ready.' : 'The login verification code page is now stable and ready.', 'ok', { step: visibleStep, stepKey: 'oauth-login' } ); @@ -4586,84 +4586,84 @@ async function finalizeStep6VerificationReady(options = {}) { } if (snapshot.state === 'oauth_consent_page') { - log('认证页已直接进入 OAuth 授权页,跳过登录验证码步骤。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); + log('The auth page entered the OAuth consent page directly, so the login verification code step is skipped.', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6OAuthConsentSuccessResult(snapshot, { via: `${via}_oauth_consent`, }); } if (snapshot.state === 'add_email_page') { - log('认证页已进入添加邮箱页,登录阶段完成。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); + log('The auth page entered the add-email page, so the login stage is complete.', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6AddEmailSuccessResult(snapshot, { via: `${via}_add_email`, }); } if (snapshot.state === 'login_timeout_error_page') { - log(`页面进入登录超时报错页,准备自动恢复后重试步骤 ${visibleStep}。`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log(`The page entered the login timeout error page; preparing to recover automatically and retry step ${visibleStep}.`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6LoginTimeoutRecoverableResult( 'login_timeout_error_page', snapshot, - '登录验证码页面准备就绪前进入登录超时报错页。', + 'Entered the login timeout error page before the login verification code page was ready.', { visibleStep } ); } if (snapshot.state === 'password_page' || snapshot.state === 'email_page') { return createStep6RecoverableResult('verification_page_unstable', snapshot, { - message: `页面曾进入登录验证码阶段,但又回到了${getLoginAuthStateLabel(snapshot)},准备重新执行步骤 ${visibleStep}。`, + message: `The page had entered the login verification code stage, but then returned to ${getLoginAuthStateLabel(snapshot)}; preparing to run step ${visibleStep} again.`, loginVerificationRequestedAt, }); } if (snapshot.state === 'add_phone_page') { - throw new Error(`登录验证码页面准备过程中页面进入手机号页面。URL: ${snapshot.url}`); + throw new Error(`The page entered the phone-number page while preparing the login verification code page. URL: ${snapshot.url}`); } } const rawSnapshot = inspectLoginAuthState(); const snapshot = normalizeStep6Snapshot(rawSnapshot); if (snapshot.state === 'verification_page' || (allowPhoneVerificationPage && snapshot.state === 'phone_verification_page')) { - log( - snapshot.state === 'phone_verification_page' ? '登录手机验证码页面已稳定就绪。' : '登录验证码页面已稳定就绪。', - 'ok', - { step: visibleStep, stepKey: 'oauth-login' } - ); + log( + snapshot.state === 'phone_verification_page' ? 'The login phone verification code page is now stable and ready.' : 'The login verification code page is now stable and ready.', + 'ok', + { step: visibleStep, stepKey: 'oauth-login' } + ); return createStep6SuccessResult(snapshot, { via, loginVerificationRequestedAt, }); } if (snapshot.state === 'oauth_consent_page') { - log('认证页已直接进入 OAuth 授权页,跳过登录验证码步骤。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); + log('The auth page entered the OAuth consent page directly, so the login verification code step is skipped.', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6OAuthConsentSuccessResult(snapshot, { via: `${via}_oauth_consent`, }); } if (snapshot.state === 'add_email_page') { - log('认证页已进入添加邮箱页,登录阶段完成。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); + log('The auth page entered the add-email page, so the login stage is complete.', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6AddEmailSuccessResult(snapshot, { via: `${via}_add_email`, }); } if (snapshot.state === 'login_timeout_error_page') { - log(`页面进入登录超时报错页,准备自动恢复后重试步骤 ${visibleStep}。`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log(`The page entered the login timeout error page; preparing to recover automatically and retry step ${visibleStep}.`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6LoginTimeoutRecoverableResult( 'login_timeout_error_page', snapshot, - '登录验证码页面准备就绪前进入登录超时报错页。', + 'Entered the login timeout error page before the login verification code page was ready.', { visibleStep } ); } if (snapshot.state === 'password_page' || snapshot.state === 'email_page') { return createStep6RecoverableResult('verification_page_unstable', snapshot, { - message: `页面曾进入登录验证码阶段,但又回到了${getLoginAuthStateLabel(snapshot)},准备重新执行步骤 ${visibleStep}。`, + message: `The page had entered the login verification code stage, but then returned to ${getLoginAuthStateLabel(snapshot)}; preparing to run step ${visibleStep} again.`, loginVerificationRequestedAt, }); } return createStep6RecoverableResult('verification_page_finalize_unknown', snapshot, { - message: `登录验证码页面状态在收尾确认阶段未稳定,准备重新执行步骤 ${visibleStep}。`, + message: `The login verification code page state was not stable during wrap-up confirmation; preparing to rerun step ${visibleStep}.`, loginVerificationRequestedAt, }); } @@ -4678,9 +4678,9 @@ function throwForStep6FatalState(snapshot, visibleStep = 7) { case 'oauth_consent_page': return; case 'add_phone_page': - throw new Error(`当前页面已进入手机号页面,未经过登录验证码页,无法完成步骤 ${visibleStep}。URL: ${snapshot.url}`); + throw new Error(`The current page entered the phone-number page without passing through the login verification code page, so step ${visibleStep} cannot be completed. URL: ${snapshot.url}`); case 'unknown': - throw new Error(`无法识别当前登录页面状态。URL: ${snapshot?.url || location.href}`); + throw new Error(`Unable to identify the current login page state. URL: ${snapshot?.url || location.href}`); default: return; } @@ -4717,7 +4717,7 @@ async function triggerLoginSubmitAction(button, fallbackField) { return; } - throw new Error('未找到可用的登录提交按钮。URL: ' + location.href); + throw new Error('No available login submit button was found. URL: ' + location.href); }); } @@ -4829,7 +4829,7 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { const { password } = payload; const prepareSource = String(payload?.prepareSource || '').trim() || 'step4_execute'; const prepareLogLabel = String(payload?.prepareLogLabel || '').trim() - || (prepareSource === 'step3_finalize' ? '步骤 3 收尾' : '步骤 4 执行'); + || (prepareSource === 'step3_finalize' ? 'Step 3 wrap-up' : 'Step 4 execution'); const start = Date.now(); let recoveryRound = 0; const maxRecoveryRounds = 3; @@ -4882,16 +4882,16 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { throwIfStopped(); const roundNo = recoveryRound + 1; - log(`${prepareLogLabel}:等待页面进入验证码阶段(第 ${roundNo}/${maxRecoveryRounds} 轮,先等待 5 秒)...`, 'info'); + log(`${prepareLogLabel}: waiting for the page to enter the verification code stage (round ${roundNo}/${maxRecoveryRounds}, wait 5 seconds first)...`, 'info'); const snapshot = await waitForSignupVerificationTransition(5000); if (snapshot.state === 'step5') { - log(`${prepareLogLabel}:页面已进入验证码后的下一阶段,本步骤按已完成处理。`, 'ok'); + log(`${prepareLogLabel}: the page has entered the stage after verification code, so this step is treated as complete.`, 'ok'); return { ready: true, alreadyVerified: true, retried: recoveryRound, prepareSource }; } if (snapshot.state === 'logged_in_home') { - log(`${prepareLogLabel}:页面已直接进入 ChatGPT 已登录态,本步骤按已完成处理,并将跳过步骤 5。`, 'ok'); + log(`${prepareLogLabel}: the page entered the ChatGPT logged-in state directly, so this step is treated as complete and step 5 will be skipped.`, 'ok'); return { ready: true, alreadyVerified: true, @@ -4902,20 +4902,20 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { } if (snapshot.state === 'verification') { - await waitForDocumentLoadComplete(15000, `${prepareLogLabel}:注册验证码页面`); + await waitForDocumentLoadComplete(15000, `${prepareLogLabel}: registration verification code page`); await waitForVerificationCodeTarget(15000); - log(`${prepareLogLabel}:验证码页面已完成加载并就绪${recoveryRound ? `(期间自动恢复 ${recoveryRound} 次)` : ''}。`, 'ok'); + log(`${prepareLogLabel}: the verification code page has finished loading and is ready${recoveryRound ? ` (auto-recovered ${recoveryRound} times during the process)` : ''}.`, 'ok'); return { ready: true, retried: recoveryRound, prepareSource }; } if (snapshot.state === 'email_exists') { - throw new Error('当前邮箱已存在,需要重新开始新一轮。'); + throw new Error('The current email already exists; start a new round.'); } if (snapshot.state === 'contact_verification_server_error') { const serverErrorText = String(snapshot.serverErrorText || '').trim() - || 'OpenAI contact-verification 页面返回 HTTP ERROR 500。'; - log(`${prepareLogLabel}:检测到 contact-verification 500 错误页,当前轮直接报错。`, 'warn'); + || 'OpenAI contact-verification page returned HTTP ERROR 500.'; + log(`${prepareLogLabel}: detected the contact-verification 500 error page; this round will fail immediately.`, 'warn'); throw buildContactVerificationServerError(serverErrorText); } @@ -4926,7 +4926,7 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { recoveryRound += 1; await recoverCurrentAuthRetryPage({ flow: 'signup', - logLabel: `${prepareLogLabel}:检测到注册认证重试页,正在点击“重试”恢复(第 ${recoveryRound}/${maxRecoveryRounds} 次)`, + logLabel: `${prepareLogLabel}: detected the registration auth retry page, clicking "Retry" to recover (attempt ${recoveryRound}/${maxRecoveryRounds})`, step: 4, timeoutMs: 12000, }); @@ -4935,19 +4935,19 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { if (snapshot.state === 'password') { if (snapshot.passwordErrorText) { - log(`${prepareLogLabel}:检测到密码页报错“${snapshot.passwordErrorText}”,当前轮将回到步骤 1 重新开始。`, 'warn'); + log(`${prepareLogLabel}: detected a password page error "${snapshot.passwordErrorText}"; this round will return to step 1 and start over.`, 'warn'); throw createSignupPhonePasswordMismatchError(snapshot.passwordErrorText); } if (!passwordPageDiagnosticsLogged) { passwordPageDiagnosticsLogged = true; - logSignupPasswordDiagnostics(`${prepareLogLabel}:页面仍停留在密码页`); + logSignupPasswordDiagnostics(`${prepareLogLabel}: the page is still staying on the password page`); } if (!password) { - throw new Error('当前回到了密码页,但没有可用密码,无法自动重新提交。'); + throw new Error('We are back on the password page, but no usable password is available, so automatic resubmission is impossible.'); } if ((snapshot.passwordInput.value || '') !== password) { - log(`${prepareLogLabel}:页面仍停留在密码页,正在重新填写密码...`, 'warn'); + log(`${prepareLogLabel}: the page is still staying on the password page, refilling the password...`, 'warn'); await humanPause(450, 1100); await performOperationWithDelay({ stepKey: 'fill-password', kind: 'fill', label: 'retry-signup-password' }, async () => { fillInput(snapshot.passwordInput, password); @@ -4956,7 +4956,7 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { if (snapshot.submitButton && isPasswordSubmitButtonReadyForRetry(snapshot.submitButton)) { recoveryRound += 1; - log(`${prepareLogLabel}:页面仍停留在密码页,正在重新点击“继续”(第 ${recoveryRound}/${maxRecoveryRounds} 次)...`, 'warn'); + log(`${prepareLogLabel}: the page is still staying on the password page, clicking "Continue" again (attempt ${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'fill-password', kind: 'submit', label: 'retry-submit-signup-password' }, async () => { simulateClick(snapshot.submitButton); @@ -4965,14 +4965,14 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { continue; } - log(`${prepareLogLabel}:页面仍停留在密码页,但“继续”按钮暂不可用,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); + log(`${prepareLogLabel}: the page is still staying on the password page, but the "Continue" button is not available yet; continuing to wait (${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); continue; } - log(`${prepareLogLabel}:页面仍在切换中,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); + log(`${prepareLogLabel}: the page is still transitioning; continuing to wait (${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); } - throw new Error(`等待注册验证码页面就绪超时或自动恢复失败(已尝试 ${recoveryRound}/${maxRecoveryRounds} 轮)。URL: ${location.href}`); + throw new Error(`Timed out waiting for the registration verification code page to become ready, or auto-recovery failed (attempted ${recoveryRound}/${maxRecoveryRounds} rounds). URL: ${location.href}`); } @@ -4999,13 +4999,13 @@ async function waitForVerificationSubmitOutcome(step, timeout, options = {}) { } if (retryState) { if (recoveryCount >= maxRecoveryCount) { - throw new Error(`步骤 ${step}:验证码提交后连续进入认证重试页 ${maxRecoveryCount} 次,页面仍未恢复。URL: ${location.href}`); + throw new Error(`Step ${step}: after verification code submission, the page entered the auth retry page ${maxRecoveryCount} times in a row and still did not recover. URL: ${location.href}`); } recoveryCount += 1; - log(`步骤 ${step}:验证码提交后进入认证重试页,正在自动恢复(${recoveryCount}/${maxRecoveryCount})...`, 'warn'); + log(`Step ${step}: the page entered the auth retry page after verification code submission, recovering automatically (${recoveryCount}/${maxRecoveryCount})...`, 'warn'); await recoverCurrentAuthRetryPage({ flow: retryFlow, - logLabel: `步骤 ${step}:验证码提交后检测到认证重试页,正在点击“重试”恢复`, + logLabel: `Step ${step}: detected the auth retry page after verification code submission, clicking "Retry" to recover`, step, timeoutMs: 12000, }); @@ -5080,7 +5080,7 @@ async function waitForVerificationSubmitOutcome(step, timeout, options = {}) { if (isVerificationPageStillVisible()) { return { invalidCode: true, - errorText: getVerificationErrorText() || '提交后仍停留在验证码页面,准备重新发送验证码。', + errorText: getVerificationErrorText() || 'Still on the verification code page after submit; preparing to resend the code.', }; } @@ -5122,7 +5122,7 @@ async function waitForVerificationSubmitButton(codeInput, timeout = 5000) { while (Date.now() - start < timeout) { throwIfStopped(); if (is405MethodNotAllowedPage()) { - throw new Error('当前页面处于 405 错误恢复流程中,暂时无法定位验证码提交按钮。'); + throw new Error('The current page is in the 405 error recovery flow, so the verification code submit button cannot be located right now.'); } const button = getVerificationSubmitButtonForTarget(codeInput, { allowDisabled: false }); @@ -5142,7 +5142,7 @@ async function waitForVerificationCodeTarget(timeout = 10000) { while (Date.now() - start < timeout) { throwIfStopped(); if (is405MethodNotAllowedPage()) { - throw new Error('当前页面处于 405 错误恢复流程中,暂时无法定位验证码输入框。'); + throw new Error('The current page is in the 405 error recovery flow, so the verification code input box cannot be located right now.'); } const target = getVerificationCodeTarget(); @@ -5153,7 +5153,7 @@ async function waitForVerificationCodeTarget(timeout = 10000) { await sleep(150); } - throw new Error('未找到验证码输入框。URL: ' + location.href); + throw new Error('No verification code input box was found. URL: ' + location.href); } async function waitForSplitVerificationInputsFilled(inputs, code, timeout = 2500) { @@ -5179,7 +5179,7 @@ async function waitForSplitVerificationInputsFilled(inputs, code, timeout = 2500 async function fillVerificationCode(step, payload) { const { code, signupProfile } = payload; - if (!code) throw new Error('未提供验证码。'); + if (!code) throw new Error('No verification code provided.'); const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { @@ -5192,7 +5192,7 @@ async function fillVerificationCode(step, payload) { const postVerificationState = getStep4PostVerificationState(); if (postVerificationState?.state === 'logged_in_home') { if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); - log(`步骤 ${step}:检测到页面已进入 ChatGPT 已登录态,本次验证码提交按成功处理。`, 'ok'); + log(`Step ${step}: detected that the page has entered the ChatGPT logged-in state; this verification code submission is treated as successful.`, 'ok'); return { success: true, assumed: true, @@ -5203,13 +5203,13 @@ async function fillVerificationCode(step, payload) { } if (postVerificationState?.state === 'step5') { if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); - log(`步骤 ${step}:检测到页面已进入下一阶段,本次验证码提交按成功处理。`, 'ok'); + log(`Step ${step}: detected that the page has entered the next stage; this verification code submission is treated as successful.`, 'ok'); return { success: true, assumed: true, alreadyAdvanced: true }; } } if (step === 8) { if (isStep8Ready()) { - log(`步骤 ${step}:检测到页面已进入 OAuth 同意页,本次验证码提交按成功处理。`, 'ok'); + log(`Step ${step}: detected that the page has entered the OAuth consent page; this verification code submission is treated as successful.`, 'ok'); return { success: true, assumed: true, alreadyAdvanced: true }; } if (isAddPhonePageReady()) { @@ -5217,7 +5217,7 @@ async function fillVerificationCode(step, payload) { } } - log(`步骤 ${step}:正在填写验证码:${code}`); + log(`Step ${step}: filling verification code: ${code}`); if (step === 8) { await waitForLoginVerificationPageReady(10000, step, { @@ -5225,14 +5225,14 @@ async function fillVerificationCode(step, payload) { }); } if (step === 4) { - await waitForDocumentLoadComplete(15000, `步骤 ${step}:注册验证码页面`); + await waitForDocumentLoadComplete(15000, `Step ${step}: registration verification code page`); } const combinedSignupProfilePage = step === 4 && await waitForCombinedSignupVerificationProfilePage(); if (combinedSignupProfilePage) { if (!signupProfile || !signupProfile.firstName || !signupProfile.lastName) { - throw new Error('当前注册验证码页面要求同时填写资料,但未提供姓名或生日数据。'); + throw new Error('The current registration verification code page requires profile data too, but no name or birthday data was provided.'); } await step5_fillNameBirthday({ ...signupProfile, @@ -5251,7 +5251,7 @@ async function fillVerificationCode(step, payload) { // Before looking for input, check if page is in 405 error state if (is405MethodNotAllowedPage()) { - log(`步骤 ${step}:检测到 405 错误页面,正在恢复...`, 'warn'); + log(`Step ${step}: detected a 405 error page, recovering...`, 'warn'); await handle405ResendError(step, 30000); continue; } @@ -5267,17 +5267,17 @@ async function fillVerificationCode(step, payload) { } catch { // No input found — check if it's a 405 error and can be recovered if (is405MethodNotAllowedPage() && retry < maxRetries) { - log(`步骤 ${step}:未找到验证码输入框且页面出现 405 错误,正在恢复...`, 'warn'); + log(`Step ${step}: no verification code input box was found and the page showed a 405 error, recovering...`, 'warn'); await handle405ResendError(step, 30000); continue; } - throw new Error('未找到验证码输入框。URL: ' + location.href); + throw new Error('No verification code input box was found. URL: ' + location.href); } } if (splitInputs?.length >= 6) { - log(`步骤 ${step}:发现分开的单字符验证码输入框,正在逐个填写...`); + log(`Step ${step}: found separate single-character verification code inputs, filling them one by one...`); await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'grouped-code', label: 'split-code' }, async () => { for (let i = 0; i < 6 && i < splitInputs.length; i++) { const targetInput = splitInputs[i]; @@ -5296,9 +5296,9 @@ async function fillVerificationCode(step, payload) { .slice(0, 6) .map((input) => String(input?.value || '').trim() || '_') .join(''); - log(`步骤 ${step}:分格验证码输入框未稳定呈现目标值,当前页面值为 ${current},准备继续观察提交流程。`, 'warn'); + log(`Step ${step}: the split verification code inputs have not yet stabilized to the target value; current page value is ${current}, so we will keep observing the submit flow.`, 'warn'); } else { - log(`步骤 ${step}:分格验证码输入框已稳定显示 ${code}。`, 'info'); + log(`Step ${step}: split verification code inputs are now stably showing ${code}.`, 'info'); } await sleep(800); @@ -5308,21 +5308,21 @@ async function fillVerificationCode(step, payload) { await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'submit', label: 'submit-code' }, async () => { simulateClick(splitSubmitBtn); }); - log(`步骤 ${step}:分格验证码已提交`); + log(`Step ${step}: split verification code submitted.`); } else { - log(`步骤 ${step}:分格验证码页面未找到可点击提交按钮,继续等待页面自动推进。`, 'info'); + log(`Step ${step}: no clickable submit button was found on the split verification code page; keep waiting for the page to advance automatically.`, 'info'); } const outcome = await waitForVerificationSubmitOutcome(step, undefined, payload); if (outcome.invalidCode) { - log(`步骤 ${step}:验证码被拒绝:${outcome.errorText}`, 'warn'); + log(`Step ${step}: verification code rejected: ${outcome.errorText}`, 'warn'); } else if (outcome.emailVerificationRequired) { - log(`步骤 ${step}:手机验证码已通过,页面进入邮箱验证码验证。`, 'ok'); + log(`Step ${step}: phone verification code accepted; the page moved to email verification.`, 'ok'); } else if (outcome.addPhonePage) { - log(`步骤 ${step}:验证码提交后页面进入手机号页面,当前流程将停止自动授权。`, 'warn'); + log(`Step ${step}: after verification code submission, the page moved to the phone-number page; the current flow will stop automatic authorization.`, 'warn'); } else { if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); - log(`步骤 ${step}:验证码已通过${outcome.assumed ? '(按成功推定)' : ''}。`, 'ok'); + log(`Step ${step}: verification code accepted${outcome.assumed ? ' (assumed successful)' : ''}.`, 'ok'); } if (combinedSignupProfilePage && !outcome.invalidCode) { outcome.skipProfileStep = true; @@ -5332,13 +5332,13 @@ async function fillVerificationCode(step, payload) { } if (!codeInput) { - throw new Error('未找到验证码输入框。URL: ' + location.href); + throw new Error('No verification code input box was found. URL: ' + location.href); } await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'fill', label: 'verification-code' }, async () => { fillInput(codeInput, code); }); - log(`步骤 ${step}:验证码已填写`); + log(`Step ${step}: verification code filled.`); // Submit await sleep(800); @@ -5349,21 +5349,21 @@ async function fillVerificationCode(step, payload) { await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'submit', label: 'submit-code' }, async () => { simulateClick(submitBtn); }); - log(`步骤 ${step}:验证码已提交`); + log(`Step ${step}: verification code submitted.`); } else { - log(`步骤 ${step}:未找到可提交的验证码按钮,先等待页面自动推进或反馈结果。`, 'warn'); + log(`Step ${step}: no submit button for the verification code was found; first wait for the page to advance automatically or return feedback.`, 'warn'); } const outcome = await waitForVerificationSubmitOutcome(step, undefined, payload); if (outcome.invalidCode) { - log(`步骤 ${step}:验证码被拒绝:${outcome.errorText}`, 'warn'); + log(`Step ${step}: verification code rejected: ${outcome.errorText}`, 'warn'); } else if (outcome.emailVerificationRequired) { - log(`步骤 ${step}:手机验证码已通过,页面进入邮箱验证码验证。`, 'ok'); + log(`Step ${step}: phone verification code accepted; the page moved to email verification.`, 'ok'); } else if (outcome.addPhonePage) { - log(`步骤 ${step}:验证码提交后页面进入手机号页面,当前流程将停止自动授权。`, 'warn'); + log(`Step ${step}: after verification code submission, the page moved to the phone-number page; the current flow will stop automatic authorization.`, 'warn'); } else { if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); - log(`步骤 ${step}:验证码已通过${outcome.assumed ? '(按成功推定)' : ''}。`, 'ok'); + log(`Step ${step}: verification code accepted${outcome.assumed ? ' (assumed successful)' : ''}.`, 'ok'); } if (combinedSignupProfilePage && !outcome.invalidCode) { @@ -5389,7 +5389,7 @@ async function resolveStep6PostSubmitSnapshot(snapshot, options = {}) { loginVerificationRequestedAt = null, oauthConsentVia = `${via}_oauth_consent`, timeoutRecoveryReason = 'login_timeout_error_page', - timeoutRecoveryMessage = '登录提交后进入登录超时报错页。', + timeoutRecoveryMessage = 'After login submission, the page entered the login timeout error page.', timeoutRecoveryVia = `${via}_timeout_recovered`, allowPhoneVerificationPage = false, allowPhoneAction = false, @@ -5484,7 +5484,7 @@ async function resolveStep6PostSubmitSnapshot(snapshot, options = {}) { if (normalizedSnapshot.state === 'add_phone_page') { const message = getStep6OptionMessage(addPhoneMessage, normalizedSnapshot) - || `登录提交后页面进入手机号页面。URL: ${normalizedSnapshot.url || location.href}`; + || `After login submission, the page entered the phone-number page. URL: ${normalizedSnapshot.url || location.href}`; throw new Error(message); } @@ -5495,7 +5495,7 @@ async function waitForStep6PostSubmitTransition(options = {}) { const { timeout = 10000, stalledReason = 'post_submit_stalled', - stalledMessage = '登录提交后未进入可识别的下一页。', + stalledMessage = 'After login submission, the page did not reach a recognizable next page.', } = options; const start = Date.now(); let snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); @@ -5538,12 +5538,12 @@ async function waitForStep6EmailSubmitTransition(emailSubmittedAt, timeout = 120 via: 'email_submit', oauthConsentVia: 'email_submit_oauth_consent', loginVerificationRequestedAt: emailSubmittedAt, - timeoutRecoveryMessage: '提交邮箱后进入登录超时报错页。', + timeoutRecoveryMessage: 'After submitting the email, the page entered the login timeout error page.', timeoutRecoveryVia: 'email_submit_timeout_recovered', allowPasswordAction: true, stalledReason: 'email_submit_stalled', - stalledMessage: '提交邮箱后长时间未进入密码页或登录验证码页。', - addPhoneMessage: (snapshot) => `提交邮箱后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`, + stalledMessage: 'After submitting the email, the page did not reach the password page or login verification code page for a long time.', + addPhoneMessage: (snapshot) => `After submitting the email, the page went directly to the phone-number page without passing through the login verification code page. URL: ${snapshot.url}`, }); } @@ -5554,14 +5554,14 @@ async function waitForStep6PhoneSubmitTransition(phoneSubmittedAt, timeout = 120 via: 'phone_submit', oauthConsentVia: 'phone_submit_oauth_consent', loginVerificationRequestedAt: phoneSubmittedAt, - timeoutRecoveryMessage: '提交手机号后进入登录超时报错页。', + timeoutRecoveryMessage: 'After submitting the phone number, the page entered the login timeout error page.', timeoutRecoveryVia: 'phone_submit_timeout_recovered', allowPhoneVerificationPage: true, allowPasswordAction: true, allowFinalPhoneAction: true, stalledReason: 'phone_submit_stalled', - stalledMessage: '提交手机号后长时间未进入密码页或手机验证码页。', - addPhoneMessage: (snapshot) => `提交手机号后页面直接进入手机号补全页面,未经过登录验证码页。URL: ${snapshot.url}`, + stalledMessage: 'After submitting the phone number, the page did not reach the password page or phone verification code page for a long time.', + addPhoneMessage: (snapshot) => `After submitting the phone number, the page went directly to the phone-number completion page without passing through the login verification code page. URL: ${snapshot.url}`, }); } @@ -5572,12 +5572,12 @@ async function waitForStep6PasswordSubmitTransition(passwordSubmittedAt, timeout via: 'password_submit', oauthConsentVia: 'password_submit_oauth_consent', loginVerificationRequestedAt: passwordSubmittedAt, - timeoutRecoveryMessage: '提交密码后进入登录超时报错页。', + timeoutRecoveryMessage: 'After submitting the password, the page entered the login timeout error page.', timeoutRecoveryVia: 'password_submit_timeout_recovered', allowFinalSwitchAction: true, stalledReason: 'password_submit_stalled', - stalledMessage: '提交密码后仍未进入登录验证码页。', - addPhoneMessage: (snapshot) => `提交密码后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`, + stalledMessage: 'After submitting the password, the page still did not reach the login verification code page.', + addPhoneMessage: (snapshot) => `After submitting the password, the page went directly to the phone-number page without passing through the login verification code page. URL: ${snapshot.url}`, }); } @@ -5588,11 +5588,11 @@ async function waitForStep6SwitchTransition(loginVerificationRequestedAt, timeou via: 'switch_to_one_time_code_login', oauthConsentVia: 'switch_to_one_time_code_oauth_consent', loginVerificationRequestedAt, - timeoutRecoveryMessage: '切换到一次性验证码登录后进入登录超时报错页。', + timeoutRecoveryMessage: 'After switching to one-time-code login, the page entered the login timeout error page.', timeoutRecoveryVia: 'switch_to_one_time_code_timeout_recovered', stalledReason: 'one_time_code_switch_stalled', - stalledMessage: '点击一次性验证码登录后仍未进入登录验证码页。', - addPhoneMessage: (snapshot) => `切换到一次性验证码登录后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`, + stalledMessage: 'After clicking one-time-code login, the page still did not reach the login verification code page.', + addPhoneMessage: (snapshot) => `After switching to one-time-code login, the page went directly to the phone-number page without passing through the login verification code page. URL: ${snapshot.url}`, }); if (transition.action === 'done' || transition.action === 'recoverable') { @@ -5650,12 +5650,12 @@ async function step6OpenLoginEntry(payload, snapshot) { if (!trigger || !isActionEnabled(trigger)) { return createStep6RecoverableResult('missing_login_entry_trigger', currentSnapshot, { message: preferPhoneLogin - ? '当前登录入口页没有可点击的手机号登录入口。' - : '当前登录入口页没有可点击的邮箱登录入口。', + ? 'The current login entry page has no clickable phone-number login entry.' + : 'The current login entry page has no clickable email login entry.', }); } - log(`检测到登录入口页,正在点击 "${getActionText(trigger).slice(0, 80)}"...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log(`Detected the login entry page, clicking "${getActionText(trigger).slice(0, 80)}"...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'open-login-entry' }, async () => { simulateClick(trigger); @@ -5695,7 +5695,7 @@ async function step6OpenLoginEntry(payload, snapshot) { const transition = await createStep6LoginTimeoutRecoveryTransition( 'login_timeout_after_entry_open', nextSnapshot, - '点击登录入口后进入登录超时报错页。', + 'After clicking the login entry, the page entered the login timeout error page.', { visibleStep } ); if (transition.action === 'done') return transition.result; @@ -5706,7 +5706,7 @@ async function step6OpenLoginEntry(payload, snapshot) { } return createStep6RecoverableResult('login_entry_open_stalled', nextSnapshot, { - message: '点击登录入口后仍未进入手机号/邮箱/密码/验证码页。', + message: 'After clicking the login entry, the page still did not reach the phone-number/email/password/verification-code page.', }); } @@ -5722,17 +5722,17 @@ async function step6SwitchToOneTimeCodeLogin(payload, snapshot) { const switchTrigger = snapshot?.switchTrigger || findOneTimeCodeLoginTrigger(); if (!switchTrigger || !isActionEnabled(switchTrigger)) { return createStep6RecoverableResult('missing_one_time_code_trigger', normalizeStep6Snapshot(inspectLoginAuthState()), { - message: '当前登录页没有可用的一次性验证码登录入口。', + message: 'The current login page has no available one-time-code login entry.', }); } - log('已检测到一次性验证码登录入口,准备切换...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('Detected the one-time-code login entry, preparing to switch...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); const loginVerificationRequestedAt = Date.now(); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'switch-one-time-code-login' }, async () => { simulateClick(switchTrigger); }); - log('已点击一次性验证码登录', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('Clicked one-time-code login.', 'info', { step: visibleStep, stepKey: 'oauth-login' }); await sleep(1200); const result = await waitForStep6SwitchTransition(loginVerificationRequestedAt, 10000, { visibleStep }); if (result?.step6Outcome === 'success') { @@ -5774,12 +5774,12 @@ async function step6LoginFromPhonePage(payload, snapshot) { if (!phoneNumber) { return createStep6RecoverableResult('missing_phone_number', currentSnapshot, { - message: '手机号登录时缺少手机号,请重新执行步骤 2 获取号码。', + message: 'Missing phone number for phone login; rerun step 2 to get a number.', }); } if (!phoneInput) { return createStep6RecoverableResult('missing_phone_input', currentSnapshot, { - message: '当前登录页没有可用的手机号输入框。', + message: 'The current login page has no available phone-number input box.', }); } @@ -5791,11 +5791,11 @@ async function step6LoginFromPhonePage(payload, snapshot) { }); const inputValue = toNationalPhoneNumber(phoneNumber, dialCode); if (!inputValue) { - throw new Error(`步骤 ${visibleStep}:手机号为空,无法填写。`); + throw new Error(`Step ${visibleStep}: phone number is empty and cannot be filled.`); } log( - `步骤 ${visibleStep}:手机号登录填写前诊断 ${JSON.stringify({ + `Step ${visibleStep}: phone login pre-fill diagnostics ${JSON.stringify({ phoneNumber, countryLabel, countryId, @@ -5806,7 +5806,7 @@ async function step6LoginFromPhonePage(payload, snapshot) { 'info', { step: visibleStep, stepKey: 'oauth-login' } ); - log(`步骤 ${visibleStep}:正在填写手机号 ${phoneNumber}...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log(`Step ${visibleStep}: filling phone number ${phoneNumber}...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(500, 1400); const fillResult = await fillLoginPhoneInputAndConfirm(phoneInput, { phoneNumber, @@ -5814,7 +5814,7 @@ async function step6LoginFromPhonePage(payload, snapshot) { visibleStep, resolvePhoneInput: () => getLoginPhoneInput() || phoneInput, }); - log(`步骤 ${visibleStep}:手机号已填写${dialCode ? `(区号 +${dialCode},本地号 ${fillResult.inputValue},可见提交值 ${fillResult.attemptedValue})` : ''}。`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log(`Step ${visibleStep}: phone number filled${dialCode ? ` (dial code +${dialCode}, local number ${fillResult.inputValue}, visible submit value ${fillResult.attemptedValue})` : ''}.`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await sleep(500); const verifiedPhoneInput = fillResult.input || phoneInput; @@ -5832,16 +5832,16 @@ async function step6LoginFromPhonePage(payload, snapshot) { submitButton: getLoginPhoneSubmitButtonDiagnostics(submitButton), }; log( - `步骤 ${visibleStep}:手机号提交前复查 ${JSON.stringify(preSubmitDiagnostics)}`, + `Step ${visibleStep}: pre-submit review for the phone number ${JSON.stringify(preSubmitDiagnostics)}`, 'info', { step: visibleStep, stepKey: 'oauth-login' } ); if (!preSubmitDiagnostics.inputVerified) { - throw new Error(`步骤 ${visibleStep}:提交前手机号输入框复查失败,完整号码 ${phoneNumber},区号 +${dialCode || '未识别'},期望本地号 ${inputValue},当前输入框为 ${normalizePhoneDigits(preSubmitRenderedValue) || '空'},已停止提交。`); + throw new Error(`Step ${visibleStep}: pre-submit review of the phone-number input failed; full number ${phoneNumber}, dial code +${dialCode || 'unrecognized'}, expected local number ${inputValue}, current input is ${normalizePhoneDigits(preSubmitRenderedValue) || 'empty'}, so submission was stopped.`); } const phoneSubmittedAt = Date.now(); await triggerLoginSubmitAction(submitButton, verifiedPhoneInput); - log(`步骤 ${visibleStep}:手机号已提交。`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log(`Step ${visibleStep}: phone number submitted.`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); const transition = await waitForStep6PhoneSubmitTransition(phoneSubmittedAt, 12000, { visibleStep }); if (transition.action === 'done') { @@ -5856,7 +5856,7 @@ async function step6LoginFromPhonePage(payload, snapshot) { }); } if (transition.action === 'recoverable') { - log(transition.result.message || `提交手机号后仍未进入目标页面,准备重新执行步骤 ${visibleStep}。`, 'warn', { + log(transition.result.message || `After submitting the phone number, the page still did not reach the target page; preparing to rerun step ${visibleStep}.`, 'warn', { step: visibleStep, stepKey: 'oauth-login', }); @@ -5873,7 +5873,7 @@ async function step6LoginFromPhonePage(payload, snapshot) { } return createStep6RecoverableResult('phone_submit_unknown', normalizeStep6Snapshot(inspectLoginAuthState()), { - message: '提交手机号后未得到可用的下一步状态。', + message: 'After submitting the phone number, no usable next-step state was returned.', }); } @@ -5891,7 +5891,7 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) { if (!phoneEntryTrigger || !isActionEnabled(phoneEntryTrigger)) { const moreOptionsTrigger = currentSnapshot.moreOptionsTrigger || findLoginMoreOptionsTrigger(); if (moreOptionsTrigger && isActionEnabled(moreOptionsTrigger)) { - log(`步骤 ${visibleStep}:手机号入口可能隐藏在更多选项中,正在展开更多选项...`, 'info', { + log(`Step ${visibleStep}: the phone-number entry may be hidden under More options, expanding them now...`, 'info', { step: visibleStep, stepKey: 'oauth-login', }); @@ -5907,11 +5907,11 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) { if (!phoneEntryTrigger || !isActionEnabled(phoneEntryTrigger)) { return createStep6RecoverableResult('missing_phone_login_entry_trigger', currentSnapshot, { - message: '本轮要求使用手机号登录,但当前邮箱登录页没有可用的手机号登录入口。', + message: 'This round requires phone-number login, but the current email login page has no available phone-number login entry.', }); } - log(`步骤 ${visibleStep}:当前在邮箱入口,正在切换到手机号登录...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log(`Step ${visibleStep}: currently on the email entry, switching to phone-number login...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'switch-phone-login' }, async () => { simulateClick(phoneEntryTrigger); @@ -5945,7 +5945,7 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) { const transition = await createStep6LoginTimeoutRecoveryTransition( 'login_timeout_after_phone_entry_switch', nextSnapshot, - '点击手机号登录入口后进入登录超时报错页。', + 'After clicking the phone-number login entry, the page entered the login timeout error page.', { visibleStep, allowPhoneVerificationPage: true, @@ -5959,7 +5959,7 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) { } return createStep6RecoverableResult('phone_login_entry_switch_stalled', nextSnapshot, { - message: `点击手机号登录入口后仍未进入手机号或密码页,当前停留在${getLoginAuthStateLabel(nextSnapshot)}。`, + message: `After clicking the phone-number login entry, the page still did not reach the phone-number or password page and is currently on ${getLoginAuthStateLabel(nextSnapshot)}.`, }); } @@ -5978,26 +5978,26 @@ async function step6LoginFromPasswordPage(payload, snapshot) { if (currentSnapshot.passwordInput) { if (!hasPassword) { if (currentSnapshot.switchTrigger) { - log('当前未提供密码,改走一次性验证码登录。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log('No password provided right now, switching to one-time-code login instead.', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return step6SwitchToOneTimeCodeLogin(payload, currentSnapshot); } return createStep6RecoverableResult('missing_password_and_one_time_code_trigger', currentSnapshot, { - message: '登录时未提供密码,且当前页面没有可用的一次性验证码登录入口。', + message: 'No password was provided for login, and the current page has no available one-time-code login entry.', }); } - log('已进入密码页,准备填写密码...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('Entered the password page, preparing to fill the password...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(550, 1450); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-password' }, async () => { fillInput(currentSnapshot.passwordInput, payload.password); }); - log('已填写密码', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('Password filled.', 'info', { step: visibleStep, stepKey: 'oauth-login' }); await sleep(500); const passwordSubmittedAt = Date.now(); await triggerLoginSubmitAction(currentSnapshot.submitButton, currentSnapshot.passwordInput); - log('已提交密码', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('Password submitted.', 'info', { step: visibleStep, stepKey: 'oauth-login' }); const transition = await waitForStep6PasswordSubmitTransition(passwordSubmittedAt, 10000, { visibleStep }); if (transition.action === 'done') { @@ -6011,7 +6011,7 @@ async function step6LoginFromPasswordPage(payload, snapshot) { }); } if (transition.action === 'recoverable') { - log(transition.result.message || `提交密码后仍未进入登录验证码页面,准备重新执行步骤 ${visibleStep}。`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log(transition.result.message || `After submitting the password, the page still did not reach the login verification code page; preparing to rerun step ${visibleStep}.`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return transition.result; } if (transition.action === 'password') { @@ -6028,7 +6028,7 @@ async function step6LoginFromPasswordPage(payload, snapshot) { } return createStep6RecoverableResult('password_submit_unknown', normalizeStep6Snapshot(inspectLoginAuthState()), { - message: '提交密码后未得到可用的下一步状态。', + message: 'After submitting the password, no usable next-step state was returned.', }); } @@ -6037,7 +6037,7 @@ async function step6LoginFromPasswordPage(payload, snapshot) { } return createStep6RecoverableResult('password_page_unactionable', currentSnapshot, { - message: '当前停留在登录页,但没有可提交密码的输入框,也没有一次性验证码登录入口。', + message: 'The page is still on the login page, but there is neither a password input box nor a one-time-code login entry.', }); } @@ -6056,7 +6056,7 @@ async function step6LoginFromEmailPage(payload, snapshot) { } const emailInput = currentSnapshot.emailInput || getLoginEmailInput(); if (!emailInput) { - throw new Error('在登录页未找到邮箱输入框。URL: ' + location.href); + throw new Error('No email input box was found on the login page. URL: ' + location.href); } if ((emailInput.value || '').trim() !== payload.email) { @@ -6064,15 +6064,15 @@ async function step6LoginFromEmailPage(payload, snapshot) { await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-email' }, async () => { fillInput(emailInput, payload.email); }); - log('已填写邮箱', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('Email filled.', 'info', { step: visibleStep, stepKey: 'oauth-login' }); } else { - log('邮箱已在输入框中,准备提交...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('Email is already in the input box, preparing to submit...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); } await sleep(500); const emailSubmittedAt = Date.now(); await triggerLoginSubmitAction(currentSnapshot.submitButton, emailInput); - log('已提交邮箱', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('Email submitted.', 'info', { step: visibleStep, stepKey: 'oauth-login' }); const transition = await waitForStep6EmailSubmitTransition(emailSubmittedAt, 12000, { visibleStep }); if (transition.action === 'done') { @@ -6086,7 +6086,7 @@ async function step6LoginFromEmailPage(payload, snapshot) { }); } if (transition.action === 'recoverable') { - log(transition.result.message || `提交邮箱后仍未进入目标页面,准备重新执行步骤 ${visibleStep}。`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log(transition.result.message || `After submitting the email, the page still did not reach the target page; preparing to rerun step ${visibleStep}.`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return transition.result; } if (transition.action === 'email') { @@ -6100,7 +6100,7 @@ async function step6LoginFromEmailPage(payload, snapshot) { } return createStep6RecoverableResult('email_submit_unknown', normalizeStep6Snapshot(inspectLoginAuthState()), { - message: '提交邮箱后未得到可用的下一步状态。', + message: 'After submitting the email, no usable next-step state was returned.', }); } @@ -6108,12 +6108,12 @@ async function step6_login(payload) { const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7; const { email, phoneNumber } = payload; const loginIdentifierType = String(payload?.loginIdentifierType || '').trim(); - if (!email && !phoneNumber) throw new Error('登录时缺少邮箱地址或手机号。'); + if (!email && !phoneNumber) throw new Error('Login is missing both an email address and a phone number.'); const snapshot = normalizeStep6Snapshot(await waitForKnownLoginAuthState(15000)); if (snapshot.state === 'verification_page' || snapshot.state === 'phone_verification_page') { - log('认证页已在登录验证码页,开始确认页面是否稳定。', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('The auth page is already on the login verification code page; checking whether the page is stable.', 'info', { step: visibleStep, stepKey: 'oauth-login' }); return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: null, @@ -6125,25 +6125,25 @@ async function step6_login(payload) { } if (snapshot.state === 'oauth_consent_page') { - log('认证页已直接进入 OAuth 授权页,跳过登录验证码步骤。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); + log('The auth page entered the OAuth consent page directly, so the login verification code step is skipped.', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6OAuthConsentSuccessResult(snapshot, { via: 'already_on_oauth_consent_page', }); } if (snapshot.state === 'add_email_page') { - log('认证页已在添加邮箱页,登录阶段完成。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); + log('The auth page is already on the add-email page, so the login stage is complete.', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6AddEmailSuccessResult(snapshot, { via: 'already_on_add_email_page', }); } if (snapshot.state === 'login_timeout_error_page') { - log('检测到登录超时报错页,先尝试恢复当前页面。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); + log('Detected the login timeout error page; trying to recover the current page first.', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); const transition = await createStep6LoginTimeoutRecoveryTransition( 'login_timeout_error_page', snapshot, - '当前页面处于登录超时报错页。', + 'The current page is on the login timeout error page.', { visibleStep, loginVerificationRequestedAt: null, @@ -6177,17 +6177,17 @@ async function step6_login(payload) { if (loginIdentifierType === 'phone' && phoneNumber) { return switchFromEmailPageToPhoneLogin(payload, snapshot); } - log(`正在使用 ${email} 登录...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log(`Logging in with ${email}...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); return step6LoginFromEmailPage(payload, snapshot); } if (snapshot.state === 'phone_entry_page') { - log('正在使用手机号登录...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('Logging in with a phone number...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); return step6LoginFromPhonePage(payload, snapshot); } if (snapshot.state === 'password_page') { - log('认证页已在密码页,继续当前登录流程。', 'info', { step: visibleStep, stepKey: 'oauth-login' }); + log('The auth page is already on the password page; continuing the current login flow.', 'info', { step: visibleStep, stepKey: 'oauth-login' }); return step6LoginFromPasswordPage(payload, snapshot); } @@ -6196,7 +6196,7 @@ async function step6_login(payload) { } throwForStep6FatalState(snapshot, visibleStep); - throw new Error(`无法识别当前登录页面状态。URL: ${snapshot?.url || location.href}`); + throw new Error(`Unable to पहचान current login page state. URL: ${snapshot?.url || location.href}`); } async function waitForAddEmailPageReady(timeout = 15000) { @@ -6214,9 +6214,9 @@ async function waitForAddEmailPageReady(timeout = 15000) { await sleep(200); } if (sawAddEmailPage) { - throw new Error('等待添加邮箱页面输入框就绪超时。URL: ' + location.href); + throw new Error('Timed out waiting for the add-email page input box to be ready. URL: ' + location.href); } - throw new Error('等待添加邮箱页面就绪超时。URL: ' + location.href); + throw new Error('Timed out waiting for the add-email page to be ready. URL: ' + location.href); } async function waitForAddEmailSubmitOutcome(timeout = 45000) { @@ -6270,7 +6270,7 @@ async function waitForAddEmailSubmitOutcome(timeout = 45000) { await sleep(200); } - throw new Error(`提交邮箱后未进入验证码页。当前状态:${getLoginAuthStateLabel(lastState)}。URL: ${lastState?.url || location.href}`); + throw new Error(`After submitting the email, the page did not enter the verification page. Current state: ${getLoginAuthStateLabel(lastState)}. URL: ${lastState?.url || location.href}`); } async function submitAddEmailAndContinue(payload = {}) { @@ -6283,36 +6283,36 @@ async function submitAddEmailAndContinue(payload = {}) { }; const email = String(payload.email || '').trim().toLowerCase(); if (!email) { - throw new Error('未提供邮箱地址,无法添加邮箱。'); + throw new Error('No email address provided; cannot add email.'); } const snapshot = await waitForAddEmailPageReady(); const emailInput = snapshot.emailInput || getLoginEmailInput(); if (!emailInput) { - throw new Error('添加邮箱页未找到邮箱输入框。URL: ' + location.href); + throw new Error('No email input box was found on the add-email page. URL: ' + location.href); } await humanPause(500, 1400); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'add-email' }, async () => { fillInput(emailInput, email); }); - log(`步骤 8:已填写邮箱:${email}`); + log(`Step 8: email filled: ${email}`); await sleep(500); const submitButton = snapshot.submitButton || getLoginSubmitButton({ allowDisabled: true }); if (!submitButton || !isActionEnabled(submitButton)) { - throw new Error('添加邮箱页未找到可点击的继续按钮。URL: ' + location.href); + throw new Error('No clickable Continue button was found on the add-email page. URL: ' + location.href); } await triggerLoginSubmitAction(submitButton, emailInput); - log('步骤 8:已提交邮箱,正在等待邮箱验证码页...'); + log('Step 8: email submitted, waiting for the email verification code page...'); const outcome = await waitForAddEmailSubmitOutcome(); if (outcome.errorText && (SIGNUP_EMAIL_EXISTS_PATTERN.test(outcome.errorText) || /email_in_use/i.test(outcome.errorText))) { throw createStep8EmailInUseError(); } if (outcome.errorText) { - throw new Error(`添加邮箱失败:${outcome.errorText}`); + throw new Error(`Add-email failed: ${outcome.errorText}`); } if (outcome.emailInUse) { throw createStep8EmailInUseError(); @@ -6321,7 +6321,7 @@ async function submitAddEmailAndContinue(payload = {}) { throw createAuthMaxCheckAttemptsError(); } if (outcome.retryPage) { - throw new Error(`添加邮箱后进入认证重试页,请重新执行步骤 8。URL: ${outcome.url}`); + throw new Error(`After adding the email, the page entered the auth retry page; please rerun step 8. URL: ${outcome.url}`); } return { @@ -6340,12 +6340,12 @@ async function submitAddEmailAndContinue(payload = {}) { async function step8_findAndClick(payload = {}) { const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 9; - log('正在查找 OAuth 同意页的“继续”按钮...', 'info', { step: visibleStep, stepKey: 'confirm-oauth' }); + log('Looking for the "Continue" button on the OAuth consent page...', 'info', { step: visibleStep, stepKey: 'confirm-oauth' }); const continueBtn = await prepareStep8ContinueButton(); const rect = getSerializableRect(continueBtn); - log('已找到“继续”按钮并准备好调试器点击坐标。', 'info', { step: visibleStep, stepKey: 'confirm-oauth' }); + log('Found the "Continue" button and prepared the debugger click coordinates.', 'info', { step: visibleStep, stepKey: 'confirm-oauth' }); return { rect, buttonText: (continueBtn.textContent || '').trim(), @@ -6397,7 +6397,7 @@ async function step8_triggerContinue(payload = {}) { switch (strategy) { case 'requestSubmit': if (!form || typeof form.requestSubmit !== 'function') { - throw new Error('“继续”按钮当前不在可提交的 form 中,无法使用 requestSubmit。URL: ' + location.href); + throw new Error('"Continue" button is not currently inside a submitable form, so requestSubmit cannot be used. URL: ' + location.href); } form.requestSubmit(continueBtn); break; @@ -6408,7 +6408,7 @@ async function step8_triggerContinue(payload = {}) { simulateClick(continueBtn); break; default: - throw new Error(`未知的 Step ${visibleStep} 触发策略:${strategy}`); + throw new Error(`Unknown Step ${visibleStep} trigger strategy: ${strategy}`); } log(`continue button triggered via ${strategy}.`, 'info', { step: visibleStep, stepKey: 'confirm-oauth' }); @@ -6439,10 +6439,10 @@ async function findContinueButton(timeout = 10000) { while (Date.now() - start < timeout) { throwIfStopped(); if (isAddPhonePageReady()) { - throw new Error('当前页面已进入手机号页面,不是 OAuth 授权同意页。URL: ' + location.href); + throw new Error('The current page has entered the phone-number page, not the OAuth consent page. URL: ' + location.href); } if (isAddEmailPageReady()) { - throw new Error('当前页面已进入添加邮箱页面,不是 OAuth 授权同意页。URL: ' + location.href); + throw new Error('The current page has entered the add-email page, not the OAuth consent page. URL: ' + location.href); } const button = getPrimaryContinueButton(); if (button && isStep8Ready()) { @@ -6451,7 +6451,7 @@ async function findContinueButton(timeout = 10000) { await sleep(150); } - throw new Error('在 OAuth 同意页未找到“继续”按钮,或页面尚未进入授权同意状态。URL: ' + location.href); + throw new Error('No "Continue" button was found on the OAuth consent page, or the page has not yet entered the consent state. URL: ' + location.href); } async function waitForButtonEnabled(button, timeout = 8000) { @@ -6461,7 +6461,7 @@ async function waitForButtonEnabled(button, timeout = 8000) { if (isButtonEnabled(button)) return; await sleep(150); } - throw new Error('“继续”按钮长时间不可点击。URL: ' + location.href); + throw new Error('"Continue" button has been unclickable for a long time. URL: ' + location.href); } function isButtonEnabled(button) { @@ -6511,7 +6511,7 @@ async function waitForStableButtonRect(button, timeout = 1500) { function getSerializableRect(el) { const rect = el.getBoundingClientRect(); if (!rect.width || !rect.height) { - throw new Error('滚动后“继续”按钮没有可点击尺寸。URL: ' + location.href); + throw new Error('After scrolling, the "Continue" button has no clickable size. URL: ' + location.href); } return { @@ -6812,7 +6812,7 @@ function logStep5SubmitDebug(message, options = {}) { ] .filter(Boolean) .join(' | '); - log(`步骤 5 [调试] ${message} | ${summary}`, options?.level || 'info', { + log(`Step 5 [debug] ${message} | ${summary}`, options?.level || 'info', { step: 5, stepKey: 'fill-profile', }); @@ -6822,7 +6822,7 @@ async function recoverStep5SubmitRetryPage(payload = {}) { return recoverCurrentAuthRetryPage({ ...payload, flow: 'signup', - logLabel: payload?.logLabel || '步骤 5:资料提交后检测到认证重试页,正在点击“重试”恢复', + logLabel: payload?.logLabel || 'Step 5: detected the auth retry page after profile submission, clicking "Retry" to recover', maxClickAttempts: payload?.maxClickAttempts ?? 2, pathPatterns: Array.isArray(payload?.pathPatterns) ? payload.pathPatterns : getStep5AuthRetryPathPatterns(), step: 5, @@ -6839,7 +6839,7 @@ function installStep5NavigationCompletionReporter(completeOnce) { ? logStep5SubmitDebug : (message, options = {}) => { if (typeof log === 'function') { - log(`步骤 5 [调试] ${message}`, options?.level || 'info', { + log(`Step 5 [debug] ${message}`, options?.level || 'info', { step: 5, stepKey: 'fill-profile', }); @@ -6860,14 +6860,14 @@ function installStep5NavigationCompletionReporter(completeOnce) { }); } catch (error) { if (typeof log === 'function') { - log(`步骤 5 [调试] 导航交棒信号发送失败:${error?.message || error}`, 'warn', { + log(`Step 5 [debug] failed to send navigation handoff signal: ${error?.message || error}`, 'warn', { step: 5, stepKey: 'fill-profile', }); } } } - debugLog(`检测到页面开始导航(event=${eventType})。`, { + debugLog(`Detected that the page started navigating (event=${eventType}).`, { level: 'warn', }); }; @@ -6886,7 +6886,7 @@ async function waitForStep5SubmitOutcome(options = {}) { ? logStep5SubmitDebug : (message, logOptions = {}) => { if (typeof log === 'function') { - log(`步骤 5 [调试] ${message}`, logOptions?.level || 'info', { + log(`Step 5 [debug] ${message}`, logOptions?.level || 'info', { step: 5, stepKey: 'fill-profile', }); @@ -6916,22 +6916,22 @@ async function waitForStep5SubmitOutcome(options = {}) { } if (retryState) { if (authRetryRecoveryCount >= maxAuthRetryRecoveries) { - throw new Error(`步骤 5:资料提交后连续进入认证重试页 ${maxAuthRetryRecoveries} 次,页面仍未恢复。URL: ${location.href}`); + throw new Error(`Step 5: after profile submission, the page entered the auth retry page ${maxAuthRetryRecoveries} times in a row and still did not recover. URL: ${location.href}`); } authRetryRecoveryCount += 1; - debugLog(`检测到资料提交后的认证重试页,准备执行恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries})。`, { + debugLog(`Detected the auth retry page after profile submission; preparing recovery (${authRetryRecoveryCount}/${maxAuthRetryRecoveries}).`, { level: 'warn', }); - log(`步骤 5:资料提交后进入认证重试页,正在自动恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries})...`, 'warn'); + log(`Step 5: after profile submission, the page entered the auth retry page; recovering automatically (${authRetryRecoveryCount}/${maxAuthRetryRecoveries})...`, 'warn'); await recoverCurrentAuthRetryPage({ flow: 'signup', - logLabel: '步骤 5:资料提交后检测到认证重试页,正在点击“重试”恢复', + logLabel: 'Step 5: detected the auth retry page after profile submission, clicking "Retry" to recover', maxClickAttempts: 2, pathPatterns: getStep5AuthRetryPathPatterns(), step: 5, timeoutMs: 12000, }); - debugLog('认证重试页恢复动作已完成,准备继续等待最终结果。', { + debugLog('Auth retry page recovery action is complete; continuing to wait for the final result.', { level: 'info', }); lastSubmitClickAt = Date.now(); @@ -6940,7 +6940,7 @@ async function waitForStep5SubmitOutcome(options = {}) { const successState = getStep5PostSubmitSuccessState(); if (successState) { - debugLog(`检测到资料提交成功状态:${successState.state || 'unknown'}`, { + debugLog(`Detected profile submission success state: ${successState.state || 'unknown'}`, { level: 'ok', }); return successState; @@ -6959,7 +6959,7 @@ async function waitForStep5SubmitOutcome(options = {}) { const submitButton = getStep5SubmitButton(); if (isStep5SubmitButtonClickable(submitButton)) { submitClickCount += 1; - log(`步骤 5:资料提交后仍停留在资料页,正在重新点击“完成帐户创建”(第 ${submitClickCount}/${maxSubmitClicks} 次)...`, 'warn'); + log(`Step 5: after profile submission, the page is still on the profile page, clicking "Complete account creation" again (attempt ${submitClickCount}/${maxSubmitClicks})...`, 'warn'); await humanPause(350, 900); simulateClick(submitButton); lastSubmitClickAt = Date.now(); @@ -6979,7 +6979,7 @@ async function waitForStep5SubmitOutcome(options = {}) { throw createAuthMaxCheckAttemptsError(); } if (finalRetryState) { - throw new Error(`步骤 5:资料提交后仍停留在认证重试页,自动恢复未完成。URL: ${location.href}`); + throw new Error(`Step 5: after profile submission, the page is still on the auth retry page and automatic recovery is not complete. URL: ${location.href}`); } const finalSuccessState = getStep5PostSubmitSuccessState(); @@ -6989,15 +6989,15 @@ async function waitForStep5SubmitOutcome(options = {}) { const finalStep5Error = (typeof getStep5ErrorText === 'function' ? getStep5ErrorText() : '') || lastStep5Error; if (finalStep5Error) { - throw new Error(`步骤 5:资料提交后页面返回错误:${finalStep5Error}。URL: ${location.href}`); + throw new Error(`Step 5: after profile submission, the page returned an error: ${finalStep5Error}. URL: ${location.href}`); } - throw new Error(`步骤 5:资料提交后未检测到页面跳转或恢复成功(已点击提交 ${submitClickCount}/${maxSubmitClicks} 次)。URL: ${location.href}`); + throw new Error(`Step 5: after profile submission, no page transition or recovery success was detected (submit clicked ${submitClickCount}/${maxSubmitClicks} times). URL: ${location.href}`); } async function step5_fillNameBirthday(payload) { const { firstName, lastName, age, year, month, day, prefillOnly = false } = payload; - if (!firstName || !lastName) throw new Error('未提供姓名数据。'); + if (!firstName || !lastName) throw new Error('No name data provided.'); const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { @@ -7009,11 +7009,11 @@ async function step5_fillNameBirthday(payload) { const resolvedAge = age ?? (year ? new Date().getFullYear() - Number(year) : null); const hasBirthdayData = [year, month, day].every(value => value != null && !Number.isNaN(Number(value))); if (!hasBirthdayData && (resolvedAge == null || Number.isNaN(Number(resolvedAge)))) { - throw new Error('未提供生日或年龄数据。'); + throw new Error('No birthday or age data provided.'); } const fullName = `${firstName} ${lastName}`; - log(`步骤 5:正在填写姓名:${fullName}`); + log(`Step 5: filling name: ${fullName}`); // Actual DOM structure: // - Full name: @@ -7028,13 +7028,13 @@ async function step5_fillNameBirthday(payload) { 10000 ); } catch { - throw new Error('未找到姓名输入框。URL: ' + location.href); + throw new Error('No name input box was found. URL: ' + location.href); } await humanPause(500, 1300); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-name' }, async () => { fillInput(nameInput, fullName); }); - log(`步骤 5:姓名已填写:${fullName}`); + log(`Step 5: name filled: ${fullName}`); let birthdayMode = false; let ageInput = null; @@ -7090,7 +7090,7 @@ async function step5_fillNameBirthday(payload) { if (birthdayMode) { if (!hasBirthdayData) { - throw new Error('检测到生日字段,但未提供生日数据。'); + throw new Error('Birthday fields were detected, but no birthday data was provided.'); } const yearSpinner = document.querySelector('[role="spinbutton"][data-type="year"]'); @@ -7104,7 +7104,7 @@ async function step5_fillNameBirthday(payload) { const desiredDate = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const hiddenBirthday = document.querySelector('input[name="birthday"]'); - log('步骤 5:检测到 React Aria 下拉生日字段,正在填写生日...'); + log('Step 5: detected React Aria birthday dropdown fields, filling birthday...'); await humanPause(450, 1100); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-year' }, async () => { await setReactAriaBirthdaySelect(yearReactSelect, year); @@ -7126,15 +7126,15 @@ async function step5_fillNameBirthday(payload) { } if ((hiddenBirthday.value || '') !== desiredDate) { - throw new Error(`生日值未成功写入页面。期望 ${desiredDate},实际 ${(hiddenBirthday.value || '空')}。`); + throw new Error(`Birthday value was not written to the page successfully. Expected ${desiredDate}, got ${(hiddenBirthday.value || 'empty')}.`); } } - log(`步骤 5:React Aria 生日已填写:${desiredDate}`); + log(`Step 5: React Aria birthday filled: ${desiredDate}`); } if (yearSpinner && monthSpinner && daySpinner) { - log('步骤 5:检测到生日字段,正在填写生日...'); + log('Step 5: detected birthday fields, filling birthday...'); async function setSpinButton(el, value) { el.focus(); @@ -7168,7 +7168,7 @@ async function step5_fillNameBirthday(payload) { await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-day' }, async () => { await setSpinButton(daySpinner, String(day).padStart(2, '0')); }); - log(`步骤 5:生日已填写:${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`); + log(`Step 5: birthday filled: ${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`); } const hiddenBirthday = document.querySelector('input[name="birthday"]'); @@ -7179,19 +7179,19 @@ async function step5_fillNameBirthday(payload) { hiddenBirthday.dispatchEvent(new Event('input', { bubbles: true })); hiddenBirthday.dispatchEvent(new Event('change', { bubbles: true })); }); - log(`步骤 5:已设置隐藏生日输入框:${dateStr}`); + log(`Step 5: hidden birthday input set: ${dateStr}`); } } else if (ageInput) { if (resolvedAge == null || Number.isNaN(Number(resolvedAge))) { - throw new Error('检测到年龄字段,但未提供年龄数据。'); + throw new Error('Age field detected, but no age data was provided.'); } await humanPause(500, 1300); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday' }, async () => { fillInput(ageInput, String(resolvedAge)); }); - log(`步骤 5:年龄已填写:${resolvedAge}`); + log(`Step 5: age filled: ${resolvedAge}`); } else { - throw new Error('未找到生日或年龄输入项。URL: ' + location.href); + throw new Error('No birthday or age input was found. URL: ' + location.href); } // 韩国IP判断勾选框""I agree" const allConsentCheckbox = findStep5AllConsentCheckbox(); @@ -7217,18 +7217,18 @@ async function step5_fillNameBirthday(payload) { } if (!isStep5CheckboxChecked(allConsentCheckbox)) { - throw new Error('未能勾选 “I agree to all of the following” 复选框。'); + throw new Error('Could not check the "I agree to all of the following" checkbox.'); } - log('步骤 5:已勾选 “I agree to all of the following”。'); + log('Step 5: checked "I agree to all of the following".'); } else { - log('步骤 5:“I agree to all of the following” 已勾选,跳过。'); + log('Step 5: "I agree to all of the following" is already checked; skipping.'); } } if (prefillOnly) { - log('步骤 4:混合注册页资料已预填,继续填写验证码。', 'info'); + log('Step 4: mixed sign-up page profile data is prefilled; continuing to fill the verification code.', 'info'); return { prefilled: true }; } @@ -7237,12 +7237,12 @@ async function step5_fillNameBirthday(payload) { const completeBtn = await waitForStep5SubmitButton(5000) || await waitForElementByText('button', /完成|完了|作成|アカウント作成|アカウントを作成|続行|続ける|次へ|同意|create|continue|finish|done|agree/i, 5000).catch(() => null); if (!completeBtn) { - throw new Error('未找到“完成帐户创建”按钮。URL: ' + location.href); + throw new Error('No "Complete account creation" button was found. URL: ' + location.href); } const isAgeMode = !birthdayMode && Boolean(ageInput); if (isAgeMode) { - log('步骤 5:当前为年龄输入模式,点击“完成帐户创建”后将等待页面结果。', 'info'); + log('Step 5: age-input mode is active; after clicking "Complete account creation" the code will wait for the page result.', 'info'); } let reportedCompletionPayload = null; @@ -7250,7 +7250,7 @@ async function step5_fillNameBirthday(payload) { ? logStep5SubmitDebug : (message, logOptions = {}) => { if (typeof log === 'function') { - log(`步骤 5 [调试] ${message}`, logOptions?.level || 'info', { + log(`Step 5 [debug] ${message}`, logOptions?.level || 'info', { step: 5, stepKey: 'fill-profile', }); @@ -7260,7 +7260,7 @@ async function step5_fillNameBirthday(payload) { const completionReason = extra?.outcome?.state || (extra?.navigationStarted ? `navigation_started:${extra?.navigationEventType || 'unknown'}` : 'direct_completion'); if (reportedCompletionPayload) { - debugLog(`忽略重复完成信号(reason=${completionReason})。`, { + debugLog(`Ignoring duplicate completion signal (reason=${completionReason}).`, { level: 'warn', }); return reportedCompletionPayload; @@ -7278,7 +7278,7 @@ async function step5_fillNameBirthday(payload) { } else { reportComplete(5, completionPayload); } - debugLog(`准备发送完成信号(reason=${completionReason},isAgeMode=${isAgeMode})。`, { + debugLog(`Preparing to send completion signal (reason=${completionReason}, isAgeMode=${isAgeMode}).`, { level: extra?.navigationStarted ? 'warn' : 'info', }); return completionPayload; @@ -7290,14 +7290,14 @@ async function step5_fillNameBirthday(payload) { await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'submit', label: 'submit-profile' }, async () => { simulateClick(completeBtn); }); - log('步骤 5:已点击“完成帐户创建”,正在等待页面跳转、重试页或提交结果。'); + log('Step 5: clicked "Complete account creation", waiting for page navigation, retry page, or submission result.'); try { const outcome = await waitForStep5SubmitOutcome(); cleanupNavigationReporter(); const completionPayload = completeStep5Once({ outcome }); - log(`步骤 5:资料提交结果已确认(${outcome.state || 'success'}),准备继续后续步骤。`, 'ok'); + log(`Step 5: profile submission result confirmed (${outcome.state || 'success'}); preparing to continue with later steps.`, 'ok'); return completionPayload; } catch (error) { cleanupNavigationReporter(); diff --git a/flows/openai/content/paypal-flow.js b/flows/openai/content/paypal-flow.js index 8f899439..1512a7f6 100644 --- a/flows/openai/content/paypal-flow.js +++ b/flows/openai/content/paypal-flow.js @@ -44,7 +44,7 @@ if (document.documentElement.getAttribute(PAYPAL_FLOW_LISTENER_SENTINEL) !== '1' } }); } else { - console.log('[MultiPage:paypal-flow] 消息监听已存在,跳过重复注册'); + console.log('[MultiPage:paypal-flow] message listener already exists, skipping duplicate registration'); } async function performPayPalOperationWithDelay(metadata, operation) { @@ -68,7 +68,7 @@ async function handlePayPalCommand(message) { case 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP': return runPayPalHostedCheckoutStep(message.payload || {}); default: - throw new Error(`paypal-flow.js 不处理消息:${message.type}`); + throw new Error(`paypal-flow.js does not handle message: ${message.type}`); } } @@ -386,13 +386,13 @@ async function clickHostedSubmitButton(options = {}) { }, { intervalMs: 500, timeoutMs: 15000, - timeoutMessage: 'PayPal hosted checkout 未找到可点击的继续/提交按钮。', + timeoutMessage: 'PayPal hosted checkout did not find a clickable continue/submit button.', }); lastButtonText = getActionText(button); lastDisabled = !isEnabledControl(button); if (lastDisabled) { if (attempt >= maxAttempts) { - throw new Error('PayPal hosted checkout 继续/提交按钮长时间不可用。'); + throw new Error('PayPal hosted checkout continue/submit button stayed unavailable for too long.'); } await sleep(1000); continue; @@ -422,7 +422,7 @@ async function clickHostedEmailNextButton() { }, { intervalMs: 500, timeoutMs: 15000, - timeoutMessage: 'PayPal hosted checkout 未找到邮箱页“下一页”按钮。', + timeoutMessage: 'PayPal hosted checkout did not find the email-page "Next page" button.', }); const buttonText = getActionText(button); await performPayPalOperationWithDelay({ @@ -445,18 +445,18 @@ function normalizeHostedPhoneDigits(value = '') { function verifyHostedPhoneBeforeSubmit(expectedPhone = '') { const phoneInput = document.getElementById('phone'); if (!phoneInput || !isVisibleElement(phoneInput)) { - throw new Error('PayPal hosted checkout 未找到电话输入框。'); + throw new Error('PayPal hosted checkout did not find the phone input.'); } const expectedDigits = normalizeHostedPhoneDigits(expectedPhone || PAYPAL_HOSTED_DEFAULT_PHONE); const renderedDigits = normalizeHostedPhoneDigits(phoneInput.value || ''); if (!expectedDigits) { - throw new Error('PayPal hosted checkout 电话配置为空。'); + throw new Error('PayPal hosted checkout phone configuration is empty.'); } const comparableRenderedDigits = renderedDigits.length > expectedDigits.length ? renderedDigits.slice(-expectedDigits.length) : renderedDigits; if (comparableRenderedDigits !== expectedDigits) { - throw new Error(`PayPal hosted checkout 电话不一致:配置 ${expectedDigits},页面 ${renderedDigits || '(空)'}。`); + throw new Error(`PayPal hosted checkout phone mismatch: configured ${expectedDigits}, page ${renderedDigits || '(empty)'}。`); } return { payloadPhoneDigits: expectedDigits, @@ -473,7 +473,7 @@ async function clickHostedCreateAccount(payload = {}) { }, { intervalMs: 500, timeoutMs: 30000, - timeoutMessage: 'PayPal hosted checkout 未找到创建账号确认按钮。', + timeoutMessage: 'PayPal hosted checkout did not find the create-account confirmation button.', }); await performPayPalOperationWithDelay({ stepKey: getHostedStepKey(PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT), @@ -538,7 +538,7 @@ async function submitHostedLogin(payload = {}) { const email = normalizeText(payload.email || buildHostedRandomEmail()); const emailInput = document.getElementById('email') || findEmailInput(); if (!emailInput) { - throw new Error('PayPal hosted checkout 未找到邮箱输入框。'); + throw new Error('PayPal hosted checkout did not find the email input.'); } refillPayPalEmailInput(emailInput, email); const clickResult = await clickHostedEmailNextButton(); @@ -604,7 +604,7 @@ async function clickHostedReviewConsent() { }, { intervalMs: 500, timeoutMs: 30000, - timeoutMessage: 'PayPal hosted checkout 未找到账单确认按钮。', + timeoutMessage: 'PayPal hosted checkout did not find the billing confirmation button.', }); await performPayPalOperationWithDelay({ stepKey: getHostedStepKey(PAYPAL_HOSTED_STAGE_REVIEW), @@ -736,7 +736,7 @@ async function submitPayPalLogin(payload = {}) { const email = normalizeText(payload.email || ''); const password = String(payload.password || ''); if (!password) { - throw new Error('PayPal 密码为空,请先在侧边栏配置。'); + throw new Error('PayPal password is empty. Configure it in the side panel first.'); } let passwordInput = findPasswordInput(); @@ -774,7 +774,7 @@ async function submitPayPalLogin(payload = {}) { awaiting: 'password_page', }; } else if (!passwordInput && emailInput && !email) { - throw new Error('PayPal 账号为空,请先在侧边栏配置。'); + throw new Error('PayPal account is empty. Configure it in the side panel first.'); } else if (emailInput && email) { await delayOperation({ stepKey: 'paypal-approve', kind: 'fill', label: 'paypal-email' }, async () => { refillPayPalEmailInput(emailInput, email); diff --git a/flows/openai/content/plus-checkout.js b/flows/openai/content/plus-checkout.js index 79e2bd94..c0bf0e63 100644 --- a/flows/openai/content/plus-checkout.js +++ b/flows/openai/content/plus-checkout.js @@ -50,7 +50,7 @@ const PAYMENT_METHOD_CONFIGS = { }, [PLUS_PAYMENT_METHOD_PAYPAL_HOSTED]: { id: PLUS_PAYMENT_METHOD_PAYPAL_HOSTED, - label: 'PayPal 无卡直绑', + label: 'PayPal no-card direct bind', diagnosticLabel: 'PayPal hosted', checkoutMerchantPath: 'openai_llc', billingDetails: { @@ -109,7 +109,7 @@ if (document.documentElement.getAttribute(PLUS_CHECKOUT_LISTENER_SENTINEL) !== ' } }); } else { - console.log('[MultiPage:plus-checkout] 消息监听已存在,跳过重复注册'); + console.log('[MultiPage:plus-checkout] message listener already exists, skipping duplicate registration'); } async function handlePlusCheckoutCommand(message) { @@ -137,13 +137,13 @@ async function handlePlusCheckoutCommand(message) { case 'PLUS_CHECKOUT_GET_STATE': return inspectPlusCheckoutState(message.payload || {}); default: - throw new Error(`plus-checkout.js 不处理消息:${message.type}`); + throw new Error(`plus-checkout.js does not handle message: ${message.type}`); } } async function waitUntil(predicate, options = {}) { const intervalMs = Math.max(50, Math.floor(Number(options.intervalMs) || 250)); - const label = String(options.label || '条件').trim() || '条件'; + const label = String(options.label || 'Condition').trim() || 'Condition'; const timeoutMs = Math.max(0, Math.floor(Number(options.timeoutMs) || 0)); const startedAt = Date.now(); while (true) { @@ -153,7 +153,7 @@ async function waitUntil(predicate, options = {}) { return value; } if (timeoutMs > 0 && Date.now() - startedAt >= timeoutMs) { - throw new Error(`${label}等待超时`); + throw new Error(`${label} timed out`); } await sleep(intervalMs); } @@ -161,7 +161,7 @@ async function waitUntil(predicate, options = {}) { async function waitForDocumentComplete() { await waitUntil(() => document.readyState === 'complete', { - label: '页面加载完成', + label: 'Page load complete', intervalMs: 200, }); await sleep(1000); @@ -236,7 +236,7 @@ function findHostedSubmitButton() { || document.querySelector('button.SubmitButton--complete') || findClickableByText([ /next|continue|pay|subscribe|agree/i, - /下一页|继续|支付|订阅|同意/i, + /下一页|继续|支付|Subscribe|同意/i, ]); } @@ -246,21 +246,21 @@ async function clickHostedSubmitButton() { const candidate = findHostedSubmitButton(); return candidate && isEnabledControl(candidate) && isVisibleElement(candidate) ? candidate : null; }, { - label: 'hosted checkout 提交按钮', + label: 'Hosted checkout submit button', intervalMs: 500, timeoutMs: 15000, }); document.activeElement?.blur?.(); await sleep(300); - const buttonTextBeforeClick = getActionText(button) || '订阅'; - log(`Plus Checkout:准备点击“${buttonTextBeforeClick}”提交 OpenAI Checkout。`); + const buttonTextBeforeClick = getActionText(button) || 'Subscribe'; + log(`Plus Checkout: Preparing to click "${buttonTextBeforeClick}" to submit OpenAI Checkout.`); simulateClick(button); await sleep(300); const buttonTextAfterClick = getActionText(button); if (buttonTextAfterClick && SUBSCRIBE_PROCESSING_TEXT_PATTERN.test(buttonTextAfterClick)) { - log(`Plus Checkout:已点击“${buttonTextBeforeClick}”,按钮进入“${buttonTextAfterClick}”,正在等待 PayPal 跳转。`); + log(`Plus Checkout: Clicked "${buttonTextBeforeClick}", button entered "${buttonTextAfterClick}", waiting for PayPal redirect.`); } else { - log(`Plus Checkout:已点击“${buttonTextBeforeClick}”,正在等待 PayPal 跳转。`); + log(`Plus Checkout: Clicked "${buttonTextBeforeClick}", waiting for PayPal redirect.`); } await sleep(900); return { @@ -275,12 +275,12 @@ async function clickHostedSubmitButton() { function fillHostedOpenAiVerificationCode(verificationCode = '') { const code = String(verificationCode || '').replace(/\D+/g, '').slice(0, 6); if (code.length !== 6) { - throw new Error('hosted checkout OpenAI 验证码无效。'); + throw new Error('Hosted checkout OpenAI verification code is invalid.'); } for (let index = 0; index < 6; index += 1) { const input = document.getElementById(`ci-ciBasic-${index}`); if (!input) { - throw new Error('hosted checkout OpenAI 页面未找到完整的验证码输入框。'); + throw new Error('Hosted checkout OpenAI page did not find a complete verification code input.'); } fillInput(input, code[index]); } @@ -293,7 +293,7 @@ function fillHostedOpenAiVerificationCode(verificationCode = '') { async function runPayPalHostedOpenAiCheckoutStep(payload = {}) { await waitForDocumentComplete(); if (!isPayPalHostedOpenAiCheckoutPage()) { - throw new Error('当前页面不是 PayPal 无卡直绑的 OpenAI hosted checkout 页面。'); + throw new Error('The current page is not the OpenAI hosted checkout page for PayPal no-card direct bind.'); } if (payload.verificationCode) { return fillHostedOpenAiVerificationCode(payload.verificationCode); @@ -741,7 +741,7 @@ function getGoPayCandidateSummaries(limit = 6) { function getPaymentTextPreview(limit = 10) { const seen = new Set(); - const pattern = /gopay|go\s*pay|paypal|card|payment|billing|subscribe|pay|银行卡|付款|支付|账单|订阅/i; + const pattern = /gopay|go\s*pay|paypal|card|payment|billing|subscribe|pay|银行卡|付款|支付|账单|Subscribe/i; return getVisibleControls('button, a, label, [role="button"], [role="radio"], input[type="radio"], input[type="button"], input[type="submit"], [data-testid]') .map((el) => getCombinedSearchText(el)) .filter((text) => text && pattern.test(text)) @@ -784,7 +784,7 @@ function writePaymentMethodDiagnostics(method = PLUS_PAYMENT_METHOD_PAYPAL, reas const diagnostics = getPaymentMethodDiagnostics(config.id, reason); const writer = typeof console[level] === 'function' ? console[level] : console.info; writer.call(console, `[MultiPage:plus-checkout] ${config.diagnosticLabel} diagnostics`, diagnostics); - log(`Plus Checkout:${reason}。${config.label} 候选 ${diagnostics.paymentCandidates.length} 个,银行卡字段${diagnostics.cardFieldsVisible ? '仍可见' : '不可见'}。`, level === 'error' ? 'error' : 'warn'); + log(`Plus Checkout: ${reason}. ${config.label} candidates: ${diagnostics.paymentCandidates.length}. Card fields ${diagnostics.cardFieldsVisible ? 'still visible' : 'not visible'}.`, level === 'error' ? 'error' : 'warn'); return diagnostics; } @@ -810,7 +810,7 @@ function buildPlusCheckoutPayload(paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL) { function buildPlusCheckoutUrl(checkoutSessionId, paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL) { const sessionId = String(checkoutSessionId || '').trim(); if (!sessionId) { - throw new Error('创建 Plus Checkout 失败:未返回 checkout_session_id。'); + throw new Error('Failed to create Plus Checkout: checkout_session_id was not returned.'); } const config = getPaymentMethodConfig(paymentMethod); return `https://chatgpt.com/checkout/${config.checkoutMerchantPath}/${sessionId}`; @@ -841,7 +841,7 @@ function findHostedCheckoutUrl(payload = {}) { async function createPlusCheckoutSession(options = {}) { await waitForDocumentComplete(); - log('Plus:正在读取 ChatGPT 登录会话...'); + log('Plus: Reading ChatGPT login session...'); const sessionResponse = await fetch('/api/auth/session', { credentials: 'include', @@ -849,10 +849,10 @@ async function createPlusCheckoutSession(options = {}) { const session = await sessionResponse.json().catch(() => ({})); const accessToken = session?.accessToken; if (!accessToken) { - throw new Error('请先登录 ChatGPT,当前页面未返回可用 accessToken。'); + throw new Error('Please log in to ChatGPT first. The current page did not return a usable accessToken.'); } - log('Plus:正在创建 checkout 会话...'); + log('Plus: Creating checkout session...'); const paymentMethod = normalizePlusPaymentMethod(options.paymentMethod); const checkoutPayload = buildPlusCheckoutPayload(paymentMethod); const response = await fetch('https://chatgpt.com/backend-api/payments/checkout', { @@ -868,7 +868,7 @@ async function createPlusCheckoutSession(options = {}) { const data = await response.json().catch(() => ({})); if (!response.ok || !data?.checkout_session_id) { const detail = data?.detail || data?.message || `HTTP ${response.status}`; - throw new Error(`创建 Plus Checkout 失败:${detail}`); + throw new Error(`Failed to create Plus Checkout: ${detail}`); } const checkoutUrl = buildPlusCheckoutUrl(data.checkout_session_id, paymentMethod); @@ -898,25 +898,25 @@ async function selectPaymentMethod(method = PLUS_PAYMENT_METHOD_PAYPAL) { const now = Date.now(); if (!lastDiagnosticsAt || now - lastDiagnosticsAt >= PAYPAL_DIAGNOSTIC_LOG_INTERVAL_MS) { lastDiagnosticsAt = now; - writePaymentMethodDiagnostics(config.id, `正在等待可点击的 ${config.label} 付款方式`, 'warn'); + writePaymentMethodDiagnostics(config.id, `Waiting for a clickable ${config.label} payment method`, 'warn'); } return null; }, { - label: `${config.label} 付款方式`, + label: `${config.label} payment method`, intervalMs: 250, }); console.info(`[MultiPage:plus-checkout] ${config.label} target selected`, summarizeElementForDebug(target)); await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'select', label: 'select-payment-method' }, async () => { simulateClick(target); }); - log(`Plus Checkout:已点击 ${config.label} 付款方式,正在确认选中状态。`); + log(`Plus Checkout: Clicked ${config.label} payment method. Confirming selected state.`); if (!await waitForPaymentMethodActive(config.id)) { - const diagnostics = writePaymentMethodDiagnostics(config.id, `点击 ${config.label} 后页面仍未进入 ${config.label} 账单表单`, 'error'); - throw new Error(`Plus Checkout:已尝试点击 ${config.label},但页面未切换到 ${config.label} 表单。请提供控制台 ${config.label} diagnostics 结构。候选数量:${diagnostics.paymentCandidates.length},银行卡字段仍可见:${diagnostics.cardFieldsVisible ? '是' : '否'}。`); + const diagnostics = writePaymentMethodDiagnostics(config.id, `After clicking ${config.label}, the page still did not enter the ${config.label} billing form`, 'error'); + throw new Error(`Plus Checkout: Tried clicking ${config.label}, but the page did not switch to the ${config.label} form. Please provide the console ${config.label} diagnostics structure. Candidate count: ${diagnostics.paymentCandidates.length}, card fields still visible: ${diagnostics.cardFieldsVisible ? 'Yes' : 'No'}.`); } - log(`Plus Checkout:已确认 ${config.label} 付款方式生效。`); + log(`Plus Checkout: Confirmed ${config.label} payment method is active.`); return true; } @@ -986,7 +986,7 @@ function isLikelyAddressSearchInput(input) { if (isNonAddressSearchInput(input)) { return false; } - if (/name|email|e-mail|phone|tel|password|coupon|promo|country|region|postal|zip|city|state|province|card|card\s*number|expiry|expiration|security|cvc|cvv|cc-|全名|姓名|邮箱|电话|密码|国家|地区|邮编|城市|省|州|银行卡|卡号|有效期|安全码/i.test(text)) { + if (/name|email|e-mail|phone|tel|password|coupon|promo|country|region|postal|zip|city|state|province|card|card\s*number|expiry|expiration|security|cvc|cvv|cc-|全名|姓名|邮箱|电话|密码|国家|地区|Postal code|City|省|州|银行卡|卡号|有效期|安全码/i.test(text)) { return false; } if (/address|street|billing|search|line\s*1|地址|街道|账单/i.test(text)) { @@ -1088,13 +1088,13 @@ async function findAddressSearchInput() { /address|street|billing|search|line\s*1/i, /地址|街道|账单/i, ], { - exclude: (input) => /city|state|province|postal|zip|country|城市|省|州|邮编|国家|地区/i.test(getFieldText(input)), + exclude: (input) => /city|state|province|postal|zip|country|City|省|州|Postal code|国家|地区/i.test(getFieldText(input)), }); if (direct && !isNonAddressSearchInput(direct)) return direct; const candidates = getVisibleTextInputs().filter(isLikelyAddressSearchInput); return candidates[0] || null; }, { - label: '地址搜索输入框', + label: 'Address search input', intervalMs: 250, }); } @@ -1133,7 +1133,7 @@ async function clickAddressSuggestion(seed = {}) { const options = getAddressSuggestions(); return options.length ? options : null; }, { - label: '地址推荐列表', + label: 'Address suggestion list', intervalMs: 250, timeoutMs: 6000, }); @@ -1436,7 +1436,7 @@ function getStructuredAddressFields() { ]); const city = findInputByFieldText([ /city|town|suburb|locality|address[_-]?level[_-]?2|address\[city\]/i, - /城市|市区|区市町村|市区町村|市町村/i, + /City|市区|区市町村|市区町村|市町村/i, ]); const region = findInputByFieldText([ /state|province|region|county|prefecture|administrative|administrative[_-]?area|address[_-]?level[_-]?1|address\[state\]/i, @@ -1444,7 +1444,7 @@ function getStructuredAddressFields() { ]); const postalCode = findInputByFieldText([ /postal|zip|postcode|postal[_-]?code|zip[_-]?code|address\[postal_code\]/i, - /邮编|邮政|郵便番号/i, + /Postal code|邮政|郵便番号/i, ]); return { address1, address2, city, region, postalCode }; } @@ -1481,7 +1481,7 @@ async function ensureStructuredAddress(seed, options = {}) { } return null; }, { - label: '结构化账单地址字段', + label: 'Structured billing address fields', intervalMs: 250, timeoutMs: 6000, }); @@ -1494,11 +1494,11 @@ async function ensureStructuredAddress(seed, options = {}) { const latest = getStructuredAddressFields(); const missing = []; - if (!String(latest.address1?.value || '').trim()) missing.push('地址1'); - if (!String(latest.city?.value || '').trim()) missing.push('城市'); - if (!String(latest.postalCode?.value || '').trim()) missing.push('邮编'); + if (!String(latest.address1?.value || '').trim()) missing.push('Address line 1'); + if (!String(latest.city?.value || '').trim()) missing.push('City'); + if (!String(latest.postalCode?.value || '').trim()) missing.push('Postal code'); if (missing.length) { - throw new Error(`Plus Checkout:账单地址字段未填写完整:${missing.join('、')}。`); + throw new Error(`Plus Checkout: Billing address fields are incomplete: ${missing.join('、')}.`); } return { @@ -1513,14 +1513,14 @@ function findSubscribeButton() { const submitButtons = getVisibleControls('button[type="submit"], input[type="submit"]'); const exactSubmit = submitButtons.find((button) => ( isEnabledControl(button) - && /订阅|subscribe|购买\s*ChatGPT\s*Plus|start\s*subscription|place\s*order/i.test(getCombinedSearchText(button)) + && /Subscribe|subscribe|购买\s*ChatGPT\s*Plus|start\s*subscription|place\s*order/i.test(getCombinedSearchText(button)) )); if (exactSubmit) { return exactSubmit; } return findClickableByText([ - /订阅|继续|确认|支付/i, + /Subscribe|继续|确认|支付/i, /subscribe|continue|confirm|pay|start\s*subscription|place\s*order/i, ]); } @@ -1584,7 +1584,7 @@ function getAssociatedForm(button) { async function humanLikeClick(el) { throwIfStopped(); if (!el) { - throw new Error('无法点击空元素。'); + throw new Error('Cannot click an empty element.'); } el.scrollIntoView?.({ block: 'center', inline: 'center', behavior: 'instant' }); @@ -1647,8 +1647,8 @@ async function humanLikeClick(el) { form.requestSubmit(el); } - console.log('[MultiPage:plus-checkout] 已执行拟人工点击', summarizeElementForDebug(el)); - log(`已拟人工点击 [${el.tagName}] "${el.textContent?.trim().slice(0, 30) || ''}"`); + console.log('[MultiPage:plus-checkout] Executed simulated human click', summarizeElementForDebug(el)); + log(`Simulated human click executed [${el.tagName}] "${el.textContent?.trim().slice(0, 30) || ''}"`); } async function fillPlusBillingAndSubmit(payload = {}) { @@ -1759,13 +1759,13 @@ async function clickPlusSubscribe(payload = {}) { const button = findSubscribeButton(); return button || null; }, { - label: '订阅按钮', + label: 'Subscribe button', intervalMs: 250, timeoutMs: 10000, }); const buttonState = getSubscribeButtonState(subscribeButton); if (!buttonState.ready) { - log(`订阅按钮当前状态 [${buttonState.status}] "${buttonState.text.slice(0, 40)}",本轮不点击`); + log(`Current subscribe button state [${buttonState.status}] "${buttonState.text.slice(0, 40)}", not clicking in this round`); return { clicked: false, subscribeButtonBusy: buttonState.busy, diff --git a/flows/openai/content/sub2api-panel.js b/flows/openai/content/sub2api-panel.js index a9ef9a5a..c5b28ef0 100644 --- a/flows/openai/content/sub2api-panel.js +++ b/flows/openai/content/sub2api-panel.js @@ -1,4 +1,4 @@ -// flows/openai/content/sub2api-panel.js — 页内脚本:SUB2API 后台(OAuth 生成与回调提交) +// flows/openai/content/sub2api-panel.js — In-page script: SUB2API admin (OAuth generation and callback submission) console.log('[MultiPage:sub2api-panel] Content script loaded on', location.href); @@ -24,7 +24,7 @@ if (document.documentElement.getAttribute(SUB2API_PANEL_LISTENER_SENTINEL) !== ' }).catch((err) => { if (isStopError(err)) { if (message.payload?.visibleStep || message.step) { - log('已被用户停止。', 'warn', { step: message.payload?.visibleStep || message.step }); + log('Stopped by user.', 'warn', { step: message.payload?.visibleStep || message.step }); } sendResponse({ stopped: true, error: err.message }); return; @@ -38,7 +38,7 @@ if (document.documentElement.getAttribute(SUB2API_PANEL_LISTENER_SENTINEL) !== ' } }); } else { - console.log('[MultiPage:sub2api-panel] 消息监听已存在,跳过重复注册'); + console.log('[MultiPage:sub2api-panel] message listener already exists, skipping duplicate registration'); } function getSub2ApiOrigin(payload = {}) { @@ -58,7 +58,7 @@ function normalizeRedirectUri() { parsed.pathname = '/auth/callback'; } if (parsed.pathname !== '/auth/callback') { - throw new Error('SUB2API 回调地址必须是 /auth/callback,例如 http://localhost:1455/auth/callback'); + throw new Error('SUB2API callback URL must be /auth/callback, for example http://localhost:1455/auth/callback'); } return parsed.toString(); } @@ -72,7 +72,7 @@ async function handleStep(step, payload = {}) { case 13: return step9_submitOpenAiCallback({ ...(payload || {}), visibleStep: step }); default: - throw new Error(`sub2api-panel.js 不处理步骤 ${step}`); + throw new Error(`sub2api-panel.js does not handle step ${step}`); } } @@ -82,7 +82,7 @@ async function handleNode(nodeId, payload = {}) { case 'platform-verify': return step9_submitOpenAiCallback(payload); default: - throw new Error(`sub2api-panel.js 不处理节点 ${normalizedNodeId}`); + throw new Error(`sub2api-panel.js does not handle node ${normalizedNodeId}`); } } @@ -120,11 +120,11 @@ async function requestJson(origin, path, options = {}) { if (json.code === 0) { return json.data; } - throw new Error(json.message || json.detail || `请求失败(${path})`); + throw new Error(json.message || json.detail || `request failed (${path})`); } if (!response.ok) { - throw new Error((json && (json.message || json.detail)) || `请求失败(HTTP ${response.status}):${path}`); + throw new Error((json && (json.message || json.detail)) || `request failed (HTTP ${response.status}): ${path}`); } return json; @@ -132,7 +132,7 @@ async function requestJson(origin, path, options = {}) { function storeAuthSession(loginData) { if (!loginData?.access_token) { - throw new Error('SUB2API 登录返回缺少 access_token。'); + throw new Error('SUB2API login response is missing access_token.'); } localStorage.setItem('auth_token', loginData.access_token); @@ -156,13 +156,13 @@ async function loginSub2Api(payload = {}) { const origin = getSub2ApiOrigin(payload); if (!email) { - throw new Error('缺少 SUB2API 登录邮箱,请先在侧边栏填写。'); + throw new Error('SUB2API login email is missing. Fill it in the side panel first.'); } if (!password) { - throw new Error('缺少 SUB2API 登录密码,请先在侧边栏填写。'); + throw new Error('SUB2API login password is missing. Fill it in the side panel first.'); } - log('步骤:正在登录 SUB2API 后台...'); + log('Step: Logging in to the SUB2API admin...'); const loginData = await requestJson(origin, '/api/v1/auth/login', { method: 'POST', body: { @@ -195,7 +195,7 @@ async function getGroupByName(origin, token, groupName) { }); if (!group) { - throw new Error(`SUB2API 中未找到名为“${targetName}”的 openai 分组。`); + throw new Error(`No openai group named "${targetName}" was found in SUB2API.`); } return group; @@ -242,7 +242,7 @@ async function getGroupsByNames(origin, token, groupNames) { } if (missing.length) { - throw new Error(`SUB2API 中未找到以下 openai 分组:${missing.join('、')}。`); + throw new Error(`The following openai groups were not found in SUB2API: ${missing.join(', ')}。`); } return matched; @@ -272,7 +272,7 @@ function resolveSub2ApiAccountPriority(payload = {}, backgroundState = {}) { } const numeric = Number(rawValue); if (!Number.isSafeInteger(numeric) || numeric < 1) { - throw new Error('SUB2API 账号优先级必须是大于等于 1 的整数。'); + throw new Error('SUB2API account priority must be an integer greater than or equal to 1.'); } return numeric; } @@ -296,7 +296,7 @@ function buildProxyDisplayName(proxy = {}) { const port = proxy.port === undefined || proxy.port === null ? '' : String(proxy.port).trim(); const address = protocol && host && port ? `${protocol}://${host}:${port}` : ''; const parts = [ - name || '(未命名代理)', + name || '(Unnamed proxy)', id ? `#${id}` : '', address, ].filter(Boolean); @@ -378,7 +378,7 @@ async function resolveSub2ApiProxy(origin, token, preference = '') { token, }); if (!Array.isArray(proxies)) { - throw new Error('SUB2API 代理列表返回格式异常,无法自动选择代理。'); + throw new Error('SUB2API proxy list returned an unexpected format. Cannot auto-select a proxy.'); } const { proxy, reason, candidates } = findSub2ApiProxy(proxies, preference); @@ -386,24 +386,24 @@ async function resolveSub2ApiProxy(origin, token, preference = '') { return proxy; } - const configured = normalizeSub2ApiProxyPreference(preference) || '(未配置)'; + const configured = normalizeSub2ApiProxyPreference(preference) || '(Not configured)'; const available = (candidates || []) .slice(0, 8) .map(buildProxyDisplayName) - .join(',') || '无可用代理'; + .join(',') || 'No available proxy'; if (reason === 'ambiguous-name' || reason === 'ambiguous-fuzzy') { - throw new Error(`SUB2API 默认代理“${configured}”匹配到多个代理,请改填代理 ID。候选:${available}`); + throw new Error(`SUB2API default proxy "${configured}" matched multiple proxies. Please enter a proxy ID instead. Candidates: ${available}`); } if (reason === 'missing-id') { - throw new Error(`SUB2API 默认代理 ID “${configured}”不存在或未启用。可用代理:${available}`); + throw new Error(`SUB2API default proxy ID "${configured}" does not exist or is not enabled. Available proxies: ${available}`); } if (reason === 'missing-name') { - throw new Error(`SUB2API 默认代理“${configured}”不存在或未启用。可用代理:${available}`); + throw new Error(`SUB2API default proxy "${configured}" does not exist or is not enabled. Available proxies: ${available}`); } if (reason === 'no-preference') { - throw new Error(`SUB2API 存在多个可用代理,请在侧边栏填写默认代理名称或 ID;留空则不使用代理。可用代理:${available}`); + throw new Error(`SUB2API has multiple available proxies. Fill in the default proxy name or ID in the side panel; leave it blank to disable proxy use. Available proxies: ${available}`); } - throw new Error('SUB2API 没有可用代理;请检查默认代理配置,或将其留空以禁用代理。'); + throw new Error('SUB2API has no available proxies. Check the default proxy configuration, or leave it blank to disable proxy use.'); } function buildDraftAccountName(groupName) { @@ -429,23 +429,23 @@ function parseLocalhostCallback(rawUrl, visibleStep = 10) { try { parsed = new URL(rawUrl); } catch { - throw new Error('提供的回调 URL 不是合法链接。'); + throw new Error('The provided callback URL is not a valid link.'); } if (!['http:', 'https:'].includes(parsed.protocol)) { - throw new Error('回调 URL 协议不正确。'); + throw new Error('Callback URL protocol is invalid.'); } if (!['localhost', '127.0.0.1'].includes(parsed.hostname)) { - throw new Error(`步骤 ${visibleStep} 只接受 localhost / 127.0.0.1 回调地址。`); + throw new Error(`Step ${visibleStep} only accepts localhost / 127.0.0.1 callback URLs.`); } if (parsed.pathname !== '/auth/callback') { - throw new Error('回调 URL 路径必须是 /auth/callback。'); + throw new Error('Callback URL path must be /auth/callback.'); } const code = (parsed.searchParams.get('code') || '').trim(); const state = (parsed.searchParams.get('state') || '').trim(); if (!code || !state) { - throw new Error('回调 URL 中缺少 code 或 state。'); + throw new Error('Callback URL is missing code or state.'); } return { @@ -477,7 +477,7 @@ function buildOpenAiCredentials(exchangeData) { } if (!credentials.access_token) { - throw new Error('SUB2API 交换授权码后未返回 access_token。'); + throw new Error('SUB2API did not return access_token after exchanging the authorization code.'); } return credentials; @@ -530,15 +530,15 @@ async function step1_generateOpenAiAuthUrl(payload = {}, options = {}) { const proxy = proxyPreference ? await resolveSub2ApiProxy(origin, token, proxyPreference) : null; const proxyId = normalizeProxyId(proxy?.id); const draftName = buildDraftAccountName(group.name || groupName); - const groupLabel = groups.map((item) => `${item.name}(#${item.id})`).join('、'); + const groupLabel = groups.map((item) => `${item.name}(#${item.id})`).join(', '); - log(`步骤 ${logStep}:已登录 SUB2API,使用分组 ${groupLabel}。`); + log(`Step ${logStep}:Logged in to SUB2API, using group ${groupLabel}。`); if (proxy) { - log(`步骤 ${logStep}:已选择 SUB2API 默认代理 ${buildProxyDisplayName(proxy)}。`); + log(`Step ${logStep}:Selected SUB2API default proxy ${buildProxyDisplayName(proxy)}。`); } else { - log(`步骤 ${logStep}:未配置 SUB2API 默认代理,本次将不使用代理。`); + log(`Step ${logStep}:SUB2API default proxy is not configured. This run will not use a proxy.`); } - log(`步骤 ${logStep}:正在向 SUB2API 生成 OpenAI Auth 链接,回调地址为 ${redirectUri}。`); + log(`Step ${logStep}:Generating OpenAI Auth URL in SUB2API. Callback URL: ${redirectUri}。`); const authRequestBody = { redirect_uri: redirectUri, @@ -558,10 +558,10 @@ async function step1_generateOpenAiAuthUrl(payload = {}, options = {}) { const oauthState = String(authData?.state || extractStateFromAuthUrl(oauthUrl)).trim(); if (!oauthUrl || !sessionId) { - throw new Error('SUB2API 未返回完整的 auth_url / session_id。'); + throw new Error('SUB2API did not return a complete auth_url / session_id.'); } - log(`步骤 ${logStep}:已获取 SUB2API OAuth 链接:${oauthUrl.slice(0, 96)}...`, 'ok'); + log(`Step ${logStep}:Obtained SUB2API OAuth URL: ${oauthUrl.slice(0, 96)}...`, 'ok'); const result = { oauthUrl, sub2apiSessionId: sessionId, @@ -607,17 +607,17 @@ async function step9_submitOpenAiCallback(payload = {}) { : await getGroupsByNames(origin, token, payload.sub2apiGroupName || backgroundState.sub2apiGroupName || SUB2API_DEFAULT_GROUP_NAME)); if (!sessionId) { - throw new Error('缺少 SUB2API session_id,请重新执行步骤 1。'); + throw new Error('Missing SUB2API session_id. Rerun Step 1.'); } if (expectedState && expectedState !== callback.state) { - throw new Error('本次 localhost 回调中的 state 与步骤 1 生成的 state 不一致,请重新执行步骤 1。'); + throw new Error('The state in this localhost callback does not match the state generated in Step 1. Rerun Step 1.'); } - log('正在向 SUB2API 交换 OpenAI 授权码...', 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log('Exchanging the OpenAI authorization code with SUB2API...', 'info', { step: visibleStep, stepKey: 'platform-verify' }); if (proxy) { - log(`使用 SUB2API 默认代理 ${buildProxyDisplayName(proxy)}。`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log(`Using SUB2API default proxy ${buildProxyDisplayName(proxy)}。`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); } else { - log('未配置 SUB2API 默认代理,本次将不使用代理。', 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log('SUB2API default proxy is not configured. This run will not use a proxy.', 'info', { step: visibleStep, stepKey: 'platform-verify' }); } const exchangeRequestBody = { session_id: sessionId, @@ -640,7 +640,7 @@ async function step9_submitOpenAiCallback(payload = {}) { .map((group) => Number(group.id)) .filter((id) => Number.isFinite(id) && id > 0); if (!groupIds.length) { - throw new Error('SUB2API 返回的目标分组 ID 无效。'); + throw new Error('SUB2API returned an invalid target group ID.'); } const accountName = resolvedEmail || flowEmail @@ -666,14 +666,14 @@ async function step9_submitOpenAiCallback(payload = {}) { createPayload.extra = extra; } - log(`授权码交换成功,正在创建 SUB2API 账号(名称:${accountName})...`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log(`Authorization code exchange succeeded. Creating SUB2API account (name: ${accountName})...`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); const createdAccount = await requestJson(origin, '/api/v1/admin/accounts', { method: 'POST', token, body: createPayload, }); - const verifiedStatus = `SUB2API 已创建账号 #${createdAccount?.id || 'unknown'}`; + const verifiedStatus = `SUB2API account created #${createdAccount?.id || 'unknown'}`; log(verifiedStatus, 'ok', { step: visibleStep, stepKey: 'platform-verify' }); reportComplete('platform-verify', { localhostUrl: callback.url, diff --git a/flows/openai/content/vps-panel.js b/flows/openai/content/vps-panel.js index 0a1d9df0..2808ec81 100644 --- a/flows/openai/content/vps-panel.js +++ b/flows/openai/content/vps-panel.js @@ -5,24 +5,24 @@ //
//
// Codex OAuth -// +// //
//
//
-//
授权链接:
+//
Authorization link:
//
https://auth.openai.com/...
//
-// -// +// +// //
//
//
// -// -//
回调 URL 已提交,等待认证中...
-//
回调 URL 提交失败: ...
+// +//
Callback URL submitted, waiting for authentication...
+//
Callback URL submission failed: ...
//
-//
等待认证中... / 认证成功! / 认证失败: ...
+//
Waiting for authentication... / Authentication successful! / Authentication failed: ...
//
//
@@ -66,7 +66,7 @@ if (document.documentElement.getAttribute(VPS_PANEL_LISTENER_SENTINEL) !== '1') }); if (isStopError(err)) { if (message.payload?.visibleStep || message.step) { - log('已被用户停止。', 'warn', { step: message.payload?.visibleStep || message.step }); + log('Stopped by user.', 'warn', { step: message.payload?.visibleStep || message.step }); } sendResponse({ stopped: true, error: err.message }); return; @@ -80,7 +80,7 @@ if (document.documentElement.getAttribute(VPS_PANEL_LISTENER_SENTINEL) !== '1') } }); } else { - console.log('[MultiPage:vps-panel] 消息监听已存在,跳过重复注册'); + console.log('[MultiPage:vps-panel] message listener already exists, skipping duplicate registration'); } async function handleStep(step, payload) { @@ -91,7 +91,7 @@ async function handleStep(step, payload) { case 13: return await step9_vpsVerify({ ...(payload || {}), visibleStep: step }); default: - throw new Error(`vps-panel.js 不处理步骤 ${step}`); + throw new Error(`vps-panel.js does not handle step ${step}`); } } @@ -101,7 +101,7 @@ async function handleNode(nodeId, payload = {}) { case 'platform-verify': return await step9_vpsVerify(payload); default: - throw new Error(`vps-panel.js 不处理节点 ${normalizedNodeId}`); + throw new Error(`vps-panel.js does not handle node ${normalizedNodeId}`); } } @@ -232,10 +232,10 @@ function getStatusBadgeEntries() { } function summarizeStatusBadgeEntries(entries) { - if (!entries.length) return '无可见状态徽标'; + if (!entries.length) return 'No visible status badge'; return entries .map((entry, index) => { - const text = entry.text || '(空文本)'; + const text = entry.text || '(empty text)'; const className = entry.className ? ` class=${getInlineTextSnippet(entry.className, 80)}` : ''; const errorVisual = entry.errorVisualSummary ? ` error=${getInlineTextSnippet(entry.errorVisualSummary, 80)}` : ''; return `#${index + 1}="${getInlineTextSnippet(text, 80)}"${className}${errorVisual}`; @@ -246,7 +246,7 @@ function summarizeStatusBadgeEntries(entries) { const STEP9_SUCCESS_STATUSES = new Set([ 'Authentication successful!', 'Аутентификация успешна!', - '认证成功!', + 'Authentication successful!', ]); function normalizeStep9StatusText(statusText) { @@ -447,7 +447,7 @@ function getStep9PageErrorEntries() { return entries; } -function formatStep10StatusSummaryValue(text, emptyText = '无') { +function formatStep10StatusSummaryValue(text, emptyText = 'None') { return text ? `"${getInlineTextSnippet(text, 80)}"` : emptyText; } @@ -459,9 +459,9 @@ function isStep10BrowserSwitchRequiredConflict(diagnostics = {}) { function getStep10BrowserSwitchRequiredMessage(diagnostics = {}) { const callbackFailureText = normalizeStep9StatusText(diagnostics?.callbackFailureText || ''); return [ - '检测到 CPA 页面同时显示“认证成功”和“回调 URL 提交失败: 请更新CLI Proxy API或检查连接”。', - '这通常不是浏览器问题,而是 CPA 项目会清理多线程 OAuth 会话。CPA 项目无法使用多线程,请修改 CPA 服务器或改为单线程注册。', - callbackFailureText ? `面板原文:${callbackFailureText}` : '', + 'Detected that the CPA page is showing both "Authentication successful" and "Callback URL submission failed: Please update CLI Proxy API or check the connection".', + 'This is usually not a browser issue. CPA clears multi-threaded OAuth sessions, so CPA cannot use multi-threaded OAuth. Update the CPA server or switch to single-threaded registration.', + callbackFailureText ? `Panel text: ${callbackFailureText}` : '', ].filter(Boolean).join(' '); } @@ -494,8 +494,8 @@ function buildStep9StatusDiagnostics(entries = [], pageErrorEntries = [], pageSn const failureSummary = summarizeStatusBadgeEntries(failureEntries); const pageErrorSummary = summarizeStatusBadgeEntries(pageErrorEntries); const errorStyledSummary = summarizeStatusBadgeEntries(errorStyledEntries); - const extraFailureSuffix = pageErrorEntries.length ? `;额外错误提示:${pageErrorSummary}` : ''; - const errorStyledSuffix = errorStyledEntries.length ? `;红色/错误样式徽标:${errorStyledSummary}` : ''; + const extraFailureSuffix = pageErrorEntries.length ? `;Additional error hint: ${pageErrorSummary}` : ''; + const errorStyledSuffix = errorStyledEntries.length ? `;Red/error-styled badge: ${errorStyledSummary}` : ''; return { selectedText, @@ -536,8 +536,8 @@ function buildStep9StatusDiagnostics(entries = [], pageErrorEntries = [], pageSn errorStyledSummary, }), summary: selectedText - ? `当前聚焦状态=${formatStep10StatusSummaryValue(selectedText)};回调提示=${formatStep10StatusSummaryValue(callbackEntries[0]?.text || '')};主状态=${formatStep10StatusSummaryValue(mainEntries[0]?.text || '')};页面错误=${formatStep10StatusSummaryValue(pageErrorEntries[0]?.text || '')};可见徽标 ${visibleEntries.length} 个:${visibleSummary}${extraFailureSuffix}${errorStyledSuffix}` - : `当前未选中任何可见状态;回调提示=${formatStep10StatusSummaryValue(callbackEntries[0]?.text || '')};主状态=${formatStep10StatusSummaryValue(mainEntries[0]?.text || '')};页面错误=${formatStep10StatusSummaryValue(pageErrorEntries[0]?.text || '')};可见徽标 ${visibleEntries.length} 个:${visibleSummary}${extraFailureSuffix}${errorStyledSuffix};页面片段="${getInlineTextSnippet(pageSnippet, 120)}"`, + ? `Current focused state=${formatStep10StatusSummaryValue(selectedText)};callback hint=${formatStep10StatusSummaryValue(callbackEntries[0]?.text || '')};main state=${formatStep10StatusSummaryValue(mainEntries[0]?.text || '')};page error=${formatStep10StatusSummaryValue(pageErrorEntries[0]?.text || '')};visible badges: ${visibleEntries.length} | ${visibleSummary}${extraFailureSuffix}${errorStyledSuffix}` + : `No visible status is currently selected; callback hint=${formatStep10StatusSummaryValue(callbackEntries[0]?.text || '')};main state=${formatStep10StatusSummaryValue(mainEntries[0]?.text || '')};page error=${formatStep10StatusSummaryValue(pageErrorEntries[0]?.text || '')};visible badges: ${visibleEntries.length} | ${visibleSummary}${extraFailureSuffix}${errorStyledSuffix};page snippet="${getInlineTextSnippet(pageSnippet, 120)}"`, }; } @@ -575,105 +575,105 @@ function explainStep10Failure(statusText, sourceKind = 'unknown') { const rawText = normalizeStep9StatusText(statusText); const detail = extractStep10FailureDetail(rawText, sourceKind) || rawText; const phaseLabel = sourceKind === 'callback' - ? '回调提交阶段' + ? 'Callback submission stage' : sourceKind === 'main' - ? '认证结果阶段' - : '页面状态阶段'; + ? 'Authentication result stage' + : 'Page state stage'; const rules = [ { code: 'callback_submit_api_unavailable', pattern: /请更新\s*cli\s*proxy\s*api\s*或检查连接/i, - message: 'CPA 面板无法把回调提交给后台,通常是 CLI Proxy API 版本过旧、管理接口未启动,或当前面板与后端连接异常。', + message: 'The CPA panel cannot submit the callback to the backend. Usually the CLI Proxy API is outdated, the admin interface is not running, or the panel connection to the backend is broken.', }, { code: 'oauth_state_expired', pattern: /unknown or expired state/i, - message: '当前 OAuth 会话在 CPA 中已不存在或已过期,通常是使用了旧回调链接、刷新过新的授权链接后仍提交旧链接,或 CPA 刚重启过。', + message: 'The current OAuth session no longer exists or has expired in CPA. Usually this means an old callback link was used, an old link was submitted after refreshing a new auth link, or CPA just restarted.', }, { code: 'oauth_not_pending', pattern: /oauth flow is not pending/i, - message: '当前 OAuth 会话已经不在等待状态,通常是重复提交、提交过慢,或这轮认证此前已经结束。', + message: 'The current OAuth session is no longer in a waiting state. Usually this means the callback was submitted repeatedly, submitted too slowly, or this authentication round already ended.', }, { code: 'callback_state_invalid', pattern: /invalid state|state is required|missing_state/i, - message: '回调链接里的 state 缺失或无效,通常是复制了不完整的 localhost 回调链接,或提交了不属于这一轮的旧链接。', + message: 'The callback link state is missing or invalid. Usually this means an incomplete localhost callback link was copied, or an old link from another round was submitted.', }, { code: 'callback_missing_result', pattern: /code or error is required/i, - message: '回调链接里既没有授权码,也没有错误信息,通常是复制的 localhost 回调链接不完整。', + message: 'The callback link contains neither an authorization code nor an error message. Usually the copied localhost callback link was incomplete.', }, { code: 'callback_invalid_url', pattern: /invalid redirect_url/i, - message: '提交给 CPA 的回调链接格式无法解析,通常是粘贴内容不完整、带了多余字符,或并不是 localhost OAuth 回调地址。', + message: 'The callback URL submitted to CPA cannot be parsed. Usually the pasted content is incomplete, contains extra characters, or is not a localhost OAuth callback URL.', }, { code: 'callback_provider_mismatch', pattern: /provider does not match state/i, - message: '这条回调不属于当前这次 Codex OAuth,会话与回调来源对不上,通常是混用了其他轮次或其他提供方的回调。', + message: 'This callback does not belong to the current Codex OAuth round. The session and callback source do not match, usually because callbacks from other rounds or providers were mixed in.', }, { code: 'callback_persist_failed', pattern: /failed to persist oauth callback/i, - message: 'CPA 已收到回调,但无法把回调结果写入本地缓存文件,通常是认证目录权限、磁盘或运行环境异常。', + message: 'CPA received the callback, but cannot write the callback result to the local cache file. Usually this is caused by auth directory permissions, disk issues, or an environment problem.', }, { code: 'oauth_bad_request', pattern: /^bad request$/i, - message: 'CPA 已收到回调,但 OpenAI OAuth 回调本身返回了错误。常见于用户取消授权、请求过期,或这条回调已经失效。', + message: 'CPA received the callback, but the OpenAI OAuth callback itself returned an error. Common causes are user-canceled authorization, expired requests, or an already-invalid callback.', }, { code: 'oauth_state_mismatch', pattern: /state code error/i, - message: 'CPA 校验到回调里的 state 与当前 OAuth 会话不一致,通常是授权链接已刷新,但平台回调验证仍提交旧回调。', + message: 'CPA detected that the state in the callback does not match the current OAuth session. Usually the auth link was refreshed, but platform callback verification still submitted the old callback.', }, { code: 'oauth_code_exchange_failed', pattern: /failed to exchange authorization code for tokens/i, - message: 'CPA 已收到授权码,但向 OpenAI 交换令牌失败。常见于 CPA 到 OpenAI 的网络或代理异常,或授权码已过期。', + message: 'CPA received the authorization code, but failed to exchange it with OpenAI for tokens. Common causes are CPA-to-OpenAI network or proxy issues, or an expired authorization code.', }, { code: 'oauth_token_save_failed', pattern: /failed to save authentication tokens/i, - message: 'CPA 已完成认证,但保存认证文件失败。常见于认证目录权限、磁盘写入,或 post-auth hook 异常。', + message: 'CPA completed authentication, but saving the auth file failed. Common causes are auth directory permissions, disk writes, or a post-auth hook issue.', }, { code: 'oauth_callback_timeout', pattern: /timeout waiting for oauth callback|oauth flow timed out/i, - message: 'CPA 长时间没有把这轮 OAuth 流程走完。常见于提交太晚、面板轮询异常,或后端状态没有及时刷新。', + message: 'CPA did not finish this OAuth flow for too long. Common causes are submitting too late, panel polling issues, or backend state not refreshing in time.', }, { code: 'oauth_http_timeout', pattern: /timeout of \d+ms exceeded/i, - message: 'CPA 面板在请求后台接口时超时,通常是 CLI Proxy API 响应过慢、接口未启动,或网络连接不稳定。', + message: 'CPA panel timed out while requesting the backend API. Usually the CLI Proxy API responded too slowly, the API was not started, or the network connection was unstable.', }, { code: 'oauth_http_status_error', pattern: /request failed with status code \d+/i, - message: 'CPA 面板请求后台接口时收到了异常 HTTP 状态码,通常是接口异常、反向代理配置错误,或当前会话已失效。', + message: 'CPA panel received an abnormal HTTP status code while requesting the backend API. Usually the API is abnormal, reverse-proxy configuration is wrong, or the current session is invalid.', }, { code: 'oauth_network_error', pattern: /network error|failed to fetch/i, - message: 'CPA 面板与后台通信失败,通常是网络不通、管理接口未启动,或浏览器当前连接已断开。', + message: 'CPA panel failed to communicate with the backend. Usually the network is unreachable, the admin API is not started, or the current browser connection was lost.', }, ]; const matchedRule = rules.find((rule) => rule.pattern.test(detail) || rule.pattern.test(rawText)); const message = matchedRule ? matchedRule.message - : `CPA 在${phaseLabel}返回了未归类的失败,请结合面板原文进一步排查。`; + : `CPA returned an uncategorized failure during ${phaseLabel}. Use the panel text for further investigation.`; return { code: matchedRule?.code || 'oauth_unknown_failure', phaseLabel, rawText, detail, - userMessage: `CPA 在${phaseLabel}返回失败:${message} 面板原文:${rawText}`, + userMessage: `CPA returned a failure during ${phaseLabel}: ${message}. Panel text: ${rawText}`, }; } @@ -692,11 +692,11 @@ async function waitForExactSuccessBadge(timeout = STEP9_SUCCESS_BADGE_TIMEOUT_MS if (diagnostics.signature !== lastDiagnosticsSignature) { lastDiagnosticsSignature = diagnostics.signature; lastHeartbeatLoggedAt = elapsed; - log(`认证状态检测中,${diagnostics.summary}`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log(`Checking authentication status, ${diagnostics.summary}`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); console.log(LOG_PREFIX, '[Step 9] status badge diagnostics changed', diagnostics); } else if (elapsed - lastHeartbeatLoggedAt >= 10000) { lastHeartbeatLoggedAt = elapsed; - log(`仍在等待认证成功,${diagnostics.summary}`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log(`Still waiting for authentication success, ${diagnostics.summary}`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); console.log(LOG_PREFIX, '[Step 9] still waiting for success badge', diagnostics); } @@ -708,7 +708,7 @@ async function waitForExactSuccessBadge(timeout = STEP9_SUCCESS_BADGE_TIMEOUT_MS if (callbackSubmittedSignature !== lastCallbackSubmittedSignature) { lastCallbackSubmittedSignature = callbackSubmittedSignature; log( - `CPA 已接受 localhost 回调,正在等待后台完成认证。回调提示=${formatStep10StatusSummaryValue(diagnostics.callbackStatusText)};主状态=${formatStep10StatusSummaryValue(diagnostics.mainStatusText)}`, + `CPA accepted the localhost callback. Waiting for the backend to finish authentication.callback hint=${formatStep10StatusSummaryValue(diagnostics.callbackStatusText)};main state=${formatStep10StatusSummaryValue(diagnostics.mainStatusText)}`, 'info', { step: visibleStep, stepKey: 'platform-verify' } ); @@ -731,11 +731,11 @@ async function waitForExactSuccessBadge(timeout = STEP9_SUCCESS_BADGE_TIMEOUT_MS }); if (conflictSignature !== lastSuccessFailureConflictSignature) { lastSuccessFailureConflictSignature = conflictSignature; - const failureSummary = diagnostics.pageErrorSummary !== '无可见状态徽标' + const failureSummary = diagnostics.pageErrorSummary !== 'No visible status badge' ? diagnostics.pageErrorSummary : diagnostics.failureSummary; log( - `同时检测到成功徽标和失败提示,本轮不判定成功。成功徽标:${diagnostics.exactSuccessSummary};失败提示:${failureSummary}`, + `Detected both a success badge and a failure hint. This round will not be treated as successful. Success badge: ${diagnostics.exactSuccessSummary}; failure hint: ${failureSummary}`, 'warn', { step: visibleStep, stepKey: 'platform-verify' } ); @@ -758,7 +758,7 @@ async function waitForExactSuccessBadge(timeout = STEP9_SUCCESS_BADGE_TIMEOUT_MS const finalDiagnostics = getStatusBadgeDiagnostics(); const finalText = finalDiagnostics.failureText || finalDiagnostics.selectedText; - const diagnosticsSuffix = ` 当前诊断:${finalDiagnostics.summary}`; + const diagnosticsSuffix = ` Current diagnostics: ${finalDiagnostics.summary}`; if (isOAuthCallbackTimeoutFailure(finalText)) { const failureExplanation = explainStep10Failure(finalText, finalDiagnostics.failureSource || 'main'); throw new Error(`STEP9_OAUTH_TIMEOUT::${failureExplanation.userMessage}${diagnosticsSuffix}`); @@ -769,18 +769,18 @@ async function waitForExactSuccessBadge(timeout = STEP9_SUCCESS_BADGE_TIMEOUT_MS } if (finalDiagnostics.hasCallbackSubmittedBadge || finalDiagnostics.mainWaitingText) { throw new Error( - 'STEP9_OAUTH_TIMEOUT::CPA 已接受回调,但 120 秒内仍未进入认证成功状态。通常是 CPA 后台处理过慢、面板轮询异常,或 CPA 到 OpenAI 的网络/代理存在问题。' + 'STEP9_OAUTH_TIMEOUT::CPA accepted the callback, but the page still did not enter the authentication-success state within 120 seconds. Usually CPA backend processing is too slow, panel polling is abnormal, or CPA-to-OpenAI network/proxy has an issue.' + diagnosticsSuffix ); } throw new Error(finalText - ? `CPA 面板状态未进入成功状态,当前为“${finalText}”。${diagnosticsSuffix}` - : `CPA 面板长时间未出现成功状态徽标。${diagnosticsSuffix}`); + ? `CPA panel state did not enter a success state. Current state: "${finalText}."${diagnosticsSuffix}` + : `CPA panel did not show a success-state badge for a long time.${diagnosticsSuffix}`); } function findManagementKeyInput() { const candidates = document.querySelectorAll( - '.LoginPage-module__loginCard___OgP-R input[type="password"], input[placeholder*="管理密钥"], input[aria-label*="管理密钥"]' + '.LoginPage-module__loginCard___OgP-R input[type="password"], input[placeholder*="admin key"], input[aria-label*="admin key"]' ); return Array.from(candidates).find(isVisibleElement) || null; } @@ -884,21 +884,21 @@ async function ensureOAuthManagementPage(vpsPassword, step = 1, timeout = 45000) const managementLoginButton = findManagementLoginButton(); if (managementKeyInput && managementLoginButton) { if (!vpsPassword) { - throw new Error('CPA 面板需要管理密钥,请先在侧边栏填写 CPA Key(管理密钥)。'); + throw new Error('The CPA panel requires an admin key. Please fill in the CPA key in the side panel first.'); } if ((managementKeyInput.value || '') !== vpsPassword) { await humanPause(350, 900); fillInput(managementKeyInput, vpsPassword); console.log(LOG_PREFIX, `[Step ${step}] filled management key after ${elapsed}ms`); - log(`步骤 ${step}:已填写 CPA 管理密钥。`); + log(`Step ${step}:CPA admin key filled.`); } const rememberCheckbox = findRememberPasswordCheckbox(); if (rememberCheckbox && !rememberCheckbox.checked) { simulateClick(rememberCheckbox); console.log(LOG_PREFIX, `[Step ${step}] toggled remember checkbox after ${elapsed}ms`); - log(`步骤 ${step}:已勾选 CPA 面板“记住密码”。`); + log(`Step ${step}:Checked "Remember password" in the CPA panel.`); await sleep(300); } @@ -909,7 +909,7 @@ async function ensureOAuthManagementPage(vpsPassword, step = 1, timeout = 45000) console.log(LOG_PREFIX, `[Step ${step}] clicked management login after ${elapsed}ms`, { buttonText: getInlineTextSnippet(getActionText(managementLoginButton), 80), }); - log(`步骤 ${step}:已提交 CPA 管理登录。`); + log(`Step ${step}:Submitted CPA admin login.`); } await sleep(1500); @@ -924,7 +924,7 @@ async function ensureOAuthManagementPage(vpsPassword, step = 1, timeout = 45000) console.log(LOG_PREFIX, `[Step ${step}] clicked OAuth nav after ${elapsed}ms`, { navText: getInlineTextSnippet(getActionText(oauthNavLink), 80), }); - log(`步骤 ${step}:已打开“OAuth 登录”导航。`); + log(`Step ${step}:Opened the "OAuth login" navigation.`); await sleep(1200); continue; } @@ -937,7 +937,7 @@ async function ensureOAuthManagementPage(vpsPassword, step = 1, timeout = 45000) snapshot: getVpsPanelSnapshot(), }); - throw new Error('无法进入 CPA 的 OAuth 管理页面,请检查面板是否正常加载。URL: ' + location.href); + throw new Error('Cannot enter the CPA OAuth admin page. Check whether the panel loaded correctly. URL: ' + location.href); } async function requestOAuthUrl(payload = {}) { @@ -958,7 +958,7 @@ async function step1_getOAuthLink(payload, options = {}) { snapshot: getVpsPanelSnapshot(), }); - log(`步骤 ${logStep}:正在等待 CPA 面板加载并进入 OAuth 页面...`); + log(`Step ${logStep}:Waiting for the CPA panel to load and enter the OAuth page...`); const { header, authUrlEl: existingAuthUrlEl } = await ensureOAuthManagementPage(vpsPassword, logStep); let authUrlEl = existingAuthUrlEl; @@ -972,7 +972,7 @@ async function step1_getOAuthLink(payload, options = {}) { if (!authUrlEl) { const loginBtn = findOAuthCardLoginButton(header); if (!loginBtn) { - throw new Error('已找到 Codex OAuth 卡片,但卡片内没有登录按钮。URL: ' + location.href); + throw new Error('Found the Codex OAuth card, but there is no login button inside it. URL: ' + location.href); } if (loginBtn.disabled) { @@ -980,7 +980,7 @@ async function step1_getOAuthLink(payload, options = {}) { url: location.href, buttonText: getInlineTextSnippet(getActionText(loginBtn), 80), }); - log(`步骤 ${logStep}:OAuth 登录按钮当前不可用,正在等待授权链接出现...`); + log(`Step ${logStep}:OAuth login button is currently unavailable. Waiting for the authorization link to appear...`); } else { await humanPause(500, 1400); simulateClick(loginBtn); @@ -988,27 +988,27 @@ async function step1_getOAuthLink(payload, options = {}) { url: location.href, buttonText: getInlineTextSnippet(getActionText(loginBtn), 80), }); - log(`步骤 ${logStep}:已点击 OAuth 登录按钮,正在等待授权链接...`); + log(`Step ${logStep}:Clicked the OAuth login button. Waiting for the authorization link...`); } try { authUrlEl = await waitForElement('[class*="authUrlValue"]', 15000); } catch { throw new Error( - '点击 OAuth 登录按钮后未出现授权链接。' + - '请检查 CPA 面板服务是否正在运行。URL: ' + location.href + 'No authorization link appeared after clicking the OAuth login button.' + + 'Check whether the CPA panel service is running. URL: ' + location.href ); } } else { - log(`步骤 ${logStep}:CPA 面板上已显示授权链接。`); + log(`Step ${logStep}:Authorization link is already shown on the CPA panel.`); } const oauthUrl = (authUrlEl.textContent || '').trim(); if (!oauthUrl || !oauthUrl.startsWith('http')) { - throw new Error(`拿到的 OAuth 链接无效:\"${oauthUrl.slice(0, 50)}\"。应为 http 开头的 URL。`); + throw new Error(`Received an invalid OAuth URL: "${oauthUrl.slice(0, 50)}". It should start with http.`); } - log(`步骤 ${logStep}:已获取 OAuth 链接:${oauthUrl.slice(0, 80)}...`, 'ok'); + log(`Step ${logStep}:Obtained OAuth URL: ${oauthUrl.slice(0, 80)}...`, 'ok'); console.log(LOG_PREFIX, '[Step 1] reporting completion with oauthUrl', { url: location.href, oauthUrlPreview: oauthUrl.slice(0, 120), @@ -1020,7 +1020,7 @@ async function step1_getOAuthLink(payload, options = {}) { } // ============================================================ -// 步骤 10:CPA 回调验证——填写 localhost 回调地址并提交 +// Step 10: CPA callback verification - fill in the localhost callback URL and submit it // ============================================================ async function step9_vpsVerify(payload) { @@ -1028,27 +1028,27 @@ async function step9_vpsVerify(payload) { const confirmStep = visibleStep >= 13 ? 12 : 9; await ensureOAuthManagementPage(payload?.vpsPassword, confirmStep); - // 优先从 payload 读取 localhostUrl;没有时再回退到全局状态 + // Prefer localhostUrl from the payload; fall back to global state if needed. let localhostUrl = payload?.localhostUrl; if (localhostUrl && !isLocalhostOAuthCallbackUrl(localhostUrl)) { - throw new Error(`步骤 ${visibleStep} 只接受真实的 localhost OAuth 回调地址,请重新执行步骤 ${confirmStep}。`); + throw new Error(`Step ${visibleStep} only accepts a real localhost OAuth callback URL. Please rerun Step ${confirmStep}.`); } if (!localhostUrl) { - log('payload 中没有 localhostUrl,正在从状态中读取...', 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log('payload does not contain localhostUrl. Reading it from state...', 'info', { step: visibleStep, stepKey: 'platform-verify' }); const state = await chrome.runtime.sendMessage({ type: 'GET_STATE' }); localhostUrl = state.localhostUrl; if (localhostUrl && !isLocalhostOAuthCallbackUrl(localhostUrl)) { - throw new Error(`步骤 ${visibleStep} 只接受真实的 localhost OAuth 回调地址,请重新执行步骤 ${confirmStep}。`); + throw new Error(`Step ${visibleStep} only accepts a real localhost OAuth callback URL. Please rerun Step ${confirmStep}.`); } } if (!localhostUrl) { - throw new Error(`未找到 localhost 回调地址,请先完成步骤 ${confirmStep}。`); + throw new Error(`No localhost callback URL was found. Please complete Step ${confirmStep} first.`); } - log(`已获取 localhostUrl:${localhostUrl.slice(0, 60)}...`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log(`Obtained localhostUrl: ${localhostUrl.slice(0, 60)}...`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); - log('正在查找回调地址输入框...', 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log('Looking for the callback URL input...', 'info', { step: visibleStep, stepKey: 'platform-verify' }); - // Find the callback URL input + // Find the callback URL input. // Actual DOM: let urlInput = null; try { @@ -1057,13 +1057,13 @@ async function step9_vpsVerify(payload) { try { urlInput = await waitForElement('input[placeholder*="localhost"]', 5000); } catch { - throw new Error('在 CPA 面板中未找到回调地址输入框。URL: ' + location.href); + throw new Error('Could not find the callback URL input in the CPA panel. URL: ' + location.href); } } await humanPause(600, 1500); fillInput(urlInput, localhostUrl); - log(`已填写回调地址:${localhostUrl.slice(0, 80)}...`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log(`Filled callback URL: ${localhostUrl.slice(0, 80)}...`, 'info', { step: visibleStep, stepKey: 'platform-verify' }); // Find and click the callback submit button in supported UI languages. const callbackSubmitPattern = /提交回调\s*URL|Submit\s+Callback\s+URL|Отправить\s+Callback\s+URL/i; @@ -1078,13 +1078,13 @@ async function step9_vpsVerify(payload) { try { submitBtn = await waitForElementByText('button.btn', callbackSubmitPattern, 5000); } catch { - throw new Error('未找到回调提交按钮(提交回调 URL / Submit Callback URL / Отправить Callback URL)。URL: ' + location.href); + throw new Error('Could not find the callback submit button (Submit Callback URL / Отправить Callback URL). URL: ' + location.href); } } await humanPause(450, 1200); simulateClick(submitBtn); - log('已点击回调提交按钮,正在等待认证结果...', 'info', { step: visibleStep, stepKey: 'platform-verify' }); + log('Clicked the callback submit button. Waiting for the authentication result...', 'info', { step: visibleStep, stepKey: 'platform-verify' }); const verifiedStatus = await waitForExactSuccessBadge(STEP9_SUCCESS_BADGE_TIMEOUT_MS, visibleStep); log(verifiedStatus, 'ok', { step: visibleStep, stepKey: 'platform-verify' }); diff --git a/flows/openai/content/whatsapp-flow.js b/flows/openai/content/whatsapp-flow.js index 064c0f25..76ff139d 100644 --- a/flows/openai/content/whatsapp-flow.js +++ b/flows/openai/content/whatsapp-flow.js @@ -26,7 +26,7 @@ if (document.documentElement.getAttribute(WHATSAPP_FLOW_LISTENER_SENTINEL) !== ' } }); } else { - console.log('[MultiPage:whatsapp-flow] 消息监听已存在,跳过重复注册'); + console.log('[MultiPage:whatsapp-flow] message listener already exists, skipping duplicate registration'); } async function handleWhatsAppCommand(message) { @@ -36,7 +36,7 @@ async function handleWhatsAppCommand(message) { case 'WHATSAPP_FIND_CODE': return findWhatsAppCode(message.payload || {}); default: - throw new Error(`whatsapp-flow.js 不处理消息:${message.type}`); + throw new Error(`whatsapp-flow.js does not handle message: ${message.type}`); } } diff --git a/flows/openai/index.js b/flows/openai/index.js index 70476f31..837bfd10 100644 --- a/flows/openai/index.js +++ b/flows/openai/index.js @@ -40,7 +40,7 @@ "supportsLuckmail": true, "canSwitchFlow": true, "stepDefinitionMode": "openai-dynamic", - "targetSelectorLabel": "来源" + "targetSelectorLabel": "Source" }, "baseGroups": [ "openai-plus", @@ -53,7 +53,7 @@ "targets": { "cpa": { "id": "cpa", - "label": "CPA 面板", + "label": "CPA panel", "defaultState": { "vpsUrl": "", "vpsPassword": "", @@ -120,7 +120,7 @@ "openai-auth": { "flowId": "openai", "kind": "flow-page", - "label": "认证页", + "label": "Auth page", "readyPolicy": "allow-child-frame", "family": "openai-auth-family", "driverId": "flows/openai/content/openai-auth", @@ -156,7 +156,7 @@ "chatgpt": { "flowId": "openai", "kind": "flow-entry", - "label": "ChatGPT 首页", + "label": "ChatGPT home page", "readyPolicy": "allow-child-frame", "family": "chatgpt-entry-family", "driverId": null, @@ -183,7 +183,7 @@ "vps-panel": { "flowId": "openai", "kind": "panel-page", - "label": "CPA 面板", + "label": "CPA panel", "readyPolicy": "allow-child-frame", "family": "vps-panel-family", "driverId": "flows/openai/content/vps-panel", @@ -198,7 +198,7 @@ "platform-panel": { "flowId": "openai", "kind": "virtual-page", - "label": "平台回调面板", + "label": "Platform callback panel", "readyPolicy": "disabled", "family": "platform-panel-family", "driverId": "content/platform-panel", @@ -208,7 +208,7 @@ "sub2api-panel": { "flowId": "openai", "kind": "panel-page", - "label": "SUB2API 后台", + "label": "SUB2API admin", "readyPolicy": "allow-child-frame", "family": "sub2api-panel-family", "driverId": "flows/openai/content/sub2api-panel", @@ -237,7 +237,7 @@ "codex2api-panel": { "flowId": "openai", "kind": "panel-page", - "label": "Codex2API 后台", + "label": "Codex2API admin", "readyPolicy": "allow-child-frame", "family": "codex2api-panel-family", "driverId": "flows/openai/content/sub2api-panel", @@ -280,7 +280,7 @@ "paypal-flow": { "flowId": "openai", "kind": "flow-page", - "label": "PayPal 授权页", + "label": "PayPal authorization page", "readyPolicy": "allow-child-frame", "family": "paypal-flow-family", "driverId": "flows/openai/content/paypal-flow", @@ -296,7 +296,7 @@ "gopay-flow": { "flowId": "openai", "kind": "flow-page", - "label": "GoPay 授权页", + "label": "GoPay authorization page", "readyPolicy": "allow-child-frame", "family": "gopay-flow-family", "driverId": "flows/openai/content/gopay-flow", @@ -378,7 +378,7 @@ "settingsGroups": { "openai-target-cpa": { "id": "openai-target-cpa", - "label": "CPA 来源", + "label": "CPA Source", "rowIds": [ "row-vps-url", "row-vps-password", @@ -387,7 +387,7 @@ }, "openai-target-sub2api": { "id": "openai-target-sub2api", - "label": "SUB2API 来源", + "label": "SUB2API Source", "rowIds": [ "row-sub2api-url", "row-sub2api-email", @@ -399,7 +399,7 @@ }, "openai-target-codex2api": { "id": "openai-target-codex2api", - "label": "Codex2API 来源", + "label": "Codex2API Source", "rowIds": [ "row-codex2api-url", "row-codex2api-admin-key" @@ -416,7 +416,7 @@ }, "openai-phone": { "id": "openai-phone", - "label": "接码设置", + "label": "SMS verification settings", "sectionIds": [ "phone-verification-section" ], @@ -432,7 +432,7 @@ }, "openai-step6": { "id": "openai-step6", - "label": "第六步", + "label": "Step 6", "rowIds": [ "row-step6-cookie-settings" ] diff --git a/flows/openai/workflow.js b/flows/openai/workflow.js index afd35ef1..7c3664cc 100644 --- a/flows/openai/workflow.js +++ b/flows/openai/workflow.js @@ -30,7 +30,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -40,7 +40,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -50,7 +50,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -60,7 +60,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -71,7 +71,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -81,7 +81,7 @@ "id": 6, "order": 60, "key": "wait-registration-success", - "title": "等待注册成功", + "title": "Wait for signup success", "sourceId": "chatgpt", "driverId": null, "command": "wait-registration-success", @@ -91,7 +91,7 @@ "id": 7, "order": 70, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -101,7 +101,7 @@ "id": 8, "order": 80, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -112,7 +112,7 @@ "id": 9, "order": 90, "key": "post-login-phone-verification", - "title": "手机号验证", + "title": "Phone number verification", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "post-login-phone-verification", @@ -122,7 +122,7 @@ "id": 10, "order": 100, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -132,7 +132,7 @@ "id": 11, "order": 110, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -144,7 +144,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -154,7 +154,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -164,7 +164,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -174,7 +174,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -185,7 +185,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -195,7 +195,7 @@ "id": 6, "order": 60, "key": "wait-registration-success", - "title": "等待注册成功", + "title": "Wait for signup success", "sourceId": "chatgpt", "driverId": null, "command": "wait-registration-success", @@ -205,7 +205,7 @@ "id": 7, "order": 70, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -215,7 +215,7 @@ "id": 8, "order": 80, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -226,7 +226,7 @@ "id": 9, "order": 90, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -236,7 +236,7 @@ "id": 10, "order": 100, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -247,7 +247,7 @@ "id": 11, "order": 110, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -257,7 +257,7 @@ "id": 12, "order": 120, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -269,7 +269,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -279,7 +279,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -289,7 +289,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -299,7 +299,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -310,7 +310,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -320,7 +320,7 @@ "id": 6, "order": 60, "key": "wait-registration-success", - "title": "等待注册成功", + "title": "Wait for signup success", "sourceId": "chatgpt", "driverId": null, "command": "wait-registration-success", @@ -330,7 +330,7 @@ "id": 7, "order": 70, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -340,7 +340,7 @@ "id": 8, "order": 80, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -351,7 +351,7 @@ "id": 9, "order": 90, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -361,7 +361,7 @@ "id": 10, "order": 100, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -372,7 +372,7 @@ "id": 11, "order": 110, "key": "relogin-bound-email", - "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "title": "Refresh OAuth and log in after email binding (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -382,7 +382,7 @@ "id": 12, "order": 120, "key": "fetch-bound-email-login-code", - "title": "获取登录验证码(邮箱)", + "title": "Get login verification code (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -393,7 +393,7 @@ "id": 13, "order": 130, "key": "post-bound-email-phone-verification", - "title": "手机号验证", + "title": "Phone number verification", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "post-login-phone-verification", @@ -403,7 +403,7 @@ "id": 14, "order": 140, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -413,7 +413,7 @@ "id": 15, "order": 150, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -425,7 +425,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -435,7 +435,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -445,7 +445,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -455,7 +455,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -466,7 +466,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -476,7 +476,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -486,7 +486,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "填写账单并提交订单", + "title": "Fill billing details and submit order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -496,7 +496,7 @@ "id": 8, "order": 80, "key": "paypal-approve", - "title": "PayPal 登录与授权", + "title": "PayPal login and authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-approve", @@ -506,7 +506,7 @@ "id": 9, "order": 90, "key": "plus-checkout-return", - "title": "订阅回跳确认", + "title": "Confirm subscription return", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-return", @@ -516,7 +516,7 @@ "id": 10, "order": 100, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -526,7 +526,7 @@ "id": 11, "order": 110, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -537,7 +537,7 @@ "id": 12, "order": 120, "key": "post-login-phone-verification", - "title": "手机号验证", + "title": "Phone number verification", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "post-login-phone-verification", @@ -547,7 +547,7 @@ "id": 13, "order": 130, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -557,7 +557,7 @@ "id": 14, "order": 140, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -569,7 +569,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -579,7 +579,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -589,7 +589,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -599,7 +599,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -610,7 +610,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -620,7 +620,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -630,7 +630,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "填写账单并提交订单", + "title": "Fill billing details and submit order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -640,7 +640,7 @@ "id": 8, "order": 80, "key": "paypal-approve", - "title": "PayPal 登录与授权", + "title": "PayPal login and authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-approve", @@ -650,7 +650,7 @@ "id": 9, "order": 90, "key": "plus-checkout-return", - "title": "订阅回跳确认", + "title": "Confirm subscription return", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-return", @@ -660,7 +660,7 @@ "id": 10, "order": 100, "key": "sub2api-session-import", - "title": "导入当前 ChatGPT 会话到 SUB2API", + "title": "Import current ChatGPT session to SUB2API", "sourceId": "sub2api-panel", "driverId": "flows/openai/background/steps/sub2api-session-import", "command": "sub2api-session-import", @@ -672,7 +672,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -682,7 +682,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -692,7 +692,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -702,7 +702,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -713,7 +713,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -723,7 +723,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -733,7 +733,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "填写账单并提交订单", + "title": "Fill billing details and submit order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -743,7 +743,7 @@ "id": 8, "order": 80, "key": "paypal-approve", - "title": "PayPal 登录与授权", + "title": "PayPal login and authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-approve", @@ -753,7 +753,7 @@ "id": 9, "order": 90, "key": "plus-checkout-return", - "title": "订阅回跳确认", + "title": "Confirm subscription return", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-return", @@ -763,7 +763,7 @@ "id": 10, "order": 100, "key": "cpa-session-import", - "title": "导入当前 ChatGPT 会话到 CPA", + "title": "Import current ChatGPT session to CPA", "sourceId": "vps-panel", "driverId": "flows/openai/background/steps/cpa-session-import", "command": "cpa-session-import", @@ -775,7 +775,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -785,7 +785,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -795,7 +795,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -805,7 +805,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -816,7 +816,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -826,7 +826,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -836,7 +836,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "填写账单并提交订单", + "title": "Fill billing details and submit order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -846,7 +846,7 @@ "id": 8, "order": 80, "key": "paypal-approve", - "title": "PayPal 登录与授权", + "title": "PayPal login and authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-approve", @@ -856,7 +856,7 @@ "id": 9, "order": 90, "key": "plus-checkout-return", - "title": "订阅回跳确认", + "title": "Confirm subscription return", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-return", @@ -866,7 +866,7 @@ "id": 10, "order": 100, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -876,7 +876,7 @@ "id": 11, "order": 110, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -887,7 +887,7 @@ "id": 12, "order": 120, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -897,7 +897,7 @@ "id": 13, "order": 130, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -908,7 +908,7 @@ "id": 14, "order": 140, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -918,7 +918,7 @@ "id": 15, "order": 150, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -930,7 +930,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -940,7 +940,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -950,7 +950,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -960,7 +960,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -971,7 +971,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -981,7 +981,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -991,7 +991,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "填写账单并提交订单", + "title": "Fill billing details and submit order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -1001,7 +1001,7 @@ "id": 8, "order": 80, "key": "paypal-approve", - "title": "PayPal 登录与授权", + "title": "PayPal login and authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-approve", @@ -1011,7 +1011,7 @@ "id": 9, "order": 90, "key": "plus-checkout-return", - "title": "订阅回跳确认", + "title": "Confirm subscription return", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-return", @@ -1021,7 +1021,7 @@ "id": 10, "order": 100, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -1031,7 +1031,7 @@ "id": 11, "order": 110, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1042,7 +1042,7 @@ "id": 12, "order": 120, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -1052,7 +1052,7 @@ "id": 13, "order": 130, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -1063,7 +1063,7 @@ "id": 14, "order": 140, "key": "relogin-bound-email", - "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "title": "Refresh OAuth and log in after email binding (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -1073,7 +1073,7 @@ "id": 15, "order": 150, "key": "fetch-bound-email-login-code", - "title": "获取登录验证码(邮箱)", + "title": "Get login verification code (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1084,7 +1084,7 @@ "id": 16, "order": 160, "key": "post-bound-email-phone-verification", - "title": "手机号验证", + "title": "Phone number verification", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "post-login-phone-verification", @@ -1094,7 +1094,7 @@ "id": 17, "order": 170, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -1104,7 +1104,7 @@ "id": 18, "order": 180, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -1116,7 +1116,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -1126,7 +1126,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -1136,7 +1136,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -1146,7 +1146,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1157,7 +1157,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -1167,7 +1167,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -1177,7 +1177,7 @@ "id": 7, "order": 70, "key": "paypal-hosted-email", - "title": "无卡直绑填写 PayPal 邮箱", + "title": "No-card direct bind: enter PayPal email", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-email", @@ -1187,7 +1187,7 @@ "id": 8, "order": 80, "key": "paypal-hosted-card", - "title": "无卡直绑填写 PayPal 资料", + "title": "No-card direct bind: fill PayPal details", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-card", @@ -1197,7 +1197,7 @@ "id": 9, "order": 90, "key": "paypal-hosted-create-account", - "title": "无卡直绑确认创建 PayPal", + "title": "No-card direct bind: confirm PayPal creation", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-create-account", @@ -1207,7 +1207,7 @@ "id": 10, "order": 100, "key": "paypal-hosted-review", - "title": "无卡直绑完成 PayPal 授权", + "title": "No-card direct bind: complete PayPal authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-review", @@ -1217,7 +1217,7 @@ "id": 11, "order": 110, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -1227,7 +1227,7 @@ "id": 12, "order": 120, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1238,7 +1238,7 @@ "id": 13, "order": 130, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -1248,7 +1248,7 @@ "id": 14, "order": 140, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -1260,7 +1260,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -1270,7 +1270,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -1280,7 +1280,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -1290,7 +1290,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1301,7 +1301,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -1311,7 +1311,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -1321,7 +1321,7 @@ "id": 7, "order": 70, "key": "paypal-hosted-email", - "title": "无卡直绑填写 PayPal 邮箱", + "title": "No-card direct bind: enter PayPal email", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-email", @@ -1331,7 +1331,7 @@ "id": 8, "order": 80, "key": "paypal-hosted-card", - "title": "无卡直绑填写 PayPal 资料", + "title": "No-card direct bind: fill PayPal details", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-card", @@ -1341,7 +1341,7 @@ "id": 9, "order": 90, "key": "paypal-hosted-create-account", - "title": "无卡直绑确认创建 PayPal", + "title": "No-card direct bind: confirm PayPal creation", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-create-account", @@ -1351,7 +1351,7 @@ "id": 10, "order": 100, "key": "paypal-hosted-review", - "title": "无卡直绑完成 PayPal 授权", + "title": "No-card direct bind: complete PayPal authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-review", @@ -1361,7 +1361,7 @@ "id": 11, "order": 110, "key": "sub2api-session-import", - "title": "导入当前 ChatGPT 会话到 SUB2API", + "title": "Import current ChatGPT session to SUB2API", "sourceId": "sub2api-panel", "driverId": "flows/openai/background/steps/sub2api-session-import", "command": "sub2api-session-import", @@ -1373,7 +1373,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -1383,7 +1383,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -1393,7 +1393,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -1403,7 +1403,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1414,7 +1414,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -1424,7 +1424,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -1434,7 +1434,7 @@ "id": 7, "order": 70, "key": "paypal-hosted-email", - "title": "无卡直绑填写 PayPal 邮箱", + "title": "No-card direct bind: enter PayPal email", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-email", @@ -1444,7 +1444,7 @@ "id": 8, "order": 80, "key": "paypal-hosted-card", - "title": "无卡直绑填写 PayPal 资料", + "title": "No-card direct bind: fill PayPal details", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-card", @@ -1454,7 +1454,7 @@ "id": 9, "order": 90, "key": "paypal-hosted-create-account", - "title": "无卡直绑确认创建 PayPal", + "title": "No-card direct bind: confirm PayPal creation", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-create-account", @@ -1464,7 +1464,7 @@ "id": 10, "order": 100, "key": "paypal-hosted-review", - "title": "无卡直绑完成 PayPal 授权", + "title": "No-card direct bind: complete PayPal authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-review", @@ -1474,7 +1474,7 @@ "id": 11, "order": 110, "key": "cpa-session-import", - "title": "导入当前 ChatGPT 会话到 CPA", + "title": "Import current ChatGPT session to CPA", "sourceId": "vps-panel", "driverId": "flows/openai/background/steps/cpa-session-import", "command": "cpa-session-import", @@ -1486,7 +1486,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -1496,7 +1496,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -1506,7 +1506,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -1516,7 +1516,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1527,7 +1527,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -1537,7 +1537,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -1547,7 +1547,7 @@ "id": 7, "order": 70, "key": "paypal-hosted-email", - "title": "无卡直绑填写 PayPal 邮箱", + "title": "No-card direct bind: enter PayPal email", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-email", @@ -1557,7 +1557,7 @@ "id": 8, "order": 80, "key": "paypal-hosted-card", - "title": "无卡直绑填写 PayPal 资料", + "title": "No-card direct bind: fill PayPal details", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-card", @@ -1567,7 +1567,7 @@ "id": 9, "order": 90, "key": "paypal-hosted-create-account", - "title": "无卡直绑确认创建 PayPal", + "title": "No-card direct bind: confirm PayPal creation", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-create-account", @@ -1577,7 +1577,7 @@ "id": 10, "order": 100, "key": "paypal-hosted-review", - "title": "无卡直绑完成 PayPal 授权", + "title": "No-card direct bind: complete PayPal authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-review", @@ -1587,7 +1587,7 @@ "id": 11, "order": 110, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -1597,7 +1597,7 @@ "id": 12, "order": 120, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1608,7 +1608,7 @@ "id": 13, "order": 130, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -1618,7 +1618,7 @@ "id": 14, "order": 140, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -1629,7 +1629,7 @@ "id": 15, "order": 150, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -1639,7 +1639,7 @@ "id": 16, "order": 160, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -1651,7 +1651,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -1661,7 +1661,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -1671,7 +1671,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -1681,7 +1681,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1692,7 +1692,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -1702,7 +1702,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 Plus Checkout", + "title": "Create Plus Checkout", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -1712,7 +1712,7 @@ "id": 7, "order": 70, "key": "paypal-hosted-email", - "title": "无卡直绑填写 PayPal 邮箱", + "title": "No-card direct bind: enter PayPal email", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-email", @@ -1722,7 +1722,7 @@ "id": 8, "order": 80, "key": "paypal-hosted-card", - "title": "无卡直绑填写 PayPal 资料", + "title": "No-card direct bind: fill PayPal details", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-card", @@ -1732,7 +1732,7 @@ "id": 9, "order": 90, "key": "paypal-hosted-create-account", - "title": "无卡直绑确认创建 PayPal", + "title": "No-card direct bind: confirm PayPal creation", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-create-account", @@ -1742,7 +1742,7 @@ "id": 10, "order": 100, "key": "paypal-hosted-review", - "title": "无卡直绑完成 PayPal 授权", + "title": "No-card direct bind: complete PayPal authorization", "sourceId": "paypal-flow", "driverId": "flows/openai/content/paypal-flow", "command": "paypal-hosted-review", @@ -1752,7 +1752,7 @@ "id": 11, "order": 110, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -1762,7 +1762,7 @@ "id": 12, "order": 120, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1773,7 +1773,7 @@ "id": 13, "order": 130, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -1783,7 +1783,7 @@ "id": 14, "order": 140, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -1794,7 +1794,7 @@ "id": 15, "order": 150, "key": "relogin-bound-email", - "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "title": "Refresh OAuth and log in after email binding (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -1804,7 +1804,7 @@ "id": 16, "order": 160, "key": "fetch-bound-email-login-code", - "title": "获取登录验证码(邮箱)", + "title": "Get login verification code (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1815,7 +1815,7 @@ "id": 17, "order": 170, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -1825,7 +1825,7 @@ "id": 18, "order": 180, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -1837,7 +1837,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -1847,7 +1847,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -1857,7 +1857,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -1867,7 +1867,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1878,7 +1878,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -1888,7 +1888,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "打开 GoPay 订阅页", + "title": "Open GoPay subscription page", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -1898,7 +1898,7 @@ "id": 7, "order": 70, "key": "gopay-subscription-confirm", - "title": "等待 GoPay 订阅确认", + "title": "Wait for GoPay subscription confirmation", "sourceId": "gopay-flow", "driverId": "flows/openai/content/gopay-flow", "command": "gopay-subscription-confirm", @@ -1908,7 +1908,7 @@ "id": 10, "order": 100, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -1918,7 +1918,7 @@ "id": 11, "order": 110, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -1929,7 +1929,7 @@ "id": 12, "order": 120, "key": "post-login-phone-verification", - "title": "手机号验证", + "title": "Phone number verification", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "post-login-phone-verification", @@ -1939,7 +1939,7 @@ "id": 13, "order": 130, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -1949,7 +1949,7 @@ "id": 14, "order": 140, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -1961,7 +1961,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -1971,7 +1971,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -1981,7 +1981,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -1991,7 +1991,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2002,7 +2002,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -2012,7 +2012,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "打开 GoPay 订阅页", + "title": "Open GoPay subscription page", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -2022,7 +2022,7 @@ "id": 7, "order": 70, "key": "gopay-subscription-confirm", - "title": "等待 GoPay 订阅确认", + "title": "Wait for GoPay subscription confirmation", "sourceId": "gopay-flow", "driverId": "flows/openai/content/gopay-flow", "command": "gopay-subscription-confirm", @@ -2032,7 +2032,7 @@ "id": 10, "order": 100, "key": "sub2api-session-import", - "title": "导入当前 ChatGPT 会话到 SUB2API", + "title": "Import current ChatGPT session to SUB2API", "sourceId": "sub2api-panel", "driverId": "flows/openai/background/steps/sub2api-session-import", "command": "sub2api-session-import", @@ -2044,7 +2044,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -2054,7 +2054,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -2064,7 +2064,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -2074,7 +2074,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2085,7 +2085,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -2095,7 +2095,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "打开 GoPay 订阅页", + "title": "Open GoPay subscription page", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -2105,7 +2105,7 @@ "id": 7, "order": 70, "key": "gopay-subscription-confirm", - "title": "等待 GoPay 订阅确认", + "title": "Wait for GoPay subscription confirmation", "sourceId": "gopay-flow", "driverId": "flows/openai/content/gopay-flow", "command": "gopay-subscription-confirm", @@ -2115,7 +2115,7 @@ "id": 10, "order": 100, "key": "cpa-session-import", - "title": "导入当前 ChatGPT 会话到 CPA", + "title": "Import current ChatGPT session to CPA", "sourceId": "vps-panel", "driverId": "flows/openai/background/steps/cpa-session-import", "command": "cpa-session-import", @@ -2127,7 +2127,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -2137,7 +2137,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -2147,7 +2147,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -2157,7 +2157,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2168,7 +2168,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -2178,7 +2178,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "打开 GoPay 订阅页", + "title": "Open GoPay subscription page", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -2188,7 +2188,7 @@ "id": 7, "order": 70, "key": "gopay-subscription-confirm", - "title": "等待 GoPay 订阅确认", + "title": "Wait for GoPay subscription confirmation", "sourceId": "gopay-flow", "driverId": "flows/openai/content/gopay-flow", "command": "gopay-subscription-confirm", @@ -2198,7 +2198,7 @@ "id": 10, "order": 100, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -2208,7 +2208,7 @@ "id": 11, "order": 110, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2219,7 +2219,7 @@ "id": 12, "order": 120, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -2229,7 +2229,7 @@ "id": 13, "order": 130, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -2240,7 +2240,7 @@ "id": 14, "order": 140, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -2250,7 +2250,7 @@ "id": 15, "order": 150, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -2262,7 +2262,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -2272,7 +2272,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -2282,7 +2282,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -2292,7 +2292,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2303,7 +2303,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -2313,7 +2313,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "打开 GoPay 订阅页", + "title": "Open GoPay subscription page", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -2323,7 +2323,7 @@ "id": 7, "order": 70, "key": "gopay-subscription-confirm", - "title": "等待 GoPay 订阅确认", + "title": "Wait for GoPay subscription confirmation", "sourceId": "gopay-flow", "driverId": "flows/openai/content/gopay-flow", "command": "gopay-subscription-confirm", @@ -2333,7 +2333,7 @@ "id": 10, "order": 100, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -2343,7 +2343,7 @@ "id": 11, "order": 110, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2354,7 +2354,7 @@ "id": 12, "order": 120, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -2364,7 +2364,7 @@ "id": 13, "order": 130, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -2375,7 +2375,7 @@ "id": 14, "order": 140, "key": "relogin-bound-email", - "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "title": "Refresh OAuth and log in after email binding (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -2385,7 +2385,7 @@ "id": 15, "order": 150, "key": "fetch-bound-email-login-code", - "title": "获取登录验证码(邮箱)", + "title": "Get login verification code (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2396,7 +2396,7 @@ "id": 16, "order": 160, "key": "post-bound-email-phone-verification", - "title": "手机号验证", + "title": "Phone number verification", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "post-login-phone-verification", @@ -2406,7 +2406,7 @@ "id": 17, "order": 170, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -2416,7 +2416,7 @@ "id": 18, "order": 180, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -2428,7 +2428,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -2438,7 +2438,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -2448,7 +2448,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -2458,7 +2458,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2469,7 +2469,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -2479,7 +2479,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 GPC 订单", + "title": "Create GPC order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -2489,7 +2489,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "等待 GPC 任务完成", + "title": "Wait for GPC task completion", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -2499,7 +2499,7 @@ "id": 10, "order": 100, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -2509,7 +2509,7 @@ "id": 11, "order": 110, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2520,7 +2520,7 @@ "id": 12, "order": 120, "key": "post-login-phone-verification", - "title": "手机号验证", + "title": "Phone number verification", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "post-login-phone-verification", @@ -2530,7 +2530,7 @@ "id": 13, "order": 130, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -2540,7 +2540,7 @@ "id": 14, "order": 140, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -2552,7 +2552,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -2562,7 +2562,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -2572,7 +2572,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -2582,7 +2582,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2593,7 +2593,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -2603,7 +2603,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 GPC 订单", + "title": "Create GPC order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -2613,7 +2613,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "等待 GPC 任务完成", + "title": "Wait for GPC task completion", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -2623,7 +2623,7 @@ "id": 10, "order": 100, "key": "sub2api-session-import", - "title": "导入当前 ChatGPT 会话到 SUB2API", + "title": "Import current ChatGPT session to SUB2API", "sourceId": "sub2api-panel", "driverId": "flows/openai/background/steps/sub2api-session-import", "command": "sub2api-session-import", @@ -2635,7 +2635,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -2645,7 +2645,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入邮箱", + "title": "Sign up and enter email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -2655,7 +2655,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -2665,7 +2665,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取注册验证码", + "title": "Get signup verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2676,7 +2676,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -2686,7 +2686,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 GPC 订单", + "title": "Create GPC order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -2696,7 +2696,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "等待 GPC 任务完成", + "title": "Wait for GPC task completion", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -2706,7 +2706,7 @@ "id": 10, "order": 100, "key": "cpa-session-import", - "title": "导入当前 ChatGPT 会话到 CPA", + "title": "Import current ChatGPT session to CPA", "sourceId": "vps-panel", "driverId": "flows/openai/background/steps/cpa-session-import", "command": "cpa-session-import", @@ -2718,7 +2718,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -2728,7 +2728,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -2738,7 +2738,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -2748,7 +2748,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2759,7 +2759,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -2769,7 +2769,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 GPC 订单", + "title": "Create GPC order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -2779,7 +2779,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "等待 GPC 任务完成", + "title": "Wait for GPC task completion", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -2789,7 +2789,7 @@ "id": 10, "order": 100, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -2799,7 +2799,7 @@ "id": 11, "order": 110, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2810,7 +2810,7 @@ "id": 12, "order": 120, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -2820,7 +2820,7 @@ "id": 13, "order": 130, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -2831,7 +2831,7 @@ "id": 14, "order": 140, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -2841,7 +2841,7 @@ "id": 15, "order": 150, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -2853,7 +2853,7 @@ "id": 1, "order": 10, "key": "open-chatgpt", - "title": "打开 ChatGPT 官网", + "title": "Open ChatGPT website", "sourceId": "chatgpt", "driverId": null, "command": "open-chatgpt", @@ -2863,7 +2863,7 @@ "id": 2, "order": 20, "key": "submit-signup-email", - "title": "注册并输入手机号", + "title": "Sign up and enter phone number", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-signup-email", @@ -2873,7 +2873,7 @@ "id": 3, "order": 30, "key": "fill-password", - "title": "填写密码并继续", + "title": "Enter password and continue", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-password", @@ -2883,7 +2883,7 @@ "id": 4, "order": 40, "key": "fetch-signup-code", - "title": "获取手机验证码", + "title": "Get SMS verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2894,7 +2894,7 @@ "id": 5, "order": 50, "key": "fill-profile", - "title": "填写姓名和生日", + "title": "Fill in name and birthday", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fill-profile", @@ -2904,7 +2904,7 @@ "id": 6, "order": 60, "key": "plus-checkout-create", - "title": "创建 GPC 订单", + "title": "Create GPC order", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-create", @@ -2914,7 +2914,7 @@ "id": 7, "order": 70, "key": "plus-checkout-billing", - "title": "等待 GPC 任务完成", + "title": "Wait for GPC task completion", "sourceId": "plus-checkout", "driverId": "flows/openai/content/plus-checkout", "command": "plus-checkout-billing", @@ -2924,7 +2924,7 @@ "id": 10, "order": 100, "key": "oauth-login", - "title": "刷新 OAuth 并登录", + "title": "Refresh OAuth and log in", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -2934,7 +2934,7 @@ "id": 11, "order": 110, "key": "fetch-login-code", - "title": "获取登录验证码", + "title": "Get login verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2945,7 +2945,7 @@ "id": 12, "order": 120, "key": "bind-email", - "title": "绑定邮箱", + "title": "Bind email", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "bind-email", @@ -2955,7 +2955,7 @@ "id": 13, "order": 130, "key": "fetch-bind-email-code", - "title": "获取绑定邮箱验证码", + "title": "Get email binding verification code", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "fetch-bind-email-code", @@ -2966,7 +2966,7 @@ "id": 14, "order": 140, "key": "relogin-bound-email", - "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "title": "Refresh OAuth and log in after email binding (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "oauth-login", @@ -2976,7 +2976,7 @@ "id": 15, "order": 150, "key": "fetch-bound-email-login-code", - "title": "获取登录验证码(邮箱)", + "title": "Get login verification code (email)", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "submit-verification-code", @@ -2987,7 +2987,7 @@ "id": 16, "order": 160, "key": "post-bound-email-phone-verification", - "title": "手机号验证", + "title": "Phone number verification", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "post-login-phone-verification", @@ -2997,7 +2997,7 @@ "id": 17, "order": 170, "key": "confirm-oauth", - "title": "自动确认 OAuth", + "title": "Auto-confirm OAuth", "sourceId": "openai-auth", "driverId": "flows/openai/content/openai-auth", "command": "confirm-oauth", @@ -3007,7 +3007,7 @@ "id": 18, "order": 180, "key": "platform-verify", - "title": "平台回调验证", + "title": "Platform callback verification", "sourceId": "platform-panel", "driverId": "content/platform-panel", "command": "platform-verify", @@ -3053,7 +3053,7 @@ return { ...(sourceStep || { key: PLUS_REGISTRATION_WAIT_STEP_KEY, - title: '等待注册成功', + title: 'Wait for signup success', sourceId: 'chatgpt', driverId: null, command: PLUS_REGISTRATION_WAIT_STEP_KEY, diff --git a/imports/legacy/account-records-importer.js b/imports/legacy/account-records-importer.js index ad159abb..eabae48d 100644 --- a/imports/legacy/account-records-importer.js +++ b/imports/legacy/account-records-importer.js @@ -7,7 +7,7 @@ function importAccountRecords(records = []) { if (!Array.isArray(records)) { - throw new Error('账号记录导入内容必须是数组。'); + throw new Error('Account-records import payload must be an array.'); } if (typeof createAccountRunHistoryHelpers !== 'function') { return records diff --git a/imports/legacy/settings-importer.js b/imports/legacy/settings-importer.js index 6a7047ee..3436d00c 100644 --- a/imports/legacy/settings-importer.js +++ b/imports/legacy/settings-importer.js @@ -110,10 +110,10 @@ function importSettings(input = {}) { if (!isPlainObject(input)) { - throw new Error('配置文件中的 settings 内容无效。'); + throw new Error('Settings payload in the config file is invalid.'); } if (!settingsSchemaApi?.normalizeSettingsState) { - throw new Error('设置导入器未完成初始化。'); + throw new Error('Settings importer is not initialized.'); } const importInput = buildImportInput(input); diff --git a/mail-provider-utils.js b/mail-provider-utils.js index c17a37a8..272573d0 100644 --- a/mail-provider-utils.js +++ b/mail-provider-utils.js @@ -16,11 +16,11 @@ const ICLOUD_TARGET_MAILBOX_TYPE_INBOX = 'icloud-inbox'; const ICLOUD_TARGET_MAILBOX_TYPE_FORWARD = 'forward-mailbox'; const ICLOUD_FORWARD_MAIL_PROVIDER_OPTIONS = [ - { value: 'qq', label: 'QQ 邮箱' }, - { value: '163', label: '163 邮箱' }, - { value: '163-vip', label: '163 VIP 邮箱' }, - { value: '126', label: '126 邮箱' }, - { value: GMAIL_PROVIDER, label: 'Gmail 邮箱' }, + { value: 'qq', label: 'QQ Mail' }, + { value: '163', label: '163 Mail' }, + { value: '163-vip', label: '163 VIP Mail' }, + { value: '126', label: '126 Mail' }, + { value: GMAIL_PROVIDER, label: 'Gmail' }, ]; function normalizeMailProvider(value = '') { @@ -62,7 +62,7 @@ return { source: 'gmail-mail', url: 'https://mail.google.com/mail/u/0/#inbox', - label: 'Gmail 邮箱', + label: 'Gmail', inject: ['content/activation-utils.js', 'content/utils.js', 'content/gmail-mail.js'], injectSource: 'gmail-mail', }; @@ -76,7 +76,7 @@ const normalizeInbucketOrigin = options.normalizeInbucketOrigin || (() => ''); if (provider === HOTMAIL_PROVIDER) { - return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(微软 Graph)' }; + return { provider: HOTMAIL_PROVIDER, label: 'Hotmail (Microsoft Graph)' }; } if (provider === YYDS_MAIL_PROVIDER) { return { provider: YYDS_MAIL_PROVIDER, label: 'YYDS Mail' }; @@ -85,42 +85,42 @@ return { source: 'mail-163', url: `https://mail.163.com${NETEASE_LIST_PATH}`, - label: '163 邮箱', + label: '163 Mail', }; } if (provider === '163-vip') { return { source: 'mail-163', url: `https://webmail.vip.163.com${NETEASE_LIST_PATH}`, - label: '163 VIP 邮箱', + label: '163 VIP Mail', }; } if (provider === '126') { return { source: 'mail-163', url: `https://mail.126.com${NETEASE_LIST_PATH}`, - label: '126 邮箱', + label: '126 Mail', }; } if (provider === 'inbucket') { const host = normalizeInbucketOrigin(state.inbucketHost); const mailbox = String(state.inbucketMailbox || '').trim(); if (!host) { - return { error: 'Inbucket 主机地址为空或无效。' }; + return { error: 'Inbucket host address is empty or invalid.' }; } if (!mailbox) { - return { error: 'Inbucket 邮箱名称为空。' }; + return { error: 'Inbucket mailbox name is empty.' }; } return { source: 'inbucket-mail', url: `${host}/m/${encodeURIComponent(mailbox)}/`, - label: `Inbucket 邮箱(${mailbox})`, + label: `Inbucket Mail (${mailbox})`, navigateOnReuse: true, inject: ['content/activation-utils.js', 'content/utils.js', 'content/inbucket-mail.js'], injectSource: 'inbucket-mail', }; } - return { source: 'qq-mail', url: 'https://wx.mail.qq.com/', label: 'QQ 邮箱' }; + return { source: 'qq-mail', url: 'https://wx.mail.qq.com/', label: 'QQ Mail' }; } return { diff --git a/mail2925-utils.js b/mail2925-utils.js index 4dbc7996..e315bad5 100644 --- a/mail2925-utils.js +++ b/mail2925-utils.js @@ -102,15 +102,15 @@ function getMail2925BulkActionLabel(mode = 'all', count = 0) { const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0; - const prefix = mode === 'cooldown' ? '清空冷却' : '全部删除'; - const suffix = normalizedCount > 0 ? `(${normalizedCount})` : ''; + const prefix = mode === 'cooldown' ? 'Clear Cooldown' : 'Delete All'; + const suffix = normalizedCount > 0 ? ` (${normalizedCount})` : ''; return `${prefix}${suffix}`; } function getMail2925ListToggleLabel(expanded, count = 0) { const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0; - const suffix = normalizedCount > 0 ? `(${normalizedCount})` : ''; - return `${expanded ? '收起列表' : '展开列表'}${suffix}`; + const suffix = normalizedCount > 0 ? ` (${normalizedCount})` : ''; + return `${expanded ? 'Collapse List' : 'Expand List'}${suffix}`; } function upsertMail2925AccountInList(accounts, nextAccount) { diff --git a/managed-alias-utils.js b/managed-alias-utils.js index 51e8c69e..916043d5 100644 --- a/managed-alias-utils.js +++ b/managed-alias-utils.js @@ -9,9 +9,9 @@ const PROVIDER_CONFIGS = { [GMAIL_PROVIDER]: { - baseLabel: 'Gmail 原邮箱', - basePlaceholder: '例如 yourname@gmail.com', - label: 'Gmail +tag 邮箱', + baseLabel: 'Gmail Base Email', + basePlaceholder: 'e.g. yourname@gmail.com', + label: 'Gmail +tag Email', parseBaseEmail(rawValue = '') { const value = String(rawValue || '').trim().toLowerCase(); const match = value.match(/^([^@\s+]+)@((?:gmail|googlemail)\.com)$/i); @@ -30,13 +30,13 @@ buildAlias(parsedBaseEmail, tag) { return `${parsedBaseEmail.localPart}+${tag}@${parsedBaseEmail.domain}`; }, - generationHint: '先填写 Gmail 原邮箱后点“生成”,也可以直接手动填写完整的 Gmail 邮箱。', - registrationPlaceholder: '点击生成 Gmail +tag 邮箱,或手动填写完整邮箱', + generationHint: 'Fill in the Gmail base email then click "Generate". You can also manually enter a full Gmail address.', + registrationPlaceholder: 'Click to generate a Gmail +tag email, or manually enter a full email', }, [MAIL_2925_PROVIDER]: { - baseLabel: '2925 基邮箱', - basePlaceholder: '例如 yourname@2925.com', - label: '2925 邮箱', + baseLabel: '2925 Base Email', + basePlaceholder: 'e.g. yourname@2925.com', + label: '2925 Email', parseBaseEmail(rawValue = '') { const value = String(rawValue || '').trim().toLowerCase(); const match = value.match(/^([^@\s+]+)@(2925\.com)$/i); @@ -58,8 +58,8 @@ buildAlias(parsedBaseEmail, tag) { return `${parsedBaseEmail.localPart}${tag}@${parsedBaseEmail.domain}`; }, - generationHint: '先填写 2925 基邮箱后点“生成”,也可以直接手动填写完整的 2925 邮箱。', - registrationPlaceholder: '点击生成 2925 邮箱,或手动填写完整邮箱', + generationHint: 'Fill in the 2925 base email then click "Generate". You can also manually enter a full 2925 address.', + registrationPlaceholder: 'Click to generate a 2925 email, or manually enter a full email', }, }; @@ -133,12 +133,12 @@ const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider); if (!parsedBaseEmail) { - throw new Error(`${config.baseLabel}格式不正确`); + throw new Error(`${config.baseLabel} format is invalid`); } const normalizedTag = String(tag || '').trim(); if (!normalizedTag) { - throw new Error(`${config.label}生成标签为空`); + throw new Error(`${config.label} generated tag is empty`); } return config.buildAlias(parsedBaseEmail, normalizedTag); @@ -150,8 +150,8 @@ return { baseLabel: config.baseLabel, basePlaceholder: config.basePlaceholder, - buttonLabel: '生成', - successVerb: '生成', + buttonLabel: 'Generate', + successVerb: 'Generated', label: config.label, placeholder: config.registrationPlaceholder, hint: config.generationHint, diff --git a/manifest.json b/manifest.json index 5c18b6b6..909a97ea 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "name": "FlowPilot", "version": "2.5", "version_name": "FlowPilot2.5", - "description": "用于自动执行多步骤 OAuth 注册流程", + "description": "Automates multi-step OAuth registration flows", "permissions": [ "sidePanel", "alarms", diff --git a/phone-sms/providers/five-sim.js b/phone-sms/providers/five-sim.js index 15312f63..f7785a40 100644 --- a/phone-sms/providers/five-sim.js +++ b/phone-sms/providers/five-sim.js @@ -1,4 +1,4 @@ -// phone-sms/providers/five-sim.js — 5sim 接码平台适配层 +// phone-sms/providers/five-sim.js — 5sim SMS verification provider adapter (function attachFiveSimSmsProvider(root, factory) { root.PhoneSmsFiveSimProvider = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createFiveSimSmsProviderModule() { @@ -587,11 +587,11 @@ return new Error(`${FIVE_SIM_RATE_LIMIT_ERROR_PREFIX}5sim 购买接口触发限流,请稍后再试${suffix}`); } - function isTerminalError(payloadOrMessage) { - const text = describePayload(payloadOrMessage); - return /not\s+enough\s+(?:user\s+)?balance|not\s+enough\s+rating|unauthorized|invalid\s+token|banned|bad\s+(?:country|operator)|no\s+product|server\s+offline/i.test(text); - } - + function isTerminalError(payloadOrMessage) { + const text = describePayload(payloadOrMessage); + return /not\s+enough\s+(?:user\s+)?balance|not\s+enough\s+rating|unauthorized|invalid\s+token|banned|bad\s+(?:country|operator)|no\s+product|server\s+offline/i.test(text); + } + function assertMaxPriceCompatibleWithOperator(state = {}) { const maxPrice = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice); const operator = normalizeFiveSimOperator(state.fiveSimOperator); @@ -641,11 +641,11 @@ return activation; } - async function requestActivation(state = {}, options = {}, deps = {}) { - assertMaxPriceCompatibleWithOperator(state); - - const allCountryCandidates = resolveCountryCandidates(state); - const blockedCountryIds = new Set( + async function requestActivation(state = {}, options = {}, deps = {}) { + assertMaxPriceCompatibleWithOperator(state); + + const allCountryCandidates = resolveCountryCandidates(state); + const blockedCountryIds = new Set( (Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : []) .map((value) => normalizeFiveSimCountryId(value, '')) .filter(Boolean) @@ -764,8 +764,9 @@ if (!phoneDigits) { throw new Error('可复用的 5sim 手机号无效。'); } - // 5sim 的 /user/reuse 会按手机号重新创建付费订单,并不是保留原订单继续收短信。 - // 真正的复用应继续轮询原 activationId,避免产生新的订单。 + // 5sim's /user/reuse creates a new paid order for the same phone number; + // it does not keep the existing order receiving SMS. + // Real reuse must keep polling the original activationId to avoid creating new orders. const config = resolveConfig(state, deps); const payload = await fetchJson(config, `/v1/user/check/${encodeURIComponent(normalizedActivation.activationId)}`, { actionLabel: '5sim 复用手机号基线检查', diff --git a/phone-sms/providers/hero-sms.js b/phone-sms/providers/hero-sms.js index da5ec308..be2abc27 100644 --- a/phone-sms/providers/hero-sms.js +++ b/phone-sms/providers/hero-sms.js @@ -1,4 +1,4 @@ -// phone-sms/providers/hero-sms.js — HeroSMS 接码平台适配层 +// phone-sms/providers/hero-sms.js — HeroSMS SMS verification provider adapter (function attachHeroSmsProvider(root, factory) { root.PhoneSmsHeroSmsProvider = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createHeroSmsProviderModule() { @@ -109,7 +109,7 @@ query = { api_key: config.apiKey, ...query }; } if (!config.fetchImpl) { - throw new Error('HeroSMS 网络请求实现不可用。'); + throw new Error('HeroSMS network fetch implementation is unavailable.'); } const controller = typeof AbortController === 'function' ? new AbortController() : null; const timeoutId = controller @@ -123,7 +123,7 @@ const text = await response.text(); const payload = parsePayload(text); if (!response.ok) { - const error = new Error(`${actionLabel}失败:${describePayload(payload) || response.status}`); + const error = new Error(`${actionLabel} failed: ${describePayload(payload) || response.status}`); error.payload = payload; error.status = response.status; throw error; @@ -131,7 +131,7 @@ return payload; } catch (error) { if (error?.name === 'AbortError') { - throw new Error(`${actionLabel}超时。`); + throw new Error(`${actionLabel} timed out.`); } throw error; } finally { @@ -162,7 +162,7 @@ async function fetchBalance(state = {}, deps = {}) { const config = resolveConfig(state, deps); if (!config.apiKey) { - throw new Error('HeroSMS API Key 缺失,请先在侧边栏保存接码 API Key。'); + throw new Error('HeroSMS API key is missing. Please save the SMS verification API key in the side panel first.'); } const payload = await fetchPayload(config, { action: 'getBalance' }, 'HeroSMS getBalance'); const balance = Number(String(describePayload(payload)).replace(/^ACCESS_BALANCE:/i, '').trim()); diff --git a/phone-sms/providers/registry.js b/phone-sms/providers/registry.js index 070da152..0847edfd 100644 --- a/phone-sms/providers/registry.js +++ b/phone-sms/providers/registry.js @@ -112,7 +112,7 @@ const definition = getProviderDefinition(providerId); const module = getProviderModule(providerId); if (!module || typeof module.createProvider !== 'function') { - throw new Error(`接码平台模块未加载:${definition.id}`); + throw new Error(`SMS provider module not loaded: ${definition.id}`); } return module.createProvider(deps); } diff --git a/scripts/gpc_sms_helper_macos.py b/scripts/gpc_sms_helper_macos.py index f02a3175..79f0d987 100644 --- a/scripts/gpc_sms_helper_macos.py +++ b/scripts/gpc_sms_helper_macos.py @@ -56,8 +56,9 @@ def is_macos() -> bool: def require_macos() -> None: if not is_macos(): raise RuntimeError( - "GPC 本地 SMS Helper 仅支持 macOS:需要读取 ~/Library/Messages/chat.db。" - "请确认接收验证码的 iPhone 已开启短信转发,并能在 Mac 信息 app 中看到短信。" + "GPC local SMS Helper only supports macOS: it needs to read ~/Library/Messages/chat.db. " + "Make sure the iPhone receiving the verification code has SMS forwarding enabled, " + "and that the messages are visible in the Mac Messages app." ) @@ -99,7 +100,7 @@ def get_state() -> dict: def copy_messages_db(db_path: Path) -> Path: if not db_path.exists(): - raise FileNotFoundError(f"Messages 数据库不存在:{db_path}") + raise FileNotFoundError(f"Messages database not found: {db_path}") tmpdir = Path(tempfile.mkdtemp(prefix="gpc_messages_")) copied = tmpdir / "chat.db" shutil.copy2(db_path, copied) @@ -377,7 +378,7 @@ def do_GET(self) -> None: # noqa: N802 state = get_state() record = select_otp_record(state, after_ms=after_ms, phone=phone) if not record: - write_json(self, 200, {"ok": True, "otp": "", "code": "", "status": "waiting", "message": "未查询到验证码"}) + write_json(self, 200, {"ok": True, "otp": "", "code": "", "status": "waiting", "message": "No verification code found yet"}) return payload = {"ok": True, "status": "found", **record} if consume: @@ -410,7 +411,7 @@ def main() -> int: scanner.start() server = ThreadingHTTPServer((args.host, int(args.port)), HelperHandler) print(f"GPC SMS Helper listening on http://{args.host}:{int(args.port)}", flush=True) - print("请确认 iPhone 短信已转发到本机 Messages。", flush=True) + print("Make sure iPhone SMS forwarding is enabled to this machine's Messages app.", flush=True) server.serve_forever() return 0 except KeyboardInterrupt: diff --git a/shared/contribution-registry.js b/shared/contribution-registry.js index b36abbec..a7256951 100644 --- a/shared/contribution-registry.js +++ b/shared/contribution-registry.js @@ -12,7 +12,7 @@ flowId: 'openai', artifactKind: 'openai-oauth', trigger: 'interactive-oauth', - label: 'OpenAI OAuth 贡献', + label: 'OpenAI OAuth contribution', defaultTargetId: DEFAULT_OPENAI_TARGET_ID, sensitiveFieldPaths: Object.freeze([ 'credentials.access_token', @@ -25,7 +25,7 @@ flowId: 'openai', artifactKind: 'codex', trigger: 'manual-upload', - label: 'OpenAI Codex 文件贡献', + label: 'OpenAI Codex file contribution', defaultTargetId: 'codex2api', sensitiveFieldPaths: Object.freeze([ 'credentials.access_token', @@ -38,7 +38,7 @@ flowId: 'openai', artifactKind: 'sub2api', trigger: 'manual-upload', - label: 'OpenAI Sub2API 文件贡献', + label: 'OpenAI Sub2API file contribution', defaultTargetId: 'sub2api', sensitiveFieldPaths: Object.freeze([ 'credentials.accounts[].credentials.access_token', @@ -51,7 +51,7 @@ flowId: 'kiro', artifactKind: 'kiro-builder-id', trigger: 'after-desktop-authorize', - label: 'Kiro Builder ID 贡献', + label: 'Kiro Builder ID contribution', defaultTargetId: DEFAULT_KIRO_TARGET_ID, sensitiveFieldPaths: Object.freeze([ 'credentials.refreshToken', diff --git a/sidepanel/account-pool-ui.js b/sidepanel/account-pool-ui.js index 642b55ff..9529e74f 100644 --- a/sidepanel/account-pool-ui.js +++ b/sidepanel/account-pool-ui.js @@ -3,8 +3,8 @@ const { formShell = null, toggleButton = null, - hiddenLabel = '添加账号', - visibleLabel = '取消添加', + hiddenLabel = 'Add Account', + visibleLabel = 'Cancel Add', onClear = null, onFocus = null, } = options; diff --git a/sidepanel/account-records-manager.js b/sidepanel/account-records-manager.js index 33d1d920..e9d58e68 100644 --- a/sidepanel/account-records-manager.js +++ b/sidepanel/account-records-manager.js @@ -13,40 +13,40 @@ const FILTER_CONFIG = { all: { - label: '总', + label: 'Total', className: '', matches: () => true, - metaLabel: '全部', + metaLabel: 'All', }, success: { - label: '成', + label: 'OK', className: 'is-success', matches: (record) => getRecordDisplayStatus(record) === 'success', - metaLabel: '成功', + metaLabel: 'Success', }, running: { - label: '运行', + label: 'Running', className: 'is-running', matches: (record) => getRecordDisplayStatus(record) === 'running', - metaLabel: '运行中', + metaLabel: 'Running', }, failed: { - label: '失', + label: 'Failed', className: 'is-failed', matches: (record) => getRecordDisplayStatus(record) === 'failed', - metaLabel: '失败', + metaLabel: 'Failed', }, stopped: { - label: '停', + label: 'Stop', className: 'is-stopped', matches: (record) => getRecordDisplayStatus(record) === 'stopped', - metaLabel: '停止', + metaLabel: 'Stopped', }, retry: { - label: '重试', + label: 'Retry', className: 'is-retry', matches: (record) => normalizeRetryCount(record.retryCount) > 0, - metaLabel: '重试', + metaLabel: 'Retry', }, }; @@ -150,7 +150,7 @@ return { ...record, displayStatus: 'running', - displaySummary: '正在运行', + displaySummary: 'Running', }; } @@ -205,16 +205,16 @@ const email = getRecordEmail(record); const phoneNumber = getRecordPhoneNumber(record); if (identifierType === 'phone' && email) { - return `邮箱 ${email}`; + return `Email ${email}`; } if (identifierType !== 'phone' && phoneNumber) { - return `绑定手机号 ${phoneNumber}`; + return `Bound Phone ${phoneNumber}`; } return ''; } function getRecordTitle(record = {}) { - const primaryIdentifier = getRecordPrimaryIdentifier(record) || '(空账号)'; + const primaryIdentifier = getRecordPrimaryIdentifier(record) || '(empty account)'; const secondaryIdentifier = getRecordSecondaryIdentifier(record); return secondaryIdentifier ? `${primaryIdentifier} / ${secondaryIdentifier}` @@ -292,15 +292,15 @@ function getStatusMeta(record = {}) { const status = getRecordDisplayStatus(record); if (status === 'success') { - return { kind: 'success', label: '成功' }; + return { kind: 'success', label: 'Success' }; } if (status === 'running') { - return { kind: 'running', label: '正在运行' }; + return { kind: 'running', label: 'Running' }; } if (status === 'stopped') { - return { kind: 'stopped', label: '停止' }; + return { kind: 'stopped', label: 'Stopped' }; } - return { kind: 'failed', label: '失败' }; + return { kind: 'failed', label: 'Failed' }; } function getRecordSummaryText(record = {}) { @@ -309,15 +309,15 @@ return String(record.displaySummary || '').trim(); } if (status === 'success') { - return '流程完成'; + return 'Flow completed'; } if (status === 'running') { - return '正在运行'; + return 'Running'; } return String(record.failureDetail || record.reason || '').trim() || String(record.failureLabel || '').trim() - || '流程失败'; + || 'Flow failed'; } function getRecordTooltipText(record = {}, summaryText = '') { @@ -438,19 +438,19 @@ } if (!allRecords.length) { - dom.accountRecordsMeta.textContent = '暂无账号记录'; + dom.accountRecordsMeta.textContent = 'No account records'; return; } const latestTime = formatAccountRecordTime(allRecords[0]?.finishedAt); - let metaText = `共 ${allRecords.length} 条,最近更新于 ${latestTime}`; + let metaText = `Total ${allRecords.length}, last updated at ${latestTime}`; if (activeFilter !== 'all') { - metaText = `共 ${allRecords.length} 条,当前筛选 ${getFilterConfig(activeFilter).metaLabel} ${filteredRecords.length} 条,最近更新于 ${latestTime}`; + metaText = `Total ${allRecords.length}, current filter ${getFilterConfig(activeFilter).metaLabel} ${filteredRecords.length}, last updated at ${latestTime}`; } if (selectionMode) { - metaText += `,已选 ${selectedRecordIds.size} 条`; + metaText += `, selected ${selectedRecordIds.size}`; } dom.accountRecordsMeta.textContent = metaText; @@ -479,14 +479,14 @@ setNodeHidden(dom.btnClearAccountRecords, selectionMode); toggleNodeClass(dom.btnToggleAccountRecordsSelection, 'is-active', selectionMode); setNodeAttr(dom.btnToggleAccountRecordsSelection, 'aria-pressed', selectionMode ? 'true' : 'false'); - setNodeText(dom.btnToggleAccountRecordsSelection, selectionMode ? '取消多选' : '多选'); + setNodeText(dom.btnToggleAccountRecordsSelection, selectionMode ? 'Cancel Multi-select' : 'Multi-select'); const selectedCount = selectedRecordIds.size; setNodeHidden(dom.btnDeleteSelectedAccountRecords, !selectionMode); setNodeDisabled(dom.btnDeleteSelectedAccountRecords, selectedCount === 0); setNodeText( dom.btnDeleteSelectedAccountRecords, - selectedCount > 0 ? `删除选中(${selectedCount})` : '删除选中' + selectedCount > 0 ? `Delete Selected(${selectedCount})` : 'Delete Selected' ); } @@ -513,8 +513,8 @@ } const message = allRecords.length - ? `当前筛选“${getFilterConfig(activeFilter).metaLabel}”下暂无记录` - : '暂无账号记录'; + ? `No records under current filter "${getFilterConfig(activeFilter).metaLabel}"` + : 'No account records'; dom.accountRecordsList.innerHTML = ``; } @@ -534,7 +534,7 @@ dom.accountRecordsList.innerHTML = visibleRecords.map((record) => { const recordId = buildRecordId(record); - const primaryIdentifier = getRecordPrimaryIdentifier(record) || '(空账号)'; + const primaryIdentifier = getRecordPrimaryIdentifier(record) || '(empty account)'; const secondaryIdentifier = getRecordSecondaryIdentifier(record); const statusMeta = getStatusMeta(record); const summaryText = getRecordSummaryText(record); @@ -580,7 +580,7 @@ `; @@ -653,14 +653,14 @@ async function clearRecords() { const records = getAccountRunRecords(); if (!records.length) { - helpers.showToast?.('没有可清理的账号记录。', 'warn', 1800); + helpers.showToast?.('No account records to clear.', 'warn', 1800); return; } const confirmed = await helpers.openConfirmModal({ - title: '清理账号记录', - message: '确认清理当前全部账号记录吗?该操作会同时清空面板记录与本地同步快照。', - confirmLabel: '确认清理', + title: 'Clear Account Records', + message: 'Are you sure you want to clear all current account records? This will also clear panel records and local sync snapshots.', + confirmLabel: 'Confirm Clear', confirmVariant: 'btn-danger', }); if (!confirmed) { @@ -680,20 +680,20 @@ selectionMode = false; resetSelection(); state.syncLatestState({ accountRunHistory: [] }); - helpers.showToast?.(`已清理 ${Math.max(0, Number(response?.clearedCount) || 0)} 条账号记录。`, 'success', 2200); + helpers.showToast?.(`Cleared ${Math.max(0, Number(response?.clearedCount) || 0)} account records.`, 'success', 2200); } async function deleteSelectedRecords() { const recordIds = Array.from(selectedRecordIds).filter(Boolean); if (!recordIds.length) { - helpers.showToast?.('请先勾选要删除的账号记录。', 'warn', 1800); + helpers.showToast?.('Please select account records to delete first.', 'warn', 1800); return; } const confirmed = await helpers.openConfirmModal({ - title: '删除选中记录', - message: `确认删除选中的 ${recordIds.length} 条账号记录吗?该操作会同步更新本地 helper 快照。`, - confirmLabel: '确认删除', + title: 'Delete Selected Records', + message: `Are you sure you want to delete ${recordIds.length} selected account records? This will sync update the local helper snapshot.`, + confirmLabel: 'Confirm Delete', confirmVariant: 'btn-danger', }); if (!confirmed) { @@ -717,7 +717,7 @@ resetSelection(); state.syncLatestState({ accountRunHistory: nextRecords }); - helpers.showToast?.(`已删除 ${Math.max(0, Number(response?.deletedCount) || 0)} 条账号记录。`, 'success', 2200); + helpers.showToast?.(`Deleted ${Math.max(0, Number(response?.deletedCount) || 0)} account records.`, 'success', 2200); } function handleStatsClick(event) { @@ -802,14 +802,14 @@ try { await deleteSelectedRecords(); } catch (error) { - helpers.showToast?.(`删除账号记录失败:${error.message}`, 'error'); + helpers.showToast?.(`Failed to delete account records: ${error.message}`, 'error'); } }); dom.btnClearAccountRecords?.addEventListener('click', async () => { try { await clearRecords(); } catch (error) { - helpers.showToast?.(`清理账号记录失败:${error.message}`, 'error'); + helpers.showToast?.(`Failed to clear account records: ${error.message}`, 'error'); } }); } diff --git a/sidepanel/contribution-content-update-service.js b/sidepanel/contribution-content-update-service.js index 386fd7af..b09380e1 100644 --- a/sidepanel/contribution-content-update-service.js +++ b/sidepanel/contribution-content-update-service.js @@ -125,12 +125,12 @@ }); if (!response.ok) { - throw new Error(`内容摘要请求失败:${response.status}`); + throw new Error(`Content summary request failed: ${response.status}`); } const payload = await response.json(); if (!payload || payload.ok !== true) { - throw new Error('内容摘要返回格式异常'); + throw new Error('Content summary response format error'); } const snapshot = buildSnapshot(payload, scope); @@ -138,7 +138,7 @@ return snapshot; } catch (error) { if (error?.name === 'AbortError') { - throw new Error('内容摘要请求超时'); + throw new Error('Content summary request timed out'); } throw error; } finally { @@ -156,7 +156,7 @@ return { ...cached, fromCache: true, - errorMessage: error?.message || '内容摘要获取失败', + errorMessage: error?.message || 'Failed to fetch content summary', }; } @@ -172,7 +172,7 @@ portalUrl: PORTAL_BASE_URL, apiUrl: buildSummaryApiUrl(scope), checkedAt: Date.now(), - errorMessage: error?.message || '内容摘要获取失败', + errorMessage: error?.message || 'Failed to fetch content summary', }; } } diff --git a/sidepanel/contribution-mode.js b/sidepanel/contribution-mode.js index d0c54e89..75813f6b 100644 --- a/sidepanel/contribution-mode.js +++ b/sidepanel/contribution-mode.js @@ -1,10 +1,10 @@ (function attachSidepanelContributionMode(globalScope) { const ACTIVE_STATUSES = new Set(['started', 'waiting', 'processing']); const FINAL_STATUSES = new Set(['auto_approved', 'auto_rejected', 'expired', 'error']); - const DEFAULT_COPY = '当前账号将用于支持项目维护。扩展会自动申请贡献登录地址并持续跟踪授权状态;如检测到回调地址,会自动提交,并继续等待服务端确认。'; + const DEFAULT_COPY = 'The current account will be used to support project maintenance. The extension will automatically request the contribution login URL and continuously track the authorization status. If a callback URL is detected, it will be submitted automatically and continue to wait for server confirmation.'; const CONTRIBUTION_SOURCE_CPA = 'cpa'; const CONTRIBUTION_SOURCE_SUB2API = 'sub2api'; - const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex号池'; + const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex pool'; function createContributionModeManager(context = {}) { const { @@ -87,7 +87,7 @@ const adapter = typeof registry.getAdapterDefinition === 'function' ? registry.getAdapterDefinition(currentState.contributionAdapterId || '', { flowId: getActiveFlowId(currentState) }) : null; - return normalizeString(adapter?.label) || '账号贡献'; + return normalizeString(adapter?.label) || 'Account Contribution'; } return getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API ? 'SUB2API' : 'CPA'; } @@ -201,13 +201,13 @@ dom.btnContributionMode.setAttribute('aria-pressed', String(enabled)); if (!available) { dom.btnContributionMode.disabled = true; - dom.btnContributionMode.title = '当前 flow 不支持贡献模式'; + dom.btnContributionMode.title = 'Current flow does not support contribution mode'; return; } dom.btnContributionMode.disabled = actionInFlight; dom.btnContributionMode.title = enabled - ? '打开当前 flow 教程;当前已在贡献模式' - : (blocked ? '打开当前 flow 教程;当前流程运行中暂时不能进入贡献模式' : '打开当前 flow 教程并进入贡献模式'); + ? 'Open current flow tutorial; currently in contribution mode' + : (blocked ? 'Open current flow tutorial; cannot enter contribution mode while current flow is running' : 'Open current flow tutorial and enter contribution mode'); } function stopPolling() { @@ -244,60 +244,60 @@ const flowRuntime = getCurrentFlowContributionRuntime(currentState); const status = normalizeString(flowRuntime.status).toLowerCase(); if (status === 'submitting') { - return '正在提交账号产物'; + return 'Submitting account artifact'; } if (status === 'submitted') { - return '账号产物已提交'; + return 'Account artifact submitted'; } if (status === 'skipped') { - return '账号产物未就绪'; + return 'Account artifact not ready'; } if (status === 'error') { - return '账号产物提交失败'; + return 'Failed to submit account artifact'; } - return isContributionModeEnabled(currentState) ? '等待账号产物' : '未开启贡献模式'; + return isContributionModeEnabled(currentState) ? 'Waiting for account artifact' : 'Contribution mode not enabled'; } const status = normalizeStatus(currentState.contributionStatus); const hasAuthUrl = Boolean(normalizeString(currentState.contributionAuthUrl)); if (!normalizeString(currentState.contributionSessionId) || !hasAuthUrl) { - return '未生成登录地址'; + return 'Login URL not generated'; } if (status === 'waiting') { - return '等待提交回调'; + return 'Waiting for callback submission'; } if (status === 'processing' || status === 'auto_approved' || status === 'auto_rejected') { - return status === 'processing' ? '已提交回调' : '授权已结束'; + return status === 'processing' ? 'Callback submitted' : 'Authorization ended'; } if (status === 'expired' || status === 'error') { - return '授权失败'; + return 'Authorization failed'; } if (Number(currentState.contributionAuthOpenedAt) > 0) { - return '已打开授权页'; + return 'Authorization page opened'; } - return '登录地址已生成'; + return 'Login URL generated'; } function getCallbackStatusText(currentState = getLatestState()) { if (getActiveFlowId(currentState) !== 'openai') { const flowRuntime = getCurrentFlowContributionRuntime(currentState); - return normalizeString(flowRuntime.lastMessage || flowRuntime.error) || '账号产物就绪后会自动提交'; + return normalizeString(flowRuntime.lastMessage || flowRuntime.error) || 'Will auto-submit once account artifact is ready'; } const status = normalizeCallbackStatus(currentState.contributionCallbackStatus); switch (status) { case 'captured': - return '已捕获回调地址'; + return 'Callback URL captured'; case 'submitting': - return '正在提交回调'; + return 'Submitting callback'; case 'submitted': - return '已提交回调'; + return 'Callback submitted'; case 'failed': - return '回调提交失败'; + return 'Callback submission failed'; case 'waiting': case 'idle': default: return normalizeString(currentState.contributionCallbackUrl) - ? '已捕获回调地址' - : '等待回调'; + ? 'Callback URL captured' + : 'Waiting for callback'; } } @@ -307,11 +307,11 @@ return statusMessage; } if (getActiveFlowId(currentState) !== 'openai') { - return '当前账号将用于支持项目维护。扩展会按当前 flow 的贡献适配器收集并提交账号产物,提交过程不会依赖 OpenAI OAuth 配置。'; + return 'The current account will be used to support project maintenance. The extension will collect and submit account artifacts according to the current flow contribution adapter; the submission process does not depend on OpenAI OAuth configuration.'; } if (getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API) { const groupName = normalizeString(currentState.contributionTargetGroupName) || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME; - return `当前账号将用于支持项目维护。贡献会通过 SUB2API 完成,并固定写入 ${groupName} 分组;如检测到回调地址,扩展会自动提交并等待服务端确认。`; + return `The current account will be used to support project maintenance. Contributions will be completed via SUB2API and written to the ${groupName} group; if a callback URL is detected, the extension will auto-submit and wait for server confirmation.`; } return DEFAULT_COPY; } @@ -353,7 +353,7 @@ const nickname = normalizeString(partial.nickname); const qq = normalizeString(partial.qq); if (qq && !/^\d{1,20}$/.test(qq)) { - throw new Error('QQ 只能填写数字,且长度不能超过 20 位。'); + throw new Error('QQ must contain only digits and cannot exceed 20 characters.'); } helpers.applySettingsState?.({ ...getLatestState(), @@ -382,7 +382,7 @@ throw new Error(response.error); } if (!response?.state) { - throw new Error('贡献模式切换后未返回最新状态。'); + throw new Error('No latest state returned after contribution mode toggle.'); } const nextState = applySelectedFlowToState(response.state, selectedFlowId, selectedTargetId); @@ -429,13 +429,13 @@ async function startAccountContributionFlow() { if (typeof helpers.startContributionAutoRun !== 'function') { - throw new Error('贡献模式尚未接入主自动流程启动能力。'); + throw new Error('Contribution mode has not yet integrated the main auto-run start capability.'); } const profile = helpers.getContributionProfile?.() || {}; const qq = normalizeString(profile.qq); if (qq && !/^\d{1,20}$/.test(qq)) { - throw new Error('QQ 只能填写数字,且长度不能超过 20 位。'); + throw new Error('QQ must contain only digits and cannot exceed 20 characters.'); } await syncContributionProfile(profile); const started = await helpers.startContributionAutoRun(); @@ -443,19 +443,19 @@ return; } - helpers.showToast?.('贡献自动流程已启动。', 'info', 1800); + helpers.showToast?.('Contribution auto-run started.', 'info', 1800); render(); } async function enterContributionMode() { await requestContributionMode(true); - helpers.showToast?.('已进入贡献模式。', 'success', 1800); + helpers.showToast?.('Entered contribution mode.', 'success', 1800); } async function exitContributionMode() { stopPolling(); await requestContributionMode(false); - helpers.showToast?.('已退出贡献模式。', 'info', 1800); + helpers.showToast?.('Exited contribution mode.', 'info', 1800); } function render() { @@ -499,13 +499,13 @@ dom.contributionOauthStatus.textContent = getOauthStatusText(currentState); } if (dom.contributionPrimaryStatusLabel) { - dom.contributionPrimaryStatusLabel.textContent = activeFlowId === 'openai' ? 'OAUTH' : '账号产物'; + dom.contributionPrimaryStatusLabel.textContent = activeFlowId === 'openai' ? 'OAUTH' : 'Account Artifact'; } if (dom.contributionCallbackStatus) { dom.contributionCallbackStatus.textContent = getCallbackStatusText(currentState); } if (dom.contributionSecondaryStatusLabel) { - dom.contributionSecondaryStatusLabel.textContent = activeFlowId === 'openai' ? '回调' : '提交'; + dom.contributionSecondaryStatusLabel.textContent = activeFlowId === 'openai' ? 'Callback' : 'Submit'; } if (dom.accountContributionSummary) { dom.accountContributionSummary.textContent = getSummaryText(currentState); @@ -525,12 +525,12 @@ if (dom.btnOpenContributionUpload) { dom.btnOpenContributionUpload.hidden = !available; dom.btnOpenContributionUpload.disabled = !available; - dom.btnOpenContributionUpload.textContent = '已有认证文件?前往上传'; + dom.btnOpenContributionUpload.textContent = 'Already have an auth file? Go to upload'; } if (dom.btnExitContributionMode) { dom.btnExitContributionMode.disabled = !available || actionInFlight || blocked; - dom.btnExitContributionMode.title = blocked ? '当前流程运行中,暂时不能退出贡献模式' : '退出贡献模式'; + dom.btnExitContributionMode.title = blocked ? 'Cannot exit contribution mode while current flow is running' : 'Exit contribution mode'; } if (dom.btnOpenAccountRecords) { @@ -557,14 +557,14 @@ try { openContributionPortalPage(); } catch (error) { - helpers.showToast?.(`打开官网页面失败:${error.message}`, 'error'); + helpers.showToast?.(`Failed to open portal page: ${error.message}`, 'error'); } render(); try { if (isContributionModeEnabled()) { - helpers.showToast?.('已打开当前 flow 教程。', 'info', 1800); + helpers.showToast?.('Opened current flow tutorial.', 'info', 1800); } else if (isModeSwitchBlocked()) { - helpers.showToast?.('已打开当前 flow 教程;当前流程运行中,暂时不能进入贡献模式。', 'warning', 2200); + helpers.showToast?.('Opened current flow tutorial; cannot enter contribution mode while current flow is running.', 'warning', 2200); } else { await enterContributionMode(); } @@ -622,7 +622,7 @@ try { openContributionUploadPage(); } catch (error) { - helpers.showToast?.(`打开上传页面失败:${error.message}`, 'error'); + helpers.showToast?.(`Failed to open upload page: ${error.message}`, 'error'); } }); diff --git a/sidepanel/custom-email-pool-manager.js b/sidepanel/custom-email-pool-manager.js index 82a3e1d8..d6405588 100644 --- a/sidepanel/custom-email-pool-manager.js +++ b/sidepanel/custom-email-pool-manager.js @@ -97,9 +97,9 @@ const haystack = [ entry.email, entry.note, - entry.enabled ? 'enabled 启用' : 'disabled 停用', - entry.used ? 'used 已用' : 'unused 未用', - entry.current ? 'current 当前' : '', + entry.enabled ? 'enabled' : 'disabled', + entry.used ? 'used' : 'unused', + entry.current ? 'current' : '', ].join(' ').toLowerCase(); return haystack.includes(normalizedSearchTerm); @@ -124,7 +124,7 @@ dom.checkboxCustomEmailPoolSelectAll.checked = hasVisible && selectedVisibleCount === visibleIds.length; dom.checkboxCustomEmailPoolSelectAll.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleIds.length; dom.checkboxCustomEmailPoolSelectAll.disabled = loading || !hasVisible; - dom.customEmailPoolSelectionSummary.textContent = `已选 ${selectedEntryIds.size} 个(当前显示 ${visibleIds.length} 个)`; + dom.customEmailPoolSelectionSummary.textContent = `Selected ${selectedEntryIds.size} (currently showing ${visibleIds.length})`; if (dom.btnCustomEmailPoolBulkUsed) dom.btnCustomEmailPoolBulkUsed.disabled = loading || !hasSelection; if (dom.btnCustomEmailPoolBulkUnused) dom.btnCustomEmailPoolBulkUnused.disabled = loading || !hasSelection; @@ -159,8 +159,8 @@ if (!renderedEntries.length) { selectedEntryIds.clear(); - dom.customEmailPoolList.innerHTML = '
还没有自定义邮箱,先导入一批邮箱再开始。
'; - dom.customEmailPoolSummary.textContent = '导入你提前准备好的注册邮箱,每行一个邮箱地址。'; + dom.customEmailPoolList.innerHTML = '
No custom emails yet. Import a batch of emails to begin.
'; + dom.customEmailPoolSummary.textContent = 'Import your pre-prepared registration emails, one email per line.'; if (dom.btnCustomEmailPoolClearUsed) dom.btnCustomEmailPoolClearUsed.disabled = true; if (dom.btnCustomEmailPoolDeleteAll) dom.btnCustomEmailPoolDeleteAll.disabled = true; updateBulkUi([]); @@ -170,13 +170,13 @@ const entriesWithCurrent = withCurrentFlag(renderedEntries); const usedCount = entriesWithCurrent.filter((entry) => entry.used).length; const enabledCount = entriesWithCurrent.filter((entry) => entry.enabled).length; - dom.customEmailPoolSummary.textContent = `已加载 ${entriesWithCurrent.length} 个邮箱,其中 ${enabledCount} 个启用,${usedCount} 个已标记为已用。`; + dom.customEmailPoolSummary.textContent = `Loaded ${entriesWithCurrent.length} emails, of which ${enabledCount} are enabled and ${usedCount} are marked as used.`; if (dom.btnCustomEmailPoolClearUsed) dom.btnCustomEmailPoolClearUsed.disabled = loading || usedCount === 0; if (dom.btnCustomEmailPoolDeleteAll) dom.btnCustomEmailPoolDeleteAll.disabled = loading || entriesWithCurrent.length === 0; const visibleEntries = getFilteredEntries(entriesWithCurrent); if (!visibleEntries.length) { - dom.customEmailPoolList.innerHTML = '
没有匹配当前筛选条件的邮箱。
'; + dom.customEmailPoolList.innerHTML = '
No emails match the current filter.
'; updateBulkUi([]); return; } @@ -189,27 +189,27 @@
- ${entry.current ? '当前' : ''} - ${entry.used ? '已用' : '未用'} - ${entry.enabled ? '启用' : '停用'} + ${entry.current ? 'Current' : ''} + ${entry.used ? 'Used' : 'Unused'} + ${entry.enabled ? 'Enabled' : 'Disabled'} ${entry.note ? `${helpers.escapeHtml(entry.note)}` : ''}
- - - - + + + +
`; @@ -224,17 +224,17 @@ item.querySelector('[data-action="copy-email"]').addEventListener('click', async () => { await helpers.copyTextToClipboard(entry.email || ''); - helpers.showToast('邮箱已复制', 'success', 1600); + helpers.showToast('Email copied', 'success', 1600); }); item.querySelector('[data-action="use"]').addEventListener('click', async () => { try { - setLoadingState(true, '正在切换当前邮箱...'); + setLoadingState(true, 'Switching current email...'); await actions.setRuntimeEmail?.(entry.email); - helpers.showToast(`已切换到 ${entry.email}`, 'success', 1800); + helpers.showToast(`Switched to ${entry.email}`, 'success', 1800); queueCustomEmailPoolRefresh(); } catch (error) { - helpers.showToast(`切换邮箱失败:${error.message}`, 'error'); + helpers.showToast(`Failed to switch email: ${error.message}`, 'error'); } finally { setLoadingState(false); } @@ -263,7 +263,7 @@ item.querySelector('[data-action="delete"]').addEventListener('click', async () => { await deleteEntries({ ids: [entry.id], - }, `确认删除 ${entry.email} 吗?此操作不可撤销。`); + }, `Are you sure you want to delete ${entry.email}? This cannot be undone.`); }); dom.customEmailPoolList.appendChild(item); @@ -276,7 +276,7 @@ const previousEntries = normalizeEntries(state.getEntries?.() || []); const nextEntries = normalizeEntries(mutator(previousEntries.map((entry) => ({ ...entry })))); - setLoadingState(true, '正在更新自定义邮箱池...'); + setLoadingState(true, 'Updating custom email pool...'); state.setEntries?.(nextEntries); renderCustomEmailPoolEntries(nextEntries); @@ -285,7 +285,7 @@ } catch (error) { state.setEntries?.(previousEntries); renderCustomEmailPoolEntries(previousEntries); - helpers.showToast(`更新自定义邮箱池失败:${error.message}`, 'error'); + helpers.showToast(`Failed to update custom email pool: ${error.message}`, 'error'); } finally { setLoadingState(false); } @@ -293,9 +293,9 @@ async function deleteEntries(payload = {}, confirmMessage = '') { const confirmed = await helpers.openConfirmModal({ - title: '删除邮箱', - message: confirmMessage || '确认删除选中的邮箱吗?此操作不可撤销。', - confirmLabel: '确认删除', + title: 'Delete Email', + message: confirmMessage || 'Are you sure you want to delete the selected emails? This cannot be undone.', + confirmLabel: 'Confirm Delete', confirmVariant: 'btn-danger', }); if (!confirmed) { @@ -327,7 +327,7 @@ async function importEntriesFromTextarea() { const text = String(dom.inputCustomEmailPoolImport?.value || ''); if (!text.trim()) { - helpers.showToast('请先粘贴邮箱列表,每行一个邮箱。', 'warn'); + helpers.showToast('Please paste the email list first, one email per line.', 'warn'); return; } @@ -358,12 +358,12 @@ } if (!importedEntries.length && skippedCount > 0) { - helpers.showToast('没有可导入的新邮箱(可能都重复或无效)。', 'warn'); + helpers.showToast('No new emails to import (all duplicates or invalid).', 'warn'); return; } const nextEntries = normalizeEntries([...previousEntries, ...importedEntries]); - setLoadingState(true, '正在导入邮箱...'); + setLoadingState(true, 'Importing emails...'); state.setEntries?.(nextEntries); renderCustomEmailPoolEntries(nextEntries); @@ -374,15 +374,15 @@ } helpers.showToast( skippedCount > 0 - ? `已导入 ${importedEntries.length} 个邮箱,跳过 ${skippedCount} 条无效或重复数据。` - : `已导入 ${importedEntries.length} 个邮箱。`, + ? `Imported ${importedEntries.length} emails, skipped ${skippedCount} invalid or duplicate entries.` + : `Imported ${importedEntries.length} emails.`, importedEntries.length > 0 ? 'success' : 'warn', 2400 ); } catch (error) { state.setEntries?.(previousEntries); renderCustomEmailPoolEntries(previousEntries); - helpers.showToast(`导入邮箱失败:${error.message}`, 'error'); + helpers.showToast(`Failed to import emails: ${error.message}`, 'error'); } finally { setLoadingState(false); } @@ -395,7 +395,7 @@ } if (!silent) { - setLoadingState(true, '正在刷新自定义邮箱池...'); + setLoadingState(true, 'Refreshing custom email pool...'); } renderCustomEmailPoolEntries(state.getEntries?.()); if (!silent) { @@ -422,7 +422,7 @@ dom.customEmailPoolList.innerHTML = ''; } if (dom.customEmailPoolSummary) { - dom.customEmailPoolSummary.textContent = '导入你提前准备好的注册邮箱,每行一个邮箱地址。'; + dom.customEmailPoolSummary.textContent = 'Import your pre-prepared registration emails, one email per line.'; } updateBulkUi([]); } @@ -503,19 +503,19 @@ dom.btnCustomEmailPoolBulkDelete?.addEventListener('click', async () => { await deleteEntries({ ids: [...selectedEntryIds], - }, `确认删除当前选中的 ${selectedEntryIds.size} 个邮箱吗?此操作不可撤销。`); + }, `Are you sure you want to delete the ${selectedEntryIds.size} selected emails? This cannot be undone.`); }); dom.btnCustomEmailPoolClearUsed?.addEventListener('click', async () => { await deleteEntries({ mode: 'used', - }, '确认删除当前所有已用邮箱吗?此操作不可撤销。'); + }, 'Are you sure you want to delete all currently used emails? This cannot be undone.'); }); dom.btnCustomEmailPoolDeleteAll?.addEventListener('click', async () => { await deleteEntries({ mode: 'all', - }, '确认删除当前全部邮箱吗?此操作不可撤销。'); + }, 'Are you sure you want to delete all current emails? This cannot be undone.'); }); } diff --git a/sidepanel/editable-list-picker.js b/sidepanel/editable-list-picker.js index fcbf61f8..fa216fb9 100644 --- a/sidepanel/editable-list-picker.js +++ b/sidepanel/editable-list-picker.js @@ -38,11 +38,11 @@ trigger, current, menu, - emptyLabel = '请先添加', + emptyLabel = 'Please add first', fallbackItems = [], minItems = 0, - deleteLabel = '删除', - itemLabel = '项目', + deleteLabel = 'Delete', + itemLabel = 'Item', normalizeItems = normalizeEditableListValues, normalizeValue = (value) => String(value || '').trim(), getItemValue = (item) => String(item || '').trim(), @@ -69,7 +69,7 @@ return picker.items.find((item) => getNormalizedItemValue(item) === normalized) || null; }; const reportDeleteError = (error) => { - const fallbackMessage = `${deleteLabel}${itemLabel}失败。`; + const fallbackMessage = `Failed to ${deleteLabel.toLowerCase()} ${itemLabel.toLowerCase()}.`; if (typeof onDeleteError === 'function') { onDeleteError(error, fallbackMessage); return; @@ -170,8 +170,8 @@ deleteButton.type = 'button'; deleteButton.className = 'editable-list-delete'; deleteButton.textContent = deleteLabel; - deleteButton.title = `${deleteLabel}${itemLabel} ${getItemDeleteLabel(item)}`; - deleteButton.setAttribute('aria-label', `${deleteLabel}${itemLabel} ${getItemDeleteLabel(item)}`); + deleteButton.title = `${deleteLabel} ${itemLabel} ${getItemDeleteLabel(item)}`; + deleteButton.setAttribute('aria-label', `${deleteLabel} ${itemLabel} ${getItemDeleteLabel(item)}`); deleteButton.disabled = picker.items.length <= minItems; deleteButton.addEventListener('click', (event) => { event.preventDefault(); diff --git a/sidepanel/form-dialog.js b/sidepanel/form-dialog.js index 7ea0da40..b2bdf559 100644 --- a/sidepanel/form-dialog.js +++ b/sidepanel/form-dialog.js @@ -130,8 +130,8 @@ toggleButton.className = 'input-icon-btn'; toggleButton.type = 'button'; const labels = { - show: String(field.showPasswordLabel || `\u663e\u793a${labelText || '\u5bc6\u7801'}`), - hide: String(field.hidePasswordLabel || `\u9690\u85cf${labelText || '\u5bc6\u7801'}`), + show: String(field.showPasswordLabel || `Show ${labelText || 'Password'}`), + hide: String(field.hidePasswordLabel || `Hide ${labelText || 'Password'}`), }; syncPasswordToggleButton(toggleButton, input, labels); toggleButton.addEventListener('click', () => { @@ -180,7 +180,7 @@ const rawValue = values[field.key]; const textValue = String(rawValue || '').trim(); if (field.required && !textValue) { - setAlert(field.requiredMessage || `${field.label || field.key}不能为空。`); + setAlert(field.requiredMessage || `${field.label || field.key} cannot be empty.`); input.focus?.(); return; } @@ -220,7 +220,7 @@ currentConfig = config || {}; currentInputs = []; - titleNode.textContent = String(currentConfig.title || '填写表单'); + titleNode.textContent = String(currentConfig.title || 'Fill Out Form'); if (messageNode) { const message = String(currentConfig.message || '').trim(); messageNode.textContent = message; @@ -231,7 +231,7 @@ setAlert(currentConfig.alert.text, currentConfig.alert.tone || 'danger'); } - confirmButton.textContent = String(currentConfig.confirmLabel || '确认'); + confirmButton.textContent = String(currentConfig.confirmLabel || 'Confirm'); confirmButton.className = `btn ${currentConfig.confirmVariant || 'btn-primary'} btn-sm`; fieldsContainer.innerHTML = ''; diff --git a/sidepanel/hotmail-manager.js b/sidepanel/hotmail-manager.js index 24927301..7a63ef1c 100644 --- a/sidepanel/hotmail-manager.js +++ b/sidepanel/hotmail-manager.js @@ -35,8 +35,8 @@ return hotmailUtils.getHotmailBulkActionLabel(mode, count); } const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0; - const prefix = mode === 'used' ? '清空已用' : '全部删除'; - const suffix = normalizedCount > 0 ? `(${normalizedCount})` : ''; + const prefix = mode === 'used' ? 'Clear Used' : 'Delete All'; + const suffix = normalizedCount > 0 ? `(${normalizedCount})` : ''; return `${prefix}${suffix}`; } @@ -45,8 +45,8 @@ return hotmailUtils.getHotmailListToggleLabel(expanded, count); } const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0; - const suffix = normalizedCount > 0 ? `(${normalizedCount})` : ''; - return `${expanded ? '收起列表' : '展开列表'}${suffix}`; + const suffix = normalizedCount > 0 ? `(${normalizedCount})` : ''; + return `${expanded ? 'Collapse List' : 'Expand List'}${suffix}`; } function updateHotmailListViewport() { @@ -142,7 +142,7 @@ function formatDateTime(timestamp) { const value = Number(timestamp); if (!Number.isFinite(value) || value <= 0) { - return '未使用'; + return 'Not used'; } return new Date(value).toLocaleString('zh-CN', { hour12: false, @@ -151,20 +151,20 @@ } function getHotmailAvailabilityLabel(account) { - if (account.used) return '已用'; - return '可分配'; + if (account.used) return 'Used'; + return 'Available'; } function getHotmailStatusLabel(account) { - if (account.used) return '已用'; + if (account.used) return 'Used'; switch (account.status) { case 'authorized': - return '可用'; + return 'OK'; case 'error': - return '异常'; + return 'Error'; default: - return '待校验'; + return 'Pending'; } } @@ -199,7 +199,7 @@ account.status, getHotmailAvailabilityLabel(account), getHotmailStatusLabel(account), - isCurrent ? 'current 当前' : '', + isCurrent ? 'current' : '', ].join(' ').toLowerCase(); return haystack.includes(normalizedSearchTerm); @@ -217,8 +217,8 @@ ? createAccountPoolFormController({ formShell: dom.hotmailFormShell, toggleButton: dom.btnToggleHotmailForm, - hiddenLabel: '添加账号', - visibleLabel: '取消添加', + hiddenLabel: 'Add Account', + visibleLabel: 'Cancel Add', onClear: () => { clearHotmailForm(); }, @@ -239,14 +239,14 @@ const currentId = latestState?.currentHotmailAccountId || ''; if (!accounts.length) { - dom.hotmailAccountsList.innerHTML = '
还没有 Hotmail 账号,先添加一条再校验。
'; + dom.hotmailAccountsList.innerHTML = '
No Hotmail accounts yet. Add one before verifying.
'; updateHotmailListViewport(); return; } const visibleAccounts = getFilteredHotmailAccounts(accounts, currentId); if (!visibleAccounts.length) { - dom.hotmailAccountsList.innerHTML = '
没有匹配当前筛选条件的 Hotmail 账号。
'; + dom.hotmailAccountsList.innerHTML = '
No Hotmail accounts match the current filter.
'; updateHotmailListViewport(); return; } @@ -255,32 +255,32 @@ `).join(''); @@ -291,16 +291,16 @@ const isUsedMode = mode === 'used'; const targetAccounts = getHotmailAccountsByUsage(isUsedMode ? 'used' : 'all'); if (!targetAccounts.length) { - helpers.showToast(isUsedMode ? '没有已用账号可清空。' : '没有可删除的 Hotmail 账号。', 'warn'); + helpers.showToast(isUsedMode ? 'No used accounts to clear.' : 'No Hotmail accounts to delete.', 'warn'); return; } const confirmed = await helpers.openConfirmModal({ - title: isUsedMode ? '清空已用账号' : '全部删除账号', + title: isUsedMode ? 'Clear Used Accounts' : 'Delete All Accounts', message: isUsedMode - ? `确认删除当前 ${targetAccounts.length} 个已用 Hotmail 账号吗?` - : `确认删除全部 ${targetAccounts.length} 个 Hotmail 账号吗?`, - confirmLabel: isUsedMode ? '确认清空已用' : '确认全部删除', + ? `Are you sure you want to delete the ${targetAccounts.length} currently used Hotmail accounts?` + : `Are you sure you want to delete all ${targetAccounts.length} Hotmail accounts?`, + confirmLabel: isUsedMode ? 'Confirm Clear Used' : 'Confirm Delete All', confirmVariant: isUsedMode ? 'btn-outline' : 'btn-danger', }); if (!confirmed) { @@ -334,8 +334,8 @@ helpers.showToast( isUsedMode - ? `已清空 ${response.deletedCount || 0} 个已用 Hotmail 账号` - : `已删除全部 ${response.deletedCount || 0} 个 Hotmail 账号`, + ? `Cleared ${response.deletedCount || 0} used Hotmail accounts` + : `Deleted all ${response.deletedCount || 0} Hotmail accounts`, 'success', 2200 ); @@ -348,15 +348,15 @@ const clientId = dom.inputHotmailClientId.value.trim(); const refreshToken = dom.inputHotmailRefreshToken.value.trim(); if (!email) { - helpers.showToast('请先填写 Hotmail 邮箱。', 'warn'); + helpers.showToast('Please fill in the Hotmail email first.', 'warn'); return; } if (!clientId) { - helpers.showToast('请先填写微软应用客户端 ID。', 'warn'); + helpers.showToast('Please fill in the Microsoft app client ID first.', 'warn'); return; } if (!refreshToken) { - helpers.showToast('请先填写刷新令牌(refresh token)。', 'warn'); + helpers.showToast('Please fill in the refresh token first.', 'warn'); return; } @@ -379,10 +379,10 @@ throw new Error(response.error); } - helpers.showToast(`已保存 Hotmail 账号 ${email}`, 'success', 1800); + helpers.showToast(`Saved Hotmail account ${email}`, 'success', 1800); formController.setVisible(false, { clearForm: true }); } catch (err) { - helpers.showToast(`保存 Hotmail 账号失败:${err.message}`, 'error'); + helpers.showToast(`Failed to save Hotmail account: ${err.message}`, 'error'); } finally { actionInFlight = false; dom.btnAddHotmailAccount.disabled = false; @@ -392,19 +392,19 @@ async function handleImportHotmailAccounts() { if (actionInFlight) return; if (typeof hotmailUtils.parseHotmailImportText !== 'function') { - helpers.showToast('导入解析器未加载,请刷新扩展后重试。', 'error'); + helpers.showToast('Import parser not loaded, please refresh the extension and retry.', 'error'); return; } const rawText = dom.inputHotmailImport.value.trim(); if (!rawText) { - helpers.showToast('请先粘贴账号导入内容。', 'warn'); + helpers.showToast('Please paste the account import content first.', 'warn'); return; } const parsedAccounts = hotmailUtils.parseHotmailImportText(rawText); if (!parsedAccounts.length) { - helpers.showToast('没有解析到有效账号,请检查格式是否为 账号----密码----ID----Token。', 'error'); + helpers.showToast('No valid accounts parsed. Please check format: account----password----ID----Token.', 'error'); return; } @@ -424,9 +424,9 @@ } dom.inputHotmailImport.value = ''; - helpers.showToast(`已导入 ${parsedAccounts.length} 条 Hotmail 账号`, 'success', 2200); + helpers.showToast(`Imported ${parsedAccounts.length} Hotmail accounts`, 'success', 2200); } catch (err) { - helpers.showToast(`批量导入失败:${err.message}`, 'error'); + helpers.showToast(`Bulk import failed: ${err.message}`, 'error'); } finally { actionInFlight = false; dom.btnImportHotmailAccounts.disabled = false; @@ -452,9 +452,9 @@ try { if (action === 'copy-email') { - if (!targetAccount?.email) throw new Error('未找到可复制的邮箱地址。'); + if (!targetAccount?.email) throw new Error('No email address found to copy.'); await helpers.copyTextToClipboard(targetAccount.email); - helpers.showToast(`已复制 ${targetAccount.email}`, 'success', 1800); + helpers.showToast(`Copied ${targetAccount.email}`, 'success', 1800); } else if (action === 'select') { const response = await runtime.sendMessage({ type: 'SELECT_HOTMAIL_ACCOUNT', @@ -464,9 +464,9 @@ if (response?.error) throw new Error(response.error); state.syncLatestState({ currentHotmailAccountId: response.account.id }); applyHotmailAccountMutation(response.account, { preserveCurrentSelection: true }); - helpers.showToast(`已切换当前 Hotmail 账号为 ${response.account.email}`, 'success', 1800); + helpers.showToast(`Switched current Hotmail account to ${response.account.email}`, 'success', 1800); } else if (action === 'toggle-used') { - if (!targetAccount) throw new Error('未找到目标 Hotmail 账号。'); + if (!targetAccount) throw new Error('Target Hotmail account not found.'); const response = await runtime.sendMessage({ type: 'PATCH_HOTMAIL_ACCOUNT', source: 'sidepanel', @@ -477,7 +477,7 @@ }); if (response?.error) throw new Error(response.error); applyHotmailAccountMutation(response.account); - helpers.showToast(`账号 ${response.account.email} 已${response.account.used ? '标记为已用' : '恢复为未用'}`, 'success', 2200); + helpers.showToast(`Account ${response.account.email} ${response.account.used ? 'marked as used' : 'restored as unused'}`, 'success', 2200); } else if (action === 'verify') { const response = await runtime.sendMessage({ type: 'VERIFY_HOTMAIL_ACCOUNT', @@ -486,7 +486,7 @@ }); if (response?.error) throw new Error(response.error); applyHotmailAccountMutation(response.account, { preserveCurrentSelection: true }); - helpers.showToast(`账号 ${response.account.email} 校验通过`, 'success', 2200); + helpers.showToast(`Account ${response.account.email} verified`, 'success', 2200); } else if (action === 'test') { const response = await runtime.sendMessage({ type: 'TEST_HOTMAIL_ACCOUNT', @@ -497,19 +497,19 @@ applyHotmailAccountMutation(response.account, { preserveCurrentSelection: true }); if (response.latestCode) { await helpers.copyTextToClipboard(response.latestCode); - const mailbox = response.latestMailbox ? `(${response.latestMailbox})` : ''; - helpers.showToast(`已复制最新验证码 ${response.latestCode}${mailbox}`, 'success', 2600); + const mailbox = response.latestMailbox ? `(${response.latestMailbox})` : ''; + helpers.showToast(`Copied latest code ${response.latestCode}${mailbox}`, 'success', 2600); } else if (response.latestSubject) { - const mailbox = response.latestMailbox ? `(${response.latestMailbox})` : ''; - helpers.showToast(`最新邮件${mailbox}没有验证码:${response.latestSubject}`, 'warn', 3200); + const mailbox = response.latestMailbox ? `(${response.latestMailbox})` : ''; + helpers.showToast(`Latest email${mailbox} has no code: ${response.latestSubject}`, 'warn', 3200); } else { - helpers.showToast('当前没有可读取的最新邮件。', 'warn', 2600); + helpers.showToast('No latest email available to read.', 'warn', 2600); } } else if (action === 'delete') { const confirmed = await helpers.openConfirmModal({ - title: '删除账号', - message: '确认删除这个 Hotmail 账号吗?对应 token 也会一起移除。', - confirmLabel: '确认删除', + title: 'Delete Account', + message: 'Are you sure you want to delete this Hotmail account? The corresponding token will also be removed.', + confirmLabel: 'Confirm Delete', confirmVariant: 'btn-danger', }); if (!confirmed) { @@ -521,7 +521,7 @@ payload: { accountId }, }); if (response?.error) throw new Error(response.error); - helpers.showToast('Hotmail 账号已删除', 'success', 1800); + helpers.showToast('Hotmail account deleted', 'success', 1800); } } catch (err) { helpers.showToast(err.message, 'error'); @@ -546,9 +546,9 @@ dom.btnHotmailUsageGuide?.addEventListener('click', async () => { await helpers.openConfirmModal({ - title: '使用教程', - message: 'API对接模式会直接调用微软邮箱接口取件;本地助手模式仍走本地服务。两种模式继续共用同一套 Hotmail 账号池与导入格式。', - confirmLabel: '确定', + title: 'Usage Guide', + message: 'API mode directly calls the Microsoft email interface to fetch mail; local helper mode still uses the local service. Both modes share the same Hotmail account pool and import format.', + confirmLabel: 'OK', confirmVariant: 'btn-primary', }); }); diff --git a/sidepanel/icloud-manager.js b/sidepanel/icloud-manager.js index a7ebe183..afc34c0e 100644 --- a/sidepanel/icloud-manager.js +++ b/sidepanel/icloud-manager.js @@ -36,9 +36,9 @@ alias.email, alias.label, alias.note, - alias.used ? '已用 used' : '未用 unused', - alias.active ? '可用 active' : '不可用 inactive', - alias.preserved ? '保留 preserved' : '', + alias.used ? 'used' : 'unused', + alias.active ? 'active' : 'inactive', + alias.preserved ? 'preserved' : '', ].join(' ').toLowerCase(); return haystack.includes(normalizedSearchTerm); @@ -62,7 +62,7 @@ dom.checkboxIcloudSelectAll.checked = hasVisible && selectedVisibleCount === visibleEmails.length; dom.checkboxIcloudSelectAll.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleEmails.length; dom.checkboxIcloudSelectAll.disabled = !hasVisible; - dom.icloudSelectionSummary.textContent = `已选 ${selectedEmails.size} 个(当前显示 ${visibleEmails.length} 个)`; + dom.icloudSelectionSummary.textContent = `Selected ${selectedEmails.size} (currently showing ${visibleEmails.length})`; const hasSelection = selectedEmails.size > 0; if (dom.btnIcloudBulkUsed) dom.btnIcloudBulkUsed.disabled = !hasSelection; @@ -98,8 +98,8 @@ host = loginUrl; } } - if (dom.icloudLoginHelpTitle) dom.icloudLoginHelpTitle.textContent = '需要登录 iCloud'; - if (dom.icloudLoginHelpText) dom.icloudLoginHelpText.textContent = `我已经为你打开 ${host}。请在那个页面完成登录,然后回到这里点击“我已登录”。`; + if (dom.icloudLoginHelpTitle) dom.icloudLoginHelpTitle.textContent = 'iCloud login required'; + if (dom.icloudLoginHelpText) dom.icloudLoginHelpText.textContent = `I have already opened ${host} for you. Please complete the login there, then return here and click "I have logged in".`; dom.icloudLoginHelp.style.display = 'flex'; } @@ -118,8 +118,8 @@ if (!aliases.length) { selectedEmails.clear(); - dom.icloudList.innerHTML = '
未找到 iCloud Hide My Email 别名。
'; - dom.icloudSummary.textContent = '加载你的 iCloud Hide My Email 别名以便在这里管理。'; + dom.icloudList.innerHTML = '
No iCloud Hide My Email aliases found.
'; + dom.icloudSummary.textContent = 'Load your iCloud Hide My Email aliases to manage them here.'; if (dom.btnIcloudDeleteUsed) dom.btnIcloudDeleteUsed.disabled = true; updateIcloudBulkUI([]); return; @@ -127,12 +127,12 @@ const usedCount = aliases.filter((alias) => alias.used).length; const deletableUsedCount = aliases.filter((alias) => alias.used && !alias.preserved).length; - dom.icloudSummary.textContent = `已加载 ${aliases.length} 个别名,其中 ${usedCount} 个已标记为已用。`; + dom.icloudSummary.textContent = `Loaded ${aliases.length} aliases, of which ${usedCount} are marked as used.`; if (dom.btnIcloudDeleteUsed) dom.btnIcloudDeleteUsed.disabled = deletableUsedCount === 0; const visibleAliases = getFilteredIcloudAliases(aliases); if (!visibleAliases.length) { - dom.icloudList.innerHTML = '
没有匹配当前筛选条件的别名。
'; + dom.icloudList.innerHTML = '
No aliases match the current filter.
'; updateIcloudBulkUI([]); return; } @@ -145,17 +145,17 @@
- ${alias.used ? '已用' : ''} - ${!alias.used && alias.active ? '可用' : ''} - ${alias.preserved ? '保留' : ''} + ${alias.used ? 'Used' : ''} + ${!alias.used && alias.active ? 'Available' : ''} + ${alias.preserved ? 'Preserved' : ''} ${alias.label ? `${helpers.escapeHtml(alias.label)}` : ''} ${alias.note ? `${helpers.escapeHtml(alias.note)}` : ''}
- - - + + +
`; @@ -188,7 +188,7 @@ return; } - if (!silent) setIcloudLoadingState(true, '正在加载 iCloud 别名...'); + if (!silent) setIcloudLoadingState(true, 'Loading iCloud aliases...'); try { const response = await runtime.sendMessage({ type: 'LIST_ICLOUD_ALIASES', @@ -201,13 +201,13 @@ } catch (err) { selectedEmails.clear(); if (dom.icloudList) { - dom.icloudList.innerHTML = '
无法加载 iCloud 别名。
'; + dom.icloudList.innerHTML = '
Failed to load iCloud aliases.
'; } if (dom.icloudSummary) { dom.icloudSummary.textContent = err.message; } updateIcloudBulkUI([]); - if (!silent) helpers.showToast(`iCloud 别名加载失败:${err.message}`, 'error'); + if (!silent) helpers.showToast(`Failed to load iCloud aliases: ${err.message}`, 'error'); } finally { setIcloudLoadingState(false); } @@ -224,16 +224,16 @@ async function deleteSingleIcloudAlias(alias) { const confirmed = await helpers.openConfirmModal({ - title: '删除 iCloud 别名', - message: `确认删除 ${alias.email} 吗?此操作不可撤销。`, - confirmLabel: '确认删除', + title: 'Delete iCloud Alias', + message: `Are you sure you want to delete ${alias.email}? This cannot be undone.`, + confirmLabel: 'Confirm Delete', confirmVariant: 'btn-danger', }); if (!confirmed) { return; } - setIcloudLoadingState(true, `正在删除 ${alias.email} ...`); + setIcloudLoadingState(true, `Deleting ${alias.email} ...`); try { const response = await runtime.sendMessage({ type: 'DELETE_ICLOUD_ALIAS', @@ -241,18 +241,18 @@ payload: { email: alias.email, anonymousId: alias.anonymousId }, }); if (response?.error) throw new Error(response.error); - helpers.showToast(`已删除 ${alias.email}`, 'success', 2200); + helpers.showToast(`Deleted ${alias.email}`, 'success', 2200); await refreshIcloudAliases({ silent: true }); } catch (err) { if (dom.icloudSummary) dom.icloudSummary.textContent = err.message; - helpers.showToast(`删除 iCloud 别名失败:${err.message}`, 'error'); + helpers.showToast(`Failed to delete iCloud alias: ${err.message}`, 'error'); } finally { setIcloudLoadingState(false); } } async function setSingleIcloudAliasUsedState(alias, used) { - setIcloudLoadingState(true, `正在更新 ${alias.email} 的使用状态...`); + setIcloudLoadingState(true, `Updating used state of ${alias.email}...`); try { const response = await runtime.sendMessage({ type: 'SET_ICLOUD_ALIAS_USED_STATE', @@ -260,18 +260,18 @@ payload: { email: alias.email, used }, }); if (response?.error) throw new Error(response.error); - helpers.showToast(`${alias.email} 已${used ? '标记为已用' : '恢复为未用'}`, 'success', 2200); + helpers.showToast(`${alias.email} ${used ? 'marked as used' : 'restored as unused'}`, 'success', 2200); await refreshIcloudAliases({ silent: true }); } catch (err) { if (dom.icloudSummary) dom.icloudSummary.textContent = err.message; - helpers.showToast(`更新 iCloud 使用状态失败:${err.message}`, 'error'); + helpers.showToast(`Failed to update iCloud used state: ${err.message}`, 'error'); } finally { setIcloudLoadingState(false); } } async function setSingleIcloudAliasPreservedState(alias, preserved) { - setIcloudLoadingState(true, `正在更新 ${alias.email} 的保留状态...`); + setIcloudLoadingState(true, `Updating preserved state of ${alias.email}...`); try { const response = await runtime.sendMessage({ type: 'SET_ICLOUD_ALIAS_PRESERVED_STATE', @@ -279,11 +279,11 @@ payload: { email: alias.email, preserved }, }); if (response?.error) throw new Error(response.error); - helpers.showToast(`${alias.email} 已${preserved ? '设为保留' : '取消保留'}`, 'success', 2200); + helpers.showToast(`${alias.email} ${preserved ? 'set as preserved' : 'unpreserved'}`, 'success', 2200); await refreshIcloudAliases({ silent: true }); } catch (err) { if (dom.icloudSummary) dom.icloudSummary.textContent = err.message; - helpers.showToast(`更新 iCloud 保留状态失败:${err.message}`, 'error'); + helpers.showToast(`Failed to update iCloud preserved state: ${err.message}`, 'error'); } finally { setIcloudLoadingState(false); } @@ -298,9 +298,9 @@ if (action === 'delete') { const confirmed = await helpers.openConfirmModal({ - title: '批量删除 iCloud 别名', - message: `确认删除选中的 ${selectedAliases.length} 个 iCloud 别名吗?此操作不可撤销。`, - confirmLabel: '确认删除', + title: 'Bulk Delete iCloud Aliases', + message: `Are you sure you want to delete the ${selectedAliases.length} selected iCloud aliases? This cannot be undone.`, + confirmLabel: 'Confirm Delete', confirmVariant: 'btn-danger', }); if (!confirmed) { @@ -309,13 +309,13 @@ } const actionLabelMap = { - used: '标记已用', - unused: '标记未用', - preserve: '保留', - unpreserve: '取消保留', - delete: '删除', + used: 'Mark Used', + unused: 'Mark Unused', + preserve: 'Preserve', + unpreserve: 'Unpreserve', + delete: 'Delete', }; - setIcloudLoadingState(true, `正在批量${actionLabelMap[action] || '处理'} iCloud 别名...`); + setIcloudLoadingState(true, `Bulk ${actionLabelMap[action] || 'processing'} iCloud aliases...`); try { for (const alias of selectedAliases) { @@ -346,11 +346,11 @@ } } - helpers.showToast(`已批量${actionLabelMap[action] || '处理'} ${selectedAliases.length} 个 iCloud 别名`, 'success', 2400); + helpers.showToast(`Bulk ${actionLabelMap[action] || 'processed'} ${selectedAliases.length} iCloud aliases`, 'success', 2400); await refreshIcloudAliases({ silent: true }); } catch (err) { if (dom.icloudSummary) dom.icloudSummary.textContent = err.message; - helpers.showToast(`批量处理 iCloud 别名失败:${err.message}`, 'error'); + helpers.showToast(`Bulk processing iCloud aliases failed: ${err.message}`, 'error'); } finally { setIcloudLoadingState(false); updateIcloudBulkUI(); @@ -359,16 +359,16 @@ async function deleteUsedIcloudAliases() { const confirmed = await helpers.openConfirmModal({ - title: '删除已用 iCloud 别名', - message: '确认删除所有未保留的已用 iCloud 别名吗?此操作不可撤销。', - confirmLabel: '确认删除', + title: 'Delete Used iCloud Aliases', + message: 'Are you sure you want to delete all unpreserved used iCloud aliases? This cannot be undone.', + confirmLabel: 'Confirm Delete', confirmVariant: 'btn-danger', }); if (!confirmed) { return; } - setIcloudLoadingState(true, '正在删除已用 iCloud 别名...'); + setIcloudLoadingState(true, 'Deleting used iCloud aliases...'); try { const response = await runtime.sendMessage({ type: 'DELETE_USED_ICLOUD_ALIASES', @@ -378,11 +378,11 @@ if (response?.error) throw new Error(response.error); const deleted = response?.deleted || []; const skipped = response?.skipped || []; - helpers.showToast(`已删除 ${deleted.length} 个已用别名,跳过 ${skipped.length} 个`, skipped.length ? 'warn' : 'success', 2800); + helpers.showToast(`Deleted ${deleted.length} used aliases, skipped ${skipped.length}`, skipped.length ? 'warn' : 'success', 2800); await refreshIcloudAliases({ silent: true }); } catch (err) { if (dom.icloudSummary) dom.icloudSummary.textContent = err.message; - helpers.showToast(`删除已用 iCloud 别名失败:${err.message}`, 'error'); + helpers.showToast(`Failed to delete used iCloud aliases: ${err.message}`, 'error'); } finally { setIcloudLoadingState(false); } @@ -415,17 +415,17 @@ throw new Error(response.error); } hideIcloudLoginHelp(); - helpers.showToast('iCloud 会话已恢复,别名列表已刷新。', 'success', 2600); + helpers.showToast('iCloud session restored, alias list refreshed.', 'success', 2600); await refreshIcloudAliases({ silent: true }); } catch (err) { - const errorMessage = String(err?.message || '未知错误'); + const errorMessage = String(err?.message || 'Unknown error'); if (isLikelyIcloudLoginRequiredMessage(errorMessage)) { - helpers.showToast(`看起来还没有登录完成:${errorMessage}`, 'warn', 4200); + helpers.showToast(`Looks like login is not complete yet: ${errorMessage}`, 'warn', 4200); return; } await refreshIcloudAliases({ silent: true }).catch(() => { }); - helpers.showToast(`iCloud 会话校验失败(非登录态):${errorMessage}`, 'warn', 4200); + helpers.showToast(`iCloud session check failed (not logged in): ${errorMessage}`, 'warn', 4200); } finally { if (dom.btnIcloudLoginDone) { dom.btnIcloudLoginDone.disabled = false; @@ -442,7 +442,7 @@ if (dom.inputIcloudSearch) dom.inputIcloudSearch.value = ''; if (dom.selectIcloudFilter) dom.selectIcloudFilter.value = 'all'; if (dom.icloudList) dom.icloudList.innerHTML = ''; - if (dom.icloudSummary) dom.icloudSummary.textContent = '加载你的 iCloud Hide My Email 别名以便在这里管理。'; + if (dom.icloudSummary) dom.icloudSummary.textContent = 'Load your iCloud Hide My Email aliases to manage them here.'; updateIcloudBulkUI([]); hideIcloudLoginHelp(); } diff --git a/sidepanel/ip-proxy-panel.js b/sidepanel/ip-proxy-panel.js index a664c0fd..40e3fb49 100644 --- a/sidepanel/ip-proxy-panel.js +++ b/sidepanel/ip-proxy-panel.js @@ -1,4 +1,4 @@ -// sidepanel/ip-proxy-panel.js — IP代理面板(轻量解耦) +// sidepanel/ip-proxy-panel.js — IP Proxy Panel (lightweight decoupled) function normalizeIpProxyService(value = '') { const normalized = String(value || '').trim().toLowerCase(); const enabledServices = Array.isArray(globalThis.IP_PROXY_ENABLED_SERVICES) @@ -66,11 +66,11 @@ function normalizeIpProxyActionType(value = '') { function getIpProxyActionLabel(action = '') { const normalized = normalizeIpProxyActionType(action); - if (normalized === 'refresh') return '同步代理'; - if (normalized === 'next') return '切换代理'; - if (normalized === 'change') return '会话换出口'; - if (normalized === 'probe') return '检测出口'; - return '代理操作'; + if (normalized === 'refresh') return 'Sync Proxy'; + if (normalized === 'next') return 'Switch Proxy'; + if (normalized === 'change') return 'Change Session Exit'; + if (normalized === 'probe') return 'Probe Exit'; + return 'Proxy Action'; } function isIpProxyActionBusy() { @@ -93,13 +93,13 @@ function setIpProxyActionBusy(action = '', busy = false) { async function runIpProxyActionWithLock(action = '', runner) { if (typeof runner !== 'function') { - throw new Error('代理操作执行器无效。'); + throw new Error('Invalid proxy action runner.'); } const nextAction = normalizeIpProxyActionType(action); const currentState = getIpProxyActionState(); if (currentState.busy) { if (typeof showToast === 'function') { - showToast(`${getIpProxyActionLabel(currentState.action)}进行中,请稍候。`, 'info', 1600); + showToast(`${getIpProxyActionLabel(currentState.action)} in progress, please wait.`, 'info', 1600); } return { skipped: true }; } @@ -117,7 +117,7 @@ async function runIpProxyActionWithLock(action = '', runner) { Promise.resolve().then(() => runner()), new Promise((_, reject) => { timeoutId = globalThis.setTimeout(() => { - reject(new Error(`${actionLabel}超时(${Math.round(timeoutMs / 1000)} 秒),已自动解锁,请重试。`)); + reject(new Error(`${actionLabel} timed out (${Math.round(timeoutMs / 1000)} seconds), automatically unlocked, please retry.`)); }, timeoutMs); }), ]); @@ -226,7 +226,7 @@ function normalizeIpProxyAccountList(value = '') { if (!line) { return false; } - // 与后台保持一致:支持 # / // / ; 注释行,注释行不参与生效。 + // Same as background: support # / // / ; comment lines, comment lines are not effective. if (/^(?:#|\/\/|;)/.test(line)) { return false; } @@ -578,8 +578,8 @@ function getIpProxyRuntimeSnapshot(state = latestState, mode = normalizeIpProxyM const hasModeCurrent = state?.[fields.currentKey] !== undefined && state?.[fields.currentKey] !== null; const hasModeIndex = state?.[fields.indexKey] !== undefined && state?.[fields.indexKey] !== null; const modePool = Array.isArray(state?.[fields.poolKey]) ? state[fields.poolKey] : []; - // 账号模式在“固定账号”场景下不应读取历史 runtime 缓存,否则会出现 - // “状态显示 A 节点、当前节点显示 B 节点”的错位。 + // Account mode under "fixed account" scenario should not read historical runtime cache, otherwise + // a "status shows node A, current node shows node B" mismatch will occur. const allowAccountRuntimeCache = normalizedMode !== 'account' || hasAccountListConfigured; const pool = hasModePool && allowAccountRuntimeCache ? modePool : []; const current = hasModeCurrent && allowAccountRuntimeCache ? state?.[fields.currentKey] : null; @@ -604,7 +604,7 @@ function buildIpProxyRuntimeStatePatchForMode(mode = DEFAULT_IP_PROXY_MODE, resp const patch = {}; if (response?.pool !== undefined) { patch[fields.poolKey] = Array.isArray(response.pool) ? response.pool : []; - // 兼容旧字段:始终反映当前激活模式的运行态。 + // Compat with old field: always reflect current active mode runtime. patch.ipProxyPool = patch[fields.poolKey]; } if (response?.index !== undefined) { @@ -961,24 +961,24 @@ function buildIpProxyActionHintText(options = {}) { if (mode === 'api') { const nextPart = poolCount > 1 - ? `下一条:当前共 ${dynamicPoolCount} 条节点,切到已拉取代理池的下一条节点。` - : `下一条:当前仅 ${dynamicPoolCount} 条节点,执行重绑复测(不保证更换出口)。`; - return `${nextPart} Change:仅账号模式可用。`; + ? `Next: currently ${dynamicPoolCount} nodes total, switch to next node in fetched proxy pool.` + : `Next: only ${dynamicPoolCount} node currently, re-bind and re-test (does not guarantee changing exit).`; + return `${nextPart} Change: only available in account mode.`; } const nextPart = poolCount > 1 - ? `下一条:当前共 ${dynamicPoolCount} 条节点,切到代理池的下一条节点。` - : `下一条:当前仅 ${dynamicPoolCount} 条节点,执行重绑复测(不保证更换出口)。`; + ? `Next: currently ${dynamicPoolCount} nodes total, switch to next node in proxy pool.` + : `Next: only ${dynamicPoolCount} node currently, re-bind and re-test (does not guarantee changing exit).`; const changePart = changeAvailable - ? 'Change:保持当前 session 重绑链路并复测出口。' - : 'Change:需 711 账号模式且用户名包含 session。'; + ? 'Change: keep current session, re-bind link and re-test exit.' + : 'Change: requires 711 account mode with username containing session.'; return `${nextPart} ${changePart}`; } function setIpProxyCurrentDisplay(text = '', hasValue = false) { if (!ipProxyCurrent) return; - ipProxyCurrent.textContent = text || '暂无可用代理'; - ipProxyCurrent.title = text || '暂无可用代理'; + ipProxyCurrent.textContent = text || 'No available proxy'; + ipProxyCurrent.title = text || 'No available proxy'; ipProxyCurrent.classList.toggle('has-value', Boolean(hasValue)); } @@ -988,7 +988,7 @@ function formatIpProxyCurrentDisplay(state = latestState) { const current = getIpProxyCurrentEntry(state); if (!current) { return { - text: '账号模式:请填写代理列表,或填写 Host / Port', + text: 'Account mode: please fill in proxy list, or fill in Host / Port', hasValue: false, }; } @@ -1004,7 +1004,7 @@ function formatIpProxyCurrentDisplay(state = latestState) { const index = runtime.index; if (!current) { return { - text: count ? `已拉取 ${count} 条,点击“下一条”切换` : '暂无可用代理', + text: count ? `Fetched ${count} entries, click "Next" to switch` : 'No available proxy', hasValue: false, }; } @@ -1046,13 +1046,13 @@ function formatIpProxyRuntimeStatus(state = latestState) { const hasAccountListConfigured = mode === 'account' && normalizeIpProxyAccountList(state?.ipProxyAccountList || '').split('\n').filter(Boolean).length > 0; const accountSourceTag = mode === 'account' - ? (hasAccountListConfigured ? '(账号列表)' : '(固定账号)') + ? (hasAccountListConfigured ? '(account list)' : '(fixed account)') : ''; const accountSourceDetail = mode === 'account' ? ( hasAccountListConfigured - ? '当前生效来源:账号列表(固定账号字段已忽略)。' - : '当前生效来源:固定账号字段。' + ? 'Current effective source: account list (fixed account fields ignored).' + : 'Current effective source: fixed account fields.' ) : ''; const activeEntry = getIpProxyCurrentEntry(state); @@ -1079,7 +1079,7 @@ function formatIpProxyRuntimeStatus(state = latestState) { const endpoint = host && port > 0 ? `${host}:${port}` : ''; const endpointWithRegion = endpoint ? `${endpoint}${region ? ` [${region}]` : ''}` : ''; const hasAuth = Boolean(state?.ipProxyAppliedHasAuth); - const authSuffix = hasAuth ? '(需要鉴权)' : ''; + const authSuffix = hasAuth ? ' (auth required)' : ''; const errorText = String(state?.ipProxyAppliedError || '').trim(); const warningText = String(state?.ipProxyAppliedWarning || '').trim(); const exitIp = String(state?.ipProxyAppliedExitIp || '').trim(); @@ -1088,16 +1088,16 @@ function formatIpProxyRuntimeStatus(state = latestState) { const exitError = String(state?.ipProxyAppliedExitError || '').trim(); const exitSource = String(state?.ipProxyAppliedExitSource || '').trim().toLowerCase(); const exitSourceSuffix = exitSource === 'page_context' - ? '(页面探测)' + ? ' (page probe)' : (exitSource === 'background_fallback' - ? '(后台兜底,可能受全局代理影响)' + ? ' (background fallback, may be affected by global proxy)' : ''); const endpointSummary = endpointWithRegion ? `${endpointWithRegion}${authSuffix}` : (endpoint ? `${endpoint}${authSuffix}` : ''); const exitSummary = exitIp ? `${exitIp}${exitRegion ? ` [${exitRegion}]` : ''}${exitSourceSuffix}` - : (exitDetecting ? '检测中...' : '未检测到'); + : (exitDetecting ? 'Detecting...' : 'Not detected'); const details = [accountSourceDetail, errorText, warningText, exitError] .map((item) => String(item || '').trim()) .filter(Boolean) @@ -1106,7 +1106,7 @@ function formatIpProxyRuntimeStatus(state = latestState) { if (!enabled) { return { stateClass: 'state-idle', - text: '未启用,沿用浏览器默认/全局代理。', + text: 'Not enabled, using browser default/global proxy.', details: '', hideCurrentDisplay: false, }; @@ -1114,11 +1114,11 @@ function formatIpProxyRuntimeStatus(state = latestState) { if (applied) { const statusPrefix = endpointSummary - ? `当前代理:${endpointSummary}${accountSourceTag}` - : '当前代理:已启用'; - const statusText = `${statusPrefix};当前出口:${exitSummary}`; + ? `Current proxy: ${endpointSummary}${accountSourceTag}` + : 'Current proxy: enabled'; + const statusText = `${statusPrefix}; current exit: ${exitSummary}`; const briefWarning = warningText - ? ';地区校验未通过(详情可展开查看)' + ? '; region check failed (expand details to view)' : ''; return { stateClass: warningText ? 'state-warning' : 'state-applied', @@ -1132,7 +1132,7 @@ function formatIpProxyRuntimeStatus(state = latestState) { if (errorText) { return { stateClass: 'state-warning', - text: '已启用,但当前没有可用代理(已阻断直连)。', + text: 'Enabled, but no available proxy currently (direct connection blocked).', details: errorText, hideCurrentDisplay: false, }; @@ -1140,8 +1140,8 @@ function formatIpProxyRuntimeStatus(state = latestState) { return { stateClass: 'state-warning', text: mode === 'account' - ? '已启用,但账号模式没有可用代理。请先填写代理列表,或填写 Host/Port。已阻断所有网站直连。' - : '已启用,但当前没有可用代理。请先点击“拉取”获取 IP 列表。已阻断所有网站直连。', + ? 'Enabled, but account mode has no available proxy. Please fill in proxy list, or fill in Host/Port. All site direct connections blocked.' + : 'Enabled, but no available proxy currently. Please click "Fetch" to get IP list. All site direct connections blocked.', details: '', hideCurrentDisplay: false, }; @@ -1149,7 +1149,7 @@ function formatIpProxyRuntimeStatus(state = latestState) { if (reason === 'proxy_api_unavailable') { return { stateClass: 'state-error', - text: '已启用,但当前浏览器不支持扩展代理 API,无法应用。', + text: 'Enabled, but current browser does not support extension proxy API, cannot apply.', details, hideCurrentDisplay: false, }; @@ -1157,20 +1157,20 @@ function formatIpProxyRuntimeStatus(state = latestState) { if (reason === 'apply_failed') { return { stateClass: 'state-error', - text: '已启用,但代理应用失败(已回退默认代理)。', + text: 'Enabled, but proxy application failed (reverted to default proxy).', details: errorText || details, hideCurrentDisplay: false, }; } if (reason === 'connectivity_failed') { - const prefix = endpointSummary ? `当前代理:${endpointSummary}${accountSourceTag}` : '当前代理:未知'; + const prefix = endpointSummary ? `Current proxy: ${endpointSummary}${accountSourceTag}` : 'Current proxy: unknown'; const targetUnreachable = /真实目标|chatgpt\.com 不可达|target:page_context/i.test(errorText || details); - const exitPart = exitIp ? `;当前出口:${exitSummary}` : ''; + const exitPart = exitIp ? `; current exit: ${exitSummary}` : ''; return { stateClass: 'state-error', text: targetUnreachable && exitIp - ? `${prefix}${exitPart};ChatGPT 目标不可达,请切换支持 ChatGPT 的节点。` - : `${prefix}${exitPart};连通性失败,请切换节点或重试。`, + ? `${prefix}${exitPart}; ChatGPT target unreachable, please switch to a ChatGPT-compatible node.` + : `${prefix}${exitPart}; connectivity failed, please switch nodes or retry.`, details: errorText || details, hideCurrentDisplay: true, }; @@ -1179,8 +1179,8 @@ function formatIpProxyRuntimeStatus(state = latestState) { return { stateClass: 'state-warning', text: endpointWithRegion - ? `已启用,等待生效:${endpointWithRegion}${authSuffix}` - : '已启用,等待拉取并应用代理。', + ? `Enabled, waiting to take effect: ${endpointWithRegion}${authSuffix}` + : 'Enabled, waiting to fetch and apply proxy.', details, hideCurrentDisplay: false, }; @@ -1190,7 +1190,7 @@ function setIpProxyRuntimeStatusDisplay(status = {}) { if (!ipProxyRuntimeStatus || !ipProxyRuntimeText) { return; } - const text = String(status?.text || '未启用,沿用浏览器默认/全局代理。').trim(); + const text = String(status?.text || 'Not enabled, using browser default/global proxy.').trim(); const stateClass = String(status?.stateClass || 'state-idle').trim() || 'state-idle'; ipProxyRuntimeStatus.classList.remove('state-idle', 'state-applied', 'state-warning', 'state-error'); ipProxyRuntimeStatus.classList.add(stateClass); @@ -1228,7 +1228,7 @@ function setIpProxyEnabledInlineStatus(state = {}, enabled = getSelectedIpProxyE ipProxyEnabledStatus.classList.remove('is-off', 'is-on'); if (!enabled) { - const text = '未开启'; + const text = 'Off'; ipProxyEnabledStatus.classList.add('is-off'); ipProxyEnabledStatusText.textContent = text; ipProxyEnabledStatusText.title = text; @@ -1242,7 +1242,7 @@ function setIpProxyEnabledInlineStatus(state = {}, enabled = getSelectedIpProxyE const region = normalizeIpProxyEnabledInlineRegion(state?.ipProxyAppliedExitRegion) || normalizeIpProxyEnabledInlineRegion(state?.ipProxyAppliedRegion) || normalizeIpProxyEnabledInlineRegion(state?.ipProxyRegion); - const text = region ? `已开启 · ${region}` : '已开启'; + const text = region ? `On · ${region}` : 'On'; ipProxyEnabledStatus.classList.add('is-on'); ipProxyEnabledStatusText.textContent = text; ipProxyEnabledStatusText.title = text; @@ -1274,10 +1274,10 @@ function updateIpProxyUI(state = latestState) { } if (btnToggleIpProxySection) { btnToggleIpProxySection.disabled = !enabled; - btnToggleIpProxySection.textContent = showSettings ? '收起设置' : '展开设置'; + btnToggleIpProxySection.textContent = showSettings ? 'Collapse Settings' : 'Expand Settings'; btnToggleIpProxySection.title = enabled - ? (showSettings ? '收起 IP 代理设置' : '展开 IP 代理设置') - : '开启 IP 代理后可展开设置'; + ? (showSettings ? 'Collapse IP proxy settings' : 'Expand IP proxy settings') + : 'Enable IP proxy to expand settings'; btnToggleIpProxySection.setAttribute('aria-expanded', String(showSettings)); } if (rowIpProxyFold) { @@ -1432,35 +1432,35 @@ function updateIpProxyUI(state = latestState) { const hasCurrentEntry = Boolean(getIpProxyCurrentEntry(runtimeState)); const changeAvailable = canChangeIpProxyExitWithCurrentSession(runtimeState); const nextActionTitle = runtimePoolCount > 1 - ? `切换到代理池下一条节点并应用(当前共 ${runtimePoolCountForDisplay} 条)` - : `当前仅 ${runtimePoolCountForDisplay} 条节点:重绑当前节点并复测连通性(不保证更换出口)`; + ? `Switch to next node in proxy pool and apply (currently ${runtimePoolCountForDisplay} nodes total)` + : `Only ${runtimePoolCountForDisplay} node currently: re-bind current node and re-test connectivity (does not guarantee changing exit)`; if (btnIpProxyRefresh) { btnIpProxyRefresh.disabled = actionBusy || !enabled || !canOperate; btnIpProxyRefresh.textContent = busyAction === 'refresh' - ? (isApiMode ? '拉取中...' : '同步中...') - : (isApiMode ? '拉取' : '同步'); - btnIpProxyRefresh.title = isApiMode ? '拉取代理池并应用当前代理' : '同步账号代理列表并应用当前代理'; + ? (isApiMode ? 'Fetching...' : 'Syncing...') + : (isApiMode ? 'Fetch' : 'Sync'); + btnIpProxyRefresh.title = isApiMode ? 'Fetch proxy pool and apply current proxy' : 'Sync account proxy list and apply current proxy'; } if (btnIpProxyNext) { btnIpProxyNext.disabled = actionBusy || !enabled || !canOperate || !hasCurrentEntry; - btnIpProxyNext.textContent = busyAction === 'next' ? '切换中...' : '下一条'; + btnIpProxyNext.textContent = busyAction === 'next' ? 'Switching...' : 'Next'; btnIpProxyNext.title = nextActionTitle; } if (btnIpProxyChange) { btnIpProxyChange.disabled = actionBusy || !enabled || !canOperate || !changeAvailable; - btnIpProxyChange.textContent = busyAction === 'change' ? 'Change中...' : 'Change'; + btnIpProxyChange.textContent = busyAction === 'change' ? 'Changing...' : 'Change'; btnIpProxyChange.title = changeAvailable - ? '保持当前会话并刷新出口(仅 711 + session)' - : '当前模式不支持 Change(需 711 账号模式且用户名包含 session)'; + ? 'Keep current session and refresh exit (711 + session only)' + : 'Current mode does not support Change (requires 711 account mode with username containing session)'; } if (btnIpProxyProbe) { btnIpProxyProbe.disabled = actionBusy || !enabled || !canOperate; - btnIpProxyProbe.textContent = busyAction === 'probe' ? '检测中...' : '检测出口'; + btnIpProxyProbe.textContent = busyAction === 'probe' ? 'Probing...' : 'Probe Exit'; } if (btnIpProxyCheckIp) { btnIpProxyCheckIp.disabled = false; - btnIpProxyCheckIp.title = '打开公网 IP 检测页'; + btnIpProxyCheckIp.title = 'Open public IP detection page'; } if (ipProxyActionHint) { const actionHint = buildIpProxyActionHintText({ @@ -1516,9 +1516,9 @@ async function refreshIpProxyPoolByApi(options = {}) { if (!silent) { if (mode === 'account') { - showToast(`已同步账号代理:${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800); + showToast(`Synced account proxy: ${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800); } else { - showToast(`已拉取代理池:${Number(response?.count) || 0} 条`, 'success', 1800); + showToast(`Fetched proxy pool: ${Number(response?.count) || 0} entries`, 'success', 1800); } } return response; @@ -1566,7 +1566,7 @@ async function switchIpProxyToNext(options = {}) { updateIpProxyUI(latestState); scheduleIpProxyExitProbe({ silent: true }); if (!silent) { - showToast(`已切换代理:${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800); + showToast(`Switched proxy: ${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800); } return response; } @@ -1611,7 +1611,7 @@ async function changeIpProxyExitBySession(options = {}) { updateIpProxyUI(latestState); scheduleIpProxyExitProbe({ silent: true }); if (!silent) { - showToast(`已执行 Change:${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800); + showToast(`Executed Change: ${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800); } return response; } @@ -1654,12 +1654,12 @@ async function probeIpProxyExit(options = {}) { const exitRegion = String(routing?.exitRegion || '').trim(); const exitSource = String(routing?.exitSource || '').trim().toLowerCase(); const sourceHint = exitSource === 'background_fallback' - ? '(后台兜底,可能受全局代理影响)' + ? ' (background fallback, may be affected by global proxy)' : ''; if (exitIp) { - showToast(`出口检测成功:${exitIp}${exitRegion ? ` [${exitRegion}]` : ''}${sourceHint}`, 'success', 2600); + showToast(`Exit probe successful: ${exitIp}${exitRegion ? ` [${exitRegion}]` : ''}${sourceHint}`, 'success', 2600); } else { - showToast('出口检测完成,但未获取到出口 IP', 'warn', 2200); + showToast('Exit probe completed but did not get exit IP', 'warn', 2200); } } return response; diff --git a/sidepanel/ip-proxy-provider-711proxy.js b/sidepanel/ip-proxy-provider-711proxy.js index 63c6e166..69fe7947 100644 --- a/sidepanel/ip-proxy-provider-711proxy.js +++ b/sidepanel/ip-proxy-provider-711proxy.js @@ -1,4 +1,4 @@ -// sidepanel/ip-proxy-provider-711proxy.js — 711Proxy 面板专属逻辑 +// sidepanel/ip-proxy-provider-711proxy.js — 711Proxy panel-specific logic function normalizeIpProxyCountryCode(value = '') { const raw = String(value || '').trim().toUpperCase().replace(/[^A-Z]/g, ''); return /^[A-Z]{2}$/.test(raw) ? raw : ''; diff --git a/sidepanel/luckmail-manager.js b/sidepanel/luckmail-manager.js index 29520a93..0fadd500 100644 --- a/sidepanel/luckmail-manager.js +++ b/sidepanel/luckmail-manager.js @@ -40,10 +40,10 @@ purchase.email_address, purchase.project_name, purchase.tag_name, - purchase.used ? '已用 used' : '未用 unused', - purchase.preserved ? '保留 preserved' : '', - purchase.disabled ? '已禁用 disabled' : '', - purchase.reusable ? '可复用 reusable' : '', + purchase.used ? 'used' : 'unused', + purchase.preserved ? 'preserved' : '', + purchase.disabled ? 'disabled' : '', + purchase.reusable ? 'reusable' : '', ].join(' ').toLowerCase(); return haystack.includes(normalizedSearchTerm); @@ -68,7 +68,7 @@ dom.checkboxLuckmailSelectAll.checked = hasVisible && selectedVisibleCount === visibleIds.length; dom.checkboxLuckmailSelectAll.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleIds.length; dom.checkboxLuckmailSelectAll.disabled = !hasVisible; - dom.luckmailSelectionSummary.textContent = `已选 ${selectedPurchaseIds.size} 个(当前显示 ${visibleIds.length} 个)`; + dom.luckmailSelectionSummary.textContent = `Selected ${selectedPurchaseIds.size} (currently showing ${visibleIds.length})`; if (dom.btnLuckmailBulkUsed) dom.btnLuckmailBulkUsed.disabled = !hasSelection; if (dom.btnLuckmailBulkUnused) dom.btnLuckmailBulkUnused.disabled = !hasSelection; @@ -104,8 +104,8 @@ if (!renderedPurchases.length) { selectedPurchaseIds.clear(); - dom.luckmailList.innerHTML = '
未找到 openai 项目的 LuckMail 邮箱。
'; - dom.luckmailSummary.textContent = '加载已购邮箱后可在这里管理 openai 项目的 LuckMail 邮箱。'; + dom.luckmailList.innerHTML = '
No LuckMail emails found for openai project.
'; + dom.luckmailSummary.textContent = 'Load purchased emails to manage openai project LuckMail emails here.'; if (dom.btnLuckmailDisableUsed) dom.btnLuckmailDisableUsed.disabled = true; updateLuckmailBulkUI([]); return; @@ -114,15 +114,15 @@ const usedCount = renderedPurchases.filter((purchase) => purchase.used).length; const reusableCount = renderedPurchases.filter((purchase) => purchase.reusable).length; const disableUsedCount = renderedPurchases.filter((purchase) => purchase.used && !purchase.preserved && !purchase.disabled).length; - dom.luckmailSummary.textContent = `已加载 ${renderedPurchases.length} 个 openai 邮箱,其中 ${reusableCount} 个可复用,${usedCount} 个已本地标记为已用。`; + dom.luckmailSummary.textContent = `Loaded ${renderedPurchases.length} openai emails, of which ${reusableCount} are reusable and ${usedCount} are locally marked as used.`; if (dom.btnLuckmailDisableUsed) { - dom.btnLuckmailDisableUsed.textContent = `禁用已用${disableUsedCount > 0 ? `(${disableUsedCount})` : ''}`; + dom.btnLuckmailDisableUsed.textContent = `Disable Used${disableUsedCount > 0 ? `(${disableUsedCount})` : ''}`; dom.btnLuckmailDisableUsed.disabled = disableUsedCount === 0; } const visiblePurchases = getFilteredLuckmailPurchases(renderedPurchases); if (!visiblePurchases.length) { - dom.luckmailList.innerHTML = '
没有匹配当前筛选条件的 LuckMail 邮箱。
'; + dom.luckmailList.innerHTML = '
No LuckMail emails match the current filter.
'; updateLuckmailBulkUI([]); return; } @@ -135,36 +135,36 @@
${helpers.escapeHtml(helpers.normalizeLuckmailProjectName(purchase.project_name) || 'openai')} - ${purchase.reusable ? '可复用' : ''} - ${purchase.current ? '当前' : ''} - ${purchase.used ? '已用' : ''} - ${purchase.preserved ? '保留' : ''} - ${purchase.disabled ? '已禁用' : ''} + ${purchase.reusable ? 'Reusable' : ''} + ${purchase.current ? 'Current' : ''} + ${purchase.used ? 'Used' : ''} + ${purchase.preserved ? 'Preserved' : ''} + ${purchase.disabled ? 'Disabled' : ''} ${purchase.tag_name && normalizeLuckmailSearchText(purchase.tag_name) !== normalizeLuckmailSearchText(helpers.getLuckmailPreserveTagName()) ? `${helpers.escapeHtml(purchase.tag_name)}` : ''}
- ID:${helpers.escapeHtml(String(purchase.id || ''))} - 保修至:${helpers.escapeHtml(helpers.formatLuckmailDateTime(purchase.warranty_until))} + ID: ${helpers.escapeHtml(String(purchase.id || ''))} + Warranty until: ${helpers.escapeHtml(helpers.formatLuckmailDateTime(purchase.warranty_until))}
- - - - + + + +
`; @@ -178,7 +178,7 @@ }); item.querySelector('[data-action="copy-email"]').addEventListener('click', async () => { await helpers.copyTextToClipboard(purchase.email_address || ''); - helpers.showToast('邮箱已复制', 'success', 1600); + helpers.showToast('Email copied', 'success', 1600); }); item.querySelector('[data-action="use"]').addEventListener('click', async () => { await selectSingleLuckmailPurchase(purchase); @@ -204,7 +204,7 @@ return; } - if (!silent) setLuckmailLoadingState(true, '正在加载 LuckMail openai 邮箱...'); + if (!silent) setLuckmailLoadingState(true, 'Loading LuckMail openai emails...'); try { const response = await runtime.sendMessage({ type: 'LIST_LUCKMAIL_PURCHASES', @@ -216,14 +216,14 @@ } catch (err) { selectedPurchaseIds.clear(); if (dom.luckmailList) { - dom.luckmailList.innerHTML = '
无法加载 LuckMail 邮箱列表。
'; + dom.luckmailList.innerHTML = '
Failed to load LuckMail email list.
'; } if (dom.luckmailSummary) { dom.luckmailSummary.textContent = err.message; } updateLuckmailBulkUI([]); if (!silent) { - helpers.showToast(`LuckMail 邮箱列表加载失败:${err.message}`, 'error'); + helpers.showToast(`Failed to load LuckMail email list: ${err.message}`, 'error'); } } finally { setLuckmailLoadingState(false); @@ -240,7 +240,7 @@ } async function selectSingleLuckmailPurchase(purchase) { - setLuckmailLoadingState(true, `正在切换到 ${purchase.email_address} ...`); + setLuckmailLoadingState(true, `Switching to ${purchase.email_address} ...`); try { const response = await runtime.sendMessage({ type: 'SELECT_LUCKMAIL_PURCHASE', @@ -249,18 +249,18 @@ }); if (response?.error) throw new Error(response.error); dom.inputEmail.value = response?.purchase?.email_address || purchase.email_address || ''; - helpers.showToast(`已切换当前 LuckMail 邮箱为 ${purchase.email_address}`, 'success', 2200); + helpers.showToast(`Switched current LuckMail email to ${purchase.email_address}`, 'success', 2200); await refreshLuckmailPurchases({ silent: true }); } catch (err) { if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message; - helpers.showToast(`切换 LuckMail 邮箱失败:${err.message}`, 'error'); + helpers.showToast(`Failed to switch LuckMail email: ${err.message}`, 'error'); } finally { setLuckmailLoadingState(false); } } async function setSingleLuckmailPurchaseUsedState(purchase, used) { - setLuckmailLoadingState(true, `正在更新 ${purchase.email_address} 的已用状态...`); + setLuckmailLoadingState(true, `Updating used state of ${purchase.email_address}...`); try { const response = await runtime.sendMessage({ type: 'SET_LUCKMAIL_PURCHASE_USED_STATE', @@ -268,18 +268,18 @@ payload: { purchaseId: purchase.id, used }, }); if (response?.error) throw new Error(response.error); - helpers.showToast(`${purchase.email_address} 已${used ? '标记为已用' : '恢复为未用'}`, 'success', 2200); + helpers.showToast(`${purchase.email_address} ${used ? 'marked as used' : 'restored as unused'}`, 'success', 2200); await refreshLuckmailPurchases({ silent: true }); } catch (err) { if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message; - helpers.showToast(`更新 LuckMail 已用状态失败:${err.message}`, 'error'); + helpers.showToast(`Failed to update LuckMail used state: ${err.message}`, 'error'); } finally { setLuckmailLoadingState(false); } } async function setSingleLuckmailPurchasePreservedState(purchase, preserved) { - setLuckmailLoadingState(true, `正在更新 ${purchase.email_address} 的保留状态...`); + setLuckmailLoadingState(true, `Updating preserved state of ${purchase.email_address}...`); try { const response = await runtime.sendMessage({ type: 'SET_LUCKMAIL_PURCHASE_PRESERVED_STATE', @@ -287,18 +287,18 @@ payload: { purchaseId: purchase.id, preserved }, }); if (response?.error) throw new Error(response.error); - helpers.showToast(`${purchase.email_address} 已${preserved ? '设为保留' : '取消保留'}`, 'success', 2200); + helpers.showToast(`${purchase.email_address} ${preserved ? 'set as preserved' : 'unpreserved'}`, 'success', 2200); await refreshLuckmailPurchases({ silent: true }); } catch (err) { if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message; - helpers.showToast(`更新 LuckMail 保留状态失败:${err.message}`, 'error'); + helpers.showToast(`Failed to update LuckMail preserved state: ${err.message}`, 'error'); } finally { setLuckmailLoadingState(false); } } async function setSingleLuckmailPurchaseDisabledState(purchase, disabled) { - setLuckmailLoadingState(true, `正在${disabled ? '禁用' : '启用'} ${purchase.email_address} ...`); + setLuckmailLoadingState(true, `${disabled ? 'Disabling' : 'Enabling'} ${purchase.email_address} ...`); try { const response = await runtime.sendMessage({ type: 'SET_LUCKMAIL_PURCHASE_DISABLED_STATE', @@ -306,11 +306,11 @@ payload: { purchaseId: purchase.id, disabled }, }); if (response?.error) throw new Error(response.error); - helpers.showToast(`${purchase.email_address} 已${disabled ? '禁用' : '启用'}`, 'success', 2200); + helpers.showToast(`${purchase.email_address} ${disabled ? 'disabled' : 'enabled'}`, 'success', 2200); await refreshLuckmailPurchases({ silent: true }); } catch (err) { if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message; - helpers.showToast(`更新 LuckMail 禁用状态失败:${err.message}`, 'error'); + helpers.showToast(`Failed to update LuckMail disabled state: ${err.message}`, 'error'); } finally { setLuckmailLoadingState(false); } @@ -326,15 +326,15 @@ } const actionLabelMap = { - used: '标记已用', - unused: '标记未用', - preserve: '保留', - unpreserve: '取消保留', - disable: '禁用', - enable: '启用', + used: 'Mark Used', + unused: 'Mark Unused', + preserve: 'Preserve', + unpreserve: 'Unpreserve', + disable: 'Disable', + enable: 'Enable', }; - setLuckmailLoadingState(true, `正在批量${actionLabelMap[action] || '处理'} LuckMail 邮箱...`); + setLuckmailLoadingState(true, `Bulk ${actionLabelMap[action] || 'processing'} LuckMail emails...`); try { const response = await runtime.sendMessage({ type: 'BATCH_UPDATE_LUCKMAIL_PURCHASES', @@ -342,11 +342,11 @@ payload: { action, ids: selectedIds }, }); if (response?.error) throw new Error(response.error); - helpers.showToast(`已批量${actionLabelMap[action] || '处理'} ${selectedIds.length} 个 LuckMail 邮箱`, 'success', 2400); + helpers.showToast(`Bulk ${actionLabelMap[action] || 'processed'} ${selectedIds.length} LuckMail emails`, 'success', 2400); await refreshLuckmailPurchases({ silent: true }); } catch (err) { if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message; - helpers.showToast(`批量处理 LuckMail 邮箱失败:${err.message}`, 'error'); + helpers.showToast(`Bulk processing LuckMail emails failed: ${err.message}`, 'error'); } finally { setLuckmailLoadingState(false); updateLuckmailBulkUI(); @@ -355,16 +355,16 @@ async function disableUsedLuckmailPurchases() { const confirmed = await helpers.openConfirmModal({ - title: '禁用已用 LuckMail 邮箱', - message: '确认禁用所有本地已用且未保留的 openai LuckMail 邮箱吗?', - confirmLabel: '确认禁用', + title: 'Disable Used LuckMail Emails', + message: 'Are you sure you want to disable all locally used and unpreserved openai LuckMail emails?', + confirmLabel: 'Confirm Disable', confirmVariant: 'btn-danger', }); if (!confirmed) { return; } - setLuckmailLoadingState(true, '正在禁用已用 LuckMail 邮箱...'); + setLuckmailLoadingState(true, 'Disabling used LuckMail emails...'); try { const response = await runtime.sendMessage({ type: 'DISABLE_USED_LUCKMAIL_PURCHASES', @@ -373,11 +373,11 @@ }); if (response?.error) throw new Error(response.error); const disabledCount = Array.isArray(response?.disabledIds) ? response.disabledIds.length : 0; - helpers.showToast(`已禁用 ${disabledCount} 个 LuckMail 邮箱`, disabledCount > 0 ? 'success' : 'info', 2400); + helpers.showToast(`Disabled ${disabledCount} LuckMail emails`, disabledCount > 0 ? 'success' : 'info', 2400); await refreshLuckmailPurchases({ silent: true }); } catch (err) { if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message; - helpers.showToast(`禁用已用 LuckMail 邮箱失败:${err.message}`, 'error'); + helpers.showToast(`Failed to disable used LuckMail emails: ${err.message}`, 'error'); } finally { setLuckmailLoadingState(false); } @@ -392,7 +392,7 @@ if (dom.inputLuckmailSearch) dom.inputLuckmailSearch.value = ''; if (dom.selectLuckmailFilter) dom.selectLuckmailFilter.value = 'all'; if (dom.luckmailList) dom.luckmailList.innerHTML = ''; - if (dom.luckmailSummary) dom.luckmailSummary.textContent = '加载已购邮箱后可在这里管理 openai 项目的 LuckMail 邮箱。'; + if (dom.luckmailSummary) dom.luckmailSummary.textContent = 'Load purchased emails to manage openai project LuckMail emails here.'; if (dom.btnLuckmailDisableUsed) dom.btnLuckmailDisableUsed.disabled = true; updateLuckmailBulkUI([]); } diff --git a/sidepanel/mail-2925-manager.js b/sidepanel/mail-2925-manager.js index 26a2afb3..8ec7ae9b 100644 --- a/sidepanel/mail-2925-manager.js +++ b/sidepanel/mail-2925-manager.js @@ -31,13 +31,13 @@ function updateMail2925ListViewport() { const count = getMail2925Accounts().length; if (dom.btnDeleteAllMail2925Accounts) { - dom.btnDeleteAllMail2925Accounts.textContent = `全部删除${count > 0 ? `(${count})` : ''}`; + dom.btnDeleteAllMail2925Accounts.textContent = `Delete All${count > 0 ? `(${count})` : ''}`; dom.btnDeleteAllMail2925Accounts.disabled = count === 0; } if (dom.btnToggleMail2925List) { const label = typeof mail2925Utils.getMail2925ListToggleLabel === 'function' ? mail2925Utils.getMail2925ListToggleLabel(listExpanded, count) - : `${listExpanded ? '收起列表' : '展开列表'}${count > 0 ? `(${count})` : ''}`; + : `${listExpanded ? 'Collapse List' : 'Expand List'}${count > 0 ? `(${count})` : ''}`; dom.btnToggleMail2925List.textContent = label; dom.btnToggleMail2925List.setAttribute('aria-expanded', String(listExpanded)); dom.btnToggleMail2925List.disabled = count === 0; @@ -65,7 +65,7 @@ function formatDateTime(timestamp) { const value = Number(timestamp); if (!Number.isFinite(value) || value <= 0) { - return '未记录'; + return 'Not recorded'; } return new Date(value).toLocaleString('zh-CN', { hour12: false, @@ -79,15 +79,15 @@ : 'ready'; switch (status) { case 'cooldown': - return { label: '冷却中', className: 'status-used' }; + return { label: 'Cooling down', className: 'status-used' }; case 'disabled': - return { label: '已禁用', className: 'status-disabled' }; + return { label: 'Disabled', className: 'status-disabled' }; case 'error': - return { label: '异常', className: 'status-error' }; + return { label: 'Error', className: 'status-error' }; case 'pending': - return { label: '待完善', className: 'status-pending' }; + return { label: 'Pending', className: 'status-pending' }; default: - return { label: '可用', className: 'status-authorized' }; + return { label: 'Available', className: 'status-authorized' }; } } @@ -125,7 +125,7 @@ account.email, statusKey, status.label, - isCurrent ? 'current 当前' : '', + isCurrent ? 'current' : '', ].join(' ').toLowerCase(); return haystack.includes(normalizedSearchTerm); @@ -166,8 +166,8 @@ ? createAccountPoolFormController({ formShell: dom.mail2925FormShell, toggleButton: dom.btnToggleMail2925Form, - hiddenLabel: '添加账号', - visibleLabel: '取消添加', + hiddenLabel: 'Add Account', + visibleLabel: 'Cancel Add', onClear: () => { stopEditingAccount({ clearForm: true }); }, @@ -183,7 +183,7 @@ function syncEditUi() { if (dom.btnAddMail2925Account) { - dom.btnAddMail2925Account.textContent = editingAccountId ? '保存修改' : '添加账号'; + dom.btnAddMail2925Account.textContent = editingAccountId ? 'Save Changes' : 'Add Account'; } } @@ -213,52 +213,52 @@ const currentId = getCurrentMail2925AccountId(latestState); if (!accounts.length) { - dom.mail2925AccountsList.innerHTML = '
还没有 2925 账号,先添加一条再使用。
'; + dom.mail2925AccountsList.innerHTML = '
No 2925 accounts yet. Add one before using.
'; updateMail2925ListViewport(); return; } const visibleAccounts = getFilteredMail2925Accounts(accounts, currentId); if (!visibleAccounts.length) { - dom.mail2925AccountsList.innerHTML = '
没有匹配当前筛选条件的 2925 账号。
'; + dom.mail2925AccountsList.innerHTML = '
No 2925 accounts match the current filter.
'; updateMail2925ListViewport(); return; } dom.mail2925AccountsList.innerHTML = visibleAccounts.map((account) => { const status = getStatusSnapshot(account); - const coolingDown = status.label === '冷却中'; + const coolingDown = status.label === 'Cooling down'; return ` `; @@ -273,11 +273,11 @@ const email = String(dom.inputMail2925Email?.value || '').trim(); const password = String(dom.inputMail2925Password?.value || ''); if (!email) { - helpers.showToast('请先填写 2925 邮箱。', 'warn'); + helpers.showToast('Please fill in the 2925 email first.', 'warn'); return; } if (!password) { - helpers.showToast('请先填写 2925 密码。', 'warn'); + helpers.showToast('Please fill in the 2925 password first.', 'warn'); return; } @@ -305,13 +305,13 @@ formController.setVisible(false, { clearForm: true }); helpers.showToast( updatingExisting - ? `已更新 2925 账号 ${email}` - : `已保存 2925 账号 ${email}`, + ? `Updated 2925 account ${email}` + : `Saved 2925 account ${email}`, 'success', 1800 ); } catch (err) { - helpers.showToast(`保存 2925 账号失败:${err.message}`, 'error'); + helpers.showToast(`Failed to save 2925 account: ${err.message}`, 'error'); } finally { actionInFlight = false; if (dom.btnAddMail2925Account) { @@ -323,19 +323,19 @@ async function handleImportMail2925Accounts() { if (actionInFlight) return; if (typeof mail2925Utils.parseMail2925ImportText !== 'function') { - helpers.showToast('2925 导入解析器未加载,请刷新扩展后重试。', 'error'); + helpers.showToast('2925 import parser not loaded, please refresh the extension and retry.', 'error'); return; } const rawText = String(dom.inputMail2925Import?.value || '').trim(); if (!rawText) { - helpers.showToast('请先粘贴 2925 账号导入内容。', 'warn'); + helpers.showToast('Please paste the 2925 account import content first.', 'warn'); return; } const parsedAccounts = mail2925Utils.parseMail2925ImportText(rawText); if (!parsedAccounts.length) { - helpers.showToast('没有解析到有效账号,请检查格式是否为 邮箱----密码。', 'error'); + helpers.showToast('No valid accounts parsed. Please check format: email----password.', 'error'); return; } @@ -359,9 +359,9 @@ if (dom.inputMail2925Import) { dom.inputMail2925Import.value = ''; } - helpers.showToast(`已导入 ${parsedAccounts.length} 条 2925 账号`, 'success', 2200); + helpers.showToast(`Imported ${parsedAccounts.length} 2925 accounts`, 'success', 2200); } catch (err) { - helpers.showToast(`批量导入 2925 账号失败:${err.message}`, 'error'); + helpers.showToast(`Bulk import of 2925 accounts failed: ${err.message}`, 'error'); } finally { actionInFlight = false; if (dom.btnImportMail2925Accounts) { @@ -373,14 +373,14 @@ async function deleteAllMail2925Accounts() { const accounts = getMail2925Accounts(); if (!accounts.length) { - helpers.showToast('没有可删除的 2925 账号。', 'warn'); + helpers.showToast('No 2925 accounts to delete.', 'warn'); return; } const confirmed = await helpers.openConfirmModal({ - title: '全部删除 2925 账号', - message: `确认删除当前全部 ${accounts.length} 个 2925 账号吗?`, - confirmLabel: '确认全部删除', + title: 'Delete All 2925 Accounts', + message: `Are you sure you want to delete all ${accounts.length} current 2925 accounts?`, + confirmLabel: 'Confirm Delete All', confirmVariant: 'btn-danger', }); if (!confirmed) { @@ -403,7 +403,7 @@ formController.setVisible(false, { clearForm: true }); refreshManagedAliasBaseEmail(); renderMail2925Accounts(); - helpers.showToast(`已删除全部 ${response.deletedCount || 0} 个 2925 账号`, 'success', 2200); + helpers.showToast(`Deleted all ${response.deletedCount || 0} 2925 accounts`, 'success', 2200); } async function handleAccountListClick(event) { @@ -424,9 +424,9 @@ try { if (action === 'copy-email') { - if (!targetAccount?.email) throw new Error('未找到可复制的 2925 邮箱。'); + if (!targetAccount?.email) throw new Error('No 2925 email found to copy.'); await helpers.copyTextToClipboard(targetAccount.email); - helpers.showToast(`已复制 ${targetAccount.email}`, 'success', 1800); + helpers.showToast(`Copied ${targetAccount.email}`, 'success', 1800); return; } @@ -440,7 +440,7 @@ state.syncLatestState({ currentMail2925AccountId: response.account.id }); refreshManagedAliasBaseEmail(); renderMail2925Accounts(); - helpers.showToast(`已切换当前 2925 账号为 ${response.account.email}`, 'success', 2000); + helpers.showToast(`Switched current 2925 account to ${response.account.email}`, 'success', 2000); return; } @@ -457,19 +457,19 @@ state.syncLatestState({ currentMail2925AccountId: response.account.id }); refreshManagedAliasBaseEmail(); renderMail2925Accounts(); - helpers.showToast(`已使用 ${response.account.email} 登录 2925 邮箱`, 'success', 2200); + helpers.showToast(`Logged in to 2925 with ${response.account.email}`, 'success', 2200); return; } if (action === 'edit') { - if (!targetAccount) throw new Error('未找到目标 2925 账号。'); + if (!targetAccount) throw new Error('Target 2925 account not found.'); startEditingAccount(targetAccount); - helpers.showToast(`已载入 ${targetAccount.email},修改后点“保存修改”即可`, 'info', 1800); + helpers.showToast(`Loaded ${targetAccount.email}, click "Save Changes" after editing`, 'info', 1800); return; } if (action === 'toggle-enabled') { - if (!targetAccount) throw new Error('未找到目标 2925 账号。'); + if (!targetAccount) throw new Error('Target 2925 account not found.'); const response = await runtime.sendMessage({ type: 'PATCH_MAIL2925_ACCOUNT', source: 'sidepanel', @@ -482,7 +482,7 @@ }); if (response?.error) throw new Error(response.error); applyMail2925AccountMutation(response.account); - helpers.showToast(`2925 账号 ${response.account.email} 已${response.account.enabled === false ? '禁用' : '启用'}`, 'success', 2200); + helpers.showToast(`2925 account ${response.account.email} ${response.account.enabled === false ? 'disabled' : 'enabled'}`, 'success', 2200); return; } @@ -500,15 +500,15 @@ }); if (response?.error) throw new Error(response.error); applyMail2925AccountMutation(response.account); - helpers.showToast(`2925 账号 ${response.account.email} 已清除冷却`, 'success', 2200); + helpers.showToast(`2925 account ${response.account.email} cooldown cleared`, 'success', 2200); return; } if (action === 'delete') { const confirmed = await helpers.openConfirmModal({ - title: '删除 2925 账号', - message: '确认删除这个 2925 账号吗?', - confirmLabel: '确认删除', + title: 'Delete 2925 Account', + message: 'Are you sure you want to delete this 2925 account?', + confirmLabel: 'Confirm Delete', confirmVariant: 'btn-danger', }); if (!confirmed) { @@ -532,7 +532,7 @@ } refreshManagedAliasBaseEmail(); renderMail2925Accounts(); - helpers.showToast('2925 账号已删除', 'success', 1800); + helpers.showToast('2925 account deleted', 'success', 1800); } } catch (err) { helpers.showToast(err.message, 'error'); diff --git a/sidepanel/paypal-manager.js b/sidepanel/paypal-manager.js index 66571031..b3ae4833 100644 --- a/sidepanel/paypal-manager.js +++ b/sidepanel/paypal-manager.js @@ -21,10 +21,10 @@ function buildSelectOptions(accounts = []) { if (!accounts.length) { - return ''; + return ''; } return accounts.map((account) => ( - `` + `` )).join(''); } @@ -33,7 +33,7 @@ } function getPayPalAccountLabel(account = {}) { - return String(account?.email || '(未命名账号)'); + return String(account?.email || '(unnamed account)'); } function normalizePickerPayPalAccounts(accounts = []) { @@ -65,8 +65,8 @@ trigger: dom.btnPayPalAccountMenu, current: dom.payPalAccountCurrent, menu: dom.payPalAccountMenu, - emptyLabel: '请先添加 PayPal 账号', - itemLabel: '账号', + emptyLabel: 'Please add a PayPal account first', + itemLabel: 'Account', normalizeItems: normalizePickerPayPalAccounts, normalizeValue: (value) => String(value || '').trim(), getItemValue: getPayPalAccountValue, @@ -138,7 +138,7 @@ }); renderPayPalAccounts(); if (!silent) { - helpers.showToast(`已切换当前 PayPal 账号为 ${response.account?.email || accountId}`, 'success', 1800); + helpers.showToast(`Switched current PayPal account to ${response.account?.email || accountId}`, 'success', 1800); } return response.account || null; } @@ -190,7 +190,7 @@ currentPayPalAccountId: payload.currentPayPalAccountId || null, }); renderPayPalAccounts(); - helpers.showToast(`已删除 PayPal 账号:${targetAccount.email || targetId}`, 'success', 1600); + helpers.showToast(`Deleted PayPal account: ${targetAccount.email || targetId}`, 'success', 1600); } finally { actionInFlight = false; if (dom.btnAddPayPalAccount) { @@ -201,40 +201,40 @@ async function openPayPalAccountDialog() { if (typeof helpers.openFormDialog !== 'function') { - throw new Error('表单弹窗能力未加载,请刷新扩展后重试。'); + throw new Error('Form dialog capability not loaded, please refresh the extension and retry.'); } return helpers.openFormDialog({ - title: '添加 PayPal 账号', - confirmLabel: '保存账号', + title: 'Add PayPal Account', + confirmLabel: 'Save Account', confirmVariant: 'btn-primary', fields: [ { key: 'email', - label: 'PayPal 账号', + label: 'PayPal Account', type: 'text', masked: true, - showPasswordLabel: '显示 PayPal 账号', - hidePasswordLabel: '隐藏 PayPal 账号', - placeholder: '请输入 PayPal 登录邮箱', + showPasswordLabel: 'Show PayPal account', + hidePasswordLabel: 'Hide PayPal account', + placeholder: 'Enter PayPal login email', autocomplete: 'username', required: true, - requiredMessage: '请先填写 PayPal 账号。', + requiredMessage: 'Please fill in PayPal account first.', validate: (value) => { const normalized = String(value || '').trim(); if (!normalized.includes('@')) { - return 'PayPal 账号需填写邮箱格式。'; + return 'PayPal account must be in email format.'; } return ''; }, }, { key: 'password', - label: 'PayPal 密码', + label: 'PayPal Password', type: 'password', - placeholder: '请输入 PayPal 登录密码', + placeholder: 'Enter PayPal login password', autocomplete: 'current-password', required: true, - requiredMessage: '请先填写 PayPal 密码。', + requiredMessage: 'Please fill in PayPal password first.', }, ], }); @@ -273,9 +273,9 @@ dom.selectPayPalAccount.value = response.account.id; await syncSelectedPayPalAccount({ silent: true }); } - helpers.showToast(`已保存 PayPal 账号 ${response.account?.email || ''}`, 'success', 2200); + helpers.showToast(`Saved PayPal account ${response.account?.email || ''}`, 'success', 2200); } catch (error) { - helpers.showToast(`保存 PayPal 账号失败:${error.message}`, 'error'); + helpers.showToast(`Failed to save PayPal account: ${error.message}`, 'error'); throw error; } finally { actionInFlight = false; diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 401a97f2..60458fc5 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -1,5 +1,5 @@ - + @@ -16,7 +16,7 @@
- - + aria-label="Open GitHub Releases page" title="Open GitHub Releases page">FlowPilot0.0 +
@@ -34,24 +34,24 @@
- - + + - +
- -
+ aria-expanded="false">Config
@@ -105,16 +105,16 @@
- +

- - + +
-

一定请先导出配置,再执行更新

+

Be sure to export your config before updating

@@ -122,18 +122,18 @@
- 注册 -
- 来源 + Source
@@ -142,35 +142,35 @@
@@ -178,24 +178,24 @@
- +
- 管理密钥 + Admin Key
- + placeholder="Enter CPA admin key" /> +
- 回调方式 -
- - + Callback Mode +
+ +
@@ -269,152 +269,152 @@
+ placeholder="Enter kiro.rs API Key" /> + data-password-toggle="input-kiro-rs-key" data-show-label="Show kiro.rs API Key" + data-hide-label="Hide kiro.rs API Key" aria-label="Show kiro.rs API Key" + title="Show kiro.rs API Key">
- +
- 账户密码 + Account Password
- + placeholder="Account password, auto-generated if empty" /> +
- Plus 模式 + Plus Mode
-
- Plus 订阅链路 + Plus subscription flow
@@ -486,142 +486,142 @@ GPC PIN
+ placeholder="Enter GoPay PIN" autocomplete="off" /> + data-password-toggle="input-gpc-helper-pin" data-show-label="Show GPC PIN" + data-hide-label="Hide GPC PIN" aria-label="Show GPC PIN" + title="Show GPC PIN">
- 邮箱服务 + Mail Service
- +
- + 0 / 9
@@ -1799,10 +1798,10 @@
- +
- - + +
@@ -1812,27 +1811,27 @@ @@ -1840,15 +1839,15 @@ @@ -1856,21 +1855,21 @@ diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 837d9767..1e03f3b4 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -6,9 +6,9 @@ const STATUS_ICONS = { completed: '\u2713', // ✓ failed: '\u2717', // ✗ stopped: '\u25A0', // ■ - manual_completed: '跳', - skipped: '跳', - disabled: '禁', + manual_completed: 'Skip', + skipped: 'Skip', + disabled: 'Off', }; const logArea = document.getElementById('log-area'); @@ -582,7 +582,7 @@ const SIGNUP_METHOD_PHONE = 'phone'; const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL; const DEFAULT_ACTIVE_FLOW_ID = 'openai'; const DEFAULT_PHONE_SIGNUP_RELOGIN_AFTER_BIND_EMAIL_ENABLED = false; -const PHONE_SIGNUP_REUSE_LOCK_TITLE = '手机号注册流程不使用号码复用,切回邮箱注册后会恢复原设置'; +const PHONE_SIGNUP_REUSE_LOCK_TITLE = 'Phone signup flow does not use number reuse; switching back to email signup will restore original settings'; let latestState = null; let currentPlusModeEnabled = false; let currentPlusPaymentMethod = DEFAULT_PLUS_PAYMENT_METHOD; @@ -592,7 +592,7 @@ let currentPhoneVerificationEnabled = false; let currentPhoneSignupReloginAfterBindEmailEnabled = DEFAULT_PHONE_SIGNUP_RELOGIN_AFTER_BIND_EMAIL_ENABLED; let currentStepDefinitionFlowId = DEFAULT_ACTIVE_FLOW_ID; let phoneSignupReuseUiWasLocked = false; -let kiroRsConnectionTestStatusText = '未测试'; +let kiroRsConnectionTestStatusText = 'Not tested'; let heroSmsCountrySelectionOrder = []; let phoneSmsProviderOrderSelection = []; let heroSmsCountryMenuSearchKeyword = ''; @@ -674,111 +674,18 @@ const HERO_SMS_ACQUIRE_PRIORITY_PRICE = 'price'; const HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH = 'price_high'; const DEFAULT_HERO_SMS_ACQUIRE_PRIORITY = HERO_SMS_ACQUIRE_PRIORITY_COUNTRY; const HERO_SMS_SUPPORTED_COUNTRY_ITEMS = Object.freeze([ - { id: 6, chn: '印度尼西亚', eng: 'Indonesia' }, - { id: 52, chn: '泰国', eng: 'Thailand' }, - { id: 187, chn: '美国(物理)', eng: 'USA' }, - { id: 16, chn: '英国', eng: 'United Kingdom' }, - { id: 151, chn: '日本', eng: 'Japan' }, - { id: 43, chn: '德国', eng: 'Germany' }, - { id: 73, chn: '法国', eng: 'France' }, - { id: 10, chn: '越南', eng: 'Vietnam' }, + { id: 6, chn: '', eng: 'Indonesia' }, + { id: 52, chn: '', eng: 'Thailand' }, + { id: 187, chn: '', eng: 'USA (physical)' }, + { id: 16, chn: '', eng: 'United Kingdom' }, + { id: 151, chn: '', eng: 'Japan' }, + { id: 43, chn: '', eng: 'Germany' }, + { id: 73, chn: '', eng: 'France' }, + { id: 10, chn: '', eng: 'Vietnam' }, ]); const HERO_SMS_SUPPORTED_COUNTRY_ID_SET = new Set(HERO_SMS_SUPPORTED_COUNTRY_ITEMS.map((item) => String(item.id))); const HERO_SMS_FALLBACK_COUNTRY_ITEMS = HERO_SMS_SUPPORTED_COUNTRY_ITEMS; -const FIVE_SIM_COUNTRY_CN_BY_ID = Object.freeze({ - afghanistan: '阿富汗', - albania: '阿尔巴尼亚', - algeria: '阿尔及利亚', - angola: '安哥拉', - argentina: '阿根廷', - armenia: '亚美尼亚', - australia: '澳大利亚', - austria: '奥地利', - azerbaijan: '阿塞拜疆', - bahamas: '巴哈马', - bahrain: '巴林', - bangladesh: '孟加拉国', - belarus: '白俄罗斯', - belgium: '比利时', - bolivia: '玻利维亚', - bosnia: '波黑', - brazil: '巴西', - bulgaria: '保加利亚', - cambodia: '柬埔寨', - cameroon: '喀麦隆', - canada: '加拿大', - chile: '智利', - china: '中国', - colombia: '哥伦比亚', - croatia: '克罗地亚', - cyprus: '塞浦路斯', - czech: '捷克', - denmark: '丹麦', - egypt: '埃及', - england: '英国', - estonia: '爱沙尼亚', - ethiopia: '埃塞俄比亚', - finland: '芬兰', - france: '法国', - georgia: '格鲁吉亚', - germany: '德国', - ghana: '加纳', - greece: '希腊', - hongkong: '中国香港', - hungary: '匈牙利', - india: '印度', - indonesia: '印度尼西亚', - ireland: '爱尔兰', - israel: '以色列', - italy: '意大利', - japan: '日本', - jordan: '约旦', - kazakhstan: '哈萨克斯坦', - kenya: '肯尼亚', - kyrgyzstan: '吉尔吉斯斯坦', - laos: '老挝', - latvia: '拉脱维亚', - lithuania: '立陶宛', - malaysia: '马来西亚', - mexico: '墨西哥', - moldova: '摩尔多瓦', - morocco: '摩洛哥', - myanmar: '缅甸', - nepal: '尼泊尔', - netherlands: '荷兰', - newzealand: '新西兰', - nigeria: '尼日利亚', - norway: '挪威', - pakistan: '巴基斯坦', - paraguay: '巴拉圭', - peru: '秘鲁', - philippines: '菲律宾', - poland: '波兰', - portugal: '葡萄牙', - romania: '罗马尼亚', - russia: '俄罗斯', - saudiarabia: '沙特阿拉伯', - serbia: '塞尔维亚', - singapore: '新加坡', - slovakia: '斯洛伐克', - slovenia: '斯洛文尼亚', - southafrica: '南非', - spain: '西班牙', - srilanka: '斯里兰卡', - sweden: '瑞典', - switzerland: '瑞士', - taiwan: '中国台湾', - tajikistan: '塔吉克斯坦', - tanzania: '坦桑尼亚', - thailand: '泰国', - turkey: '土耳其', - ukraine: '乌克兰', - uruguay: '乌拉圭', - usa: '美国', - uzbekistan: '乌兹别克斯坦', - venezuela: '委内瑞拉', - vietnam: '越南', -}); +const FIVE_SIM_COUNTRY_CN_BY_ID = Object.freeze({}); const HERO_SMS_COUNTRY_CODE_ALIAS_OVERRIDES = Object.freeze({ 'bahamas': ['BS'], 'bolivia': ['BO'], @@ -830,7 +737,7 @@ const AUTO_SKIP_FAILURES_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-skip-fai const AUTO_RUN_FALLBACK_RISK_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-run-fallback-risk-prompt-dismissed'; const CPA_PHONE_SIGNUP_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-cpa-phone-signup-prompt-dismissed'; const CLOUDFLARE_TEMP_EMAIL_REGISTRATION_LOOKUP_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-cloudflare-temp-email-registration-lookup-prompt-dismissed'; -const CPA_PHONE_SIGNUP_WARNING_MESSAGE = '请确保打开手机接码设置中的“绑定后重登”开关,不然可能无法使用(有些版本无需开启)'; +const CPA_PHONE_SIGNUP_WARNING_MESSAGE = 'Please enable the "Re-login After Bind" toggle in phone SMS settings, otherwise it may not work (some versions don\'t require it)'; const PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY = 'multipage-phone-verification-section-expanded'; let phoneVerificationSectionExpanded = false; @@ -1109,15 +1016,15 @@ const DEFAULT_PHONE_VERIFICATION_ENABLED = false; const DEFAULT_HERO_SMS_COUNTRY_ID = 52; const DEFAULT_HERO_SMS_COUNTRY_LABEL = 'Thailand'; const DEFAULT_FIVE_SIM_COUNTRY_ID = 'vietnam'; -const DEFAULT_FIVE_SIM_COUNTRY_LABEL = '越南 (Vietnam)'; +const DEFAULT_FIVE_SIM_COUNTRY_LABEL = 'Vietnam'; const FIVE_SIM_SUPPORTED_COUNTRY_ITEMS = Object.freeze([ - { id: 'indonesia', chn: '印度尼西亚', eng: 'Indonesia', searchText: 'indonesia 印度尼西亚 印尼 Indonesia ID +62' }, - { id: 'thailand', chn: '泰国', eng: 'Thailand', searchText: 'thailand 泰国 Thailand TH +66' }, - { id: 'england', chn: '英国', eng: 'England', searchText: 'england 英国 England UK GB United Kingdom +44' }, - { id: 'usa', chn: '美国', eng: 'United States', searchText: 'usa 美国 United States US +1' }, - { id: 'japan', chn: '日本', eng: 'Japan', searchText: 'japan 日本 Japan JP +81' }, - { id: 'germany', chn: '德国', eng: 'Germany', searchText: 'germany 德国 Germany DE +49' }, - { id: 'vietnam', chn: '越南', eng: 'Vietnam', searchText: 'vietnam 越南 Vietnam VN +84' }, + { id: 'indonesia', chn: '', eng: 'Indonesia', searchText: 'indonesia Indonesia ID +62' }, + { id: 'thailand', chn: '', eng: 'Thailand', searchText: 'thailand Thailand TH +66' }, + { id: 'england', chn: '', eng: 'England', searchText: 'england England UK GB United Kingdom +44' }, + { id: 'usa', chn: '', eng: 'United States', searchText: 'usa United States US +1' }, + { id: 'japan', chn: '', eng: 'Japan', searchText: 'japan Japan JP +81' }, + { id: 'germany', chn: '', eng: 'Germany', searchText: 'germany Germany DE +49' }, + { id: 'vietnam', chn: '', eng: 'Vietnam', searchText: 'vietnam Vietnam VN +84' }, ]); const FIVE_SIM_SUPPORTED_COUNTRY_ID_SET = new Set(FIVE_SIM_SUPPORTED_COUNTRY_ITEMS.map((item) => item.id)); const NEX_SMS_FALLBACK_COUNTRY_ITEMS = Object.freeze([ @@ -1185,24 +1092,24 @@ function getManagedAliasProviderUiCopy(provider = selectMailProvider.value, mail } if (String(provider || '').trim().toLowerCase() === GMAIL_PROVIDER) { return { - baseLabel: '基邮箱', - basePlaceholder: '例如 yourname@gmail.com', - buttonLabel: '生成', - successVerb: '生成', - label: 'Gmail +tag 邮箱', - placeholder: '点击生成 Gmail +tag 邮箱,或手动填写完整邮箱', - hint: '先填写基邮箱后点“生成”,也可以直接手动填写完整的 Gmail 邮箱。', + baseLabel: 'Base Email', + basePlaceholder: 'e.g. yourname@gmail.com', + buttonLabel: 'Generate', + successVerb: 'Generated', + label: 'Gmail +tag Email', + placeholder: 'Click to generate Gmail +tag email, or fill in a complete email manually', + hint: 'Fill in the base email and click "Generate", or fill in a complete Gmail email manually.', }; } if (String(provider || '').trim().toLowerCase() === '2925') { return { - baseLabel: '基邮箱', - basePlaceholder: '例如 yourname@2925.com', - buttonLabel: '生成', - successVerb: '生成', - label: '2925 邮箱', - placeholder: '点击生成 2925 邮箱,或手动填写完整邮箱', - hint: '先填写基邮箱后点“生成”,也可以直接手动填写完整的 2925 邮箱。', + baseLabel: 'Base Email', + basePlaceholder: 'e.g. yourname@2925.com', + buttonLabel: 'Generate', + successVerb: 'Generated', + label: '2925 Email', + placeholder: 'Click to generate 2925 email, or fill in a complete email manually', + hint: 'Fill in the base email and click "Generate", or fill in a complete 2925 email manually.', }; } return null; @@ -1238,8 +1145,8 @@ function syncMail2925PoolAccountOptions(state = latestState) { const accounts = getMail2925Accounts(state); const selectedId = getPreferredMail2925PoolAccountId(state); - const options = [''].concat( - accounts.map((account) => ``) + const options = [''].concat( + accounts.map((account) => ``) ); selectMail2925PoolAccount.innerHTML = options.join(''); selectMail2925PoolAccount.value = selectedId; @@ -1273,7 +1180,7 @@ async function syncSelectedMail2925PoolAccount(options = {}) { }); setManagedAliasBaseEmailInputForProvider('2925', latestState); if (!silent) { - showToast(`已切换当前 2925 号池邮箱为 ${response.account?.email || accountId}`, 'success', 1800); + showToast(`Switched current 2925 pool email to ${response.account?.email || accountId}`, 'success', 1800); } return response.account || null; } @@ -1343,9 +1250,9 @@ function getCurrentRegistrationEmailUiCopy() { : String(selectMailProvider.value || '').trim().toLowerCase() === 'yyds-mail'; if (useYydsMail) { return { - buttonLabel: '获取', - placeholder: '点击获取 YYDS Mail 邮箱,或手动粘贴邮箱', - successVerb: '获取', + buttonLabel: 'Fetch', + placeholder: 'Click to fetch YYDS Mail email, or paste email manually', + successVerb: 'Fetched', label: 'YYDS Mail', }; } @@ -1374,8 +1281,8 @@ function validateCurrentRegistrationEmail(email = inputEmail.value.trim(), optio const baseEmail = getManagedAliasBaseEmailForProvider(); showToast( baseEmail - ? `当前邮箱服务为“${uiCopy?.label || '别名邮箱'}”,注册邮箱需与 ${uiCopy?.baseLabel || '基邮箱'} 对应。` - : `当前邮箱服务为“${uiCopy?.label || '别名邮箱'}”,请直接填写完整邮箱,或先填写基邮箱后点击“生成”。`, + ? `Current mail service is "${uiCopy?.label || 'Alias mail'}". Registration email must match the ${uiCopy?.baseLabel || 'base email'}.` + : `Current mail service is "${uiCopy?.label || 'Alias mail'}". Please fill in a complete email directly, or fill in the base email and click "Generate".`, 'warn' ); } @@ -1541,9 +1448,9 @@ const sub2ApiGroupPicker = createEditableListPicker({ menu: sub2ApiGroupMenu, fallbackItems: DEFAULT_SUB2API_GROUP_OPTIONS, minItems: 1, - itemLabel: '分组', + itemLabel: 'group', onDelete: handleDeleteSub2ApiGroup, - onDeleteError: (error) => showToast(error?.message || '删除 SUB2API 分组失败。', 'error'), + onDeleteError: (error) => showToast(error?.message || 'Failed to delete SUB2API group.', 'error'), }); const cfDomainPicker = createEditableListPicker({ @@ -1552,12 +1459,12 @@ const cfDomainPicker = createEditableListPicker({ trigger: btnCfDomainMenu, current: cfDomainCurrent, menu: cfDomainMenu, - emptyLabel: '请先添加域名', - itemLabel: '域名', + emptyLabel: 'Please add a domain first', + itemLabel: 'domain', normalizeItems: normalizeCloudflareDomains, normalizeValue: normalizeCloudflareDomainValue, onDelete: handleDeleteCloudflareDomain, - onDeleteError: (error) => showToast(error?.message || '删除 Cloudflare 域名失败。', 'error'), + onDeleteError: (error) => showToast(error?.message || 'Failed to delete Cloudflare domain.', 'error'), }); const tempEmailDomainPicker = createEditableListPicker({ @@ -1566,12 +1473,12 @@ const tempEmailDomainPicker = createEditableListPicker({ trigger: btnTempEmailDomainMenu, current: tempEmailDomainCurrent, menu: tempEmailDomainMenu, - emptyLabel: '请先更新域名', - itemLabel: '域名', + emptyLabel: 'Please update domain first', + itemLabel: 'domain', normalizeItems: normalizeCloudflareTempEmailDomains, normalizeValue: normalizeCloudflareTempEmailDomainValue, onDelete: handleDeleteCloudflareTempEmailDomain, - onDeleteError: (error) => showToast(error?.message || '删除 Cloudflare Temp Email 域名失败。', 'error'), + onDeleteError: (error) => showToast(error?.message || 'Failed to delete Cloudflare Temp Email domain.', 'error'), }); function renderSub2ApiGroupOptions(state = latestState, selectedValue = '') { @@ -1665,7 +1572,7 @@ const sharedFormDialog = window.SidepanelFormDialog?.createFormDialog?.({ cancelButton: btnSharedFormModalCancel, confirmButton: btnSharedFormModalConfirm, }); -const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = window.LuckMailUtils?.DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME || '保留'; +const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = window.LuckMailUtils?.DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME || 'Preserved'; const normalizeIcloudHost = window.IcloudUtils?.normalizeIcloudHost || ((value) => { const normalized = String(value || '').trim().toLowerCase(); @@ -1702,46 +1609,46 @@ const getIcloudLoginUrlForHost = window.IcloudUtils?.getIcloudLoginUrlForHost const MAIL_PROVIDER_LOGIN_CONFIGS = { [ICLOUD_PROVIDER]: { - label: 'iCloud 邮箱', - buttonLabel: '登录', + label: 'iCloud Mail', + buttonLabel: 'Login', }, [GMAIL_PROVIDER]: { - label: 'Gmail 邮箱', + label: 'Gmail', url: 'https://mail.google.com/mail/u/0/#inbox', - buttonLabel: '登录', + buttonLabel: 'Login', }, '163': { - label: '163 邮箱', + label: '163 Mail', url: 'https://mail.163.com/', - buttonLabel: '登录', + buttonLabel: 'Login', }, '163-vip': { - label: '163 VIP 邮箱', + label: '163 VIP Mail', url: 'https://webmail.vip.163.com/', - buttonLabel: '登录', + buttonLabel: 'Login', }, '126': { - label: '126 邮箱', + label: '126 Mail', url: 'https://mail.126.com/', - buttonLabel: '登录', + buttonLabel: 'Login', }, qq: { - label: 'QQ 邮箱', + label: 'QQ Mail', url: 'https://wx.mail.qq.com/', - buttonLabel: '登录', + buttonLabel: 'Login', }, 'cloudflare-temp-email': { - label: 'Cloudflare Temp Email 部署', + label: 'Cloudflare Temp Email Deploy', url: 'https://github.com/QLHazyCoder/cloudflare_temp_email', - buttonLabel: '部署', + buttonLabel: 'Deploy', }, [YYDS_MAIL_PROVIDER]: { label: 'YYDS Mail', url: 'https://vip.215.im/docs', - buttonLabel: '文档', + buttonLabel: 'Docs', }, '2925': { - label: '2925 邮箱', + label: '2925 Mail', url: 'https://2925.com/#/mailList', }, }; @@ -1749,7 +1656,7 @@ const IP_PROXY_SERVICE_LOGIN_CONFIGS = { '711proxy': { label: '711Proxy', url: 'https://www.711proxy.com/signup?code=AD2497', - buttonLabel: '注册', + buttonLabel: 'Sign Up', }, }; @@ -1767,10 +1674,10 @@ const TOAST_ICONS = { }; const LOG_LEVEL_LABELS = { - info: '信息', - ok: '成功', - warn: '警告', - error: '错误', + info: 'Info', + ok: 'Success', + warn: 'Warning', + error: 'Error', }; const CLOUDFLARE_TEMP_EMAIL_REPOSITORY_URL = 'https://github.com/QLHazyCoder/cloudflare_temp_email'; @@ -1838,7 +1745,7 @@ function resetActionModalOption() { modalOptionRow.hidden = true; modalOptionInput.checked = false; modalOptionInput.disabled = false; - modalOptionText.textContent = '不再提示'; + modalOptionText.textContent = 'Don\'t show again'; } function resetActionModalAlert() { @@ -1903,7 +1810,7 @@ function configureActionModalOption(option) { modalOptionRow.hidden = false; modalOptionInput.checked = Boolean(option.checked); modalOptionInput.disabled = Boolean(option.disabled); - modalOptionText.textContent = option.label || '不再提示'; + modalOptionText.textContent = option.label || 'Don\'t show again'; } function configureActionModalAlert(alert) { @@ -1971,26 +1878,26 @@ function openActionModal({ title, message, messageHtml, actions, option, alert, function openAutoStartChoiceDialog(startStep, options = {}) { const runningStep = Number.isInteger(options.runningStep) ? options.runningStep : null; const continueMessage = runningStep - ? `继续当前会先等待步骤 ${runningStep} 完成,再按最新进度自动执行。` - : `继续当前会从步骤 ${startStep} 开始自动执行。`; + ? `Continue will first wait for step ${runningStep} to complete, then auto-execute based on the latest progress.` + : `Continue will start auto-execution from step ${startStep}.`; return openActionModal({ - title: '启动自动', - message: `检测到当前已有流程进度。${continueMessage}重新开始会清空当前流程进度并从步骤 1 新开一轮。`, + title: 'Start Auto', + message: `Existing flow progress detected. ${continueMessage} Restart will clear current flow progress and start a new round from step 1.`, actions: [ - { id: null, label: '取消', variant: 'btn-ghost' }, - { id: 'restart', label: '重新开始', variant: 'btn-outline' }, - { id: 'continue', label: '继续当前', variant: 'btn-primary' }, + { id: null, label: 'Cancel', variant: 'btn-ghost' }, + { id: 'restart', label: 'Restart', variant: 'btn-outline' }, + { id: 'continue', label: 'Continue', variant: 'btn-primary' }, ], }); } -async function openConfirmModal({ title, message, confirmLabel = '确认', confirmVariant = 'btn-primary', alert = null }) { +async function openConfirmModal({ title, message, confirmLabel = 'Confirm', confirmVariant = 'btn-primary', alert = null }) { const choice = await openActionModal({ title, message, alert, actions: [ - { id: null, label: '取消', variant: 'btn-ghost' }, + { id: null, label: 'Cancel', variant: 'btn-ghost' }, { id: 'confirm', label: confirmLabel, variant: confirmVariant }, ], }); @@ -2001,10 +1908,10 @@ async function openConfirmModalWithOption({ title, message, messageHtml = '', - confirmLabel = '确认', + confirmLabel = 'Confirm', confirmVariant = 'btn-primary', alert = null, - optionLabel = '不再提示', + optionLabel = 'Don\'t show again', optionChecked = false, optionDisabled = false, }) { @@ -2014,7 +1921,7 @@ async function openConfirmModalWithOption({ messageHtml, alert, actions: [ - { id: null, label: '取消', variant: 'btn-ghost' }, + { id: null, label: 'Cancel', variant: 'btn-ghost' }, { id: 'confirm', label: confirmLabel, variant: confirmVariant }, ], option: { @@ -2078,22 +1985,22 @@ async function openPlusManualConfirmationDialog(options = {}) { && signupMethod === 'email' && plusAccountAccessStrategy === 'sub2api_codex_session'; const continuationActionLabel = useSub2ApiSessionImport - ? '导入当前 ChatGPT 会话到 SUB2API' - : 'OAuth 登录'; - const title = String(options.title || '').trim() || (method === 'gopay' ? 'GoPay 订阅确认' : '手动确认'); + ? 'Import current ChatGPT session to SUB2API' + : 'OAuth login'; + const title = String(options.title || '').trim() || (method === 'gopay' ? 'GoPay Subscription Confirm' : 'Manual Confirmation'); const message = String(options.message || '').trim() || (method === 'gopay' - ? '请在当前订阅页中手动完成 GoPay 订阅,完成后点击“我已完成订阅”继续。' - : '请先在页面中完成当前手动操作,完成后点击确认继续。'); + ? 'Please manually complete the GoPay subscription on the current page, then click "I\'ve completed subscription" to continue.' + : 'Please complete the current manual operation on the page first, then click Confirm to continue.'); return openActionModal({ title, message, actions: [ - { id: 'cancel', label: '取消等待', variant: 'btn-ghost' }, - { id: 'confirm', label: '我已完成订阅', variant: 'btn-primary' }, + { id: 'cancel', label: 'Cancel Wait', variant: 'btn-ghost' }, + { id: 'confirm', label: 'I\'ve completed subscription', variant: 'btn-primary' }, ], alert: method === 'gopay' - ? { text: `确认后流程会直接继续到 Plus 模式后续的${continuationActionLabel}。`, tone: 'info' } + ? { text: `After confirmation, the flow will continue directly to the Plus mode ${continuationActionLabel}.`, tone: 'info' } : null, }); } @@ -2149,8 +2056,8 @@ async function syncPlusManualConfirmationDialog() { && signupMethod === 'email' && plusAccountAccessStrategy === 'sub2api_codex_session'; const continuationActionLabel = useSub2ApiSessionImport - ? '导入当前 ChatGPT 会话到 SUB2API' - : 'OAuth 登录'; + ? 'Import current ChatGPT session to SUB2API' + : 'OAuth login'; const title = latestState?.plusManualConfirmationTitle; const message = latestState?.plusManualConfirmationMessage; activePlusManualConfirmationRequestId = requestId; @@ -2170,7 +2077,7 @@ async function syncPlusManualConfirmationDialog() { } if (choice == null) { shouldReopenDialog = true; - showToast('当前订阅确认仍在等待中,将重新弹出确认窗口。', 'info', 1800); + showToast('Current subscription confirmation is still pending; the confirmation window will be reopened.', 'info', 1800); return; } @@ -2188,12 +2095,12 @@ async function syncPlusManualConfirmationDialog() { throw new Error(response.error); } if (confirmed) { - showToast(method === 'gopay' ? `GoPay 订阅已确认,正在继续${continuationActionLabel}...` : '已确认,流程继续执行中...', 'info', 2200); + showToast(method === 'gopay' ? `GoPay subscription confirmed, continuing ${continuationActionLabel}...` : 'Confirmed, flow continuing...', 'info', 2200); } else { - showToast(method === 'gopay' ? '已取消 GoPay 订阅等待。' : '已取消当前手动确认。', 'warn', 2200); + showToast(method === 'gopay' ? 'Canceled GoPay subscription wait.' : 'Canceled current manual confirmation.', 'warn', 2200); } } catch (error) { - showToast(error?.message || String(error || '未知错误'), 'error'); + showToast(error?.message || String(error || 'Unknown error'), 'error'); } finally { if (activePlusManualConfirmationRequestId === requestId) { activePlusManualConfirmationRequestId = ''; @@ -2264,14 +2171,14 @@ function getContributionContentTargetId(state = latestState) { function openNewUserGuidePrompt() { return openActionModal({ - title: '新手引导', - message: '如果你是第一次使用,可以先查看贡献页里的公告和使用教程。点击“查看引导”会自动打开贡献页面。', + title: 'New User Guide', + message: 'If this is your first time, you can check the announcements and tutorials on the contribution page. Clicking "View Guide" will automatically open the contribution page.', alert: { - text: '本提示仅出现一次。', + text: 'This prompt only shows once.', }, actions: [ - { id: null, label: '取消', variant: 'btn-ghost' }, - { id: 'confirm', label: '查看引导', variant: 'btn-primary' }, + { id: null, label: 'Cancel', variant: 'btn-ghost' }, + { id: 'confirm', label: 'View Guide', variant: 'btn-primary' }, ], }); } @@ -2435,10 +2342,10 @@ function shouldWarnCpaPhoneSignup(signupMethod = null, targetId = null) { async function openCpaPhoneSignupWarningModal() { const result = await openConfirmModalWithOption({ - title: 'CPA 手机号注册提醒', + title: 'CPA Phone Signup Reminder', message: CPA_PHONE_SIGNUP_WARNING_MESSAGE, - confirmLabel: '继续', - optionLabel: '不再提醒', + confirmLabel: 'Continue', + optionLabel: 'Don\'t remind again', }); return { @@ -2469,7 +2376,7 @@ async function confirmCpaPhoneSignupIfNeeded(options = {}) { function buildCloudflareTempEmailRegistrationLookupPromptHtml() { const repositoryUrl = escapeHtml(CLOUDFLARE_TEMP_EMAIL_REPOSITORY_URL); - return `需要部署本扩展作者修改后的 Cloudflare Temp Email;部署后可支持多线程收码。`; + return `Requires deploying the author's modified Cloudflare Temp Email; after deployment, multi-threaded code reception is supported.`; } async function confirmCloudflareTempEmailRegistrationLookupIfNeeded() { @@ -2478,10 +2385,10 @@ async function confirmCloudflareTempEmailRegistrationLookupIfNeeded() { } const result = await openConfirmModalWithOption({ - title: '注册邮箱查信', + title: 'Registration Email Lookup', messageHtml: buildCloudflareTempEmailRegistrationLookupPromptHtml(), - confirmLabel: '我已知晓', - optionLabel: '不再提醒', + confirmLabel: 'Understood', + optionLabel: 'Don\'t remind again', }); if (result.confirmed && result.optionChecked) { @@ -2493,9 +2400,9 @@ async function confirmCloudflareTempEmailRegistrationLookupIfNeeded() { async function openAutoSkipFailuresConfirmModal() { const result = await openConfirmModalWithOption({ - title: '自动重试说明', - message: `开启后,自动模式在某一轮失败时,会先在当前轮自动重试;单轮最多重试 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次,仍失败则放弃当前轮并继续下一轮。线程间隔只在开启自动重试且总轮数大于 1 时生效。`, - confirmLabel: '确认开启', + title: 'Auto Retry Description', + message: `When enabled, in auto mode, if a round fails, it will first auto-retry in the current round; max ${AUTO_RUN_MAX_RETRIES_PER_ROUND} retries per round. If it still fails, abandon the current round and continue to the next. Thread interval only takes effect when auto retry is enabled and total rounds is greater than 1.`, + confirmLabel: 'Confirm Enable', }); return { @@ -2506,9 +2413,9 @@ async function openAutoSkipFailuresConfirmModal() { async function openAutoRunFallbackRiskConfirmModal(totalRuns) { const result = await openConfirmModalWithOption({ - title: '自动运行风险提醒', - message: `当前轮数已经不适合单节点情况,请确保已经配置并打开节点轮询功能(若没有配置,请点击贡献/使用按钮,根据网页中使用教程进行配置),避免连续使用一个节点注册,导致出现手机号验证。`, - confirmLabel: '继续', + title: 'Auto Run Risk Reminder', + message: `The current round count is not suitable for single-node use. Please make sure you've configured and enabled the node rotation feature (if not configured, click the Contribute/Tutorial button and follow the tutorial), to avoid continuously using one node for registration, which may trigger phone verification.`, + confirmLabel: 'Continue', }); return { @@ -2656,28 +2563,28 @@ function normalizeStepExecutionRangeEntry(value = {}) { function getKiroUploadStatusLabel(value = '') { const rawValue = String(value || '').trim(); if (!rawValue) { - return '未开始'; + return 'Not started'; } const normalizedValue = rawValue.toLowerCase(); switch (normalizedValue) { case 'waiting_login': - return '等待登录授权'; + return 'Waiting for login authorization'; case 'ready_to_upload': - return '等待上传'; + return 'Waiting to upload'; case 'uploading': - return '上传中'; + return 'Uploading'; case 'uploaded': case 'credential uploaded.': - return '上传成功'; + return 'Upload successful'; case 'error': - return '上传失败'; + return 'Upload failed'; case 'waiting_user': - return '等待用户确认'; + return 'Waiting for user confirmation'; case 'authorized': - return '已授权'; + return 'Authorized'; case 'expired': - return '已过期'; + return 'Expired'; default: return rawValue; } @@ -2723,23 +2630,23 @@ function getGrokRegisterStatusLabel(value = '') { const normalized = String(value || '').trim().toLowerCase(); switch (normalized) { case 'signup_page_opened': - return '注册页已打开'; + return 'Signup page opened'; case 'email_submitting': - return '正在提交邮箱'; + return 'Submitting email'; case 'verification_requested': - return '等待验证码'; + return 'Waiting for verification code'; case 'verified': - return '验证码已提交'; + return 'Verification code submitted'; case 'profile_submitting': - return '正在提交资料'; + return 'Submitting profile'; case 'profile_submitted': - return '资料已提交'; + return 'Profile submitted'; case 'completed': - return '已完成'; + return 'Completed'; case 'error': - return '失败'; + return 'Failed'; default: - return String(value || '').trim() || '未开始'; + return String(value || '').trim() || 'Not started'; } } @@ -2747,13 +2654,13 @@ function getGrokWebchat2ApiUploadStatusLabel(value = '') { const normalized = String(value || '').trim().toLowerCase(); switch (normalized) { case 'uploading': - return '正在上传'; + return 'Uploading'; case 'uploaded': - return '已上传'; + return 'Uploaded'; case 'error': - return '上传失败'; + return 'Upload failed'; default: - return String(value || '').trim() || '未开始'; + return String(value || '').trim() || 'Not started'; } } @@ -2803,19 +2710,19 @@ function renderGrokRuntimeState(state = latestState) { } if (displayGrokSsoStatus) { displayGrokSsoStatus.textContent = currentCookie - ? `已提取 ${cookies.length || 1} 条${extractedAt ? `,${new Date(extractedAt).toLocaleString()}` : ''}` - : '未提取'; + ? `Extracted ${cookies.length || 1}${extractedAt ? `, ${new Date(extractedAt).toLocaleString()}` : ''}` + : 'Not extracted'; } if (displayGrokSsoCookie) { displayGrokSsoCookie.textContent = currentCookie ? `${currentCookie.slice(0, 8)}...${currentCookie.slice(-6)}` - : '未提取'; - displayGrokSsoCookie.title = currentCookie ? '已隐藏完整 SSO Cookie,可使用复制' : ''; + : 'Not extracted'; + displayGrokSsoCookie.title = currentCookie ? 'Full SSO Cookie hidden, use copy button' : ''; } if (displayGrokWebchat2ApiUploadStatus) { const label = getGrokWebchat2ApiUploadStatusLabel(uploadStatus); - const suffix = uploadedAt ? `,${new Date(uploadedAt).toLocaleString()}` : ''; - displayGrokWebchat2ApiUploadStatus.textContent = `${label}${uploadMessage ? `:${uploadMessage}` : ''}${suffix}`; + const suffix = uploadedAt ? `, ${new Date(uploadedAt).toLocaleString()}` : ''; + displayGrokWebchat2ApiUploadStatus.textContent = `${label}${uploadMessage ? `: ${uploadMessage}` : ''}${suffix}`; displayGrokWebchat2ApiUploadStatus.title = uploadTargetUrl || ''; } [btnCopyGrokSso, btnClearGrokSso].forEach((button) => { @@ -2826,7 +2733,7 @@ function renderGrokRuntimeState(state = latestState) { } function setKiroRsConnectionTestStatus(message = '') { - const nextText = String(message || '').trim() || '未测试'; + const nextText = String(message || '').trim() || 'Not tested'; kiroRsConnectionTestStatusText = nextText; if (typeof displayKiroRsTestStatus !== 'undefined' && displayKiroRsTestStatus) { displayKiroRsTestStatus.textContent = nextText; @@ -2877,7 +2784,7 @@ function getStepExecutionRangeNodeLabel(node = {}) { const displayOrder = Number(node?.displayOrder); const title = String(node?.title || nodeId).trim(); const orderLabel = Number.isInteger(displayOrder) && displayOrder > 0 - ? `步骤 ${displayOrder}` + ? `Step ${displayOrder}` : nodeId; return title ? `${orderLabel} · ${title}` : orderLabel; } @@ -3193,8 +3100,8 @@ function appendOperationDelayLog(enabled, level = 'info', message = '') { timestamp: Date.now(), level, message: message || (enabled - ? '操作间延迟已固定开启。' - : '操作间延迟保持固定开启。'), + ? 'Inter-operation delay is locked on.' + : 'Inter-operation delay remains locked on.'), }); } @@ -3204,7 +3111,7 @@ function applyOperationDelayState(state = latestState, options = {}) { syncLatestState({ operationDelayEnabled: enabled }); } if (options.restoreFailed) { - appendOperationDelayLog(true, 'warn', '操作间延迟设置读取失败,已回退为默认开启。'); + appendOperationDelayLog(true, 'warn', 'Failed to read inter-operation delay setting, reverted to default on.'); } } @@ -3226,7 +3133,7 @@ async function persistOperationDelayToggle() { appendOperationDelayLog(confirmed); } catch (error) { if (inputOperationDelayEnabled) inputOperationDelayEnabled.checked = lastConfirmedOperationDelayEnabled; - appendOperationDelayLog(lastConfirmedOperationDelayEnabled, 'error', `操作间延迟设置保存失败,已恢复为上一次确认的状态:${error.message}`); + appendOperationDelayLog(lastConfirmedOperationDelayEnabled, 'error', `Failed to save inter-operation delay setting, restored to last confirmed state: ${error.message}`); throw error; } } @@ -3393,30 +3300,30 @@ function normalizePlusStrategyTargetId(value = '') { function getPlusAccountAccessStrategyContinuationLabel(strategy = '', targetId = '') { const normalizedStrategy = normalizePlusAccountAccessStrategy(strategy); if (normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { - return '导入当前 ChatGPT 会话到 SUB2API'; + return 'Import current ChatGPT session to SUB2API'; } if (normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { - return '导入当前 ChatGPT 会话到 CPA'; + return 'Import current ChatGPT session to CPA'; } - return 'OAuth 登录'; + return 'OAuth login'; } function getPlusAccountAccessStrategyDescription(strategy = '', targetId = '') { const normalizedStrategy = normalizePlusAccountAccessStrategy(strategy); const normalizedTargetId = normalizePlusStrategyTargetId(targetId); if (normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { - return '复用当前 Plus 已登录会话,直接导入到 SUB2API'; + return 'Reuse current Plus session, import directly to SUB2API'; } if (normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { - return '复用当前 Plus 已登录会话,直接导入到 CPA'; + return 'Reuse current Plus session, import directly to CPA'; } if (normalizedTargetId === 'sub2api') { - return '通过 OAuth 回调创建 SUB2API 账号'; + return 'Create SUB2API account via OAuth callback'; } if (normalizedTargetId === 'codex2api') { - return '通过 OAuth 回调创建 Codex2API 账号'; + return 'Create Codex2API account via OAuth callback'; } - return '通过 OAuth 回调创建 CPA 账号'; + return 'Create CPA account via OAuth callback'; } function resolvePlusManualContinuationActionLabelFromState(state = latestState) { @@ -3721,9 +3628,9 @@ function shouldSyncRunCountFromAutoRunSource(source = {}) { function getAutoRunLabel(payload = currentAutoRun) { if ((payload.phase ?? currentAutoRun.phase) === 'scheduled') { - return (payload.totalRuns || 1) > 1 ? ` (${payload.totalRuns}轮)` : ''; + return (payload.totalRuns || 1) > 1 ? ` (${payload.totalRuns} rounds)` : ''; } - const attemptLabel = payload.attemptRun ? ` · 尝试${payload.attemptRun}` : ''; + const attemptLabel = payload.attemptRun ? ` · Attempt ${payload.attemptRun}` : ''; if ((payload.totalRuns || 1) > 1) { return ` (${payload.currentRun}/${payload.totalRuns}${attemptLabel})`; } @@ -3989,7 +3896,7 @@ function getActiveAutoRunCountdown() { return { at: currentAutoRun.countdownAt, - title: currentAutoRun.countdownTitle || '等待中', + title: currentAutoRun.countdownTitle || 'Waiting', note: currentAutoRun.countdownNote || '', tone: 'running', }; @@ -4010,12 +3917,12 @@ function renderAutoRunCountdownInfo() { autoCountdownBar.style.display = 'flex'; if (btnAutoRunNow) { btnAutoRunNow.hidden = false; - btnAutoRunNow.textContent = '立即继续'; + btnAutoRunNow.textContent = 'Continue Now'; } autoCountdownTitle.textContent = countdown.title; autoCountdownMeta.textContent = remainingMs > 0 - ? `${countdown.note ? `${countdown.note},` : ''}剩余 ${formatCountdown(remainingMs)}` - : '倒计时即将结束,正在准备继续...'; + ? `${countdown.note ? `${countdown.note}, ` : ''}${formatCountdown(remainingMs)} remaining` + : 'Countdown ending soon, preparing to continue...'; return; } @@ -4040,7 +3947,7 @@ function syncAutoRunCountdownTicker() { function setDefaultAutoRunButton() { btnAutoRun.disabled = false; inputRunCount.disabled = shouldLockRunCountToEmailPool(); - btnAutoRun.innerHTML = ' 自动'; + btnAutoRun.innerHTML = ' Auto'; } function normalizeCloudflareDomainValue(value = '') { @@ -4153,7 +4060,7 @@ function setCloudflareDomainEditMode(editing, options = {}) { cloudflareDomainEditMode = Boolean(editing); cfDomainPicker.setVisible(!cloudflareDomainEditMode); inputCfDomain.style.display = cloudflareDomainEditMode ? '' : 'none'; - btnCfDomainMode.textContent = cloudflareDomainEditMode ? '保存' : '添加'; + btnCfDomainMode.textContent = cloudflareDomainEditMode ? 'Save' : 'Add'; if (cloudflareDomainEditMode) { if (clearInput) { inputCfDomain.value = ''; @@ -4169,7 +4076,7 @@ function setCloudflareTempEmailDomainEditMode(editing, options = {}) { cloudflareTempEmailDomainEditMode = false; tempEmailDomainPicker.setVisible(true); inputTempEmailDomain.style.display = 'none'; - btnTempEmailDomainMode.textContent = '更新'; + btnTempEmailDomainMode.textContent = 'Update'; if (clearInput) { inputTempEmailDomain.value = ''; } @@ -5499,7 +5406,7 @@ function normalizePhoneSmsProviderOrderValue(value = [], fallbackOrder = []) { function formatPhoneSmsProviderOrderSummary(order = []) { const normalized = normalizePhoneSmsProviderOrderValue(order, []); if (!normalized.length) { - return '未设置'; + return 'Not set'; } return normalized .map((provider, index) => `${index + 1}. ${getPhoneSmsProviderLabel(provider)}`) @@ -5514,7 +5421,7 @@ function updatePhoneSmsProviderOrderSummary(order = []) { if (btnPhoneSmsProviderOrderMenu) { btnPhoneSmsProviderOrderMenu.textContent = normalized.length ? `${normalized.map((provider) => getPhoneSmsProviderLabel(provider)).join(' / ')} (${normalized.length}/3)` - : `未选择 (0/3)`; + : `None selected (0/3)`; } } @@ -5619,7 +5526,7 @@ function syncPhoneSmsProviderOrderFromSelect(options = {}) { const droppedCount = nextOrder.length - selectionLimit; nextOrder = nextOrder.slice(0, selectionLimit); if (showLimitToast && droppedCount > 0 && typeof showToast === 'function') { - showToast(`服务商顺序最多 ${selectionLimit} 个,已保留前 ${selectionLimit} 个。`, 'warn', 2200); + showToast(`Provider order is limited to ${selectionLimit}; kept the first ${selectionLimit}.`, 'warn', 2200); } } @@ -6153,13 +6060,13 @@ function buildHeroSmsCountryDisplayLabel(country = {}) { function normalizeHeroSmsFetchErrorMessage(error) { const message = String(error?.message || error || '').trim(); if (!message) { - return '未知网络错误'; + return 'Unknown network error'; } if (/aborted|abort|timed out|timeout/i.test(message)) { - return '请求超时,请稍后重试'; + return 'Request timed out, please retry later'; } if (/failed to fetch|networkerror|network request failed/i.test(message)) { - return '网络不可用或被拦截'; + return 'Network unavailable or blocked'; } return message; } @@ -6460,37 +6367,37 @@ function describeHeroSmsPreviewPayload(payload) { function summarizeHeroSmsPreviewError(payload, responseStatus = 0) { if (isHeroSmsPreviewEmptyPayload(payload)) { - return '未返回有效价格'; + return 'No valid price returned'; } const text = describeHeroSmsPreviewPayload(payload); if (text === '{}' || text === '[]') { - return '未返回有效价格'; + return 'No valid price returned'; } if (/UNPROCESSABLE_ENTITY/i.test(text) && /api_key/i.test(text) && /REQUIRED/i.test(text)) { - return '请先填写接码 API Key'; + return 'Please fill in SMS API Key first'; } if (/BAD_KEY|WRONG_KEY|INVALID_KEY/i.test(text)) { - return 'API Key 无效'; + return 'Invalid API Key'; } if (/NO_BALANCE|NOT_ENOUGH_BALANCE/i.test(text)) { - return '余额不足'; + return 'Insufficient balance'; } if (/BANNED|ACCOUNT_BANNED/i.test(text)) { - return '账号已被封禁'; + return 'Account banned'; } if (/WRONG_SERVICE|SERVICE_NOT_FOUND/i.test(text)) { - return '服务代码无效'; + return 'Invalid service code'; } if (/WRONG_COUNTRY|COUNTRY_NOT_FOUND/i.test(text)) { - return '国家参数无效'; + return 'Invalid country parameter'; } if (/NO_NUMBERS/i.test(text)) { - return '暂无可用号源'; + return 'No available numbers'; } if (responseStatus && responseStatus >= 400) { return `HTTP ${responseStatus}`; } - return text || '未知错误'; + return text || 'Unknown error'; } function resolvePhoneSmsPricePreviewRange(provider = '') { @@ -6579,9 +6486,9 @@ function formatPhoneSmsPriceRangePreviewText(range = {}) { function buildPhoneSmsPriceRangePreviewMessage(range = {}) { const rangeText = formatPhoneSmsPriceRangePreviewText(range); if (range?.invalid) { - return `价格区间无效:最低购买价 ${formatHeroSmsPriceForPreview(range.minPrice) || range.minPrice} 高于价格上限 ${formatHeroSmsPriceForPreview(range.maxPrice) || range.maxPrice}`; + return `Invalid price range: min buy price ${formatHeroSmsPriceForPreview(range.minPrice) || range.minPrice} is higher than price cap ${formatHeroSmsPriceForPreview(range.maxPrice) || range.maxPrice}`; } - return rangeText ? `区间内无可用号源(当前 ${rangeText})` : '暂无可用号源'; + return rangeText ? `No available numbers within range (current ${rangeText})` : 'No available numbers'; } function formatPriceTiersForPreview(entries = [], options = {}) { @@ -6611,7 +6518,7 @@ function formatPriceTiersForPreview(entries = [], options = {}) { const limit = 16; const displayed = normalized.slice(0, limit); - const suffix = normalized.length > limit ? ` ... +${normalized.length - limit} 档` : ''; + const suffix = normalized.length > limit ? ` ... +${normalized.length - limit} tiers` : ''; return displayed.map((entry) => { const priceText = formatHeroSmsPriceForPreview(entry.price) || String(entry.price); const countText = entry.count === null ? '' : `x${entry.count}`; @@ -6647,7 +6554,7 @@ function formatPriceTiersWithZeroStockForPreview(entries = [], options = {}) { const limit = 16; const displayed = normalized.slice(0, limit); - const suffix = normalized.length > limit ? ` ... +${normalized.length - limit} 档` : ''; + const suffix = normalized.length > limit ? ` ... +${normalized.length - limit} tiers` : ''; return displayed.map((entry) => { const priceText = formatHeroSmsPriceForPreview(entry.price) || String(entry.price); const countText = entry.count === null ? '' : `x${entry.count}`; @@ -6757,8 +6664,8 @@ function updateHeroSmsPlatformDisplay() { displayHeroSmsPlatform.textContent = `${getPhoneSmsProviderLabel(provider)} / OpenAI${countryText}`; if (inputHeroSmsApiKey) { inputHeroSmsApiKey.placeholder = provider === PHONE_SMS_PROVIDER_FIVE_SIM - ? '请输入 5sim API Key' - : (provider === PHONE_SMS_PROVIDER_NEXSMS ? '请输入 NexSMS API Key' : '请输入 HeroSMS API Key'); + ? 'Enter 5sim API Key' + : (provider === PHONE_SMS_PROVIDER_NEXSMS ? 'Enter NexSMS API Key' : 'Enter HeroSMS API Key'); } } @@ -6780,7 +6687,7 @@ function renderHeroSmsCountryFallbackOrder(countries = []) { displayHeroSmsCountryFallbackOrder.textContent = ''; const normalized = isFiveSimProviderSelected() ? normalizeFiveSimCountryFallbackList(countries) : normalizeHeroSmsCountryFallbackList(countries); if (!normalized.length) { - displayHeroSmsCountryFallbackOrder.textContent = '未设置'; + displayHeroSmsCountryFallbackOrder.textContent = 'Not set'; return; } normalized.forEach((country, index) => { @@ -6792,8 +6699,8 @@ function renderHeroSmsCountryFallbackOrder(countries = []) { const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'country-order-remove'; - removeBtn.title = `移除 ${country.label}`; - removeBtn.setAttribute('aria-label', `移除 ${country.label}`); + removeBtn.title = `Remove ${country.label}`; + removeBtn.setAttribute('aria-label', `Remove ${country.label}`); removeBtn.textContent = '×'; removeBtn.addEventListener('click', (event) => { event.preventDefault(); @@ -6856,7 +6763,7 @@ function applyHeroSmsCountryMenuFilter(keyword = '') { if (!empty) { empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; - empty.textContent = '没有匹配国家'; + empty.textContent = 'No matching countries'; heroSmsCountryMenu.appendChild(empty); } } else if (empty) { @@ -6891,7 +6798,7 @@ function renderHeroSmsCountryChoiceButtons() { const searchInput = document.createElement('input'); searchInput.type = 'search'; searchInput.className = 'data-input mono hero-sms-country-menu-search-input'; - searchInput.placeholder = '搜索国家(中/英/代码/ID)'; + searchInput.placeholder = 'Search countries (name/code/ID)'; searchInput.value = heroSmsCountryMenuSearchKeyword; searchInput.addEventListener('input', () => { heroSmsCountryMenuSearchKeyword = String(searchInput.value || '').trim(); @@ -6903,7 +6810,7 @@ function renderHeroSmsCountryChoiceButtons() { if (!options.length) { const empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; - empty.textContent = '暂无国家选项'; + empty.textContent = 'No country options'; heroSmsCountryMenu.appendChild(empty); updateHeroSmsCountryMenuSummary([]); return; @@ -7000,7 +6907,7 @@ function syncHeroSmsFallbackSelectionOrderFromSelect(options = {}) { const droppedCount = nextOrder.length - selectionLimit; nextOrder = nextOrder.slice(0, selectionLimit); if (showLimitToast && droppedCount > 0 && typeof showToast === 'function') { - showToast(`接码国家最多选择 ${selectionLimit} 个,已保留前 ${selectionLimit} 个。`, 'warn', 2200); + showToast(`SMS countries are limited to ${selectionLimit}; kept the first ${selectionLimit}.`, 'warn', 2200); } } @@ -7193,7 +7100,7 @@ function renderPhoneRuntimeCountdown() { const endsAt = Number(phoneRuntimeCountdownEndsAt) || 0; const now = Date.now(); if (!hasActivation || !Number.isFinite(endsAt) || endsAt <= 0) { - displayHeroSmsCurrentCountdown.textContent = hasActivation ? '等待中' : '未启动'; + displayHeroSmsCurrentCountdown.textContent = hasActivation ? 'Waiting' : 'Not started'; stopPhoneRuntimeCountdownTicker(); return; } @@ -7256,16 +7163,16 @@ function renderPhonePreferredActivationOptions(state = {}) { label: buildPhoneActivationOptionLabel(normalized, sourceLabel), }); }; - pushActivationOption(state?.currentPhoneActivation || latestState?.currentPhoneActivation, '当前'); - pushActivationOption(state?.reusablePhoneActivation || latestState?.reusablePhoneActivation, '复用'); - reusablePool.forEach((activation) => pushActivationOption(activation, '池')); - pushActivationOption(selectedPreferred, '已选'); + pushActivationOption(state?.currentPhoneActivation || latestState?.currentPhoneActivation, 'Current'); + pushActivationOption(state?.reusablePhoneActivation || latestState?.reusablePhoneActivation, 'Reuse'); + reusablePool.forEach((activation) => pushActivationOption(activation, 'Pool')); + pushActivationOption(selectedPreferred, 'Selected'); phonePreferredActivationOptionMap.clear(); selectHeroSmsPreferredActivation.innerHTML = ''; const autoOption = document.createElement('option'); autoOption.value = ''; - autoOption.textContent = '自动(先复用已有可用号,再创建新号)'; + autoOption.textContent = 'Auto (reuse existing available number first, then create new)'; selectHeroSmsPreferredActivation.appendChild(autoOption); optionEntries.forEach((entry) => { phonePreferredActivationOptionMap.set(entry.key, entry.activation); @@ -7306,11 +7213,11 @@ function updateHeroSmsRuntimeDisplay(state = {}) { ); displayHeroSmsCurrentNumber.textContent = phoneNumber ? `${phoneNumber}${activationId ? ` (#${activationId})` : ''}${countryLabel ? ` / ${countryLabel}` : ''}` - : '未分配'; + : 'Unallocated'; } if (displayHeroSmsCurrentCode) { const code = String(state?.currentPhoneVerificationCode ?? latestState?.currentPhoneVerificationCode ?? '').trim(); - displayHeroSmsCurrentCode.textContent = code || '未获取'; + displayHeroSmsCurrentCode.textContent = code || 'Not received'; } if (displayFreeReusablePhone || displayFreeReusablePhoneCountry || inputFreeReusablePhone) { const activation = state?.freeReusablePhoneActivation ?? latestState?.freeReusablePhoneActivation ?? null; @@ -7336,12 +7243,12 @@ function updateHeroSmsRuntimeDisplay(state = {}) { if (displayFreeReusablePhone) { displayFreeReusablePhone.textContent = phoneNumber ? `${phoneNumber}${activationId ? ` (#${activationId})` : ''}${usesText}` - : '未保存'; + : 'Not saved'; } if (displayFreeReusablePhoneCountry) { displayFreeReusablePhoneCountry.textContent = phoneNumber - ? `地区:${countryLabel || '未保存'}` - : '地区:未保存'; + ? `Region: ${countryLabel || 'not saved'}` + : 'Region: not saved'; } if (inputFreeReusablePhone) { inputFreeReusablePhone.value = phoneNumber; @@ -7489,7 +7396,7 @@ async function loadHeroSmsCountries(options = {}) { .filter((entry) => entry.id) .sort((left, right) => String(left.label || '').localeCompare(String(right.label || ''))); if (!optionItems.length) { - throw new Error('国家列表为空'); + throw new Error('Country list is empty'); } heroSmsCountrySearchTextById.clear(); optionItems.forEach((entry) => heroSmsCountrySearchTextById.set(String(entry.id), entry.searchText)); @@ -7498,7 +7405,7 @@ async function loadHeroSmsCountries(options = {}) { } catch (error) { applyFiveSimFallbackOptions(); if (!silent && typeof showToast === 'function') { - showToast(`5sim 国家列表加载失败:${normalizeHeroSmsFetchErrorMessage(error)}(已切换为内置国家列表)`, 'warn', 2800); + showToast(`5sim country list load failed: ${normalizeHeroSmsFetchErrorMessage(error)} (switched to built-in country list)`, 'warn', 2800); } } } else if (preferFallbackOnly) { @@ -7514,7 +7421,7 @@ async function loadHeroSmsCountries(options = {}) { const payload = await response.json(); const countries = parseHeroSmsCountryPayload(payload); if (!countries.length) { - throw new Error('国家列表为空'); + throw new Error('Country list is empty'); } const optionItems = countries .filter((item) => Number(item?.id) > 0 && (String(item?.eng || '').trim() || String(item?.chn || '').trim())) @@ -7530,7 +7437,7 @@ async function loadHeroSmsCountries(options = {}) { }); if (!optionItems.length) { - throw new Error('国家列表为空'); + throw new Error('Country list is empty'); } heroSmsCountrySearchTextById.clear(); @@ -7543,7 +7450,7 @@ async function loadHeroSmsCountries(options = {}) { } catch (error) { applyHeroSmsFallbackOptions(); if (!silent && typeof showToast === 'function') { - showToast(`国家列表加载失败:${normalizeHeroSmsFetchErrorMessage(error)}(已切换为内置国家列表)`, 'warn', 2800); + showToast(`Country list load failed: ${normalizeHeroSmsFetchErrorMessage(error)} (switched to built-in country list)`, 'warn', 2800); } } const availableIds = new Set(Array.from(countrySelect.options).map((option) => String(option.value))); @@ -7581,7 +7488,7 @@ function renderFiveSimCountryFallbackOrder(countries = []) { const normalized = normalizeFiveSimCountryFallbackList(countries); displayFiveSimCountryFallbackOrder.textContent = ''; if (!normalized.length) { - displayFiveSimCountryFallbackOrder.textContent = '未设置'; + displayFiveSimCountryFallbackOrder.textContent = 'Not set'; return; } normalized.forEach((country, index) => { @@ -7593,8 +7500,8 @@ function renderFiveSimCountryFallbackOrder(countries = []) { const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'country-order-remove'; - removeBtn.title = `移除 ${country.label}`; - removeBtn.setAttribute('aria-label', `移除 ${country.label}`); + removeBtn.title = `Remove ${country.label}`; + removeBtn.setAttribute('aria-label', `Remove ${country.label}`); removeBtn.textContent = '×'; removeBtn.addEventListener('click', (event) => { event.preventDefault(); @@ -7656,7 +7563,7 @@ function applyFiveSimCountryMenuFilter(keyword = '') { if (!empty) { empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; - empty.textContent = '没有匹配国家'; + empty.textContent = 'No matching countries'; fiveSimCountryMenu.appendChild(empty); } } else if (empty) { @@ -7670,7 +7577,7 @@ function updateFiveSimCountryMenuSummary(selectedCountries = []) { } const normalized = normalizeFiveSimCountryFallbackList(selectedCountries); if (!normalized.length) { - btnFiveSimCountryMenu.textContent = `未选择 (0/${HERO_SMS_COUNTRY_SELECTION_MAX})`; + btnFiveSimCountryMenu.textContent = `None selected (0/${HERO_SMS_COUNTRY_SELECTION_MAX})`; return; } const labels = normalized.map((country) => country.label); @@ -7691,7 +7598,7 @@ function renderFiveSimCountryChoiceButtons() { const searchInput = document.createElement('input'); searchInput.type = 'search'; searchInput.className = 'data-input mono hero-sms-country-menu-search-input'; - searchInput.placeholder = '搜索国家(中/英/代码)'; + searchInput.placeholder = 'Search countries (name/code)'; searchInput.value = fiveSimCountryMenuSearchKeyword; searchInput.addEventListener('input', () => { fiveSimCountryMenuSearchKeyword = String(searchInput.value || '').trim(); @@ -7703,7 +7610,7 @@ function renderFiveSimCountryChoiceButtons() { if (!options.length) { const empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; - empty.textContent = '暂无国家选项'; + empty.textContent = 'No country options'; fiveSimCountryMenu.appendChild(empty); updateFiveSimCountryMenuSummary([]); return; @@ -7764,7 +7671,7 @@ function syncFiveSimCountrySelectionOrderFromSelect(options = {}) { const droppedCount = nextOrder.length - selectionLimit; nextOrder = nextOrder.slice(0, selectionLimit); if (showLimitToast && droppedCount > 0 && typeof showToast === 'function') { - showToast(`5sim 国家最多选择 ${selectionLimit} 个,已保留前 ${selectionLimit} 个。`, 'warn', 2200); + showToast(`5sim countries are limited to ${selectionLimit}; kept the first ${selectionLimit}.`, 'warn', 2200); } } @@ -7892,7 +7799,7 @@ async function loadFiveSimCountries(options = {}) { } catch (error) { applyOptions(fallbackItems); if (!silent && typeof showToast === 'function') { - showToast(`5sim 国家列表加载失败:${normalizeHeroSmsFetchErrorMessage(error)}(已切换为内置国家列表)`, 'warn', 2800); + showToast(`5sim country list failed to load: ${normalizeHeroSmsFetchErrorMessage(error)} (switched to the built-in country list)`, 'warn', 2800); } } @@ -7908,7 +7815,7 @@ function renderNexSmsCountryFallbackOrder(countries = []) { const normalized = normalizeNexSmsCountryFallbackList(countries); displayNexSmsCountryFallbackOrder.textContent = ''; if (!normalized.length) { - displayNexSmsCountryFallbackOrder.textContent = '未设置'; + displayNexSmsCountryFallbackOrder.textContent = 'Not set'; return; } normalized.forEach((country, index) => { @@ -7920,8 +7827,8 @@ function renderNexSmsCountryFallbackOrder(countries = []) { const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'country-order-remove'; - removeBtn.title = `移除 ${country.label}`; - removeBtn.setAttribute('aria-label', `移除 ${country.label}`); + removeBtn.title = `Remove ${country.label}`; + removeBtn.setAttribute('aria-label', `Remove ${country.label}`); removeBtn.textContent = '×'; removeBtn.addEventListener('click', (event) => { event.preventDefault(); @@ -7982,7 +7889,7 @@ function applyNexSmsCountryMenuFilter(keyword = '') { if (!empty) { empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; - empty.textContent = '没有匹配国家'; + empty.textContent = 'No matching countries'; nexSmsCountryMenu.appendChild(empty); } } else if (empty) { @@ -7997,7 +7904,7 @@ function updateNexSmsCountryMenuSummary(selectedCountries = []) { const normalized = normalizeNexSmsCountryFallbackList(selectedCountries); btnNexSmsCountryMenu.textContent = normalized.length ? `${normalized.map((country) => country.label).join(' / ')} (${normalized.length}/${HERO_SMS_COUNTRY_SELECTION_MAX})` - : `未选择 (0/${HERO_SMS_COUNTRY_SELECTION_MAX})`; + : `None selected (0/${HERO_SMS_COUNTRY_SELECTION_MAX})`; } function renderNexSmsCountryChoiceButtons() { @@ -8013,7 +7920,7 @@ function renderNexSmsCountryChoiceButtons() { const searchInput = document.createElement('input'); searchInput.type = 'search'; searchInput.className = 'data-input mono hero-sms-country-menu-search-input'; - searchInput.placeholder = '搜索国家 ID'; + searchInput.placeholder = 'Search country ID'; searchInput.value = nexSmsCountryMenuSearchKeyword; searchInput.addEventListener('input', () => { nexSmsCountryMenuSearchKeyword = String(searchInput.value || '').trim(); @@ -8025,7 +7932,7 @@ function renderNexSmsCountryChoiceButtons() { if (!options.length) { const empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; - empty.textContent = '暂无国家选项'; + empty.textContent = 'No country options'; nexSmsCountryMenu.appendChild(empty); updateNexSmsCountryMenuSummary([]); return; @@ -8086,7 +7993,7 @@ function syncNexSmsCountrySelectionOrderFromSelect(options = {}) { const droppedCount = nextOrder.length - selectionLimit; nextOrder = nextOrder.slice(0, selectionLimit); if (showLimitToast && droppedCount > 0 && typeof showToast === 'function') { - showToast(`NexSMS 国家最多选择 ${selectionLimit} 个,已保留前 ${selectionLimit} 个。`, 'warn', 2200); + showToast(`NexSMS countries are limited to ${selectionLimit}; kept the first ${selectionLimit}.`, 'warn', 2200); } } @@ -8208,13 +8115,13 @@ async function buildNexSmsPricePreviewLines(options = {}) { const providerLabel = String(options?.providerLabel || 'NexSMS').trim(); if (!apiKey) { - return [`${providerLabel}: 请先填写 NexSMS API Key`]; + return [`${providerLabel}: Please fill in NexSMS API Key first`]; } if (priceRange.invalid) { return [`${providerLabel}: ${buildPhoneSmsPriceRangePreviewMessage(priceRange)}`]; } if (!countryIds.length) { - return [`${providerLabel}: 请先选择至少 1 个国家`]; + return [`${providerLabel}: Please select at least 1 country`]; } const previews = []; @@ -8266,15 +8173,15 @@ async function buildNexSmsPricePreviewLines(options = {}) { continue; } const tierText = formatPriceTiersForPreview(filteredTierEntries, { maxPrice }); - const lowestLabel = priceRange.hasMinPrice || priceRange.hasMaxPrice ? '区间内最低' : '最低'; - previews.push(`${countryLabel}: ${lowestLabel} ${lowest}${tierText ? `;档位:${tierText}` : ''}`); + const lowestLabel = priceRange.hasMinPrice || priceRange.hasMaxPrice ? 'In-range lowest' : 'Lowest'; + previews.push(`${countryLabel}: ${lowestLabel} ${lowest}${tierText ? `; tiers: ${tierText}` : ''}`); } catch (error) { - previews.push(`${countryLabel}: 查询失败(${normalizeHeroSmsFetchErrorMessage(error)})`); + previews.push(`${countryLabel}: Query failed (${normalizeHeroSmsFetchErrorMessage(error)})`); } } if (!previews.length) { - previews.push('未获取'); + previews.push('Not retrieved'); } return [`${providerLabel}:`, ...previews]; } @@ -8300,7 +8207,7 @@ async function previewFiveSimPriceTiers() { ); const maxPrice = priceRange.maxPrice; - displayHeroSmsPriceTiers.textContent = '查询中...'; + displayHeroSmsPriceTiers.textContent = 'Querying...'; if (rowHeroSmsPriceTiers) { rowHeroSmsPriceTiers.style.display = ''; } @@ -8309,7 +8216,7 @@ async function previewFiveSimPriceTiers() { return; } if (!countryCodes.length) { - displayHeroSmsPriceTiers.textContent = '请先选择至少 1 个国家,再查询价格'; + displayHeroSmsPriceTiers.textContent = 'Please select at least 1 country, then query price'; return; } @@ -8385,13 +8292,13 @@ async function previewFiveSimPriceTiers() { } const lowest = rangePrices[0]; const tierText = formatPriceTiersForPreview(filteredTierEntries, { maxPrice }); - const lowestLabel = priceRange.hasMinPrice || priceRange.hasMaxPrice ? '区间内最低' : '最低'; - previews.push(`${countryLabel}: ${lowestLabel} ${lowest}${tierText ? `;档位:${tierText}` : ''}`); + const lowestLabel = priceRange.hasMinPrice || priceRange.hasMaxPrice ? 'In-range lowest' : 'Lowest'; + previews.push(`${countryLabel}: ${lowestLabel} ${lowest}${tierText ? `; tiers: ${tierText}` : ''}`); } catch (error) { - previews.push(`${countryLabel}: 查询失败(${normalizeHeroSmsFetchErrorMessage(error)})`); + previews.push(`${countryLabel}: Query failed (${normalizeHeroSmsFetchErrorMessage(error)})`); } } - displayHeroSmsPriceTiers.textContent = previews.join('\n') || '未获取'; + displayHeroSmsPriceTiers.textContent = previews.join('\n') || 'Not retrieved'; } async function buildFiveSimPricePreviewLines(options = {}) { @@ -8414,7 +8321,7 @@ async function buildFiveSimPricePreviewLines(options = {}) { const providerLabel = String(options?.providerLabel || '5sim').trim(); if (!countryCodes.length) { - return [`${providerLabel}: 请先选择至少 1 个国家`]; + return [`${providerLabel}: Please select at least 1 country`]; } if (priceRange.invalid) { return [`${providerLabel}: ${buildPhoneSmsPriceRangePreviewMessage(priceRange)}`]; @@ -8492,15 +8399,15 @@ async function buildFiveSimPricePreviewLines(options = {}) { } const lowest = rangePrices[0]; const tierText = formatPriceTiersForPreview(filteredTierEntries, { maxPrice }); - const lowestLabel = priceRange.hasMinPrice || priceRange.hasMaxPrice ? '区间内最低' : '最低'; - previews.push(`${countryLabel}: ${lowestLabel} ${lowest}${tierText ? `;档位:${tierText}` : ''}`); + const lowestLabel = priceRange.hasMinPrice || priceRange.hasMaxPrice ? 'In-range lowest' : 'Lowest'; + previews.push(`${countryLabel}: ${lowestLabel} ${lowest}${tierText ? `; tiers: ${tierText}` : ''}`); } catch (error) { - previews.push(`${countryLabel}: 查询失败(${normalizeHeroSmsFetchErrorMessage(error)})`); + previews.push(`${countryLabel}: Query failed (${normalizeHeroSmsFetchErrorMessage(error)})`); } } if (!previews.length) { - previews.push('未获取'); + previews.push('Not retrieved'); } return [`${providerLabel}:`, ...previews]; } @@ -8521,7 +8428,7 @@ async function previewHeroSmsPriceTiers() { const activeProvider = typeof getSelectedPhoneSmsProvider === 'function' ? getSelectedPhoneSmsProvider() : normalizeProvider(selectPhoneSmsProvider?.value || defaultProviderValue); - displayHeroSmsPriceTiers.textContent = '查询中...'; + displayHeroSmsPriceTiers.textContent = 'Querying...'; if (rowHeroSmsPriceTiers) { rowHeroSmsPriceTiers.style.display = ''; } @@ -8571,7 +8478,7 @@ async function previewHeroSmsPriceTiers() { const heroLines = ['HeroSMS:']; if (!apiKey) { - heroLines.push('请先填写接码 API Key'); + heroLines.push('Please fill in SMS API Key first'); previews.push(...heroLines, ''); continue; } @@ -8581,7 +8488,7 @@ async function previewHeroSmsPriceTiers() { continue; } if (!candidates.length) { - heroLines.push('请先选择至少 1 个国家'); + heroLines.push('Please select at least 1 country'); previews.push(...heroLines, ''); continue; } @@ -8716,26 +8623,26 @@ async function previewHeroSmsPriceTiers() { if (rangeAllPrices.length || allPrices.length) { const lowestKnownPrice = rangeAllPrices.length ? rangeAllPrices[0] : allPrices[0]; const lowestKnown = formatHeroSmsPriceForPreview(lowestKnownPrice) || String(lowestKnownPrice); - heroLines.push(`${countryLabel}: 全档位均无库存(最低标价 ${lowestKnown})${tierPreviewText ? `;档位:${tierPreviewText}` : ''}`); + heroLines.push(`${countryLabel}: All tiers out of stock (lowest listed ${lowestKnown})${tierPreviewText ? `; tiers: ${tierPreviewText}` : ''}`); continue; } if (failedPriceActions.length) { heroLines.push(`${countryLabel}: ${failedPriceActions.join(' | ')}`); continue; } - heroLines.push(`${countryLabel}: 无可用价格`); + heroLines.push(`${countryLabel}: No available prices`); continue; } const lowestWithinRange = rangeInStockPrices[0]; const lowestText = formatHeroSmsPriceForPreview(lowestWithinRange) || String(lowestWithinRange); - const lowestLabel = priceRange.hasMinPrice || priceRange.hasMaxPrice ? '区间内最低' : '最低'; - heroLines.push(`${countryLabel}: ${lowestLabel} ${lowestText}${tierPreviewText ? `;档位:${tierPreviewText}` : ''}`); + const lowestLabel = priceRange.hasMinPrice || priceRange.hasMaxPrice ? 'In-range lowest' : 'Lowest'; + heroLines.push(`${countryLabel}: ${lowestLabel} ${lowestText}${tierPreviewText ? `; tiers: ${tierPreviewText}` : ''}`); } catch (error) { - heroLines.push(`${countryLabel}: 查询失败(${normalizeHeroSmsFetchErrorMessage(error)})`); + heroLines.push(`${countryLabel}: Query failed (${normalizeHeroSmsFetchErrorMessage(error)})`); } } if (heroLines.length === 1) { - heroLines.push('未获取'); + heroLines.push('Not retrieved'); } previews.push(...heroLines, ''); } @@ -8743,7 +8650,7 @@ async function previewHeroSmsPriceTiers() { while (previews.length && previews[previews.length - 1] === '') { previews.pop(); } - displayHeroSmsPriceTiers.textContent = previews.join('\n') || '未获取'; + displayHeroSmsPriceTiers.textContent = previews.join('\n') || 'Not retrieved'; } async function previewPhoneSmsBalance() { @@ -8753,11 +8660,11 @@ async function previewPhoneSmsBalance() { const provider = getSelectedPhoneSmsProvider(); const apiKey = String(inputHeroSmsApiKey?.value || '').trim(); if (!apiKey) { - displayPhoneSmsBalance.textContent = '请先填写接码 API Key'; + displayPhoneSmsBalance.textContent = 'Please fill in SMS API Key first'; if (rowHeroSmsPriceTiers) rowHeroSmsPriceTiers.style.display = ''; return; } - displayPhoneSmsBalance.textContent = '余额查询中...'; + displayPhoneSmsBalance.textContent = 'Querying balance...'; if (rowHeroSmsPriceTiers) rowHeroSmsPriceTiers.style.display = ''; try { const url = provider === PHONE_SMS_PROVIDER_FIVE_SIM @@ -8782,21 +8689,21 @@ async function previewPhoneSmsBalance() { payload = rawText; } if (!response.ok) { - displayPhoneSmsBalance.textContent = `余额查询失败:${summarizeHeroSmsPreviewError(payload, response.status)}`; + displayPhoneSmsBalance.textContent = `Balance query failed: ${summarizeHeroSmsPreviewError(payload, response.status)}`; return; } if (provider === PHONE_SMS_PROVIDER_FIVE_SIM) { const balance = Number(payload?.balance); const frozen = Number(payload?.frozen_balance); displayPhoneSmsBalance.textContent = Number.isFinite(balance) - ? `5sim 余额 ${formatHeroSmsPriceForPreview(balance) || balance}${Number.isFinite(frozen) ? `,冻结 ${formatHeroSmsPriceForPreview(frozen) || frozen}` : ''}` - : `5sim 余额:${describeHeroSmsPreviewPayload(payload) || '未知'}`; + ? `5sim balance ${formatHeroSmsPriceForPreview(balance) || balance}${Number.isFinite(frozen) ? `, frozen ${formatHeroSmsPriceForPreview(frozen) || frozen}` : ''}` + : `5sim balance: ${describeHeroSmsPreviewPayload(payload) || 'unknown'}`; } else { const text = describeHeroSmsPreviewPayload(payload).replace(/^ACCESS_BALANCE:/i, '').trim(); - displayPhoneSmsBalance.textContent = `HeroSMS 余额 ${text || '未知'}`; + displayPhoneSmsBalance.textContent = `HeroSMS balance ${text || 'unknown'}`; } } catch (error) { - displayPhoneSmsBalance.textContent = `余额查询失败:${normalizeHeroSmsFetchErrorMessage(error)}`; + displayPhoneSmsBalance.textContent = `Balance query failed: ${normalizeHeroSmsFetchErrorMessage(error)}`; } } @@ -9081,7 +8988,7 @@ function renderTargetSelectorOptions(flowId = getSelectedFlowId(), selectedTarge selectPanelMode.appendChild(option); }); if (labelSourceSelector) { - labelSourceSelector.textContent = '来源'; + labelSourceSelector.textContent = 'Source'; } selectPanelMode.disabled = targetOptions.length <= 1; if (targetOptions.length > 0) { @@ -9346,7 +9253,7 @@ function updateSignupMethodUI(options = {}) { if (!phoneSelectable && selectedMethod === SIGNUP_METHOD_PHONE) { selectedMethod = setSignupMethod(SIGNUP_METHOD_EMAIL); if (options.notify && typeof showToast === 'function') { - showToast('已切回邮箱注册', 'info', 1600); + showToast('Switched back to email signup', 'info', 1600); } } else { setSignupMethod(selectedMethod); @@ -9360,13 +9267,13 @@ function updateSignupMethodUI(options = {}) { button.setAttribute('aria-disabled', String(disabled)); if (method === SIGNUP_METHOD_PHONE) { if (!Boolean(inputPhoneVerificationEnabled?.checked)) { - button.title = '开启接码后可选择手机号注册'; + button.title = 'Enable SMS verification to select phone signup'; } else if (typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled?.checked) { - button.title = 'Plus 模式暂不支持手机号注册'; + button.title = 'Plus mode does not support phone signup'; } else if (accountContributionEnabled) { - button.title = '账号贡献开启时不能使用手机号注册'; + button.title = 'Phone signup is not allowed when account contribution is enabled'; } else if (locked) { - button.title = '自动流程运行中不能切换注册方式'; + button.title = 'Cannot switch signup method while auto flow is running'; } else { button.title = ''; } @@ -9476,10 +9383,10 @@ function updatePhoneVerificationSettingsUI() { updateSignupMethodUI(); if (btnTogglePhoneVerificationSection) { btnTogglePhoneVerificationSection.disabled = !enabled; - btnTogglePhoneVerificationSection.textContent = showSettings ? '收起设置' : '展开设置'; + btnTogglePhoneVerificationSection.textContent = showSettings ? 'Collapse Settings' : 'Expand Settings'; btnTogglePhoneVerificationSection.title = enabled - ? (showSettings ? '收起接码设置' : '展开接码设置') - : '开启接码后可展开设置'; + ? (showSettings ? 'Collapse SMS settings' : 'Expand SMS settings') + : 'Enable SMS verification to expand settings'; btnTogglePhoneVerificationSection.setAttribute('aria-expanded', String(showSettings)); } if (rowPhoneVerificationFold) { @@ -9683,18 +9590,18 @@ function updatePlusModeUI() { const normalizedStrategy = normalizePlusAccountAccessStrategy(strategy); const normalizedTargetId = resolveStrategyTargetId(targetId); if (normalizedStrategy === sub2apiSessionStrategyValue) { - return '复用当前 Plus 已登录会话,直接导入到 SUB2API'; + return 'Reuse current Plus session, import directly to SUB2API'; } if (normalizedStrategy === cpaSessionStrategyValue) { - return '复用当前 Plus 已登录会话,直接导入到 CPA'; + return 'Reuse current Plus session, import directly to CPA'; } if (normalizedTargetId === 'sub2api') { - return '通过 OAuth 回调创建 SUB2API 账号'; + return 'Create SUB2API account via OAuth callback'; } if (normalizedTargetId === 'codex2api') { - return '通过 OAuth 回调创建 Codex2API 账号'; + return 'Create Codex2API account via OAuth callback'; } - return '通过 OAuth 回调创建 CPA 账号'; + return 'Create CPA account via OAuth callback'; }); const normalizeStrategyUiValue = typeof normalizePlusAccountAccessStrategyUiValue === 'function' ? normalizePlusAccountAccessStrategyUiValue @@ -9800,17 +9707,17 @@ function updatePlusModeUI() { } if (typeof plusPaymentMethodCaption !== 'undefined' && plusPaymentMethodCaption) { plusPaymentMethodCaption.textContent = method === gpcValue - ? `GPC ${isGpcAutoMode ? '自动' : '手动'}订阅链路` + ? `GPC ${isGpcAutoMode ? 'auto' : 'manual'} subscription path` : method === gopayValue - ? 'GoPay 印尼订阅链路' + ? 'GoPay Indonesia subscription path' : method === noneValue - ? '已有 Plus,无需配置支付链路' + ? 'Already has Plus, no payment path needed' : method === paypalHostedValue - ? 'PayPal 无卡直绑链路' - : 'PayPal 订阅链路'; + ? 'PayPal cardless direct bind path' + : 'PayPal subscription path'; } if (typeof plusPaymentMethodCaption !== 'undefined' && plusPaymentMethodCaption && method === gpcValue && gpcAutoModeBlocked) { - plusPaymentMethodCaption.textContent = 'GPC 自动订阅链路(需手动切换)'; + plusPaymentMethodCaption.textContent = 'GPC auto subscription path (manual switch required)'; } [ typeof rowPlusPaymentMethod !== 'undefined' ? rowPlusPaymentMethod : null, @@ -9845,20 +9752,20 @@ function updatePlusModeUI() { } if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) { if (!enabled) { - plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth'; + plusAccountAccessStrategyCaption.textContent = 'Current source only supports OAuth'; } else if (!canEditPlusAccountAccessStrategy) { - plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth'; + plusAccountAccessStrategyCaption.textContent = 'Current source only supports OAuth'; } else if (effectivePlusAccountAccessStrategy === sub2apiSessionStrategyValue) { - plusAccountAccessStrategyCaption.textContent = '复用当前 Plus 已登录会话,直接导入到 SUB2API'; + plusAccountAccessStrategyCaption.textContent = 'Reuse current Plus session, import directly to SUB2API'; } else if (effectivePlusAccountAccessStrategy === oauthStrategyValue) { - plusAccountAccessStrategyCaption.textContent = '通过 OAuth 回调创建 SUB2API 账号'; + plusAccountAccessStrategyCaption.textContent = 'Create SUB2API account via OAuth callback'; } else { - plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth'; + plusAccountAccessStrategyCaption.textContent = 'Current source only supports OAuth'; } } if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) { if (!enabled || !canEditPlusAccountAccessStrategy) { - plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth'; + plusAccountAccessStrategyCaption.textContent = 'Current source only supports OAuth'; } else { plusAccountAccessStrategyCaption.textContent = describePlusAccountAccessStrategy( effectivePlusAccountAccessStrategy, @@ -9868,13 +9775,13 @@ function updatePlusModeUI() { } if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) { plusAccountAccessStrategyCaption.textContent = !enabled - ? '当前来源仅支持 OAuth' + ? 'Current source only supports OAuth' : ((effectivePlusAccountAccessStrategy !== oauthStrategyValue || canEditPlusAccountAccessStrategy) ? describePlusAccountAccessStrategy( effectivePlusAccountAccessStrategy, effectiveTargetId ) - : '当前来源仅支持 OAuth'); + : 'Current source only supports OAuth'); } if (enabled && effectivePlusAccountAccessStrategy === sub2apiSessionStrategyValue) { [ @@ -10173,7 +10080,7 @@ async function persistSignupPhoneInputValue(options = {}) { }); syncSignupPhoneInputFromState(latestState); if (!silent) { - showToast(normalizedPhone ? '注册手机号已保存。' : '注册手机号已清空。', 'success', 1600); + showToast(normalizedPhone ? 'Signup phone saved.' : 'Signup phone cleared.', 'success', 1600); } return normalizedPhone; })(); @@ -10215,9 +10122,9 @@ function getSelectedGpcHelperPhoneMode() { async function showGpcStartBlockedDialog(message) { await openConfirmModal({ - title: 'GPC 任务无法开启', + title: 'Cannot start GPC task', message, - confirmLabel: '知道了', + confirmLabel: 'OK', }); } @@ -10260,28 +10167,28 @@ async function ensureGpcApiKeyReadyForStart(options = {}) { try { balanceState = await refreshGpcBalanceForStart(); } catch (error) { - await showGpcStartBlockedDialog(`API Key 余额校验失败:${error?.message || '未知错误'}。请先确认 API Key 是否正确。`); + await showGpcStartBlockedDialog(`API Key balance check failed: ${error?.message || 'Unknown error'}. Please verify the API Key is correct.`); return false; } const remainingUses = normalizeGpcRemainingUsesValue(balanceState.gopayHelperRemainingUses); const apiKeyStatus = String(balanceState.gopayHelperApiKeyStatus || '').trim().toLowerCase(); if (apiKeyStatus && apiKeyStatus !== 'active') { - await showGpcStartBlockedDialog(`当前 GPC API Key 状态为 ${balanceState.gopayHelperApiKeyStatus},不能开启任务。`); + await showGpcStartBlockedDialog(`Current GPC API Key status is ${balanceState.gopayHelperApiKeyStatus}, cannot start task.`); return false; } if (remainingUses !== null && remainingUses <= 0) { - await showGpcStartBlockedDialog('当前 GPC API Key 剩余次数不足,不能开启任务。'); + await showGpcStartBlockedDialog('Current GPC API Key has insufficient remaining uses, cannot start task.'); return false; } if (selectedMode === GPC_HELPER_PHONE_MODE_AUTO && isGpcAutoModePermissionDenied(balanceState)) { - await showGpcStartBlockedDialog('当前 GPC API Key 未开通自动模式,已保留你的当前选择。如需继续,请由你手动切换到手动模式后再开启任务。'); + await showGpcStartBlockedDialog('Current GPC API Key does not have auto mode enabled; your current selection has been kept. To continue, please manually switch to manual mode before starting the task.'); return false; } if (options?.notify) { - showToast('GPC API Key 余额和权限校验通过。', 'success', 1800); + showToast('GPC API Key balance and permission check passed.', 'success', 1800); } return true; } @@ -10331,52 +10238,52 @@ async function openPlusManualConfirmationDialog(options = {}) { && signupMethod === 'email' && plusAccountAccessStrategy === 'sub2api_codex_session'; const continuationActionLabel = useSub2ApiSessionImport - ? '导入当前 ChatGPT 会话到 SUB2API' - : 'OAuth 登录'; + ? 'Import current ChatGPT session to SUB2API' + : 'OAuth login'; if (method === 'gopay-otp') { if (!sharedFormDialog?.open) { return null; } const result = await sharedFormDialog.open({ - title: String(options.title || '').trim() || 'GPC OTP 验证', - message: String(options.message || '').trim() || '请在WhatsApp里面获取验证码(耐心等待三十秒左右)', + title: String(options.title || '').trim() || 'GPC OTP Verification', + message: String(options.message || '').trim() || 'Please get the verification code from WhatsApp (wait about thirty seconds)', fields: [ { key: 'otp', label: 'OTP', type: 'text', - placeholder: '请输入 OTP 验证码', + placeholder: 'Enter OTP code', inputMode: 'numeric', autocomplete: 'one-time-code', required: true, - requiredMessage: '请输入 OTP 验证码。', + requiredMessage: 'Please enter OTP code.', normalize: (value) => String(value || '').trim().replace(/[^\d]/g, ''), validate: (value) => { const normalized = String(value || '').trim().replace(/[^\d]/g, ''); - if (!normalized) return '请输入 OTP 验证码。'; - if (!/^\d{6}$/.test(normalized)) return 'OTP 必须是 6 位数字,请检查。'; + if (!normalized) return 'Please enter OTP code.'; + if (!/^\d{6}$/.test(normalized)) return 'OTP must be 6 digits, please check.'; return ''; }, }, ], - confirmLabel: '提交 OTP', + confirmLabel: 'Submit OTP', }); return result ? { action: 'confirm', otp: String(result.otp || '').trim().replace(/[^\d]/g, '') } : { action: 'cancel' }; } - const title = String(options.title || '').trim() || (method === gopayValue ? 'GoPay 订阅确认' : '手动确认'); + const title = String(options.title || '').trim() || (method === gopayValue ? 'GoPay Subscription Confirm' : 'Manual Confirmation'); const message = String(options.message || '').trim() || (method === gopayValue - ? '请在当前订阅页中手动完成 GoPay 订阅,完成后点击“我已完成订阅”继续。' - : '请先在页面中完成当前手动操作,完成后点击确认继续。'); + ? 'Please manually complete the GoPay subscription on the current page, then click "I\'ve completed subscription" to continue.' + : 'Please complete the current manual operation on the page first, then click Confirm to continue.'); return openActionModal({ title, message, actions: [ - { id: 'cancel', label: '取消等待', variant: 'btn-ghost' }, - { id: 'confirm', label: '我已完成订阅', variant: 'btn-primary' }, + { id: 'cancel', label: 'Cancel Wait', variant: 'btn-ghost' }, + { id: 'confirm', label: 'I\'ve completed subscription', variant: 'btn-primary' }, ], alert: method === gopayValue - ? { text: `确认后流程会直接继续到 Plus 模式后续的${continuationActionLabel}。`, tone: 'info' } + ? { text: `After confirmation, the flow will continue directly to the Plus mode ${continuationActionLabel}.`, tone: 'info' } : null, }); } @@ -10433,8 +10340,8 @@ async function syncPlusManualConfirmationDialog() { && signupMethod === 'email' && plusAccountAccessStrategy === 'sub2api_codex_session'; const continuationActionLabel = useSub2ApiSessionImport - ? '导入当前 ChatGPT 会话到 SUB2API' - : 'OAuth 登录'; + ? 'Import current ChatGPT session to SUB2API' + : 'OAuth login'; const title = latestState?.plusManualConfirmationTitle; const message = latestState?.plusManualConfirmationMessage; activePlusManualConfirmationRequestId = requestId; @@ -10454,7 +10361,7 @@ async function syncPlusManualConfirmationDialog() { } if (choice == null) { shouldReopenDialog = true; - showToast('当前订阅确认仍在等待中,将重新弹出确认窗口。', 'info', 1800); + showToast('Current subscription confirmation is still pending; the confirmation window will be reopened.', 'info', 1800); return; } @@ -10475,22 +10382,22 @@ async function syncPlusManualConfirmationDialog() { if (confirmed) { showToast( method === 'gopay-otp' - ? 'GPC OTP 已提交,正在继续验证...' - : (method === gopayValue ? `GoPay 订阅已确认,正在继续${continuationActionLabel}...` : '已确认,流程继续执行中...'), + ? 'GPC OTP submitted, continuing verification...' + : (method === gopayValue ? `GoPay subscription confirmed, continuing ${continuationActionLabel}...` : 'Confirmed, flow continuing...'), 'info', 2200 ); } else { showToast( method === 'gopay-otp' - ? '已取消 GPC OTP 输入。' - : (method === gopayValue ? '已取消 GoPay 订阅等待。' : '已取消当前手动确认。'), + ? 'Canceled GPC OTP input.' + : (method === gopayValue ? 'Canceled GoPay subscription wait.' : 'Canceled current manual confirmation.'), 'warn', 2200 ); } } catch (error) { - showToast(error?.message || String(error || '未知错误'), 'error'); + showToast(error?.message || String(error || 'Unknown error'), 'error'); } finally { if (activePlusManualConfirmationRequestId === requestId) { activePlusManualConfirmationRequestId = ''; @@ -10516,28 +10423,28 @@ async function openPlusManualConfirmationDialog(options = {}) { return null; } const result = await sharedFormDialog.open({ - title: String(options.title || '').trim() || 'GPC OTP 验证', - message: String(options.message || '').trim() || '请在WhatsApp里面获取验证码(耐心等待三十秒左右)', + title: String(options.title || '').trim() || 'GPC OTP Verification', + message: String(options.message || '').trim() || 'Please get the verification code from WhatsApp (wait about thirty seconds)', fields: [ { key: 'otp', label: 'OTP', type: 'text', - placeholder: '请输入 OTP 验证码', + placeholder: 'Enter OTP code', inputMode: 'numeric', autocomplete: 'one-time-code', required: true, - requiredMessage: '请输入 OTP 验证码。', + requiredMessage: 'Please enter OTP code.', normalize: (value) => String(value || '').trim().replace(/[^\d]/g, ''), validate: (value) => { const normalized = String(value || '').trim().replace(/[^\d]/g, ''); - if (!normalized) return '请输入 OTP 验证码。'; - if (!/^\d{6}$/.test(normalized)) return 'OTP 必须是 6 位数字,请检查。'; + if (!normalized) return 'Please enter OTP code.'; + if (!/^\d{6}$/.test(normalized)) return 'OTP must be 6 digits, please check.'; return ''; }, }, ], - confirmLabel: '提交 OTP', + confirmLabel: 'Submit OTP', }); return result ? { action: 'confirm', otp: String(result.otp || '').trim().replace(/[^\d]/g, '') } @@ -10575,7 +10482,7 @@ async function clearRegistrationEmail(options = {}) { await setRuntimeEmailState(null); } catch (err) { if (!silent) { - showToast(`清空邮箱失败:${err.message}`, 'error'); + showToast(`Failed to clear email: ${err.message}`, 'error'); } throw err; } @@ -10611,7 +10518,7 @@ async function clearRegistrationSignupPhone(options = {}) { await setRuntimeSignupPhoneState(null); } catch (err) { if (!silent) { - showToast(`清空注册手机号失败:${err.message}`, 'error'); + showToast(`Failed to clear signup phone: ${err.message}`, 'error'); } throw err; } @@ -10628,7 +10535,7 @@ function markSettingsDirty(isDirty = true) { function updateSaveButtonState() { btnSaveSettings.disabled = settingsSaveInFlight || !settingsDirty; updateConfigMenuControls(); - btnSaveSettings.textContent = settingsSaveInFlight ? '保存中' : '保存'; + btnSaveSettings.textContent = settingsSaveInFlight ? 'Saving' : 'Save'; } function isEditableElementInSettingsCard(element) { @@ -10703,12 +10610,12 @@ async function saveSettings(options = {}) { updateButtonStates(); } if (!silent) { - showToast('配置已保存', 'success', 1800); + showToast('Config saved', 'success', 1800); } } catch (err) { markSettingsDirty(true); if (!silent) { - showToast(`保存失败:${err.message}`, 'error'); + showToast(`Save failed: ${err.message}`, 'error'); } throw err; } finally { @@ -10770,23 +10677,23 @@ function applyAutoRunStatus(payload = currentAutoRun) { switch (currentAutoRun.phase) { case 'waiting_step': autoContinueBar.style.display = 'none'; - btnAutoRun.innerHTML = `等待中${runLabel}`; + btnAutoRun.innerHTML = `Waiting${runLabel}`; break; case 'waiting_email': autoContinueBar.style.display = 'flex'; - btnAutoRun.innerHTML = `已暂停${runLabel}`; + btnAutoRun.innerHTML = `Paused${runLabel}`; break; case 'running': autoContinueBar.style.display = 'none'; - btnAutoRun.innerHTML = `运行中${runLabel}`; + btnAutoRun.innerHTML = `Running${runLabel}`; break; case 'retrying': autoContinueBar.style.display = 'none'; - btnAutoRun.innerHTML = `重试中${runLabel}`; + btnAutoRun.innerHTML = `Retrying${runLabel}`; break; case 'waiting_interval': autoContinueBar.style.display = 'none'; - btnAutoRun.innerHTML = `等待中${runLabel}`; + btnAutoRun.innerHTML = `Waiting${runLabel}`; break; default: autoContinueBar.style.display = 'none'; @@ -10823,8 +10730,8 @@ function initializeManualStepActions() { manualBtn.className = 'step-manual-btn'; manualBtn.dataset.step = String(step); manualBtn.dataset.nodeId = nodeId; - manualBtn.title = '跳过此节点'; - manualBtn.setAttribute('aria-label', `跳过节点 ${nodeId || step}`); + manualBtn.title = 'Skip this node'; + manualBtn.setAttribute('aria-label', `Skip node ${nodeId || step}`); manualBtn.innerHTML = ''; manualBtn.addEventListener('click', async (event) => { event.stopPropagation(); @@ -11095,7 +11002,7 @@ function applySettingsState(state) { if (!hasOption && normalizedCountryCode) { const option = document.createElement('option'); option.value = normalizedCountryCode; - option.textContent = `自定义 ${normalizedCountryCode}`; + option.textContent = `Custom ${normalizedCountryCode}`; selectGpcHelperCountryCode.appendChild(option); } selectGpcHelperCountryCode.value = normalizedCountryCode || '+86'; @@ -11120,8 +11027,8 @@ function applySettingsState(state) { const balanceError = String(state?.gopayHelperBalanceError || '').trim(); const balanceAt = Number(state?.gopayHelperBalanceUpdatedAt) || 0; displayGpcHelperBalance.textContent = balanceError - ? `余额查询失败:${balanceError}` - : (balanceText || (balanceAt ? '余额已更新' : '余额未获取')); + ? `Balance query failed: ${balanceError}` + : (balanceText || (balanceAt ? 'Balance updated' : 'Balance not retrieved')); } if (typeof selectGoPayCountryCode !== 'undefined' && selectGoPayCountryCode) { const normalizedGoPayCountryCode = window.GoPayUtils?.normalizeGoPayCountryCode @@ -11132,7 +11039,7 @@ function applySettingsState(state) { if (!hasOption && normalizedGoPayCountryCode) { const option = document.createElement('option'); option.value = normalizedGoPayCountryCode; - option.textContent = `自定义 ${normalizedGoPayCountryCode}`; + option.textContent = `Custom ${normalizedGoPayCountryCode}`; selectGoPayCountryCode.appendChild(option); } selectGoPayCountryCode.value = normalizedGoPayCountryCode || '+86'; @@ -11203,14 +11110,14 @@ function applySettingsState(state) { || kiroRuntimeState?.register?.status || '' ).trim(); - displayKiroWebStatus.textContent = kiroWebStatus || '未开始'; + displayKiroWebStatus.textContent = kiroWebStatus || 'Not started'; } if (typeof displayKiroLoginUrl !== 'undefined' && displayKiroLoginUrl) { const kiroLoginUrl = String( kiroRuntimeState?.register?.loginUrl || '' ).trim(); - displayKiroLoginUrl.textContent = kiroLoginUrl || '未打开'; + displayKiroLoginUrl.textContent = kiroLoginUrl || 'Not opened'; } if (typeof displayKiroUploadStatus !== 'undefined' && displayKiroUploadStatus) { const kiroUploadStatus = String( @@ -11719,7 +11626,7 @@ function ignoreCurrentReleaseUpdate() { status: 'ignored', ignoredVersion, }); - showToast(`已忽略 ${ignoredVersion} 更新,有新版本时会再次提醒。`, 'info', 2200); + showToast(`Ignored ${ignoredVersion} update. You'll be reminded again when a new version is available.`, 'info', 2200); } function openCloudflareTempEmailUsageGuidePage() { @@ -11738,7 +11645,7 @@ function createUpdateNoteList(notes = []) { if (!Array.isArray(notes) || notes.length === 0) { const empty = document.createElement('p'); empty.className = 'update-release-empty'; - empty.textContent = '该版本未提供可解析的更新说明,请查看完整更新日志。'; + empty.textContent = 'No parseable release notes for this version. Please check the full release log.'; return empty; } @@ -11843,7 +11750,7 @@ function renderReleaseSnapshot(snapshot) { switch (snapshot?.status) { case 'update-available': { - extensionUpdateStatus.textContent = '有更新'; + extensionUpdateStatus.textContent = 'Update available'; extensionUpdateStatus.classList.add('is-update-available'); if (btnReleaseLog) { btnReleaseLog.hidden = false; @@ -11853,18 +11760,18 @@ function renderReleaseSnapshot(snapshot) { updateSection.hidden = false; } if (updateCardVersion) { - updateCardVersion.textContent = `最新版本 ${snapshot.latestVersion}`; + updateCardVersion.textContent = `Latest version ${snapshot.latestVersion}`; } if (updateCardSummary) { const updateCount = Array.isArray(snapshot.newerReleases) ? snapshot.newerReleases.length : 0; updateCardSummary.textContent = updateCount > 1 - ? `当前 ${localVersionText},共有 ${updateCount} 个新版本可更新。` - : `当前 ${localVersionText},可更新到 ${snapshot.latestVersion}。`; + ? `Current ${localVersionText}; ${updateCount} new versions available.` + : `Current ${localVersionText}; update available to ${snapshot.latestVersion}.`; } renderUpdateReleaseList(snapshot.newerReleases || []); if (btnOpenRelease) { btnOpenRelease.hidden = false; - btnOpenRelease.textContent = '前往更新'; + btnOpenRelease.textContent = 'Go to Update'; btnOpenRelease.onclick = () => openExternalUrl(logUrl); } if (btnIgnoreRelease) { @@ -11899,7 +11806,7 @@ function renderReleaseSnapshot(snapshot) { default: { extensionUpdateStatus.textContent = localVersionText || 'FlowPilot0.0'; extensionUpdateStatus.classList.add('is-version-label', 'is-check-failed'); - extensionVersionMeta.textContent = snapshot?.errorMessage || 'GitHub Releases 检查失败'; + extensionVersionMeta.textContent = snapshot?.errorMessage || 'GitHub Releases check failed'; extensionVersionMeta.hidden = false; resetUpdateCard(); break; @@ -11932,7 +11839,7 @@ async function initializeReleaseInfo() { resetUpdateCard(); if (!sidepanelUpdateService) { - extensionVersionMeta.textContent = '更新检查服务不可用'; + extensionVersionMeta.textContent = 'Update check service unavailable'; extensionVersionMeta.hidden = false; return; } @@ -11980,10 +11887,10 @@ function getContributionUpdatePromptLines(snapshot = currentContributionContentS const lines = []; if (hasAnnouncementOrTutorial) { - lines.push('公告 / 使用教程有更新了,可点上方“贡献/使用”查看。'); + lines.push('Announcements / Tutorials have been updated. Click "Contribute/Tutorial" above to view.'); } if (hasQuestionnaire) { - lines.push('有新的征求意见,请佬友共同参与选择。'); + lines.push('New survey available, please participate.'); } return lines; } @@ -12435,92 +12342,92 @@ function getEmailGeneratorUiCopy() { } if (getSelectedEmailGenerator() === GMAIL_ALIAS_GENERATOR) { return { - buttonLabel: '生成', - placeholder: '步骤 3 自动生成 Gmail +tag 邮箱并回填', - successVerb: '生成', - label: 'Gmail +tag 邮箱', + buttonLabel: 'Generate', + placeholder: 'Step 3 will auto-generate Gmail +tag email and fill it back', + successVerb: 'Generated', + label: 'Gmail +tag Email', }; } if (getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR) { return { - buttonLabel: '取下一个', - placeholder: '按邮箱池顺序自动回填,也可以手动粘贴当前轮邮箱', - successVerb: '取用', - label: '自定义邮箱池', + buttonLabel: 'Take Next', + placeholder: 'Auto-fills from email pool in order; can also paste current round email manually', + successVerb: 'Picked', + label: 'Custom Email Pool', }; } if (getSelectedEmailGenerator() === 'icloud') { return { - buttonLabel: '获取', - placeholder: '点击获取 iCloud 隐私邮箱,或手动粘贴邮箱', - successVerb: '获取', - label: 'iCloud 隐私邮箱', + buttonLabel: 'Fetch', + placeholder: 'Click to fetch iCloud Hide My Email, or paste email manually', + successVerb: 'Fetched', + label: 'iCloud Hide My Email', }; } if (getSelectedEmailGenerator() === 'cloudflare') { return { - buttonLabel: '生成', - placeholder: '点击生成 Cloudflare 邮箱,或手动粘贴邮箱', - successVerb: '生成', - label: 'Cloudflare 邮箱', + buttonLabel: 'Generate', + placeholder: 'Click to generate Cloudflare email, or paste email manually', + successVerb: 'Generated', + label: 'Cloudflare Email', }; } if (getSelectedEmailGenerator() === 'cloudflare-temp-email') { return { - buttonLabel: '生成 Temp', - placeholder: '点击生成 Cloudflare Temp Email,或手动粘贴邮箱', - successVerb: '生成', + buttonLabel: 'Generate Temp', + placeholder: 'Click to generate Cloudflare Temp Email, or paste email manually', + successVerb: 'Generated', label: 'Cloudflare Temp Email', }; } if (getSelectedEmailGenerator() === 'cloudmail') { return { - buttonLabel: '生成', - placeholder: '点击生成 Cloud Mail 邮箱,或手动粘贴邮箱', - successVerb: '生成', + buttonLabel: 'Generate', + placeholder: 'Click to generate Cloud Mail email, or paste email manually', + successVerb: 'Generated', label: 'Cloud Mail', }; } return { - buttonLabel: '获取', - placeholder: '点击获取 DuckDuckGo 邮箱,或手动粘贴邮箱', - successVerb: '获取', - label: 'Duck 邮箱', + buttonLabel: 'Fetch', + placeholder: 'Click to fetch DuckDuckGo email, or paste email manually', + successVerb: 'Fetched', + label: 'Duck Email', }; } function getCustomMailProviderUiCopy() { if (usesCustomMailProviderPool()) { return { - buttonLabel: '自定义邮箱', - placeholder: '号池会按顺序自动回填,也可以手动覆盖当前轮邮箱', - successVerb: '使用', - label: '自定义邮箱', + buttonLabel: 'Custom Email', + placeholder: 'Pool auto-fills in order; can also manually override current round email', + successVerb: 'Used', + label: 'Custom Email', }; } return { - buttonLabel: '自定义邮箱', - placeholder: '请填写本轮要使用的注册邮箱', - successVerb: '使用', - label: '自定义邮箱', + buttonLabel: 'Custom Email', + placeholder: 'Fill in the registration email for this round', + successVerb: 'Used', + label: 'Custom Email', }; } function getCustomVerificationPromptCopy(step) { - const verificationLabel = step === 4 ? '注册验证码' : '登录验证码'; + const verificationLabel = step === 4 ? 'registration code' : 'login code'; const isLoginVerificationStep = step === 8 || step === 11; return { - title: `手动处理${verificationLabel}`, - message: `当前邮箱服务为“自定义邮箱”。请先在页面中手动输入${verificationLabel},并确认已经进入下一页面后,再点击确认。`, + title: `Manual ${verificationLabel} handling`, + message: `Current email service is "Custom Email". Please manually enter the ${verificationLabel} on the page and confirm you've reached the next page, then click Confirm.`, alert: { - text: `点击确认后会跳过步骤 ${step}。`, + text: `After clicking Confirm, step ${step} will be skipped.`, tone: 'danger', }, ...(isLoginVerificationStep ? { - phoneActionLabel: '出现手机号验证', + phoneActionLabel: 'Phone verification appeared', phoneActionAlert: { - text: '如果当前页面已经进入手机号验证,可直接标记为失败并继续下一个邮箱。', + text: 'If the current page has shown phone verification, you can mark it failed directly and continue to the next email.', tone: 'danger', }, } : {}), @@ -12535,30 +12442,30 @@ function normalizeGoPayOtpInputValue(value = '') { async function openGoPayOtpInputDialog(payload = {}) { if (!sharedFormDialog?.open) { - throw new Error('验证码输入弹窗未加载,请刷新扩展后重试。'); + throw new Error('Verification code input dialog not loaded. Please refresh the extension and retry.'); } const initialCode = normalizeGoPayOtpInputValue(payload.code || inputGoPayOtp?.value || latestState?.gopayOtp || ''); const result = await sharedFormDialog.open({ - title: '输入 GoPay 验证码', - message: '请把当前 GoPay 页面收到的验证码填到这里,确认后插件会继续填写验证码并进入 PIN 步骤。', - confirmLabel: '提交验证码', + title: 'Enter GoPay verification code', + message: 'Paste the verification code received on the current GoPay page here; after confirming, the extension will fill it in and continue to the PIN step.', + confirmLabel: 'Submit Code', confirmVariant: 'btn-primary', fields: [ { key: 'code', - label: '验证码', + label: 'Code', type: 'text', required: true, - requiredMessage: '请输入 GoPay 验证码。', - placeholder: '请输入数字验证码', + requiredMessage: 'Please enter GoPay verification code.', + placeholder: 'Enter numeric verification code', inputMode: 'numeric', autocomplete: 'one-time-code', value: initialCode, validate: (value) => { const normalized = normalizeGoPayOtpInputValue(value); - if (!normalized) return '请输入 GoPay 验证码。'; - if (normalized.length < 4) return 'GoPay 验证码长度过短,请检查。'; + if (!normalized) return 'Please enter GoPay verification code.'; + if (normalized.length < 4) return 'GoPay verification code is too short, please check.'; return ''; }, }, @@ -12585,9 +12492,9 @@ async function openCustomVerificationConfirmDialog(step) { message: promptCopy.message, alert: promptCopy.alert, actions: [ - { id: null, label: '取消', variant: 'btn-ghost' }, - { id: 'add_phone', label: promptCopy.phoneActionLabel || '出现手机号验证', variant: 'btn-outline' }, - { id: 'confirm', label: '确认跳过', variant: 'btn-danger' }, + { id: null, label: 'Cancel', variant: 'btn-ghost' }, + { id: 'add_phone', label: promptCopy.phoneActionLabel || 'Phone verification appeared', variant: 'btn-outline' }, + { id: 'confirm', label: 'Confirm Skip', variant: 'btn-danger' }, ], buildResult: (choice) => ({ confirmed: choice === 'confirm', @@ -12599,7 +12506,7 @@ async function openCustomVerificationConfirmDialog(step) { const confirmed = await openConfirmModal({ title: promptCopy.title, message: promptCopy.message, - confirmLabel: '确认跳过', + confirmLabel: 'Confirm Skip', confirmVariant: 'btn-danger', alert: promptCopy.alert, }); @@ -12694,7 +12601,7 @@ function getLuckmailPreserveTagName(state = latestState) { function formatLuckmailDateTime(value) { const timestamp = normalizeLuckmailTimestampValue(value); if (!timestamp) { - return String(value || '').trim() || '未知'; + return String(value || '').trim() || 'Unknown'; } return new Date(timestamp).toLocaleString('zh-CN', { hour12: false, @@ -12801,8 +12708,8 @@ function updateMailLoginButtonState() { const config = getMailProviderLoginConfig(); const loginUrl = getMailProviderLoginUrl(); btnMailLogin.disabled = !loginUrl; - btnMailLogin.textContent = config?.buttonLabel || '登录'; - btnMailLogin.title = loginUrl ? `打开 ${config.label} 登录页` : '当前邮箱服务没有可跳转的登录页'; + btnMailLogin.textContent = config?.buttonLabel || 'Login'; + btnMailLogin.title = loginUrl ? `Open ${config.label} login page` : 'Current mail service has no login page'; } function updateIpProxyServiceLoginButtonState(options = {}) { @@ -12821,11 +12728,11 @@ function updateIpProxyServiceLoginButtonState(options = {}) { ? Boolean(options.enabled) : Boolean(getSelectedIpProxyEnabled()); btnIpProxyServiceLogin.disabled = !enabled || !loginUrl; - const buttonLabel = loginConfig?.buttonLabel || '登录'; + const buttonLabel = loginConfig?.buttonLabel || 'Login'; btnIpProxyServiceLogin.textContent = buttonLabel; btnIpProxyServiceLogin.title = loginUrl - ? `打开 ${loginConfig?.label || service} ${buttonLabel}页` - : '当前代理服务没有可跳转的登录页'; + ? `Open ${loginConfig?.label || service} ${buttonLabel} page` + : 'Current proxy service has no login page'; } function updateMailProviderUI() { @@ -13035,8 +12942,8 @@ function updateMailProviderUI() { if (luckmailSection) { luckmailSection.style.display = useLuckmail ? '' : 'none'; } - labelEmailPrefix.textContent = '邮箱前缀'; - inputEmailPrefix.placeholder = '例如 abc'; + labelEmailPrefix.textContent = 'Email Prefix'; + inputEmailPrefix.placeholder = 'e.g. abc'; if (labelMail2925UseAccountPool) { labelMail2925UseAccountPool.style.display = useMail2925 ? '' : 'none'; } @@ -13049,8 +12956,8 @@ function updateMailProviderUI() { inputEmailPrefix.readOnly = false; selectEmailGenerator.disabled = useHotmail || useLuckmail || useYydsMail || useCustomEmail || (useGeneratedAlias && !useGmail); if (useGmail) { - labelEmailPrefix.textContent = 'Gmail 原邮箱'; - inputEmailPrefix.placeholder = '例如 yourname@gmail.com'; + labelEmailPrefix.textContent = 'Gmail Base Email'; + inputEmailPrefix.placeholder = 'e.g. yourname@gmail.com'; } labelEmailPrefix.textContent = aliasUiCopy?.baseLabel || labelEmailPrefix.textContent; inputEmailPrefix.placeholder = aliasUiCopy?.basePlaceholder || inputEmailPrefix.placeholder; @@ -13066,18 +12973,18 @@ function updateMailProviderUI() { btnFetchEmail.hidden = useHotmail || useLuckmail || useCustomEmail || useCustomEmailPool; inputEmail.readOnly = useHotmail || useLuckmail; inputEmail.placeholder = useHotmail - ? '由 Hotmail 账号池自动分配' + ? 'Auto-assigned by Hotmail account pool' : (useLuckmail - ? '步骤 3 自动购买 LuckMail 邮箱并回填' - : (useGeneratedAlias ? '步骤 3 自动生成 2925 邮箱并回填' : uiCopy.placeholder)); + ? 'Step 3 will auto-purchase LuckMail email and fill it back' + : (useGeneratedAlias ? 'Step 3 will auto-generate 2925 email and fill it back' : uiCopy.placeholder)); if (useGmail && useGeneratedAlias) { - inputEmail.placeholder = '步骤 3 自动生成 Gmail +tag 邮箱并回填'; + inputEmail.placeholder = 'Step 3 will auto-generate Gmail +tag email and fill it back'; } if (!useHotmail && !useLuckmail) { inputEmail.placeholder = uiCopy.placeholder; } if (useCustomEmail && useCustomMailProviderPool) { - inputEmail.placeholder = '号池会按顺序自动回填当前轮邮箱,也可以手动覆盖'; + inputEmail.placeholder = 'Pool auto-fills current round email in order; can also be manually overridden'; } btnFetchEmail.disabled = useLuckmail || useCustomEmail || useCustomEmailPool || isAutoRunLockedPhase(); if (!btnFetchEmail.disabled) { @@ -13085,23 +12992,23 @@ function updateMailProviderUI() { } if (autoHintText) { autoHintText.textContent = useHotmail - ? '请先校验并选择一个 Hotmail 账号' + ? 'Please verify and select a Hotmail account first' : (useLuckmail - ? '步骤 3 会自动购买 LuckMail 邮箱并用于收码' + ? 'Step 3 will auto-purchase LuckMail email and use it for code reception' : (useGeneratedAlias - ? '步骤 3 会自动生成邮箱,无需手动获取' - : (useCustomEmail ? '请先填写自定义注册邮箱,成功一轮后会自动清空' : `先自动获取${uiCopy.label},或手动粘贴邮箱后再继续`))); + ? 'Step 3 will auto-generate email, no manual fetch needed' + : (useCustomEmail ? 'Please fill in custom registration email; will auto-clear after one successful round' : `Auto-fetch ${uiCopy.label} first, or paste email manually before continuing`))); } if (autoHintText && useCustomEmailPool) { autoHintText.textContent = getCustomEmailPoolSize() > 0 - ? `当前邮箱池共 ${getCustomEmailPoolSize()} 个邮箱,自动轮数会跟随数量;实际收码仍走当前邮箱服务` - : '请先在邮箱池里每行填写一个邮箱,自动轮数会跟随数量'; + ? `Current email pool has ${getCustomEmailPoolSize()} emails; auto rounds will match this count; actual code reception still uses current mail service` + : 'Please fill in one email per line in the email pool first; auto rounds will match the count'; } if (autoHintText && useCustomEmail && useCustomMailProviderPool) { - autoHintText.textContent = `当前自定义号池共 ${getCustomMailProviderPoolSize()} 个邮箱,自动轮数会跟随数量;第 4/8 步仍需手动输入验证码`; + autoHintText.textContent = `Current custom pool has ${getCustomMailProviderPoolSize()} emails; auto rounds will match this count; steps 4/8 still require manual verification code entry`; } if (autoHintText && useGmail && useGeneratedAlias) { - autoHintText.textContent = '请先填写 Gmail 原邮箱,步骤 3 会自动生成 Gmail +tag 地址'; + autoHintText.textContent = 'Please fill in your Gmail base email first; step 3 will auto-generate Gmail +tag addresses'; } if (autoHintText && useGeneratedAlias && aliasUiCopy?.hint) { autoHintText.textContent = aliasUiCopy.hint; @@ -13109,22 +13016,22 @@ function updateMailProviderUI() { if (autoHintText && useMail2925AccountPool && !useCustomEmailPool) { autoHintText.textContent = getMail2925Accounts().length ? (useGeneratedAlias - ? '当前已启用 2925 号池模式,步骤 3 会基于下拉框选中的号池邮箱生成别名地址' - : '当前已启用 2925 号池模式,步骤 4 / 8 遇到登录页时会优先使用下拉框选中的账号自动登录') - : '当前已启用 2925 号池模式,请先在下方 2925 账号池中添加账号并选择邮箱'; + ? '2925 pool mode enabled; step 3 will generate alias addresses based on the pool email selected in the dropdown' + : '2925 pool mode enabled; steps 4/8 will preferentially use the account selected in the dropdown for auto-login when login page appears') + : '2925 pool mode enabled; please add accounts and select an email in the 2925 account pool below first'; } if (autoHintText && showCloudflareTempEmailReceiveMailbox && !useCustomEmailPool) { - autoHintText.textContent = '若注册邮箱会转发到 Cloudflare Temp Email,请在“邮件接收”中填写实际接收转发邮件的邮箱。'; + autoHintText.textContent = 'If the registration email forwards to Cloudflare Temp Email, fill in the actual receiving mailbox in "Receive Mailbox".'; } if (autoHintText && showCloudflareTempEmailRandomSubdomainToggle && inputTempEmailUseRandomSubdomain?.checked) { - autoHintText.textContent = '已启用随机子域名:扩展会按当前选中的 Temp 域名提交,并额外携带 enableRandomSubdomain;是否生效取决于后端 RANDOM_SUBDOMAIN_DOMAINS 配置。'; + autoHintText.textContent = 'Random subdomain enabled: the extension will submit using the currently selected Temp domain with enableRandomSubdomain; whether it takes effect depends on backend RANDOM_SUBDOMAIN_DOMAINS config.'; } if (autoHintText && useIcloudProvider && showIcloudForwardMailProvider) { const forwardProvider = normalizeIcloudForwardMailProvider(icloudForwardMailProviderValue); const forwardProviderLabel = ICLOUD_FORWARD_MAIL_PROVIDER_LABELS[forwardProvider] || MAIL_PROVIDER_LOGIN_CONFIGS[forwardProvider]?.label - || '目标邮箱'; - autoHintText.textContent = `iCloud ${isIcloudComCnHost ? 'com.cn' : ''} 当前使用转发收码:第 4/8 步会从 ${forwardProviderLabel} 轮询验证码。`; + || 'target mailbox'; + autoHintText.textContent = `iCloud ${isIcloudComCnHost ? 'com.cn' : ''} currently uses forward code reception: steps 4/8 will poll verification code from ${forwardProviderLabel}.`; } if (useHotmail) { inputEmail.value = getCurrentHotmailEmail(); @@ -13180,7 +13087,7 @@ async function saveCloudflareDomainSettings(domains, activeDomain, options = {}) updateMailProviderUI(); if (!silent) { - showToast('Cloudflare 域名已保存', 'success', 1800); + showToast('Cloudflare domain saved', 'success', 1800); } } @@ -13212,7 +13119,7 @@ async function saveCloudflareTempEmailDomainSettings(domains, activeDomain, opti updateMailProviderUI(); if (!silent) { - showToast('Cloudflare Temp Email 域名已保存', 'success', 1800); + showToast('Cloudflare Temp Email domain saved', 'success', 1800); } } @@ -13246,7 +13153,7 @@ function buildCloudflareTempEmailSyncHeaders(options = {}) { async function requestCloudflareTempEmailSyncPayload(baseUrl, path, options = {}) { const url = joinCloudflareTempEmailSettingsUrl(baseUrl, path); if (!url) { - throw new Error('Cloudflare Temp Email 服务地址为空或格式无效。'); + throw new Error('Cloudflare Temp Email service URL is empty or invalid.'); } const response = await fetch(url, { @@ -13298,14 +13205,14 @@ async function fetchCloudflareTempEmailAvailableDomains(baseUrl) { source: 'open_api/settings', }; } - openSettingsError = new Error('公开设置未返回可用域名。'); + openSettingsError = new Error('Open settings did not return available domains.'); } catch (error) { openSettingsError = error; } const adminAuth = String(inputTempEmailAdminAuth?.value || '').trim(); if (!adminAuth) { - throw openSettingsError || new Error('未获取到可用域名。'); + throw openSettingsError || new Error('No available domains retrieved.'); } const payload = await requestCloudflareTempEmailSyncPayload(baseUrl, '/admin/worker/configs', { @@ -13313,7 +13220,7 @@ async function fetchCloudflareTempEmailAvailableDomains(baseUrl) { }); const domains = normalizeCloudflareTempEmailDomains(payload?.DOMAINS || []); if (!domains.length) { - throw openSettingsError || new Error('管理配置未返回可用域名。'); + throw openSettingsError || new Error('Admin config did not return available domains.'); } return { domains, @@ -13324,12 +13231,12 @@ async function fetchCloudflareTempEmailAvailableDomains(baseUrl) { async function syncCloudflareTempEmailDomainsFromService() { const normalizedBaseUrl = normalizeCloudflareTempEmailBaseUrlValue(inputTempEmailBaseUrl?.value || ''); if (!normalizedBaseUrl) { - throw new Error('请先填写有效的 Cloudflare Temp Email 服务地址。'); + throw new Error('Please fill in a valid Cloudflare Temp Email service URL.'); } const previousButtonText = btnTempEmailDomainMode.textContent; btnTempEmailDomainMode.disabled = true; - btnTempEmailDomainMode.textContent = '更新中...'; + btnTempEmailDomainMode.textContent = 'Updating...'; try { const { domains: fetchedDomains, source } = await fetchCloudflareTempEmailAvailableDomains(normalizedBaseUrl); @@ -13344,9 +13251,9 @@ async function syncCloudflareTempEmailDomainsFromService() { await saveCloudflareTempEmailDomainSettings(mergedDomains, nextActiveDomain, { silent: true }); if (addedCount > 0) { - showToast(`已更新 Cloudflare Temp Email 域名,新增 ${addedCount} 个。`, 'success', 2200); + showToast(`Updated Cloudflare Temp Email domains, added ${addedCount}.`, 'success', 2200); } else { - showToast('已同步 Cloudflare Temp Email 域名,暂无新增项。', 'info', 2200); + showToast('Synced Cloudflare Temp Email domains, no new entries.', 'info', 2200); } return { domains: mergedDomains, @@ -13354,11 +13261,11 @@ async function syncCloudflareTempEmailDomainsFromService() { source, }; } catch (error) { - const message = error?.message || String(error || '未知错误'); - throw new Error(`更新 Cloudflare Temp Email 域名失败:${message}`); + const message = error?.message || String(error || 'Unknown error'); + throw new Error(`Failed to update Cloudflare Temp Email domains: ${message}`); } finally { btnTempEmailDomainMode.disabled = false; - btnTempEmailDomainMode.textContent = previousButtonText || '更新'; + btnTempEmailDomainMode.textContent = previousButtonText || 'Update'; } } @@ -13379,7 +13286,7 @@ async function handleDeleteCloudflareDomain(domain) { ? (nextDomains[0] || '') : (nextDomains.includes(currentDomain) ? currentDomain : nextDomains[0] || ''); await saveCloudflareDomainSettings(nextDomains, nextActiveDomain, { silent: true }); - showToast(`已删除 Cloudflare 域名:${targetDomain}`, 'success', 1600); + showToast(`Deleted Cloudflare domain: ${targetDomain}`, 'success', 1600); } async function handleDeleteCloudflareTempEmailDomain(domain) { @@ -13399,31 +13306,31 @@ async function handleDeleteCloudflareTempEmailDomain(domain) { ? (nextDomains[0] || '') : (nextDomains.includes(currentDomain) ? currentDomain : nextDomains[0] || ''); await saveCloudflareTempEmailDomainSettings(nextDomains, nextActiveDomain, { silent: true }); - showToast(`已删除 Cloudflare Temp Email 域名:${targetDomain}`, 'success', 1600); + showToast(`Deleted Cloudflare Temp Email domain: ${targetDomain}`, 'success', 1600); } async function handleAddSub2ApiGroup() { if (!sharedFormDialog?.open) { - showToast('表单弹窗未加载,请刷新扩展后重试。', 'error'); + showToast('Form dialog not loaded. Please refresh the extension and retry.', 'error'); return; } const result = await sharedFormDialog.open({ - title: '添加 SUB2API 分组', - confirmLabel: '添加', + title: 'Add SUB2API Group', + confirmLabel: 'Add', confirmVariant: 'btn-primary', fields: [ { key: 'groupName', - label: '分组', + label: 'Group', type: 'text', - placeholder: '例如 openai-plus', + placeholder: 'e.g. openai-plus', autocomplete: 'off', required: true, - requiredMessage: '请先填写 SUB2API 分组名称。', + requiredMessage: 'Please fill in SUB2API group name.', validate: (value) => { const names = normalizeSub2ApiGroupOptions(value); - return names.length ? '' : '请先填写 SUB2API 分组名称。'; + return names.length ? '' : 'Please fill in SUB2API group name.'; }, }, ], @@ -13449,7 +13356,7 @@ async function handleAddSub2ApiGroup() { renderSub2ApiGroupOptions(latestState, selectedGroup); markSettingsDirty(true); await saveSettings({ silent: true }).catch(() => { }); - showToast(`已添加并切换到 SUB2API 分组:${selectedGroup}`, 'success', 1800); + showToast(`Added and switched to SUB2API group: ${selectedGroup}`, 'success', 1800); } async function handleDeleteSub2ApiGroup(groupName) { @@ -13460,7 +13367,7 @@ async function handleDeleteSub2ApiGroup(groupName) { const currentGroups = getSub2ApiGroupOptionsState(latestState); if (currentGroups.length <= 1) { - showToast('至少保留一个 SUB2API 分组。', 'warn', 1800); + showToast('At least one SUB2API group must be kept.', 'warn', 1800); return; } @@ -13483,7 +13390,7 @@ async function handleDeleteSub2ApiGroup(groupName) { sub2ApiGroupPicker.setOpen(true); markSettingsDirty(true); await saveSettings({ silent: true }).catch(() => { }); - showToast(`已删除 SUB2API 分组:${targetName}`, 'success', 1600); + showToast(`Deleted SUB2API group: ${targetName}`, 'success', 1600); } function updatePanelModeUI() { @@ -13536,8 +13443,8 @@ function updatePanelModeUI() { const step9Btn = document.querySelector('.step-btn[data-step-key="platform-verify"]'); if (step9Btn && activeFlowId === DEFAULT_ACTIVE_FLOW_ID) { step9Btn.textContent = displayTargetId === 'sub2api' - ? 'SUB2API 回调验证' - : (useCodex2Api ? 'Codex2API 回调验证' : 'CPA 回调验证'); + ? 'SUB2API callback verification' + : (useCodex2Api ? 'Codex2API callback verification' : 'CPA callback verification'); } } @@ -13672,20 +13579,20 @@ function updateButtonStates() { if (!SKIPPABLE_NODES.has(nodeId) || currentStatus === 'disabled' || anyRunning || autoLocked || currentStatus === 'running' || isDoneStatus(currentStatus)) { btn.style.display = 'none'; btn.disabled = true; - btn.title = '当前不可跳过'; + btn.title = 'Cannot skip right now'; return; } if (prevNodeId !== null && !isDoneStatus(prevStatus)) { btn.style.display = 'none'; btn.disabled = true; - btn.title = `请先完成节点 ${prevNodeId}`; + btn.title = `Complete node ${prevNodeId}`; return; } btn.style.display = ''; btn.disabled = false; - btn.title = `跳过节点 ${nodeId}`; + btn.title = `Skip node ${nodeId}`; }); btnReset.disabled = anyRunning || isAutoRunPausedPhase() || autoLocked; @@ -13729,14 +13636,14 @@ function updateStatusDisplay(state) { if (countdown) { const remainingMs = countdown.at - Date.now(); displayStatus.textContent = remainingMs > 0 - ? `${countdown.title},剩余 ${formatCountdown(remainingMs)}` - : `${countdown.title},即将结束...`; + ? `${countdown.title}, remaining ${formatCountdown(remainingMs)}` + : `${countdown.title}, ending soon...`; statusBar.classList.add('running'); return; } if (isAutoRunPausedPhase()) { - displayStatus.textContent = `自动已暂停${getAutoRunLabel()},等待邮箱后继续`; + displayStatus.textContent = `Auto paused${getAutoRunLabel()}, waiting for email to continue`; statusBar.classList.add('paused'); return; } @@ -13744,35 +13651,35 @@ function updateStatusDisplay(state) { if (isAutoRunWaitingStepPhase()) { const runningNodes = getRunningNodes(state); displayStatus.textContent = runningNodes.length - ? `自动等待节点 ${runningNodes.join(', ')} 完成后继续${getAutoRunLabel()}` - : `自动正在按最新进度准备继续${getAutoRunLabel()}`; + ? `Auto waiting for nodes ${runningNodes.join(', ')} to finish before continuing${getAutoRunLabel()}` + : `Auto preparing to continue with the latest progress${getAutoRunLabel()}`; statusBar.classList.add('running'); return; } const running = Object.entries(nodeStatuses).find(([, s]) => s === 'running'); if (running) { - displayStatus.textContent = `节点 ${running[0]} 运行中...`; + displayStatus.textContent = `Node ${running[0]} running...`; statusBar.classList.add('running'); return; } if (isAutoRunLockedPhase()) { - displayStatus.textContent = `${currentAutoRun.phase === 'retrying' ? '自动重试中' : '自动运行中'}${getAutoRunLabel()}`; + displayStatus.textContent = `${currentAutoRun.phase === 'retrying' ? 'Auto retrying' : 'Auto running'}${getAutoRunLabel()}`; statusBar.classList.add('running'); return; } const failed = Object.entries(nodeStatuses).find(([, s]) => s === 'failed'); if (failed) { - displayStatus.textContent = `节点 ${failed[0]} 失败`; + displayStatus.textContent = `Node ${failed[0]} failed`; statusBar.classList.add('failed'); return; } const stopped = Object.entries(nodeStatuses).find(([, s]) => s === 'stopped'); if (stopped) { - displayStatus.textContent = `节点 ${stopped[0]} 已停止`; + displayStatus.textContent = `Node ${stopped[0]} stopped`; statusBar.classList.add('stopped'); return; } @@ -13786,15 +13693,15 @@ function updateStatusDisplay(state) { if (lastCompleted === lastEnabledNodeId) { const range = getStepExecutionRangeForCurrentFlow(state); - const doneText = range.enabled ? '执行范围已完成' : '全部节点已完成'; - displayStatus.textContent = (nodeStatuses[lastCompleted] === 'manual_completed' || nodeStatuses[lastCompleted] === 'skipped') ? `${doneText}/跳过` : doneText; + const doneText = range.enabled ? 'Selected range completed' : 'All nodes completed'; + displayStatus.textContent = (nodeStatuses[lastCompleted] === 'manual_completed' || nodeStatuses[lastCompleted] === 'skipped') ? `${doneText}/skipped` : doneText; statusBar.classList.add('completed'); } else if (lastCompleted) { displayStatus.textContent = (nodeStatuses[lastCompleted] === 'manual_completed' || nodeStatuses[lastCompleted] === 'skipped') - ? `节点 ${lastCompleted} 已跳过` - : `节点 ${lastCompleted} 已完成`; + ? `Node ${lastCompleted} skipped` + : `Node ${lastCompleted} completed`; } else { - displayStatus.textContent = '就绪'; + displayStatus.textContent = 'Ready'; } } @@ -13813,7 +13720,7 @@ function appendLog(entry) { let html = `${time} `; html += `${levelLabel} `; if (stepNum) { - html += `步${stepNum}`; + html += `Step ${stepNum}`; } html += `${escapeHtml(entry.message)}`; @@ -13832,7 +13739,7 @@ async function fetchGeneratedEmail(options = {}) { const { showFailureToast = true } = options; const uiCopy = getCurrentRegistrationEmailUiCopy(); if (isCustomMailProvider()) { - throw new Error('当前邮箱服务为自定义邮箱,请直接填写注册邮箱。'); + throw new Error('The current email service is custom email. Please enter the signup email directly.'); } const defaultLabel = uiCopy.buttonLabel; btnFetchEmail.disabled = true; @@ -13861,18 +13768,18 @@ async function fetchGeneratedEmail(options = {}) { throw new Error(response.error); } if (!response?.email) { - throw new Error('未返回可用邮箱。'); + throw new Error('No available email was returned.'); } inputEmail.value = response.email; if (getSelectedEmailGenerator() === 'icloud') { queueIcloudAliasRefresh(); } - showToast(`已${uiCopy.successVerb} ${uiCopy.label}:${response.email}`, 'success', 2500); + showToast(`${uiCopy.successVerb} ${uiCopy.label}: ${response.email}`, 'success', 2500); return response.email; } catch (err) { if (showFailureToast) { - showToast(`${uiCopy.label}${uiCopy.successVerb}失败:${err.message}`, 'error'); + showToast(`Failed to ${String(uiCopy.successVerb || 'process').toLowerCase()} ${uiCopy.label}: ${err.message}`, 'error'); } throw err; } finally { @@ -14067,10 +13974,10 @@ bindPasswordVisibilityToggles(); async function copyTextToClipboard(text) { const value = String(text || '').trim(); if (!value) { - throw new Error('没有可复制的内容。'); + throw new Error('There is no content to copy.'); } if (!navigator.clipboard?.writeText) { - throw new Error('当前环境不支持剪贴板复制。'); + throw new Error('The current environment does not support clipboard copy.'); } await navigator.clipboard.writeText(value); } @@ -14079,9 +13986,9 @@ btnCopyGrokSso?.addEventListener('click', async () => { try { const cookies = normalizeGrokSsoCookies(latestState); await copyTextToClipboard(cookies.join('\n')); - showToast('Grok SSO Cookie 已复制。', 'success'); + showToast('Grok SSO Cookie copied.', 'success'); } catch (error) { - showToast(error?.message || '复制 Grok SSO Cookie 失败。', 'error'); + showToast(error?.message || 'Failed to copy Grok SSO Cookie.', 'error'); } }); @@ -14089,7 +13996,7 @@ btnClearGrokSso?.addEventListener('click', async () => { try { const cookies = normalizeGrokSsoCookies(latestState); if (!cookies.length) { - showToast('当前没有 Grok SSO Cookie。', 'info'); + showToast('There is currently no Grok SSO Cookie.', 'info'); return; } const response = await chrome.runtime.sendMessage({ @@ -14110,9 +14017,9 @@ btnClearGrokSso?.addEventListener('click', async () => { }); } renderGrokRuntimeState(latestState); - showToast('Grok SSO Cookie 已清空。', 'success'); + showToast('Grok SSO Cookie cleared.', 'success'); } catch (error) { - showToast(error?.message || '清空 Grok SSO Cookie 失败。', 'error'); + showToast(error?.message || 'Failed to clear Grok SSO Cookie.', 'error'); } }); @@ -14194,7 +14101,7 @@ const payPalManager = window.SidepanelPayPalManager?.createPayPalManager({ getPayPalAccounts, openFormDialog: (options) => { if (!sharedFormDialog?.open) { - throw new Error('表单弹窗能力未加载,请刷新扩展后重试。'); + throw new Error('Form modal support is not loaded. Refresh the extension and try again.'); } return sharedFormDialog.open(options); }, @@ -14634,50 +14541,50 @@ async function importSettingsFromFile(file) { function syncPasswordToggleLabel() { syncToggleButtonLabel(btnTogglePassword, inputPassword, { - show: '显示密码', - hide: '隐藏密码', + show: 'Show password', + hide: 'Hide password', }); } function syncVpsUrlToggleLabel() { syncToggleButtonLabel(btnToggleVpsUrl, inputVpsUrl, { - show: '显示 CPA 地址', - hide: '隐藏 CPA 地址', + show: 'Show CPA URL', + hide: 'Hide CPA URL', }); } function syncVpsPasswordToggleLabel() { syncToggleButtonLabel(btnToggleVpsPassword, inputVpsPassword, { - show: '显示管理密钥', - hide: '隐藏管理密钥', + show: 'Show admin key', + hide: 'Hide admin key', }); } function syncIpProxyApiUrlToggleLabel() { syncToggleButtonLabel(btnToggleIpProxyApiUrl, inputIpProxyApiUrl, { - show: '显示代理 API', - hide: '隐藏代理 API', + show: 'Show proxy API', + hide: 'Hide proxy API', }); } function syncIpProxyUsernameToggleLabel() { syncToggleButtonLabel(btnToggleIpProxyUsername, inputIpProxyUsername, { - show: '显示代理账号', - hide: '隐藏代理账号', + show: 'Show proxy account', + hide: 'Hide proxy account', }); } function syncIpProxyPasswordToggleLabel() { syncToggleButtonLabel(btnToggleIpProxyPassword, inputIpProxyPassword, { - show: '显示代理密码', - hide: '隐藏代理密码', + show: 'Show proxy password', + hide: 'Hide proxy password', }); } function syncHeroSmsApiKeyToggleLabel() { syncToggleButtonLabel(btnToggleHeroSmsApiKey, inputHeroSmsApiKey, { - show: '显示接码 API Key', - hide: '隐藏接码 API Key', + show: 'Show SMS verification API key', + hide: 'Hide SMS verification API key', }); } @@ -14687,9 +14594,9 @@ async function maybeTakeoverAutoRun(actionLabel) { } const confirmed = await openConfirmModal({ - title: '接管自动', - message: `当前自动流程已暂停。若继续${actionLabel},将停止自动流程并切换为手动控制。是否继续?`, - confirmLabel: '确认接管', + title: 'Take over auto', + message: `The auto flow is currently paused. If you continue with ${actionLabel}, the auto flow will stop and switch to manual control. Continue?`, + confirmLabel: 'Confirm takeover', confirmVariant: 'btn-primary', }); if (!confirmed) { @@ -14703,7 +14610,7 @@ async function maybeTakeoverAutoRun(actionLabel) { async function handleSkipNode(nodeId) { const normalizedNodeId = String(nodeId || '').trim(); if (!normalizedNodeId) { - throw new Error('缺少要跳过的节点。'); + throw new Error('Missing node to skip.'); } if (isAutoRunPausedPhase()) { const takeoverResponse = await chrome.runtime.sendMessage({ @@ -14731,13 +14638,13 @@ async function handleSkipNode(nodeId) { throw new Error(response.error); } - showToast(`节点 ${normalizedNodeId} 已跳过`, 'success', 2200); + showToast(`Node ${normalizedNodeId} skipped`, 'success', 2200); } async function handleSkipStep(step) { const nodeId = getNodeIdByStepForCurrentMode(step); if (!nodeId) { - throw new Error(`无效步骤:${step}`); + throw new Error(`Invalid step: ${step}`); } return handleSkipNode(nodeId); } @@ -14754,7 +14661,7 @@ stepsList?.addEventListener('click', async (event) => { try { const step = Number(btn.dataset.step); const nodeId = String(btn.dataset.nodeId || getNodeIdByStepForCurrentMode(step) || '').trim(); - if (!(await maybeTakeoverAutoRun(`执行节点 ${nodeId || step}`))) { + if (!(await maybeTakeoverAutoRun(`run node ${nodeId || step}`))) { return; } await persistCurrentSettingsForAction(); @@ -14787,7 +14694,7 @@ stepsList?.addEventListener('click', async (event) => { } else if (false && usesGeneratedAliasMailProvider(selectMailProvider.value)) { const emailPrefix = inputEmailPrefix.value.trim(); if (!emailPrefix) { - showToast(selectMailProvider.value === GMAIL_PROVIDER ? '请先填写 Gmail 原邮箱。' : '请先填写 2925 邮箱前缀。', 'warn'); + showToast(selectMailProvider.value === GMAIL_PROVIDER ? 'Please enter the original Gmail address first.' : 'Please enter the 2925 email prefix first.', 'warn'); return; } const response = await sendSidepanelMessage({ type: 'EXECUTE_NODE', source: 'sidepanel', payload: { nodeId, emailPrefix } }); @@ -14798,13 +14705,13 @@ stepsList?.addEventListener('click', async (event) => { let email = inputEmail.value.trim(); if (!email) { if (isCustomMailProvider()) { - showToast('当前邮箱服务为自定义邮箱,请先填写注册邮箱后再执行第 3 步。', 'warn'); + showToast('The current email service is custom email. Enter the signup email before running Step 3.', 'warn'); return; } try { email = await fetchGeneratedEmail({ showFailureToast: false }); } catch (err) { - showToast(`自动获取失败:${err.message},请手动粘贴邮箱后重试。`, 'warn'); + showToast(`Auto-fetch failed: ${err.message}. Paste the email manually and try again.`, 'warn'); return; } } @@ -14884,7 +14791,7 @@ btnMailLogin?.addEventListener('click', async () => { try { await chrome.tabs.create({ url: loginUrl, active: true }); } catch (err) { - showToast(`打开${config.label}失败:${err.message}`, 'error'); + showToast(`Failed to open ${config.label}: ${err.message}`, 'error'); } }); @@ -14895,7 +14802,7 @@ btnIpProxyServiceLogin?.addEventListener('click', () => { const config = getIpProxyServiceLoginConfig(service); const loginUrl = getIpProxyServiceLoginUrl(service); if (!config || !loginUrl) { - showToast('当前代理服务没有可跳转的登录页。', 'warn', 1800); + showToast('The current proxy service has no login page to open.', 'warn', 1800); return; } openExternalUrl(loginUrl); @@ -14931,7 +14838,7 @@ hotmailServiceModeButtons.forEach((button) => { btnSaveSettings.addEventListener('click', async () => { if (!settingsDirty) { - showToast('配置已是最新', 'info', 1400); + showToast('Config is already up to date', 'info', 1400); return; } await saveSettings({ silent: false }).catch(() => { }); @@ -14940,7 +14847,7 @@ btnSaveSettings.addEventListener('click', async () => { btnStop.addEventListener('click', async () => { btnStop.disabled = true; await chrome.runtime.sendMessage({ type: 'STOP_FLOW', source: 'sidepanel', payload: {} }); - showToast(currentAutoRun.phase === 'waiting_interval' ? '正在取消等待中的倒计时...' : '正在停止当前流程...', 'warn', 2000); + showToast(currentAutoRun.phase === 'waiting_interval' ? 'Canceling the waiting countdown...' : 'Stopping the current flow...', 'warn', 2000); }); btnConfigMenu?.addEventListener('click', (event) => { @@ -15024,7 +14931,7 @@ async function startAutoRunFromCurrentSettings() { : getRunCountValue(); registerPendingAutoRunStartRunCount(requestedTotalRuns); - // 站点内容刷新只影响提示/广告展示,不应阻塞自动流程启动。 + // Content refresh only affects notices and ads. It should not block auto-flow startup. refreshContributionContentHint().catch((error) => { console.warn('Failed to refresh contribution content hint before auto run:', error); }); @@ -15063,7 +14970,7 @@ async function startAutoRunFromCurrentSettings() { })(); if (autoRunStartValidation?.ok === false) { clearPendingAutoRunStartRunCount(); - throw new Error(autoRunStartValidation.errors?.[0]?.message || '当前设置不支持启动自动流程。'); + throw new Error(autoRunStartValidation.errors?.[0]?.message || 'The current settings do not support starting the auto flow.'); } if (!(await ensureGpcApiKeyReadyForStart())) { clearPendingAutoRunStartRunCount(); @@ -15076,7 +14983,7 @@ async function startAutoRunFromCurrentSettings() { ? getLockedRunCountFromEmailPool() : 0; if (customEmailPoolEnabled && lockedRunCount <= 0) { - throw new Error('请先在邮箱池里至少填写 1 个邮箱。'); + throw new Error('Please add at least 1 email to the email pool first.'); } const totalRuns = lockedRunCount > 0 ? lockedRunCount : requestedTotalRuns; registerPendingAutoRunStartRunCount(totalRuns); @@ -15123,7 +15030,7 @@ async function startAutoRunFromCurrentSettings() { const targetId = typeof getSelectedTargetId === 'function' ? getSelectedTargetId(activeFlowId) : normalizeTargetIdForFlow(activeFlowId, latestState?.targetId || '', getDefaultTargetIdForFlow(activeFlowId)); - btnAutoRun.innerHTML = ' 运行中...'; + btnAutoRun.innerHTML = ' Running...'; const response = await sendSidepanelMessage({ type: 'AUTO_RUN', source: 'sidepanel', @@ -15162,7 +15069,7 @@ btnAutoContinue.addEventListener('click', async () => { const email = inputEmail.value.trim(); if (!email) { showToast( - isCustomMailProvider() ? '请先填写自定义注册邮箱。' : '请先获取或粘贴邮箱。', + isCustomMailProvider() ? 'Please enter the custom signup email first.' : 'Please fetch or paste an email first.', 'warn' ); return; @@ -15179,7 +15086,7 @@ btnAutoRunNow?.addEventListener('click', async () => { source: 'sidepanel', payload: {}, }); - showToast('已跳过当前倒计时,自动流程将立即继续。', 'info', 1800); + showToast('Skipped the current countdown. The auto flow will continue immediately.', 'info', 1800); } catch (err) { showToast(err.message, 'error'); } finally { @@ -15190,9 +15097,9 @@ btnAutoRunNow?.addEventListener('click', async () => { // Reset btnReset.addEventListener('click', async () => { const confirmed = await openConfirmModal({ - title: '重置流程', - message: '确认重置全部步骤和数据吗?', - confirmLabel: '确认重置', + title: 'Reset flow', + message: 'Reset all steps and data?', + confirmLabel: 'Confirm reset', confirmVariant: 'btn-danger', }); if (!confirmed) { @@ -15220,11 +15127,11 @@ btnReset.addEventListener('click', async () => { autoRunCountdownTitle: '', autoRunCountdownNote: '', }); - displayOauthUrl.textContent = '等待中...'; + displayOauthUrl.textContent = 'Waiting...'; displayOauthUrl.classList.remove('has-value'); - displayLocalhostUrl.textContent = '等待中...'; + displayLocalhostUrl.textContent = 'Waiting...'; displayLocalhostUrl.classList.remove('has-value'); - setKiroRsConnectionTestStatus('未测试'); + setKiroRsConnectionTestStatus('Not tested'); inputEmail.value = ''; if (typeof inputSignupPhone !== 'undefined' && inputSignupPhone) { inputSignupPhone.value = ''; @@ -15232,7 +15139,7 @@ btnReset.addEventListener('click', async () => { if (typeof syncSignupPhoneInputFromState === 'function') { syncSignupPhoneInputFromState(latestState); } - displayStatus.textContent = '就绪'; + displayStatus.textContent = 'Ready'; statusBar.className = 'status-bar'; logArea.innerHTML = ''; resetIcloudManager(); @@ -15425,7 +15332,7 @@ btnGpcHelperBalance?.addEventListener('click', async () => { throw new Error(response.error); } if (displayGpcHelperBalance) { - displayGpcHelperBalance.textContent = response?.balance || '余额已更新'; + displayGpcHelperBalance.textContent = response?.balance || 'Balance updated'; } const nextState = { gopayHelperBalance: response?.balance || latestState?.gopayHelperBalance || '', @@ -15442,25 +15349,25 @@ btnGpcHelperBalance?.addEventListener('click', async () => { const selectedModeBeforeBalanceState = getSelectedGpcHelperPhoneMode(); syncLatestState(nextState); if (nextAutoModeDenied && selectedModeBeforeBalanceState === GPC_HELPER_PHONE_MODE_AUTO) { - showToast('当前 API Key 未开通自动模式,已保留当前选择;如需继续请手动切换到手动模式。', 'warn'); + showToast('The current API key does not have Auto mode enabled. Your current selection was kept. Switch to Manual mode to continue.', 'warn'); } else if (nextAutoModeDenied) { - showToast('GPC 余额已更新,当前 API Key 只能使用手动模式。', 'success'); + showToast('GPC balance updated. The current API key supports Manual mode only.', 'success'); } else if (nextAutoModeConfirmed) { - showToast('GPC 余额已更新,自动模式可用。', 'success'); + showToast('GPC balance updated. Auto mode is available.', 'success'); } else { - showToast('GPC 余额已更新,当前接口未返回自动模式权限,已保留所选模式。', 'success'); + showToast('GPC balance updated. The current API did not return Auto mode permission, so the selected mode was kept.', 'success'); } updatePlusModeUI(); } catch (error) { - showToast(error?.message || '查询 GPC 余额失败。', 'error'); + showToast(error?.message || 'Failed to query GPC balance.', 'error'); } }); btnTestKiroRs?.addEventListener('click', async () => { - const defaultLabel = btnTestKiroRs.textContent || '测试'; + const defaultLabel = btnTestKiroRs.textContent || 'Test'; btnTestKiroRs.disabled = true; - btnTestKiroRs.textContent = '测试中'; - setKiroRsConnectionTestStatus('测试中...'); + btnTestKiroRs.textContent = 'Testing'; + setKiroRsConnectionTestStatus('Testing...'); try { await persistCurrentSettingsForAction(); const activeFlowId = typeof getSelectedFlowId === 'function' @@ -15481,11 +15388,11 @@ btnTestKiroRs?.addEventListener('click', async () => { if (response?.error) { throw new Error(response.error); } - const message = String(response?.message || '').trim() || 'kiro.rs 测试完成。'; + const message = String(response?.message || '').trim() || 'kiro.rs test completed.'; setKiroRsConnectionTestStatus(message); showToast(message, response?.ok ? 'success' : 'error', response?.ok ? 2200 : 4200); } catch (error) { - const message = error?.message || 'kiro.rs 测试失败。'; + const message = error?.message || 'kiro.rs test failed.'; setKiroRsConnectionTestStatus(message); showToast(message, 'error', 4200); } finally { @@ -15765,7 +15672,7 @@ selectPlusAccountAccessStrategy?.addEventListener('change', () => { [inputKiroRsUrl, inputKiroRsKey].forEach((input) => { input?.addEventListener('input', () => { markSettingsDirty(true); - setKiroRsConnectionTestStatus('未测试'); + setKiroRsConnectionTestStatus('Not tested'); scheduleSettingsAutoSave(); }); input?.addEventListener('blur', () => { @@ -15911,7 +15818,7 @@ ipProxyModeButtons.forEach((button) => { if (!apiModeAvailable && nextMode === 'api') { setIpProxyMode('account'); updateIpProxyUI(latestState); - showToast('API 模式暂未开放,请先使用账号密码模式。', 'info', 1800); + showToast('API mode is not available yet. Use account/password mode first.', 'info', 1800); return; } if (getSelectedIpProxyMode() === nextMode) { @@ -15948,7 +15855,7 @@ btnIpProxyRefresh?.addEventListener('click', async () => { return; } } catch (err) { - showToast(err?.message || String(err || '未知错误'), 'error'); + showToast(err?.message || String(err || 'Unknown error'), 'error'); } }); @@ -15968,7 +15875,7 @@ btnIpProxyNext?.addEventListener('click', async () => { return; } } catch (err) { - showToast(err?.message || String(err || '未知错误'), 'error'); + showToast(err?.message || String(err || 'Unknown error'), 'error'); } }); @@ -15988,7 +15895,7 @@ btnIpProxyChange?.addEventListener('click', async () => { return; } } catch (err) { - showToast(err?.message || String(err || '未知错误'), 'error'); + showToast(err?.message || String(err || 'Unknown error'), 'error'); } }); @@ -16008,7 +15915,7 @@ btnIpProxyProbe?.addEventListener('click', async () => { return; } } catch (err) { - showToast(err?.message || String(err || '未知错误'), 'error'); + showToast(err?.message || String(err || 'Unknown error'), 'error'); } }); @@ -16016,7 +15923,7 @@ btnIpProxyCheckIp?.addEventListener('click', async () => { try { await chrome.tabs.create({ url: 'https://ipinfo.io/what-is-my-ip' }); } catch (err) { - showToast(`打开 IP 检测页失败:${err?.message || String(err || '未知错误')}`, 'error'); + showToast(`Failed to open the IP check page: ${err?.message || String(err || 'Unknown error')}`, 'error'); } }); @@ -16045,7 +15952,7 @@ btnCfDomainMode.addEventListener('click', async () => { const newDomain = normalizeCloudflareDomainValue(inputCfDomain.value); if (!newDomain) { - showToast('请输入有效的 Cloudflare 域名。', 'warn'); + showToast('Please enter a valid Cloudflare domain.', 'warn'); inputCfDomain.focus(); return; } @@ -16126,7 +16033,7 @@ inputSub2ApiAccountPriority.addEventListener('blur', () => { btnAddSub2ApiGroup?.addEventListener('click', () => { handleAddSub2ApiGroup().catch((error) => { - showToast(error?.message || '添加 SUB2API 分组失败。', 'error'); + showToast(error?.message || 'Failed to add SUB2API group.', 'error'); }); }); @@ -16708,8 +16615,8 @@ async function switchPhoneSmsProvider(nextProvider) { if (inputFiveSimOperator) { inputFiveSimOperator.value = normalizeFiveSimOperator(latestState?.fiveSimOperator); } - if (displayHeroSmsPriceTiers) displayHeroSmsPriceTiers.textContent = '未获取'; - if (displayPhoneSmsBalance) displayPhoneSmsBalance.textContent = '余额未获取'; + if (displayHeroSmsPriceTiers) displayHeroSmsPriceTiers.textContent = 'Not fetched'; + if (displayPhoneSmsBalance) displayPhoneSmsBalance.textContent = 'Balance not fetched'; if (rowHeroSmsPriceTiers) rowHeroSmsPriceTiers.style.display = 'none'; await loadHeroSmsCountries({ silent: true }); @@ -16733,7 +16640,7 @@ async function switchPhoneSmsProvider(nextProvider) { selectPhoneSmsProvider?.addEventListener('change', () => { switchPhoneSmsProvider(selectPhoneSmsProvider.value).catch((error) => { - showToast(`切换接码平台失败:${error?.message || error}`, 'warn', 2200); + showToast(`Failed to switch SMS verification platform: ${error?.message || error}`, 'warn', 2200); }); }); @@ -16743,7 +16650,7 @@ inputPhoneVerificationEnabled?.addEventListener('change', () => { } else { setSignupMethod(SIGNUP_METHOD_EMAIL); updatePhoneVerificationSettingsUI(); - showToast('已切回邮箱注册', 'info', 1600); + showToast('Switched back to email signup', 'info', 1600); } syncStepDefinitionsFromUiState({ phoneVerificationEnabled: Boolean(inputPhoneVerificationEnabled.checked), @@ -16974,13 +16881,13 @@ inputFreePhoneReuseAutoEnabled?.addEventListener('change', () => { btnSaveFreeReusablePhone?.addEventListener('click', async () => { if (isPhoneSignupReuseLocked(latestState)) { - showToast?.('手机号注册流程不能记录白嫖复用号码,请切回邮箱注册后再使用。', 'warn', 2600); + showToast?.('The phone signup flow cannot record free-reuse phone numbers. Switch back to email signup before using this.', 'warn', 2600); updatePhoneVerificationSettingsUI(); return; } const phoneNumber = String(inputFreeReusablePhone?.value || '').trim(); if (!phoneNumber) { - showToast?.('请先填写白嫖复用手机号。', 'warn', 2200); + showToast?.('Please enter the free-reuse phone number first.', 'warn', 2200); inputFreeReusablePhone?.focus?.(); return; } @@ -16992,10 +16899,10 @@ btnSaveFreeReusablePhone?.addEventListener('click', async () => { throw new Error(response.error); } await refreshFreeReusablePhoneStateFallback(response || {}); - showToast?.('已记录白嫖复用手机号。', 'success', 1800); + showToast?.('Free-reuse phone number recorded.', 'success', 1800); } catch (error) { console.error('Failed to save free reusable phone:', error); - showToast?.(`记录白嫖复用手机号失败:${error?.message || error}`, 'error', 4000); + showToast?.(`Failed to record the free-reuse phone number: ${error?.message || error}`, 'error', 4000); } }); @@ -17008,10 +16915,10 @@ btnClearFreeReusablePhone?.addEventListener('click', async () => { throw new Error(response.error); } await refreshFreeReusablePhoneStateFallback(response || {}, { clear: true }); - showToast?.('已清除白嫖复用手机号。', 'info', 1800); + showToast?.('Free-reuse phone number cleared.', 'info', 1800); } catch (error) { console.error('Failed to clear free reusable phone:', error); - showToast?.(`清除白嫖复用手机号失败:${error?.message || error}`, 'error', 4000); + showToast?.(`Failed to clear the free-reuse phone number: ${error?.message || error}`, 'error', 4000); } }); @@ -17086,11 +16993,11 @@ btnHeroSmsPricePreview?.addEventListener('click', async () => { try { await previewHeroSmsPriceTiers(); if (typeof showToast === 'function') { - showToast('已刷新接码国家价格预览。', 'info', 1600); + showToast('SMS verification country price preview refreshed.', 'info', 1600); } } catch (error) { if (typeof showToast === 'function') { - showToast(`价格预览失败:${error?.message || error}`, 'warn', 2200); + showToast(`Price preview failed: ${error?.message || error}`, 'warn', 2200); } } }); @@ -17099,11 +17006,11 @@ btnPhoneSmsBalance?.addEventListener('click', async () => { try { await previewPhoneSmsBalance(); if (typeof showToast === 'function') { - showToast('已刷新接码平台余额。', 'info', 1600); + showToast('SMS verification platform balance refreshed.', 'info', 1600); } } catch (error) { if (typeof showToast === 'function') { - showToast(`余额查询失败:${error?.message || error}`, 'warn', 2200); + showToast(`Balance query failed: ${error?.message || error}`, 'warn', 2200); } } }); @@ -17220,7 +17127,7 @@ btnHeroSmsCountryClear?.addEventListener('click', () => { markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); if (typeof showToast === 'function') { - showToast('已清空国家优先级。', 'info', 1800); + showToast('Country priority cleared.', 'info', 1800); } }); @@ -17247,7 +17154,7 @@ btnFiveSimCountryClear?.addEventListener('click', () => { markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); if (typeof showToast === 'function') { - showToast('已清空国家优先级。', 'info', 1800); + showToast('Country priority cleared.', 'info', 1800); } }); @@ -17275,7 +17182,7 @@ btnNexSmsCountryClear?.addEventListener('click', () => { markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); if (typeof showToast === 'function') { - showToast('已清空国家优先级。', 'info', 1800); + showToast('Country priority cleared.', 'info', 1800); } }); @@ -17310,7 +17217,7 @@ btnPhoneSmsProviderOrderReset?.addEventListener('click', () => { markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); if (typeof showToast === 'function') { - showToast('已清空服务商顺序。', 'info', 1800); + showToast('Provider order cleared.', 'info', 1800); } }); @@ -17343,10 +17250,10 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { case 'SECURITY_BLOCKED_ALERT': { openConfirmModal({ - title: message.payload?.title || '流程已完全停止', - message: message.payload?.message || '检测到安全风控,当前流程已完全停止。', - alert: message.payload?.alert || { text: '检测到 Cloudflare 风控,请暂停当前操作。', tone: 'danger' }, - confirmLabel: '我知道了', + title: message.payload?.title || 'Flow fully stopped', + message: message.payload?.message || 'A security risk control was detected. The current flow has been fully stopped.', + alert: message.payload?.alert || { text: 'Cloudflare risk control detected. Please pause the current action.', tone: 'danger' }, + confirmLabel: 'I understand', confirmVariant: 'btn-danger', }).catch(() => { }); break; @@ -17397,9 +17304,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { autoRunCountdownTitle: '', autoRunCountdownNote: '', }); - displayOauthUrl.textContent = '等待中...'; + displayOauthUrl.textContent = 'Waiting...'; displayOauthUrl.classList.remove('has-value'); - displayLocalhostUrl.textContent = '等待中...'; + displayLocalhostUrl.textContent = 'Waiting...'; displayLocalhostUrl.classList.remove('has-value'); inputEmail.value = ''; if (typeof inputSignupPhone !== 'undefined' && inputSignupPhone) { @@ -17413,7 +17320,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (typeof syncSignupPhoneInputFromState === 'function') { syncSignupPhoneInputFromState(latestState); } - displayStatus.textContent = '就绪'; + displayStatus.textContent = 'Ready'; statusBar.className = 'status-bar'; logArea.innerHTML = ''; resetIcloudManager(); @@ -17666,11 +17573,11 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { updateIpProxyUI(latestState); } if (message.payload.oauthUrl !== undefined) { - displayOauthUrl.textContent = message.payload.oauthUrl || '等待中...'; + displayOauthUrl.textContent = message.payload.oauthUrl || 'Waiting...'; displayOauthUrl.classList.toggle('has-value', Boolean(message.payload.oauthUrl)); } if (message.payload.localhostUrl !== undefined) { - displayLocalhostUrl.textContent = message.payload.localhostUrl || '等待中...'; + displayLocalhostUrl.textContent = message.payload.localhostUrl || 'Waiting...'; displayLocalhostUrl.classList.toggle('has-value', Boolean(message.payload.localhostUrl)); } if (message.payload.cloudflareTempEmailBaseUrl !== undefined) { @@ -17747,8 +17654,8 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { const balanceText = String(message.payload.gopayHelperBalance ?? latestState?.gopayHelperBalance ?? '').trim(); const balanceError = String(message.payload.gopayHelperBalanceError ?? latestState?.gopayHelperBalanceError ?? '').trim(); displayGpcHelperBalance.textContent = balanceError - ? `余额查询失败:${balanceError}` - : (balanceText || '余额已更新'); + ? `Balance query failed: ${balanceError}` + : (balanceText || 'Balance updated'); } } if ( @@ -18155,7 +18062,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { } case 'ICLOUD_LOGIN_REQUIRED': { - const loginMessage = '需要登录 iCloud,我已经为你打开登录页。'; + const loginMessage = 'iCloud login is required. I have opened the login page for you.'; showToast(loginMessage, 'warn', 5000); if (icloudSummary) { icloudSummary.textContent = loginMessage; diff --git a/sidepanel/update-service.js b/sidepanel/update-service.js index d3cf79fc..9356f800 100644 --- a/sidepanel/update-service.js +++ b/sidepanel/update-service.js @@ -309,12 +309,12 @@ }); if (!response.ok) { - throw new Error(`GitHub Releases 请求失败(${response.status})`); + throw new Error(`GitHub Releases request failed (${response.status})`); } const payload = await response.json(); if (!Array.isArray(payload)) { - throw new Error('GitHub Releases 返回格式异常'); + throw new Error('GitHub Releases response format error'); } const releases = payload @@ -328,7 +328,7 @@ return sortedReleases; } catch (error) { if (error?.name === 'AbortError') { - throw new Error('GitHub Releases 请求超时'); + throw new Error('GitHub Releases request timed out'); } throw error; } finally { @@ -422,7 +422,7 @@ logUrl: RELEASES_PAGE_URL, releasesPageUrl: RELEASES_PAGE_URL, checkedAt: Date.now(), - errorMessage: error?.message || '更新检查失败', + errorMessage: error?.message || 'Update check failed', }; } }