Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

167 lines
6.0 KiB
Swift

import Foundation
import MacToolsPluginKit
struct TranslatorPanelPresentation: Equatable {
struct Tip: Equatable {
var title: String
var message: String
var actionTitle: String
var systemImage: String
}
struct ProviderRow: Equatable, Identifiable {
var id: String
var title: String
var symbolName: String
var bodyText: String
var isExpanded: Bool
var isLoading: Bool
var isError: Bool
var canCopy: Bool
}
var sourceText: String
var usesSourceCaretPlaceholder: Bool
var sourceLanguageTitle: String
var targetLanguageTitle: String
var tip: Tip?
var providerRows: [ProviderRow]
init(
snapshot: TranslatorPanelSnapshot,
localization: PluginLocalization = PluginLocalization(bundle: .main)
) {
let trimmedSource = snapshot.sourceText?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
sourceText = trimmedSource
usesSourceCaretPlaceholder = trimmedSource.isEmpty
sourceLanguageTitle = snapshot.languageSelection?.sourceDisplayName(localization: localization)
?? localization.string("language.automatic", defaultValue: "自动检测")
targetLanguageTitle = snapshot.languageSelection?.target.displayName(localization: localization)
?? localization.string("language.automaticTarget", defaultValue: "自动选择")
tip = Self.tip(for: snapshot, localization: localization)
let results = Self.providerResults(for: snapshot, localization: localization)
let expandedProviderID = Self.expandedProviderID(in: results)
providerRows = results.map { result in
let text = Self.providerBodyText(result, localization: localization)
let translatedText = result.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return ProviderRow(
id: result.id,
title: result.providerTitle,
symbolName: Self.symbolName(forProviderTitle: result.providerTitle),
bodyText: text,
isExpanded: result.id == expandedProviderID,
isLoading: result.phase == .translating || result.phase == .waiting,
isError: result.phase == .error,
canCopy: !translatedText.isEmpty
)
}
}
private static func providerResults(
for snapshot: TranslatorPanelSnapshot,
localization: PluginLocalization
) -> [TranslatorProviderResult] {
if !snapshot.providerResults.isEmpty {
return snapshot.providerResults
}
let providerTitle = snapshot.translation?.providerTitle
?? localization.string("openAIClient.providerTitle", defaultValue: "OpenAI 翻译")
return [
TranslatorProviderResult(
id: "placeholder",
providerTitle: providerTitle,
phase: placeholderProviderPhase(for: snapshot.phase),
translation: snapshot.translation,
errorMessage: snapshot.errorMessage
),
]
}
private static func placeholderProviderPhase(for phase: TranslatorPanelPhase) -> TranslatorProviderResultPhase {
switch phase {
case .translating:
return .translating
case .success:
return .success
case .error:
return .error
case .idle, .capturing:
return .waiting
}
}
private static func expandedProviderID(in results: [TranslatorProviderResult]) -> String? {
results.first { result in
let text = result.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return !text.isEmpty || result.phase == .error
}?.id
}
private static func tip(
for snapshot: TranslatorPanelSnapshot,
localization: PluginLocalization
) -> Tip? {
guard case let .error(error) = snapshot.phase else {
return nil
}
let title = localization.string("panel.tip.title", defaultValue: "提示")
let actionTitle = localization.string("panel.tip.actionTitle", defaultValue: "如何解决")
let message: String
switch error {
case .missingSelection:
message = localization.string(
"panel.tip.missingSelection",
defaultValue: "划词翻译没有获取到文本"
)
default:
message = snapshot.errorMessage ?? error.message(localization: localization)
}
return Tip(
title: title,
message: message,
actionTitle: actionTitle,
systemImage: "lightbulb"
)
}
private static func providerBodyText(
_ result: TranslatorProviderResult,
localization: PluginLocalization
) -> String {
let text = result.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !text.isEmpty {
return text
}
switch result.phase {
case .translating:
return localization.string("panel.resultPlaceholder.translating", defaultValue: "正在翻译...")
case .error:
return result.errorMessage
?? localization.string("openAIClient.error.requestFailed", defaultValue: "请求失败,请稍后重试")
case .waiting:
return localization.string("panel.resultPlaceholder.idle", defaultValue: "等待翻译")
case .success:
return localization.string("panel.resultPlaceholder.emptyResponse", defaultValue: "响应为空")
}
}
private static func symbolName(forProviderTitle title: String) -> String {
let normalized = title.lowercased()
if normalized.contains("deepseek") {
return "sparkle.magnifyingglass"
}
if normalized.contains("fireworks") {
return "sparkles"
}
if normalized.contains("openai") {
return "circle.hexagongrid"
}
return "bubble.left.and.text.bubble.right"
}
}