feat(translation-ui): tighten settings/onboarding UX around the translation feature
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).
This commit is contained in:
@@ -28,12 +28,22 @@ public struct TranslationLanguage: Identifiable, Hashable, Sendable {
|
||||
}
|
||||
|
||||
public enum TranslationLanguageCatalog {
|
||||
/// Default target language id used on fresh installs.
|
||||
/// 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, and `defaultLocaleId` is the default selection.
|
||||
/// 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: "繁體中文"),
|
||||
@@ -46,14 +56,23 @@ public enum TranslationLanguageCatalog {
|
||||
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
|
||||
/// `defaultLocaleId` 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.
|
||||
/// `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 == defaultLocaleId } ?? all[0]
|
||||
return all.first { $0.id == offLocaleId } ?? all[0]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user