fix(audit): 复查第三波 — Hermes 安装向导 i18n 化漏网中文

第三轮逐文件复查发现 hermes/pages/setup.js 还有大量用户首屏可见的
中文硬编码(之前两轮主要扫 timer/cleanup/syntax/listener 类 bug)。

## 涉及修复

### setup.js phase 标签(5 处)
phase indicator 是用户最早看到的 UI 元素:
```
{ id: 'detect',    label: '检测' }   →  t('engine.hermesPhaseDetect')
{ id: 'install',   label: '安装' }   →  t('engine.hermesPhaseInstall')
{ id: 'configure', label: '配置' }   →  t('engine.hermesPhaseConfigure')
{ id: 'gateway',   label: '启动' }   →  t('engine.hermesPhaseGateway')
{ id: 'complete',  label: '完成' }   →  t('engine.hermesPhaseComplete')
```
英文用户原本看到的是 5 个汉字,现在按系统语言显示
Detect / Install / Configure / Start / Complete。

### setup.js Provider 分组标题(4 组)
configure 阶段渲染 provider 选择按钮时分组标题:
```
'国际 · API Key'        →  hermesProviderGroupIntl
'国内 · API Key'        →  hermesProviderGroupCn
'聚合 / 路由'           →  hermesProviderGroupAggregator
'OAuth 登录(需终端)'   →  hermesProviderGroupOAuth
'<provider>:需运行'     →  hermesProviderOAuthRunHint
```

### setup.js Provider 加载失败 fallback 文案
Web 模式下 provider 列表加载失败时的 12 行多行 hint,原本只有中文,
现已 11 语言 i18n。

### setup.js 检测异常日志 prefix
```js
logs.push(`检测错误: ${e}`)  →  `[detect error] ${e?.message || e}`
```
日志输出(用户在向导首屏可见)跟其他英文 prefix 一致,便于日志聚合。

## 新增 i18n key(engine 模块)
- hermesPhaseDetect / Install / Configure / Gateway / Complete
- hermesProviderGroupIntl / Cn / Aggregator / OAuth
- hermesProviderOAuthRunHint
- hermesProvidersLoadFallback

11 语言全覆盖(zh-CN / zh-TW / en / ja / ko / vi / es / pt / ru / fr / de)。

## 复查范围(第三轮)
- src-tauri/src/commands/*.rs  unwrap 全 safe(regex / SystemTime / Mutex)
- src/lib/  humanize-error / engine-manager / ws-client / message-db / tts 全部健全
- src/components/  modal / toast 无问题
- scripts/dev-api.js  176 个 handler 无 duplicate / 关键命令全有
- src/pages/  没发现新硬编码(之前的 i18n 覆盖到位)
- src/engines/hermes/pages/  setup.js 是唯一遗漏的中文重灾区,已修

## 验证
- npm run build:PASS(1.46s)
- 总计三轮复查共修 11 个 bug + 5 个 i18n 漏网
This commit is contained in:
晴天
2026-05-14 07:41:01 +08:00
parent d30714d406
commit 2256c2c711
2 changed files with 37 additions and 12 deletions

View File

@@ -67,11 +67,11 @@ export function render() {
// --- 阶段指示器 ---
function renderPhaseIndicator() {
const phases = [
{ id: 'detect', label: '检测' },
{ id: 'install', label: '安装' },
{ id: 'configure', label: '配置' },
{ id: 'gateway', label: '启动' },
{ id: 'complete', label: '完成' },
{ id: 'detect', label: t('engine.hermesPhaseDetect') },
{ id: 'install', label: t('engine.hermesPhaseInstall') },
{ id: 'configure', label: t('engine.hermesPhaseConfigure') },
{ id: 'gateway', label: t('engine.hermesPhaseGateway') },
{ id: 'complete', label: t('engine.hermesPhaseComplete') },
]
const idx = phases.findIndex(p => p.id === phase)
return `<div class="hermes-phases">${phases.map((p, i) => {
@@ -420,7 +420,7 @@ export function render() {
}
draw()
} catch (e) {
logs.push(`检测错误: ${e}`)
logs.push(`[detect error] ${e?.message || e}`)
phase = 'install'
draw()
}
@@ -686,7 +686,7 @@ export function render() {
function renderGroupedProviderButtons() {
if (!hermesProviders.length) {
return `<div style="padding:10px 12px;background:var(--bg-tertiary);border-radius:var(--radius-sm,6px);color:var(--text-secondary);font-size:12px;line-height:1.6">
未能加载 provider 列表。Web 模式下可手动填写下方 Base URL 与 API Key 完成配置。
${t('engine.hermesProvidersLoadFallback')}
</div>`
}
@@ -708,19 +708,19 @@ function renderGroupedProviderButtons() {
const parts = []
if (hermesGroups.apiKeyIntl.length) {
parts.push(`<div style="${sectionStyle}"><div style="${titleStyle}">国际 · API Key</div><div style="${rowStyle}">${hermesGroups.apiKeyIntl.map(btn).join('')}</div></div>`)
parts.push(`<div style="${sectionStyle}"><div style="${titleStyle}">${t('engine.hermesProviderGroupIntl')}</div><div style="${rowStyle}">${hermesGroups.apiKeyIntl.map(btn).join('')}</div></div>`)
}
if (hermesGroups.apiKeyCn.length) {
parts.push(`<div style="${sectionStyle}"><div style="${titleStyle}">国内 · API Key</div><div style="${rowStyle}">${hermesGroups.apiKeyCn.map(btn).join('')}</div></div>`)
parts.push(`<div style="${sectionStyle}"><div style="${titleStyle}">${t('engine.hermesProviderGroupCn')}</div><div style="${rowStyle}">${hermesGroups.apiKeyCn.map(btn).join('')}</div></div>`)
}
if (hermesGroups.aggregators.length) {
parts.push(`<div style="${sectionStyle}"><div style="${titleStyle}">聚合 / 路由</div><div style="${rowStyle}">${hermesGroups.aggregators.map(btn).join('')}</div></div>`)
parts.push(`<div style="${sectionStyle}"><div style="${titleStyle}">${t('engine.hermesProviderGroupAggregator')}</div><div style="${rowStyle}">${hermesGroups.aggregators.map(btn).join('')}</div></div>`)
}
if (hermesGroups.oauth.length) {
const oauthItems = hermesGroups.oauth.map(p =>
`<div style="font-size:11px;color:var(--text-tertiary);margin-right:10px"><code>${p.name}</code>需运行 <code>${p.cliAuthHint}</code></div>`
`<div style="font-size:11px;color:var(--text-tertiary);margin-right:10px"><code>${p.name}</code>${t('engine.hermesProviderOAuthRunHint') || '需运行'} <code>${p.cliAuthHint}</code></div>`
).join('')
parts.push(`<div style="${sectionStyle}"><div style="${titleStyle}">OAuth 登录(需终端)</div><div style="display:flex;flex-wrap:wrap;gap:4px 0">${oauthItems}</div></div>`)
parts.push(`<div style="${sectionStyle}"><div style="${titleStyle}">${t('engine.hermesProviderGroupOAuth')}</div><div style="display:flex;flex-wrap:wrap;gap:4px 0">${oauthItems}</div></div>`)
}
return parts.join('')