Skip to content

fix(translation): 翻译不会生效时不再进入翻译态(胶囊误报 + 空转 LLM) - #908

Merged
H-Chris233 merged 4 commits into
Open-Less:betafrom
bigsongeth:fix/translation-target-guards
Aug 5, 2026
Merged

fix(translation): 翻译不会生效时不再进入翻译态(胶囊误报 + 空转 LLM)#908
H-Chris233 merged 4 commits into
Open-Less:betafrom
bigsongeth:fix/translation-target-guards

Conversation

@bigsongeth

@bigsongethbigsongeth commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

User description ## 问题 「翻译」这个功能有两个入口条件,但判定散落在读取侧,胶囊那一侧漏了一个条件。 1. 没设目标语言,按 Shift 也会显示「正在翻译」 后端判定翻译要同时满足「按过 Shift」和「设了目标语言」(dictation.rs),但胶囊只看了前者(capsule_focus.rs): rust let translation = !selection_polish && inner.translation_modifier_seen.load(Ordering::SeqCst); 于是用户没在翻译页选目标语言、录音中按了 Shift → 光标处显示「正在翻译」,而 end_session 走的是普通润色,什么也没翻。日志里能同时看到这两行: [coord] translation modifier seen during Listening [style-pack] runtime dispatch ... translation_active=false2. 目标语言 = 唯一的工作语言时,仍然发起一次翻译 工作语言只勾了简体中文、翻译目标也选简体中文 —— 源语言必定就是目标语言,翻译是可证的空操作,但仍然照常发起一次 LLM 往返: [coord] translation mode → target=「简体中文」 working=["简体中文"] [llm] POST ... provider=codex_oauth model=gpt-5.5 [llm] codex HTTP 200 stream done; total chars=67 3.4 秒 + 一次 token 消耗,换回一段和输入同语言的文字。 ## 改动 新增 types::translation_effective 作为唯一判定入口,并把判定收到写入侧(arm_translation_if_effective)。 判定放在写入侧而不是读取侧,是因为读取侧之一是 emit_capsule —— 它在音频回调线程上按帧执行,不能碰偏好锁(capsule_focus.rs 既有注释已经就此立过规矩,capsule_style 就是为此走的原子缓存)。而 arm_translation_if_effective 每次按下修饰键才跑一次,读一次 prefs 无所谓。 收紧后 translation_active 的语义从「按过 Shift」变成「本次会话真的要翻译」,胶囊提示与 polish 分派读同一个真值,不会再漂移。 刻意不拦的情况: - 工作语言有多个时不拦。中/英双语用户把目标设成英文是正常用法(说中文出英文),源语言无法预先判定。 - 简体 → 繁体照常翻译。它们是语言列表里两个独立条目,简→繁是真实转换。 安卓浮层的 start_dictation_with_translation 一并走同一个 gate。 「按了但不翻」会记一条 INFO 说明原因,否则用户只看到胶囊没提示,无从判断是没生效还是没按到。 前端同步:翻译页在「目标语言 = 唯一工作语言」时给出提示并给两条出路(换目标语言 / 多勾一个工作语言),状态灯不再谎报「已启用」。判定逻辑抽成 lib/translationTarget.ts,与后端同一套规则。 ## 验证 - 后端 6 个单测 + 前端 9 条断言,钉住「多工作语言不拦」「简→繁不误判」两条边界 - cargo check / cargo test --lib / npm testpretest 自动跑 npm run build) 全绿 - 本地装机实测 🤖 Generated with Claude Code ___ ### PR Type Bug fix ___ ### Description - Central translation_effective guard prevents no-op LLM translations. - Sets flag only when translation is genuinely effective. - Warns users when target equals sole working language. - Adds tests for backend and frontend translation guards. ___ ### Diagram Walkthrough mermaid flowchart LR A["Shift pressed / overlay start"] --> B["translation_effective gate"] B -->|"not effective"| C["plain polish (no LLM)"] B -->|"effective"| D["translation mode"] D --> E["same flag for capsule and dispatch"]

File Walkthrough

