Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
Rotate AI idle suggestions with optional remote packs, move history/dictionary onto self-sizing Home preview cards, harden clipboard capture/prompting, and simplify keyboard chrome by dropping most liquid-glass shadows.
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).
Adds an opt-in translation pipeline that reuses the existing
PolishingService + LLMClient + AppGroupStore chain. Translation is
implemented as a new PolishMode (.translate(targetLocaleId:)); all
existing call sites are unchanged.
Settings:
- New TranslationPickerRow in the language tab (Toggle + 10-locale
picker: en/zh-Hans/zh-Hant/ja/ko/fr/de/es/ru/pt), persisted to the
App Group so the keyboard extension can read it during live dictation.
- 5 new strings per language (en + zh-Hans).
Keyboard:
- New TranslationChip on the top bar to the right of LocaleChip;
same Menu pattern, lets users toggle or quickly switch target
language without leaving the keyboard.
- PolishingService dispatches .translate with a parameterised prompt
(en/zh variants selected by provider id); PolishingService.error
gains a translationNotAvailable case so local-engine users get a
clear inline warning when the toggle is on but cloud is off.
- 6 new strings per language (en + zh-Hans) for the chip + banner.
Local engine policy:
- Translation is cloud-only by design (local engine stays ASR-only
to honour the no-roundtrip promise). Chip shows a 'cloud required'
state and raw transcript still inserts on failure — no data loss.
Build:
- OSGKeyboardShared adds TranslationLanguage enum (10 locales) and
TranslationPrompt factory.
- 4 new files, 9 modified. xcodebuild scheme=OSGKeyboard
config=Debug destination=iPhone 17 Simulator: BUILD SUCCEEDED
(0 warning, 0 error).
Also pins DEVELOPMENT_TEAM in project.yml for TestFlight uploads
(3 targets; Team X329MZU23S).