Files
kangkang/康康/Services/ReminderService.swift
link2026 d2c77d5c51 feat: 国际化(i18n) en/ja/ko + App 内语言切换
主体:多语言支持(简体中文源 + 英/日/韩)
- 基础设施:Localizable.xcstrings(String Catalog,sourceLanguage=zh-Hans)
  + pbxproj developmentRegion/knownRegions 注册 en/ja/ko
- 全部硬编码 Locale("zh_CN") → Locale.current;中文 dateFormat → Date.FormatStyle(跟随系统)
- UI 中文字面量统一为 String(appLoc:)(显式绑定所选语言 bundle+locale,即时切换)
  Text 字面量走环境 \.locale + Bundle 重定向
- 549 个 catalog key 全部 en/ja/ko 翻译完成(0 未翻译)
- App 内语言切换:我的 → 语言(LanguageManager + 即时生效,无需重启)
- 双用预设(症状/监测指标/慢病)本地化:static→computed 避免缓存

注:本提交为 WIP,一并打包了并行进行的功能模块
(HealthExport 健康导出、Security/Face ID 锁、DiaryAssist 日记 AI 辅助)
及 App 图标、CLAUDE.md、docs/scripts。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 10:28:24 +08:00

95 lines
3.8 KiB
Swift

import Foundation
import UserNotifications
///
/// `metricId` iOS N weekly-repeats ,id
/// `kangkang.reminder.<metricId>.w<weekday>`,便 weekday cancel
///
/// SwiftData `MetricReminder`;,
/// SwiftData
enum ReminderService {
static let idPrefix = "kangkang.reminder."
enum AuthState: String {
case granted, denied, notDetermined, provisional
}
// MARK: - authorization
static func currentAuthState() async -> AuthState {
let settings = await UNUserNotificationCenter.current().notificationSettings()
switch settings.authorizationStatus {
case .authorized: return .granted
case .denied: return .denied
case .provisional: return .provisional
case .ephemeral: return .granted
case .notDetermined: return .notDetermined
@unknown default: return .notDetermined
}
}
/// granted/denied
@discardableResult
static func requestAuthorization() async -> AuthState {
let center = UNUserNotificationCenter.current()
let settings = await center.notificationSettings()
if settings.authorizationStatus != .notDetermined {
return await currentAuthState()
}
do {
let granted = try await center.requestAuthorization(options: [.alert, .sound, .badge])
return granted ? .granted : .denied
} catch {
return .denied
}
}
// MARK: - upsert / cancel
/// metric pending , enabled//weekdays
/// `MetricReminder` save
static func sync(_ reminder: MetricReminder) async {
cancel(metricId: reminder.metricId)
guard reminder.enabled, !reminder.weekdays.isEmpty else { return }
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = String(appLoc: "该测\(reminder.displayName)")
content.body = String(appLoc: "在「+ 新建 → 指标记录 → \(reminder.displayName)」记录一次")
content.sound = .default
content.threadIdentifier = "kangkang.reminder.\(reminder.metricId)"
for weekday in reminder.weekdays {
var comps = DateComponents()
comps.hour = reminder.hour
comps.minute = reminder.minute
comps.weekday = weekday
let trigger = UNCalendarNotificationTrigger(dateMatching: comps, repeats: true)
let id = identifier(metricId: reminder.metricId, weekday: weekday)
let request = UNNotificationRequest(identifier: id,
content: content,
trigger: trigger)
try? await center.add(request)
}
}
/// metric pending (7 weekday ,)
static func cancel(metricId: String) {
let center = UNUserNotificationCenter.current()
let ids = (1...7).map { identifier(metricId: metricId, weekday: $0) }
center.removePendingNotificationRequests(withIdentifiers: ids)
}
/// Me Tab
static func cancelAll() {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
}
// MARK: - helpers
private static func identifier(metricId: String, weekday: Int) -> String {
"\(idPrefix)\(metricId).w\(weekday)"
}
}