mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-07 19:06:11 +00:00
Part 1 — Simplified Chinese localization of the iOS app layer: - ios/Hapi/Resources/Localizable.xcstrings: hand-authored String Catalog (395 keys, zh-Hans; en implicit as source). Terminology mined from web/src/lib/locales/zh-CN.ts (会话/新建会话/权限模式/允许/拒绝/工作树/ 机器/语音输入/用量/草稿夹 …). - Mechanical edits only: merged multi-part string concatenations into single LocalizedStringKey literals, converted ternary/plain-String user copy to String(localized:), switched helper params (detailRow, DashboardCard, optionPicker, notice) to LocalizedStringKey. - LocalizedNoticeMapper (app layer): display-point translation of the 27 known HapiKit-emitted strings (ChatInteractor notices, dictation errors, window-sync warnings, files fallbacks, worktree-name validation) with verbatim passthrough for server-originated text; package stays language-free. - Deliberately untranslated (web parity / non-copy): event rows (EventPresentation — web renders presentation.ts verbatim), catalog option labels (Default/Auto/Sonnet/permission modes — web shows them verbatim), tool names, code-like titles (grep(pattern:), MCP:, Skill, Task), unit suffixes (B/KB/MB, m/h/d/w), decorative separators. - Language row wired: AppLanguage gains .system (follow system, new default); explicit picks write the AppleLanguages override, Follow system removes it; footer notes a relaunch applies it (no supported in-place SwiftUI locale swap). - project.pbxproj: zh-Hans added to knownRegions. Part 2 — #39 catalog dedupe (HapiProtocol): - NewSessionCatalogs.claudeModels/claudeEfforts now derive from ClaudeModels/ClaudeEfforts (single source shared with ModelCatalog); codexReasoningEfforts stays own data (web CODEX_REASONING_EFFORT_OPTIONS minus max); effortLabel delegates to ModelCatalog.capitalizedFirst. - New CatalogTests lock test for the derived option lists. Gate: bash ios/scripts/linux-test.sh — 459 tests green.
112 lines
3.8 KiB
Swift
112 lines
3.8 KiB
Swift
import Foundation
|
|
import Observation
|
|
|
|
/// Theme choice (web `ThemeMode` twin; Android
|
|
/// `feature/settings/ThemePrefs.kt`). ``oled`` is the pure-black variant — it
|
|
/// implies dark and maps to the HapiUI `.oled` palette at the app's root.
|
|
public enum ThemeMode: String, CaseIterable, Sendable {
|
|
case system
|
|
case light
|
|
case dark
|
|
case oled
|
|
|
|
/// Unknown/corrupt stored values degrade to ``system``.
|
|
public init(storageKey: String?) {
|
|
self = storageKey.flatMap(ThemeMode.init(rawValue:)) ?? .system
|
|
}
|
|
|
|
public var storageKey: String { rawValue }
|
|
|
|
/// The palette this mode resolves to: ``system`` follows the OS
|
|
/// appearance, explicit modes override it.
|
|
public func resolvedAppearance(systemIsDark: Bool) -> ResolvedAppearance {
|
|
switch self {
|
|
case .system: return systemIsDark ? .dark : .light
|
|
case .light: return .light
|
|
case .dark: return .dark
|
|
case .oled: return .oled
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The appearance a ``ThemeMode`` resolves to for a given system scheme.
|
|
/// The app maps this to the HapiUI palette (`.light` / `.dark` / `.oled`)
|
|
/// and to `preferredColorScheme`; kept SwiftUI-free here so it is testable.
|
|
public enum ResolvedAppearance: Equatable, Sendable {
|
|
case light
|
|
case dark
|
|
case oled
|
|
}
|
|
|
|
/// App language choice (web `Locale` twin plus a follow-system default).
|
|
///
|
|
/// A-M5 wires the selection to an `AppleLanguages` override at the app layer
|
|
/// (explicit picks pin the app locale, ``system`` removes the override);
|
|
/// this type only persists the choice.
|
|
public enum AppLanguage: String, CaseIterable, Sendable {
|
|
case system = "system"
|
|
case english = "en"
|
|
case simplifiedChinese = "zh-Hans"
|
|
|
|
/// Unknown/corrupt/absent stored values degrade to ``system``.
|
|
public init(storageKey: String?) {
|
|
self = storageKey.flatMap(AppLanguage.init(rawValue:)) ?? .system
|
|
}
|
|
|
|
public var storageKey: String { rawValue }
|
|
|
|
/// Explicit language names are shown in their own language (standard
|
|
/// picker convention). ``system``'s label is UI copy — the app layer
|
|
/// localizes it at the display point (this package stays language-free).
|
|
public var displayName: String {
|
|
switch self {
|
|
case .system: return "Follow system"
|
|
case .english: return "English"
|
|
case .simplifiedChinese: return "简体中文"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Appearance persistence over `UserDefaults` (app-wide, like the Android
|
|
/// `hapi_prefs` DataStore). Read at the app root to drive the HapiUI palette,
|
|
/// written by the settings screen; unreadable values degrade to defaults.
|
|
@MainActor @Observable
|
|
public final class ThemePrefs {
|
|
public private(set) var mode: ThemeMode
|
|
|
|
// `let` storage is inert under @Observable (no annotation needed).
|
|
private let defaults: UserDefaults
|
|
|
|
public static let modeKey = "settings.themeMode"
|
|
|
|
public init(defaults: UserDefaults = .standard) {
|
|
self.defaults = defaults
|
|
self.mode = ThemeMode(storageKey: defaults.string(forKey: Self.modeKey))
|
|
}
|
|
|
|
public func setMode(_ mode: ThemeMode) {
|
|
self.mode = mode
|
|
defaults.set(mode.storageKey, forKey: Self.modeKey)
|
|
}
|
|
}
|
|
|
|
/// Language persistence over `UserDefaults` (persist-only until M5).
|
|
@MainActor @Observable
|
|
public final class LanguagePrefs {
|
|
public private(set) var language: AppLanguage
|
|
|
|
private let defaults: UserDefaults
|
|
|
|
public static let languageKey = "settings.appLanguage"
|
|
|
|
public init(defaults: UserDefaults = .standard) {
|
|
self.defaults = defaults
|
|
self.language = AppLanguage(storageKey: defaults.string(forKey: Self.languageKey))
|
|
}
|
|
|
|
public func setLanguage(_ language: AppLanguage) {
|
|
self.language = language
|
|
defaults.set(language.storageKey, forKey: Self.languageKey)
|
|
}
|
|
}
|