diff --git a/PureMac.xcodeproj/project.pbxproj b/PureMac.xcodeproj/project.pbxproj index b9a48ae..40bc663 100644 --- a/PureMac.xcodeproj/project.pbxproj +++ b/PureMac.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 25E2304A3FF257F1740E304B /* FileProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7953B398E47A4C603DE287F1 /* FileProtection.swift */; }; 27F449EDD1B082FE11FEC9DF /* SchedulerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28181F034530331A550C6A3D /* SchedulerService.swift */; }; 340E424F759ACCDE7372F99F /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5ADDC35F31E40780FB5D017 /* Theme.swift */; }; + 39BEE5E2BEC526CCADFC9DDD /* ServicesMenu.strings in Resources */ = {isa = PBXBuildFile; fileRef = 57FBBF8C7EF27C8E09126ED5 /* ServicesMenu.strings */; }; 41B27CACD7C6C7471EFD03F9 /* UpdateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022D0EAEE2B6E54069FC2CBE /* UpdateService.swift */; }; 47C5ECD49C4DD75F271DB6CE /* StringNormalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3AC5F17D7CA94FAB1E8D7A /* StringNormalization.swift */; }; 48D1431A7C99C19EEBDB056B /* CleaningEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8DE5D45BA19E2670B57DC5 /* CleaningEngine.swift */; }; @@ -44,6 +45,7 @@ B51E5A95BB49A6C0F5273E21 /* FileSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE522B406791BCE905BC55B /* FileSize.swift */; }; B52938BBD11842631314543D /* CategoryDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10B0AF194677EAC1D5568785 /* CategoryDetailView.swift */; }; B928E4369A3FC4369F014A29 /* SmartCareCoreArtwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA389C4F50505795FB31A51 /* SmartCareCoreArtwork.swift */; }; + BB10A9AFDA394F918E068351 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B55B1419D88851567DFB783A /* InfoPlist.strings */; }; BC6C800216343438413349A3 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D4FD34378988D430A582ED0 /* OnboardingView.swift */; }; CDCDFEBA39A3290101F86AC6 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F510F232341EE18F11DC934 /* MainWindow.swift */; }; CFA64F54CDBF8A4765E0068D /* LocalizationFilesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 155CE1B8CDCCCA6F9FD028C1 /* LocalizationFilesTests.swift */; }; @@ -81,6 +83,7 @@ 10B0AF194677EAC1D5568785 /* CategoryDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryDetailView.swift; sourceTree = ""; }; 155CE1B8CDCCCA6F9FD028C1 /* LocalizationFilesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationFilesTests.swift; sourceTree = ""; }; 1AB003A7751F05727DBFD1A5 /* AppTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTheme.swift; sourceTree = ""; }; + 1C0D9A6F75D0B8ADDB32FC2A /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 1D3AC5F17D7CA94FAB1E8D7A /* StringNormalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringNormalization.swift; sourceTree = ""; }; 231FF7CBB5F621B66F2AB8A5 /* FDADemoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FDADemoView.swift; sourceTree = ""; }; 23486A54A82EE3865B784D1C /* AppInfoFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoFetcher.swift; sourceTree = ""; }; @@ -91,10 +94,13 @@ 311078221878708524283765 /* PureMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PureMac.app; sourceTree = BUILT_PRODUCTS_DIR; }; 340D303C60FF878B019056B6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 3667D46D8E2004EB4D73835A /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; + 374A3BE46E71E54B6927E9EF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/ServicesMenu.strings; sourceTree = ""; }; 3A8D39C3C250F26302EC45AB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 3D4FD34378988D430A582ED0 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; 40A3F22FCEF534DE4A2CAD0E /* Haptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptics.swift; sourceTree = ""; }; + 40CD4855FB5FA866CAA52C10 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 424F7B28624C271620E13BBC /* EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateView.swift; sourceTree = ""; }; + 44392E486177839BA978685E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ServicesMenu.strings; sourceTree = ""; }; 46660271CFF167AB0FE7371D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 491771F923C93FD61A263893 /* AppLanguagePreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLanguagePreferencesTests.swift; sourceTree = ""; }; 4AE790496C936424EF98320A /* OrphanSafetyPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrphanSafetyPolicy.swift; sourceTree = ""; }; @@ -105,6 +111,7 @@ 60E6BC61614B27C065BB18C9 /* AppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListView.swift; sourceTree = ""; }; 63581B70F9B10231964E3602 /* PureMacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PureMacApp.swift; sourceTree = ""; }; 69B24B648B82186FC3B6EF2B /* BinaryThinner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryThinner.swift; sourceTree = ""; }; + 6ED5C9C10C69C3143B1F5CA6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/ServicesMenu.strings; sourceTree = ""; }; 752603285F7DBBA12BB3AA91 /* AppStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateTests.swift; sourceTree = ""; }; 77D3D9A9BC52839E6D0A22BC /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = ""; }; 7953B398E47A4C603DE287F1 /* FileProtection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProtection.swift; sourceTree = ""; }; @@ -124,13 +131,16 @@ B2EA41E1096FA8E3B916AD13 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B71E8F62DB76D85F41F9A2E9 /* OrphanListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrphanListView.swift; sourceTree = ""; }; C0899FE169D270DF95334905 /* LanguageFilesScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageFilesScanner.swift; sourceTree = ""; }; + C4332033E243C3571C49A5E9 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; C5963EBBEE5BA6E147700821 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; C5ADDC35F31E40780FB5D017 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; CB94E06E145558123BB5BFB3 /* Conditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conditions.swift; sourceTree = ""; }; CED1B71D5F9510582E869CFD /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; DAA389C4F50505795FB31A51 /* SmartCareCoreArtwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCareCoreArtwork.swift; sourceTree = ""; }; DB798781B99987F55D77E2E3 /* SystemMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemMonitor.swift; sourceTree = ""; }; + DFC4A1FD7DB92C82725B4314 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; E866A1541D289C69144A5E62 /* FullDiskAccessManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullDiskAccessManager.swift; sourceTree = ""; }; + ED0C4D8912DA94F81B2AEF8F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; EEF15CB1B8EFCA78EF491824 /* ScanError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanError.swift; sourceTree = ""; }; F0599AA604B0D6BA4F957537 /* ConfettiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfettiView.swift; sourceTree = ""; }; F31F91226CDCFBB8E303B7DA /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; @@ -307,10 +317,12 @@ 63581B70F9B10231964E3602 /* PureMacApp.swift */, D4333B07691BD85CAE0E5B15 /* Core */, 7C1729F88C0E5563E1A3DB40 /* Extensions */, + B55B1419D88851567DFB783A /* InfoPlist.strings */, 241E0895B09C71AB423B2F9E /* Localizable.strings */, F283C00EB52AB140F61500A3 /* Logic */, 3CF46713F75B81F0F86D1C6F /* Models */, 6184B2EC3D01E6E95633406E /* Services */, + 57FBBF8C7EF27C8E09126ED5 /* ServicesMenu.strings */, A8F1C251567E5321E1CA2484 /* ViewModels */, 45B485C4ACB5B08C1B87917F /* Views */, ); @@ -411,6 +423,8 @@ ja, pl, "pt-BR", + ru, + uk, "zh-Hans", "zh-Hant", ); @@ -436,7 +450,9 @@ buildActionMask = 2147483647; files = ( DDD6BA35DBF32E7A6B5F6F8B /* Assets.xcassets in Resources */, + BB10A9AFDA394F918E068351 /* InfoPlist.strings in Resources */, 013EF7B96BF046D4DB38FBC5 /* Localizable.strings in Resources */, + 39BEE5E2BEC526CCADFC9DDD /* ServicesMenu.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -527,12 +543,34 @@ 5A5C80929EE4A430272674BC /* ja */, C5963EBBEE5BA6E147700821 /* pl */, 8D5E63D733D1A3BDEDF4DCA5 /* pt-BR */, + C4332033E243C3571C49A5E9 /* ru */, + 1C0D9A6F75D0B8ADDB32FC2A /* uk */, 2641C6376DD6F5889F35510E /* zh-Hans */, 02E502E2B5C6AECC76E5CFEF /* zh-Hant */, ); name = Localizable.strings; sourceTree = ""; }; + 57FBBF8C7EF27C8E09126ED5 /* ServicesMenu.strings */ = { + isa = PBXVariantGroup; + children = ( + 44392E486177839BA978685E /* en */, + 6ED5C9C10C69C3143B1F5CA6 /* ru */, + 374A3BE46E71E54B6927E9EF /* uk */, + ); + name = ServicesMenu.strings; + sourceTree = ""; + }; + B55B1419D88851567DFB783A /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 40CD4855FB5FA866CAA52C10 /* en */, + ED0C4D8912DA94F81B2AEF8F /* ru */, + DFC4A1FD7DB92C82725B4314 /* uk */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/PureMac/Models/AppLanguage.swift b/PureMac/Models/AppLanguage.swift index 982a9d8..3a4b077 100644 --- a/PureMac/Models/AppLanguage.swift +++ b/PureMac/Models/AppLanguage.swift @@ -8,6 +8,8 @@ enum AppLanguage: String, CaseIterable, Identifiable { case arabic = "ar" case portugueseBrazil = "pt-BR" case polish = "pl" + case russian = "ru" + case ukrainian = "uk" case simplifiedChinese = "zh-Hans" case traditionalChinese = "zh-Hant" @@ -24,6 +26,8 @@ enum AppLanguage: String, CaseIterable, Identifiable { case .arabic: return "Arabic" case .polish: return "Polish" case .portugueseBrazil: return "Portuguese (Brazil)" + case .russian: return "Russian" + case .ukrainian: return "Ukrainian" case .simplifiedChinese: return "Chinese (Simplified)" case .traditionalChinese: return "Chinese (Traditional)" } diff --git a/PureMac/PureMacApp.swift b/PureMac/PureMacApp.swift index 4078a74..362c1b4 100644 --- a/PureMac/PureMacApp.swift +++ b/PureMac/PureMacApp.swift @@ -44,7 +44,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { let urls = (pboard.readObjects(forClasses: [NSURL.self], options: [.urlReadingFileURLsOnly: true]) as? [URL]) ?? [] guard let appURL = urls.first(where: { $0.pathExtension == "app" }) else { - error?.pointee = "Select an application (.app) to uninstall." as NSString + error?.pointee = String(localized: "Select an application (.app) to uninstall.") as NSString return } NSApp.activate(ignoringOtherApps: true) diff --git a/PureMac/Services/MenuBarController.swift b/PureMac/Services/MenuBarController.swift index e5afcbb..115c8c3 100644 --- a/PureMac/Services/MenuBarController.swift +++ b/PureMac/Services/MenuBarController.swift @@ -40,7 +40,7 @@ final class MenuBarController: NSObject, NSPopoverDelegate { if let button = statusItem.button { button.image = NSImage( systemSymbolName: "gauge.with.dots.needle.67percent", - accessibilityDescription: "System Monitor" + accessibilityDescription: String(localized: "System Monitor") ) button.imagePosition = .imageLeading button.target = self diff --git a/PureMac/Services/ScanEngine.swift b/PureMac/Services/ScanEngine.swift index ea0c1c5..f02c08c 100644 --- a/PureMac/Services/ScanEngine.swift +++ b/PureMac/Services/ScanEngine.swift @@ -226,37 +226,37 @@ actor ScanEngine { private func scanAIApps() -> CategoryResult { let targets = [ CleanupTarget( - name: "Ollama Logs", + name: String(localized: "Ollama Logs"), path: "\(home)/.ollama/logs" ), CleanupTarget( - name: "Ollama Cache", + name: String(localized: "Ollama Cache"), path: "\(home)/Library/Caches/ollama" ), CleanupTarget( - name: "Ollama Electron Cache", + name: String(localized: "Ollama Electron Cache"), path: "\(home)/Library/Caches/com.electron.ollama" ), CleanupTarget( - name: "Ollama WebKit Data", + name: String(localized: "Ollama WebKit Data"), path: "\(home)/Library/WebKit/com.electron.ollama" ), CleanupTarget( - name: "Ollama Saved State", + name: String(localized: "Ollama Saved State"), path: "\(home)/Library/Saved Application State/com.electron.ollama.savedState" ), CleanupTarget( - name: "Ollama CLI Prompt History (Optional)", + name: String(localized: "Ollama CLI Prompt History (Optional)"), path: "\(home)/.ollama/history", isSelected: false, minimumSize: 0 ), CleanupTarget( - name: "LM Studio Server Logs", + name: String(localized: "LM Studio Server Logs"), path: "\(home)/.lmstudio/server-logs" ), CleanupTarget( - name: "LM Studio Conversations (Optional)", + name: String(localized: "LM Studio Conversations (Optional)"), path: "\(home)/.lmstudio/conversations", isSelected: false, minimumSize: 0 @@ -572,12 +572,12 @@ actor ScanEngine { let managers: [ManagerCache] = [ ManagerCache( - name: "npm cache", + name: String(localized: "npm cache"), defaultPath: "\(home)/.npm", detectionCommand: (cli: "npm", args: ["config", "get", "cache"]) ), ManagerCache( - name: "yarn classic cache", + name: String(localized: "yarn classic cache"), defaultPath: "\(home)/Library/Caches/Yarn", detectionCommand: (cli: "yarn", args: ["cache", "dir"]) ), @@ -586,7 +586,7 @@ actor ScanEngine { // touched by a system cleaner. The classic cache above remains the // global, safe-to-clean location. ManagerCache( - name: "pnpm content-addressable store", + name: String(localized: "pnpm content-addressable store"), defaultPath: "\(home)/Library/pnpm/store", detectionCommand: (cli: "pnpm", args: ["store", "path"]) ), @@ -735,7 +735,7 @@ actor ScanEngine { for dockerBin in dockerBinPaths where fileManager.fileExists(atPath: dockerBin) { if let reclaimable = reclaimableDockerSpace(dockerBin: dockerBin), reclaimable > 0 { items.append(CleanableItem( - name: "Docker prune (stopped containers, dangling images, build cache)", + name: String(localized: "Docker prune (stopped containers, dangling images, build cache)"), path: "", size: reclaimable, category: .dockerCache, diff --git a/PureMac/Views/Components/DashboardCharts.swift b/PureMac/Views/Components/DashboardCharts.swift index a83760b..8abc1cb 100644 --- a/PureMac/Views/Components/DashboardCharts.swift +++ b/PureMac/Views/Components/DashboardCharts.swift @@ -231,7 +231,7 @@ struct CategoryBarChart: View { return Chart(bars) { bar in BarMark( x: .value("Size", Double(bar.size) * reveal), - y: .value("Category", bar.name) + y: .value(String(localized: "Category"), bar.name) ) .foregroundStyle(bar.category.color.gradient) .cornerRadius(5) diff --git a/PureMac/Views/Components/FDADemoView.swift b/PureMac/Views/Components/FDADemoView.swift index 52d8a9d..d6f5590 100644 --- a/PureMac/Views/Components/FDADemoView.swift +++ b/PureMac/Views/Components/FDADemoView.swift @@ -170,7 +170,7 @@ struct FDADemoView: View { } private struct DemoRow { - let name: String + let name: LocalizedStringKey let systemImage: String let granted: Bool var isPureMac: Bool = false diff --git a/PureMac/Views/Components/PermissionSheet.swift b/PureMac/Views/Components/PermissionSheet.swift index ebeb437..41ab850 100644 --- a/PureMac/Views/Components/PermissionSheet.swift +++ b/PureMac/Views/Components/PermissionSheet.swift @@ -50,9 +50,11 @@ struct PermissionSheet: View { VStack(alignment: .leading, spacing: 2) { Text(coordinator.context.headline) .font(.system(size: 16, weight: .bold)) - Text(coordinator.hasFullDiskAccess - ? "Access granted. Retrying…" - : "1-tap setup. We'll detect the change automatically.") + Text(LocalizedStringKey( + coordinator.hasFullDiskAccess + ? "Access granted. Retrying…" + : "1-tap setup. We'll detect the change automatically." + )) .font(.system(size: 12)) .foregroundStyle(.secondary) } @@ -160,7 +162,7 @@ struct PermissionSheet: View { .font(.system(size: 11.5)) .foregroundStyle(.secondary) Spacer() - Button(showAdvanced ? "Hide help" : "PureMac not in the list?") { + Button(LocalizedStringKey(showAdvanced ? "Hide help" : "PureMac not in the list?")) { withAnimation(.easeInOut(duration: 0.25)) { showAdvanced.toggle() } } .buttonStyle(.link) diff --git a/PureMac/Views/OnboardingView.swift b/PureMac/Views/OnboardingView.swift index 228a8e0..7cca0ce 100644 --- a/PureMac/Views/OnboardingView.swift +++ b/PureMac/Views/OnboardingView.swift @@ -137,7 +137,7 @@ struct OnboardingView: View { Button("Start") { isComplete = true } .buttonStyle(GlowProminentButtonStyle(breathes: true)) } else { - Button(page == .permission ? "Continue" : "Next") { advance(by: 1) } + Button(LocalizedStringKey(page == .permission ? "Continue" : "Next")) { advance(by: 1) } .buttonStyle(.borderedProminent) .controlSize(.large) } @@ -307,10 +307,10 @@ private struct MissionScene: View { private struct FeatureRow: View { let systemImage: String let tint: Color - let title: String - let body_: String + let title: LocalizedStringKey + let body_: LocalizedStringKey - init(systemImage: String, tint: Color, title: String, body: String) { + init(systemImage: String, tint: Color, title: LocalizedStringKey, body: LocalizedStringKey) { self.systemImage = systemImage self.tint = tint self.title = title @@ -347,12 +347,14 @@ private struct PermissionScene: View { var body: some View { VStack(spacing: 18) { VStack(spacing: 8) { - Text(hasFda ? "Permission granted" : "One permission, then we're done") + Text(LocalizedStringKey(hasFda ? "Permission granted" : "One permission, then we're done")) .font(.system(size: 26, weight: .semibold)) .multilineTextAlignment(.center) - Text(hasFda - ? "PureMac can now reach the locations macOS protects by default." - : "macOS hides certain folders from every app until you say otherwise. We need them to find caches and uninstall cleanly.") + Text(LocalizedStringKey( + hasFda + ? "PureMac can now reach the locations macOS protects by default." + : "macOS hides certain folders from every app until you say otherwise. We need them to find caches and uninstall cleanly." + )) .font(.system(size: 13)) .foregroundStyle(.secondary) .multilineTextAlignment(.center) @@ -382,7 +384,7 @@ private struct PermissionScene: View { Button { openSettings() } label: { - Label(hasOpenedSettings ? "Reopen Settings" : "Open Settings & reveal PureMac", + Label(LocalizedStringKey(hasOpenedSettings ? "Reopen Settings" : "Open Settings & reveal PureMac"), systemImage: "gear") .font(.system(size: 13, weight: .semibold)) .frame(minWidth: 240) @@ -473,11 +475,13 @@ private struct ReadyScene: View { } VStack(spacing: 10) { - Text(hasFda ? "You're ready" : "Ready when you are") + Text(LocalizedStringKey(hasFda ? "You're ready" : "Ready when you are")) .font(.system(size: 30, weight: .semibold)) - Text(hasFda - ? "Hit Start to run your first Smart Scan." - : "Some features will be limited without Full Disk Access. You can grant it later in Settings.") + Text(LocalizedStringKey( + hasFda + ? "Hit Start to run your first Smart Scan." + : "Some features will be limited without Full Disk Access. You can grant it later in Settings." + )) .font(.system(size: 13.5)) .foregroundStyle(.secondary) .multilineTextAlignment(.center) diff --git a/PureMac/Views/Orphans/OrphanListView.swift b/PureMac/Views/Orphans/OrphanListView.swift index 652524c..9d33309 100644 --- a/PureMac/Views/Orphans/OrphanListView.swift +++ b/PureMac/Views/Orphans/OrphanListView.swift @@ -156,7 +156,7 @@ struct OrphanListView: View { for url in urlsToRemove { guard OrphanSafetyPolicy.isSafeCandidate(url) else { - failedPaths.append("\(url.path) (blocked by safety policy)") + failedPaths.append("\(url.path) (\(String(localized: "blocked by safety policy")))") continue } @@ -198,7 +198,11 @@ struct OrphanListView: View { if !failedPaths.isEmpty { let preview = failedPaths.prefix(3).joined(separator: "\n") let suffix = failedPaths.count > 3 ? "\n…" : "" - removalErrorMessage = "\(failedPaths.count) item(s) failed to delete.\n\n\(preview)\(suffix)" + removalErrorMessage = String( + format: String(localized: "%lld item(s) failed to delete.\n\n%@"), + Int64(failedPaths.count), + preview + suffix + ) } } diff --git a/PureMac/ar.lproj/Localizable.strings b/PureMac/ar.lproj/Localizable.strings index e3e95f1..a1a2228 100644 --- a/PureMac/ar.lproj/Localizable.strings +++ b/PureMac/ar.lproj/Localizable.strings @@ -41,6 +41,7 @@ "Suggested for you" = "اقتراحات لك"; "Found so far" = "العثور عليه حتى الآن"; "By category" = "حسب الفئة"; +"Category" = "الفئة"; "%@ is using %@" = "%@ يستخدم %@"; "Grant Full Disk Access for full results" = "امنح الوصول الكامل للقرص للحصول على نتائج كاملة"; "Without it, most caches and uninstall flows fail." = "من دونه ستفشل معظم عمليات تنظيف الذاكرة المؤقتة وإزالة التطبيقات."; @@ -121,6 +122,8 @@ "Orphaned Files (%lld)" = "الملفات اليتيمة (%lld)"; "Remove Selected (%lld)" = "إزالة المحدد (%lld)"; "Some files could not be removed" = "تعذّرت إزالة بعض الملفات"; +"blocked by safety policy" = "محظور بواسطة سياسة الأمان"; +"%lld item(s) failed to delete.\n\n%@" = "فشل حذف %lld عنصرًا.\n\n%@"; /* Onboarding */ "Back" = "السابق"; @@ -151,7 +154,7 @@ "PureMac can now manage protected files." = "أصبح بإمكان PureMac إدارة الملفات المحمية."; "You're Ready" = "أنت جاهز"; "%lld/%lld protected locations accessible" = "%lld/%lld من المواقع المحمية متاحة"; -"Some features will be limited. You can grant Full Disk Access later in System Settings." = "ستكون بعض الميزات محدودة. يمكنك منح الوصول الكامل للقرص لاحقًا من إعدادات النظام."; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "ستكون بعض الميزات محدودة دون الوصول الكامل إلى القرص. يمكنك منحه لاحقًا في الإعدادات."; "Trash" = "سلة المهملات"; "Mail Data" = "بيانات البريد"; "Safari Data" = "بيانات Safari"; @@ -160,6 +163,10 @@ "TCC Database" = "قاعدة بيانات TCC"; "Blocked" = "محظور"; +/* FDA demo system apps */ +"Finder" = "فايندر"; +"Terminal" = "الطرفية"; + /* Settings */ "General" = "عام"; "Cleaning" = "التنظيف"; @@ -185,6 +192,8 @@ "Arabic" = "العربية"; "Polish" = "البولندية"; "Portuguese (Brazil)" = "البرتغالية (البرازيل)"; +"Russian" = "الروسية"; +"Ukrainian" = "الأوكرانية"; "Chinese (Simplified)" = "الصينية (المبسطة)"; "Chinese (Traditional)" = "الصينية (التقليدية)"; "Safety" = "الأمان"; @@ -206,6 +215,8 @@ "GitHub Repository" = "مستودع GitHub"; "Report an Issue" = "الإبلاغ عن مشكلة"; "MIT License" = "ترخيص MIT"; +"Updates" = "التحديثات"; +"Check for Updates" = "التحقق من وجود تحديثات"; /* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "نفايات النظام"; @@ -238,13 +249,23 @@ "Unused CPU architecture slices in app binaries" = "شرائح بنى معالج غير مستخدمة في الملفات الثنائية للتطبيقات"; "Unused app localizations" = "ترجمات التطبيقات غير المستخدمة"; +/* AI Apps scan item names */ +"Ollama Logs" = "سجلات Ollama"; +"Ollama Cache" = "ذاكرة التخزين المؤقت لـ Ollama"; +"Ollama Electron Cache" = "ذاكرة التخزين المؤقت لـ Electron في Ollama"; +"Ollama WebKit Data" = "بيانات WebKit في Ollama"; +"Ollama Saved State" = "الحالة المحفوظة لـ Ollama"; +"Ollama CLI Prompt History (Optional)" = "سجل مطالبات Ollama CLI (اختياري)"; +"LM Studio Server Logs" = "سجلات خادم LM Studio"; +"LM Studio Conversations (Optional)" = "محادثات LM Studio (اختياري)"; + /* Node Cache subcategories */ "npm cache" = "ذاكرة npm المؤقتة"; "yarn classic cache" = "ذاكرة yarn الكلاسيكية المؤقتة"; "pnpm content-addressable store" = "مخزن pnpm القابل للعنونة بالمحتوى"; /* Docker Cache helper */ -"Reclaimable (run `docker system prune -af`)" = "قابلة للاسترجاع (شغّل `docker system prune -af`)"; +"Docker prune (stopped containers, dangling images, build cache)" = "تنظيف Docker (الحاويات المتوقفة، والصور غير المرتبطة، وذاكرة التخزين المؤقت للبناء)"; /* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "كل ساعة"; @@ -306,6 +327,8 @@ "1-tap setup. We'll auto-retry what failed." = "إعداد بضغطة واحدة. سنعيد المحاولة تلقائيًا."; "Fix permission" = "إصلاح الإذن"; "Dismiss" = "تجاهل"; +"Fix" = "إصلاح"; +"Set up" = "إعداد"; /* Onboarding v2 */ "Reclaim your Mac" = "استعد جهاز Mac الخاص بك"; @@ -342,7 +365,6 @@ "Free" = "متاح"; /* UI polish (v2.8.0): leftover groups + sound toggle */ -"Application" = "التطبيق"; "Caches" = "ذاكرة التخزين المؤقت"; "Application Support" = "دعم التطبيقات"; "Preferences" = "التفضيلات"; @@ -379,3 +401,6 @@ "Disk" = "القرص"; "Open PureMac" = "فتح PureMac"; "Quit PureMac" = "إنهاء PureMac"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "حدد تطبيقًا (.app) لإلغاء تثبيته."; diff --git a/PureMac/en.lproj/InfoPlist.strings b/PureMac/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..3ae9ddd --- /dev/null +++ b/PureMac/en.lproj/InfoPlist.strings @@ -0,0 +1,8 @@ +/* macOS permission prompts */ +"NSAppleEventsUsageDescription" = "PureMac uses Apple Events to interact with Finder when revealing files."; +"NSDesktopFolderUsageDescription" = "PureMac needs access to your Desktop to find junk and orphaned files."; +"NSDocumentsFolderUsageDescription" = "PureMac needs access to your Documents to find junk and orphaned files."; +"NSDownloadsFolderUsageDescription" = "PureMac needs access to your Downloads to find junk and orphaned files."; +"NSRemovableVolumesUsageDescription" = "PureMac needs access to removable volumes to scan for junk files."; +"NSSystemAdministrationUsageDescription" = "PureMac needs system administration access to clean caches and uninstall apps completely."; +"NSHumanReadableCopyright" = "Copyright 2026. MIT License."; diff --git a/PureMac/en.lproj/Localizable.strings b/PureMac/en.lproj/Localizable.strings index 49a77b3..87c6d3e 100644 --- a/PureMac/en.lproj/Localizable.strings +++ b/PureMac/en.lproj/Localizable.strings @@ -58,6 +58,8 @@ "1-tap setup. We'll auto-retry what failed." = "1-tap setup. We'll auto-retry what failed."; "Fix permission" = "Fix permission"; "Dismiss" = "Dismiss"; +"Fix" = "Fix"; +"Set up" = "Set up"; /* Dashboard */ "Storage" = "Storage"; @@ -80,6 +82,7 @@ "Suggested for you" = "Suggested for you"; "Found so far" = "Found so far"; "By category" = "By category"; +"Category" = "Category"; "%@ is using %@" = "%@ is using %@"; "Grant Full Disk Access for full results" = "Grant Full Disk Access for full results"; "Without it, most caches and uninstall flows fail." = "Without it, most caches and uninstall flows fail."; @@ -160,6 +163,8 @@ "Orphaned Files (%lld)" = "Orphaned Files (%lld)"; "Remove Selected (%lld)" = "Remove Selected (%lld)"; "Some files could not be removed" = "Some files could not be removed"; +"blocked by safety policy" = "blocked by safety policy"; +"%lld item(s) failed to delete.\n\n%@" = "%lld item(s) failed to delete.\n\n%@"; /* Onboarding */ "Back" = "Back"; @@ -190,7 +195,7 @@ "PureMac can now manage protected files." = "PureMac can now manage protected files."; "You're Ready" = "You're Ready"; "%lld/%lld protected locations accessible" = "%lld/%lld protected locations accessible"; -"Some features will be limited. You can grant Full Disk Access later in System Settings." = "Some features will be limited. You can grant Full Disk Access later in System Settings."; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "Some features will be limited without Full Disk Access. You can grant it later in Settings."; "Trash" = "Trash"; "Mail Data" = "Mail Data"; "Safari Data" = "Safari Data"; @@ -199,6 +204,10 @@ "TCC Database" = "TCC Database"; "Blocked" = "Blocked"; +/* FDA demo system apps */ +"Finder" = "Finder"; +"Terminal" = "Terminal"; + /* Settings */ "General" = "General"; "Cleaning" = "Cleaning"; @@ -224,6 +233,8 @@ "Arabic" = "Arabic"; "Polish" = "Polish"; "Portuguese (Brazil)" = "Portuguese (Brazil)"; +"Russian" = "Russian"; +"Ukrainian" = "Ukrainian"; "Chinese (Simplified)" = "Chinese (Simplified)"; "Chinese (Traditional)" = "Chinese (Traditional)"; "Safety" = "Safety"; @@ -245,6 +256,8 @@ "GitHub Repository" = "GitHub Repository"; "Report an Issue" = "Report an Issue"; "MIT License" = "MIT License"; +"Updates" = "Updates"; +"Check for Updates" = "Check for Updates"; /* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "System Junk"; @@ -277,13 +290,23 @@ "Unused CPU architecture slices in app binaries" = "Unused CPU architecture slices in app binaries"; "Unused app localizations" = "Unused app localizations"; +/* AI Apps scan item names */ +"Ollama Logs" = "Ollama Logs"; +"Ollama Cache" = "Ollama Cache"; +"Ollama Electron Cache" = "Ollama Electron Cache"; +"Ollama WebKit Data" = "Ollama WebKit Data"; +"Ollama Saved State" = "Ollama Saved State"; +"Ollama CLI Prompt History (Optional)" = "Ollama CLI Prompt History (Optional)"; +"LM Studio Server Logs" = "LM Studio Server Logs"; +"LM Studio Conversations (Optional)" = "LM Studio Conversations (Optional)"; + /* Node Cache subcategories */ "npm cache" = "npm cache"; "yarn classic cache" = "yarn classic cache"; "pnpm content-addressable store" = "pnpm content-addressable store"; /* Docker Cache helper */ -"Reclaimable (run `docker system prune -af`)" = "Reclaimable (run `docker system prune -af`)"; +"Docker prune (stopped containers, dangling images, build cache)" = "Docker prune (stopped containers, dangling images, build cache)"; /* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "Every Hour"; @@ -342,7 +365,6 @@ "Free" = "Free"; /* UI polish (v2.8.0): leftover groups + sound toggle */ -"Application" = "Application"; "Caches" = "Caches"; "Application Support" = "Application Support"; "Preferences" = "Preferences"; @@ -379,3 +401,6 @@ "Disk" = "Disk"; "Open PureMac" = "Open PureMac"; "Quit PureMac" = "Quit PureMac"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "Select an application (.app) to uninstall."; diff --git a/PureMac/en.lproj/ServicesMenu.strings b/PureMac/en.lproj/ServicesMenu.strings new file mode 100644 index 0000000..98b186a --- /dev/null +++ b/PureMac/en.lproj/ServicesMenu.strings @@ -0,0 +1 @@ +"Uninstall with PureMac" = "Uninstall with PureMac"; diff --git a/PureMac/es.lproj/Localizable.strings b/PureMac/es.lproj/Localizable.strings index 8844a69..797d083 100644 --- a/PureMac/es.lproj/Localizable.strings +++ b/PureMac/es.lproj/Localizable.strings @@ -41,6 +41,7 @@ "Suggested for you" = "Sugerencias para ti"; "Found so far" = "Encontrado hasta ahora"; "By category" = "Por categoría"; +"Category" = "Categoría"; "%@ is using %@" = "%@ está usando %@"; "Grant Full Disk Access for full results" = "Concede acceso total al disco para obtener resultados completos"; "Without it, most caches and uninstall flows fail." = "Sin él, la mayoría de cachés y desinstalaciones fallan."; @@ -121,6 +122,8 @@ "Orphaned Files (%lld)" = "Archivos huérfanos (%lld)"; "Remove Selected (%lld)" = "Eliminar seleccionados (%lld)"; "Some files could not be removed" = "Algunos archivos no se pudieron eliminar"; +"blocked by safety policy" = "bloqueado por la política de seguridad"; +"%lld item(s) failed to delete.\n\n%@" = "No se pudieron eliminar %lld elemento(s).\n\n%@"; /* Onboarding */ "Back" = "Atrás"; @@ -151,7 +154,7 @@ "PureMac can now manage protected files." = "PureMac ahora puede gestionar archivos protegidos."; "You're Ready" = "Todo listo"; "%lld/%lld protected locations accessible" = "%lld/%lld ubicaciones protegidas accesibles"; -"Some features will be limited. You can grant Full Disk Access later in System Settings." = "Algunas funciones serán limitadas. Puedes conceder el acceso total al disco más tarde en Ajustes del Sistema."; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "Algunas funciones estarán limitadas sin acceso total al disco. Puedes concederlo más tarde en Ajustes."; "Trash" = "Papelera"; "Mail Data" = "Datos de Mail"; "Safari Data" = "Datos de Safari"; @@ -160,6 +163,10 @@ "TCC Database" = "Base de datos TCC"; "Blocked" = "Bloqueado"; +/* FDA demo system apps */ +"Finder" = "Finder"; +"Terminal" = "Terminal"; + /* Settings */ "General" = "General"; "Cleaning" = "Limpieza"; @@ -185,6 +192,8 @@ "Arabic" = "Árabe"; "Polish" = "Polaco"; "Portuguese (Brazil)" = "Portugués (Brasil)"; +"Russian" = "Ruso"; +"Ukrainian" = "Ucraniano"; "Chinese (Simplified)" = "Chino (simplificado)"; "Chinese (Traditional)" = "Chino (tradicional)"; "Safety" = "Seguridad"; @@ -206,6 +215,8 @@ "GitHub Repository" = "Repositorio de GitHub"; "Report an Issue" = "Informar de un problema"; "MIT License" = "Licencia MIT"; +"Updates" = "Actualizaciones"; +"Check for Updates" = "Buscar actualizaciones"; /* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "Basura del sistema"; @@ -238,13 +249,23 @@ "Unused CPU architecture slices in app binaries" = "Arquitecturas de CPU sin usar en los binarios de las apps"; "Unused app localizations" = "Localizaciones de apps sin usar"; +/* AI Apps scan item names */ +"Ollama Logs" = "Registros de Ollama"; +"Ollama Cache" = "Caché de Ollama"; +"Ollama Electron Cache" = "Caché de Electron de Ollama"; +"Ollama WebKit Data" = "Datos de WebKit de Ollama"; +"Ollama Saved State" = "Estado guardado de Ollama"; +"Ollama CLI Prompt History (Optional)" = "Historial de indicaciones de Ollama CLI (opcional)"; +"LM Studio Server Logs" = "Registros del servidor de LM Studio"; +"LM Studio Conversations (Optional)" = "Conversaciones de LM Studio (opcional)"; + /* Node Cache subcategories */ "npm cache" = "Caché de npm"; "yarn classic cache" = "Caché clásica de yarn"; "pnpm content-addressable store" = "Almacén direccionable de pnpm"; /* Docker Cache helper */ -"Reclaimable (run `docker system prune -af`)" = "Recuperable (ejecuta `docker system prune -af`)"; +"Docker prune (stopped containers, dangling images, build cache)" = "Limpieza de Docker (contenedores detenidos, imágenes sin usar y caché de compilación)"; /* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "Cada hora"; @@ -306,6 +327,8 @@ "1-tap setup. We'll auto-retry what failed." = "Configuración en un toque. Reintentaremos automáticamente lo que falle."; "Fix permission" = "Arreglar permiso"; "Dismiss" = "Descartar"; +"Fix" = "Corregir"; +"Set up" = "Configurar"; /* Onboarding v2 */ "Reclaim your Mac" = "Recupera tu Mac"; @@ -342,7 +365,6 @@ "Free" = "Libre"; /* UI polish (v2.8.0): leftover groups + sound toggle */ -"Application" = "Aplicación"; "Caches" = "Cachés"; "Application Support" = "Soporte de aplicaciones"; "Preferences" = "Preferencias"; @@ -379,3 +401,6 @@ "Disk" = "Disco"; "Open PureMac" = "Abrir PureMac"; "Quit PureMac" = "Salir de PureMac"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "Selecciona una aplicación (.app) para desinstalarla."; diff --git a/PureMac/ja.lproj/Localizable.strings b/PureMac/ja.lproj/Localizable.strings index b06b045..a398835 100644 --- a/PureMac/ja.lproj/Localizable.strings +++ b/PureMac/ja.lproj/Localizable.strings @@ -41,6 +41,7 @@ "Suggested for you" = "あなたへのおすすめ"; "Found so far" = "これまでに見つかったもの"; "By category" = "カテゴリ別"; +"Category" = "カテゴリ"; "%@ is using %@" = "%@ は %@ を使用しています"; "Grant Full Disk Access for full results" = "完全な結果を得るためにフルディスクアクセスを許可してください"; "Without it, most caches and uninstall flows fail." = "許可しないと、ほとんどのキャッシュ削除やアンインストールに失敗します。"; @@ -121,6 +122,8 @@ "Orphaned Files (%lld)" = "孤立ファイル (%lld)"; "Remove Selected (%lld)" = "選択した項目を削除 (%lld)"; "Some files could not be removed" = "一部のファイルを削除できませんでした"; +"blocked by safety policy" = "安全ポリシーによりブロック"; +"%lld item(s) failed to delete.\n\n%@" = "%lld 項目を削除できませんでした。\n\n%@"; /* Onboarding */ "Back" = "戻る"; @@ -151,7 +154,7 @@ "PureMac can now manage protected files." = "PureMacは保護されたファイルを管理できるようになりました。"; "You're Ready" = "準備完了"; "%lld/%lld protected locations accessible" = "%lld/%lld の保護された場所にアクセス可能"; -"Some features will be limited. You can grant Full Disk Access later in System Settings." = "一部の機能は制限されます。フルディスクアクセスは後でシステム設定から許可できます。"; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "フルディスクアクセスがない場合、一部の機能が制限されます。後で設定から許可できます。"; "Trash" = "ゴミ箱"; "Mail Data" = "メールデータ"; "Safari Data" = "Safariデータ"; @@ -160,6 +163,10 @@ "TCC Database" = "TCCデータベース"; "Blocked" = "ブロック"; +/* FDA demo system apps */ +"Finder" = "Finder"; +"Terminal" = "ターミナル"; + /* Settings */ "General" = "一般"; "Cleaning" = "クリーニング"; @@ -185,6 +192,8 @@ "Arabic" = "アラビア語"; "Polish" = "ポーランド語"; "Portuguese (Brazil)" = "ポルトガル語(ブラジル)"; +"Russian" = "ロシア語"; +"Ukrainian" = "ウクライナ語"; "Chinese (Simplified)" = "中国語(簡体字)"; "Chinese (Traditional)" = "中国語(繁体字)"; "Safety" = "安全"; @@ -206,6 +215,8 @@ "GitHub Repository" = "GitHubリポジトリ"; "Report an Issue" = "問題を報告"; "MIT License" = "MITライセンス"; +"Updates" = "アップデート"; +"Check for Updates" = "アップデートを確認"; /* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "システムジャンク"; @@ -238,13 +249,23 @@ "Unused CPU architecture slices in app binaries" = "アプリバイナリ内の未使用CPUアーキテクチャコード"; "Unused app localizations" = "使用されていないアプリのローカライゼーション"; +/* AI Apps scan item names */ +"Ollama Logs" = "Ollamaログ"; +"Ollama Cache" = "Ollamaキャッシュ"; +"Ollama Electron Cache" = "Ollama Electronキャッシュ"; +"Ollama WebKit Data" = "Ollama WebKitデータ"; +"Ollama Saved State" = "Ollamaの保存状態"; +"Ollama CLI Prompt History (Optional)" = "Ollama CLIプロンプト履歴(任意)"; +"LM Studio Server Logs" = "LM Studioサーバーログ"; +"LM Studio Conversations (Optional)" = "LM Studioの会話(任意)"; + /* Node Cache subcategories */ "npm cache" = "npmキャッシュ"; "yarn classic cache" = "yarnクラシックキャッシュ"; "pnpm content-addressable store" = "pnpmコンテンツアドレッサブルストア"; /* Docker Cache helper */ -"Reclaimable (run `docker system prune -af`)" = "再利用可能 ( `docker system prune -af` を実行)"; +"Docker prune (stopped containers, dangling images, build cache)" = "Docker クリーンアップ(停止中のコンテナ、未使用イメージ、ビルドキャッシュ)"; /* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "1時間ごと"; @@ -306,6 +327,8 @@ "1-tap setup. We'll auto-retry what failed." = "ワンタップ設定。失敗した処理は自動的に再試行します。"; "Fix permission" = "権限を修正"; "Dismiss" = "閉じる"; +"Fix" = "修正"; +"Set up" = "設定"; /* Onboarding v2 */ "Reclaim your Mac" = "Mac を取り戻そう"; @@ -342,7 +365,6 @@ "Free" = "空き"; /* UI polish (v2.8.0): leftover groups + sound toggle */ -"Application" = "アプリケーション"; "Caches" = "キャッシュ"; "Application Support" = "アプリケーションサポート"; "Preferences" = "環境設定"; @@ -379,3 +401,6 @@ "Disk" = "ディスク"; "Open PureMac" = "PureMacを開く"; "Quit PureMac" = "PureMacを終了"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "アンインストールするアプリケーション(.app)を選択してください。"; diff --git a/PureMac/pl.lproj/Localizable.strings b/PureMac/pl.lproj/Localizable.strings index 6950dcc..07cf4ed 100644 --- a/PureMac/pl.lproj/Localizable.strings +++ b/PureMac/pl.lproj/Localizable.strings @@ -58,6 +58,8 @@ "1-tap setup. We'll auto-retry what failed." = "Konfiguracja jednym kliknięciem. Automatycznie ponowimy to, co się nie udało."; "Fix permission" = "Napraw uprawnienie"; "Dismiss" = "Zamknij"; +"Fix" = "Napraw"; +"Set up" = "Skonfiguruj"; /* Dashboard */ "Storage" = "Pamięć masowa"; @@ -80,6 +82,7 @@ "Suggested for you" = "Sugerowane dla Ciebie"; "Found so far" = "Znalezione dotychczas"; "By category" = "Według kategorii"; +"Category" = "Kategoria"; "%@ is using %@" = "%@ zajmuje %@"; "Grant Full Disk Access for full results" = "Przyznaj pełny dostęp do dysku, aby zobaczyć pełne wyniki"; "Without it, most caches and uninstall flows fail." = "Bez niego większość czyszczenia i odinstalowywania nie działa."; @@ -160,6 +163,8 @@ "Orphaned Files (%lld)" = "Osierocone pliki (%lld)"; "Remove Selected (%lld)" = "Usuń zaznaczone (%lld)"; "Some files could not be removed" = "Niektórych plików nie udało się usunąć"; +"blocked by safety policy" = "zablokowano przez zasady bezpieczeństwa"; +"%lld item(s) failed to delete.\n\n%@" = "Nie udało się usunąć %lld elementów.\n\n%@"; /* Onboarding */ "Back" = "Wstecz"; @@ -190,7 +195,7 @@ "PureMac can now manage protected files." = "PureMac może teraz zarządzać chronionymi plikami."; "You're Ready" = "Wszystko gotowe"; "%lld/%lld protected locations accessible" = "%lld/%lld chronionych lokalizacji dostępnych"; -"Some features will be limited. You can grant Full Disk Access later in System Settings." = "Niektóre funkcje będą ograniczone. Możesz przyznać pełny dostęp do dysku później w Ustawieniach systemowych."; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "Bez pełnego dostępu do dysku niektóre funkcje będą ograniczone. Możesz przyznać go później w Ustawieniach."; "Trash" = "Kosz"; "Mail Data" = "Dane poczty"; "Safari Data" = "Dane Safari"; @@ -199,6 +204,10 @@ "TCC Database" = "Baza TCC"; "Blocked" = "Zablokowane"; +/* FDA demo system apps */ +"Finder" = "Finder"; +"Terminal" = "Terminal"; + /* Settings */ "General" = "Ogólne"; "Cleaning" = "Czyszczenie"; @@ -224,6 +233,8 @@ "Arabic" = "Arabski"; "Polish" = "Polski"; "Portuguese (Brazil)" = "Portugalski (Brazylia)"; +"Russian" = "Rosyjski"; +"Ukrainian" = "Ukraiński"; "Chinese (Simplified)" = "Chiński (uproszczony)"; "Chinese (Traditional)" = "Chiński (tradycyjny)"; "Safety" = "Bezpieczeństwo"; @@ -245,6 +256,8 @@ "GitHub Repository" = "Repozytorium GitHub"; "Report an Issue" = "Zgłoś problem"; "MIT License" = "Licencja MIT"; +"Updates" = "Aktualizacje"; +"Check for Updates" = "Sprawdź dostępność aktualizacji"; /* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "Śmieci systemowe"; @@ -277,13 +290,23 @@ "Unused CPU architecture slices in app binaries" = "Nieużywane architektury CPU w plikach binarnych aplikacji"; "Unused app localizations" = "Nieużywane lokalizacje aplikacji"; +/* AI Apps scan item names */ +"Ollama Logs" = "Dzienniki Ollama"; +"Ollama Cache" = "Pamięć podręczna Ollama"; +"Ollama Electron Cache" = "Pamięć podręczna Electron Ollama"; +"Ollama WebKit Data" = "Dane WebKit Ollama"; +"Ollama Saved State" = "Zapisany stan Ollama"; +"Ollama CLI Prompt History (Optional)" = "Historia promptów Ollama CLI (opcjonalnie)"; +"LM Studio Server Logs" = "Dzienniki serwera LM Studio"; +"LM Studio Conversations (Optional)" = "Konwersacje LM Studio (opcjonalnie)"; + /* Node Cache subcategories */ "npm cache" = "cache npm"; "yarn classic cache" = "cache yarn classic"; "pnpm content-addressable store" = "magazyn adresowany treścią pnpm"; /* Docker Cache helper */ -"Reclaimable (run `docker system prune -af`)" = "Do odzyskania (uruchom `docker system prune -af`)"; +"Docker prune (stopped containers, dangling images, build cache)" = "Czyszczenie Dockera (zatrzymane kontenery, nieużywane obrazy i pamięć podręczna kompilacji)"; /* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "Co godzinę"; @@ -378,3 +401,6 @@ "Disk" = "Dysk"; "Open PureMac" = "Otwórz PureMac"; "Quit PureMac" = "Zamknij PureMac"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "Wybierz aplikację (.app) do odinstalowania."; diff --git a/PureMac/pt-BR.lproj/Localizable.strings b/PureMac/pt-BR.lproj/Localizable.strings index e4ac453..274f32d 100644 --- a/PureMac/pt-BR.lproj/Localizable.strings +++ b/PureMac/pt-BR.lproj/Localizable.strings @@ -41,6 +41,7 @@ "Suggested for you" = "Sugestões para você"; "Found so far" = "Encontrado até agora"; "By category" = "Por categoria"; +"Category" = "Categoria"; "%@ is using %@" = "%@ está usando %@"; "Grant Full Disk Access for full results" = "Conceda acesso total ao disco para ver resultados completos"; "Without it, most caches and uninstall flows fail." = "Sem ele, a maioria dos caches e desinstalações falha."; @@ -121,6 +122,8 @@ "Orphaned Files (%lld)" = "Arquivos órfãos (%lld)"; "Remove Selected (%lld)" = "Remover selecionados (%lld)"; "Some files could not be removed" = "Alguns arquivos não puderam ser removidos"; +"blocked by safety policy" = "bloqueado pela política de segurança"; +"%lld item(s) failed to delete.\n\n%@" = "Falha ao excluir %lld item(ns).\n\n%@"; /* Onboarding */ "Back" = "Voltar"; @@ -151,7 +154,7 @@ "PureMac can now manage protected files." = "O PureMac agora pode gerenciar arquivos protegidos."; "You're Ready" = "Tudo pronto"; "%lld/%lld protected locations accessible" = "%lld/%lld locais protegidos acessíveis"; -"Some features will be limited. You can grant Full Disk Access later in System Settings." = "Alguns recursos ficarão limitados. Você pode conceder o acesso total ao disco depois em Ajustes do Sistema."; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "Alguns recursos ficarão limitados sem Acesso Total ao Disco. Você poderá concedê-lo depois nos Ajustes."; "Trash" = "Lixo"; "Mail Data" = "Dados do Mail"; "Safari Data" = "Dados do Safari"; @@ -160,6 +163,10 @@ "TCC Database" = "Banco de dados TCC"; "Blocked" = "Bloqueado"; +/* FDA demo system apps */ +"Finder" = "Finder"; +"Terminal" = "Terminal"; + /* Settings */ "General" = "Geral"; "Cleaning" = "Limpeza"; @@ -185,6 +192,8 @@ "Arabic" = "Árabe"; "Polish" = "Polonês"; "Portuguese (Brazil)" = "Português (Brasil)"; +"Russian" = "Russo"; +"Ukrainian" = "Ucraniano"; "Chinese (Simplified)" = "Chinês (simplificado)"; "Chinese (Traditional)" = "Chinês (tradicional)"; "Safety" = "Segurança"; @@ -206,6 +215,8 @@ "GitHub Repository" = "Repositório no GitHub"; "Report an Issue" = "Relatar um problema"; "MIT License" = "Licença MIT"; +"Updates" = "Atualizações"; +"Check for Updates" = "Buscar atualizações"; /* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "Lixo do sistema"; @@ -238,13 +249,23 @@ "Unused CPU architecture slices in app binaries" = "Arquiteturas de CPU não usadas nos binários dos apps"; "Unused app localizations" = "Localizações de apps não usadas"; +/* AI Apps scan item names */ +"Ollama Logs" = "Logs do Ollama"; +"Ollama Cache" = "Cache do Ollama"; +"Ollama Electron Cache" = "Cache do Electron do Ollama"; +"Ollama WebKit Data" = "Dados do WebKit do Ollama"; +"Ollama Saved State" = "Estado salvo do Ollama"; +"Ollama CLI Prompt History (Optional)" = "Histórico de prompts do Ollama CLI (opcional)"; +"LM Studio Server Logs" = "Logs do servidor do LM Studio"; +"LM Studio Conversations (Optional)" = "Conversas do LM Studio (opcional)"; + /* Node Cache subcategories */ "npm cache" = "Cache do npm"; "yarn classic cache" = "Cache clássico do yarn"; "pnpm content-addressable store" = "Repositório endereçável por conteúdo do pnpm"; /* Docker Cache helper */ -"Reclaimable (run `docker system prune -af`)" = "Recuperável (execute `docker system prune -af`)"; +"Docker prune (stopped containers, dangling images, build cache)" = "Limpeza do Docker (contêineres parados, imagens não utilizadas e cache de compilação)"; /* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "A cada hora"; @@ -306,6 +327,8 @@ "1-tap setup. We'll auto-retry what failed." = "Configuração em 1 toque. Tentaremos de novo o que falhar."; "Fix permission" = "Corrigir permissão"; "Dismiss" = "Dispensar"; +"Fix" = "Corrigir"; +"Set up" = "Configurar"; /* Onboarding v2 */ "Reclaim your Mac" = "Recupere seu Mac"; @@ -342,7 +365,6 @@ "Free" = "Livre"; /* UI polish (v2.8.0): leftover groups + sound toggle */ -"Application" = "Aplicativo"; "Caches" = "Caches"; "Application Support" = "Suporte de aplicativos"; "Preferences" = "Preferências"; @@ -379,3 +401,6 @@ "Disk" = "Disco"; "Open PureMac" = "Abrir o PureMac"; "Quit PureMac" = "Encerrar o PureMac"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "Selecione um aplicativo (.app) para desinstalar."; diff --git a/PureMac/ru.lproj/InfoPlist.strings b/PureMac/ru.lproj/InfoPlist.strings new file mode 100644 index 0000000..11bafad --- /dev/null +++ b/PureMac/ru.lproj/InfoPlist.strings @@ -0,0 +1,8 @@ +/* macOS permission prompts */ +"NSAppleEventsUsageDescription" = "PureMac использует Apple Events для взаимодействия с Finder при показе файлов."; +"NSDesktopFolderUsageDescription" = "PureMac требуется доступ к папке «Рабочий стол», чтобы находить мусорные и остаточные файлы."; +"NSDocumentsFolderUsageDescription" = "PureMac требуется доступ к папке «Документы», чтобы находить мусорные и остаточные файлы."; +"NSDownloadsFolderUsageDescription" = "PureMac требуется доступ к папке «Загрузки», чтобы находить мусорные и остаточные файлы."; +"NSRemovableVolumesUsageDescription" = "PureMac требуется доступ к съёмным томам для поиска мусорных файлов."; +"NSSystemAdministrationUsageDescription" = "PureMac требуются права администратора, чтобы полностью очищать кэши и удалять приложения."; +"NSHumanReadableCopyright" = "© 2026. Лицензия MIT."; diff --git a/PureMac/ru.lproj/Localizable.strings b/PureMac/ru.lproj/Localizable.strings new file mode 100644 index 0000000..e121583 --- /dev/null +++ b/PureMac/ru.lproj/Localizable.strings @@ -0,0 +1,406 @@ +/* Sidebar (MainWindow) */ +"Overview" = "Обзор"; +"Applications" = "Приложения"; +"Cleanup" = "Очистка"; +"Dashboard" = "Сводка"; +"Installed Apps" = "Установленные приложения"; +"Orphaned Files" = "Остаточные файлы"; +"PureMac" = "PureMac"; +"Ready to clean" = "Готово к очистке"; +"Limited access" = "Ограниченный доступ"; +"Full Disk Access granted" = "Полный доступ к диску включён"; +"Grant FDA in Settings" = "Включите доступ в Настройках"; +"Select a category from the sidebar to get started." = "Чтобы начать, выберите категорию в боковом меню."; + +/* FDA toast + clean error alert (MainWindow) */ +"Couldn't clean everything" = "Не удалось завершить очистку"; +"Open System Settings" = "Открыть Системные настройки"; +"OK" = "ОК"; +"Full Disk Access required" = "Требуется полный доступ к диску"; +"macOS blocks PureMac from cleaning caches and uninstalling apps until you grant access." = "macOS не позволяет PureMac очищать кэши и удалять приложения, пока вы не предоставите доступ."; +"Grant Access" = "Предоставить доступ"; + +/* Permission Sheet */ +"Grant Full Disk Access" = "Предоставьте полный доступ к диску"; +"%lld item(s) need Full Disk Access" = "Требуется полный доступ к диску · объектов: %lld"; +"Uninstalling %@: %lld file(s) need Full Disk Access" = "Удаление %@: полный доступ нужен для файлов (%lld)"; +"%lld item(s) need Full Disk Access to remove. Tap Grant Access to fix in one step." = "Для удаления нужен полный доступ к диску. Объектов: %lld. Нажмите «Предоставить доступ», чтобы всё исправить за один шаг."; +"Couldn't remove %@%@. They may be in use or protected by macOS." = "Не удалось удалить %@%@. Возможно, они используются или защищены macOS."; +" and %lld more" = " и ещё %lld"; +"Access granted. Retrying…" = "Доступ предоставлен. Повторная попытка…"; +"1-tap setup. We'll detect the change automatically." = "Настройка одним нажатием. Изменение будет обнаружено автоматически."; +"Open Settings & reveal PureMac" = "Открыть Настройки и показать PureMac в Finder"; +"We'll open both windows side-by-side." = "Оба окна откроются рядом."; +"Turn on PureMac" = "Включите PureMac"; +"Toggle the row that appears." = "Включите переключатель в появившейся строке."; +"Authenticate" = "Подтвердите действие"; +"Touch ID or password." = "С помощью Touch ID или пароля."; +"We auto-retry — no need to come back." = "Повторная попытка выполнится автоматически — возвращаться сюда не нужно."; +"Watching for permission change…" = "Ожидание изменения разрешения…"; +"PureMac not in the list?" = "PureMac нет в списке?"; +"Hide help" = "Скрыть подсказку"; +"Try this if PureMac doesn't appear:" = "Если PureMac не появляется, попробуйте следующее:"; +"Reveal app" = "Показать в Finder"; +"Drag PureMac.app into the list" = "Перетащите PureMac.app в список"; +"Reset + reprompt" = "Сбросить и запросить заново"; +"Clear stale TCC entry" = "Удалить устаревшую запись TCC"; +"+ %lld more" = "+ ещё %lld"; +"%lld blocked path(s)" = "Недоступных путей: %lld"; +"Access granted" = "Доступ предоставлен"; +"Retrying the operation now." = "Повторяем операцию…"; +"Skip for now" = "Не сейчас"; +"I granted it — retry" = "Проверить доступ и повторить"; + +/* Dashboard polish */ +"Low space" = "Мало места"; +"CLEANING" = "ОЧИСТКА"; +"Quick Setup" = "Быстрая настройка"; +"1-tap setup. We'll auto-retry what failed." = "Настройка одним нажатием. Неудавшаяся операция повторится автоматически."; +"Fix permission" = "Настроить доступ"; +"Dismiss" = "Закрыть"; +"Fix" = "Настроить"; +"Set up" = "Настроить"; + +/* Dashboard */ +"Storage" = "Хранилище"; +"Smart Scan" = "Умное сканирование"; +"free of %@" = "свободно из %@"; +"%lld%% used" = "занято %lld%%"; +"Used" = "Занято"; +"Junk" = "Мусор"; +"Purgeable" = "Очищаемое"; +"USED" = "ЗАНЯТО"; +"SCANNING" = "СКАНИРОВАНИЕ"; +"Free Space" = "Свободное место"; +"Junk Found" = "Найдено мусора"; +"Apps" = "Приложения"; +"of %@ · %lld%% used" = "из %@ · занято %lld%%"; +"Run a scan" = "Запустить сканирование"; +"across %lld categories" = "Категорий: %lld"; +"installed" = "установлено"; +"APFS reclaimable" = "можно освободить в APFS"; +"Suggested for you" = "Рекомендации"; +"Found so far" = "Уже найдено"; +"By category" = "По категориям"; +"Category" = "Категория"; +"%@ is using %@" = "%@ занимает %@"; +"Grant Full Disk Access for full results" = "Предоставьте полный доступ к диску, чтобы получить полные результаты"; +"Without it, most caches and uninstall flows fail." = "Без него большую часть кэшей не удастся очистить, а приложения — удалить полностью."; +"Action" = "Требуется действие"; +"Scanning your Mac" = "Сканирование Mac"; +"Currently in: %@" = "Категория: %@"; +"found" = "найдено"; +"Your Mac is clean" = "На вашем Mac чисто"; +"Scan Again" = "Сканировать снова"; +"Clean %@" = "Очистить %@"; +"Clean %@?" = "Очистить %@?"; +"Cleaning…" = "Очистка…"; +"%lld%% complete" = "завершено на %lld%%"; +"freed" = "освобождено"; +"Done" = "Готово"; +"%lld items" = "Объектов: %lld"; + +/* Common destructive dialog */ +"Clean" = "Очистить"; +"Cancel" = "Отмена"; +"This will permanently delete the selected files. This cannot be undone." = "Выбранные файлы будут удалены без возможности восстановления. Это действие нельзя отменить."; + +/* Category Detail */ +"All Clean" = "Всё чисто"; +"No junk files found in this category." = "В этой категории мусорных файлов не найдено."; +"Not Scanned" = "Сканирование не выполнено"; +"Run a scan to analyze this category." = "Запустите сканирование, чтобы проанализировать эту категорию."; +"Scan Now" = "Сканировать"; +"Filter files" = "Фильтр файлов"; +"Scan" = "Сканировать"; +"Select All" = "Выбрать все"; +"Deselect All" = "Снять выделение"; +"Largest First" = "Сначала самые большие"; +"Smallest First" = "Сначала самые маленькие"; +"Sorted: Largest First" = "Сортировка: сначала самые большие"; +"Sorted: Smallest First" = "Сортировка: сначала самые маленькие"; +"Clean %lld items" = "Очистить выбранное (%lld)"; +"%lld items · %@" = "Объектов: %lld · %@"; +"Scanning…" = "Сканирование…"; +"Rescan" = "Сканировать снова"; +"%lld of %lld selected" = "Выбрано: %lld из %lld"; +"Reveal in Finder" = "Показать в Finder"; +"Copy Path" = "Скопировать путь"; +"Move to Trash" = "Переместить в Корзину"; + +/* Apps (AppListView) */ +"Search apps" = "Поиск приложений"; +"Refresh" = "Обновить"; +"Installed Apps (%lld)" = "Установленные приложения (%lld)"; +"Uninstall (%lld files)" = "Удалить приложение (%lld)"; +"Loading installed apps..." = "Загрузка установленных приложений…"; +"No Apps Found" = "Приложения не найдены"; +"Could not find any installed applications." = "Не удалось найти установленные приложения."; +"Retry" = "Повторить"; +"Application" = "Приложение"; +"Size" = "Размер"; +"Select an App" = "Выберите приложение"; +"Select an app from the list to see all its related files across your system." = "Выберите приложение в списке, чтобы увидеть все связанные с ним файлы в системе."; + +/* App Files (AppFilesView) */ +"%lld files" = "Файлов: %lld"; +"Scanning for related files..." = "Поиск связанных файлов…"; +"Checking %lld locations..." = "Проверка расположений (%lld)…"; +"No Related Files" = "Связанные файлы не найдены"; +"No additional files found for %@." = "Дополнительные файлы для %@ не найдены."; +"Remove %lld files (%@)" = "Удалить выбранное (%lld, %@)"; +"Removal Failed" = "Не удалось удалить"; +"Remove this file" = "Удалить этот файл"; +"Remove %@?" = "Удалить %@?"; +"Remove" = "Удалить"; +"This will permanently delete this file. This action cannot be undone." = "Этот файл будет удалён без возможности восстановления. Это действие нельзя отменить."; + +/* Orphans */ +"Scanning for orphaned files..." = "Поиск остаточных файлов…"; +"No Orphaned Files" = "Остаточные файлы не найдены"; +"No leftover files from uninstalled apps were found." = "Файлы, оставшиеся после удаления приложений, не найдены."; +"Scan for Orphans" = "Найти остаточные файлы"; +"Orphaned Files (%lld)" = "Остаточные файлы (%lld)"; +"Remove Selected (%lld)" = "Удалить выбранное (%lld)"; +"Some files could not be removed" = "Некоторые файлы не удалось удалить"; +"blocked by safety policy" = "заблокировано политикой безопасности"; +"%lld item(s) failed to delete.\n\n%@" = "Не удалось удалить %lld объект(ов).\n\n%@"; + +/* Onboarding */ +"Back" = "Назад"; +"Next" = "Далее"; +"Get Started" = "Начать"; +"Welcome to PureMac" = "Добро пожаловать в PureMac"; +"Free, open-source macOS app manager and system cleaner." = "Бесплатный менеджер приложений и средство очистки системы для macOS с открытым исходным кодом."; +"Find junk files across your system" = "Находите мусорные файлы во всей системе"; +"App Uninstaller" = "Удаление приложений"; +"Remove apps and all their files" = "Удаляйте приложения и все их файлы"; +"Orphan Finder" = "Поиск остаточных файлов"; +"Find leftovers from deleted apps" = "Находите файлы, оставшиеся после удаления приложений"; +"Full Disk Access" = "Полный доступ к диску"; +"PureMac needs Full Disk Access to uninstall apps, find leftover files, and clean protected caches." = "PureMac нужен полный доступ к диску, чтобы удалять приложения, находить остаточные файлы и очищать защищённые кэши."; +"We'll guide you through the next steps." = "Мы поможем вам выполнить следующие шаги."; +"In System Settings, do this:" = "В Системных настройках выполните следующее:"; +"Privacy & Security → Full Disk Access" = "Конфиденциальность и безопасность → Полный доступ к диску"; +"Find **PureMac** and turn the toggle on" = "Найдите **PureMac** и включите переключатель"; +"Authenticate with Touch ID or your password" = "Подтвердите действие с помощью Touch ID или пароля"; +"Reopen Settings" = "Снова открыть Настройки"; +"PureMac isn't in the list — reveal it" = "PureMac нет в списке — показать в Finder"; +"Reset permissions and re-prompt" = "Сбросить разрешения и запросить заново"; +"Hide diagnostics" = "Скрыть диагностику"; +"Show diagnostics" = "Показать диагностику"; +"Trouble?" = "Возникли трудности?"; +"Waiting for permission…" = "Ожидание разрешения…"; +"Permission granted." = "Разрешение предоставлено."; +"PureMac can now manage protected files." = "Теперь PureMac может управлять защищёнными файлами."; +"You're Ready" = "Всё готово"; +"%lld/%lld protected locations accessible" = "Доступно защищённых расположений: %lld/%lld"; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "Без полного доступа к диску некоторые функции будут ограничены. Его можно предоставить позже в настройках."; +"Trash" = "Корзина"; +"Mail Data" = "Данные Почты"; +"Safari Data" = "Данные Safari"; +"Desktop" = "Рабочий стол"; +"Documents" = "Документы"; +"TCC Database" = "База данных TCC"; +"Blocked" = "Недоступно"; + +/* FDA demo system apps */ +"Finder" = "Finder"; +"Terminal" = "Терминал"; + +/* Settings */ +"General" = "Основные"; +"Cleaning" = "Очистка"; +"Schedule" = "Расписание"; +"About" = "О приложении"; +"Startup" = "Запуск"; +"Launch PureMac at login" = "Открывать PureMac при входе"; +"App Scanning" = "Сканирование приложений"; +"Search sensitivity" = "Чувствительность поиска"; +"Strict" = "Строгий"; +"Enhanced" = "Расширенный"; +"Deep" = "Глубокий"; +"Exact bundle ID and name matches only. Safest option." = "Только точные совпадения идентификатора пакета и названия. Самый безопасный вариант."; +"Includes partial name matching and bundle ID components." = "Включает частичные совпадения названия и компонентов идентификатора пакета."; +"Includes company name, entitlements, and team identifier matching." = "Учитывает название компании, права доступа и идентификатор команды."; +"Language" = "Язык"; +"Restart PureMac to apply the selected language." = "Перезапустите PureMac, чтобы применить выбранный язык."; +"Relaunch Now" = "Перезапустить сейчас"; +"System Default" = "Как в системе"; +"English" = "Английский"; +"Spanish" = "Испанский"; +"Japanese" = "Японский"; +"Arabic" = "Арабский"; +"Polish" = "Польский"; +"Portuguese (Brazil)" = "Португальский (Бразилия)"; +"Russian" = "Русский"; +"Ukrainian" = "Украинский"; +"Chinese (Simplified)" = "Китайский (упрощённый)"; +"Chinese (Traditional)" = "Китайский (традиционный)"; +"Safety" = "Безопасность"; +"Confirm before deleting files" = "Подтверждать удаление файлов"; +"File Discovery" = "Поиск файлов"; +"Skip hidden files during scan" = "Пропускать скрытые файлы при сканировании"; +"Large Files" = "Большие файлы"; +"Minimum size: %lld MB" = "Минимальный размер: %lld МБ"; +"Files older than: %lld months" = "Возраст файлов: более %lld мес."; +"Automatic Scanning" = "Автоматическое сканирование"; +"Enable scheduled scanning" = "Включить сканирование по расписанию"; +"Scan interval" = "Интервал сканирования"; +"Auto-clean after scan" = "Автоматически очищать после сканирования"; +"Auto-purge purgeable space" = "Автоматически освобождать очищаемое пространство"; +"Notify on completion" = "Уведомлять о завершении"; +"Last run" = "Последний запуск"; +"Version %@" = "Версия %@"; +"Free, open-source macOS app manager." = "Бесплатный менеджер приложений для macOS с открытым исходным кодом."; +"GitHub Repository" = "Репозиторий GitHub"; +"Report an Issue" = "Сообщить о проблеме"; +"MIT License" = "Лицензия MIT"; +"Updates" = "Обновления"; +"Check for Updates" = "Проверить наличие обновлений"; + +/* Cleaning categories (CleaningCategory.rawValue) */ +"System Junk" = "Системный мусор"; +"User Cache" = "Пользовательский кэш"; +"AI Apps" = "ИИ-приложения"; +"Mail Files" = "Файлы Почты"; +"Trash Bins" = "Корзины"; +"Large & Old Files" = "Большие и старые файлы"; +"Purgeable Space" = "Очищаемое пространство"; +"Xcode Junk" = "Мусор Xcode"; +"Brew Cache" = "Кэш Homebrew"; +"Node Cache" = "Кэш Node.js"; +"Docker Cache" = "Кэш Docker"; +"Universal Binaries" = "Универсальные бинарные файлы"; +"Language Files" = "Языковые файлы"; + +/* Cleaning category descriptions */ +"Scan everything at once" = "Сканирование всех категорий сразу"; +"System caches, logs, and temporary files" = "Системные кэши, журналы и временные файлы"; +"Application caches and browser data" = "Кэши приложений и данные браузеров"; +"Local AI app logs, caches, and optional history" = "Локальные журналы, кэши и необязательная история ИИ-приложений"; +"Downloaded mail attachments" = "Загруженные почтовые вложения"; +"Files in your Trash" = "Файлы в Корзине"; +"Files over 100 MB or older than 1 year" = "Файлы размером более 100 МБ или старше 1 года"; +"APFS purgeable disk space" = "Очищаемое пространство на диске APFS"; +"Derived data, archives, and simulators" = "Производные данные, архивы и симуляторы"; +"Homebrew download cache" = "Кэш загрузок Homebrew"; +"npm, yarn, and pnpm download caches" = "Кэши загрузок npm, yarn и pnpm"; +"Docker images, containers, and build cache" = "Образы и контейнеры Docker, а также кэш сборки"; +"Unused CPU architecture slices in app binaries" = "Неиспользуемые архитектуры процессора в бинарных файлах приложений"; +"Unused app localizations" = "Неиспользуемые локализации приложений"; + +/* AI Apps scan item names */ +"Ollama Logs" = "Журналы Ollama"; +"Ollama Cache" = "Кэш Ollama"; +"Ollama Electron Cache" = "Кэш Electron в Ollama"; +"Ollama WebKit Data" = "Данные WebKit в Ollama"; +"Ollama Saved State" = "Сохранённое состояние Ollama"; +"Ollama CLI Prompt History (Optional)" = "История запросов Ollama CLI (необязательно)"; +"LM Studio Server Logs" = "Журналы сервера LM Studio"; +"LM Studio Conversations (Optional)" = "Диалоги LM Studio (необязательно)"; + +/* Node Cache subcategories */ +"npm cache" = "Кэш npm"; +"yarn classic cache" = "Классический кэш yarn"; +"pnpm content-addressable store" = "Хранилище pnpm с адресацией по содержимому"; + +/* Docker Cache helper */ +"Docker prune (stopped containers, dangling images, build cache)" = "Очистка Docker (остановленные контейнеры, непривязанные образы и кэш сборки)"; + +/* Schedule intervals (ScheduleInterval.rawValue) */ +"Every Hour" = "Каждый час"; +"Every 3 Hours" = "Каждые 3 часа"; +"Every 6 Hours" = "Каждые 6 часов"; +"Every 12 Hours" = "Каждые 12 часов"; +"Daily" = "Ежедневно"; +"Weekly" = "Еженедельно"; +"Every 2 Weeks" = "Раз в 2 недели"; +"Monthly" = "Ежемесячно"; + +/* Schedule status */ +"Never" = "Никогда"; +"Not scheduled" = "Не запланировано"; + +/* Appearance modes (AppearanceMode.label) */ +"System" = "Как в системе"; +"Light" = "Светлая тема"; +"Dark" = "Тёмная тема"; + +/* Notifications */ +"Found %@ of junk files." = "Найдено мусорных файлов на %@."; + +/* Onboarding v2 */ +"Reclaim your Mac" = "Освободите место на Mac"; +"Apple sells you small disks. We help you keep them clean." = "Apple продаёт Mac с небольшими накопителями. Мы помогаем поддерживать на них порядок."; +"Free. Open source. MIT licensed." = "Бесплатно. Открытый исходный код. Лицензия MIT."; +"What's inside" = "Что внутри"; +"Three things, done well." = "Три задачи, выполненные как следует."; +"Find caches, logs, broken installs, and the AI-app history hiding in your library." = "Находит кэши, журналы, следы неудачных установок и скрытую в вашей библиотеке историю ИИ-приложений."; +"Drag an app, see every file it dropped, remove all of it. No leftovers." = "Перетащите приложение, просмотрите все созданные им файлы и удалите их полностью. Никаких остатков."; +"Surfaces files that outlived the apps that created them." = "Находит файлы, оставшиеся после удаления создавших их приложений."; +"One permission, then we're done" = "Одно разрешение — и всё готово"; +"Permission granted" = "Разрешение предоставлено"; +"PureMac can now reach the locations macOS protects by default." = "Теперь PureMac может обращаться к папкам, которые macOS защищает по умолчанию."; +"macOS hides certain folders from every app until you say otherwise. We need them to find caches and uninstall cleanly." = "macOS скрывает некоторые папки от всех приложений, пока вы явно не разрешите доступ. Он нужен, чтобы находить кэши и полностью удалять приложения."; +"All set — moving you to the next step." = "Всё готово — переходим к следующему шагу."; +"Reveal app again" = "Снова показать в Finder"; +"Reset permissions" = "Сбросить разрешения"; +"You're ready" = "Всё готово"; +"Ready when you are" = "Можно начинать"; +"Hit Start to run your first Smart Scan." = "Нажмите «Начать», чтобы запустить первое умное сканирование."; +"Skip" = "Пропустить"; +"Start" = "Начать"; +"Continue" = "Продолжить"; + +/* Drag handle */ +"Drag to the Settings list" = "Перетащите в список в Настройках"; +"Drag this icon into the Full Disk Access list in System Settings." = "Перетащите этот значок в список «Полный доступ к диску» в Системных настройках."; +"Tip: drag the icon on the left straight into the list." = "Совет: перетащите значок слева прямо в список."; +"Or drag the icon on the left straight into Settings." = "Или перетащите значок слева прямо в Настройки."; + +/* Dashboard storage composition (v2.7.0) */ +"Storage composition" = "Состав хранилища"; +"total" = "всего"; +"Free" = "Свободно"; + +/* UI polish (v2.8.0): leftover groups + sound toggle */ +"Caches" = "Кэши"; +"Application Support" = "Поддержка приложений"; +"Preferences" = "Настройки"; +"Logs" = "Журналы"; +"Containers" = "Контейнеры"; +"Launch Agents" = "Агенты запуска"; +"Other Files" = "Другие файлы"; +"Sound" = "Звук"; +"Play sound effects" = "Воспроизводить звуковые эффекты"; + +/* v2.8.1: purgeable honesty */ +"Managed by macOS" = "Управляется macOS"; +"Reserved by macOS - freed automatically when space is needed" = "Зарезервировано macOS — освобождается автоматически при нехватке места"; + +/* v2.8.2: large-file folder exclusions (#121) */ +"Excluded Folders" = "Исключённые папки"; +"Files inside these folders are skipped from the Large & Old Files scan (Downloads, Documents, Desktop)." = "Файлы в этих папках пропускаются при поиске больших и старых файлов («Загрузки», «Документы», «Рабочий стол»)."; +"Remove from exclusions" = "Убрать из исключений"; +"Add Folder…" = "Добавить папку…"; + +/* v2.8.2: orphan ignore list (#114) */ +"Always Ignore" = "Всегда игнорировать"; +"Ignore Selected (%lld)" = "Игнорировать выбранное (%lld)"; +"Ignored orphans: %lld" = "Игнорируемых остаточных файлов: %lld"; +"Forget Ignored" = "Очистить список"; +"Ignored files won't appear in future orphan scans." = "Игнорируемые файлы не будут появляться при последующем поиске остаточных файлов."; + +/* v2.8.3: menu-bar system monitor */ +"System Monitor" = "Системный монитор"; +"Show system monitor in menu bar" = "Показывать системный монитор в строке меню"; +"Live CPU, memory, and disk meters in the menu bar. PureMac keeps running in the background while this is on." = "Индикаторы процессора, памяти и диска в строке меню в реальном времени. Пока они включены, PureMac продолжает работать в фоновом режиме."; +"CPU" = "Процессор"; +"Memory" = "Память"; +"Disk" = "Диск"; +"Open PureMac" = "Открыть PureMac"; +"Quit PureMac" = "Завершить PureMac"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "Выберите приложение (.app) для удаления."; diff --git a/PureMac/ru.lproj/ServicesMenu.strings b/PureMac/ru.lproj/ServicesMenu.strings new file mode 100644 index 0000000..13d941c --- /dev/null +++ b/PureMac/ru.lproj/ServicesMenu.strings @@ -0,0 +1 @@ +"Uninstall with PureMac" = "Удалить приложение с помощью PureMac"; diff --git a/PureMac/uk.lproj/InfoPlist.strings b/PureMac/uk.lproj/InfoPlist.strings new file mode 100644 index 0000000..30bc5bc --- /dev/null +++ b/PureMac/uk.lproj/InfoPlist.strings @@ -0,0 +1,8 @@ +/* macOS permission prompts */ +"NSAppleEventsUsageDescription" = "PureMac використовує Apple Events для взаємодії з Finder під час показу файлів."; +"NSDesktopFolderUsageDescription" = "PureMac потрібен доступ до папки «Робочий стіл», щоб знаходити непотрібні й залишкові файли."; +"NSDocumentsFolderUsageDescription" = "PureMac потрібен доступ до папки «Документи», щоб знаходити непотрібні й залишкові файли."; +"NSDownloadsFolderUsageDescription" = "PureMac потрібен доступ до папки «Завантаження», щоб знаходити непотрібні й залишкові файли."; +"NSRemovableVolumesUsageDescription" = "PureMac потрібен доступ до знімних томів для пошуку непотрібних файлів."; +"NSSystemAdministrationUsageDescription" = "PureMac потрібні права адміністратора, щоб повністю очищати кеші та видаляти програми."; +"NSHumanReadableCopyright" = "© 2026. Ліцензія MIT."; diff --git a/PureMac/uk.lproj/Localizable.strings b/PureMac/uk.lproj/Localizable.strings new file mode 100644 index 0000000..c19e4bf --- /dev/null +++ b/PureMac/uk.lproj/Localizable.strings @@ -0,0 +1,406 @@ +/* Sidebar (MainWindow) */ +"Overview" = "Огляд"; +"Applications" = "Програми"; +"Cleanup" = "Очищення"; +"Dashboard" = "Головна панель"; +"Installed Apps" = "Встановлені програми"; +"Orphaned Files" = "Залишкові файли"; +"PureMac" = "PureMac"; +"Ready to clean" = "Готово до очищення"; +"Limited access" = "Обмежений доступ"; +"Full Disk Access granted" = "Надано повний доступ до диска"; +"Grant FDA in Settings" = "Надати доступ у Системних параметрах"; +"Select a category from the sidebar to get started." = "Щоб почати, виберіть категорію на бічній панелі."; + +/* FDA toast + clean error alert (MainWindow) */ +"Couldn't clean everything" = "Не вдалося очистити все"; +"Open System Settings" = "Відкрити Системні параметри"; +"OK" = "Гаразд"; +"Full Disk Access required" = "Потрібен повний доступ до диска"; +"macOS blocks PureMac from cleaning caches and uninstalling apps until you grant access." = "macOS не дозволяє PureMac очищати кеші та видаляти програми, доки ви не надасте доступ."; +"Grant Access" = "Надати доступ"; + +/* Permission Sheet */ +"Grant Full Disk Access" = "Надати повний доступ до диска"; +"%lld item(s) need Full Disk Access" = "Об’єкти, яким потрібен повний доступ до диска: %lld"; +"Uninstalling %@: %lld file(s) need Full Disk Access" = "Видалення %@. Файли, яким потрібен повний доступ до диска: %lld"; +"%lld item(s) need Full Disk Access to remove. Tap Grant Access to fix in one step." = "Об’єкти для видалення, яким потрібен повний доступ до диска: %lld. Натисніть «Надати доступ», щоб виправити це за один крок."; +"Couldn't remove %@%@. They may be in use or protected by macOS." = "Не вдалося видалити %@%@. Можливо, вони використовуються або захищені macOS."; +" and %lld more" = " і ще %lld"; +"Access granted. Retrying…" = "Доступ надано. Повторюємо спробу…"; +"1-tap setup. We'll detect the change automatically." = "Налаштування одним натисканням. Зміну буде виявлено автоматично."; +"Open Settings & reveal PureMac" = "Відкрити Параметри й показати PureMac"; +"We'll open both windows side-by-side." = "Ми відкриємо обидва вікна поруч."; +"Turn on PureMac" = "Увімкніть PureMac"; +"Toggle the row that appears." = "Увімкніть перемикач у рядку, що з’явиться."; +"Authenticate" = "Підтвердьте особу"; +"Touch ID or password." = "За допомогою Touch ID або пароля."; +"We auto-retry — no need to come back." = "Ми повторимо спробу автоматично — повертатися не потрібно."; +"Watching for permission change…" = "Очікуємо зміни дозволу…"; +"PureMac not in the list?" = "PureMac немає у списку?"; +"Hide help" = "Сховати довідку"; +"Try this if PureMac doesn't appear:" = "Якщо PureMac не з’являється, спробуйте таке:"; +"Reveal app" = "Показати у Finder"; +"Drag PureMac.app into the list" = "Перетягніть PureMac.app до списку"; +"Reset + reprompt" = "Скинути й запитати знову"; +"Clear stale TCC entry" = "Видалити застарілий запис TCC"; +"+ %lld more" = "+ ще %lld"; +"%lld blocked path(s)" = "Заблокованих шляхів: %lld"; +"Access granted" = "Доступ надано"; +"Retrying the operation now." = "Зараз повторюємо операцію."; +"Skip for now" = "Поки що пропустити"; +"I granted it — retry" = "Доступ надано — повторити"; + +/* Dashboard polish */ +"Low space" = "Мало місця"; +"CLEANING" = "ОЧИЩЕННЯ"; +"Quick Setup" = "Швидке налаштування"; +"1-tap setup. We'll auto-retry what failed." = "Налаштування одним натисканням. Невдалу операцію буде повторено автоматично."; +"Fix permission" = "Надати доступ"; +"Dismiss" = "Закрити"; +"Fix" = "Виправити"; +"Set up" = "Налаштувати"; + +/* Dashboard */ +"Storage" = "Сховище"; +"Smart Scan" = "Розумне сканування"; +"free of %@" = "вільно із %@"; +"%lld%% used" = "використано %lld%%"; +"Used" = "Використано"; +"Junk" = "Сміття"; +"Purgeable" = "Можна звільнити"; +"USED" = "ВИКОРИСТАНО"; +"SCANNING" = "СКАНУВАННЯ"; +"Free Space" = "Вільне місце"; +"Junk Found" = "Знайдено зайве"; +"Apps" = "Програми"; +"of %@ · %lld%% used" = "усього %@ · використано %lld%%"; +"Run a scan" = "Запустити сканування"; +"across %lld categories" = "Категорій: %lld"; +"installed" = "встановлено"; +"APFS reclaimable" = "можна звільнити в APFS"; +"Suggested for you" = "Рекомендовано для вас"; +"Found so far" = "Знайдено наразі"; +"By category" = "За категоріями"; +"Category" = "Категорія"; +"%@ is using %@" = "%@ займає %@"; +"Grant Full Disk Access for full results" = "Надайте повний доступ до диска, щоб отримати повні результати"; +"Without it, most caches and uninstall flows fail." = "Без нього більшість кешів неможливо очистити, а програми — видалити."; +"Action" = "Дія"; +"Scanning your Mac" = "Скануємо ваш Mac"; +"Currently in: %@" = "Зараз: %@"; +"found" = "знайдено"; +"Your Mac is clean" = "На вашому Mac чисто"; +"Scan Again" = "Сканувати знову"; +"Clean %@" = "Очистити %@"; +"Clean %@?" = "Очистити %@?"; +"Cleaning…" = "Очищення…"; +"%lld%% complete" = "Виконано %lld%%"; +"freed" = "звільнено"; +"Done" = "Готово"; +"%lld items" = "Об’єктів: %lld"; + +/* Common destructive dialog */ +"Clean" = "Очистити"; +"Cancel" = "Скасувати"; +"This will permanently delete the selected files. This cannot be undone." = "Вибрані файли буде видалено назавжди. Цю дію неможливо скасувати."; + +/* Category Detail */ +"All Clean" = "Усе чисто"; +"No junk files found in this category." = "У цій категорії не знайдено непотрібних файлів."; +"Not Scanned" = "Не скановано"; +"Run a scan to analyze this category." = "Запустіть сканування, щоб проаналізувати цю категорію."; +"Scan Now" = "Сканувати зараз"; +"Filter files" = "Пошук файлів"; +"Scan" = "Сканувати"; +"Select All" = "Вибрати все"; +"Deselect All" = "Скасувати вибір"; +"Largest First" = "Спочатку найбільші"; +"Smallest First" = "Спочатку найменші"; +"Sorted: Largest First" = "Сортування: спочатку найбільші"; +"Sorted: Smallest First" = "Сортування: спочатку найменші"; +"Clean %lld items" = "Очистити вибране (%lld)"; +"%lld items · %@" = "Об’єктів: %lld · %@"; +"Scanning…" = "Сканування…"; +"Rescan" = "Сканувати повторно"; +"%lld of %lld selected" = "Вибрано %lld з %lld"; +"Reveal in Finder" = "Показати у Finder"; +"Copy Path" = "Копіювати шлях"; +"Move to Trash" = "Перемістити в Смітник"; + +/* Apps (AppListView) */ +"Search apps" = "Пошук програм"; +"Refresh" = "Оновити"; +"Installed Apps (%lld)" = "Встановлені програми (%lld)"; +"Uninstall (%lld files)" = "Видалити (%lld)"; +"Loading installed apps..." = "Завантаження встановлених програм..."; +"No Apps Found" = "Програм не знайдено"; +"Could not find any installed applications." = "Не вдалося знайти жодної встановленої програми."; +"Retry" = "Повторити"; +"Application" = "Програма"; +"Size" = "Розмір"; +"Select an App" = "Виберіть програму"; +"Select an app from the list to see all its related files across your system." = "Виберіть програму зі списку, щоб переглянути всі пов’язані з нею файли в системі."; + +/* App Files (AppFilesView) */ +"%lld files" = "Файлів: %lld"; +"Scanning for related files..." = "Пошук пов’язаних файлів..."; +"Checking %lld locations..." = "Перевіряємо розташування: %lld..."; +"No Related Files" = "Пов’язаних файлів немає"; +"No additional files found for %@." = "Додаткових файлів для %@ не знайдено."; +"Remove %lld files (%@)" = "Видалити вибране: %lld (%@)"; +"Removal Failed" = "Не вдалося видалити"; +"Remove this file" = "Видалити цей файл"; +"Remove %@?" = "Видалити %@?"; +"Remove" = "Видалити"; +"This will permanently delete this file. This action cannot be undone." = "Цей файл буде видалено назавжди. Цю дію неможливо скасувати."; + +/* Orphans */ +"Scanning for orphaned files..." = "Пошук залишкових файлів..."; +"No Orphaned Files" = "Залишкових файлів немає"; +"No leftover files from uninstalled apps were found." = "Залишків видалених програм не знайдено."; +"Scan for Orphans" = "Знайти залишкові файли"; +"Orphaned Files (%lld)" = "Залишкові файли (%lld)"; +"Remove Selected (%lld)" = "Видалити вибране (%lld)"; +"Some files could not be removed" = "Деякі файли не вдалося видалити"; +"blocked by safety policy" = "заблоковано політикою безпеки"; +"%lld item(s) failed to delete.\n\n%@" = "Не вдалося видалити %lld об’єкт(ів).\n\n%@"; + +/* Onboarding */ +"Back" = "Назад"; +"Next" = "Далі"; +"Get Started" = "Почати"; +"Welcome to PureMac" = "Вітаємо у PureMac"; +"Free, open-source macOS app manager and system cleaner." = "Безкоштовний менеджер програм і засіб очищення системи з відкритим кодом для macOS."; +"Find junk files across your system" = "Знаходьте непотрібні файли в усій системі"; +"App Uninstaller" = "Засіб видалення програм"; +"Remove apps and all their files" = "Видаляйте програми разом з усіма їхніми файлами"; +"Orphan Finder" = "Пошук залишкових файлів"; +"Find leftovers from deleted apps" = "Знаходьте залишки видалених програм"; +"Full Disk Access" = "Повний доступ до диска"; +"PureMac needs Full Disk Access to uninstall apps, find leftover files, and clean protected caches." = "PureMac потрібен повний доступ до диска, щоб видаляти програми, знаходити залишкові файли й очищати захищені кеші."; +"We'll guide you through the next steps." = "Ми допоможемо виконати наступні кроки."; +"In System Settings, do this:" = "У Системних параметрах виконайте такі дії:"; +"Privacy & Security → Full Disk Access" = "Приватність і безпека → Повний доступ до диска"; +"Find **PureMac** and turn the toggle on" = "Знайдіть **PureMac** та увімкніть перемикач"; +"Authenticate with Touch ID or your password" = "Підтвердьте особу за допомогою Touch ID або пароля"; +"Reopen Settings" = "Знову відкрити Системні параметри"; +"PureMac isn't in the list — reveal it" = "PureMac немає у списку — покажіть програму"; +"Reset permissions and re-prompt" = "Скинути дозволи й запитати знову"; +"Hide diagnostics" = "Сховати діагностику"; +"Show diagnostics" = "Показати діагностику"; +"Trouble?" = "Виникли труднощі?"; +"Waiting for permission…" = "Очікуємо дозволу…"; +"Permission granted." = "Дозвіл надано."; +"PureMac can now manage protected files." = "Тепер PureMac може керувати захищеними файлами."; +"You're Ready" = "Усе готово"; +"%lld/%lld protected locations accessible" = "Доступні захищені розташування: %lld/%lld"; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "Без повного доступу до диска деякі функції будуть обмежені. Його можна надати пізніше в налаштуваннях."; +"Trash" = "Смітник"; +"Mail Data" = "Дані Пошти"; +"Safari Data" = "Дані Safari"; +"Desktop" = "Робочий стіл"; +"Documents" = "Документи"; +"TCC Database" = "База даних TCC"; +"Blocked" = "Заблоковано"; + +/* FDA demo system apps */ +"Finder" = "Finder"; +"Terminal" = "Термінал"; + +/* Settings */ +"General" = "Загальні"; +"Cleaning" = "Очищення"; +"Schedule" = "Розклад"; +"About" = "Про програму"; +"Startup" = "Запуск"; +"Launch PureMac at login" = "Запускати PureMac під час входу"; +"App Scanning" = "Сканування програм"; +"Search sensitivity" = "Чутливість пошуку"; +"Strict" = "Точний"; +"Enhanced" = "Розширений"; +"Deep" = "Глибокий"; +"Exact bundle ID and name matches only. Safest option." = "Лише точні збіги ідентифікатора пакета й назви. Найбезпечніший варіант."; +"Includes partial name matching and bundle ID components." = "Враховує часткові збіги назви та складників ідентифікатора пакета."; +"Includes company name, entitlements, and team identifier matching." = "Враховує збіги назви компанії, прав доступу та ідентифікатора команди."; +"Language" = "Мова"; +"Restart PureMac to apply the selected language." = "Перезапустіть PureMac, щоб застосувати вибрану мову."; +"Relaunch Now" = "Перезапустити зараз"; +"System Default" = "Системна мова"; +"English" = "Англійська"; +"Spanish" = "Іспанська"; +"Japanese" = "Японська"; +"Arabic" = "Арабська"; +"Polish" = "Польська"; +"Portuguese (Brazil)" = "Португальська (Бразилія)"; +"Russian" = "Російська"; +"Ukrainian" = "Українська"; +"Chinese (Simplified)" = "Китайська (спрощена)"; +"Chinese (Traditional)" = "Китайська (традиційна)"; +"Safety" = "Безпека"; +"Confirm before deleting files" = "Підтверджувати видалення файлів"; +"File Discovery" = "Пошук файлів"; +"Skip hidden files during scan" = "Пропускати приховані файли під час сканування"; +"Large Files" = "Великі файли"; +"Minimum size: %lld MB" = "Мінімальний розмір: %lld МБ"; +"Files older than: %lld months" = "Файли, старіші за %lld міс."; +"Automatic Scanning" = "Автоматичне сканування"; +"Enable scheduled scanning" = "Увімкнути сканування за розкладом"; +"Scan interval" = "Інтервал сканування"; +"Auto-clean after scan" = "Автоматично очищати після сканування"; +"Auto-purge purgeable space" = "Автоматично звільняти доступне місце"; +"Notify on completion" = "Сповіщати про завершення"; +"Last run" = "Останній запуск"; +"Version %@" = "Версія %@"; +"Free, open-source macOS app manager." = "Безкоштовний менеджер програм із відкритим кодом для macOS."; +"GitHub Repository" = "Репозиторій GitHub"; +"Report an Issue" = "Повідомити про проблему"; +"MIT License" = "Ліцензія MIT"; +"Updates" = "Оновлення"; +"Check for Updates" = "Перевірити наявність оновлень"; + +/* Cleaning categories (CleaningCategory.rawValue) */ +"System Junk" = "Системне сміття"; +"User Cache" = "Кеш користувача"; +"AI Apps" = "Програми ШІ"; +"Mail Files" = "Файли Пошти"; +"Trash Bins" = "Смітники"; +"Large & Old Files" = "Великі й старі файли"; +"Purgeable Space" = "Місце, яке можна звільнити"; +"Xcode Junk" = "Сміття Xcode"; +"Brew Cache" = "Кеш Brew"; +"Node Cache" = "Кеш Node"; +"Docker Cache" = "Кеш Docker"; +"Universal Binaries" = "Універсальні бінарні файли"; +"Language Files" = "Мовні файли"; + +/* Cleaning category descriptions */ +"Scan everything at once" = "Усе за одне сканування"; +"System caches, logs, and temporary files" = "Системні кеші, журнали й тимчасові файли"; +"Application caches and browser data" = "Кеші програм і дані браузерів"; +"Local AI app logs, caches, and optional history" = "Журнали, кеші та, за бажанням, історія локальних програм ШІ"; +"Downloaded mail attachments" = "Викачані вкладення Пошти"; +"Files in your Trash" = "Файли у Смітнику"; +"Files over 100 MB or older than 1 year" = "Файли розміром понад 100 МБ або старші за 1 рік"; +"APFS purgeable disk space" = "Місце APFS, яке можна звільнити"; +"Derived data, archives, and simulators" = "Похідні дані, архіви та симулятори"; +"Homebrew download cache" = "Кеш завантажень Homebrew"; +"npm, yarn, and pnpm download caches" = "Кеші завантажень npm, yarn і pnpm"; +"Docker images, containers, and build cache" = "Образи й контейнери Docker, а також кеш збірок"; +"Unused CPU architecture slices in app binaries" = "Невикористовувані архітектури процесора в бінарних файлах програм"; +"Unused app localizations" = "Невикористовувані локалізації програм"; + +/* AI Apps scan item names */ +"Ollama Logs" = "Журнали Ollama"; +"Ollama Cache" = "Кеш Ollama"; +"Ollama Electron Cache" = "Кеш Electron в Ollama"; +"Ollama WebKit Data" = "Дані WebKit в Ollama"; +"Ollama Saved State" = "Збережений стан Ollama"; +"Ollama CLI Prompt History (Optional)" = "Історія запитів Ollama CLI (необов’язково)"; +"LM Studio Server Logs" = "Журнали сервера LM Studio"; +"LM Studio Conversations (Optional)" = "Розмови LM Studio (необов’язково)"; + +/* Node Cache subcategories */ +"npm cache" = "Кеш npm"; +"yarn classic cache" = "Кеш Yarn Classic"; +"pnpm content-addressable store" = "Сховище pnpm з адресацією за вмістом"; + +/* Docker Cache helper */ +"Docker prune (stopped containers, dangling images, build cache)" = "Очищення Docker (зупинені контейнери, неприв’язані образи та кеш збірок)"; + +/* Schedule intervals (ScheduleInterval.rawValue) */ +"Every Hour" = "Щогодини"; +"Every 3 Hours" = "Кожні 3 години"; +"Every 6 Hours" = "Кожні 6 годин"; +"Every 12 Hours" = "Кожні 12 годин"; +"Daily" = "Щодня"; +"Weekly" = "Щотижня"; +"Every 2 Weeks" = "Кожні 2 тижні"; +"Monthly" = "Щомісяця"; + +/* Schedule status */ +"Never" = "Ніколи"; +"Not scheduled" = "Не заплановано"; + +/* Appearance modes (AppearanceMode.label) */ +"System" = "Як у системі"; +"Light" = "Світлий"; +"Dark" = "Темний"; + +/* Notifications */ +"Found %@ of junk files." = "Знайдено непотрібних файлів на %@."; + +/* Onboarding v2 */ +"Reclaim your Mac" = "Звільніть місце на Mac"; +"Apple sells you small disks. We help you keep them clean." = "Apple продає Mac із невеликими накопичувачами. Ми допомагаємо не засмічувати їх."; +"Free. Open source. MIT licensed." = "Безкоштовно. Відкритий код. Ліцензія MIT."; +"What's inside" = "Що всередині"; +"Three things, done well." = "Три речі, зроблені як слід."; +"Find caches, logs, broken installs, and the AI-app history hiding in your library." = "Знаходьте кеші, журнали, невдалі інсталяції та приховану в бібліотеці історію програм ШІ."; +"Drag an app, see every file it dropped, remove all of it. No leftovers." = "Перетягніть програму, перегляньте всі створені нею файли та видаліть усе. Без залишків."; +"Surfaces files that outlived the apps that created them." = "Знаходить файли, що пережили програми, які їх створили."; +"One permission, then we're done" = "Один дозвіл — і все готово"; +"Permission granted" = "Дозвіл надано"; +"PureMac can now reach the locations macOS protects by default." = "Тепер PureMac має доступ до розташувань, які macOS захищає за замовчуванням."; +"macOS hides certain folders from every app until you say otherwise. We need them to find caches and uninstall cleanly." = "macOS приховує певні папки від усіх програм, доки ви не дозволите доступ. Він потрібен, щоб знаходити кеші та повністю видаляти програми."; +"All set — moving you to the next step." = "Усе готово — переходимо до наступного кроку."; +"Reveal app again" = "Знову показати програму"; +"Reset permissions" = "Скинути дозволи"; +"You're ready" = "Усе готово"; +"Ready when you are" = "Можна починати"; +"Hit Start to run your first Smart Scan." = "Натисніть «Почати», щоб запустити перше розумне сканування."; +"Skip" = "Пропустити"; +"Start" = "Почати"; +"Continue" = "Продовжити"; + +/* Drag handle */ +"Drag to the Settings list" = "Перетягніть у Параметри"; +"Drag this icon into the Full Disk Access list in System Settings." = "Перетягніть цю іконку до списку «Повний доступ до диска» в Системних параметрах."; +"Tip: drag the icon on the left straight into the list." = "Порада: перетягніть іконку зліва прямо до списку."; +"Or drag the icon on the left straight into Settings." = "Або перетягніть іконку зліва прямо в Системні параметри."; + +/* Dashboard storage composition (v2.7.0) */ +"Storage composition" = "Розподіл сховища"; +"total" = "усього"; +"Free" = "Вільно"; + +/* UI polish (v2.8.0): leftover groups + sound toggle */ +"Caches" = "Кеші"; +"Application Support" = "Допоміжні файли"; +"Preferences" = "Параметри"; +"Logs" = "Журнали"; +"Containers" = "Контейнери"; +"Launch Agents" = "Агенти запуску"; +"Other Files" = "Інші файли"; +"Sound" = "Звук"; +"Play sound effects" = "Відтворювати звукові ефекти"; + +/* v2.8.1: purgeable honesty */ +"Managed by macOS" = "Керується macOS"; +"Reserved by macOS - freed automatically when space is needed" = "Зарезервовано macOS — звільняється автоматично, коли потрібне місце"; + +/* v2.8.2: large-file folder exclusions (#121) */ +"Excluded Folders" = "Папки-винятки"; +"Files inside these folders are skipped from the Large & Old Files scan (Downloads, Documents, Desktop)." = "Файли в цих папках пропускаються під час пошуку великих і старих файлів (Викачане, Документи, Робочий стіл)."; +"Remove from exclusions" = "Вилучити з винятків"; +"Add Folder…" = "Додати папку…"; + +/* v2.8.2: orphan ignore list (#114) */ +"Always Ignore" = "Завжди ігнорувати"; +"Ignore Selected (%lld)" = "Ігнорувати вибране (%lld)"; +"Ignored orphans: %lld" = "Ігноровані файли: %lld"; +"Forget Ignored" = "Очистити список"; +"Ignored files won't appear in future orphan scans." = "Проігноровані файли не з’являтимуться під час подальшого пошуку залишків."; + +/* v2.8.3: menu-bar system monitor */ +"System Monitor" = "Моніторинг системи"; +"Show system monitor in menu bar" = "Показувати стан системи у смузі меню"; +"Live CPU, memory, and disk meters in the menu bar. PureMac keeps running in the background while this is on." = "Показники процесора, пам’яті й диска в реальному часі у смузі меню. Поки цю функцію ввімкнено, PureMac працює у фоновому режимі."; +"CPU" = "ЦП"; +"Memory" = "Пам’ять"; +"Disk" = "Диск"; +"Open PureMac" = "Відкрити PureMac"; +"Quit PureMac" = "Завершити PureMac"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "Виберіть програму (.app) для видалення."; diff --git a/PureMac/uk.lproj/ServicesMenu.strings b/PureMac/uk.lproj/ServicesMenu.strings new file mode 100644 index 0000000..bb36e63 --- /dev/null +++ b/PureMac/uk.lproj/ServicesMenu.strings @@ -0,0 +1 @@ +"Uninstall with PureMac" = "Видалити програму за допомогою PureMac"; diff --git a/PureMac/zh-Hans.lproj/Localizable.strings b/PureMac/zh-Hans.lproj/Localizable.strings index 4e71fc5..4d8060e 100644 --- a/PureMac/zh-Hans.lproj/Localizable.strings +++ b/PureMac/zh-Hans.lproj/Localizable.strings @@ -41,6 +41,7 @@ "Suggested for you" = "推荐"; "Found so far" = "目前发现"; "By category" = "按类别"; +"Category" = "类别"; "%@ is using %@" = "%@ 占用 %@"; "Grant Full Disk Access for full results" = "授予完整磁盘访问以获得完整结果"; "Without it, most caches and uninstall flows fail." = "没有它,大多数缓存清理和卸载流程都会失败。"; @@ -121,6 +122,8 @@ "Orphaned Files (%lld)" = "孤立文件 (%lld)"; "Remove Selected (%lld)" = "删除所选 (%lld)"; "Some files could not be removed" = "部分文件无法删除"; +"blocked by safety policy" = "已被安全策略阻止"; +"%lld item(s) failed to delete.\n\n%@" = "有 %lld 个项目删除失败。\n\n%@"; /* Onboarding */ "Back" = "返回"; @@ -151,7 +154,7 @@ "PureMac can now manage protected files." = "PureMac 现在可以管理受保护的文件。"; "You're Ready" = "已准备就绪"; "%lld/%lld protected locations accessible" = "可访问的受保护位置 %lld/%lld"; -"Some features will be limited. You can grant Full Disk Access later in System Settings." = "部分功能将受限。你可以稍后在“系统设置”中授予完整磁盘访问。"; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "没有完全磁盘访问权限时,部分功能将受限。你可以稍后在设置中授予该权限。"; "Trash" = "废纸篓"; "Mail Data" = "邮件数据"; "Safari Data" = "Safari 数据"; @@ -160,6 +163,10 @@ "TCC Database" = "TCC 数据库"; "Blocked" = "已阻止"; +/* FDA demo system apps */ +"Finder" = "访达"; +"Terminal" = "终端"; + /* Settings */ "General" = "通用"; "Cleaning" = "清理"; @@ -185,6 +192,8 @@ "Arabic" = "阿拉伯语"; "Polish" = "波兰语"; "Portuguese (Brazil)" = "葡萄牙语(巴西)"; +"Russian" = "俄语"; +"Ukrainian" = "乌克兰语"; "Chinese (Simplified)" = "简体中文"; "Chinese (Traditional)" = "繁体中文"; "Safety" = "安全"; @@ -206,6 +215,8 @@ "GitHub Repository" = "GitHub 仓库"; "Report an Issue" = "报告问题"; "MIT License" = "MIT 许可"; +"Updates" = "更新"; +"Check for Updates" = "检查更新"; /* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "系统垃圾"; @@ -238,13 +249,23 @@ "Unused CPU architecture slices in app binaries" = "应用二进制文件中未使用的 CPU 架构代码"; "Unused app localizations" = "未使用的应用本地化文件"; +/* AI Apps scan item names */ +"Ollama Logs" = "Ollama 日志"; +"Ollama Cache" = "Ollama 缓存"; +"Ollama Electron Cache" = "Ollama Electron 缓存"; +"Ollama WebKit Data" = "Ollama WebKit 数据"; +"Ollama Saved State" = "Ollama 已保存状态"; +"Ollama CLI Prompt History (Optional)" = "Ollama CLI 提示词历史记录(可选)"; +"LM Studio Server Logs" = "LM Studio 服务器日志"; +"LM Studio Conversations (Optional)" = "LM Studio 对话(可选)"; + /* Node Cache subcategories */ "npm cache" = "npm 缓存"; "yarn classic cache" = "yarn 经典缓存"; "pnpm content-addressable store" = "pnpm 内容寻址存储"; /* Docker Cache helper */ -"Reclaimable (run `docker system prune -af`)" = "可回收(运行 `docker system prune -af`)"; +"Docker prune (stopped containers, dangling images, build cache)" = "Docker 清理(已停止的容器、悬空镜像和构建缓存)"; /* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "每小时"; @@ -306,6 +327,8 @@ "1-tap setup. We'll auto-retry what failed." = "一键设置。我们会自动重试失败的操作。"; "Fix permission" = "修复权限"; "Dismiss" = "忽略"; +"Fix" = "修复"; +"Set up" = "设置"; /* Onboarding v2 */ "Reclaim your Mac" = "夺回你的 Mac"; @@ -342,7 +365,6 @@ "Free" = "可用"; /* UI polish (v2.8.0): leftover groups + sound toggle */ -"Application" = "应用程序"; "Caches" = "缓存"; "Application Support" = "应用程序支持"; "Preferences" = "偏好设置"; @@ -379,3 +401,6 @@ "Disk" = "磁盘"; "Open PureMac" = "打开 PureMac"; "Quit PureMac" = "退出 PureMac"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "请选择要卸载的应用程序(.app)。"; diff --git a/PureMac/zh-Hant.lproj/Localizable.strings b/PureMac/zh-Hant.lproj/Localizable.strings index df3edf2..aaa7e4b 100644 --- a/PureMac/zh-Hant.lproj/Localizable.strings +++ b/PureMac/zh-Hant.lproj/Localizable.strings @@ -41,6 +41,7 @@ "Suggested for you" = "為你建議"; "Found so far" = "目前找到"; "By category" = "依類別"; +"Category" = "類別"; "%@ is using %@" = "%@ 正在使用 %@"; "Grant Full Disk Access for full results" = "授予完整磁碟取用權以取得完整結果"; "Without it, most caches and uninstall flows fail." = "若沒有它,多數快取清理與解除安裝流程會失敗。"; @@ -121,6 +122,8 @@ "Orphaned Files (%lld)" = "孤立檔案 (%lld)"; "Remove Selected (%lld)" = "移除所選 (%lld)"; "Some files could not be removed" = "部分檔案無法移除"; +"blocked by safety policy" = "已被安全性原則阻擋"; +"%lld item(s) failed to delete.\n\n%@" = "有 %lld 個項目刪除失敗。\n\n%@"; /* Onboarding */ "Back" = "返回"; @@ -151,7 +154,7 @@ "PureMac can now manage protected files." = "PureMac 現在可以管理受保護的檔案。"; "You're Ready" = "已就緒"; "%lld/%lld protected locations accessible" = "可存取的受保護位置 %lld/%lld"; -"Some features will be limited. You can grant Full Disk Access later in System Settings." = "部分功能將受到限制。你可以稍後在「系統設定」中授予完整磁碟取用權。"; +"Some features will be limited without Full Disk Access. You can grant it later in Settings." = "沒有完整磁碟取用權時,部分功能將受到限制。你可以稍後在設定中授予該權限。"; "Trash" = "垃圾桶"; "Mail Data" = "Mail 資料"; "Safari Data" = "Safari 資料"; @@ -160,6 +163,10 @@ "TCC Database" = "TCC 資料庫"; "Blocked" = "已阻擋"; +/* FDA demo system apps */ +"Finder" = "Finder"; +"Terminal" = "終端機"; + /* Settings */ "General" = "一般"; "Cleaning" = "清理"; @@ -185,6 +192,8 @@ "Arabic" = "阿拉伯文"; "Polish" = "波蘭文"; "Portuguese (Brazil)" = "葡萄牙文(巴西)"; +"Russian" = "俄文"; +"Ukrainian" = "烏克蘭文"; "Chinese (Simplified)" = "簡體中文"; "Chinese (Traditional)" = "繁體中文"; "Safety" = "安全"; @@ -206,6 +215,8 @@ "GitHub Repository" = "GitHub 儲存庫"; "Report an Issue" = "回報問題"; "MIT License" = "MIT 授權"; +"Updates" = "更新"; +"Check for Updates" = "檢查更新"; /* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "系統垃圾"; @@ -238,13 +249,23 @@ "Unused CPU architecture slices in app binaries" = "應用程式二進位檔中未使用的 CPU 架構程式碼"; "Unused app localizations" = "未使用的應用程式本地化檔案"; +/* AI Apps scan item names */ +"Ollama Logs" = "Ollama 日誌"; +"Ollama Cache" = "Ollama 快取"; +"Ollama Electron Cache" = "Ollama Electron 快取"; +"Ollama WebKit Data" = "Ollama WebKit 資料"; +"Ollama Saved State" = "Ollama 已儲存狀態"; +"Ollama CLI Prompt History (Optional)" = "Ollama CLI 提示詞歷史記錄(選用)"; +"LM Studio Server Logs" = "LM Studio 伺服器日誌"; +"LM Studio Conversations (Optional)" = "LM Studio 對話(選用)"; + /* Node Cache subcategories */ "npm cache" = "npm 快取"; "yarn classic cache" = "yarn 經典快取"; "pnpm content-addressable store" = "pnpm 內容定址儲存庫"; /* Docker Cache helper */ -"Reclaimable (run `docker system prune -af`)" = "可回收(執行 `docker system prune -af`)"; +"Docker prune (stopped containers, dangling images, build cache)" = "Docker 清理(已停止的容器、懸空映像檔和建置快取)"; /* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "每小時"; @@ -306,6 +327,8 @@ "1-tap setup. We'll auto-retry what failed." = "一鍵設定。我們會自動重試失敗的操作。"; "Fix permission" = "修正權限"; "Dismiss" = "略過"; +"Fix" = "修正"; +"Set up" = "設定"; /* Onboarding v2 */ "Reclaim your Mac" = "奪回你的 Mac"; @@ -342,7 +365,6 @@ "Free" = "可用"; /* UI polish (v2.8.0): leftover groups + sound toggle */ -"Application" = "應用程式"; "Caches" = "快取"; "Application Support" = "應用程式支援"; "Preferences" = "偏好設定"; @@ -379,3 +401,6 @@ "Disk" = "磁碟"; "Open PureMac" = "開啟 PureMac"; "Quit PureMac" = "結束 PureMac"; + +/* Finder Service */ +"Select an application (.app) to uninstall." = "請選取要解除安裝的應用程式(.app)。"; diff --git a/PureMacTests/AppLanguagePreferencesTests.swift b/PureMacTests/AppLanguagePreferencesTests.swift index 1dcb320..3546ec5 100644 --- a/PureMacTests/AppLanguagePreferencesTests.swift +++ b/PureMacTests/AppLanguagePreferencesTests.swift @@ -2,6 +2,13 @@ import XCTest @testable import PureMac final class AppLanguagePreferencesTests: XCTestCase { + func testRussianAndUkrainianLanguagesUseExpectedIdentifiers() { + XCTAssertEqual(AppLanguage.russian.rawValue, "ru") + XCTAssertEqual(AppLanguage.ukrainian.rawValue, "uk") + XCTAssertTrue(AppLanguage.allCases.contains(.russian)) + XCTAssertTrue(AppLanguage.allCases.contains(.ukrainian)) + } + func testApplyCustomLanguageSetsAppleLanguagesAndPreservesLocale() { let context = makeDefaults() let defaults = context.defaults @@ -13,6 +20,17 @@ final class AppLanguagePreferencesTests: XCTestCase { XCTAssertEqual(defaults.string(forKey: "AppleLocale"), "pt_BR") } + func testApplyRussianAndUkrainianLanguagesSetsAppleLanguages() { + let context = makeDefaults() + let defaults = context.defaults + + AppLanguagePreferences.apply(.russian, defaults: defaults) + XCTAssertEqual(defaults.array(forKey: "AppleLanguages") as? [String], ["ru"]) + + AppLanguagePreferences.apply(.ukrainian, defaults: defaults) + XCTAssertEqual(defaults.array(forKey: "AppleLanguages") as? [String], ["uk"]) + } + func testApplySystemLanguageRemovesAppleLanguagesAndPreservesLocale() { let context = makeDefaults() let defaults = context.defaults diff --git a/PureMacTests/LocalizationFilesTests.swift b/PureMacTests/LocalizationFilesTests.swift index 949abbb..ba38003 100644 --- a/PureMacTests/LocalizationFilesTests.swift +++ b/PureMacTests/LocalizationFilesTests.swift @@ -1,6 +1,51 @@ import XCTest final class LocalizationFilesTests: XCTestCase { + func testRussianAndUkrainianLocalizationsExist() throws { + let localizationFiles = try localizableStringsFiles() + + XCTAssertNotNil(localizationFiles["ru"], "Expected ru.lproj/Localizable.strings to exist") + XCTAssertNotNil(localizationFiles["uk"], "Expected uk.lproj/Localizable.strings to exist") + } + + func testBuiltAppBundleContainsRussianAndUkrainianLocalizations() throws { + for language in ["ru", "uk"] { + XCTAssertTrue( + Bundle.main.localizations.contains(language), + "Expected the built app bundle to register the \(language) localization" + ) + for resource in ["Localizable", "InfoPlist", "ServicesMenu"] { + XCTAssertNotNil( + Bundle.main.path( + forResource: resource, + ofType: "strings", + inDirectory: nil, + forLocalization: language + ), + "Expected the built app bundle to contain \(language).lproj/\(resource).strings" + ) + } + + let localizablePath = try XCTUnwrap( + Bundle.main.path( + forResource: "Localizable", + ofType: "strings", + inDirectory: nil, + forLocalization: language + ) + ) + let localizationPath = URL(fileURLWithPath: localizablePath) + .deletingLastPathComponent() + .path + let localizationBundle = try XCTUnwrap(Bundle(path: localizationPath)) + XCTAssertNotEqual( + localizationBundle.localizedString(forKey: "Language", value: nil, table: nil), + "Language", + "Expected the \(language) localization bundle to resolve a translated sentinel value" + ) + } + } + func testAllLocalizableStringsFilesHaveEnglishKeyParity() throws { let localizationFiles = try localizableStringsFiles() let englishURL = try XCTUnwrap( @@ -25,6 +70,40 @@ final class LocalizationFilesTests: XCTestCase { } } + func testAllLocalizedValuesPreserveEnglishFormatSpecifiers() throws { + let localizationFiles = try localizableStringsFiles() + let englishURL = try XCTUnwrap(localizationFiles["en"]) + let englishStrings = try localizedStrings(in: englishURL) + + for (language, fileURL) in localizationFiles where language != "en" { + let localizedStrings = try localizedStrings(in: fileURL) + + for (key, englishValue) in englishStrings { + let localizedValue = try XCTUnwrap(localizedStrings[key]) + XCTAssertEqual( + formatSignature(in: localizedValue), + formatSignature(in: englishValue), + "\(language).lproj has incompatible format specifiers for key: \(key)" + ) + } + } + } + + func testLocalizableStringsFilesDoNotContainDuplicateKeys() throws { + for (language, fileURL) in try localizableStringsFiles() { + let keys = try declaredKeys(in: fileURL) + let duplicates = Dictionary(grouping: keys, by: { $0 }) + .filter { $0.value.count > 1 } + .keys + .sorted() + + XCTAssertTrue( + duplicates.isEmpty, + "\(language).lproj/Localizable.strings has duplicate keys:\n\(duplicates.joined(separator: "\n"))" + ) + } + } + private func localizableStringsFiles() throws -> [String: URL] { let sourceRoot = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() @@ -47,14 +126,50 @@ final class LocalizationFilesTests: XCTestCase { } private func localizedKeys(in fileURL: URL) throws -> Set { + Set(try localizedStrings(in: fileURL).keys) + } + + private func localizedStrings(in fileURL: URL) throws -> [String: String] { let data = try Data(contentsOf: fileURL) let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) guard let strings = plist as? [String: String] else { XCTFail("\(fileURL.path) is not a valid Localizable.strings dictionary") - return [] + return [:] + } + + return strings + } + + private func declaredKeys(in fileURL: URL) throws -> [String] { + let contents = try String(contentsOf: fileURL, encoding: .utf8) + let regex = try NSRegularExpression(pattern: #"(?m)^"((?:\\.|[^"])*)"\s*="#) + let range = NSRange(contents.startIndex..., in: contents) + + return regex.matches(in: contents, range: range).compactMap { match in + Range(match.range(at: 1), in: contents).map { String(contents[$0]) } } + } - return Set(strings.keys) + private func formatSignature(in value: String) -> [String] { + let pattern = #"%(?:(\d+)\$)?(lld|@|%)"# + let regex = try! NSRegularExpression(pattern: pattern) + let range = NSRange(value.startIndex..., in: value) + var sequentialPosition = 1 + + return regex.matches(in: value, range: range).compactMap { match in + guard let typeRange = Range(match.range(at: 2), in: value) else { return nil } + let type = String(value[typeRange]) + guard type != "%" else { return "literal-percent" } + + if let explicitRange = Range(match.range(at: 1), in: value), + let position = Int(value[explicitRange]) { + return "\(position):\(type)" + } + + defer { sequentialPosition += 1 } + return "\(sequentialPosition):\(type)" + } + .sorted() } } diff --git a/README.md b/README.md index cec063f..4457d80 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ Pull requests welcome. See [CONTRIBUTING.md](CONTRIBUTING.md). Especially welcome: - Per-category size and date filter presets - Wider XCTest coverage for `AppState` and the scan engine -- Translations beyond the current set (en, ar, es, ja, pt-BR, zh-Hans, zh-Hant) +- Translations beyond the current set (en, ar, es, ja, pl, pt-BR, ru, uk, zh-Hans, zh-Hant) - App icon design ## Acknowledgments