Skip to content

feat: integrate model discovery and fix keychain authorization popups#74

Merged
hubo1989 merged 2 commits into
mainfrom
feature/model-discovery-and-keychain-fixes
May 28, 2026
Merged

feat: integrate model discovery and fix keychain authorization popups#74
hubo1989 merged 2 commits into
mainfrom
feature/model-discovery-and-keychain-fixes

Conversation

@hubo1989
Copy link
Copy Markdown
Owner

@hubo1989 hubo1989 commented May 27, 2026

Summary

Brief description of the changes in this PR.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)

Related Issues

Fixes #(issue number)

Changes Made

  • Change 1
  • Change 2
  • Change 3

Testing

Describe the tests you ran to verify your changes:

  • Full screen capture works
  • Selection capture works
  • Annotation tools function correctly
  • Undo/redo works
  • Save and copy to clipboard work
  • Settings persist correctly
  • No memory leaks observed

Screenshots

If applicable, add screenshots showing the changes.

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code where necessary
  • I have updated the documentation accordingly
  • My changes generate no new warnings
  • I have tested on macOS 13+
  • I have tested with multiple displays (if applicable)

Summary by CodeRabbit

发布说明

  • 新功能

    • 新增模型发现与获取功能,用户可从翻译引擎获取可用模型列表
    • 优化权限授权流程,支持屏幕录制和无障碍功能的拖拽授权
    • 增加源语言自动检测,避免相同语言间的重复翻译
  • 改进

    • 增强兼容API端点和本地主机地址处理
    • 改进翻译结果清理与错误恢复能力
    • 多语言本地化字符串支持
  • 测试

    • 扩展测试覆盖,包括模型发现和翻译流程验证

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Warning

Review limit reached

@hubo1989, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 23 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9dca6fca-01db-477a-991c-ec0eff31b502

📥 Commits

Reviewing files that changed from the base of the PR and between 01c57a7 and 0f14c16.

📒 Files selected for processing (17)
  • ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swift
  • ScreenTranslate/Models/AppSettings.swift
  • ScreenTranslate/Services/AppleTranslationProvider.swift
  • ScreenTranslate/Services/MTranServerEngine.swift
  • ScreenTranslate/Services/ModelDiscoveryService.swift
  • ScreenTranslate/Services/PermissionManager.swift
  • ScreenTranslate/Services/Security/KeychainService.swift
  • ScreenTranslate/Services/Translation/Providers/BaiduTranslationProvider.swift
  • ScreenTranslate/Services/Translation/Providers/CompatibleTranslationProvider.swift
  • ScreenTranslate/Services/Translation/Providers/DeepLTranslationProvider.swift
  • ScreenTranslate/Services/Translation/Providers/GoogleTranslationProvider.swift
  • ScreenTranslate/Services/Translation/Providers/LLMTranslationProvider.swift
  • ScreenTranslate/Services/TranslationEngine.swift
  • ScreenTranslate/Services/TranslationProvider.swift
  • ScreenTranslate/Services/TranslationService.swift
  • ScreenTranslateTests/TranslationServiceMocks.swift
  • ScreenTranslateTests/TranslationServicePipelineTests.swift
📝 Walkthrough

Walkthrough

本PR为ScreenTranslate新增模型发现服务与权限授权流程,增强翻译管道语言检测与输出清洁化,重构Keychain凭证存储以支持DEBUG调试模式,并改进VLM供应商的请求/响应处理与localhost本地开发支持。

Changes

模型发现与引擎配置

Layer / File(s) Summary
URL本地解析与模型发现服务
ScreenTranslate/Extensions/URLExtensions.swiftScreenTranslate/Services/ModelDiscoveryService.swift
新增URL.resolvingLocalhost扩展属性用于localhost→127.0.0.1映射;新增ModelDiscoveryService.fetchModels静态方法,支持从OpenAI兼容、Ollama等端点拉取模型列表,自动检测引擎类型与格式,包含多种JSON结构解析与Bearer鉴权。
兼容与通用引擎配置UI模型选择
ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swiftScreenTranslate/Features/Settings/EngineConfigSheet.swift
在模型名称输入区添加"获取模型"按钮与可用模型下拉菜单,支持异步拉取并显示加载进度,失败时弹出错误告警;连接测试改为调用verifyConnection而非checkConnection
VLM与PaddleOCR设置界面模型发现
ScreenTranslate/Features/Settings/EngineSettingsTab.swift
在VLMConfigurationSection与PaddleOCRStatusSection分别新增模型列表获取能力,支持动态下拉选择与错误告警,模型列表随提供商变更自动清空。
引擎配置状态与凭证检查
ScreenTranslate/Features/Settings/MultiEngineSettingsSection.swift
新增configuredEngines状态集合异步检查Keychain中的凭证,在sheet出现/消失时刷新;兼容引擎卡片根据选中状态动态改变图标、颜色与背景描边。

权限与安全

Layer / File(s) Summary
屏幕录制与无障碍权限授权流程
ScreenTranslate/Services/PermissionManager.swiftScreenTranslate/App/Coordinators/CaptureCoordinator.swiftScreenTranslate/App/Coordinators/TextTranslationCoordinator.swift
新增ensureScreenRecordingPermissionensureAccessibilityPermissionFlow方法,通过权限授权面板进行拖拽授权,后台轮询约30秒自动关闭面板;CaptureCoordinator与TextTranslationCoordinator在权限检查失败时调用新流程而非系统弹窗。
Keychain凭证存储重构
ScreenTranslate/Services/Security/KeychainService.swiftScreenTranslate/Models/AppSettings.swift
将凭证存储统一为saveRaw/loadRaw/deleteRaw等原始操作,使用JSONEncoder编码存储;DEBUG模式下改用UserDefaults(按account前缀),非DEBUG仍用Keychain;AppSettings的凭证加载函数添加#if DEBUG条件分支。

翻译管道与供应商增强

Layer / File(s) Summary
翻译管道语言检测与自译旁路
ScreenTranslate/Services/TranslationService.swiftScreenTranslate/Services/TranslationEngine.swift
导入NaturalLanguage并新增dominantLanguage检测;TranslationService对源/目标语言匹配自动旁路翻译,强化sanitizeTranslation以移除JSON花括号包裹、处理畸形结构、提取有效字段;新增verifyConnection验证连接可用性。
VLM供应商响应解析与请求处理
ScreenTranslate/Services/OpenAIVLMProvider.swiftScreenTranslate/Services/SettingsViewModel.swift
OpenAIVLMProvider新增reasoning_content/reasoning回退提取、enhanced extractContentManually;OpenAI/Claude/Ollama的VLM测试改为POST请求(chat/completions或messages API)而非GET /models;新增更详细错误信息包含cleaned/raw内容片段。
翻译提供商SSE与授权头处理
ScreenTranslate/Services/Translation/Providers/LLMTranslationProvider.swiftScreenTranslate/Services/Translation/Providers/CompatibleTranslationProvider.swift
两个提供商新增SSE流式响应解析、Authorization头脱敏日志、更细致的JSON解析与错误处理;URL端点构造使用resolvingLocalhost;请求体显式包含stream:false与OpenAI兼容字段。
VLM供应商端点localhost解析
ScreenTranslate/Services/ClaudeVLMProvider.swiftScreenTranslate/Services/GLMOCRVLMProvider.swiftScreenTranslate/Services/OllamaVLMProvider.swift
所有VLM供应商在构建端点URL时使用baseURL.resolvingLocalhost替代直接appendingPathComponent,支持本地开发环境localhost映射。

