diff --git a/Sources/MacOptimizer/App/AppState.swift b/Sources/MacOptimizer/App/AppState.swift
index 85f67ee..26185d1 100644
--- a/Sources/MacOptimizer/App/AppState.swift
+++ b/Sources/MacOptimizer/App/AppState.swift
@@ -348,11 +348,7 @@ public final class AppState: ObservableObject {
chatMessages.appendBounded(userMsg, limit: Self.maxChatMessages)
isChatThinking = true
- let baseContext = """
- Mac Modeli: \(hardwareInfo.modelName) (\(hardwareInfo.chipName)), macOS Sürümü: \(hardwareInfo.osVersion)
- RAM: %\(Int(memoryStats.usedPercentage * 100))
- CPU: %\(String(format: "%.1f", cpuStats.totalUsage)), Boş Disk: %\(Int(diskStats.freePercentage * 100))
- """
+ let baseContext = Self.cloudSnapshotContext(hardware: hardwareInfo, memory: memoryStats, cpu: cpuStats, disk: diskStats)
let context = NvidiaNIMProvider.cloudContext(
baseContext,
processNames: runningProcesses.prefix(4).map { "\($0.name) (PID: \($0.pid))" },
@@ -379,6 +375,17 @@ public final class AppState: ObservableObject {
}
}
+ /// System summary sent to the cloud model. Always English (model input, like the prompts); the model
+ /// is told separately which language to reply in. Contains no app names, file paths or secrets —
+ /// app names are only added by `NvidiaNIMProvider.cloudContext` when the user opted in.
+ nonisolated static func cloudSnapshotContext(hardware: HardwareInfo, memory: MemoryStats, cpu: CPUStats, disk: DiskStats) -> String {
+ """
+ Mac model: \(hardware.modelName) (\(hardware.chipName)), macOS version: \(hardware.osVersion)
+ RAM: \(Int(memory.usedPercentage * 100))%
+ CPU: \(String(format: "%.1f", cpu.totalUsage))%, Free disk: \(Int(disk.freePercentage * 100))%
+ """
+ }
+
public func executeAIAction(_ action: AIAction) {
switch action.type {
case .analyzeSystem:
@@ -531,10 +538,10 @@ public final class AppState: ObservableObject {
guard let self else { return }
self.isScanningJunk = false
if Task.isCancelled {
- self.junkStatusMessage = "Tarama iptal edildi."
+ self.junkStatusMessage = L10n.string("Scan cancelled.")
} else {
self.junkGroups = groups
- self.junkStatusMessage = "Tarama tamamlandı."
+ self.junkStatusMessage = L10n.string("Scan complete.")
}
}
}
@@ -562,7 +569,7 @@ public final class AppState: ObservableObject {
Task {
let confirmation = SafeOperationExecutor.confirm(plan)
let result = await JunkCleanerService.shared.executeCleaningPlan(plan, confirmation: confirmation, progressHandler: self.throttledProgress { state, name, progress in
- state.junkStatusMessage = "\(name) temizleniyor..."
+ state.junkStatusMessage = L10n.string("Cleaning %@…", name)
state.junkScanProgress = progress
})
@@ -572,12 +579,12 @@ public final class AppState: ObservableObject {
self.scanJunk()
let report = OptimizationReport(
- title: "Gereksiz Dosya Temizliği (Dry-Run Onaylı)",
+ title: L10n.string("Junk File Cleanup (Dry-Run Approved)"),
freedMemoryBytes: 0,
freedDiskBytes: result.totalFreedBytes,
details: [
- "\(result.cleanedItemCount) öğe başarıyla temizlendi.",
- result.failedItemCount > 0 ? "\(result.failedItemCount) öğe atlandı." : "Hata oluşmadı."
+ L10n.string("%lld items cleaned successfully.", result.cleanedItemCount),
+ result.failedItemCount > 0 ? L10n.string("%lld items skipped.", result.failedItemCount) : L10n.string("No errors occurred.")
],
durationSeconds: result.durationSeconds
)
@@ -613,7 +620,7 @@ public final class AppState: ObservableObject {
guard !isScanningApps else { return }
isScanningApps = true
appScanProgress = 0.0
- appStatusMessage = "Uygulamalar taranıyor..."
+ appStatusMessage = L10n.string("Scanning apps…")
let progress = throttledProgress { state, name, progress in
state.appStatusMessage = name
@@ -624,10 +631,10 @@ public final class AppState: ObservableObject {
guard let self else { return }
self.isScanningApps = false
if Task.isCancelled {
- self.appStatusMessage = "Tarama iptal edildi."
+ self.appStatusMessage = L10n.string("Scan cancelled.")
} else {
self.installedApps = apps
- self.appStatusMessage = "\(apps.count) uygulama bulundu."
+ self.appStatusMessage = L10n.string("%lld apps found.", apps.count)
}
}
}
@@ -635,7 +642,7 @@ public final class AppState: ObservableObject {
public func checkAllAppUpdates() {
guard !isCheckingUpdates else { return }
isCheckingUpdates = true
- appStatusMessage = "Güncellemeler kontrol ediliyor..."
+ appStatusMessage = L10n.string("Checking for updates…")
Task {
// Wait for an in-flight app scan so updates are checked against the complete list.
@@ -649,7 +656,7 @@ public final class AppState: ObservableObject {
self.installedApps = updated
self.isCheckingUpdates = false
let updatesFound = updated.filter { $0.updateInfo.hasUpdate }.count
- self.appStatusMessage = updatesFound > 0 ? "\(updatesFound) güncelleme mevcut!" : "Tüm uygulamalar güncel."
+ self.appStatusMessage = updatesFound > 0 ? L10n.string("%lld updates available!", updatesFound) : L10n.string("All apps are up to date.")
self.showNotification(message: self.appStatusMessage)
}
}
@@ -682,10 +689,10 @@ public final class AppState: ObservableObject {
self.refreshMetrics()
let report = OptimizationReport(
- title: "\(app.name) Kaldırıldı",
+ title: L10n.string("%@ Uninstalled", app.name),
freedMemoryBytes: 0,
freedDiskBytes: result.freedBytes,
- details: ["\(result.deletedCount) ilişkili dosya silindi."],
+ details: [L10n.string("%lld associated files deleted.", result.deletedCount)],
durationSeconds: 0.0
)
self.addReport(report)
@@ -809,7 +816,7 @@ public final class AppState: ObservableObject {
guard !isScanningDuplicates else { return }
isScanningDuplicates = true
duplicateScanProgress = 0.0
- duplicateStatusMessage = "Yinelenen dosyalar taranıyor..."
+ duplicateStatusMessage = L10n.string("Scanning for duplicate files…")
let progress = throttledProgress { state, msg, progress in
state.duplicateStatusMessage = msg
@@ -820,12 +827,12 @@ public final class AppState: ObservableObject {
guard let self else { return }
self.isScanningDuplicates = false
if Task.isCancelled {
- self.duplicateStatusMessage = "Tarama iptal edildi."
+ self.duplicateStatusMessage = L10n.string("Scan cancelled.")
return
}
self.duplicateGroups = groups
self.duplicateScanProgress = 1.0
- self.duplicateStatusMessage = groups.isEmpty ? "Yinelenen dosya bulunamadı." : "\(groups.count) yinelenen dosya grubu bulundu."
+ self.duplicateStatusMessage = groups.isEmpty ? L10n.string("No duplicate files found.") : L10n.string("%lld duplicate file groups found.", groups.count)
}
}
diff --git a/Sources/MacOptimizer/Resources/en.lproj/common.strings b/Sources/MacOptimizer/Resources/en.lproj/common.strings
index baf491a..83ce832 100644
--- a/Sources/MacOptimizer/Resources/en.lproj/common.strings
+++ b/Sources/MacOptimizer/Resources/en.lproj/common.strings
@@ -38,3 +38,14 @@
"Cleaned %@ of junk files successfully." = "Cleaned %@ of junk files successfully.";
"%@ and all its leftovers (%@) were removed successfully." = "%@ and all its leftovers (%@) were removed successfully.";
"Moved %lld duplicate files to Trash (%@ freed)." = "Moved %lld duplicate files to Trash (%@ freed).";
+"Scan cancelled." = "Scan cancelled.";
+"Scan complete." = "Scan complete.";
+"Cleaning %@…" = "Cleaning %@…";
+"Junk File Cleanup (Dry-Run Approved)" = "Junk File Cleanup (Dry-Run Approved)";
+"No errors occurred." = "No errors occurred.";
+"Scanning apps…" = "Scanning apps…";
+"Checking for updates…" = "Checking for updates…";
+"All apps are up to date." = "All apps are up to date.";
+"%@ Uninstalled" = "%@ Uninstalled";
+"Scanning for duplicate files…" = "Scanning for duplicate files…";
+"No duplicate files found." = "No duplicate files found.";
diff --git a/Sources/MacOptimizer/Resources/en.lproj/common.stringsdict b/Sources/MacOptimizer/Resources/en.lproj/common.stringsdict
index 30fe7bb..25da4a9 100644
--- a/Sources/MacOptimizer/Resources/en.lproj/common.stringsdict
+++ b/Sources/MacOptimizer/Resources/en.lproj/common.stringsdict
@@ -10,4 +10,58 @@
otherFound and listed %lld models from NVIDIA NIM.
+ %lld items cleaned successfully.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneOne item cleaned successfully.
+ other%lld items cleaned successfully.
+
+
+ %lld items skipped.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneOne item skipped.
+ other%lld items skipped.
+
+
+ %lld apps found.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneOne app found.
+ other%lld apps found.
+
+
+ %lld updates available!
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneOne update available!
+ other%lld updates available!
+
+
+ %lld associated files deleted.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneOne associated file deleted.
+ other%lld associated files deleted.
+
+
+ %lld duplicate file groups found.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneOne duplicate file group found.
+ other%lld duplicate file groups found.
+
+
diff --git a/Sources/MacOptimizer/Resources/en.lproj/services.strings b/Sources/MacOptimizer/Resources/en.lproj/services.strings
index f295f12..c1e8129 100644
--- a/Sources/MacOptimizer/Resources/en.lproj/services.strings
+++ b/Sources/MacOptimizer/Resources/en.lproj/services.strings
@@ -142,7 +142,7 @@
"High Memory Pressure Warning" = "High Memory Pressure Warning";
"RAM usage has reached %lld%%. Try closing the apps using the most memory to reduce memory pressure." = "RAM usage has reached %lld%%. Try closing the apps using the most memory to reduce memory pressure.";
"Runaway Process: %@" = "Runaway Process: %@";
-"%@ (PID: %d) is continuously consuming %@ CPU. It may be frozen or overloaded." = "%@ (PID: %d) is continuously consuming %@ CPU. It may be frozen or overloaded.";
+"%@ (PID: %d) is continuously consuming %@%% CPU. It may be frozen or overloaded." = "%@ (PID: %d) is continuously consuming %@%% CPU. It may be frozen or overloaded.";
"Terminate Process" = "Terminate Process";
"Thermal Throttling / Temperature Warning" = "Thermal Throttling / Temperature Warning";
"CPU temperature has reached a critical threshold (%@). Close apps with high CPU usage to protect the hardware." = "CPU temperature has reached a critical threshold (%@). Close apps with high CPU usage to protect the hardware.";
diff --git a/Sources/MacOptimizer/Resources/tr.lproj/common.strings b/Sources/MacOptimizer/Resources/tr.lproj/common.strings
index 176b8b9..4392617 100644
--- a/Sources/MacOptimizer/Resources/tr.lproj/common.strings
+++ b/Sources/MacOptimizer/Resources/tr.lproj/common.strings
@@ -38,3 +38,14 @@
"Cleaned %@ of junk files successfully." = "%@ gereksiz dosya başarıyla temizlendi.";
"%@ and all its leftovers (%@) were removed successfully." = "%@ ve tüm kalıntıları (%@) başarıyla kaldırıldı.";
"Moved %lld duplicate files to Trash (%@ freed)." = "%lld yinelenen dosya Çöp Sepeti’ne taşındı (%@ alan kazanıldı).";
+"Scan cancelled." = "Tarama iptal edildi.";
+"Scan complete." = "Tarama tamamlandı.";
+"Cleaning %@…" = "%@ temizleniyor…";
+"Junk File Cleanup (Dry-Run Approved)" = "Gereksiz Dosya Temizliği (Dry-Run Onaylı)";
+"No errors occurred." = "Hata oluşmadı.";
+"Scanning apps…" = "Uygulamalar taranıyor…";
+"Checking for updates…" = "Güncellemeler kontrol ediliyor…";
+"All apps are up to date." = "Tüm uygulamalar güncel.";
+"%@ Uninstalled" = "%@ Kaldırıldı";
+"Scanning for duplicate files…" = "Yinelenen dosyalar taranıyor…";
+"No duplicate files found." = "Yinelenen dosya bulunamadı.";
diff --git a/Sources/MacOptimizer/Resources/tr.lproj/common.stringsdict b/Sources/MacOptimizer/Resources/tr.lproj/common.stringsdict
index f178644..dcd6285 100644
--- a/Sources/MacOptimizer/Resources/tr.lproj/common.stringsdict
+++ b/Sources/MacOptimizer/Resources/tr.lproj/common.stringsdict
@@ -10,4 +10,58 @@
otherNVIDIA NIM üzerinden %lld model bulundu ve listelendi.
+ %lld items cleaned successfully.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneBir öğe başarıyla temizlendi.
+ other%lld öğe başarıyla temizlendi.
+
+
+ %lld items skipped.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneBir öğe atlandı.
+ other%lld öğe atlandı.
+
+
+ %lld apps found.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneBir uygulama bulundu.
+ other%lld uygulama bulundu.
+
+
+ %lld updates available!
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneBir güncelleme mevcut!
+ other%lld güncelleme mevcut!
+
+
+ %lld associated files deleted.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneBir ilişkili dosya silindi.
+ other%lld ilişkili dosya silindi.
+
+
+ %lld duplicate file groups found.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneBir yinelenen dosya grubu bulundu.
+ other%lld yinelenen dosya grubu bulundu.
+
+
diff --git a/Sources/MacOptimizer/Resources/tr.lproj/services.strings b/Sources/MacOptimizer/Resources/tr.lproj/services.strings
index 32e6736..2ae7ee7 100644
--- a/Sources/MacOptimizer/Resources/tr.lproj/services.strings
+++ b/Sources/MacOptimizer/Resources/tr.lproj/services.strings
@@ -142,7 +142,7 @@
"High Memory Pressure Warning" = "Yüksek Bellek Baskısı Uyarısı";
"RAM usage has reached %lld%%. Try closing the apps using the most memory to reduce memory pressure." = "RAM kullanımı %%%lld seviyesine ulaştı. Bellek baskısını azaltmak için en çok bellek kullanan uygulamaları kapatmayı deneyin.";
"Runaway Process: %@" = "Kaçak Süreç: %@";
-"%@ (PID: %d) is continuously consuming %@ CPU. It may be frozen or overloaded." = "%@ (PID: %d) sürekli olarak %%%@ işlemci tüketiyor. Donmuş veya aşırı yüklenmiş olabilir.";
+"%@ (PID: %d) is continuously consuming %@%% CPU. It may be frozen or overloaded." = "%@ (PID: %d) sürekli olarak %%%@ işlemci tüketiyor. Donmuş veya aşırı yüklenmiş olabilir.";
"Terminate Process" = "İşlemi Sonlandır";
"Thermal Throttling / Temperature Warning" = "Termal Kısılma / Sıcaklık Uyarısı";
"CPU temperature has reached a critical threshold (%@). Close apps with high CPU usage to protect the hardware." = "İşlemci sıcaklığı kritik eşiğe ulaştı (%@). Donanımı korumak için yüksek işlemci kullanan uygulamaları kapatın.";
diff --git a/Sources/MacOptimizer/Services/AutonomousGuardService.swift b/Sources/MacOptimizer/Services/AutonomousGuardService.swift
index a047c2d..70a9fe0 100644
--- a/Sources/MacOptimizer/Services/AutonomousGuardService.swift
+++ b/Sources/MacOptimizer/Services/AutonomousGuardService.swift
@@ -85,7 +85,7 @@ public actor AutonomousGuardService {
if count >= 3 { // Detected high CPU for 3 consecutive samples
let alert = AutonomousAlert(
title: L10n.string("Runaway Process: %@", table: .services, proc.name),
- message: L10n.string("%@ (PID: %d) is continuously consuming %@ CPU. It may be frozen or overloaded.", table: .services, proc.name, proc.pid, proc.cpuFormatted),
+ message: L10n.string("%@ (PID: %d) is continuously consuming %@%% CPU. It may be frozen or overloaded.", table: .services, proc.name, proc.pid, String(format: "%.1f", proc.cpuPercentage)),
type: .runawayProcess,
timestamp: now,
isResolved: false,
diff --git a/Tests/MacOptimizerTests/LocalizationTests.swift b/Tests/MacOptimizerTests/LocalizationTests.swift
index 7ce10dd..ee6921b 100644
--- a/Tests/MacOptimizerTests/LocalizationTests.swift
+++ b/Tests/MacOptimizerTests/LocalizationTests.swift
@@ -85,9 +85,10 @@ final class LocalizationTests: XCTestCase {
}
private func formatSpecifiers(in value: String) -> [String] {
- let pattern = #"%(?:[0-9]+\$)?[-+#0 ]*(?:[0-9]+|\*)?(?:\.(?:[0-9]+|\*))?(?:hh|h|ll|l|L|z|t|j)?[@diuoxXfFeEgGaAcCsSp]"#
+ // "%%" is a literal percent sign, not a specifier; match it first so "%@%% CPU" is not read as "% C".
+ let pattern = #"%%|%(?:[0-9]+\$)?[-+#0 ]*(?:[0-9]+|\*)?(?:\.(?:[0-9]+|\*))?(?:hh|h|ll|l|L|z|t|j)?[@diuoxXfFeEgGaAcCsSp]"#
guard let regex = try? NSRegularExpression(pattern: pattern) else { return [] }
let range = NSRange(value.startIndex..., in: value)
- return regex.matches(in: value, range: range).compactMap { Range($0.range, in: value).map { String(value[$0]) } }.sorted()
+ return regex.matches(in: value, range: range).compactMap { Range($0.range, in: value).map { String(value[$0]) } }.filter { $0 != "%%" }.sorted()
}
}
diff --git a/Tests/MacOptimizerTests/MacOptimizerTests.swift b/Tests/MacOptimizerTests/MacOptimizerTests.swift
index 6525b92..5d70445 100644
--- a/Tests/MacOptimizerTests/MacOptimizerTests.swift
+++ b/Tests/MacOptimizerTests/MacOptimizerTests.swift
@@ -509,6 +509,52 @@ final class MacOptimizerTests: XCTestCase {
XCTAssertEqual((decoded?["messages"] as? [[String: String]])?.count, 7)
}
+ func testCloudSnapshotContextIsEnglishAndPrivate() {
+ var memory = MemoryStats()
+ memory.totalBytes = 16_000_000_000
+ memory.activeBytes = 8_000_000_000
+ var cpu = CPUStats()
+ cpu.totalUsage = 12.34
+ var disk = DiskStats()
+ disk.totalBytes = 500_000_000_000
+ disk.freeBytes = 250_000_000_000
+ let context = AppState.cloudSnapshotContext(hardware: HardwareInfo(), memory: memory, cpu: cpu, disk: disk)
+ XCTAssertTrue(context.hasPrefix("Mac model: "))
+ XCTAssertTrue(context.contains("macOS version: "))
+ XCTAssertTrue(context.contains("CPU: 12.3%"))
+ XCTAssertTrue(context.contains("Free disk: 50%"))
+ for turkish in ["Mac Modeli", "Sürümü", "Boş Disk"] { XCTAssertFalse(context.contains(turkish)) }
+ XCTAssertFalse(context.contains("/Users/"))
+ XCTAssertFalse(context.contains("Running apps"))
+ XCTAssertFalse(context.contains("PID"))
+ }
+
+ func testRunawayMessageShowsPercentOnceInBothLanguages() async {
+ let prior = UserDefaults.standard.object(forKey: "AppleLanguages")
+ defer {
+ if let prior { UserDefaults.standard.set(prior, forKey: "AppleLanguages") }
+ else { UserDefaults.standard.removeObject(forKey: "AppleLanguages") }
+ }
+ var config = AutonomousConfig()
+ config.isWatchdogActive = true
+ config.cpuRunawayThresholdPercent = 80
+ let hot = ProcessInfoModel(pid: 4244, name: "HotApp", path: "/Applications/HotApp.app/Contents/MacOS/HotApp", cpuPercentage: 97.34)
+ var mem = MemoryStats()
+ mem.totalBytes = 16_000_000_000
+
+ for (language, expected) in [("en", "97.3% CPU"), ("tr", "%97.3 işlemci")] {
+ UserDefaults.standard.set([language], forKey: "AppleLanguages")
+ let guardService = AutonomousGuardService()
+ var alerts: [AutonomousAlert] = []
+ for _ in 0..<3 {
+ alerts += await guardService.evaluateCycle(memory: mem, cpu: CPUStats(), disk: DiskStats(), processes: [hot], config: config)
+ }
+ let message = alerts.first { $0.type == .runawayProcess }?.message ?? ""
+ XCTAssertEqual(message.filter { $0 == "%" }.count, 1, "\(language): \(message)")
+ XCTAssertTrue(message.contains(expected), "\(language): \(message)")
+ }
+ }
+
func testNIMPromptsAreEnglishAndNameReplyLanguage() {
let system = NvidiaNIMProvider.copilotSystemPrompt(snapshotContext: "RAM 50%", replyLanguage: "Turkish")
XCTAssertTrue(system.hasPrefix("You are the MacOptimizer Pro macOS assistant."))
@@ -665,11 +711,13 @@ final class MacOptimizerTests: XCTestCase {
)
XCTAssertFalse(alerts.isEmpty, "Watchdog should generate alerts for critical metrics")
- let titles = alerts.map { $0.title }
- XCTAssertTrue(titles.contains(where: { $0.contains("Bellek") || $0.contains("RAM") || $0.contains("Memory") }))
- XCTAssertTrue(titles.contains(where: { $0.contains("Termal") || $0.contains("Sıcaklık") || $0.contains("Thermal") }))
- XCTAssertTrue(titles.contains(where: { $0.contains("Swap") }))
- XCTAssertTrue(titles.contains(where: { $0.contains("Disk") }))
+ // Titles follow the UI language, so compare against the localized value rather than literal text.
+ let titles = Set(alerts.map { $0.title })
+ XCTAssertTrue(titles.contains(L10n.string("High Memory Pressure Warning", table: .services)))
+ XCTAssertTrue(titles.contains(L10n.string("Thermal Throttling / Temperature Warning", table: .services)))
+ XCTAssertTrue(titles.contains(L10n.string("High Swap Usage", table: .services)))
+ XCTAssertTrue(titles.contains(L10n.string("Low Disk Space Warning", table: .services)))
+ XCTAssertTrue(alerts.contains { $0.type == .lowDisk })
}
func testAutonomousGuardSkipsProtectedSystemProcesses() async {