diff --git a/Sources/MacOptimizer/Models/InstalledApp.swift b/Sources/MacOptimizer/Models/InstalledApp.swift index 5cbc173..3da40b5 100644 --- a/Sources/MacOptimizer/Models/InstalledApp.swift +++ b/Sources/MacOptimizer/Models/InstalledApp.swift @@ -13,7 +13,7 @@ public enum AppArchitecture: String, Sendable, Equatable { case .universal: return "Universal" case .appleSilicon: return "Apple Silicon" case .intel: return "Intel" - case .unknown: return "Diğer" + case .unknown: return L10n.string("Other", table: .services) } } @@ -43,6 +43,14 @@ public struct AppUpdateInfo: Sendable, Equatable { case homebrew = "Homebrew Cask" case appStore = "Mac App Store" case directCheck = "Doğrudan Kontrol" + + /// Localized display label. Raw values are kept stable and must not be shown in the UI. + public var localizedTitle: String { + switch self { + case .sparkle, .homebrew, .appStore: return rawValue + case .directCheck: return L10n.string("Direct Check", table: .services) + } + } } public init( diff --git a/Sources/MacOptimizer/Models/JunkItem.swift b/Sources/MacOptimizer/Models/JunkItem.swift index 01d7028..7cbf393 100644 --- a/Sources/MacOptimizer/Models/JunkItem.swift +++ b/Sources/MacOptimizer/Models/JunkItem.swift @@ -14,25 +14,25 @@ public enum JunkCategoryType: String, CaseIterable, Identifiable, Sendable { public var title: String { switch self { - case .systemCache: return "Sistem ve Uygulama Önbelleği" - case .systemLogs: return "Sistem ve Hata Günlükleri" - case .developerCache: return "Geliştirici & Build Kalıntıları" - case .browserCache: return "Tarayıcı Önbellekleri" - case .trashBin: return "Çöp Sepeti" - case .largeFiles: return "Büyük ve Eski Dosyalar" - case .appLeftovers: return "Kaldırılmış Uygulama Kalıntıları" + case .systemCache: return L10n.string("System and App Cache", table: .services) + case .systemLogs: return L10n.string("System and Error Logs", table: .services) + case .developerCache: return L10n.string("Developer & Build Leftovers", table: .services) + case .browserCache: return L10n.string("Browser Caches", table: .services) + case .trashBin: return L10n.string("Trash", table: .services) + case .largeFiles: return L10n.string("Large and Old Files", table: .services) + case .appLeftovers: return L10n.string("Removed App Leftovers", table: .services) } } public var description: String { switch self { - case .systemCache: return "Uygulamalar ve macOS tarafından oluşturulan geçici önbellek dosyaları." - case .systemLogs: return "Eski sistem çökme raporları, hata logları ve tanı dosyaları." - case .developerCache: return "Xcode DerivedData, Archives, DeviceSupport, Node/NPM, CocoaPods ve Cargo önbellekleri." - case .browserCache: return "Safari, Chrome, Arc, Firefox ve Edge tarayıcılarının web önbellekleri." - case .trashBin: return "Kullanıcı çöp kutusunda bekleyen silinmiş dosyalar." - case .largeFiles: return "İndirilenler ve Belgeler'de yer kaplayan büyük boyutlu dosyalar (>100 MB)." - case .appLeftovers: return "Silinmiş uygulamalardan geriye kalan artık klasör ve ayar dosyaları." + case .systemCache: return L10n.string("Temporary cache files created by apps and macOS.", table: .services) + case .systemLogs: return L10n.string("Old system crash reports, error logs, and diagnostic files.", table: .services) + case .developerCache: return L10n.string("Xcode DerivedData, Archives, DeviceSupport, Node/NPM, CocoaPods, and Cargo caches.", table: .services) + case .browserCache: return L10n.string("Web caches of the Safari, Chrome, Arc, Firefox, and Edge browsers.", table: .services) + case .trashBin: return L10n.string("Deleted files waiting in the user's Trash.", table: .services) + case .largeFiles: return L10n.string("Large files (>100 MB) taking up space in Downloads and Documents.", table: .services) + case .appLeftovers: return L10n.string("Leftover folders and settings files from deleted apps.", table: .services) } } diff --git a/Sources/MacOptimizer/Resources/en.lproj/services.strings b/Sources/MacOptimizer/Resources/en.lproj/services.strings index eb10734..f295f12 100644 --- a/Sources/MacOptimizer/Resources/en.lproj/services.strings +++ b/Sources/MacOptimizer/Resources/en.lproj/services.strings @@ -74,3 +74,109 @@ "Critical" = "Critical"; "Warning" = "Warning"; "Secure" = "Secure"; +"Trash must be emptied separately with explicit confirmation: %@" = "Trash must be emptied separately with explicit confirmation: %@"; +"Protected file was not added to the plan: %@" = "Protected file was not added to the plan: %@"; +"Cleanup Complete" = "Cleanup Complete"; +"Scanning junk files..." = "Scanning junk files..."; +"Scan Complete" = "Scan Complete"; +"System Crash & Diagnostic Reports" = "System Crash & Diagnostic Reports"; +"Xcode Archives" = "Xcode Archives"; +"Simulator Caches" = "Simulator Caches"; +"NPM Cache" = "NPM Cache"; +"Yarn Cache" = "Yarn Cache"; +"Rust Cargo Cache" = "Rust Cargo Cache"; +"CocoaPods Cache" = "CocoaPods Cache"; +"Gradle Cache" = "Gradle Cache"; +"Python pip Cache" = "Python pip Cache"; +"Homebrew Download Cache" = "Homebrew Download Cache"; +"Python UV Cache" = "Python UV Cache"; +"Python Poetry Cache" = "Python Poetry Cache"; +"Build and index caches" = "Build and index caches"; +"Old app build archives" = "Old app build archives"; +"Old iOS device symbols" = "Old iOS device symbols"; +"Old watchOS device symbols" = "Old watchOS device symbols"; +"iOS Simulator temporary files" = "iOS Simulator temporary files"; +"Node Package Manager cache" = "Node Package Manager cache"; +"Yarn package cache" = "Yarn package cache"; +"pnpm global package store" = "pnpm global package store"; +"Cargo crate cache files" = "Cargo crate cache files"; +"iOS Pods download caches" = "iOS Pods download caches"; +"Android and Java build caches" = "Android and Java build caches"; +"Python package download caches" = "Python package download caches"; +"Downloaded formulae and bottle packages" = "Downloaded formulae and bottle packages"; +"UV package manager cache" = "UV package manager cache"; +"Poetry virtual environments and package store" = "Poetry virtual environments and package store"; +"Safari Web Cache" = "Safari Web Cache"; +"Safari Container Cache" = "Safari Container Cache"; +"Google Chrome Cache" = "Google Chrome Cache"; +"Arc Browser Cache" = "Arc Browser Cache"; +"Brave Browser Cache" = "Brave Browser Cache"; +"Microsoft Edge Cache" = "Microsoft Edge Cache"; +"Mozilla Firefox Cache" = "Mozilla Firefox Cache"; +"In Trash" = "In Trash"; +"%@ / %@ File" = "%@ / %@ File"; +"Deleted app leftover (Application Support)" = "Deleted app leftover (Application Support)"; +"System and App Cache" = "System and App Cache"; +"System and Error Logs" = "System and Error Logs"; +"Developer & Build Leftovers" = "Developer & Build Leftovers"; +"Browser Caches" = "Browser Caches"; +"Trash" = "Trash"; +"Large and Old Files" = "Large and Old Files"; +"Removed App Leftovers" = "Removed App Leftovers"; +"Temporary cache files created by apps and macOS." = "Temporary cache files created by apps and macOS."; +"Old system crash reports, error logs, and diagnostic files." = "Old system crash reports, error logs, and diagnostic files."; +"Xcode DerivedData, Archives, DeviceSupport, Node/NPM, CocoaPods, and Cargo caches." = "Xcode DerivedData, Archives, DeviceSupport, Node/NPM, CocoaPods, and Cargo caches."; +"Web caches of the Safari, Chrome, Arc, Firefox, and Edge browsers." = "Web caches of the Safari, Chrome, Arc, Firefox, and Edge browsers."; +"Deleted files waiting in the user's Trash." = "Deleted files waiting in the user's Trash."; +"Large files (>100 MB) taking up space in Downloads and Documents." = "Large files (>100 MB) taking up space in Downloads and Documents."; +"Leftover folders and settings files from deleted apps." = "Leftover folders and settings files from deleted apps."; +"Other" = "Other"; +"Direct Check" = "Direct Check"; +"Downloads" = "Downloads"; +"Documents" = "Documents"; +"Pictures" = "Pictures"; +"Desktop" = "Desktop"; +"Scanning files and indexing sizes..." = "Scanning files and indexing sizes..."; +"No duplicate files found." = "No duplicate files found."; +"Computing SHA-256: %@" = "Computing SHA-256: %@"; +"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."; +"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."; +"High Swap Usage" = "High Swap Usage"; +"%@ of virtual memory swap is in use on the system disk. Close heavy apps to reduce memory pressure." = "%@ of virtual memory swap is in use on the system disk. Close heavy apps to reduce memory pressure."; +"Low Disk Space Warning" = "Low Disk Space Warning"; +"Only %@ of free space is left on the main disk. Clean unnecessary system caches to free up space." = "Only %@ of free space is left on the main disk. Clean unnecessary system caches to free up space."; +"Scan Junk Files" = "Scan Junk Files"; +"App Bundle (Main File)" = "App Bundle (Main File)"; +"Caches" = "Caches"; +"Preferences" = "Preferences"; +"Saved Application State" = "Saved Application State"; +"App Sandbox Container" = "App Sandbox Container"; +"WebKit Data" = "WebKit Data"; +"HTTP Storage" = "HTTP Storage"; +"App Logs" = "App Logs"; +"Launch Service (LaunchAgent)" = "Launch Service (LaunchAgent)"; +"Group Container" = "Group Container"; +"%lld/%lld Apps Checked" = "%lld/%lld Apps Checked"; +"Update Check Complete" = "Update Check Complete"; +"Invalid Homebrew package name." = "Invalid Homebrew package name."; +"Homebrew not found." = "Homebrew not found."; +"%@ was updated via Homebrew." = "%@ was updated via Homebrew."; +"%@ could not be updated: %@" = "%@ could not be updated: %@"; +"Scanning..." = "Scanning..."; +"Complete" = "Complete"; +"AC Adapter" = "AC Adapter"; +"Battery" = "Battery"; +"%lld h %lld min remaining" = "%lld h %lld min remaining"; +"Until full: %lld h %lld min" = "Until full: %lld h %lld min"; +"Charging" = "Charging"; +"Calculating..." = "Calculating..."; +"Service Recommended" = "Service Recommended"; +"Poor Battery Health" = "Poor Battery Health"; +"%lld d, %lld h" = "%lld d, %lld h"; +"%lld h, %lld min" = "%lld h, %lld min"; +"%lld min" = "%lld min"; diff --git a/Sources/MacOptimizer/Resources/tr.lproj/services.strings b/Sources/MacOptimizer/Resources/tr.lproj/services.strings index 3e58a45..32e6736 100644 --- a/Sources/MacOptimizer/Resources/tr.lproj/services.strings +++ b/Sources/MacOptimizer/Resources/tr.lproj/services.strings @@ -74,3 +74,109 @@ "Critical" = "Kritik"; "Warning" = "Uyarı"; "Secure" = "Güvenli"; +"Trash must be emptied separately with explicit confirmation: %@" = "Çöp Sepeti ayrı ve açık onayla boşaltılmalıdır: %@"; +"Protected file was not added to the plan: %@" = "Korumalı dosya plana eklenmedi: %@"; +"Cleanup Complete" = "Temizlik Tamamlandı"; +"Scanning junk files..." = "Gereksiz dosyalar taranıyor..."; +"Scan Complete" = "Tarama Tamamlandı"; +"System Crash & Diagnostic Reports" = "Sistem Çökme & Tanı Raporları"; +"Xcode Archives" = "Xcode Arşivleri"; +"Simulator Caches" = "Simülatör Önbellekleri"; +"NPM Cache" = "NPM Önbelleği"; +"Yarn Cache" = "Yarn Önbelleği"; +"Rust Cargo Cache" = "Rust Cargo Önbelleği"; +"CocoaPods Cache" = "CocoaPods Önbelleği"; +"Gradle Cache" = "Gradle Önbelleği"; +"Python pip Cache" = "Python pip Önbelleği"; +"Homebrew Download Cache" = "Homebrew İndirme Önbelleği"; +"Python UV Cache" = "Python UV Önbelleği"; +"Python Poetry Cache" = "Python Poetry Önbelleği"; +"Build and index caches" = "Derleme ve indeks önbellekleri"; +"Old app build archives" = "Eski uygulama derleme arşivleri"; +"Old iOS device symbols" = "Eski iOS cihaz sembolleri"; +"Old watchOS device symbols" = "Eski watchOS cihaz sembolleri"; +"iOS Simulator temporary files" = "iOS Simülatör geçici dosyaları"; +"Node Package Manager cache" = "Node Package Manager önbelleği"; +"Yarn package cache" = "Yarn paket önbelleği"; +"pnpm global package store" = "pnpm global paket havuzu"; +"Cargo crate cache files" = "Cargo crates önbellek dosyaları"; +"iOS Pods download caches" = "iOS Pods indirme önbellekleri"; +"Android and Java build caches" = "Android ve Java derleme önbellekleri"; +"Python package download caches" = "Python paket indirme önbellekleri"; +"Downloaded formulae and bottle packages" = "İndirilen formül ve bottle paketleri"; +"UV package manager cache" = "UV paket yöneticisi önbelleği"; +"Poetry virtual environments and package store" = "Poetry sanal ortam ve paket havuzu"; +"Safari Web Cache" = "Safari Web Önbelleği"; +"Safari Container Cache" = "Safari Container Önbelleği"; +"Google Chrome Cache" = "Google Chrome Önbelleği"; +"Arc Browser Cache" = "Arc Tarayıcı Önbelleği"; +"Brave Browser Cache" = "Brave Tarayıcı Önbelleği"; +"Microsoft Edge Cache" = "Microsoft Edge Önbelleği"; +"Mozilla Firefox Cache" = "Mozilla Firefox Önbelleği"; +"In Trash" = "Çöp Sepetinde"; +"%@ / %@ File" = "%@ / %@ Dosyası"; +"Deleted app leftover (Application Support)" = "Silinmiş uygulama kalıntısı (Application Support)"; +"System and App Cache" = "Sistem ve Uygulama Önbelleği"; +"System and Error Logs" = "Sistem ve Hata Günlükleri"; +"Developer & Build Leftovers" = "Geliştirici & Build Kalıntıları"; +"Browser Caches" = "Tarayıcı Önbellekleri"; +"Trash" = "Çöp Sepeti"; +"Large and Old Files" = "Büyük ve Eski Dosyalar"; +"Removed App Leftovers" = "Kaldırılmış Uygulama Kalıntıları"; +"Temporary cache files created by apps and macOS." = "Uygulamalar ve macOS tarafından oluşturulan geçici önbellek dosyaları."; +"Old system crash reports, error logs, and diagnostic files." = "Eski sistem çökme raporları, hata logları ve tanı dosyaları."; +"Xcode DerivedData, Archives, DeviceSupport, Node/NPM, CocoaPods, and Cargo caches." = "Xcode DerivedData, Archives, DeviceSupport, Node/NPM, CocoaPods ve Cargo önbellekleri."; +"Web caches of the Safari, Chrome, Arc, Firefox, and Edge browsers." = "Safari, Chrome, Arc, Firefox ve Edge tarayıcılarının web önbellekleri."; +"Deleted files waiting in the user's Trash." = "Kullanıcı çöp kutusunda bekleyen silinmiş dosyalar."; +"Large files (>100 MB) taking up space in Downloads and Documents." = "İndirilenler ve Belgeler'de yer kaplayan büyük boyutlu dosyalar (>100 MB)."; +"Leftover folders and settings files from deleted apps." = "Silinmiş uygulamalardan geriye kalan artık klasör ve ayar dosyaları."; +"Other" = "Diğer"; +"Direct Check" = "Doğrudan Kontrol"; +"Downloads" = "İndirilenler"; +"Documents" = "Belgeler"; +"Pictures" = "Resimler"; +"Desktop" = "Masaüstü"; +"Scanning files and indexing sizes..." = "Dosyalar taranıyor ve boyutlar indeksleniyor..."; +"No duplicate files found." = "Yinelenen dosya bulunamadı."; +"Computing SHA-256: %@" = "SHA-256 hesaplanıyor: %@"; +"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."; +"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."; +"High Swap Usage" = "Yüksek Swap (Takas Alanı) Kullanımı"; +"%@ of virtual memory swap is in use on the system disk. Close heavy apps to reduce memory pressure." = "Sistem diski üzerinde %@ sanal bellek takası kullanılıyor. Bellek baskısını azaltmak için yoğun uygulamaları kapatın."; +"Low Disk Space Warning" = "Düşük Disk Alanı Uyarısı"; +"Only %@ of free space is left on the main disk. Clean unnecessary system caches to free up space." = "Ana disk üzerinde yalnızca %@ boş alan kaldı. Alan kazanmak için gereksiz sistem önbelleklerini temizleyin."; +"Scan Junk Files" = "Gereksiz Dosyaları Tara"; +"App Bundle (Main File)" = "Uygulama Paketi (Ana Dosya)"; +"Caches" = "Önbellek (Caches)"; +"Preferences" = "Tercihler (Preferences)"; +"Saved Application State" = "Kayıtlı Uygulama Durumu"; +"App Sandbox Container" = "Uygulama Sandbox Kapsayıcısı"; +"WebKit Data" = "WebKit Verileri"; +"HTTP Storage" = "HTTP Depolama"; +"App Logs" = "Uygulama Günlükleri"; +"Launch Service (LaunchAgent)" = "Başlangıç Servisi (LaunchAgent)"; +"Group Container" = "Grup Kapsayıcısı (Group Container)"; +"%lld/%lld Apps Checked" = "%lld/%lld Uygulama Kontrol Edildi"; +"Update Check Complete" = "Güncelleme Kontrolü Tamamlandı"; +"Invalid Homebrew package name." = "Geçersiz Homebrew paket adı."; +"Homebrew not found." = "Homebrew bulunamadı."; +"%@ was updated via Homebrew." = "%@ Homebrew üzerinden güncellendi."; +"%@ could not be updated: %@" = "%@ güncellenemedi: %@"; +"Scanning..." = "Taranıyor..."; +"Complete" = "Tamamlandı"; +"AC Adapter" = "AC Adaptör"; +"Battery" = "Pil"; +"%lld h %lld min remaining" = "%lld sa %lld dk kaldı"; +"Until full: %lld h %lld min" = "Dolmasına: %lld sa %lld dk"; +"Charging" = "Şarj Oluyor"; +"Calculating..." = "Hesaplanıyor..."; +"Service Recommended" = "Servis Öneriliyor"; +"Poor Battery Health" = "Pil Sağlığı Zayıf"; +"%lld d, %lld h" = "%lld gün, %lld saat"; +"%lld h, %lld min" = "%lld saat, %lld dk"; +"%lld min" = "%lld dakika"; diff --git a/Sources/MacOptimizer/Services/AppScannerService.swift b/Sources/MacOptimizer/Services/AppScannerService.swift index b86fa8e..c5d7295 100644 --- a/Sources/MacOptimizer/Services/AppScannerService.swift +++ b/Sources/MacOptimizer/Services/AppScannerService.swift @@ -65,14 +65,14 @@ public final class AppScannerService: Sendable { } processedCount += 1 let progress = Double(processedCount) / max(1.0, total) - progressHandler?(app?.name ?? "Taranıyor...", progress) + progressHandler?(app?.name ?? L10n.string("Scanning...", table: .services), progress) } } index = endIndex } - progressHandler?("Tamamlandı", 1.0) + progressHandler?(L10n.string("Complete", table: .services), 1.0) // Sort user apps first, then alphabetically return installedApps.sorted { diff --git a/Sources/MacOptimizer/Services/AppUninstallerService.swift b/Sources/MacOptimizer/Services/AppUninstallerService.swift index 5091832..2f3462e 100644 --- a/Sources/MacOptimizer/Services/AppUninstallerService.swift +++ b/Sources/MacOptimizer/Services/AppUninstallerService.swift @@ -45,7 +45,7 @@ public actor AppUninstallerService { id: app.path, path: app.path, name: "\(app.name).app", - locationName: "Uygulama Paketi (Ana Dosya)", + locationName: L10n.string("App Bundle (Main File)", table: .services), sizeBytes: mainAppSize, isSelected: true, isMainApp: true @@ -61,14 +61,14 @@ public actor AppUninstallerService { if validBundleId { targetLocations.append(("Library/Application Support/\(bundleId)", "Application Support")) - targetLocations.append(("Library/Caches/\(bundleId)", "Önbellek (Caches)")) - targetLocations.append(("Library/Preferences/\(bundleId).plist", "Tercihler (Preferences)")) - targetLocations.append(("Library/Saved Application State/\(bundleId).savedState", "Kayıtlı Uygulama Durumu")) - targetLocations.append(("Library/Containers/\(bundleId)", "Uygulama Sandbox Kapsayıcısı")) - targetLocations.append(("Library/WebKit/\(bundleId)", "WebKit Verileri")) - targetLocations.append(("Library/HTTPStorages/\(bundleId)", "HTTP Depolama")) - targetLocations.append(("Library/Logs/\(bundleId)", "Uygulama Günlükleri")) - targetLocations.append(("Library/LaunchAgents/\(bundleId).plist", "Başlangıç Servisi (LaunchAgent)")) + targetLocations.append(("Library/Caches/\(bundleId)", L10n.string("Caches", table: .services))) + targetLocations.append(("Library/Preferences/\(bundleId).plist", L10n.string("Preferences", table: .services))) + targetLocations.append(("Library/Saved Application State/\(bundleId).savedState", L10n.string("Saved Application State", table: .services))) + targetLocations.append(("Library/Containers/\(bundleId)", L10n.string("App Sandbox Container", table: .services))) + targetLocations.append(("Library/WebKit/\(bundleId)", L10n.string("WebKit Data", table: .services))) + targetLocations.append(("Library/HTTPStorages/\(bundleId)", L10n.string("HTTP Storage", table: .services))) + targetLocations.append(("Library/Logs/\(bundleId)", L10n.string("App Logs", table: .services))) + targetLocations.append(("Library/LaunchAgents/\(bundleId).plist", L10n.string("Launch Service (LaunchAgent)", table: .services))) } for loc in targetLocations { @@ -103,7 +103,7 @@ public actor AppUninstallerService { id: dir.path, path: dir.path, name: dir.lastPathComponent, - locationName: "Grup Kapsayıcısı (Group Container)", + locationName: L10n.string("Group Container", table: .services), sizeBytes: size, isSelected: false, isMainApp: false diff --git a/Sources/MacOptimizer/Services/AppUpdateCheckerService.swift b/Sources/MacOptimizer/Services/AppUpdateCheckerService.swift index b9fa576..1fbe424 100644 --- a/Sources/MacOptimizer/Services/AppUpdateCheckerService.swift +++ b/Sources/MacOptimizer/Services/AppUpdateCheckerService.swift @@ -61,10 +61,10 @@ public actor AppUpdateCheckerService { index = endIndex let progress = Double(index) / Swift.max(1.0, total) - progressHandler?("\(index)/\(apps.count) Uygulama Kontrol Edildi", progress) + progressHandler?(L10n.string("%lld/%lld Apps Checked", table: .services, index, apps.count), progress) } - progressHandler?("Güncelleme Kontrolü Tamamlandı", 1.0) + progressHandler?(L10n.string("Update Check Complete", table: .services), 1.0) return updatedApps } @@ -133,10 +133,10 @@ public actor AppUpdateCheckerService { /// timeout sized for real downloads. Remote appcast data is never interpreted as a command. public func upgradeHomebrewCask(token: String) async -> (success: Bool, message: String) { guard Self.isValidCaskToken(token) else { - return (false, "Geçersiz Homebrew paket adı.") + return (false, L10n.string("Invalid Homebrew package name.", table: .services)) } guard let brew = ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"].first(where: { FileManager.default.isExecutableFile(atPath: $0) }) else { - return (false, "Homebrew bulunamadı.") + return (false, L10n.string("Homebrew not found.", table: .services)) } let result = await SystemCommandRunner.run( executable: brew, @@ -144,7 +144,7 @@ public actor AppUpdateCheckerService { timeoutSeconds: 900, outputLimit: 256 * 1024 ) - return (result.isSuccess, result.isSuccess ? "\(token) Homebrew üzerinden güncellendi." : "\(token) güncellenemedi: \(result.standardError.suffix(200))") + return (result.isSuccess, result.isSuccess ? L10n.string("%@ was updated via Homebrew.", table: .services, token) : L10n.string("%@ could not be updated: %@", table: .services, token, String(result.standardError.suffix(200)))) } /// Checks Homebrew Cask outdated list diff --git a/Sources/MacOptimizer/Services/AutonomousGuardService.swift b/Sources/MacOptimizer/Services/AutonomousGuardService.swift index e974dd8..a047c2d 100644 --- a/Sources/MacOptimizer/Services/AutonomousGuardService.swift +++ b/Sources/MacOptimizer/Services/AutonomousGuardService.swift @@ -59,8 +59,8 @@ public actor AutonomousGuardService { let ramPercent = memory.usedPercentage * 100.0 if ramPercent >= config.ramThresholdPercent || memory.pressureLevel == .critical { generatedAlerts.append(AutonomousAlert( - title: "Yüksek Bellek Baskısı Uyarısı", - message: "RAM kullanımı %\(Int(ramPercent)) seviyesine ulaştı. Bellek baskısını azaltmak için en çok bellek kullanan uygulamaları kapatmayı deneyin.", + title: L10n.string("High Memory Pressure Warning", table: .services), + message: L10n.string("RAM usage has reached %lld%%. Try closing the apps using the most memory to reduce memory pressure.", table: .services, Int(ramPercent)), type: .memorySpike, timestamp: now, isResolved: false, @@ -84,13 +84,13 @@ public actor AutonomousGuardService { if count >= 3 { // Detected high CPU for 3 consecutive samples let alert = AutonomousAlert( - title: "Kaçak Süreç: \(proc.name)", - message: "\(proc.name) (PID: \(proc.pid)) sürekli olarak %\(proc.cpuFormatted) işlemci tüketiyor. Donmuş veya aşırı yüklenmiş olabilir.", + 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), type: .runawayProcess, timestamp: now, isResolved: false, autoHealed: false, - action: AIAction(title: "İşlemi Sonlandır", type: .killProcess, targetPID: proc.pid) + action: AIAction(title: L10n.string("Terminate Process", table: .services), type: .killProcess, targetPID: proc.pid) ) generatedAlerts.append(alert) consecutiveHighCPUCounts[proc.pid] = 0 // Reset count after alert @@ -107,8 +107,8 @@ public actor AutonomousGuardService { if canAlertThermal { lastThermalAlertTime = now let alert = AutonomousAlert( - title: "Termal Kısılma / Sıcaklık Uyarısı", - message: "İşlemci sıcaklığı kritik eşiğe ulaştı (\(cpu.thermalState.rawValue)). Donanımı korumak için yüksek işlemci kullanan uygulamaları kapatın.", + title: L10n.string("Thermal Throttling / Temperature Warning", table: .services), + message: L10n.string("CPU temperature has reached a critical threshold (%@). Close apps with high CPU usage to protect the hardware.", table: .services, cpu.thermalState.rawValue), type: .runawayProcess, timestamp: now, isResolved: false, @@ -125,8 +125,8 @@ public actor AutonomousGuardService { if canAlertSwap { lastSwapAlertTime = now let alert = AutonomousAlert( - title: "Yüksek Swap (Takas Alanı) Kullanımı", - message: "Sistem diski üzerinde \(ByteFormatter.formatMemory(memory.swapUsedBytes)) sanal bellek takası kullanılıyor. Bellek baskısını azaltmak için yoğun uygulamaları kapatın.", + title: L10n.string("High Swap Usage", table: .services), + message: L10n.string("%@ of virtual memory swap is in use on the system disk. Close heavy apps to reduce memory pressure.", table: .services, ByteFormatter.formatMemory(memory.swapUsedBytes)), type: .memorySpike, timestamp: now, isResolved: false, @@ -143,13 +143,13 @@ public actor AutonomousGuardService { if canAlertDisk { lastDiskAlertTime = now let alert = AutonomousAlert( - title: "Düşük Disk Alanı Uyarısı", - message: "Ana disk üzerinde yalnızca \(ByteFormatter.format(disk.freeBytes)) boş alan kaldı. Alan kazanmak için gereksiz sistem önbelleklerini temizleyin.", + title: L10n.string("Low Disk Space Warning", table: .services), + message: L10n.string("Only %@ of free space is left on the main disk. Clean unnecessary system caches to free up space.", table: .services, ByteFormatter.format(disk.freeBytes)), type: .lowDisk, timestamp: now, isResolved: false, autoHealed: false, - action: AIAction(title: "Gereksiz Dosyaları Tara", type: .cleanJunk) + action: AIAction(title: L10n.string("Scan Junk Files", table: .services), type: .cleanJunk) ) generatedAlerts.append(alert) } diff --git a/Sources/MacOptimizer/Services/DuplicateFileFinderService.swift b/Sources/MacOptimizer/Services/DuplicateFileFinderService.swift index 8d73f8d..2d895d0 100644 --- a/Sources/MacOptimizer/Services/DuplicateFileFinderService.swift +++ b/Sources/MacOptimizer/Services/DuplicateFileFinderService.swift @@ -54,6 +54,16 @@ public final class DuplicateFileFinderService: Sendable { case desktop = "Masaüstü" public var id: String { rawValue } + + /// Localized display label. Raw values are kept stable (used as `id`) and must not be shown in the UI. + public var localizedTitle: String { + switch self { + case .downloads: return L10n.string("Downloads", table: .services) + case .documents: return L10n.string("Documents", table: .services) + case .pictures: return L10n.string("Pictures", table: .services) + case .desktop: return L10n.string("Desktop", table: .services) + } + } public func resolveURL() -> URL { let home = FileManager.default.homeDirectoryForCurrentUser @@ -87,7 +97,7 @@ public final class DuplicateFileFinderService: Sendable { progressHandler: (@Sendable (String, Double) -> Void)? ) -> [DuplicateFileGroup] { let fileManager = FileManager.default - progressHandler?("Dosyalar taranıyor ve boyutlar indeksleniyor...", 0.1) + progressHandler?(L10n.string("Scanning files and indexing sizes...", table: .services), 0.1) var sizeMap: [Int64: [URL]] = [:] @@ -125,7 +135,7 @@ public final class DuplicateFileFinderService: Sendable { sizeMap.removeAll() let totalCandidates = candidateGroups.values.reduce(0) { $0 + $1.count } guard totalCandidates > 0, !flag.isCancelled else { - progressHandler?("Yinelenen dosya bulunamadı.", 1.0) + progressHandler?(L10n.string("No duplicate files found.", table: .services), 1.0) return [] } @@ -146,7 +156,7 @@ public final class DuplicateFileFinderService: Sendable { for fileURL in group where !flag.isCancelled { processedCount += 1 let progress = 0.1 + (0.8 * Double(processedCount) / Double(totalCandidates)) - progressHandler?("SHA-256 hesaplanıyor: \(fileURL.lastPathComponent)", progress) + progressHandler?(L10n.string("Computing SHA-256: %@", table: .services, fileURL.lastPathComponent), progress) // A file no larger than the prefix is already fully hashed. let fullHash = size <= Int64(partialHashBytes) @@ -197,7 +207,7 @@ public final class DuplicateFileFinderService: Sendable { )) } - progressHandler?("Tarama Tamamlandı", 1.0) + progressHandler?(L10n.string("Scan Complete", table: .services), 1.0) return resultGroups.sorted { $0.totalWastedBytes > $1.totalWastedBytes } } diff --git a/Sources/MacOptimizer/Services/JunkCleanerService.swift b/Sources/MacOptimizer/Services/JunkCleanerService.swift index 55d9894..58c9e8b 100644 --- a/Sources/MacOptimizer/Services/JunkCleanerService.swift +++ b/Sources/MacOptimizer/Services/JunkCleanerService.swift @@ -35,7 +35,7 @@ public final class JunkCleanerService: Sendable { for group in groups { for item in group.items { if item.category == .trashBin { - warnings.append("Çöp Sepeti ayrı ve açık onayla boşaltılmalıdır: \(item.name)") + warnings.append(L10n.string("Trash must be emptied separately with explicit confirmation: %@", table: .services, item.name)) continue } let canonicalPath = URL(fileURLWithPath: item.path).resolvingSymlinksInPath().standardizedFileURL.path @@ -43,7 +43,7 @@ public final class JunkCleanerService: Sendable { // Never add forbidden paths into the plan if risk == .forbidden { - warnings.append("Korumalı dosya plana eklenmedi: \(item.name)") + warnings.append(L10n.string("Protected file was not added to the plan: %@", table: .services, item.name)) continue } @@ -101,7 +101,7 @@ public final class JunkCleanerService: Sendable { } let duration = CFAbsoluteTimeGetCurrent() - startTime - progressHandler?("Temizlik Tamamlandı", 1.0) + progressHandler?(L10n.string("Cleanup Complete", table: .services), 1.0) return CleaningExecutionResult( planId: plan.id, @@ -125,7 +125,7 @@ public final class JunkCleanerService: Sendable { .appLeftovers ] - progressHandler?("Gereksiz dosyalar taranıyor...", 0.1) + progressHandler?(L10n.string("Scanning junk files...", table: .services), 0.1) var categoryResults: [JunkCategoryType: [JunkFileItem]] = [:] @@ -155,7 +155,7 @@ public final class JunkCleanerService: Sendable { groups.append(JunkCategoryGroup(type: cat, items: items)) } - progressHandler?("Tarama Tamamlandı", 1.0) + progressHandler?(L10n.string("Scan Complete", table: .services), 1.0) return groups } @@ -207,7 +207,7 @@ public final class JunkCleanerService: Sendable { if size > 0 { items.append(JunkFileItem( path: crashReporterURL.path, - name: "Sistem Çökme & Tanı Raporları", + name: L10n.string("System Crash & Diagnostic Reports", table: .services), sizeBytes: size, category: .systemLogs, isSelected: true, @@ -224,21 +224,21 @@ public final class JunkCleanerService: Sendable { var items: [JunkFileItem] = [] let devTargets: [(path: String, name: String, desc: String)] = [ - ("Library/Developer/Xcode/DerivedData", "Xcode DerivedData", "Derleme ve indeks önbellekleri"), - ("Library/Developer/Xcode/Archives", "Xcode Arşivleri", "Eski uygulama derleme arşivleri"), - ("Library/Developer/Xcode/iOS DeviceSupport", "iOS Device Support", "Eski iOS cihaz sembolleri"), - ("Library/Developer/Xcode/watchOS DeviceSupport", "watchOS Device Support", "Eski watchOS cihaz sembolleri"), - ("Library/Developer/CoreSimulator/Caches", "Simülatör Önbellekleri", "iOS Simülatör geçici dosyaları"), - (".npm/_cacache", "NPM Önbelleği", "Node Package Manager önbelleği"), - (".yarn/cache", "Yarn Önbelleği", "Yarn paket önbelleği"), - (".pnpm-store", "pnpm Store", "pnpm global paket havuzu"), - (".cargo/registry/cache", "Rust Cargo Önbelleği", "Cargo crates önbellek dosyaları"), - ("Library/Caches/CocoaPods", "CocoaPods Önbelleği", "iOS Pods indirme önbellekleri"), - (".gradle/caches", "Gradle Önbelleği", "Android ve Java derleme önbellekleri"), - (".cache/pip", "Python pip Önbelleği", "Python paket indirme önbellekleri"), - ("Library/Caches/Homebrew", "Homebrew İndirme Önbelleği", "İndirilen formül ve bottle paketleri"), - ("Library/Caches/uv", "Python UV Önbelleği", "UV paket yöneticisi önbelleği"), - ("Library/Caches/pypoetry", "Python Poetry Önbelleği", "Poetry sanal ortam ve paket havuzu") + ("Library/Developer/Xcode/DerivedData", "Xcode DerivedData", L10n.string("Build and index caches", table: .services)), + ("Library/Developer/Xcode/Archives", L10n.string("Xcode Archives", table: .services), L10n.string("Old app build archives", table: .services)), + ("Library/Developer/Xcode/iOS DeviceSupport", "iOS Device Support", L10n.string("Old iOS device symbols", table: .services)), + ("Library/Developer/Xcode/watchOS DeviceSupport", "watchOS Device Support", L10n.string("Old watchOS device symbols", table: .services)), + ("Library/Developer/CoreSimulator/Caches", L10n.string("Simulator Caches", table: .services), L10n.string("iOS Simulator temporary files", table: .services)), + (".npm/_cacache", L10n.string("NPM Cache", table: .services), L10n.string("Node Package Manager cache", table: .services)), + (".yarn/cache", L10n.string("Yarn Cache", table: .services), L10n.string("Yarn package cache", table: .services)), + (".pnpm-store", "pnpm Store", L10n.string("pnpm global package store", table: .services)), + (".cargo/registry/cache", L10n.string("Rust Cargo Cache", table: .services), L10n.string("Cargo crate cache files", table: .services)), + ("Library/Caches/CocoaPods", L10n.string("CocoaPods Cache", table: .services), L10n.string("iOS Pods download caches", table: .services)), + (".gradle/caches", L10n.string("Gradle Cache", table: .services), L10n.string("Android and Java build caches", table: .services)), + (".cache/pip", L10n.string("Python pip Cache", table: .services), L10n.string("Python package download caches", table: .services)), + ("Library/Caches/Homebrew", L10n.string("Homebrew Download Cache", table: .services), L10n.string("Downloaded formulae and bottle packages", table: .services)), + ("Library/Caches/uv", L10n.string("Python UV Cache", table: .services), L10n.string("UV package manager cache", table: .services)), + ("Library/Caches/pypoetry", L10n.string("Python Poetry Cache", table: .services), L10n.string("Poetry virtual environments and package store", table: .services)) ] for target in devTargets where !flag.isCancelled { @@ -266,13 +266,13 @@ public final class JunkCleanerService: Sendable { var items: [JunkFileItem] = [] let browserPaths: [(path: String, name: String)] = [ - ("Library/Caches/com.apple.Safari", "Safari Web Önbelleği"), - ("Library/Containers/com.apple.Safari/Data/Library/Caches", "Safari Container Önbelleği"), - ("Library/Caches/Google/Chrome", "Google Chrome Önbelleği"), - ("Library/Caches/company.thebrowser.Browser", "Arc Tarayıcı Önbelleği"), - ("Library/Caches/BraveSoftware/Brave-Browser", "Brave Tarayıcı Önbelleği"), - ("Library/Caches/com.microsoft.edgemac", "Microsoft Edge Önbelleği"), - ("Library/Caches/Firefox", "Mozilla Firefox Önbelleği") + ("Library/Caches/com.apple.Safari", L10n.string("Safari Web Cache", table: .services)), + ("Library/Containers/com.apple.Safari/Data/Library/Caches", L10n.string("Safari Container Cache", table: .services)), + ("Library/Caches/Google/Chrome", L10n.string("Google Chrome Cache", table: .services)), + ("Library/Caches/company.thebrowser.Browser", L10n.string("Arc Browser Cache", table: .services)), + ("Library/Caches/BraveSoftware/Brave-Browser", L10n.string("Brave Browser Cache", table: .services)), + ("Library/Caches/com.microsoft.edgemac", L10n.string("Microsoft Edge Cache", table: .services)), + ("Library/Caches/Firefox", L10n.string("Mozilla Firefox Cache", table: .services)) ] for browser in browserPaths where !flag.isCancelled { @@ -314,7 +314,7 @@ public final class JunkCleanerService: Sendable { sizeBytes: size, category: .trashBin, isSelected: false, - detail: "Çöp Sepetinde", + detail: L10n.string("In Trash", table: .services), lastModifiedDate: modDate )) } @@ -357,7 +357,7 @@ public final class JunkCleanerService: Sendable { sizeBytes: Int64(size), category: .largeFiles, isSelected: false, - detail: "\(folder) / \(fileURL.pathExtension.uppercased()) Dosyası", + detail: L10n.string("%@ / %@ File", table: .services, folder, fileURL.pathExtension.uppercased()), lastModifiedDate: values.contentModificationDate )) } @@ -436,7 +436,7 @@ public final class JunkCleanerService: Sendable { sizeBytes: size, category: .appLeftovers, isSelected: false, - detail: "Silinmiş uygulama kalıntısı (Application Support)" + detail: L10n.string("Deleted app leftover (Application Support)", table: .services) )) } } diff --git a/Sources/MacOptimizer/Services/SystemMonitorService.swift b/Sources/MacOptimizer/Services/SystemMonitorService.swift index 89abc5e..79d0b48 100644 --- a/Sources/MacOptimizer/Services/SystemMonitorService.swift +++ b/Sources/MacOptimizer/Services/SystemMonitorService.swift @@ -199,23 +199,23 @@ public actor SystemMonitorService: SystemMetricsSampling { } if let powerSource = description[kIOPSPowerSourceStateKey as String] as? String { - stats.powerSource = (powerSource == kIOPSACPowerValue as String) ? "AC Adaptör" : "Pil" + stats.powerSource = (powerSource == kIOPSACPowerValue as String) ? L10n.string("AC Adapter", table: .services) : L10n.string("Battery", table: .services) } if let timeRemaining = description[kIOPSTimeToEmptyKey as String] as? Int, timeRemaining > 0 { let hours = timeRemaining / 60 let mins = timeRemaining % 60 - stats.timeRemainingFormatted = "\(hours) sa \(mins) dk kaldı" + stats.timeRemainingFormatted = L10n.string("%lld h %lld min remaining", table: .services, hours, mins) } else if stats.isCharging { if let timeToFull = description[kIOPSTimeToFullChargeKey as String] as? Int, timeToFull > 0 { let hours = timeToFull / 60 let mins = timeToFull % 60 - stats.timeRemainingFormatted = "Dolmasına: \(hours) sa \(mins) dk" + stats.timeRemainingFormatted = L10n.string("Until full: %lld h %lld min", table: .services, hours, mins) } else { - stats.timeRemainingFormatted = "Şarj Oluyor" + stats.timeRemainingFormatted = L10n.string("Charging", table: .services) } } else { - stats.timeRemainingFormatted = "Hesaplanıyor..." + stats.timeRemainingFormatted = L10n.string("Calculating...", table: .services) } break @@ -242,9 +242,9 @@ public actor SystemMonitorService: SystemMetricsSampling { stats.healthPercentage = Self.batteryHealthPercentage(properties: props.compactMapValues { $0 as? Int }) } if let isPermanentFail = props["PermanentFailureStatus"] as? Int, isPermanentFail != 0 { - stats.condition = "Servis Öneriliyor" + stats.condition = L10n.string("Service Recommended", table: .services) } else if stats.healthPercentage < 80 { - stats.condition = "Pil Sağlığı Zayıf" + stats.condition = L10n.string("Poor Battery Health", table: .services) } else { stats.condition = "Normal" } @@ -488,13 +488,13 @@ public actor SystemMonitorService: SystemMetricsSampling { let minutes = (Int(uptime) % 3600) / 60 if days > 0 { - return "\(days) gün, \(hours) saat" + return L10n.string("%lld d, %lld h", table: .services, days, hours) } else if hours > 0 { - return "\(hours) saat, \(minutes) dk" + return L10n.string("%lld h, %lld min", table: .services, hours, minutes) } else { - return "\(minutes) dakika" + return L10n.string("%lld min", table: .services, minutes) } } - return "Bilinmiyor" + return L10n.string("Unknown", table: .services) } } diff --git a/Sources/MacOptimizer/Views/AppManager/AppUpdatesView.swift b/Sources/MacOptimizer/Views/AppManager/AppUpdatesView.swift index 34beaed..c33614d 100644 --- a/Sources/MacOptimizer/Views/AppManager/AppUpdatesView.swift +++ b/Sources/MacOptimizer/Views/AppManager/AppUpdatesView.swift @@ -104,7 +104,7 @@ public struct AppUpdatesView: View { .font(.system(size: 13, weight: .bold)) .lineLimit(1) - MetricBadge(text: app.updateInfo.updateSource.rawValue, colorName: "purple") + MetricBadge(text: app.updateInfo.updateSource.localizedTitle, colorName: "purple") } HStack(spacing: 6) { diff --git a/Sources/MacOptimizer/Views/DuplicateFinder/DuplicateFinderView.swift b/Sources/MacOptimizer/Views/DuplicateFinder/DuplicateFinderView.swift index f721df3..57f4891 100644 --- a/Sources/MacOptimizer/Views/DuplicateFinder/DuplicateFinderView.swift +++ b/Sources/MacOptimizer/Views/DuplicateFinder/DuplicateFinderView.swift @@ -100,7 +100,7 @@ public struct DuplicateFinderView: View { HStack(spacing: 4) { Image(systemName: selectedTargets.contains(target) ? "checkmark.circle.fill" : "circle") .font(.system(size: 10)) - Text(target.rawValue) + Text(verbatim: target.localizedTitle) .font(.system(size: 11, weight: .medium)) } .padding(.horizontal, 8) diff --git a/Tests/MacOptimizerTests/MacOptimizerTests.swift b/Tests/MacOptimizerTests/MacOptimizerTests.swift index caabc17..bf8bf92 100644 --- a/Tests/MacOptimizerTests/MacOptimizerTests.swift +++ b/Tests/MacOptimizerTests/MacOptimizerTests.swift @@ -635,8 +635,8 @@ 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") })) - XCTAssertTrue(titles.contains(where: { $0.contains("Termal") || $0.contains("Sıcaklık") })) + 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") })) }