测试与本地化

Layer / File(s) Summary
模型发现与翻译清洁化测试
ScreenTranslateTests/ModelDiscoveryServiceTests.swiftScreenTranslateTests/GLMOCRVLMProviderTests.swiftScreenTranslateTests/TranslationServicePipelineTests.swift
新增ModelDiscoveryServiceTests验证OpenAI/Ollama模型列表解析与空URL错误;新增OpenAIVLMProviderTests覆盖reasoning回退与内容解析;TranslationServicePipelineTests新增setUp启用引擎、自语言旁路测试、花括号JSON清洁化验证。
多语言文案支持
ScreenTranslate/Resources/**/Localizable.strings
十个本地化文件新增engine.config.fetchModelsengine.config.fetchModels.failed字符串条目,支持模型拉取功能的UI提示与失败告警。

构建配置

Layer / File(s) Summary
Xcode项目与构建脚本调整
ScreenTranslate.xcodeproj/project.pbxprojcreate_dmg.sh
新增PBXFileSystemSynchronizedBuildFileExceptionSet为ScreenTranslate同步目录配置Info.plist例外;调整ScreenTranslateTests的Frameworks构建阶段定义位置;create_dmg.sh扩展APP_PATH候选目录搜索范围以提高构建产物查找成功率。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • hubo1989/ScreenTranslate#70: 修改了TextTranslationCoordinator与PermissionManager的无障碍权限获取流程,与本PR权限授权面板实现的同一路径有直接关联。
  • hubo1989/ScreenTranslate#34: 本PR新增的CaptureCoordinator屏幕录制权限检查直接依赖该PR在CaptureManager中实现的权限状态检测能力。
  • hubo1989/ScreenTranslate#69: 本PR翻译管道在TranslationService的自语言旁路、输出清洁化与verifyConnection实现与该PR的翻译流程加固在同一代码区域有直接关联。

Poem

🐰 模型自动现,权限流程闪,
翻译更聪慧,localhost不怕,
Keychain大整理,代码美如画,
一键获模型,调试梦成真啊!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题准确总结了主要变更:引入模型发现服务和修复钥匙串授权弹窗,涵盖了核心功能改进。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/model-discovery-and-keychain-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ScreenTranslate/Features/Settings/EngineConfigSheet.swift (1)

393-416: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

连接测试未覆盖当前未保存的表单配置

Line 415 仅按 engine 调用 verifyConnection,但当前界面里的 baseURL/modelName 还在本地状态(Line 348-349),未写回 config。这会出现“测试通过但保存后失败”或反向情况。建议测试前构建临时配置并显式传入测试逻辑。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Features/Settings/EngineConfigSheet.swift` around lines 393 -
416, testConnection() currently calls
TranslationService.shared.verifyConnection(for: engine) which only uses saved
config, ignoring the unsaved local state for
baseURL/modelName/apiKey/secretKey/appID in the sheet; construct a temporary
EngineConfig (or a dictionary) from the local state fields (baseURL, modelName,
apiKey, secretKey, appID) and pass it into the verification logic instead of
relying on persisted config—either call an existing verifyConnection overload
that accepts a config or add one (e.g., verifyConnection(for:engine, config:
tempConfig)); ensure you use temporary credentials for the test (do not persist
them) and only save to Keychain when the user explicitly saves.
🟡 Minor comments (15)
ScreenTranslate/Resources/es.lproj/Localizable.strings-825-826 (1)

825-826: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

请改为西班牙语文案,避免语言回退策略失效。

Line 825 和 Line 826 在西语资源里仍是英文,这会让西语用户看到混合语言文本。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Resources/es.lproj/Localizable.strings` around lines 825 -
826, 两条本地化键 engine.config.fetchModels 和 engine.config.fetchModels.failed
目前值仍为英文,导致西班牙语界面出现混合语言;将这两项替换为对应的西班牙语翻译(例如 "engine.config.fetchModels" =
"Obtener"; "engine.config.fetchModels.failed" = "Error al obtener los modelos";
或根据产品术语表使用合适译文),并确保保存到 es.lproj/Localizable.strings,避免回退到英文。
ScreenTranslate/Resources/de.lproj/Localizable.strings-825-826 (1)

825-826: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

请将新增键值本地化为德语,避免界面语言混杂。

Line 825 和 Line 826 目前仍是英文,德语 UI 下会直接显示英文按钮/错误提示,影响一致性。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Resources/de.lproj/Localizable.strings` around lines 825 -
826, 文件中两条本地化键 "engine.config.fetchModels" 和 "engine.config.fetchModels.failed"
仍为英文,需将它们翻译为德语以避免界面语言混杂;请在
ScreenTranslate/Resources/de.lproj/Localizable.strings 中替换这两个键的值为德语对应文本(例如
"Abrufen" 或更合适的上下文词,以及 "Fehler beim Abrufen der Modelle"
或同义的德语错误提示),确保键名不变只修改右侧字符串并遵循现有本地化风格和语气。
ScreenTranslate/Resources/ko.lproj/Localizable.strings-825-826 (1)

825-826: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

韩语资源中的新增键需要本地化。

Line 825 和 Line 826 当前是英文,会在韩语界面直接显示英文文案。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Resources/ko.lproj/Localizable.strings` around lines 825 -
826, 这两条资源键 "engine.config.fetchModels" 和 "engine.config.fetchModels.failed"
在韩语词条中仍为英文,请在 ko.lproj
的本地化文件中为这两个键提供对应的韩文翻译(替换当前英文值),确保字符串值使用正确的韩语文本并保持与其他条目相同的格式和编码,以便韩语界面显示本地化文案。
ScreenTranslate/Resources/ja.lproj/Localizable.strings-825-826 (1)

825-826: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

请补齐日语翻译,避免新增按钮/错误提示显示英文。

