Files
OSGKeyboard/OSGKeyboard/Views/TranslationPickerRow.swift
T
Rocky cc8dd1070a feat: macOS architecture, cloud ASR/LLM providers, and 6-step iOS onboarding
- Add macOS menu-bar dictation app with local ASR models (SenseVoice/Qwen3),
  global Option hotkey, and bottom overlay
- Add cloud ASR/LLM providers (Anthropic, Volcengine, Bailian, and more) with
  provider logos, model listing, and connection checks
- Add shared 7-day usage stats UI (UsageStatsCluster / SevenDayUsageChart)
- Add iOS onboarding step 6 for polish LLM setup; hide custom-language-model
  diagnostic toggle behind DEBUG
- Unify iOS onboarding tagline with the macOS brand line ("开口即文字。")
- Rewrite README (Chinese-first, product-oriented) and refresh GitHub Pages
2026-07-11 19:10:20 +08:00

105 lines
4.2 KiB
Swift

// TranslationPickerRow.swift
// OSGKeyboard · Main App
//
// Single-row "翻译" picker — replaces the previous two-row toggle +
// target-locale dropdown. Lets the user pick "不翻译" (off, the
// default) or one of the 10 target languages, all from a single
// `Menu`.
//
// v0.2.1 follow-up: row is rendered through an `isVisible` parameter
// so callers (`SettingsView`, `OnboardingView`) can drop the row
// entirely when the engine can't run the cloud translate-and-polish
// step (`ProviderConfig.isTranslationRowVisible`). The "needs cloud"
// inline hint was deleted along with the previous Bool toggle — the
// user only sees the row when the engine can act on the choice.
//
// v0.2.1 final review: both engines now run the translate-and-polish
// step (the local engine routes through DeepSeek via
// `ProviderConfig.localModeProviderId`), so the row title changed
// from "Translation" to "Polish then translate" to match the new
// always-on translation contract.
//
// Mapping to persisted state:
// • "不翻译" → translationTargetLocaleId = "off"
// • any specific locale → translationTargetLocaleId = <id>
//
// The pipeline (`PolishingService`) honors `.translate` on both
// engines when this row is visible — no more "rejected mode" toast.
import SwiftUI
import OSGKeyboardShared
struct TranslationPickerRow: View {
@Environment(\.themePalette) private var palette: ThemePalette
@ObservedObject var config: ProviderConfig
/// Visibility flag — when `false` the row renders as `EmptyView`
/// (callers can also wrap the call site in `if` for symmetry, but
/// having the guard here means a forgotten `if` still produces a
/// safe no-op rather than a leaked dead row).
var isVisible: Bool = true
var body: some View {
if isVisible {
HStack {
Text("settings.translation.afterPolish")
.font(TypeStyle.body)
.foregroundStyle(palette.textPrimary)
Spacer()
Menu {
ForEach(TranslationLanguageCatalog.all) { language in
Button {
apply(language)
} label: {
if currentSelectionId == language.id {
Label(displayLabel(for: language), systemImage: "checkmark")
} else {
Text(displayLabel(for: language))
}
}
}
} label: {
HStack(spacing: 6) {
Text(currentLabel)
.font(TypeStyle.body)
.foregroundStyle(currentIsOff ? palette.textSecondary : palette.textPrimary)
Image(systemName: "chevron.up.chevron.down")
.font(.system(size: 11, weight: .bold))
.foregroundStyle(palette.textTertiary)
}
}
}
.settingsListRow()
}
}
/// Currently selected id — the picker always reads
/// `translationTargetLocaleId` directly (the previous
/// `translationEnabled` boolean is now derived from it).
private var currentSelectionId: String {
config.translationTargetLocaleId
}
private var currentLabel: String {
displayLabel(for: TranslationLanguageCatalog.resolve(currentSelectionId))
}
private var currentIsOff: Bool {
TranslationLanguageCatalog.isOff(currentSelectionId)
}
private func displayLabel(for language: TranslationLanguage) -> String {
if language.id == TranslationLanguageCatalog.offLocaleId {
return AppL10n.string("settings.translation.off")
}
return language.nativeName
}
/// Translates a picker choice into a single persisted field.
/// "不翻译" writes `offLocaleId`; any concrete locale writes its
/// id. `ProviderConfig.translationEnabled` is derived from the
/// resulting value, so callers don't need to flip a separate Bool.
private func apply(_ language: TranslationLanguage) {
config.translationTargetLocaleId = language.id
}
}