Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 28 additions & 21 deletions Sources/MacOptimizer/App/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))" },
Expand All @@ -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:
Expand Down Expand Up @@ -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.")
}
}
}
Expand Down Expand Up @@ -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
})

Expand All @@ -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
)
Expand Down Expand Up @@ -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
Expand All @@ -624,18 +631,18 @@ 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)
}
}
}

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.
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
}

Expand Down
11 changes: 11 additions & 0 deletions Sources/MacOptimizer/Resources/en.lproj/common.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
54 changes: 54 additions & 0 deletions Sources/MacOptimizer/Resources/en.lproj/common.stringsdict
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,58 @@
<key>other</key><string>Found and listed %lld models from NVIDIA NIM.</string>
</dict>
</dict>
<key>%lld items cleaned successfully.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>One item cleaned successfully.</string>
<key>other</key><string>%lld items cleaned successfully.</string>
</dict>
</dict>
<key>%lld items skipped.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>One item skipped.</string>
<key>other</key><string>%lld items skipped.</string>
</dict>
</dict>
<key>%lld apps found.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>One app found.</string>
<key>other</key><string>%lld apps found.</string>
</dict>
</dict>
<key>%lld updates available!</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>One update available!</string>
<key>other</key><string>%lld updates available!</string>
</dict>
</dict>
<key>%lld associated files deleted.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>One associated file deleted.</string>
<key>other</key><string>%lld associated files deleted.</string>
</dict>
</dict>
<key>%lld duplicate file groups found.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>One duplicate file group found.</string>
<key>other</key><string>%lld duplicate file groups found.</string>
</dict>
</dict>
</dict></plist>
2 changes: 1 addition & 1 deletion Sources/MacOptimizer/Resources/en.lproj/services.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
11 changes: 11 additions & 0 deletions Sources/MacOptimizer/Resources/tr.lproj/common.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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ı.";
54 changes: 54 additions & 0 deletions Sources/MacOptimizer/Resources/tr.lproj/common.stringsdict
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,58 @@
<key>other</key><string>NVIDIA NIM üzerinden %lld model bulundu ve listelendi.</string>
</dict>
</dict>
<key>%lld items cleaned successfully.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>Bir öğe başarıyla temizlendi.</string>
<key>other</key><string>%lld öğe başarıyla temizlendi.</string>
</dict>
</dict>
<key>%lld items skipped.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>Bir öğe atlandı.</string>
<key>other</key><string>%lld öğe atlandı.</string>
</dict>
</dict>
<key>%lld apps found.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>Bir uygulama bulundu.</string>
<key>other</key><string>%lld uygulama bulundu.</string>
</dict>
</dict>
<key>%lld updates available!</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>Bir güncelleme mevcut!</string>
<key>other</key><string>%lld güncelleme mevcut!</string>
</dict>
</dict>
<key>%lld associated files deleted.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>Bir ilişkili dosya silindi.</string>
<key>other</key><string>%lld ilişkili dosya silindi.</string>
</dict>
</dict>
<key>%lld duplicate file groups found.</key><dict>
<key>NSStringLocalizedFormatKey</key><string>%#@count@</string>
<key>count</key><dict>
<key>NSStringFormatSpecTypeKey</key><string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key><string>lld</string>
<key>one</key><string>Bir yinelenen dosya grubu bulundu.</string>
<key>other</key><string>%lld yinelenen dosya grubu bulundu.</string>
</dict>
</dict>
</dict></plist>
2 changes: 1 addition & 1 deletion Sources/MacOptimizer/Resources/tr.lproj/services.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
2 changes: 1 addition & 1 deletion Sources/MacOptimizer/Services/AutonomousGuardService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions Tests/MacOptimizerTests/LocalizationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Loading
Loading