feat(keyboard): harden clipboard command and add What's New sheet
Stabilize clipboard long-press prepare/resume across paste alerts and cold start, add an in-app release-notes sheet with remote bilingual HTML, localize typing input settings, and bump build to 55.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 649 KiB |
@@ -8,6 +8,18 @@
|
||||
"hidden" : false,
|
||||
"layers" : [
|
||||
{
|
||||
"image-name" : "Group 29 3.png",
|
||||
"name" : "Group 29 3",
|
||||
"position" : {
|
||||
"scale" : 0.67,
|
||||
"translation-in-points" : [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"hidden" : true,
|
||||
"image-name" : "Group 29.svg",
|
||||
"name" : "Group 29"
|
||||
}
|
||||
|
||||
@@ -1141,9 +1141,19 @@ final class FlowSessionManager: ObservableObject {
|
||||
if capture.engineHasRecentAudio(maxAge: 2) {
|
||||
micReady = true
|
||||
} else {
|
||||
micReady = await capture.awaitAudioFlowing(
|
||||
var flowing = await capture.awaitAudioFlowing(
|
||||
timeout: Self.coldStartAudioProofTimeout
|
||||
)
|
||||
if !flowing {
|
||||
// First cold capture after PiP arm often proves audio late — one rebuild.
|
||||
debug("PiP audio proof timeout — one capture rebuild before failing")
|
||||
capture.stop(releaseSession: false)
|
||||
_ = await startCaptureForPiPUtteranceIfNeeded()
|
||||
flowing = await capture.awaitAudioFlowing(
|
||||
timeout: Self.coldStartAudioProofTimeout
|
||||
)
|
||||
}
|
||||
micReady = flowing
|
||||
}
|
||||
guard !Task.isCancelled, canContinueStart(startToken) else {
|
||||
releaseOrphanedCaptureIfNeeded()
|
||||
@@ -1168,16 +1178,33 @@ final class FlowSessionManager: ObservableObject {
|
||||
}
|
||||
|
||||
/// Start capture for a PiP utterance without blocking on the first frame.
|
||||
/// Cold first start after relaunch often needs one rebuild (VPIO / -66635).
|
||||
private func startCaptureForPiPUtteranceIfNeeded() async -> Bool {
|
||||
if capture.engineHasRecentAudio(maxAge: 2) {
|
||||
return true
|
||||
}
|
||||
do {
|
||||
try await capture.start()
|
||||
return true
|
||||
if capture.engineIsLive || capture.engineHasRecentAudio(maxAge: 2) {
|
||||
return true
|
||||
}
|
||||
debug("PiP utterance capture soft-dead after start — one rebuild retry")
|
||||
capture.stop(releaseSession: false)
|
||||
try? await Task.sleep(nanoseconds: 120_000_000)
|
||||
try await capture.start()
|
||||
// Do not accept `running && !engineLive` — that is the silent-mic failure mode.
|
||||
return capture.engineIsLive || capture.engineHasRecentAudio(maxAge: 2)
|
||||
} catch {
|
||||
debug("PiP utterance capture start failed: \(error.localizedDescription)")
|
||||
return false
|
||||
debug("PiP utterance capture start failed: \(error.localizedDescription) — retry once")
|
||||
capture.stop(releaseSession: false)
|
||||
try? await Task.sleep(nanoseconds: 150_000_000)
|
||||
do {
|
||||
try await capture.start()
|
||||
return capture.engineIsLive || capture.engineHasRecentAudio(maxAge: 2)
|
||||
} catch {
|
||||
debug("PiP utterance capture retry failed: \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,4 +15,10 @@ enum LegalLinks {
|
||||
static var supportURL: URL? {
|
||||
URL(string: "https://github.com/hkgood/OSGKeyboard/issues")
|
||||
}
|
||||
|
||||
/// In-app release notes (hosted on download.osglab.com). Prefer
|
||||
/// `ReleaseNotesStore.pageURL(language:colorScheme:)` so v/lang/theme are set.
|
||||
static var releaseNotesURL: URL? {
|
||||
URL(string: ReleaseNotesStore.pageURLString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// ReleaseNotesStore.swift
|
||||
// OSGKeyboard · Main App
|
||||
//
|
||||
// Tracks which marketing version's release notes the user has already seen,
|
||||
// and builds the remote release-notes URL with v / lang / theme query params.
|
||||
|
||||
import Foundation
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
|
||||
enum ReleaseNotesStore {
|
||||
static let lastSeenKey = "config.lastSeenMarketingVersion"
|
||||
static let pageURLString = "https://download.osglab.com/osgkeyboardversion.html"
|
||||
|
||||
static var lastSeenMarketingVersion: String? {
|
||||
get {
|
||||
UserDefaults.standard.string(forKey: lastSeenKey)
|
||||
}
|
||||
set {
|
||||
if let newValue {
|
||||
UserDefaults.standard.set(newValue, forKey: lastSeenKey)
|
||||
} else {
|
||||
UserDefaults.standard.removeObject(forKey: lastSeenKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// True when the user has not acknowledged the current marketing version.
|
||||
static var shouldPresentAutomatically: Bool {
|
||||
let current = AppVersionDisplay.marketingVersion
|
||||
guard current != "—" else { return false }
|
||||
return lastSeenMarketingVersion != current
|
||||
}
|
||||
|
||||
static func markCurrentVersionSeen() {
|
||||
let current = AppVersionDisplay.marketingVersion
|
||||
guard current != "—" else { return }
|
||||
lastSeenMarketingVersion = current
|
||||
}
|
||||
|
||||
/// Query language for the remote page (`zh` / `en`).
|
||||
static func queryLanguage(for uiLanguage: AppUILanguage) -> String {
|
||||
let code = uiLanguage.resolvedLanguageCode()
|
||||
return code.hasPrefix("zh") ? "zh" : "en"
|
||||
}
|
||||
|
||||
/// Query theme for the remote page (`light` / `dark`).
|
||||
static func queryTheme(for colorScheme: ColorScheme) -> String {
|
||||
colorScheme == .dark ? "dark" : "light"
|
||||
}
|
||||
|
||||
static func pageURL(
|
||||
version: String = AppVersionDisplay.marketingVersion,
|
||||
language: AppUILanguage,
|
||||
colorScheme: ColorScheme
|
||||
) -> URL? {
|
||||
guard version != "—",
|
||||
var components = URLComponents(string: pageURLString) else { return nil }
|
||||
components.queryItems = [
|
||||
URLQueryItem(name: "v", value: version),
|
||||
URLQueryItem(name: "lang", value: queryLanguage(for: language)),
|
||||
URLQueryItem(name: "theme", value: queryTheme(for: colorScheme)),
|
||||
]
|
||||
return components.url
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class ReleaseNotesController: ObservableObject {
|
||||
static let shared = ReleaseNotesController()
|
||||
|
||||
@Published var isPresented = false
|
||||
|
||||
func presentIfNeeded(onboardingCompleted: Bool) {
|
||||
guard onboardingCompleted, ReleaseNotesStore.shouldPresentAutomatically else { return }
|
||||
isPresented = true
|
||||
}
|
||||
|
||||
func presentManually() {
|
||||
isPresented = true
|
||||
}
|
||||
}
|
||||
@@ -19,15 +19,26 @@ struct RemoteWebView: UIViewRepresentable {
|
||||
webView.isOpaque = false
|
||||
webView.backgroundColor = .clear
|
||||
webView.scrollView.backgroundColor = .clear
|
||||
// Sheet / NavigationStack already lays out inside the safe area.
|
||||
// Automatic adjustment would add a second bottom inset and leave a dead band.
|
||||
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
webView.scrollView.contentInset = .zero
|
||||
webView.scrollView.scrollIndicatorInsets = .zero
|
||||
webView.navigationDelegate = context.coordinator
|
||||
context.coordinator.loadedURL = url
|
||||
webView.load(URLRequest(url: url))
|
||||
return webView
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: WKWebView, context: Context) {}
|
||||
func updateUIView(_ uiView: WKWebView, context: Context) {
|
||||
guard context.coordinator.loadedURL != url else { return }
|
||||
context.coordinator.loadedURL = url
|
||||
uiView.load(URLRequest(url: url))
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, WKNavigationDelegate {
|
||||
@Binding var isLoading: Bool
|
||||
var loadedURL: URL?
|
||||
|
||||
init(isLoading: Binding<Bool>) {
|
||||
_isLoading = isLoading
|
||||
|
||||
@@ -15,6 +15,7 @@ struct MainAppRoot: View {
|
||||
// Singleton is owned by `ProviderConfig.shared`, not by this view —
|
||||
// `@ObservedObject` keeps subscriptions correct across Settings replay.
|
||||
@ObservedObject private var config = ProviderConfig.shared
|
||||
@ObservedObject private var releaseNotes = ReleaseNotesController.shared
|
||||
@StateObject private var flowManager = FlowSessionManager()
|
||||
@State private var postOnboardingWarmupTask: Task<Void, Never>?
|
||||
|
||||
@@ -40,6 +41,14 @@ struct MainAppRoot: View {
|
||||
.allowsHitTesting(false)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
.sheet(isPresented: $releaseNotes.isPresented, onDismiss: {
|
||||
// Auto-present and Settings entry both count as acknowledged.
|
||||
ReleaseNotesStore.markCurrentVersionSeen()
|
||||
}) {
|
||||
// Pass language explicitly; sheet chrome uses AppL10n (in-app override).
|
||||
ReleaseNotesSheet(language: config.uiLanguage)
|
||||
.environment(\.locale, config.uiLanguage.swiftUILocale)
|
||||
}
|
||||
.onAppear {
|
||||
flowManager.setAppForeground(scenePhase == .active)
|
||||
// Register the URL handler BEFORE the foreground auto-start.
|
||||
@@ -66,6 +75,7 @@ struct MainAppRoot: View {
|
||||
// Capture/ASR remain lazy and start only on an actual mic press.
|
||||
flowManager.activateOnForeground(reason: "MainAppRoot.onAppear")
|
||||
schedulePostOnboardingWarmup(reason: "MainAppRoot.onAppear")
|
||||
releaseNotes.presentIfNeeded(onboardingCompleted: true)
|
||||
} else {
|
||||
OSGDiag.log(
|
||||
"MainAppRoot.onAppear skip Flow/CLM/Rime (onboarding incomplete)",
|
||||
@@ -80,6 +90,7 @@ struct MainAppRoot: View {
|
||||
if done {
|
||||
flowManager.activateOnForeground(reason: "onboardingCompleted")
|
||||
schedulePostOnboardingWarmup(reason: "onboardingCompleted")
|
||||
releaseNotes.presentIfNeeded(onboardingCompleted: true)
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { _, phase in
|
||||
@@ -94,6 +105,7 @@ struct MainAppRoot: View {
|
||||
flowManager.activateOnForeground(reason: "scenePhase.active")
|
||||
// Retry deferred Rime/CLM after a jetsam-prone launch.
|
||||
schedulePostOnboardingWarmup(reason: "scenePhase.active.retry")
|
||||
releaseNotes.presentIfNeeded(onboardingCompleted: true)
|
||||
}
|
||||
Task {
|
||||
await AppCloudSync.shared.pullAllIfEnabled()
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// ReleaseNotesSheet.swift
|
||||
// OSGKeyboard · Main App
|
||||
//
|
||||
// Sheet that loads the remote release-notes HTML for the current version.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
|
||||
struct ReleaseNotesSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.themePalette) private var palette: ThemePalette
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
/// In-app UI language (not system-only) — chrome strings go through `AppL10n`.
|
||||
let language: AppUILanguage
|
||||
|
||||
@AppStorage(AppearancePreference.storageKey)
|
||||
private var appearanceRaw = AppearancePreference.system.rawValue
|
||||
|
||||
@State private var isLoading = true
|
||||
|
||||
private var appearance: AppearancePreference {
|
||||
AppearancePreference.fromStored(appearanceRaw)
|
||||
}
|
||||
|
||||
/// Theme actually shown in this sheet (honors Settings → Appearance).
|
||||
private var resolvedColorScheme: ColorScheme {
|
||||
appearance.colorScheme ?? colorScheme
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Group {
|
||||
if let url = ReleaseNotesStore.pageURL(
|
||||
language: language,
|
||||
colorScheme: resolvedColorScheme
|
||||
) {
|
||||
ZStack {
|
||||
RemoteWebView(url: url, isLoading: $isLoading)
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.tint(palette.accent)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ContentUnavailableView(
|
||||
AppL10n.string("releaseNotes.unavailable.title", language: language),
|
||||
systemImage: "wifi.slash",
|
||||
description: Text(
|
||||
AppL10n.string("releaseNotes.unavailable.message", language: language)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.background(palette.background.ignoresSafeArea())
|
||||
.navigationTitle(AppL10n.string("releaseNotes.title", language: language))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(AppL10n.string("common.done", language: language)) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sheet can drop WindowGroup environment; re-assert language + appearance.
|
||||
.environment(\.locale, language.swiftUILocale)
|
||||
.preferredColorScheme(appearance.colorScheme)
|
||||
.environment(\.themePalette, resolvedColorScheme == .dark ? Palette.dark : Palette.light)
|
||||
}
|
||||
}
|
||||
@@ -132,15 +132,16 @@ struct PolishIntensityPickerRow: View {
|
||||
|
||||
struct DefaultTypingInputToggleRow: View {
|
||||
@Environment(\.themePalette) private var palette: ThemePalette
|
||||
@ObservedObject private var config = ProviderConfig.shared
|
||||
@Binding var isOn: Bool
|
||||
|
||||
var body: some View {
|
||||
Toggle(isOn: $isOn) {
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text("settings.typingInput.default.title")
|
||||
Text(AppL10n.string("settings.typingInput.default.title", language: config.uiLanguage))
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
Text("settings.typingInput.default.description")
|
||||
Text(AppL10n.string("settings.typingInput.default.description", language: config.uiLanguage))
|
||||
.font(TypeStyle.caption)
|
||||
.foregroundStyle(palette.textSecondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
@@ -153,15 +154,16 @@ struct DefaultTypingInputToggleRow: View {
|
||||
|
||||
struct RememberLastSurfaceToggleRow: View {
|
||||
@Environment(\.themePalette) private var palette: ThemePalette
|
||||
@ObservedObject private var config = ProviderConfig.shared
|
||||
@Binding var isOn: Bool
|
||||
|
||||
var body: some View {
|
||||
Toggle(isOn: $isOn) {
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text("settings.typingInput.rememberLast.title")
|
||||
Text(AppL10n.string("settings.typingInput.rememberLast.title", language: config.uiLanguage))
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
Text("settings.typingInput.rememberLast.description")
|
||||
Text(AppL10n.string("settings.typingInput.rememberLast.description", language: config.uiLanguage))
|
||||
.font(TypeStyle.caption)
|
||||
.foregroundStyle(palette.textSecondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
@@ -13,15 +13,35 @@ import OSGKeyboardShared
|
||||
struct SettingsNavigationRow: View {
|
||||
@Environment(\.themePalette) private var palette: ThemePalette
|
||||
|
||||
let title: LocalizedStringKey
|
||||
private let localizedTitle: LocalizedStringKey?
|
||||
private let resolvedTitle: String?
|
||||
var subtitle: String?
|
||||
|
||||
init(title: LocalizedStringKey, subtitle: String? = nil) {
|
||||
self.localizedTitle = title
|
||||
self.resolvedTitle = nil
|
||||
self.subtitle = subtitle
|
||||
}
|
||||
|
||||
/// Prefers in-app language via an already-resolved string (`AppL10n`).
|
||||
init(titleText: String, subtitle: String? = nil) {
|
||||
self.localizedTitle = nil
|
||||
self.resolvedTitle = titleText
|
||||
self.subtitle = subtitle
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Spacing.sm) {
|
||||
VStack(alignment: .leading, spacing: Spacing.xxs) {
|
||||
Text(title)
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
if let resolvedTitle {
|
||||
Text(resolvedTitle)
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
} else if let localizedTitle {
|
||||
Text(localizedTitle)
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
}
|
||||
if let subtitle, !subtitle.isEmpty {
|
||||
Text(subtitle)
|
||||
.font(TypeStyle.caption2)
|
||||
@@ -39,23 +59,33 @@ struct SettingsNavigationRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-only version / build row for Settings home (below About).
|
||||
/// Version / build row for Settings home (below About). Opens release notes.
|
||||
struct SettingsVersionRow: View {
|
||||
@Environment(\.themePalette) private var palette: ThemePalette
|
||||
@ObservedObject private var config = ProviderConfig.shared
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Spacing.sm) {
|
||||
Text("settings.version.title")
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
Spacer(minLength: Spacing.xs)
|
||||
Text(AppVersionDisplay.detailedLabel)
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textSecondary)
|
||||
.multilineTextAlignment(.trailing)
|
||||
Button {
|
||||
ReleaseNotesController.shared.presentManually()
|
||||
} label: {
|
||||
HStack(spacing: Spacing.sm) {
|
||||
Text(AppL10n.string("settings.version.title", language: config.uiLanguage))
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
Spacer(minLength: Spacing.xs)
|
||||
Text(AppVersionDisplay.detailedLabel)
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textSecondary)
|
||||
.multilineTextAlignment(.trailing)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.foregroundStyle(palette.textTertiary)
|
||||
}
|
||||
.settingsListRow()
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.settingsListRow()
|
||||
.accessibilityElement(children: .combine)
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityHint(AppL10n.string("releaseNotes.openHint", language: config.uiLanguage))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +235,12 @@ struct GeneralSettingsView: View {
|
||||
NavigationLink {
|
||||
TypingInputSettingsView()
|
||||
} label: {
|
||||
SettingsNavigationRow(title: "settings.typingInput.title")
|
||||
SettingsNavigationRow(
|
||||
titleText: AppL10n.string(
|
||||
"settings.typingInput.title",
|
||||
language: config.uiLanguage
|
||||
)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ struct SettingsView: View {
|
||||
|
||||
Divider().background(palette.divider)
|
||||
|
||||
// Read-only version row — not tappable.
|
||||
// Opens the remote release-notes sheet (same as post-upgrade prompt).
|
||||
SettingsVersionRow()
|
||||
}
|
||||
.surfaceCard()
|
||||
|
||||
@@ -9,6 +9,7 @@ import OSGKeyboardShared
|
||||
|
||||
struct TypingInputSettingsView: View {
|
||||
@Environment(\.themePalette) private var palette: ThemePalette
|
||||
@ObservedObject private var config = ProviderConfig.shared
|
||||
@ObservedObject private var configuration = TypingInputConfiguration.shared
|
||||
|
||||
@State private var isDeploying = false
|
||||
@@ -16,10 +17,14 @@ struct TypingInputSettingsView: View {
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section("输入方案") {
|
||||
Picker("拼音方案", selection: $configuration.schema) {
|
||||
Section(AppL10n.string("settings.typingInput.schema.section", language: config.uiLanguage)) {
|
||||
Picker(
|
||||
AppL10n.string("settings.typingInput.schema.picker", language: config.uiLanguage),
|
||||
selection: $configuration.schema
|
||||
) {
|
||||
ForEach(TypingInputSchema.allCases) { schema in
|
||||
Text(schema.displayName).tag(schema)
|
||||
Text(AppL10n.string(schema.labelKey, language: config.uiLanguage))
|
||||
.tag(schema)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.inline)
|
||||
@@ -39,14 +44,14 @@ struct TypingInputSettingsView: View {
|
||||
)
|
||||
}
|
||||
} header: {
|
||||
Text("模糊音")
|
||||
Text(AppL10n.string("settings.typingInput.fuzzy.section", language: config.uiLanguage))
|
||||
} footer: {
|
||||
Text("默认全部关闭。只开启你需要的组合,避免候选噪音。")
|
||||
Text(AppL10n.string("settings.typingInput.fuzzy.footer", language: config.uiLanguage))
|
||||
}
|
||||
|
||||
Section("输入法资源") {
|
||||
Section(AppL10n.string("settings.typingInput.resources.section", language: config.uiLanguage)) {
|
||||
HStack {
|
||||
Text("状态")
|
||||
Text(AppL10n.string("settings.typingInput.resources.status", language: config.uiLanguage))
|
||||
Spacer()
|
||||
if isDeploying {
|
||||
ProgressView()
|
||||
@@ -59,7 +64,7 @@ struct TypingInputSettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Button("重新部署输入法资源") {
|
||||
Button(AppL10n.string("settings.typingInput.resources.redeploy", language: config.uiLanguage)) {
|
||||
deployUpdatedSchemas()
|
||||
}
|
||||
.disabled(isDeploying)
|
||||
@@ -67,13 +72,18 @@ struct TypingInputSettingsView: View {
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(palette.background)
|
||||
.navigationTitle("settings.typingInput.title")
|
||||
.navigationTitle(AppL10n.string("settings.typingInput.title", language: config.uiLanguage))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
private var statusText: String {
|
||||
if let deploymentError { return deploymentError }
|
||||
return RimeResourceInstaller.isReady ? "已就绪" : "待初始化"
|
||||
return AppL10n.string(
|
||||
RimeResourceInstaller.isReady
|
||||
? "settings.typingInput.resources.ready"
|
||||
: "settings.typingInput.resources.pending",
|
||||
language: config.uiLanguage
|
||||
)
|
||||
}
|
||||
|
||||
private func deployUpdatedSchemas() {
|
||||
|
||||
@@ -182,6 +182,10 @@
|
||||
"settings.systemPrompt.hint" = "Used by the cloud LLM when polishing your dictation. Local engine skips this step.";
|
||||
"settings.about.title" = "About";
|
||||
"settings.version.title" = "Version";
|
||||
"releaseNotes.title" = "What's New";
|
||||
"releaseNotes.openHint" = "View release notes for this version";
|
||||
"releaseNotes.unavailable.title" = "Cannot load page";
|
||||
"releaseNotes.unavailable.message" = "Release notes could not be opened. Check your network connection and try again.";
|
||||
"settings.daily.title" = "Daily";
|
||||
"settings.config.title" = "Configuration";
|
||||
"settings.general.title" = "General";
|
||||
@@ -193,6 +197,18 @@
|
||||
"settings.typingInput.default.description" = "Open the text input keyboard instead of voice input by default";
|
||||
"settings.typingInput.rememberLast.title" = "Remember Last Choice";
|
||||
"settings.typingInput.rememberLast.description" = "Reopen on the voice or text surface you left last time";
|
||||
"settings.typingInput.schema.section" = "Input Method";
|
||||
"settings.typingInput.schema.picker" = "Pinyin Scheme";
|
||||
"typing.schema.fullPinyin" = "Full Pinyin";
|
||||
"typing.schema.microsoftDoublePinyin" = "Microsoft Shuangpin";
|
||||
"typing.schema.sogouDoublePinyin" = "Sogou Shuangpin";
|
||||
"settings.typingInput.fuzzy.section" = "Fuzzy Pinyin";
|
||||
"settings.typingInput.fuzzy.footer" = "All off by default. Enable only the pairs you need to avoid noisy candidates.";
|
||||
"settings.typingInput.resources.section" = "Input Resources";
|
||||
"settings.typingInput.resources.status" = "Status";
|
||||
"settings.typingInput.resources.ready" = "Ready";
|
||||
"settings.typingInput.resources.pending" = "Not initialized";
|
||||
"settings.typingInput.resources.redeploy" = "Redeploy Input Resources";
|
||||
"settings.speechRecognition.title" = "Speech Recognition";
|
||||
"settings.textPolish.title" = "Text Polish";
|
||||
"settings.preferences.title" = "Preferences";
|
||||
|
||||
@@ -182,6 +182,10 @@
|
||||
"settings.systemPrompt.hint" = "云端润色时使用。本地识别模式不会用到此提示词。";
|
||||
"settings.about.title" = "关于";
|
||||
"settings.version.title" = "版本";
|
||||
"releaseNotes.title" = "更新说明";
|
||||
"releaseNotes.openHint" = "查看本版本更新说明";
|
||||
"releaseNotes.unavailable.title" = "无法加载页面";
|
||||
"releaseNotes.unavailable.message" = "无法打开更新说明,请检查网络连接后重试。";
|
||||
"settings.daily.title" = "日常";
|
||||
"settings.config.title" = "配置";
|
||||
"settings.general.title" = "通用";
|
||||
@@ -193,6 +197,18 @@
|
||||
"settings.typingInput.default.description" = "打开键盘时,默认使用文字输入键盘而非语音输入";
|
||||
"settings.typingInput.rememberLast.title" = "记住上次选择";
|
||||
"settings.typingInput.rememberLast.description" = "下次打开键盘时,保持你上次离开时的语音或文字输入界面";
|
||||
"settings.typingInput.schema.section" = "输入方案";
|
||||
"settings.typingInput.schema.picker" = "拼音方案";
|
||||
"typing.schema.fullPinyin" = "全拼";
|
||||
"typing.schema.microsoftDoublePinyin" = "微软双拼";
|
||||
"typing.schema.sogouDoublePinyin" = "搜狗双拼";
|
||||
"settings.typingInput.fuzzy.section" = "模糊音";
|
||||
"settings.typingInput.fuzzy.footer" = "默认全部关闭。只开启你需要的组合,避免候选噪音。";
|
||||
"settings.typingInput.resources.section" = "输入法资源";
|
||||
"settings.typingInput.resources.status" = "状态";
|
||||
"settings.typingInput.resources.ready" = "已就绪";
|
||||
"settings.typingInput.resources.pending" = "待初始化";
|
||||
"settings.typingInput.resources.redeploy" = "重新部署输入法资源";
|
||||
"settings.speechRecognition.title" = "语音识别配置";
|
||||
"settings.textPolish.title" = "文本润色配置";
|
||||
"settings.preferences.title" = "偏好设置";
|
||||
|
||||
Reference in New Issue
Block a user