Line 825 和 Line 826 在日语资源中仍为英文,建议改为日语文本。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Resources/ja.lproj/Localizable.strings` around lines 825 -
826, 资源文件中的两个键 "engine.config.fetchModels" 和 "engine.config.fetchModels.failed"
在日语本地化仍为英文;请在 ScreenTranslate/Resources/ja.lproj/Localizable.strings
中找到这两个键并将其值替换为日语翻译(例如 "モデルを取得" 或更自然的 "モデルを取得する" 用于按钮,"モデルの取得に失敗しました"
用于错误提示),确保按钮和错误提示在日语环境下不再显示英文。
ScreenTranslate/Resources/fr.lproj/Localizable.strings-825-826 (1)

825-826: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

新增键请使用法语翻译,当前会破坏法语界面一致性。

Line 825 和 Line 826 为英文文案,建议替换为法语对应文本。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Resources/fr.lproj/Localizable.strings` around lines 825 -
826, Replace the English values for the localization keys
"engine.config.fetchModels" and "engine.config.fetchModels.failed" with proper
French translations so the French locale remains consistent: change "Fetch" to a
French equivalent (e.g., "Récupérer") and "Failed to fetch models" to a French
sentence (e.g., "Échec de la récupération des modèles" or "Impossible de
récupérer les modèles") for the keys "engine.config.fetchModels" and
"engine.config.fetchModels.failed" respectively.
ScreenTranslate/Resources/it.lproj/Localizable.strings-826-827 (1)

826-827: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

请将新增字符串本地化为意大利语。

Line 826 和 Line 827 目前是英文,意大利语环境下会显示不一致的语言。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Resources/it.lproj/Localizable.strings` around lines 826 -
827, 替换这两个键的英文字符串为意大利语:将 "engine.config.fetchModels" 的值从 "Fetch" 修改为意大利语(例如
"Recupera" 或 "Ottieni"),并将 "engine.config.fetchModels.failed" 的值从 "Failed to
fetch models" 修改为意大利语(建议 "Impossibile recuperare i modelli"
或相近表述),确保只更新这两个键的右侧字符串以保持文件其它条目不变。
ScreenTranslate/Resources/pt.lproj/Localizable.strings-840-841 (1)

840-841: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

请将新增键值翻译为葡萄牙语。

Line 840 和 Line 841 仍是英文,会导致葡语 UI 的文案不统一。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Resources/pt.lproj/Localizable.strings` around lines 840 -
841, 两个新添加的本地化键 engine.config.fetchModels 和 engine.config.fetchModels.failed
仍为英文,按葡萄牙语本地化替换即可:将 "engine.config.fetchModels" 的值改为 "Buscar modelos"(或 "Buscar"
视上下文为按钮简短文案),将 "engine.config.fetchModels.failed" 的值改为 "Falha ao buscar
modelos"(或 "Falha ao obter modelos"),确保用双引号包裹并保持与现有 Localizable.strings 格式一致。
ScreenTranslate/Resources/ru.lproj/Localizable.strings-825-826 (1)

825-826: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

俄语本地化键值仍是英文。

这会导致俄语界面出现英文文案,建议改为俄语翻译。

建议修复
-"engine.config.fetchModels" = "Fetch";
-"engine.config.fetchModels.failed" = "Failed to fetch models";
+"engine.config.fetchModels" = "Получить";
+"engine.config.fetchModels.failed" = "Не удалось получить модели";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Resources/ru.lproj/Localizable.strings` around lines 825 -
826, The Russian localization strings for the keys "engine.config.fetchModels"
and "engine.config.fetchModels.failed" are still in English; update their values
to proper Russian translations (e.g., set "engine.config.fetchModels" to a
Russian word for "Fetch" like "Загрузить" and "engine.config.fetchModels.failed"
to a Russian phrase for "Failed to fetch models" like "Не удалось загрузить
модели") so the ru.lproj Localizable.strings file shows Russian text for these
keys.
ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swift-320-324 (1)

320-324: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

拉取失败后应清空旧模型列表

Line 320-324 失败分支仅弹错,不会清空 availableModels。这会让 UI 继续展示旧数据,用户可能误选过期模型。建议在 catch 中将 availableModels = []

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swift` around
lines 320 - 324, The catch block in CompatibleEngineConfigSheet.swift currently
only sets errorMessage, showErrorAlert and isFetchingModels but does not clear
the old model list; update the catch handler to also set availableModels = []
(reset the property on the view) so the UI won't display stale models when the
fetch fails, keeping the existing errorMessage, showErrorAlert and
isFetchingModels updates intact.
ScreenTranslate/Features/Settings/EngineConfigSheet.swift-446-450 (1)

446-450: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

拉取失败后应清空旧模型列表

Line 446-450 失败分支未清空 availableModels,会遗留旧结果。建议在 catch 中重置为空,避免配置误导。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Features/Settings/EngineConfigSheet.swift` around lines 446 -
450, 在对可用模型拉取的异常分支(当前 catch 块,已设置 self.errorMessage / self.showErrorAlert /
self.isFetchingModels)中清空旧结果:将 self.availableModels
重置为一个空数组或合适的初始值,以避免在拉取失败后保留过时的模型列表并误导配置界面;在 EngineConfigSheet 的该 catch
分支中加入这一步骤即可。
ScreenTranslate/Features/Settings/EngineSettingsTab.swift-120-133 (1)

120-133: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

模型菜单标签为空,交互可发现性偏弱

Line 128-130 与 Line 333-335 的 Menu 使用空文本标签,用户不易理解其用途。建议改成可见图标(如下拉箭头)并补充可访问性描述。

Also applies to: 325-338

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Features/Settings/EngineSettingsTab.swift` around lines 120 -
133, The Menu currently uses an empty Text label which makes the control
invisible and hard to discover; replace the empty label with a visible
affordance such as a Label or Image (e.g. Image(systemName: "chevron.down") or
Label("Model", systemImage: "chevron.down")) and set accessibility modifiers so
VoiceOver reads its purpose; update the Menu instances that iterate
availableVLMModels (the Menu that sets viewModel.vlmModelName) to use the
visible icon/label and add .accessibilityLabel("Select VLM model") or a
localized string and .accessibilityHint(...) to improve discoverability.
ScreenTranslate/Features/Settings/EngineConfigSheet.swift-274-287 (1)

274-287: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

模型下拉入口可访问性不足

Line 282-284 的 Menu 标签为空文本且区域很小,建议改为可见图标并添加可访问性标签,避免用户难以发现模型选择入口。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Features/Settings/EngineConfigSheet.swift` around lines 274 -
287, The model selection Menu uses an invisible tiny label (Text("") with a
small frame), making the entry hard to discover; replace the empty Text label
with a visible tappable icon (e.g., an Image system icon like a chevron or gear)
and increase its tappable frame, update the Menu label in the same block that
wraps ForEach(availableModels, id: \.self) and Button(model) so users can see
and hit it, and add accessibility modifiers (e.g., accessibilityLabel /
accessibilityIdentifier and accessibilityAddTraits(.isButton)) targeting the
Menu label so VoiceOver/screen readers announce it while preserving the existing
.menuStyle(.borderlessButton) and modelName assignment logic.
ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swift-84-97 (1)

84-97: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

模型下拉入口可访问性不足

Line 92-94 的 Menu 标签是空文本且点击区域极小,用户很难发现该入口。建议使用明确图标(如 chevron.down)并补充可访问性标签。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swift` around
lines 84 - 97, The Menu's label is an empty Text with a tiny hit area, which
hurts discoverability and accessibility; replace the empty label with a clear
tappable icon (e.g., Image(systemName: "chevron.down")) and increase its touch
target (padding or larger frame), then add accessibility modifiers (e.g.,
.accessibilityLabel("Select model") and an optional .accessibilityHint) so
VoiceOver users can find and understand the control; keep the existing Menu,
ForEach(availableModels, id: \.self) and modelName assignment logic unchanged.
ScreenTranslate/Features/Settings/EngineSettingsTab.swift-224-228 (1)

224-228: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

模型拉取失败后建议清空旧列表

Line 224-228 和 Line 449-452 的失败分支没有清空已有模型,可能继续显示上一次成功结果。建议在两处 catch 中重置对应 available...Models = []

Also applies to: 449-452

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Features/Settings/EngineSettingsTab.swift` around lines 224 -
228, In each catch block where you set self.errorMessage, self.showErrorAlert,
and self.isFetchingVLMModels (e.g., the catch at EngineSettingsTab.swift lines
around the isFetchingVLMModels flow), also reset the model list to an empty
array by assigning self.availableVLMModels = []; likewise, in the other failure
catch (the block around lines 449-452) clear the corresponding list (e.g.,
self.availableLLMModels = []) so stale models aren’t shown after a failed fetch.
ScreenTranslate/Services/OpenAIVLMProvider.swift-349-383 (1)

349-383: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

手工字段提取的括号深度逻辑会误伤字符串边界

在 Line 364 到 Line 372,extractField{/[ 维护 depth,会让字符串里的普通字符影响终止条件。若内容中出现未配对花括号,Line 371 的结束引号不会被及时识别,可能提取过长内容并导致后续解析失败。

建议修复
 private func extractField(from json: String, pattern: String) -> String? {
     guard let range = json.range(of: pattern) else { return nil }
     let start = range.upperBound

     var end = start
     var escaped = false
-    var depth = 0

     for char in json[start...] {
         if escaped {
             escaped = false
             end = json.index(after: end)
         } else if char == "\\" {
             escaped = true
             end = json.index(after: end)
-        } else if char == "{" || char == "[" {
-            depth += 1
-            end = json.index(after: end)
-        } else if char == "}" || char == "]" {
-            depth -= 1
-            end = json.index(after: end)
-            if depth < 0 { break }
-        } else if char == "\"" && depth == 0 {
+        } else if char == "\"" {
             break
         } else {
             end = json.index(after: end)
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Services/OpenAIVLMProvider.swift` around lines 349 - 383, The
loop in extractField(from:pattern:) misinterprets braces inside string literals
because it only tracks depth and not whether we're inside a quoted string;
update the loop to track an inString Bool (alongside escaped) so you only
increment/decrement depth when inString == false, and toggle inString when
encountering an unescaped quote; then treat the end condition as "encounter an
unescaped quote that closes the top-level string (inString becomes false and
depth == 0)" so the break happens correctly. Ensure you reference the existing
variables (start, end, escaped, depth) and add inString to control when '{',
'}', '[' and ']' affect depth and when '"' toggles string state.
🧹 Nitpick comments (5)
create_dmg.sh (1)

38-38: ⚡ Quick win

确认:set -e 不会在该写法下因 find 非零而提前退出,但仍会产生 stderr 噪音

本仓库中 APP_PATH=$(find ... | head -n 1) 属于“命令替换 + 赋值”的场景;在 bash 下该写法不会因为 find 在部分目录不存在时返回非零而导致脚本提前退出(只会继续执行)。不过当目录不存在时仍会打印 find: ... No such file or directory 到 stderr,建议静音该 find 的错误输出(如重定向到 /dev/null 或忽略错误),以避免日志干扰。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@create_dmg.sh` at line 38, The APP_PATH assignment uses command substitution
with find (the APP_PATH=$(find "$PROJECT_PATH/build" ... | head -n 1)) which can
emit noisy "No such file or directory" messages to stderr when some searched
paths are absent; update the find invocation to suppress its error output (e.g.,
redirect find's stderr to /dev/null or otherwise ignore errors) so the command
substitution still returns the first match but does not print spurious errors to
stderr while preserving existing behavior when set -e is enabled.
ScreenTranslateTests/ModelDiscoveryServiceTests.swift (2)

99-105: ⚡ Quick win

避免用错误文案子串做断言。

localizedDescription.contains("empty") 对文案变更很敏感,建议断言具体错误类型/枚举 case(或稳定错误码)。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslateTests/ModelDiscoveryServiceTests.swift` around lines 99 - 105,
The test testEmptyURLThrowsError relies on string matching of
error.localizedDescription; change it to assert the concrete error type/enum
case returned by ModelDiscoveryService.fetchModels instead. Update the catch to
cast the thrown error to the specific error type (e.g., ModelDiscoveryError) and
assert the exact case (for example .emptyURL or the appropriate case defined on
ModelDiscoveryError), or compare against a stable error value/errno if your
service exposes one, so the test checks ModelDiscoveryService.fetchModels for
the expected error case rather than a substring of localizedDescription.

8-95: 🏗️ Heavy lift

这两条解析测试未真正覆盖 ModelDiscoveryService 的解析逻辑。

目前只验证了测试内临时 Codable 结构体能解码,生产代码改动时这两条用例可能仍会误通过。建议改为直接调用 ModelDiscoveryService 的可测试入口(或通过可注入网络层构造响应),断言最终模型列表。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslateTests/ModelDiscoveryServiceTests.swift` around lines 8 - 95,
The tests currently only decode local temporary Codable structs instead of
exercising the real parsing in ModelDiscoveryService; update
testParseOpenAIModelsResponse and testParseOllamaTagsResponse to call the
ModelDiscoveryService parsing entry point (or instantiate ModelDiscoveryService
with a mock/injectable network layer that returns the JSON data) and assert the
returned model list contents and order; locate parsing logic on the
ModelDiscoveryService type (e.g., a parseModels/fromData or fetchAvailableModels
method) and use that API so the tests validate production parsing behavior
rather than the local test-only Codable structs.
ScreenTranslateTests/TranslationServicePipelineTests.swift (1)

770-783: ⚡ Quick win

testPrintRealUserSettings 不是有效测试用例。

该方法只有打印没有断言,建议删除,或改成明确可重复的断言测试。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslateTests/TranslationServicePipelineTests.swift` around lines 770
- 783, The testPrintRealUserSettings method is not a valid test because it only
prints values; either remove this method or replace it with a deterministic,
repeatable test: obtain AppSettings.shared inside MainActor.run (keep the
existing reference to AppSettings.shared and the same properties ocrEngine,
translationEngine, translationTargetLanguage, translationSourceLanguage,
translationFallbackEnabled, engineSelectionMode, vlmProvider), set or reset
AppSettings to a known state before reading, and add XCTAssert assertions for
each expected property value (instead of printing) so the test is deterministic
and repeatable; if you prefer deletion, simply remove the
testPrintRealUserSettings function.
ScreenTranslate/Models/AppSettings.swift (1)

743-783: ⚡ Quick win

避免在 AppSettings 里复制 KeychainService 的存储协议。

这两段把 debug key 前缀、StoredCredentials 编码格式和 Keychain 查询细节又实现了一遍。现在凭证读取规则已经散落在两个文件里,后面只要改一次存储 key 或序列化格式,DEBUG/Release 就很容易出现一边能写一边读不出来的静默漂移。更稳妥的做法是把共享常量或读取 helper 收敛回 KeychainService,让这里走单一入口。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslate/Models/AppSettings.swift` around lines 743 - 783, The
duplicated Keychain read logic in loadPaddleOCRAPIKeyFromKeychain and
loadVLMAPIKeyFromKeychain should be removed and replaced with a single call into
KeychainService (or a new helper on KeychainService) so storage keys, JSON
encoding for StoredCredentials and query parameters are centralized; update
these two functions to call
KeychainService.getStoredCredentials(account:)/KeychainService.apiKey(forAccount:)
(or similar) and return credentials.apiKey, keeping DEBUG behavior delegated to
KeychainService as well so key prefixes and serialization aren’t duplicated in
AppSettings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swift`:
- Line 289: 当前连通性测试直接调用
provider.translate(...)(provider.translate),会发起真实推理请求产生费用与延迟;请替换为轻量的连通性检查:在
Provider 协议/类中新增一个无副作用的方法(例如
verifyConnection()/ping()/healthCheck()),让各实现返回仅校验认证与可达性的结果,并在
CompatibleEngineConfigSheet.swift 中把原来调用 provider.translate("Hello", from: "en",
to: "zh") 的逻辑改为调用该新方法;如果某些 provider 目前无法实现完整 ping,可实现为调用一个内部 metadata/health
路由或模拟快速认证请求,确保不触发真实推理调用。

In `@ScreenTranslate/Services/ModelDiscoveryService.swift`:
- Around line 47-49: The code currently always prefixes cleanURL with "https://"
in the ModelDiscoveryService code path (the if block that checks
cleanURL.lowercased().hasPrefix("http://") / "https://"), which breaks local
Ollama connections that use plain HTTP; update that logic to detect local hosts
(e.g., hostnames/hosts like "localhost", "127.0.0.1", "::1", or host:port forms)
and prefix "http://" for those, otherwise continue to default to "https://".
Locate the cleanURL handling in ModelDiscoveryService (the cleanURL variable and
its protocol-prefixing if-block) and implement host detection (or use
URLComponents parsing) to decide which scheme to prepend. Ensure existing
.lowercased() checks are preserved for non-local URLs and that ported localhost
forms (e.g., "localhost:11434") are recognized as local.

In `@ScreenTranslate/Services/PermissionManager.swift`:
- Around line 31-35: 当前实现重用单个 flowController 和 单一 flowPollingTask
导致不同权限流程互相覆盖;修改为为每次发起的权限流程创建独立的 PermissionFlowController 实例并且为该实例维护各自的
Task(不要复用类级别的 flowController/flowPollingTask),或者使用基于权限类型的字典(keyed by permission
identifier)来存储 PermissionFlowController 和对应的 polling Task,启动新流程时只取消与该权限类型关联的旧
Task/Controller;在代码中调整使用到的符号包括 flowController, flowPollingTask,
PermissionFlowController(以及发起/取消轮询的相关方法)以确保取消/刷新只影响对应权限流程。

In `@ScreenTranslate/Services/Security/KeychainService.swift`:
- Around line 268-284: The existsRaw(account:) helper currently treats any
non-errSecSuccess as "not configured"; change it to distinguish
errSecItemNotFound from other Keychain errors: in existsRaw(account:) (the
function with the SecItemCopyMatching call) return false only for
errSecItemNotFound, and for any other non-success status either log the status
(e.g., via your app logger) with context including the account and service or
propagate the error to the caller (throw or return a Result/optional with error)
so callers like MultiEngineSettingsSection can handle auth/interaction failures
correctly. Ensure the `#if` DEBUG branch remains unchanged and only modify the
SecItemCopyMatching handling to check for errSecItemNotFound versus other status
codes.

In
`@ScreenTranslate/Services/Translation/Providers/CompatibleTranslationProvider.swift`:
- Around line 321-323: The logger calls in CompatibleTranslationProvider
(references: displayString, trimmedResponse, jsonString, streamText, and
logger.error) currently write raw response fragments to logs; change those error
logs to omit any response body or truncated content and instead log only safe
metadata (e.g., HTTP status code, response byte count/length, and a request or
correlation ID if available). Locate the three error sites (around the JSON
serialization error using displayString, and the other two sites that reference
jsonString and streamText) and replace the message payload with a concise,
non-sensitive diagnostic message plus metadata; ensure the thrown error is still
propagated but never concatenate or include
trimmedResponse/jsonString/streamText in the logged text.

In `@ScreenTranslate/Services/Translation/Providers/LLMTranslationProvider.swift`:
- Around line 341-343: The code currently logs raw response text (via
displayString/trimmedResponse) in logger.error; remove any raw-response content
from logs and instead log only non-sensitive metadata: include
engineType.rawValue, the response byte/character count (e.g.,
trimmedResponse.utf8.count or .count), any HTTP/status code or SSE status
available, and the structured error.localizedDescription; apply this change to
the logger.error sites referenced (the block using displayString/trimmedResponse
around logger.error, and the similar occurrences at the other spots). Keep
throwing the original error, but ensure no full response body is written to logs
(optionally include a stable checksum/fingerprint if you need to correlate
without exposing content).
- Around line 295-301: The log currently only masks the "Authorization" header
and therefore may print the Claude API key sent as "x-api-key"; update the
header-sanitizing logic in LLMTranslationProvider.swift (the block that reads
request.allHTTPHeaderFields and constructs safeHeaders) to also detect
"x-api-key" (handle common casing variants) and replace its value with a masked
version (e.g., show the first 4 chars then "..."), then pass that masked
safeHeaders to logger.info for the endpoint log; ensure you reference and modify
the same safeHeaders variable used before calling logger.info so no real API
keys are logged.

In `@ScreenTranslate/Services/TranslationEngine.swift`:
- Around line 236-249: The code currently falls back to `.english` when
detection fails and then short-circuits translation if that fallback equals the
target; change this so the short-circuit only happens when an explicit source
was provided (config.sourceLanguage) or detection actually returned a non-nil
result: compute an optional detectedSourceOptional = config.sourceLanguage ??
(config.autoDetectSourceLanguage ? Self.detectLanguage(for: text) : nil), then
only compare/evaluate short-circuit when detectedSourceOptional is non-nil (let
detectedSource = detectedSourceOptional!), preserving the existing
signpost/logging and return behavior; apply the same conditional change to the
similar block that was referenced (the lines around the second short-circuit at
361-364).

In `@ScreenTranslate/Services/TranslationService.swift`:
- Around line 421-429: The current Han-character shortcut wrongly treats any
text with Han characters as Chinese by setting resolvedSourceLang = targetLang,
which causes Japanese (or other languages with Kanji) to be skipped by the
self-translation check; change the logic so you only take this shortcut when the
detected/assigned resolvedSourceLang is already a Chinese variant (i.e.,
resolvedSourceLang == .chineseSimplified || resolvedSourceLang ==
.chineseTraditional) OR when no explicit sourceLang was provided and detection
yielded a Chinese variant; do NOT overwrite an explicit non-Chinese
sourceLang—use the existing containsHanCharacters check only as a tie-breaker
when detection/result already indicates Chinese, and keep the self-translation
bypass using resolvedSourceLang and targetLang as before.

In `@ScreenTranslateTests/TranslationServicePipelineTests.swift`:
- Line 844: 当前测试文件过长导致 file_length lint 阻断;将 MockTranslationProvider 和
MockTranslationServicing 以及与它们相关的辅助类型从 TranslationServicePipelineTests
中抽离到一个新的测试文件(例如 MockTranslationProvidersTests.swift),并在新文件顶部添加相同的 test
target/imports,确保类/struct 的访问级别(public/internal)能被测试目标访问;在原测试文件中移除这些 mock
定义并更新任何引用(如
枚举/方法名:MockTranslationProvider、MockTranslationServicing、TranslationServicePipelineTests)以导入或使用新文件中的实现,运行测试并修复编译或命名冲突。
- Around line 195-207: The setUp modifies and persists
AppSettings.shared.engineConfigs via saveEngineConfigs(), which can pollute
other tests; capture a snapshot of the original settings (e.g., copy
AppSettings.shared or its engineConfigs) before enabling engines in setUp, and
implement tearDown to restore that snapshot and call saveEngineConfigs() to
revert persistent state; reference the setUp override, AppSettings.shared,
engineConfigs, and saveEngineConfigs(), and add an override func tearDown()
async throws that restores the previous config and persists it.
- Line 755: The conditional using appleRequests.count > 0 triggers SwiftLint
empty_count; change the check to use the collection API (replace if
appleRequests.count > 0 with if !appleRequests.isEmpty) or remove the if
entirely because the test already asserts appleRequests.count == 1 earlier in
TranslationServicePipelineTests, so the branch can be simplified accordingly.

---

Outside diff comments:
In `@ScreenTranslate/Features/Settings/EngineConfigSheet.swift`:
- Around line 393-416: testConnection() currently calls
TranslationService.shared.verifyConnection(for: engine) which only uses saved
config, ignoring the unsaved local state for
baseURL/modelName/apiKey/secretKey/appID in the sheet; construct a temporary
EngineConfig (or a dictionary) from the local state fields (baseURL, modelName,
apiKey, secretKey, appID) and pass it into the verification logic instead of
relying on persisted config—either call an existing verifyConnection overload
that accepts a config or add one (e.g., verifyConnection(for:engine, config:
tempConfig)); ensure you use temporary credentials for the test (do not persist
them) and only save to Keychain when the user explicitly saves.

---

Minor comments:
In `@ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swift`:
- Around line 320-324: The catch block in CompatibleEngineConfigSheet.swift
currently only sets errorMessage, showErrorAlert and isFetchingModels but does
not clear the old model list; update the catch handler to also set
availableModels = [] (reset the property on the view) so the UI won't display
stale models when the fetch fails, keeping the existing errorMessage,
showErrorAlert and isFetchingModels updates intact.
- Around line 84-97: The Menu's label is an empty Text with a tiny hit area,
which hurts discoverability and accessibility; replace the empty label with a
clear tappable icon (e.g., Image(systemName: "chevron.down")) and increase its
touch target (padding or larger frame), then add accessibility modifiers (e.g.,
.accessibilityLabel("Select model") and an optional .accessibilityHint) so
VoiceOver users can find and understand the control; keep the existing Menu,
ForEach(availableModels, id: \.self) and modelName assignment logic unchanged.

In `@ScreenTranslate/Features/Settings/EngineConfigSheet.swift`:
- Around line 446-450: 在对可用模型拉取的异常分支(当前 catch 块,已设置 self.errorMessage /
self.showErrorAlert / self.isFetchingModels)中清空旧结果:将 self.availableModels
重置为一个空数组或合适的初始值,以避免在拉取失败后保留过时的模型列表并误导配置界面;在 EngineConfigSheet 的该 catch
分支中加入这一步骤即可。
- Around line 274-287: The model selection Menu uses an invisible tiny label
(Text("") with a small frame), making the entry hard to discover; replace the
empty Text label with a visible tappable icon (e.g., an Image system icon like a
chevron or gear) and increase its tappable frame, update the Menu label in the
same block that wraps ForEach(availableModels, id: \.self) and Button(model) so
users can see and hit it, and add accessibility modifiers (e.g.,
accessibilityLabel / accessibilityIdentifier and
accessibilityAddTraits(.isButton)) targeting the Menu label so VoiceOver/screen
readers announce it while preserving the existing .menuStyle(.borderlessButton)
and modelName assignment logic.

In `@ScreenTranslate/Features/Settings/EngineSettingsTab.swift`:
- Around line 120-133: The Menu currently uses an empty Text label which makes
the control invisible and hard to discover; replace the empty label with a
visible affordance such as a Label or Image (e.g. Image(systemName:
"chevron.down") or Label("Model", systemImage: "chevron.down")) and set
accessibility modifiers so VoiceOver reads its purpose; update the Menu
instances that iterate availableVLMModels (the Menu that sets
viewModel.vlmModelName) to use the visible icon/label and add
.accessibilityLabel("Select VLM model") or a localized string and
.accessibilityHint(...) to improve discoverability.
- Around line 224-228: In each catch block where you set self.errorMessage,
self.showErrorAlert, and self.isFetchingVLMModels (e.g., the catch at
EngineSettingsTab.swift lines around the isFetchingVLMModels flow), also reset
the model list to an empty array by assigning self.availableVLMModels = [];
likewise, in the other failure catch (the block around lines 449-452) clear the
corresponding list (e.g., self.availableLLMModels = []) so stale models aren’t
shown after a failed fetch.

In `@ScreenTranslate/Resources/de.lproj/Localizable.strings`:
- Around line 825-826: 文件中两条本地化键 "engine.config.fetchModels" 和
"engine.config.fetchModels.failed" 仍为英文,需将它们翻译为德语以避免界面语言混杂;请在
ScreenTranslate/Resources/de.lproj/Localizable.strings 中替换这两个键的值为德语对应文本(例如
"Abrufen" 或更合适的上下文词,以及 "Fehler beim Abrufen der Modelle"
或同义的德语错误提示),确保键名不变只修改右侧字符串并遵循现有本地化风格和语气。

In `@ScreenTranslate/Resources/es.lproj/Localizable.strings`:
- Around line 825-826: 两条本地化键 engine.config.fetchModels 和
engine.config.fetchModels.failed 目前值仍为英文,导致西班牙语界面出现混合语言;将这两项替换为对应的西班牙语翻译(例如
"engine.config.fetchModels" = "Obtener"; "engine.config.fetchModels.failed" =
"Error al obtener los modelos"; 或根据产品术语表使用合适译文),并确保保存到
es.lproj/Localizable.strings,避免回退到英文。

In `@ScreenTranslate/Resources/fr.lproj/Localizable.strings`:
- Around line 825-826: Replace the English values for the localization keys
"engine.config.fetchModels" and "engine.config.fetchModels.failed" with proper
French translations so the French locale remains consistent: change "Fetch" to a
French equivalent (e.g., "Récupérer") and "Failed to fetch models" to a French
sentence (e.g., "Échec de la récupération des modèles" or "Impossible de
récupérer les modèles") for the keys "engine.config.fetchModels" and
"engine.config.fetchModels.failed" respectively.

In `@ScreenTranslate/Resources/it.lproj/Localizable.strings`:
- Around line 826-827: 替换这两个键的英文字符串为意大利语:将 "engine.config.fetchModels" 的值从
"Fetch" 修改为意大利语(例如 "Recupera" 或 "Ottieni"),并将 "engine.config.fetchModels.failed"
的值从 "Failed to fetch models" 修改为意大利语(建议 "Impossibile recuperare i modelli"
或相近表述),确保只更新这两个键的右侧字符串以保持文件其它条目不变。

In `@ScreenTranslate/Resources/ja.lproj/Localizable.strings`:
- Around line 825-826: 资源文件中的两个键 "engine.config.fetchModels" 和
"engine.config.fetchModels.failed" 在日语本地化仍为英文;请在
ScreenTranslate/Resources/ja.lproj/Localizable.strings 中找到这两个键并将其值替换为日语翻译(例如
"モデルを取得" 或更自然的 "モデルを取得する" 用于按钮,"モデルの取得に失敗しました" 用于错误提示),确保按钮和错误提示在日语环境下不再显示英文。

In `@ScreenTranslate/Resources/ko.lproj/Localizable.strings`:
- Around line 825-826: 这两条资源键 "engine.config.fetchModels" 和
"engine.config.fetchModels.failed" 在韩语词条中仍为英文,请在 ko.lproj
的本地化文件中为这两个键提供对应的韩文翻译(替换当前英文值),确保字符串值使用正确的韩语文本并保持与其他条目相同的格式和编码,以便韩语界面显示本地化文案。

In `@ScreenTranslate/Resources/pt.lproj/Localizable.strings`:
- Around line 840-841: 两个新添加的本地化键 engine.config.fetchModels 和
engine.config.fetchModels.failed 仍为英文,按葡萄牙语本地化替换即可:将 "engine.config.fetchModels"
的值改为 "Buscar modelos"(或 "Buscar" 视上下文为按钮简短文案),将
"engine.config.fetchModels.failed" 的值改为 "Falha ao buscar modelos"(或 "Falha ao
obter modelos"),确保用双引号包裹并保持与现有 Localizable.strings 格式一致。

In `@ScreenTranslate/Resources/ru.lproj/Localizable.strings`:
- Around line 825-826: The Russian localization strings for the keys
"engine.config.fetchModels" and "engine.config.fetchModels.failed" are still in
English; update their values to proper Russian translations (e.g., set
"engine.config.fetchModels" to a Russian word for "Fetch" like "Загрузить" and
"engine.config.fetchModels.failed" to a Russian phrase for "Failed to fetch
models" like "Не удалось загрузить модели") so the ru.lproj Localizable.strings
file shows Russian text for these keys.

In `@ScreenTranslate/Services/OpenAIVLMProvider.swift`:
- Around line 349-383: The loop in extractField(from:pattern:) misinterprets
braces inside string literals because it only tracks depth and not whether we're
inside a quoted string; update the loop to track an inString Bool (alongside
escaped) so you only increment/decrement depth when inString == false, and
toggle inString when encountering an unescaped quote; then treat the end
condition as "encounter an unescaped quote that closes the top-level string
(inString becomes false and depth == 0)" so the break happens correctly. Ensure
you reference the existing variables (start, end, escaped, depth) and add
inString to control when '{', '}', '[' and ']' affect depth and when '"' toggles
string state.

---

Nitpick comments:
In `@create_dmg.sh`:
- Line 38: The APP_PATH assignment uses command substitution with find (the
APP_PATH=$(find "$PROJECT_PATH/build" ... | head -n 1)) which can emit noisy "No
such file or directory" messages to stderr when some searched paths are absent;
update the find invocation to suppress its error output (e.g., redirect find's
stderr to /dev/null or otherwise ignore errors) so the command substitution
still returns the first match but does not print spurious errors to stderr while
preserving existing behavior when set -e is enabled.

In `@ScreenTranslate/Models/AppSettings.swift`:
- Around line 743-783: The duplicated Keychain read logic in
loadPaddleOCRAPIKeyFromKeychain and loadVLMAPIKeyFromKeychain should be removed
and replaced with a single call into KeychainService (or a new helper on
KeychainService) so storage keys, JSON encoding for StoredCredentials and query
parameters are centralized; update these two functions to call
KeychainService.getStoredCredentials(account:)/KeychainService.apiKey(forAccount:)
(or similar) and return credentials.apiKey, keeping DEBUG behavior delegated to
KeychainService as well so key prefixes and serialization aren’t duplicated in
AppSettings.

In `@ScreenTranslateTests/ModelDiscoveryServiceTests.swift`:
- Around line 99-105: The test testEmptyURLThrowsError relies on string matching
of error.localizedDescription; change it to assert the concrete error type/enum
case returned by ModelDiscoveryService.fetchModels instead. Update the catch to
cast the thrown error to the specific error type (e.g., ModelDiscoveryError) and
assert the exact case (for example .emptyURL or the appropriate case defined on
ModelDiscoveryError), or compare against a stable error value/errno if your
service exposes one, so the test checks ModelDiscoveryService.fetchModels for
the expected error case rather than a substring of localizedDescription.
- Around line 8-95: The tests currently only decode local temporary Codable
structs instead of exercising the real parsing in ModelDiscoveryService; update
testParseOpenAIModelsResponse and testParseOllamaTagsResponse to call the
ModelDiscoveryService parsing entry point (or instantiate ModelDiscoveryService
with a mock/injectable network layer that returns the JSON data) and assert the
returned model list contents and order; locate parsing logic on the
ModelDiscoveryService type (e.g., a parseModels/fromData or fetchAvailableModels
method) and use that API so the tests validate production parsing behavior
rather than the local test-only Codable structs.

In `@ScreenTranslateTests/TranslationServicePipelineTests.swift`:
- Around line 770-783: The testPrintRealUserSettings method is not a valid test
because it only prints values; either remove this method or replace it with a
deterministic, repeatable test: obtain AppSettings.shared inside MainActor.run
(keep the existing reference to AppSettings.shared and the same properties
ocrEngine, translationEngine, translationTargetLanguage,
translationSourceLanguage, translationFallbackEnabled, engineSelectionMode,
vlmProvider), set or reset AppSettings to a known state before reading, and add
XCTAssert assertions for each expected property value (instead of printing) so
the test is deterministic and repeatable; if you prefer deletion, simply remove
the testPrintRealUserSettings function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0061bf8c-75a6-404a-8d4d-406010b5007a

📥 Commits

Reviewing files that changed from the base of the PR and between 6ab74ab and 01c57a7.

📒 Files selected for processing (35)
  • ScreenTranslate.xcodeproj/project.pbxproj
  • ScreenTranslate/App/Coordinators/CaptureCoordinator.swift
  • ScreenTranslate/App/Coordinators/TextTranslationCoordinator.swift
  • ScreenTranslate/Extensions/URLExtensions.swift
  • ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swift
  • ScreenTranslate/Features/Settings/EngineConfigSheet.swift
  • ScreenTranslate/Features/Settings/EngineSettingsTab.swift
  • ScreenTranslate/Features/Settings/MultiEngineSettingsSection.swift
  • ScreenTranslate/Features/Settings/SettingsViewModel.swift
  • ScreenTranslate/Models/AppSettings.swift
  • ScreenTranslate/Resources/de.lproj/Localizable.strings
  • ScreenTranslate/Resources/en.lproj/Localizable.strings
  • ScreenTranslate/Resources/es.lproj/Localizable.strings
  • ScreenTranslate/Resources/fr.lproj/Localizable.strings
  • ScreenTranslate/Resources/it.lproj/Localizable.strings
  • ScreenTranslate/Resources/ja.lproj/Localizable.strings
  • ScreenTranslate/Resources/ko.lproj/Localizable.strings
  • ScreenTranslate/Resources/pt.lproj/Localizable.strings
  • ScreenTranslate/Resources/ru.lproj/Localizable.strings
  • ScreenTranslate/Resources/zh-Hans.lproj/Localizable.strings
  • ScreenTranslate/Services/ClaudeVLMProvider.swift
  • ScreenTranslate/Services/GLMOCRVLMProvider.swift
  • ScreenTranslate/Services/ModelDiscoveryService.swift
  • ScreenTranslate/Services/OllamaVLMProvider.swift
  • ScreenTranslate/Services/OpenAIVLMProvider.swift
  • ScreenTranslate/Services/PermissionManager.swift
  • ScreenTranslate/Services/Security/KeychainService.swift
  • ScreenTranslate/Services/Translation/Providers/CompatibleTranslationProvider.swift
  • ScreenTranslate/Services/Translation/Providers/LLMTranslationProvider.swift
  • ScreenTranslate/Services/TranslationEngine.swift
  • ScreenTranslate/Services/TranslationService.swift
  • ScreenTranslateTests/GLMOCRVLMProviderTests.swift
  • ScreenTranslateTests/ModelDiscoveryServiceTests.swift
  • ScreenTranslateTests/TranslationServicePipelineTests.swift
  • create_dmg.sh

Comment thread ScreenTranslate/Features/Settings/CompatibleEngineConfigSheet.swift Outdated
Comment thread ScreenTranslate/Services/ModelDiscoveryService.swift
Comment thread ScreenTranslate/Services/PermissionManager.swift Outdated
Comment thread ScreenTranslate/Services/Security/KeychainService.swift
Comment thread ScreenTranslate/Services/TranslationEngine.swift Outdated
Comment thread ScreenTranslate/Services/TranslationService.swift
Comment thread ScreenTranslateTests/TranslationServicePipelineTests.swift
let appleRequests = await apple.requests
print("DEBUG_APPLE_REQUESTS: \(appleRequests)")
XCTAssertEqual(appleRequests.count, 1)
if appleRequests.count > 0 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP 'count\s*>\s*0' ScreenTranslateTests/TranslationServicePipelineTests.swift

Repository: hubo1989/ScreenTranslate

Length of output: 109


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="ScreenTranslateTests/TranslationServicePipelineTests.swift"
# 打印 740-770 行上下文,确认 appleRequests 的类型与该 if 的语义
nl -ba "$FILE" | sed -n '740,770p'

Repository: hubo1989/ScreenTranslate

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="ScreenTranslateTests/TranslationServicePipelineTests.swift"

# 740-770 行上下文(使用 awk 自带行号)
awk 'NR>=740 && NR<=770 {printf "%d:%s\n", NR, $0}' "$FILE"

# 查找 appleRequests 的声明位置与类型线索
rg -n --context 2 'appleRequests' "$FILE"

Repository: hubo1989/ScreenTranslate

Length of output: 2175


修复 SwiftLint empty_count 规则触发点
ScreenTranslateTests/TranslationServicePipelineTests.swift:755 处的 if appleRequests.count > 0 { 会触发 empty_count;改为 if !appleRequests.isEmpty {。另外前面已断言 appleRequests.count == 1,该 if 也可考虑直接移除。

🧰 Tools
🪛 SwiftLint (0.63.2)

[Error] 755-755: Prefer checking isEmpty over comparing count to zero

(empty_count)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ScreenTranslateTests/TranslationServicePipelineTests.swift` at line 755, The
conditional using appleRequests.count > 0 triggers SwiftLint empty_count; change
the check to use the collection API (replace if appleRequests.count > 0 with if
!appleRequests.isEmpty) or remove the if entirely because the test already
asserts appleRequests.count == 1 earlier in TranslationServicePipelineTests, so
the branch can be simplified accordingly.

Comment thread ScreenTranslateTests/TranslationServicePipelineTests.swift
@hubo1989 hubo1989 merged commit 1a0269b into main May 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant