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:
zhongshu
2026-06-25 13:23:54 +08:00
parent deddb49d56
commit 4d92999347
7 changed files with 156 additions and 117 deletions
+17 -17
View File
@@ -62,17 +62,16 @@ struct SettingsView: View {
VStack(spacing: Spacing.md) {
appLanguageSection
engineSection
// v0.2.1: hide provider/api card when the
// local engine is active regardless of the
// cloud-polish toggle. Local mode is
// contractually ASR-only, so provider/model/
// base URL/API key controls have no use
// and exposing them invites the user to fill
// out a DeepSeek key they can't use.
if config.engineMode == "cloud" {
providerSection
apiSection
} else if config.localModeCloudPolishEnabled {
// v0.2.0: local engine + cloud polish on.
// Surface the provider / API key fields so
// the user can fill in their DeepSeek key.
// We hide them when the toggle is off so the
// local engine stays genuinely local.
providerSection
apiSection
}
languageAndModelsSection
if config.engineMode == "cloud" {
@@ -128,10 +127,12 @@ struct SettingsView: View {
VStack(alignment: .leading, spacing: SettingsListMetrics.sectionLabelSpacing) {
sectionHeader("settings.language.title")
VStack(spacing: 0) {
if config.engineMode == "local" {
LocalModelsGroup(config: config)
Divider().background(palette.divider)
}
// v0.2.1: language tab reorder ASR locale ("")
// now sits above the cloud-polish toggle / local models
// block so the row that maps to microphone input comes
// first, the row that maps to post-processing comes
// second, and translation (post-post-processing) sits at
// the bottom.
LocalePickerRow(
locales: effectiveLocales,
selection: Binding(
@@ -139,11 +140,10 @@ struct SettingsView: View {
set: { config.localeId = $0 }
)
)
// v0.2.1: translation toggle + target-language picker.
// Sits at the bottom of the language section so the user
// finds it next to the ASR locale it complements. Reuses
// `config.translationEnabled` / `config.translationTargetLocaleId`
// bindings no new state, no new persistence path.
if config.engineMode == "local" {
Divider().background(palette.divider)
LocalModelsGroup(config: config)
}
Divider().background(palette.divider)
TranslationPickerRow(config: config)
}