Relevant files
Bug fix
6 files
coordinator.rs
Update translation flag comment and Android overlay gate
+9/-7
dictation.rs
Use translation_effective for polish/translate dispatch
+5/-2
hotkey_loops.rs
Arm translation_active flag with effectiveness check
+29/-5
types.rs
Add translation_effective helper with unit tests
+94/-0
translationTarget.ts
Add frontend translation availability helpers
+24/-0
Translation.tsx
Warn when translation target is redundant
+25/-1
Localization
5 files
en.ts
Add English warning for redundant target
+1/-0
ja.ts
Add Japanese warning for redundant target
+1/-0
ko.ts
Add Korean warning for redundant target
+1/-0
zh-CN.ts
Add Simplified Chinese warning for redundant target
+1/-0
zh-TW.ts
Add Traditional Chinese warning for redundant target
+1/-0
Tests
1 files
translationTarget.test.ts
Add tests for translation target helpers
+42/-0
___

bigsongethand others added 2 commits August 4, 2026 14:21
两个症状同源:`translation_modifier_seen` 的语义只是「按过 Shift」,
而「是否真的翻译」的判定散落在读取侧,胶囊那一侧漏了目标语言检查。
- 没在翻译页选目标语言,录音中按 Shift → 光标处显示「正在翻译」,
但 end_session 走的是普通润色,什么也没翻。
- 目标语言等于用户唯一的工作语言(例:工作语言只勾了简体中文、目标也选
简体中文)→ 源语言必定就是目标语言,仍会照常发起一次 LLM 翻译往返,
纯浪费时延和 token。
新增 `types::translation_effective` 作为唯一判定入口,并把判定收到写入侧
(`mark_translation_modifier_seen`):该函数每次按键才跑一次,读一次
prefs 无所谓;而读取侧之一是音频回调线程上的 emit_capsule,按帧执行,
不能碰偏好锁(capsule_focus.rs 既有注释已就此立过规矩)。收紧后 flag 的
语义变成「本次会话真的要翻译」,胶囊提示与 polish 分派读同一个真值。
工作语言有多个时不拦:中/英双语用户把目标设成英文是正常用法(说中文出
英文),源语言无法预先判定。简体/繁体是语言列表里两个独立条目,简→繁
仍照常翻译。安卓浮层的 start_dictation_with_translation 一并走同一个 gate。
「按了但不翻」的情况会记一条 INFO 说明原因,否则用户只看到胶囊没提示,
无从判断是没生效还是没按到。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
后端已经不会在这种组合下发起翻译(上一个 commit),但用户在设置页看不出
任何异常:状态灯仍写「已启用」,按 Shift 却什么也不会发生,只能靠翻日志
才知道为什么。
- 翻译目标语言卡片下方出现一条警示:说明这个组合不会生效,并给出两条
出路(换目标语言,或多勾一个工作语言)。
- 「已启用 / 未启用」状态灯改为同时看目标语言和这个冗余判定,不再谎报。
判定逻辑抽成 `lib/translationTarget.ts`,与后端 `translation_effective`
同一套规则(多工作语言不拦、简→繁不误判),两侧各带单测钉住契约。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ No major issues detected

审查跟进(PR Open-Less#908):mark_translation_active 的实际行为是「只在翻译会
生效时置位并返回是否置位」,名字却像无条件 setter,改为
arm_translation_if_effective 更诚实;translation_effective 里的
matches!([only] if ...) 改写为显式 len==1 + trim 相等(语义完全等价)。
行为零变化:translation_effective 6 个单测全过(backend-tests),
cargo check 全绿。
@H-Chris233
H-Chris233 merged commit cd58759 into Open-Less:betaAug 5, 2026
4 checks passed
bigsongeth added a commit to bigsongeth/openless that referenced this pull request Aug 5, 2026
beta 一次进了 13 个提交,其中三条正是本地 daily 上那批私货合上游了
(Open-Less#910 历史落点+重试、Open-Less#908 翻译、Open-Less#909 概览),daily 的 patch queue 相应缩小。
冲突只有 selection.rs 一处,而且 HEAD 侧是空的:beta 的选区润色 macOS 移植
(Open-Less#926)在同一位置加了 current_front_app_pid 与 Windows/其他平台的
current_front_app,本分支在附近加了 bundle_id_for_pid(闸门按元素归属判定用)。
两者互不相干,取 beta 侧即可。
解完核对:bundle_id_for_pid 仍在(1 处),current_front_app 三个定义分属
macOS / Windows / 其他平台且 cfg 互斥,current_front_app_pid 无重复。
验证:cargo check 干净、cargo test --lib 1032 passed(beta 带来 30 个新用例)、
tsc --noEmit 干净、npm test 通过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bigsongeth@H-Chris233