db65db6232
Android UI Tests / ui-tests (push) Has been cancelled
Localize all user-facing strings across iOS (SwiftUI), shared Kotlin, and Android Compose into en/es/fr/de/pt/it/ja/ko/nl/zh: - iOS String Catalogs: main + widget Localizable.xcstrings, InfoPlist.xcstrings (permissions), plural variations, ~200 new keys translated - Shared Kotlin ClientStrings table + Android composeResources/values-* (884 keys ×10), routed Api/ViewModel/util error & UI strings through localization - Backend-localized lookups/suggestions consumed via display names - Widget extension catalog; theme names, home-profile fallbacks, validation, network errors, accessibility labels all localized Add re-runnable verification gates: - scripts/i18n_audit.py — enumerate every literal, partition to GAP=0 - scripts/i18n_coverage.py — all 10 locales translated, format-specifier parity Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
143 lines
5.0 KiB
Swift
143 lines
5.0 KiB
Swift
import UserNotifications
|
|
|
|
/// Notification Category Identifiers matching backend constants
|
|
enum NotificationCategoryID: String {
|
|
case taskActionable = "TASK_ACTIONABLE" // overdue, due_soon, upcoming
|
|
case taskInProgress = "TASK_IN_PROGRESS" // tasks in progress
|
|
case taskCancelled = "TASK_CANCELLED" // cancelled tasks
|
|
case taskCompleted = "TASK_COMPLETED" // completed tasks (read-only)
|
|
case taskGeneric = "TASK_NOTIFICATION_GENERIC" // non-premium users
|
|
}
|
|
|
|
/// Action Identifiers for notification buttons
|
|
enum NotificationActionID: String {
|
|
// Task actions
|
|
case viewTask = "VIEW_TASK"
|
|
case completeTask = "COMPLETE_TASK"
|
|
case markInProgress = "MARK_IN_PROGRESS"
|
|
case cancelTask = "CANCEL_TASK"
|
|
case uncancelTask = "UNCANCEL_TASK"
|
|
case editTask = "EDIT_TASK"
|
|
|
|
// Default action (tapped notification body)
|
|
case defaultAction = "com.apple.UNNotificationDefaultActionIdentifier"
|
|
}
|
|
|
|
/// Manages notification category registration
|
|
struct NotificationCategories {
|
|
|
|
/// Registers all notification categories with the system
|
|
/// Call this early in app launch (didFinishLaunching)
|
|
static func registerCategories() {
|
|
let categories = createAllCategories()
|
|
UNUserNotificationCenter.current().setNotificationCategories(categories)
|
|
#if DEBUG
|
|
print("Registered \(categories.count) notification categories")
|
|
#endif
|
|
}
|
|
|
|
/// Creates all notification categories for the app
|
|
private static func createAllCategories() -> Set<UNNotificationCategory> {
|
|
return [
|
|
createTaskActionableCategory(),
|
|
createTaskInProgressCategory(),
|
|
createTaskCancelledCategory(),
|
|
createTaskCompletedCategory(),
|
|
createTaskGenericCategory()
|
|
]
|
|
}
|
|
|
|
// MARK: - Category Definitions
|
|
|
|
/// TASK_ACTIONABLE: For overdue, due_soon, upcoming tasks
|
|
/// Actions: Complete, Mark In Progress, Cancel
|
|
private static func createTaskActionableCategory() -> UNNotificationCategory {
|
|
let completeAction = UNNotificationAction(
|
|
identifier: NotificationActionID.completeTask.rawValue,
|
|
title: String(localized: "Complete"),
|
|
options: []
|
|
)
|
|
|
|
let inProgressAction = UNNotificationAction(
|
|
identifier: NotificationActionID.markInProgress.rawValue,
|
|
title: String(localized: "Start"),
|
|
options: []
|
|
)
|
|
|
|
let cancelAction = UNNotificationAction(
|
|
identifier: NotificationActionID.cancelTask.rawValue,
|
|
title: String(localized: "Cancel"),
|
|
options: [.destructive]
|
|
)
|
|
|
|
return UNNotificationCategory(
|
|
identifier: NotificationCategoryID.taskActionable.rawValue,
|
|
actions: [completeAction, inProgressAction, cancelAction],
|
|
intentIdentifiers: [],
|
|
options: []
|
|
)
|
|
}
|
|
|
|
/// TASK_IN_PROGRESS: For tasks currently being worked on
|
|
/// Actions: Complete, Cancel
|
|
private static func createTaskInProgressCategory() -> UNNotificationCategory {
|
|
let completeAction = UNNotificationAction(
|
|
identifier: NotificationActionID.completeTask.rawValue,
|
|
title: String(localized: "Complete"),
|
|
options: []
|
|
)
|
|
|
|
let cancelAction = UNNotificationAction(
|
|
identifier: NotificationActionID.cancelTask.rawValue,
|
|
title: String(localized: "Cancel"),
|
|
options: [.destructive]
|
|
)
|
|
|
|
return UNNotificationCategory(
|
|
identifier: NotificationCategoryID.taskInProgress.rawValue,
|
|
actions: [completeAction, cancelAction],
|
|
intentIdentifiers: [],
|
|
options: []
|
|
)
|
|
}
|
|
|
|
/// TASK_CANCELLED: For cancelled tasks
|
|
/// Actions: Uncancel
|
|
private static func createTaskCancelledCategory() -> UNNotificationCategory {
|
|
let uncancelAction = UNNotificationAction(
|
|
identifier: NotificationActionID.uncancelTask.rawValue,
|
|
title: String(localized: "Restore"),
|
|
options: []
|
|
)
|
|
|
|
return UNNotificationCategory(
|
|
identifier: NotificationCategoryID.taskCancelled.rawValue,
|
|
actions: [uncancelAction],
|
|
intentIdentifiers: [],
|
|
options: []
|
|
)
|
|
}
|
|
|
|
/// TASK_COMPLETED: For completed tasks (read-only, tap to view)
|
|
/// No actions - just view on tap
|
|
private static func createTaskCompletedCategory() -> UNNotificationCategory {
|
|
return UNNotificationCategory(
|
|
identifier: NotificationCategoryID.taskCompleted.rawValue,
|
|
actions: [],
|
|
intentIdentifiers: [],
|
|
options: []
|
|
)
|
|
}
|
|
|
|
/// TASK_NOTIFICATION_GENERIC: For non-premium users
|
|
/// No actions - tap opens app home
|
|
private static func createTaskGenericCategory() -> UNNotificationCategory {
|
|
return UNNotificationCategory(
|
|
identifier: NotificationCategoryID.taskGeneric.rawValue,
|
|
actions: [],
|
|
intentIdentifiers: [],
|
|
options: []
|
|
)
|
|
}
|
|
}
|