4d92999347
UI refinements on top of the translation pipeline (feature/translation@HEAD):
1. Onboarding engine page now hosts a translation row.
APISetupPage renders the same TranslationPickerRow used in the
language tab, so first-time users can pick a target language
before they ever see the keyboard. Same persisted bindings; same
'needs cloud' hint when the local engine is active.
2. Local engine hides the provider / API card unconditionally.
Removed the 'local + cloud polish on → show API fields' branch
from SettingsView. Provider/base URL/API key/model controls have
no use in local mode (translation is cloud-only anyway), and
exposing them invited users to fill in a DeepSeek key they
can't use.
3. 'Cloud polish after ASR' toggle loses its long subtitle.
The descriptive copy in LocalEngineSettingsRows.cloudPolishRow
was a wall of text that explained things visible elsewhere in
Settings. Title + switch is enough; the CloudPolishDisclosureBanner
(rendered by EnginePickerSection when cloud is active) already
covers the 'this sends text to your API' disclosure.
4. Translation row becomes a single dropdown with a 'Don't
translate' default.
TranslationPickerRow replaced with a one-row Menu picker:
'不翻译 / English / 中文 (简体) / 中文 (繁體) / 日本語 / 한국어 /
Français / Deutsch / Español / Русский / Português'.
'不翻译' maps to translationEnabled=false; any locale maps to
translationEnabled=true + translationTargetLocaleId=<id>.
TranslationLanguageCatalog gains an 'off' sentinel so the picker's
single binding stays a plain String.
5. Language tab reorder.
SettingsView.languageAndModelsSection: ASR locale ('识别语言')
now sits above the local-models block; translation row sits at
the bottom. The reading order follows the pipeline direction
(input → post-processing → post-post-processing).
Localization:
- 'settings.translation.title' → '翻译' / 'Translation'
- new 'settings.translation.off' / 'settings.translation.hint.needsCloud'
- dropped unused subtitle / target-language keys
xcodebuild scheme=OSGKeyboard config=Debug destination=iPhone 17
Simulator: BUILD SUCCEEDED (0 warning, 0 error).
78 lines
3.9 KiB
Swift
78 lines
3.9 KiB
Swift
// TranslationLanguage.swift
|
|
// OSGKeyboard · Shared
|
|
//
|
|
// Catalog of target languages the translation feature can produce.
|
|
//
|
|
// Kept deliberately small (~10 entries) to match the kind of choices
|
|
// the user makes in the Settings picker / keyboard chip. We don't try
|
|
// to expose every BCP-47 locale — the prompt just needs a target
|
|
// language name, and a curated list reads better than a 100-row scroll.
|
|
//
|
|
// `id` is what gets persisted to the App Group. `promptLanguageName`
|
|
// is the human-readable target name injected into the prompt (e.g.
|
|
// the LLM sees "English", not "en"). `nativeName` is the endonym we
|
|
// show in the picker UI ("日本語" instead of "Japanese").
|
|
|
|
import Foundation
|
|
|
|
public struct TranslationLanguage: Identifiable, Hashable, Sendable {
|
|
public let id: String
|
|
public let promptLanguageName: String
|
|
public let nativeName: String
|
|
|
|
public init(id: String, promptLanguageName: String, nativeName: String) {
|
|
self.id = id
|
|
self.promptLanguageName = promptLanguageName
|
|
self.nativeName = nativeName
|
|
}
|
|
}
|
|
|
|
public enum TranslationLanguageCatalog {
|
|
/// Sentinel id for "don't translate" — the default selection in the
|
|
/// picker. Picked over an `Optional<TranslationLanguage>` so the
|
|
/// single-row `Picker` binding stays a plain `String` (and the same
|
|
/// code path also works for the `TranslationChip` Menu).
|
|
public static let offLocaleId = "off"
|
|
/// Default target language id used on fresh installs when translation
|
|
/// is enabled. The picker still defaults to `offLocaleId` — this is
|
|
/// only the language we'd fall back to if a stale "on" state is
|
|
/// recovered without a remembered target.
|
|
public static let defaultLocaleId = "en"
|
|
|
|
/// Curated set. Order matters — the picker / chip render top-to-
|
|
/// bottom, with `offLocaleId` ("不翻译") at the very top so the
|
|
/// "turn off" action is one tap away from any enabled state.
|
|
public static let all: [TranslationLanguage] = [
|
|
TranslationLanguage(id: offLocaleId, promptLanguageName: "", nativeName: ""),
|
|
TranslationLanguage(id: "en", promptLanguageName: "English", nativeName: "English"),
|
|
TranslationLanguage(id: "zh-Hans", promptLanguageName: "Simplified Chinese", nativeName: "简体中文"),
|
|
TranslationLanguage(id: "zh-Hant", promptLanguageName: "Traditional Chinese", nativeName: "繁體中文"),
|
|
TranslationLanguage(id: "ja", promptLanguageName: "Japanese", nativeName: "日本語"),
|
|
TranslationLanguage(id: "ko", promptLanguageName: "Korean", nativeName: "한국어"),
|
|
TranslationLanguage(id: "fr", promptLanguageName: "French", nativeName: "Français"),
|
|
TranslationLanguage(id: "de", promptLanguageName: "German", nativeName: "Deutsch"),
|
|
TranslationLanguage(id: "es", promptLanguageName: "Spanish", nativeName: "Español"),
|
|
TranslationLanguage(id: "ru", promptLanguageName: "Russian", nativeName: "Русский"),
|
|
TranslationLanguage(id: "pt", promptLanguageName: "Portuguese", nativeName: "Português"),
|
|
]
|
|
|
|
/// True when the given id is the "off" sentinel. Used by the picker
|
|
/// to flip `translationEnabled` and by the pipeline to skip the
|
|
/// translate prompt.
|
|
public static func isOff(_ id: String) -> Bool {
|
|
id == offLocaleId
|
|
}
|
|
|
|
/// Resolve a stored locale id to its catalog entry. Falls back to
|
|
/// `offLocaleId` (the picker default) when the id is missing or
|
|
/// unknown — matches the pattern used elsewhere (e.g.
|
|
/// `ASRLocaleLabels`) so the keyboard never crashes on a stale
|
|
/// persisted value, and the picker lands on the safe "off" state
|
|
/// instead of an arbitrary language.
|
|
public static func resolve(_ id: String) -> TranslationLanguage {
|
|
if let match = all.first(where: { $0.id == id }) {
|
|
return match
|
|
}
|
|
return all.first { $0.id == offLocaleId } ?? all[0]
|
|
}
|
|
} |