diff --git a/Sources/MacOptimizer/Infrastructure/AI/LocalHeuristicProvider.swift b/Sources/MacOptimizer/Infrastructure/AI/LocalHeuristicProvider.swift
index ed5bde5..21c2111 100644
--- a/Sources/MacOptimizer/Infrastructure/AI/LocalHeuristicProvider.swift
+++ b/Sources/MacOptimizer/Infrastructure/AI/LocalHeuristicProvider.swift
@@ -3,7 +3,7 @@ import Foundation
/// 100% Offline, zero-network rule-based diagnostic and intelligence provider.
public struct LocalHeuristicProvider: AIProvider {
public let providerId: String = "local_heuristics"
- public let displayName: String = "Yerel Kural Motoru (Çevrimdışı & Güvenli)"
+ public let displayName: String = L10n.string("Local Rule Engine (100% Offline & Secure)", table: .ai)
public let requiresNetwork: Bool = false
public init() {}
@@ -20,22 +20,22 @@ public struct LocalHeuristicProvider: AIProvider {
var insights: [AIInsight] = []
let heaviestApps = topProcesses.filter { $0.isUserApp && !$0.isProtected }.prefix(3)
let memoryAdvice = heaviestApps.isEmpty
- ? "En çok bellek kullanan uygulamaları kapatmayı deneyin."
- : "Bellek kullanımını azaltmak için şu yoğun uygulamaları kapatmayı deneyin: " + heaviestApps.map { "\($0.name) (\($0.memoryFormatted))" }.joined(separator: ", ") + "."
+ ? L10n.string("Try closing the apps using the most memory.", table: .ai)
+ : L10n.string("To reduce memory usage, try closing these heavy apps: %@.", table: .ai, heaviestApps.map { "\($0.name) (\($0.memoryFormatted))" }.joined(separator: ", "))
// 1. Memory Pressure Evaluation
if memory.pressureLevel == .critical || memory.usedPercentage > 0.88 {
insights.append(AIInsight(
- title: "Kritik Bellek Baskısı Tespit Edildi",
- summary: "RAM kullanımı %\(Int(memory.usedPercentage * 100)) seviyesinde. \(memoryAdvice)",
+ title: L10n.string("Critical Memory Pressure Detected", table: .ai),
+ summary: L10n.string("RAM usage is at %lld%%. %@", table: .ai, Int(memory.usedPercentage * 100), memoryAdvice),
severity: .critical,
category: "RAM",
actions: []
))
} else if memory.pressureLevel == .warning || memory.usedPercentage > 0.75 {
insights.append(AIInsight(
- title: "Orta Düzey Bellek Yoğunluğu",
- summary: "Bellek doluluğu %\(Int(memory.usedPercentage * 100)). \(memoryAdvice)",
+ title: L10n.string("Moderate Memory Load", table: .ai),
+ summary: L10n.string("Memory usage is %lld%%. %@", table: .ai, Int(memory.usedPercentage * 100), memoryAdvice),
severity: .warning,
category: "RAM",
actions: []
@@ -46,12 +46,12 @@ public struct LocalHeuristicProvider: AIProvider {
let heavyProcs = topProcesses.filter { $0.cpuPercentage > 75.0 && !$0.isProtected }
if let runaway = heavyProcs.first {
insights.append(AIInsight(
- title: "Yüksek CPU Tüketen Süreç: \(runaway.name)",
- summary: "\(runaway.name) (PID: \(runaway.pid)) tek başına %\(String(format: "%.1f", runaway.cpuPercentage)) CPU tüketiyor. Bu durum fan gürültüsüne ve pilin hızlı tükenmesine yol açabilir.",
+ title: L10n.string("High CPU Process: %@", table: .ai, runaway.name),
+ summary: L10n.string("%@ (PID: %d) alone is using CPU at %@%%. This can cause fan noise and fast battery drain.", table: .ai, runaway.name, runaway.pid, String(format: "%.1f", runaway.cpuPercentage)),
severity: .warning,
- category: "İşlemci",
+ category: L10n.string("CPU", table: .ai),
actions: [
- AIAction(title: "\(runaway.name) Kapat", type: .killProcess, targetPID: runaway.pid)
+ AIAction(title: L10n.string("Quit %@", table: .ai, runaway.name), type: .killProcess, targetPID: runaway.pid)
]
))
}
@@ -60,12 +60,12 @@ public struct LocalHeuristicProvider: AIProvider {
let totalJunk = junkGroups.reduce(0) { $0 + $1.totalSizeBytes }
if totalJunk > 3 * 1024 * 1024 * 1024 {
insights.append(AIInsight(
- title: "\(ByteFormatter.format(totalJunk)) Gereksiz Önbellek Birikimi",
- summary: "Xcode DerivedData, sistem logları ve tarayıcı önbelleklerinde önemli miktarda yer kazanılabilir alan tespit edildi.",
+ title: L10n.string("%@ of Unneeded Cache Buildup", table: .ai, ByteFormatter.format(totalJunk)),
+ summary: L10n.string("A significant amount of reclaimable space was found in Xcode DerivedData, system logs, and browser caches.", table: .ai),
severity: .recommendation,
- category: "Disk",
+ category: L10n.string("Disk", table: .ai),
actions: [
- AIAction(title: "Gereksiz Dosyaları Temizle", type: .cleanJunk)
+ AIAction(title: L10n.string("Clean Junk Files", table: .ai), type: .cleanJunk)
]
))
}
@@ -73,12 +73,12 @@ public struct LocalHeuristicProvider: AIProvider {
// 4. Software Updates
if outdatedAppsCount > 0 {
insights.append(AIInsight(
- title: "\(outdatedAppsCount) Uygulama Güncellemesi Mevcut",
- summary: "Yüklü uygulamalarınızın yeni sürümleri yayınlandı. En son güvenlik yamaları ve performans iyileştirmeleri için güncellemeniz önerilir.",
+ title: L10n.string("%lld App Updates Available", table: .ai, outdatedAppsCount),
+ summary: L10n.string("New versions of your installed apps have been released. Updating is recommended for the latest security patches and performance improvements.", table: .ai),
severity: .info,
- category: "Yazılım",
+ category: L10n.string("Software", table: .ai),
actions: [
- AIAction(title: "Güncellemeleri İncele", type: .checkUpdates)
+ AIAction(title: L10n.string("Review Updates", table: .ai), type: .checkUpdates)
]
))
}
@@ -86,10 +86,10 @@ public struct LocalHeuristicProvider: AIProvider {
// 5. Default Healthy State
if insights.isEmpty {
insights.append(AIInsight(
- title: "Mac'iniz Harika Durumda",
- summary: "Bellek baskısı normal seviyede, olağandışı kaçak CPU süreci bulunmuyor ve disk alanınız dengeli.",
+ title: L10n.string("Your Mac Is in Great Shape", table: .ai),
+ summary: L10n.string("Memory pressure is normal, there is no unusual runaway CPU process, and your disk space is balanced.", table: .ai),
severity: .info,
- category: "Genel"
+ category: L10n.string("General", table: .ai)
))
}
@@ -101,17 +101,19 @@ public struct LocalHeuristicProvider: AIProvider {
snapshotContext: String
) async throws -> String {
guard let lastUserMsg = messages.last(where: { $0.role == .user })?.content.lowercased() else {
- return "Size nasıl yardımcı olabilirim?"
+ return L10n.string("How can I help you?", table: .ai)
}
- if lastUserMsg.contains("ram") || lastUserMsg.contains("bellek") {
- return "Mac'inizin bellek durumunu inceledim. Bellek baskısını azaltmak için en yoğun uygulamaları kapatmayı deneyebilirsiniz.\n\nSistem Bilgisi:\n\(snapshotContext)"
- } else if lastUserMsg.contains("ısın") || lastUserMsg.contains("cpu") || lastUserMsg.contains("fan") {
- return "İşlemci ve donanım telemetrisine göre en çok kaynak tüketen süreçleri Görev Yöneticisi'nden kontrol edebilir, askıda kalan kullanıcı uygulamalarını güvenle sonlandırabilirsiniz."
- } else if lastUserMsg.contains("temiz") || lastUserMsg.contains("disk") || lastUserMsg.contains("yer") {
- return "Disk Temizleyici modülü ile sistem önbelleklerini, derleme kalıntılarını (Xcode DerivedData) ve tarayıcı verilerini güvenle temizleyebilirsiniz."
+ // Keyword lists cover both Turkish and English user input.
+ if lastUserMsg.contains("ram") || lastUserMsg.contains("bellek") || lastUserMsg.contains("memory") {
+ return L10n.string("I reviewed your Mac’s memory status. To reduce memory pressure, you can try closing the most demanding apps.\n\nSystem Info:\n%@", table: .ai, snapshotContext)
+ } else if lastUserMsg.contains("ısın") || lastUserMsg.contains("cpu") || lastUserMsg.contains("fan") || lastUserMsg.contains("heat") {
+ return L10n.string("Based on processor and hardware telemetry, you can check the most resource-hungry processes in Task Manager and safely terminate unresponsive user apps.", table: .ai)
+ } else if lastUserMsg.contains("temiz") || lastUserMsg.contains("disk") || lastUserMsg.contains("yer")
+ || lastUserMsg.contains("clean") || lastUserMsg.contains("space") || lastUserMsg.contains("storage") {
+ return L10n.string("With the Disk Cleaner module, you can safely clean system caches, build leftovers (Xcode DerivedData), and browser data.", table: .ai)
} else {
- return "MacOptimizer Pro yerel yapay zeka motoru devrede. Mac'inizin performansı, bellek yönetimi ve sistem sağlığı hakkında dilediğiniz soruyu sorabilirsiniz."
+ return L10n.string("The MacOptimizer Pro local AI engine is active. Ask any question about your Mac’s performance, memory management, and system health.", table: .ai)
}
}
}
diff --git a/Sources/MacOptimizer/Infrastructure/AI/NvidiaNIMProvider.swift b/Sources/MacOptimizer/Infrastructure/AI/NvidiaNIMProvider.swift
index 16f5c10..bb7d960 100644
--- a/Sources/MacOptimizer/Infrastructure/AI/NvidiaNIMProvider.swift
+++ b/Sources/MacOptimizer/Infrastructure/AI/NvidiaNIMProvider.swift
@@ -2,7 +2,7 @@ import Foundation
public struct NvidiaNIMProvider: AIProvider {
public let providerId = "nvidia_nim"
- public let displayName = "NVIDIA NIM (Bulut)"
+ public let displayName = L10n.string("NVIDIA NIM (Cloud)", table: .ai)
public let requiresNetwork = true
private let config: NIMConfig
@@ -10,17 +10,27 @@ public struct NvidiaNIMProvider: AIProvider {
public nonisolated static func cloudContext(_ base: String, processNames: [String], includeProcesses: Bool) -> String {
guard includeProcesses, !processNames.isEmpty else { return base }
- return base + "\nÇalışan uygulamalar: " + processNames.joined(separator: ", ")
+ return base + "\nRunning apps: " + processNames.joined(separator: ", ")
+ }
+
+ /// Model instructions are always English; the reply language follows the app's UI language.
+ public nonisolated static func diagnosisPrompt(_ metrics: String, replyLanguage: String) -> String {
+ metrics + " Give short system recommendations. Reply in \(replyLanguage)."
+ }
+
+ public nonisolated static func copilotSystemPrompt(snapshotContext: String, replyLanguage: String) -> String {
+ "You are the MacOptimizer Pro macOS assistant. Reply in \(replyLanguage); only offer recommendations, never execute actions. System summary: \(snapshotContext)"
}
public func diagnose(memory: MemoryStats, cpu: CPUStats, disk: DiskStats, hardware: HardwareInfo,
topProcesses: [ProcessInfoModel], junkGroups: [JunkCategoryGroup], outdatedAppsCount: Int) async -> [AIInsight] {
let totalJunk = junkGroups.reduce(0) { $0 + $1.totalSizeBytes }
- let base = "Mac: \(hardware.modelName), \(hardware.chipName), \(hardware.osVersion). RAM %\(Int(memory.usedPercentage * 100)), CPU %\(cpu.totalUsage), Boş disk %\(Int(disk.freePercentage * 100)), Gereksiz dosyalar \(ByteFormatter.format(totalJunk)), Güncelleme bekleyen uygulama: \(outdatedAppsCount). Türkçe kısa sistem önerileri ver."
+ let metrics = "Mac: \(hardware.modelName), \(hardware.chipName), \(hardware.osVersion). RAM \(Int(memory.usedPercentage * 100))%, CPU \(cpu.totalUsage)%, Free disk \(Int(disk.freePercentage * 100))%, Junk files \(ByteFormatter.format(totalJunk)), Apps with pending updates: \(outdatedAppsCount)."
+ let base = Self.diagnosisPrompt(metrics, replyLanguage: L10n.currentLanguageEnglishName())
let prompt = Self.cloudContext(base, processNames: topProcesses.prefix(4).map { "\($0.name) (PID: \($0.pid))" }, includeProcesses: UserDefaults.standard.bool(forKey: "MacOptimizer_IncludeRunningAppNames"))
do {
let answer = try await NvidiaNIMService.shared.sendChatCompletion(messages: [["role": "user", "content": prompt]], config: config)
- return [AIInsight(title: "NVIDIA NIM Sistem Analizi", summary: answer, severity: .recommendation, category: "Yapay Zeka Raporu")]
+ return [AIInsight(title: L10n.string("NVIDIA NIM System Analysis", table: .ai), summary: answer, severity: .recommendation, category: L10n.string("AI Report", table: .ai))]
} catch {
return await LocalHeuristicProvider().diagnose(memory: memory, cpu: cpu, disk: disk, hardware: hardware,
topProcesses: topProcesses, junkGroups: junkGroups, outdatedAppsCount: outdatedAppsCount)
@@ -28,7 +38,7 @@ public struct NvidiaNIMProvider: AIProvider {
}
public func queryCopilot(messages: [AIChatMessage], snapshotContext: String) async throws -> String {
- let system = "MacOptimizer Pro macOS asistanısın. Türkçe yanıt ver; sadece öneri sun, eylem çalıştırma. Sistem özeti: \(snapshotContext)"
+ let system = Self.copilotSystemPrompt(snapshotContext: snapshotContext, replyLanguage: L10n.currentLanguageEnglishName())
let chat = messages.suffix(6).map { ["role": $0.role.rawValue, "content": $0.content] }
return try await NvidiaNIMService.shared.sendChatCompletion(messages: chat, config: config, systemPrompt: system)
}
diff --git a/Sources/MacOptimizer/L10n.swift b/Sources/MacOptimizer/L10n.swift
index 69f78aa..1051043 100644
--- a/Sources/MacOptimizer/L10n.swift
+++ b/Sources/MacOptimizer/L10n.swift
@@ -6,15 +6,26 @@ public enum L10n {
case common, dashboard, cleanup, ai, services
}
- public static func string(_ key: String, table: Table = .common) -> String {
+ /// The module localization (e.g. "en", "tr") that `string(_:table:)` resolves against, if any.
+ public static func currentLanguage() -> String? {
let preferences = UserDefaults.standard.stringArray(forKey: "AppleLanguages")
?? Bundle.main.preferredLocalizations
- let language = preferences.lazy.compactMap { preference in
+ return preferences.lazy.compactMap { preference in
Bundle.module.localizations.first {
preference.caseInsensitiveCompare($0) == .orderedSame
|| preference.lowercased().hasPrefix($0.lowercased() + "-")
}
}.first
+ }
+
+ /// English name of the UI language in use (e.g. "Turkish", "English"), for model instructions.
+ public static func currentLanguageEnglishName() -> String {
+ let code = currentLanguage() ?? Bundle.module.developmentLocalization ?? "en"
+ return Locale(identifier: "en").localizedString(forLanguageCode: code) ?? "English"
+ }
+
+ public static func string(_ key: String, table: Table = .common) -> String {
+ let language = currentLanguage()
if let language,
let path = Bundle.module.path(forResource: language, ofType: "lproj"),
let bundle = Bundle(path: path) {
diff --git a/Sources/MacOptimizer/Resources/en.lproj/ai.strings b/Sources/MacOptimizer/Resources/en.lproj/ai.strings
index 7726883..6d288dd 100644
--- a/Sources/MacOptimizer/Resources/en.lproj/ai.strings
+++ b/Sources/MacOptimizer/Resources/en.lproj/ai.strings
@@ -61,3 +61,50 @@
"General & Menu Bar" = "General & Menu Bar";
"Show live status icon in the menu bar" = "Show live status icon in the menu bar";
"Open-source, security-first, local macOS system health and optimization toolkit." = "Open-source, security-first, local macOS system health and optimization toolkit.";
+"NVIDIA NIM (Cloud)" = "NVIDIA NIM (Cloud)";
+"Try closing the apps using the most memory." = "Try closing the apps using the most memory.";
+"To reduce memory usage, try closing these heavy apps: %@." = "To reduce memory usage, try closing these heavy apps: %@.";
+"Critical Memory Pressure Detected" = "Critical Memory Pressure Detected";
+"RAM usage is at %lld%%. %@" = "RAM usage is at %lld%%. %@";
+"Moderate Memory Load" = "Moderate Memory Load";
+"Memory usage is %lld%%. %@" = "Memory usage is %lld%%. %@";
+"High CPU Process: %@" = "High CPU Process: %@";
+"%@ (PID: %d) alone is using CPU at %@%%. This can cause fan noise and fast battery drain." = "%@ (PID: %d) alone is using CPU at %@%%. This can cause fan noise and fast battery drain.";
+"CPU" = "CPU";
+"Quit %@" = "Quit %@";
+"%@ of Unneeded Cache Buildup" = "%@ of Unneeded Cache Buildup";
+"A significant amount of reclaimable space was found in Xcode DerivedData, system logs, and browser caches." = "A significant amount of reclaimable space was found in Xcode DerivedData, system logs, and browser caches.";
+"Disk" = "Disk";
+"Clean Junk Files" = "Clean Junk Files";
+"New versions of your installed apps have been released. Updating is recommended for the latest security patches and performance improvements." = "New versions of your installed apps have been released. Updating is recommended for the latest security patches and performance improvements.";
+"Software" = "Software";
+"Review Updates" = "Review Updates";
+"Your Mac Is in Great Shape" = "Your Mac Is in Great Shape";
+"Memory pressure is normal, there is no unusual runaway CPU process, and your disk space is balanced." = "Memory pressure is normal, there is no unusual runaway CPU process, and your disk space is balanced.";
+"General" = "General";
+"How can I help you?" = "How can I help you?";
+"I reviewed your Mac’s memory status. To reduce memory pressure, you can try closing the most demanding apps.\n\nSystem Info:\n%@" = "I reviewed your Mac’s memory status. To reduce memory pressure, you can try closing the most demanding apps.\n\nSystem Info:\n%@";
+"Based on processor and hardware telemetry, you can check the most resource-hungry processes in Task Manager and safely terminate unresponsive user apps." = "Based on processor and hardware telemetry, you can check the most resource-hungry processes in Task Manager and safely terminate unresponsive user apps.";
+"With the Disk Cleaner module, you can safely clean system caches, build leftovers (Xcode DerivedData), and browser data." = "With the Disk Cleaner module, you can safely clean system caches, build leftovers (Xcode DerivedData), and browser data.";
+"The MacOptimizer Pro local AI engine is active. Ask any question about your Mac’s performance, memory management, and system health." = "The MacOptimizer Pro local AI engine is active. Ask any question about your Mac’s performance, memory management, and system health.";
+"NVIDIA NIM System Analysis" = "NVIDIA NIM System Analysis";
+"AI Report" = "AI Report";
+"Check for Updates" = "Check for Updates";
+"Reset DNS Cache" = "Reset DNS Cache";
+"NVIDIA NIM connection error. Local suggestions are offered in the action buttons below." = "NVIDIA NIM connection error. Local suggestions are offered in the action buttons below.";
+"Invalid Base URL format." = "Invalid Base URL format.";
+"Security Warning: HTTPS is required for remote API connections." = "Security Warning: HTTPS is required for remote API connections.";
+"Unsupported URL protocol. (Use https://)" = "Unsupported URL protocol. (Use https://)";
+"NVIDIA NIM API key is missing. Please enter your key." = "NVIDIA NIM API key is missing. Please enter your key.";
+"Invalid /models URL." = "Invalid /models URL.";
+"Received an invalid HTTP response from the server." = "Received an invalid HTTP response from the server.";
+"NVIDIA NIM Error (%lld): %@" = "NVIDIA NIM Error (%lld): %@";
+"HTTP Error Code: %lld" = "HTTP Error Code: %lld";
+"Could not parse the model list." = "Could not parse the model list.";
+"Model scanned via NVIDIA NIM." = "Model scanned via NVIDIA NIM.";
+"No NVIDIA NIM API key entered. Please enter your key starting with 'nvapi-...'." = "No NVIDIA NIM API key entered. Please enter your key starting with 'nvapi-...'.";
+"NVIDIA NIM connection and model ('%@') verified!" = "NVIDIA NIM connection and model ('%@') verified!";
+"Connection error: %@" = "Connection error: %@";
+"NVIDIA NIM API key is missing." = "NVIDIA NIM API key is missing.";
+"Invalid /chat/completions URL format." = "Invalid /chat/completions URL format.";
+"Could not parse the API response." = "Could not parse the API response.";
diff --git a/Sources/MacOptimizer/Resources/en.lproj/ai.stringsdict b/Sources/MacOptimizer/Resources/en.lproj/ai.stringsdict
new file mode 100644
index 0000000..f82321c
--- /dev/null
+++ b/Sources/MacOptimizer/Resources/en.lproj/ai.stringsdict
@@ -0,0 +1,22 @@
+
+
+
+ %lld App Updates Available
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ one%lld App Update Available
+ other%lld App Updates Available
+
+
+ NVIDIA NIM connection successful! %lld models scanned.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneNVIDIA NIM connection successful! %lld model scanned.
+ otherNVIDIA NIM connection successful! %lld models scanned.
+
+
+
diff --git a/Sources/MacOptimizer/Resources/tr.lproj/ai.strings b/Sources/MacOptimizer/Resources/tr.lproj/ai.strings
index 090b11c..c76a9db 100644
--- a/Sources/MacOptimizer/Resources/tr.lproj/ai.strings
+++ b/Sources/MacOptimizer/Resources/tr.lproj/ai.strings
@@ -61,3 +61,50 @@
"General & Menu Bar" = "Genel ve Menü Çubuğu";
"Show live status icon in the menu bar" = "Menü Çubuğunda (Menu Bar) canlı durum simgesi göster";
"Open-source, security-first, local macOS system health and optimization toolkit." = "Açık kaynaklı, güvenlik öncelikli, yerel macOS sistem sağlığı ve optimizasyon araç seti.";
+"NVIDIA NIM (Cloud)" = "NVIDIA NIM (Bulut)";
+"Try closing the apps using the most memory." = "En çok bellek kullanan uygulamaları kapatmayı deneyin.";
+"To reduce memory usage, try closing these heavy apps: %@." = "Bellek kullanımını azaltmak için şu yoğun uygulamaları kapatmayı deneyin: %@.";
+"Critical Memory Pressure Detected" = "Kritik Bellek Baskısı Tespit Edildi";
+"RAM usage is at %lld%%. %@" = "RAM kullanımı %%%lld seviyesinde. %@";
+"Moderate Memory Load" = "Orta Düzey Bellek Yoğunluğu";
+"Memory usage is %lld%%. %@" = "Bellek doluluğu %%%lld. %@";
+"High CPU Process: %@" = "Yüksek CPU Tüketen Süreç: %@";
+"%@ (PID: %d) alone is using CPU at %@%%. This can cause fan noise and fast battery drain." = "%@ (PID: %d) tek başına %%%@ CPU tüketiyor. Bu durum fan gürültüsüne ve pilin hızlı tükenmesine yol açabilir.";
+"CPU" = "İşlemci";
+"Quit %@" = "%@ Kapat";
+"%@ of Unneeded Cache Buildup" = "%@ Gereksiz Önbellek Birikimi";
+"A significant amount of reclaimable space was found in Xcode DerivedData, system logs, and browser caches." = "Xcode DerivedData, sistem logları ve tarayıcı önbelleklerinde önemli miktarda yer kazanılabilir alan tespit edildi.";
+"Disk" = "Disk";
+"Clean Junk Files" = "Gereksiz Dosyaları Temizle";
+"New versions of your installed apps have been released. Updating is recommended for the latest security patches and performance improvements." = "Yüklü uygulamalarınızın yeni sürümleri yayınlandı. En son güvenlik yamaları ve performans iyileştirmeleri için güncellemeniz önerilir.";
+"Software" = "Yazılım";
+"Review Updates" = "Güncellemeleri İncele";
+"Your Mac Is in Great Shape" = "Mac'iniz Harika Durumda";
+"Memory pressure is normal, there is no unusual runaway CPU process, and your disk space is balanced." = "Bellek baskısı normal seviyede, olağandışı kaçak CPU süreci bulunmuyor ve disk alanınız dengeli.";
+"General" = "Genel";
+"How can I help you?" = "Size nasıl yardımcı olabilirim?";
+"I reviewed your Mac’s memory status. To reduce memory pressure, you can try closing the most demanding apps.\n\nSystem Info:\n%@" = "Mac'inizin bellek durumunu inceledim. Bellek baskısını azaltmak için en yoğun uygulamaları kapatmayı deneyebilirsiniz.\n\nSistem Bilgisi:\n%@";
+"Based on processor and hardware telemetry, you can check the most resource-hungry processes in Task Manager and safely terminate unresponsive user apps." = "İşlemci ve donanım telemetrisine göre en çok kaynak tüketen süreçleri Görev Yöneticisi'nden kontrol edebilir, askıda kalan kullanıcı uygulamalarını güvenle sonlandırabilirsiniz.";
+"With the Disk Cleaner module, you can safely clean system caches, build leftovers (Xcode DerivedData), and browser data." = "Disk Temizleyici modülü ile sistem önbelleklerini, derleme kalıntılarını (Xcode DerivedData) ve tarayıcı verilerini güvenle temizleyebilirsiniz.";
+"The MacOptimizer Pro local AI engine is active. Ask any question about your Mac’s performance, memory management, and system health." = "MacOptimizer Pro yerel yapay zeka motoru devrede. Mac'inizin performansı, bellek yönetimi ve sistem sağlığı hakkında dilediğiniz soruyu sorabilirsiniz.";
+"NVIDIA NIM System Analysis" = "NVIDIA NIM Sistem Analizi";
+"AI Report" = "Yapay Zeka Raporu";
+"Check for Updates" = "Güncellemeleri Denetle";
+"Reset DNS Cache" = "DNS Önbelleğini Sıfırla";
+"NVIDIA NIM connection error. Local suggestions are offered in the action buttons below." = "NVIDIA NIM bağlantı hatası. Yerel öneriler aşağıdaki eylem düğmelerinde sunuluyor.";
+"Invalid Base URL format." = "Geçersiz Base URL formatı.";
+"Security Warning: HTTPS is required for remote API connections." = "Güvenlik Uyarısı: Uzak API bağlantıları için HTTPS zorunludur.";
+"Unsupported URL protocol. (Use https://)" = "Desteklenmeyen URL protokolü. (https:// kullanın)";
+"NVIDIA NIM API key is missing. Please enter your key." = "NVIDIA NIM API Anahtarı eksik. Lütfen anahtarınızı girin.";
+"Invalid /models URL." = "Geçersiz /models URL'si.";
+"Received an invalid HTTP response from the server." = "Sunucudan geçersiz HTTP yanıtı alındı.";
+"NVIDIA NIM Error (%lld): %@" = "NVIDIA NIM Hatası (%lld): %@";
+"HTTP Error Code: %lld" = "HTTP Hata Kodu: %lld";
+"Could not parse the model list." = "Model listesi ayrıştırılamadı.";
+"Model scanned via NVIDIA NIM." = "NVIDIA NIM üzerinden taranmış model.";
+"No NVIDIA NIM API key entered. Please enter your key starting with 'nvapi-...'." = "NVIDIA NIM API Anahtarı girilmedi. Lütfen 'nvapi-...' ile başlayan anahtarınızı girin.";
+"NVIDIA NIM connection and model ('%@') verified!" = "NVIDIA NIM bağlantısı ve model ('%@') doğrulandı!";
+"Connection error: %@" = "Bağlantı hatası: %@";
+"NVIDIA NIM API key is missing." = "NVIDIA NIM API Anahtarı eksik.";
+"Invalid /chat/completions URL format." = "Geçersiz /chat/completions URL formatı.";
+"Could not parse the API response." = "API yanıtı ayrıştırılamadı.";
diff --git a/Sources/MacOptimizer/Resources/tr.lproj/ai.stringsdict b/Sources/MacOptimizer/Resources/tr.lproj/ai.stringsdict
new file mode 100644
index 0000000..98cfeff
--- /dev/null
+++ b/Sources/MacOptimizer/Resources/tr.lproj/ai.stringsdict
@@ -0,0 +1,22 @@
+
+
+
+ %lld App Updates Available
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ one%lld Uygulama Güncellemesi Mevcut
+ other%lld Uygulama Güncellemesi Mevcut
+
+
+ NVIDIA NIM connection successful! %lld models scanned.
+ NSStringLocalizedFormatKey%#@count@
+ count
+ NSStringFormatSpecTypeKeyNSStringPluralRuleType
+ NSStringFormatValueTypeKeylld
+ oneNVIDIA NIM bağlantısı başarılı! %lld adet model tarandı.
+ otherNVIDIA NIM bağlantısı başarılı! %lld adet model tarandı.
+
+
+
diff --git a/Sources/MacOptimizer/Services/AIAssistantService.swift b/Sources/MacOptimizer/Services/AIAssistantService.swift
index 5460c06..0f819da 100644
--- a/Sources/MacOptimizer/Services/AIAssistantService.swift
+++ b/Sources/MacOptimizer/Services/AIAssistantService.swift
@@ -22,14 +22,16 @@ public actor AIAssistantService {
config: NIMConfig) async -> (reply: String, actions: [AIAction]) {
let lower = userMessage.lowercased()
var actions: [AIAction] = []
- if lower.contains("çöp") || lower.contains("önbellek") || lower.contains("gereksiz") || lower.contains("temiz") { actions.append(AIAction(title: "Gereksiz Dosyaları Tara", type: .scanJunk)) }
- if lower.contains("güncelle") || lower.contains("update") || lower.contains("yeni sürüm") { actions.append(AIAction(title: "Güncellemeleri Denetle", type: .checkUpdates)) }
- if lower.contains("dns") || lower.contains("ağ") || lower.contains("internet") { actions.append(AIAction(title: "DNS Önbelleğini Sıfırla", type: .flushDNS)) }
+ // Keyword lists cover both Turkish and English user input.
+ if lower.contains("çöp") || lower.contains("önbellek") || lower.contains("gereksiz") || lower.contains("temiz")
+ || lower.contains("junk") || lower.contains("cache") || lower.contains("clean") { actions.append(AIAction(title: L10n.string("Scan for Junk Files", table: .ai), type: .scanJunk)) }
+ if lower.contains("güncelle") || lower.contains("update") || lower.contains("yeni sürüm") || lower.contains("new version") { actions.append(AIAction(title: L10n.string("Check for Updates", table: .ai), type: .checkUpdates)) }
+ if lower.contains("dns") || lower.contains("ağ") || lower.contains("internet") || lower.contains("network") { actions.append(AIAction(title: L10n.string("Reset DNS Cache", table: .ai), type: .flushDNS)) }
do {
let reply = try await provider(for: config).queryCopilot(messages: Array(history.suffix(6)), snapshotContext: systemContext)
return (reply, actions)
} catch {
- return ("NVIDIA NIM bağlantı hatası. Yerel öneriler aşağıdaki eylem düğmelerinde sunuluyor.", actions)
+ return (L10n.string("NVIDIA NIM connection error. Local suggestions are offered in the action buttons below.", table: .ai), actions)
}
}
}
diff --git a/Sources/MacOptimizer/Services/NvidiaNIMService.swift b/Sources/MacOptimizer/Services/NvidiaNIMService.swift
index 7e89492..f24adc9 100644
--- a/Sources/MacOptimizer/Services/NvidiaNIMService.swift
+++ b/Sources/MacOptimizer/Services/NvidiaNIMService.swift
@@ -26,7 +26,7 @@ public actor NvidiaNIMService {
public nonisolated static func redactFilePaths(_ text: String) -> String {
let pattern = #"(?:"|?*]+/)*[^\s<>:"|?*]+"#
guard let regex = try? NSRegularExpression(pattern: pattern) else { return text }
- return regex.stringByReplacingMatches(in: text, range: NSRange(text.startIndex..., in: text), withTemplate: "[dosya yolu]")
+ return regex.stringByReplacingMatches(in: text, range: NSRange(text.startIndex..., in: text), withTemplate: "[file path]")
}
// MARK: - Safe Base URL Validator
@@ -37,7 +37,7 @@ public actor NvidiaNIMService {
}
guard let url = URL(string: base), let scheme = url.scheme?.lowercased() else {
- throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: "Geçersiz Base URL formatı."])
+ throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: L10n.string("Invalid Base URL format.", table: .ai)])
}
let host = url.host?.lowercased() ?? ""
@@ -45,11 +45,11 @@ public actor NvidiaNIMService {
// Disallow insecure HTTP over non-localhost to protect API keys in transit
if scheme == "http" && !isLocalhost {
- throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: "Güvenlik Uyarısı: Uzak API bağlantıları için HTTPS zorunludur."])
+ throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: L10n.string("Security Warning: HTTPS is required for remote API connections.", table: .ai)])
}
guard scheme == "https" || (scheme == "http" && isLocalhost) else {
- throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: "Desteklenmeyen URL protokolü. (https:// kullanın)"])
+ throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: L10n.string("Unsupported URL protocol. (Use https://)", table: .ai)])
}
return base
@@ -59,12 +59,12 @@ public actor NvidiaNIMService {
public func fetchAvailableModels(config: NIMConfig) async throws -> [NIMModelOption] {
let trimmedKey = config.apiKey.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmedKey.isEmpty else {
- throw NSError(domain: "NvidiaNIM", code: 401, userInfo: [NSLocalizedDescriptionKey: "NVIDIA NIM API Anahtarı eksik. Lütfen anahtarınızı girin."])
+ throw NSError(domain: "NvidiaNIM", code: 401, userInfo: [NSLocalizedDescriptionKey: L10n.string("NVIDIA NIM API key is missing. Please enter your key.", table: .ai)])
}
let base = try validateAndFormatBaseURL(config.baseURL)
guard let url = URL(string: "\(base)/models") else {
- throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: "Geçersiz /models URL'si."])
+ throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: L10n.string("Invalid /models URL.", table: .ai)])
}
var request = URLRequest(url: url)
@@ -76,20 +76,20 @@ public actor NvidiaNIMService {
let (data, response) = try await urlSession.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
- throw NSError(domain: "NvidiaNIM", code: 500, userInfo: [NSLocalizedDescriptionKey: "Sunucudan geçersiz HTTP yanıtı alındı."])
+ throw NSError(domain: "NvidiaNIM", code: 500, userInfo: [NSLocalizedDescriptionKey: L10n.string("Received an invalid HTTP response from the server.", table: .ai)])
}
if httpResponse.statusCode != 200 {
if let errorJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let message = (errorJson["error"] as? [String: Any])?["message"] as? String ?? errorJson["message"] as? String {
- throw NSError(domain: "NvidiaNIM", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "NVIDIA NIM Hatası (\(httpResponse.statusCode)): \(message)"])
+ throw NSError(domain: "NvidiaNIM", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: L10n.string("NVIDIA NIM Error (%lld): %@", table: .ai, httpResponse.statusCode, message)])
}
- throw NSError(domain: "NvidiaNIM", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "HTTP Hata Kodu: \(httpResponse.statusCode)"])
+ throw NSError(domain: "NvidiaNIM", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: L10n.string("HTTP Error Code: %lld", table: .ai, httpResponse.statusCode)])
}
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let dataArray = json["data"] as? [[String: Any]] else {
- throw NSError(domain: "NvidiaNIM", code: 500, userInfo: [NSLocalizedDescriptionKey: "Model listesi ayrıştırılamadı."])
+ throw NSError(domain: "NvidiaNIM", code: 500, userInfo: [NSLocalizedDescriptionKey: L10n.string("Could not parse the model list.", table: .ai)])
}
var models: [NIMModelOption] = []
@@ -119,7 +119,7 @@ public actor NvidiaNIMService {
id: modelId,
name: "\(cleanName) (\(modelId))",
provider: providerName,
- description: "NVIDIA NIM üzerinden taranmış model.",
+ description: L10n.string("Model scanned via NVIDIA NIM.", table: .ai),
isRecommended: isRec
))
}
@@ -136,22 +136,22 @@ public actor NvidiaNIMService {
// MARK: - Test API Connection
public func testConnection(config: NIMConfig) async -> (success: Bool, message: String) {
guard !config.apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
- return (false, "NVIDIA NIM API Anahtarı girilmedi. Lütfen 'nvapi-...' ile başlayan anahtarınızı girin.")
+ return (false, L10n.string("No NVIDIA NIM API key entered. Please enter your key starting with 'nvapi-...'.", table: .ai))
}
// Try testing with models endpoint first, then chat probe
do {
let models = try await fetchAvailableModels(config: config)
- return (true, "NVIDIA NIM bağlantısı başarılı! \(models.count) adet model tarandı.")
+ return (true, L10n.string("NVIDIA NIM connection successful! %lld models scanned.", table: .ai, models.count))
} catch {
// Fallback to chat test
let testPrompt = "Test"
let messages = [["role": "user", "content": testPrompt]]
do {
- _ = try await sendChatCompletion(messages: messages, config: config, systemPrompt: "Kısa yanıt ver.")
- return (true, "NVIDIA NIM bağlantısı ve model ('\(config.selectedModel)') doğrulandı!")
+ _ = try await sendChatCompletion(messages: messages, config: config, systemPrompt: "Reply briefly.")
+ return (true, L10n.string("NVIDIA NIM connection and model ('%@') verified!", table: .ai, config.selectedModel))
} catch {
- return (false, "Bağlantı hatası: \(error.localizedDescription)")
+ return (false, L10n.string("Connection error: %@", table: .ai, error.localizedDescription))
}
}
}
@@ -164,12 +164,12 @@ public actor NvidiaNIMService {
) async throws -> String {
let trimmedKey = config.apiKey.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmedKey.isEmpty else {
- throw NSError(domain: "NvidiaNIM", code: 401, userInfo: [NSLocalizedDescriptionKey: "NVIDIA NIM API Anahtarı eksik."])
+ throw NSError(domain: "NvidiaNIM", code: 401, userInfo: [NSLocalizedDescriptionKey: L10n.string("NVIDIA NIM API key is missing.", table: .ai)])
}
let base = try validateAndFormatBaseURL(config.baseURL)
guard let url = URL(string: "\(base)/chat/completions") else {
- throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: "Geçersiz /chat/completions URL formatı."])
+ throw NSError(domain: "NvidiaNIM", code: 400, userInfo: [NSLocalizedDescriptionKey: L10n.string("Invalid /chat/completions URL format.", table: .ai)])
}
var allMessages: [[String: String]] = []
@@ -189,15 +189,15 @@ public actor NvidiaNIMService {
let (data, response) = try await urlSession.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
- throw NSError(domain: "NvidiaNIM", code: 500, userInfo: [NSLocalizedDescriptionKey: "Sunucudan geçersiz HTTP yanıtı alındı."])
+ throw NSError(domain: "NvidiaNIM", code: 500, userInfo: [NSLocalizedDescriptionKey: L10n.string("Received an invalid HTTP response from the server.", table: .ai)])
}
if httpResponse.statusCode != 200 {
if let errorJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let message = (errorJson["error"] as? [String: Any])?["message"] as? String ?? errorJson["message"] as? String {
- throw NSError(domain: "NvidiaNIM", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "NVIDIA NIM Hatası (\(httpResponse.statusCode)): \(message)"])
+ throw NSError(domain: "NvidiaNIM", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: L10n.string("NVIDIA NIM Error (%lld): %@", table: .ai, httpResponse.statusCode, message)])
}
- throw NSError(domain: "NvidiaNIM", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "HTTP Hata Kodu: \(httpResponse.statusCode)"])
+ throw NSError(domain: "NvidiaNIM", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: L10n.string("HTTP Error Code: %lld", table: .ai, httpResponse.statusCode)])
}
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
@@ -205,7 +205,7 @@ public actor NvidiaNIMService {
let firstChoice = choices.first,
let message = firstChoice["message"] as? [String: Any],
let content = message["content"] as? String else {
- throw NSError(domain: "NvidiaNIM", code: 500, userInfo: [NSLocalizedDescriptionKey: "API yanıtı ayrıştırılamadı."])
+ throw NSError(domain: "NvidiaNIM", code: 500, userInfo: [NSLocalizedDescriptionKey: L10n.string("Could not parse the API response.", table: .ai)])
}
return content.trimmingCharacters(in: .whitespacesAndNewlines)
diff --git a/Tests/MacOptimizerTests/MacOptimizerTests.swift b/Tests/MacOptimizerTests/MacOptimizerTests.swift
index bf8bf92..6525b92 100644
--- a/Tests/MacOptimizerTests/MacOptimizerTests.swift
+++ b/Tests/MacOptimizerTests/MacOptimizerTests.swift
@@ -487,7 +487,7 @@ final class MacOptimizerTests: XCTestCase {
XCTAssertTrue(insights.contains(where: { $0.severity == .critical }))
let memoryInsight = insights.first(where: { $0.category == "RAM" })
XCTAssertTrue(memoryInsight?.actions.isEmpty == true)
- XCTAssertTrue(memoryInsight?.summary.contains("uygulamaları kapatmayı") == true)
+ XCTAssertTrue(memoryInsight?.summary.contains(L10n.string("Try closing the apps using the most memory.", table: .ai)) == true)
}
func testNIMRequestPayloadDisclosureAndHistoryLimit() throws {
@@ -495,18 +495,49 @@ final class MacOptimizerTests: XCTestCase {
XCTAssertFalse(off.contains("SecretApp"))
let on = NvidiaNIMProvider.cloudContext("metrics", processNames: ["SecretApp (PID: 7)"], includeProcesses: true)
XCTAssertTrue(on.contains("SecretApp"))
+ XCTAssertTrue(on.contains("Running apps: SecretApp (PID: 7)"))
let messages = (0..<10).map { ["role": "user", "content": "message-\($0)" + ($0 == 9 ? " /Users/alice/Secret.txt" : "")] }
let body = try NvidiaNIMService.requestBody(messages: messages, config: NIMConfig(apiKey: "secret-key"))
let payload = String(decoding: body, as: UTF8.self)
XCTAssertFalse(payload.contains("secret-key"))
XCTAssertFalse(payload.contains("/Users/"))
- XCTAssertTrue(payload.contains("[dosya yolu]"))
+ XCTAssertTrue(payload.contains("[file path]"))
XCTAssertFalse(payload.contains("message-0"))
XCTAssertTrue(payload.contains("message-9"))
let decoded = try JSONSerialization.jsonObject(with: body) as? [String: Any]
XCTAssertEqual((decoded?["messages"] as? [[String: String]])?.count, 7)
}
+
+ func testNIMPromptsAreEnglishAndNameReplyLanguage() {
+ let system = NvidiaNIMProvider.copilotSystemPrompt(snapshotContext: "RAM 50%", replyLanguage: "Turkish")
+ XCTAssertTrue(system.hasPrefix("You are the MacOptimizer Pro macOS assistant."))
+ XCTAssertTrue(system.contains("Reply in Turkish"))
+ XCTAssertTrue(system.contains("RAM 50%"))
+ XCTAssertTrue(NvidiaNIMProvider.diagnosisPrompt("metrics", replyLanguage: "English").contains("Reply in English."))
+
+ let prior = UserDefaults.standard.object(forKey: "AppleLanguages")
+ defer {
+ if let prior { UserDefaults.standard.set(prior, forKey: "AppleLanguages") }
+ else { UserDefaults.standard.removeObject(forKey: "AppleLanguages") }
+ }
+ UserDefaults.standard.set(["tr-TR"], forKey: "AppleLanguages")
+ XCTAssertEqual(L10n.currentLanguageEnglishName(), "Turkish")
+ UserDefaults.standard.set(["en"], forKey: "AppleLanguages")
+ XCTAssertEqual(L10n.currentLanguageEnglishName(), "English")
+ }
+
+ func testCopilotKeywordsMatchTurkishAndEnglishInput() async {
+ let service = AIAssistantService()
+ let english = await service.chatWithCopilot(userMessage: "Please clean junk and check for update", history: [],
+ systemContext: "", config: NIMConfig())
+ XCTAssertEqual(english.actions.map(\.type), [.scanJunk, .checkUpdates])
+ let turkish = await service.chatWithCopilot(userMessage: "Önbellek temizle ve DNS sıfırla", history: [],
+ systemContext: "", config: NIMConfig())
+ XCTAssertEqual(turkish.actions.map(\.type), [.scanJunk, .flushDNS])
+ let memory = try? await LocalHeuristicProvider().queryCopilot(messages: [AIChatMessage(role: .user, content: "Why is memory high?")], snapshotContext: "ctx")
+ XCTAssertTrue(memory?.hasSuffix("ctx") == true)
+ }
// MARK: - 11. Mach-O Architecture Detector Tests
func testMachOArchitectureDetectorHeaders() {