feat(translation-v2): refactor translationEnabled into computed + onboarding row visibility
This commit is contained in:
@@ -138,7 +138,8 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
state.setLocale = { [weak self] l in self?.persistLocale(l) }
|
||||
state.setEngineMode = { [weak self] m in self?.persistEngineMode(m) }
|
||||
state.setLocalASRBackend = { [weak self] b in self?.persistLocalASRBackend(b) }
|
||||
state.setTranslationEnabled = { [weak self] enabled in self?.persistTranslationEnabled(enabled) }
|
||||
// v0.2.1 follow-up: removed `setTranslationEnabled` — the chip
|
||||
// / picker only writes the locale id now; `enabled` is derived.
|
||||
state.setTranslationTargetLocaleId = { [weak self] id in self?.persistTranslationTargetLocaleId(id) }
|
||||
state.insertNewline = { [weak self] in self?.textDocumentProxy.insertText("\n") }
|
||||
state.insertSpace = { [weak self] in self?.textDocumentProxy.insertText(" ") }
|
||||
@@ -645,20 +646,12 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
|
||||
// MARK: - Translation persistence
|
||||
|
||||
/// v0.2.1: persist translation toggle. When the user turns the
|
||||
/// feature on while the local engine is active we still write the
|
||||
/// value — `isTranslationEffective` will return `false` until they
|
||||
/// switch to cloud, but the chip on the keyboard reflects their
|
||||
/// intent immediately so they get feedback.
|
||||
private func persistTranslationEnabled(_ enabled: Bool) {
|
||||
state.translationEnabled = enabled
|
||||
persistor.persist(translationEnabled: enabled)
|
||||
}
|
||||
|
||||
/// v0.2.1: persist translation target locale id. Resolved via
|
||||
/// `TranslationLanguageCatalog.resolve` so a stale persisted value
|
||||
/// (e.g. a removed locale id from an older build) still finds the
|
||||
/// right entry instead of crashing the picker.
|
||||
/// v0.2.1 follow-up: persist translation target locale id. Resolved
|
||||
/// via `TranslationLanguageCatalog.resolve` so a stale persisted
|
||||
/// value (e.g. a removed locale id from an older build) still finds
|
||||
/// the right entry instead of crashing the picker. Translation's
|
||||
/// "on/off" state is now derived from this id (== `offLocaleId`
|
||||
/// means off), so there's no separate toggle to persist.
|
||||
private func persistTranslationTargetLocaleId(_ id: String) {
|
||||
let resolved = TranslationLanguageCatalog.resolve(id).id
|
||||
state.translationTargetLocaleId = resolved
|
||||
|
||||
@@ -35,11 +35,10 @@ public struct AppGroupPersistor {
|
||||
state.mode = .polish
|
||||
state.engineMode = store.engineMode
|
||||
state.localASRBackend = store.localASRBackend
|
||||
// v0.2.1: translation toggle + target locale. Read once at
|
||||
// hydration; `refreshRuntimeFlags` keeps them in sync while the
|
||||
// keyboard stays open so a Settings change shows up without a
|
||||
// re-present cycle.
|
||||
state.translationEnabled = store.translationEnabled
|
||||
// v0.2.1 follow-up: only the target locale is persisted —
|
||||
// `translationEnabled` is derived from it. Hydrate once at
|
||||
// startup; `refreshRuntimeFlags` keeps the chip in sync while
|
||||
// the keyboard stays open.
|
||||
state.translationTargetLocaleId = store.translationTargetLocaleId
|
||||
// v0.2.0: iOS `SpeechAnalyzer` is always ready; mirror that
|
||||
// into the State flags so downstream consumers see the same
|
||||
@@ -81,10 +80,8 @@ public struct AppGroupPersistor {
|
||||
let store = AppGroupStore()
|
||||
state.engineMode = store.engineMode
|
||||
state.localASRBackend = store.localASRBackend
|
||||
// v0.2.1: keep translation state in sync with the host app so the
|
||||
// chip on the keyboard reflects the latest value without a re-
|
||||
// present cycle.
|
||||
state.translationEnabled = store.translationEnabled
|
||||
// v0.2.1 follow-up: same as `load` — only the locale is
|
||||
// persisted, `enabled` is derived.
|
||||
state.translationTargetLocaleId = store.translationTargetLocaleId
|
||||
// v0.2.0: iOS `SpeechAnalyzer` is always ready. Keep these
|
||||
// toggles here so the keyboard UI doesn't flicker if the host
|
||||
@@ -117,14 +114,15 @@ public struct AppGroupPersistor {
|
||||
AppGroupStore().setLocalASRBackend(localASRBackend)
|
||||
}
|
||||
|
||||
/// v0.2.1: persist translation toggle. Wired through the
|
||||
/// `KeyboardViewController.setTranslation` action hook.
|
||||
public func persist(translationEnabled: Bool) {
|
||||
guard AppGroup.isAvailable else { return }
|
||||
AppGroupStore().setTranslationEnabled(translationEnabled)
|
||||
}
|
||||
|
||||
/// v0.2.1: persist translation target locale id (e.g. `"en"`).
|
||||
/// v0.2.1: persist translation target locale id (e.g. `"en"`,
|
||||
/// `"ja"`, or `TranslationLanguageCatalog.offLocaleId`). The
|
||||
/// chip / picker call this through `KeyboardState.setTranslationTargetLocaleId`.
|
||||
///
|
||||
/// v0.2.1 follow-up: removed `persist(translationEnabled:)` — the
|
||||
/// enabled state is derived from the locale id, so callers only
|
||||
/// need to write the locale. Keeping the legacy Bool overload
|
||||
/// around would have implied that there's a separate on/off
|
||||
/// switch to persist, which is no longer the model.
|
||||
public func persist(translationTargetLocaleId: String) {
|
||||
guard AppGroup.isAvailable else { return }
|
||||
AppGroupStore().setTranslationTargetLocaleId(translationTargetLocaleId)
|
||||
|
||||
@@ -5,10 +5,17 @@
|
||||
// top bar. Doubles as both the on/off switch and the target-language
|
||||
// picker — same Menu pattern as `LocaleChip` so muscle memory transfers.
|
||||
//
|
||||
// v0.2.1 follow-up: removed the explicit on/off toggle entry. The
|
||||
// chip is now a pure picker over the 11 catalog rows (off + 10
|
||||
// locales); selecting "不翻译" turns translation off, selecting any
|
||||
// locale turns it on with that target. `translationEnabled` is
|
||||
// derived from the locale id so the chip / pipeline read the same
|
||||
// source of truth.
|
||||
//
|
||||
// Visual states:
|
||||
// • disabled → dim outline, "翻译" label
|
||||
// • enabled + cloud → accent fill, "→ EN" / "→ 日本語" style label
|
||||
// • enabled + local engine→ warning fill + "翻译需云端" hint (effectively
|
||||
// • off → dim outline, "翻译" label
|
||||
// • on + cloud → accent fill, "→ EN" / "→ 日本語" style label
|
||||
// • on + local engine→ warning fill + "翻译需云端" hint (effectively
|
||||
// inert; the pipeline rejects the mode and the controller surfaces
|
||||
// the error toast)
|
||||
//
|
||||
@@ -26,28 +33,19 @@ struct TranslationChip: View {
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
// Toggle entry sits at the top so the user can flip the feature
|
||||
// without picking a language first.
|
||||
Button {
|
||||
state.setTranslationEnabled(!state.translationEnabled)
|
||||
} label: {
|
||||
if state.translationEnabled {
|
||||
Label(ExtL10n.string("keyboard.translation.disable"), systemImage: "checkmark")
|
||||
} else {
|
||||
Text(ExtL10n.string("keyboard.translation.enable"))
|
||||
}
|
||||
}
|
||||
if state.translationEnabled {
|
||||
Divider()
|
||||
ForEach(TranslationLanguageCatalog.all) { language in
|
||||
Button {
|
||||
state.setTranslationTargetLocaleId(language.id)
|
||||
} label: {
|
||||
if language.id == state.translationTargetLocaleId {
|
||||
Label(language.nativeName, systemImage: "checkmark")
|
||||
} else {
|
||||
Text(language.nativeName)
|
||||
}
|
||||
// v0.2.1 follow-up: pure picker over the full catalog,
|
||||
// including `offLocaleId` at the top so "turn off" is one
|
||||
// tap from any enabled state. Picking a row writes
|
||||
// `translationTargetLocaleId`; `translationEnabled` is
|
||||
// derived from it.
|
||||
ForEach(TranslationLanguageCatalog.all) { language in
|
||||
Button {
|
||||
state.setTranslationTargetLocaleId(language.id)
|
||||
} label: {
|
||||
if language.id == currentSelectionId {
|
||||
Label(displayLabel(for: language), systemImage: "checkmark")
|
||||
} else {
|
||||
Text(displayLabel(for: language))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +78,19 @@ struct TranslationChip: View {
|
||||
.overlay(Capsule().stroke(stroke(enabled: enabled, isLocal: isLocal), lineWidth: 0.5))
|
||||
}
|
||||
|
||||
/// Active selection id — the chip derives "on" from a non-off
|
||||
/// locale id, so reading `translationTargetLocaleId` is enough.
|
||||
private var currentSelectionId: String {
|
||||
state.translationTargetLocaleId
|
||||
}
|
||||
|
||||
private func displayLabel(for language: TranslationLanguage) -> String {
|
||||
if language.id == TranslationLanguageCatalog.offLocaleId {
|
||||
return ExtL10n.string("keyboard.translation.off")
|
||||
}
|
||||
return language.nativeName
|
||||
}
|
||||
|
||||
private func chipLabel(target: TranslationLanguage, enabled: Bool, isLocal: Bool) -> String {
|
||||
// Local-engine + on shows the constraint hint instead of the
|
||||
// target label so the user knows why nothing's happening.
|
||||
|
||||
Reference in New Issue
Block a user