diff --git a/PureMac.xcodeproj/project.pbxproj b/PureMac.xcodeproj/project.pbxproj index d4700db..782303b 100644 --- a/PureMac.xcodeproj/project.pbxproj +++ b/PureMac.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 48D1431A7C99C19EEBDB056B /* CleaningEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8DE5D45BA19E2670B57DC5 /* CleaningEngine.swift */; }; 4F754D89F4CE5142BE384062 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31F91226CDCFBB8E303B7DA /* AppConstants.swift */; }; 535B23C0108C06475215B8E8 /* AppTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB003A7751F05727DBFD1A5 /* AppTheme.swift */; }; + 59B3096E2AE8ED0397B16798 /* AppLanguagePreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491771F923C93FD61A263893 /* AppLanguagePreferencesTests.swift */; }; 75B5F0401D37F2872B1AD85A /* AppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E6BC61614B27C065BB18C9 /* AppListView.swift */; }; 76B132F9C499225D33E0D075 /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 424F7B28624C271620E13BBC /* EmptyStateView.swift */; }; 826A750D2D7EC14C2AE306A3 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3667D46D8E2004EB4D73835A /* Models.swift */; }; @@ -29,21 +30,35 @@ B52938BBD11842631314543D /* CategoryDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10B0AF194677EAC1D5568785 /* CategoryDetailView.swift */; }; 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 */; }; D50EB059E741011EB2523731 /* ScanError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF15CB1B8EFCA78EF491824 /* ScanError.swift */; }; D9445C2641A8637B65DA5ACE /* ScanEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A711CDF5285F68775D9B5513 /* ScanEngine.swift */; }; DDD6BA35DBF32E7A6B5F6F8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B2EA41E1096FA8E3B916AD13 /* Assets.xcassets */; }; DDDA5879006CB97D5D7BDD87 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5785762276FB5E3209C6DE4D /* Logger.swift */; }; + E2403D82F00D749C9AD4A6D4 /* AppStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752603285F7DBBA12BB3AA91 /* AppStateTests.swift */; }; E60B0A2C5D0A6CAE35BF4DFB /* Locations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77D3D9A9BC52839E6D0A22BC /* Locations.swift */; }; + E726702EABBE56155861DBA9 /* AppLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82BB0904726B38DEB141BECA /* AppLanguage.swift */; }; EDEF28CAD23E936FBED1783B /* PureMacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63581B70F9B10231964E3602 /* PureMacApp.swift */; }; F1F506C146E17D6CEC4A12F3 /* AppInfoFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23486A54A82EE3865B784D1C /* AppInfoFetcher.swift */; }; F2FA881A4B2209CC2A6342FB /* CLI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B2C5F66B6D812572BD4F05 /* CLI.swift */; }; FDA30FE6349CA01C8D859F04 /* OrphanListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B71E8F62DB76D85F41F9A2E9 /* OrphanListView.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 8D91DC174C821BE74C4B518C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 37BE87544E2EC9F6FF1CD280 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B19E38DEAA0144A77120F39C; + remoteInfo = PureMac; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 01B2C5F66B6D812572BD4F05 /* CLI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLI.swift; sourceTree = ""; }; 02E502E2B5C6AECC76E5CFEF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; 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 = ""; }; 1D3AC5F17D7CA94FAB1E8D7A /* StringNormalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringNormalization.swift; sourceTree = ""; }; 23486A54A82EE3865B784D1C /* AppInfoFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoFetcher.swift; sourceTree = ""; }; @@ -57,19 +72,25 @@ 3D4FD34378988D430A582ED0 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; 424F7B28624C271620E13BBC /* EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateView.swift; 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 = ""; }; 5664D2BDAEAA9AE3A53DB364 /* PureMac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PureMac.entitlements; sourceTree = ""; }; 5785762276FB5E3209C6DE4D /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 5A5C80929EE4A430272674BC /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 607B9A8C7B34D6A7DCF4FFE2 /* PureMacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PureMacTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 60E6BC61614B27C065BB18C9 /* AppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListView.swift; sourceTree = ""; }; 63581B70F9B10231964E3602 /* PureMacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PureMacApp.swift; 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 = ""; }; 798B80977D14647A5691B0A0 /* AppFilesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFilesView.swift; sourceTree = ""; }; + 82BB0904726B38DEB141BECA /* AppLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLanguage.swift; sourceTree = ""; }; + 8D5E63D733D1A3BDEDF4DCA5 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; 92259B3E9F4468865F15DEEC /* AppearancePill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePill.swift; sourceTree = ""; }; 9F04B811BB0012F6D2F07F91 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 9F510F232341EE18F11DC934 /* MainWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = ""; }; A711CDF5285F68775D9B5513 /* ScanEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanEngine.swift; sourceTree = ""; }; AC0FEE7141871ED5F9E36121 /* AppPathFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathFinder.swift; sourceTree = ""; }; + B2CD0028599BE178B96F18A6 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; B2EA41E1096FA8E3B916AD13 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B71E8F62DB76D85F41F9A2E9 /* OrphanListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrphanListView.swift; sourceTree = ""; }; C5ADDC35F31E40780FB5D017 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; @@ -86,13 +107,25 @@ isa = PBXGroup; children = ( E383F69184F4552E5A41D010 /* PureMac */, + 261FB8D961FEC76F15EA523A /* PureMacTests */, 4562CA9E5625FA4EEEFECB6D /* Products */, ); sourceTree = ""; }; + 261FB8D961FEC76F15EA523A /* PureMacTests */ = { + isa = PBXGroup; + children = ( + 491771F923C93FD61A263893 /* AppLanguagePreferencesTests.swift */, + 752603285F7DBBA12BB3AA91 /* AppStateTests.swift */, + 155CE1B8CDCCCA6F9FD028C1 /* LocalizationFilesTests.swift */, + ); + path = PureMacTests; + sourceTree = ""; + }; 3CF46713F75B81F0F86D1C6F /* Models */ = { isa = PBXGroup; children = ( + 82BB0904726B38DEB141BECA /* AppLanguage.swift */, 3667D46D8E2004EB4D73835A /* Models.swift */, EEF15CB1B8EFCA78EF491824 /* ScanError.swift */, ); @@ -103,6 +136,7 @@ isa = PBXGroup; children = ( 311078221878708524283765 /* PureMac.app */, + 607B9A8C7B34D6A7DCF4FFE2 /* PureMacTests.xctest */, ); name = Products; sourceTree = ""; @@ -207,11 +241,11 @@ children = ( B2EA41E1096FA8E3B916AD13 /* Assets.xcassets */, 46660271CFF167AB0FE7371D /* Info.plist */, - 241E0895B09C71AB423B2F9E /* Localizable.strings */, 5664D2BDAEAA9AE3A53DB364 /* PureMac.entitlements */, 63581B70F9B10231964E3602 /* PureMacApp.swift */, D4333B07691BD85CAE0E5B15 /* Core */, 7C1729F88C0E5563E1A3DB40 /* Extensions */, + 241E0895B09C71AB423B2F9E /* Localizable.strings */, F283C00EB52AB140F61500A3 /* Logic */, 3CF46713F75B81F0F86D1C6F /* Models */, 6184B2EC3D01E6E95633406E /* Services */, @@ -263,6 +297,24 @@ productReference = 311078221878708524283765 /* PureMac.app */; productType = "com.apple.product-type.application"; }; + FBA8B0DE95746207A043B802 /* PureMacTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 25F40856927F2EE5BC50A0FB /* Build configuration list for PBXNativeTarget "PureMacTests" */; + buildPhases = ( + A6847DD7D5552F4AA6B17BA8 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + 0E97FDF7F6FA15845FCE6737 /* PBXTargetDependency */, + ); + name = PureMacTests; + packageProductDependencies = ( + ); + productName = PureMacTests; + productReference = 607B9A8C7B34D6A7DCF4FFE2 /* PureMacTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -276,27 +328,34 @@ DevelopmentTeam = H3WXHVTP97; ProvisioningStyle = Automatic; }; + FBA8B0DE95746207A043B802 = { + DevelopmentTeam = H3WXHVTP97; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 2ABAFAE07AA42044AE58F688 /* Build configuration list for PBXProject "PureMac" */; - compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, + ar, en, es, ja, + "pt-BR", "zh-Hans", "zh-Hant", ); mainGroup = 13CF0676D0E93925F46C13AA; minimizedProjectReferenceProxies = 1; preferredProjectObjectVersion = 77; + productRefGroup = 4562CA9E5625FA4EEEFECB6D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( B19E38DEAA0144A77120F39C /* PureMac */, + FBA8B0DE95746207A043B802 /* PureMacTests */, ); }; /* End PBXProject section */ @@ -321,6 +380,7 @@ 4F754D89F4CE5142BE384062 /* AppConstants.swift in Sources */, 1B0D043102FDB245312A5147 /* AppFilesView.swift in Sources */, F1F506C146E17D6CEC4A12F3 /* AppInfoFetcher.swift in Sources */, + E726702EABBE56155861DBA9 /* AppLanguage.swift in Sources */, 75B5F0401D37F2872B1AD85A /* AppListView.swift in Sources */, 9BB5AAA574AFED6C27A3F8E2 /* AppPathFinder.swift in Sources */, 9AA80E035DF7B33F6EE118DF /* AppState.swift in Sources */, @@ -350,15 +410,35 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A6847DD7D5552F4AA6B17BA8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 59B3096E2AE8ED0397B16798 /* AppLanguagePreferencesTests.swift in Sources */, + E2403D82F00D749C9AD4A6D4 /* AppStateTests.swift in Sources */, + CFA64F54CDBF8A4765E0068D /* LocalizationFilesTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 0E97FDF7F6FA15845FCE6737 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B19E38DEAA0144A77120F39C /* PureMac */; + targetProxy = 8D91DC174C821BE74C4B518C /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 241E0895B09C71AB423B2F9E /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( + B2CD0028599BE178B96F18A6 /* ar */, 9F04B811BB0012F6D2F07F91 /* en */, 340D303C60FF878B019056B6 /* es */, 5A5C80929EE4A430272674BC /* ja */, + 8D5E63D733D1A3BDEDF4DCA5 /* pt-BR */, 2641C6376DD6F5889F35510E /* zh-Hans */, 02E502E2B5C6AECC76E5CFEF /* zh-Hant */, ); @@ -408,7 +488,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 6; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = H3WXHVTP97; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -430,7 +510,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = PureMac/Info.plist; MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.1.0; + MARKETING_VERSION = 2.2.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = NO; @@ -458,6 +538,40 @@ }; name = Debug; }; + 6AC7FF3E26B642C05D435ADB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + SDKROOT = macosx; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PureMac.app/Contents/MacOS/PureMac"; + }; + name = Debug; + }; + 8632219D8F7DC9932C9DCC9F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + SDKROOT = macosx; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PureMac.app/Contents/MacOS/PureMac"; + }; + name = Release; + }; D1B9D379DD6CC0DA24E58BCF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -498,7 +612,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 6; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = H3WXHVTP97; ENABLE_NS_ASSERTIONS = NO; @@ -514,7 +628,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = PureMac/Info.plist; MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.1.0; + MARKETING_VERSION = 2.2.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = NO; @@ -543,6 +657,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 25F40856927F2EE5BC50A0FB /* Build configuration list for PBXNativeTarget "PureMacTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6AC7FF3E26B642C05D435ADB /* Debug */, + 8632219D8F7DC9932C9DCC9F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 2754925F163D15C85EDF494D /* Build configuration list for PBXNativeTarget "PureMac" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/PureMac.xcodeproj/xcshareddata/xcschemes/PureMac.xcscheme b/PureMac.xcodeproj/xcshareddata/xcschemes/PureMac.xcscheme new file mode 100644 index 0000000..564f560 --- /dev/null +++ b/PureMac.xcodeproj/xcshareddata/xcschemes/PureMac.xcscheme @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PureMac/Models/AppLanguage.swift b/PureMac/Models/AppLanguage.swift new file mode 100644 index 0000000..62ca935 --- /dev/null +++ b/PureMac/Models/AppLanguage.swift @@ -0,0 +1,56 @@ +import Foundation + +enum AppLanguage: String, CaseIterable, Identifiable { + case system = "system" + case english = "en" + case spanish = "es" + case japanese = "ja" + case arabic = "ar" + case portugueseBrazil = "pt-BR" + case simplifiedChinese = "zh-Hans" + case traditionalChinese = "zh-Hant" + + static let preferenceKey = "settings.general.appLanguage" + + var id: String { rawValue } + + var displayName: String { + switch self { + case .system: return "System Default" + case .english: return "English" + case .spanish: return "Spanish" + case .japanese: return "Japanese" + case .arabic: return "Arabic" + case .portugueseBrazil: return "Portuguese (Brazil)" + case .simplifiedChinese: return "Chinese (Simplified)" + case .traditionalChinese: return "Chinese (Traditional)" + } + } + + static var current: AppLanguage { + if let selectedLanguage = UserDefaults.standard.string(forKey: preferenceKey), + let language = AppLanguage(rawValue: selectedLanguage) { + return language + } + + guard let bundleIdentifier = Bundle.main.bundleIdentifier, + let appDefaults = UserDefaults.standard.persistentDomain(forName: bundleIdentifier), + let preferredLanguages = appDefaults["AppleLanguages"] as? [String], + let preferredLanguage = preferredLanguages.first else { + return .system + } + + let normalized = preferredLanguage.replacingOccurrences(of: "_", with: "-") + return allCases.first { $0.rawValue == normalized } ?? .system + } +} + +enum AppLanguagePreferences { + static func apply(_ language: AppLanguage, defaults: UserDefaults = .standard) { + if language == .system { + defaults.removeObject(forKey: "AppleLanguages") + } else { + defaults.set([language.rawValue], forKey: "AppleLanguages") + } + } +} diff --git a/PureMac/ViewModels/AppState.swift b/PureMac/ViewModels/AppState.swift index 8fdc233..7223758 100644 --- a/PureMac/ViewModels/AppState.swift +++ b/PureMac/ViewModels/AppState.swift @@ -13,6 +13,11 @@ enum AppSection: Hashable { @MainActor final class AppState: ObservableObject { + typealias AppFileScanner = @MainActor ( + _ app: InstalledApp, + _ locations: Locations, + _ completion: @escaping (Set) -> Void + ) -> Void // MARK: - Scan / Clean State @@ -44,12 +49,15 @@ final class AppState: ObservableObject { @Published var isLoadingApps: Bool = false @Published var isScanningAppFiles: Bool = false @Published var removalError: String? + @Published var appFileScanLocationCount: Int = 0 // MARK: - Services var scheduler = SchedulerService() private let scanEngine = ScanEngine() private let cleaningEngine = CleaningEngine() + private let locationsProvider: () -> Locations + private let appFileScanner: AppFileScanner // MARK: - Computed @@ -69,21 +77,37 @@ final class AppState: ObservableObject { allResults.flatMap { $0.items }.filter { isItemSelected($0) }.reduce(0) { $0 + $1.size } } + var currentAppFileSearchLocationCount: Int { + if isScanningAppFiles && appFileScanLocationCount > 0 { + return appFileScanLocationCount + } + return discoveredFiles.count + } + // MARK: - Init - init() { - loadDiskInfo() - checkFullDiskAccess() - loadInstalledApps() - scheduler.setTrigger { [weak self] in - await self?.runScheduledScan() - } - // Only arm the scheduler once onboarding has completed. Before the - // first launch the defaults plist may have been attacker-planted with - // autoClean=true - waiting for onboarding ensures a human consents to - // auto-clean before we start honoring it. - if UserDefaults.standard.bool(forKey: "PureMac.OnboardingComplete") { - scheduler.start() + init( + performStartupTasks: Bool = true, + locationsProvider: @escaping () -> Locations = Locations.init, + appFileScanner: @escaping AppFileScanner = AppState.defaultAppFileScanner + ) { + self.locationsProvider = locationsProvider + self.appFileScanner = appFileScanner + + if performStartupTasks { + loadDiskInfo() + checkFullDiskAccess() + loadInstalledApps() + scheduler.setTrigger { [weak self] in + await self?.runScheduledScan() + } + // Only arm the scheduler once onboarding has completed. Before + // the first launch the defaults plist may have been + // attacker-planted with autoClean=true; wait for human consent + // via onboarding. + if UserDefaults.standard.bool(forKey: "PureMac.OnboardingComplete") { + scheduler.start() + } } } @@ -104,21 +128,15 @@ final class AppState: ObservableObject { discoveredFiles = [] selectedFiles = [] isScanningAppFiles = true - let locations = Locations() - let appInfo = AppPathFinder.AppInfo( - appName: app.appName, - bundleIdentifier: app.bundleIdentifier, - path: app.path, - entitlements: nil, - teamIdentifier: nil - ) - let finder = AppPathFinder(appInfo: appInfo, locations: locations) - finder.findPathsAsync { [weak self] urls in + let locations = locationsProvider() + appFileScanLocationCount = locations.appSearch.paths.count + appFileScanner(app, locations) { [weak self] urls in guard let self else { return } let sorted = urls.sorted { $0.path < $1.path } self.discoveredFiles = sorted self.selectedFiles = urls self.isScanningAppFiles = false + self.appFileScanLocationCount = 0 } } @@ -598,4 +616,20 @@ final class AppState: ObservableObject { UNUserNotificationCenter.current().add(request) } + + private static func defaultAppFileScanner( + app: InstalledApp, + locations: Locations, + completion: @escaping (Set) -> Void + ) { + let appInfo = AppPathFinder.AppInfo( + appName: app.appName, + bundleIdentifier: app.bundleIdentifier, + path: app.path, + entitlements: nil, + teamIdentifier: nil + ) + let finder = AppPathFinder(appInfo: appInfo, locations: locations) + finder.findPathsAsync(completion: completion) + } } diff --git a/PureMac/Views/Apps/AppFilesView.swift b/PureMac/Views/Apps/AppFilesView.swift index 9fbaeb8..87c6a19 100644 --- a/PureMac/Views/Apps/AppFilesView.swift +++ b/PureMac/Views/Apps/AppFilesView.swift @@ -30,7 +30,7 @@ struct AppFilesView: View { if !appState.discoveredFiles.isEmpty { VStack(alignment: .trailing, spacing: 2) { - Text("\(appState.discoveredFiles.count) files") + Text(filesCountText(count: appState.discoveredFiles.count)) .font(.callout) .foregroundStyle(.secondary) Text(ByteCountFormatter.string(fromByteCount: totalSelectedSize, countStyle: .file)) @@ -46,8 +46,8 @@ struct AppFilesView: View { if appState.isScanningAppFiles { VStack(spacing: 12) { Spacer() - ProgressView("Scanning for related files...") - Text("Checking \(appState.discoveredFiles.count) locations...") + ProgressView(LocalizedStringKey("Scanning for related files...")) + Text(checkingLocationsText(count: appState.currentAppFileSearchLocationCount)) .font(.caption) .foregroundStyle(.secondary) Spacer() @@ -57,7 +57,9 @@ struct AppFilesView: View { EmptyStateView( "No Related Files", systemImage: "checkmark.circle", - description: "No additional files found for \(app.appName)." + description: LocalizedStringKey( + String(format: String(localized: "No additional files found for %@."), app.appName) + ) ) } else { List(appState.discoveredFiles, id: \.self) { fileURL in @@ -81,7 +83,7 @@ struct AppFilesView: View { Spacer() if !appState.selectedFiles.isEmpty { - Button("Remove \(appState.selectedFiles.count) files (\(ByteCountFormatter.string(fromByteCount: totalSelectedSize, countStyle: .file)))", role: .destructive) { + Button(removeFilesLabel, role: .destructive) { appState.removeSelectedFiles() } .buttonStyle(.borderedProminent) @@ -107,6 +109,22 @@ struct AppFilesView: View { } } + private func filesCountText(count: Int) -> String { + String(format: String(localized: "%lld files"), Int64(count)) + } + + private func checkingLocationsText(count: Int) -> String { + String(format: String(localized: "Checking %lld locations..."), Int64(count)) + } + + private var removeFilesLabel: String { + String( + format: String(localized: "Remove %lld files (%@)"), + Int64(appState.selectedFiles.count), + ByteCountFormatter.string(fromByteCount: totalSelectedSize, countStyle: .file) + ) + } + private func fileSelectionBinding(for url: URL) -> Binding { Binding( get: { appState.selectedFiles.contains(url) }, @@ -214,7 +232,12 @@ struct FileRow: View { .onHover { hovering in withAnimation(.easeInOut(duration: 0.15)) { isHovering = hovering } } - .alert("Remove \(fileURL.lastPathComponent)?", isPresented: $showConfirmation) { + .alert( + Text( + String(format: String(localized: "Remove %@?"), fileURL.lastPathComponent) + ), + isPresented: $showConfirmation + ) { Button("Cancel", role: .cancel) {} Button("Remove", role: .destructive) { onRemove() } } message: { diff --git a/PureMac/Views/Apps/AppListView.swift b/PureMac/Views/Apps/AppListView.swift index 057a2b3..844976d 100644 --- a/PureMac/Views/Apps/AppListView.swift +++ b/PureMac/Views/Apps/AppListView.swift @@ -33,7 +33,7 @@ struct AppListView: View { .frame(minWidth: 300) } .searchable(text: $searchText, prompt: "Search apps") - .navigationTitle("Installed Apps (\(appState.installedApps.count))") + .navigationTitle(installedAppsTitle) .toolbar { ToolbarItemGroup { Button { @@ -43,7 +43,7 @@ struct AppListView: View { } if !appState.selectedFiles.isEmpty { - Button("Uninstall (\(appState.selectedFiles.count) files)", role: .destructive) { + Button(uninstallLabel(count: appState.selectedFiles.count), role: .destructive) { appState.removeSelectedFiles() } .buttonStyle(.borderedProminent) @@ -53,13 +53,21 @@ struct AppListView: View { } } + private var installedAppsTitle: String { + String(format: String(localized: "Installed Apps (%lld)"), Int64(appState.installedApps.count)) + } + + private func uninstallLabel(count: Int) -> String { + String(format: String(localized: "Uninstall (%lld files)"), Int64(count)) + } + // MARK: - App Table (left side) private var appTable: some View { Group { if appState.isLoadingApps { VStack(spacing: 12) { - ProgressView("Loading installed apps...") + ProgressView(LocalizedStringKey("Loading installed apps...")) } .frame(maxWidth: .infinity, maxHeight: .infinity) } else if appState.installedApps.isEmpty { diff --git a/PureMac/Views/CategoryDetailView.swift b/PureMac/Views/CategoryDetailView.swift index 31e4d24..8ffc433 100644 --- a/PureMac/Views/CategoryDetailView.swift +++ b/PureMac/Views/CategoryDetailView.swift @@ -32,7 +32,7 @@ struct CategoryDetailView: View { } } .searchable(text: $searchText, prompt: "Filter files") - .navigationTitle(category.rawValue) + .navigationTitle(Text(LocalizedStringKey(category.rawValue))) .toolbar { ToolbarItem(placement: .automatic) { Button { @@ -52,12 +52,13 @@ struct CategoryDetailView: View { appState.deselectAllInCategory(category) } Button(action: { sortDescending.toggle() }) { - Label( - sortDescending ? "Largest First" : "Smallest First", - systemImage: "arrow.up.arrow.down" - ) + Label { + Text(LocalizedStringKey(sortDescending ? "Largest First" : "Smallest First")) + } icon: { + Image(systemName: "arrow.up.arrow.down") + } } - .help(sortDescending ? "Sorted: Largest First" : "Sorted: Smallest First") + .help(LocalizedStringKey(sortDescending ? "Sorted: Largest First" : "Sorted: Smallest First")) } } @@ -69,16 +70,17 @@ struct CategoryDetailView: View { Button { showConfirmation = true } label: { - Label( - "Clean \(selectedCount) items", - systemImage: "trash" - ) + Label { + Text(cleanItemsLabel(count: selectedCount)) + } icon: { + Image(systemName: "trash") + } } } } } } - .confirmationDialog("Clean \(ByteCountFormatter.string(fromByteCount: appState.selectedSizeInCategory(category), countStyle: .file))?", isPresented: $showConfirmation, titleVisibility: .visible) { + .confirmationDialog(cleanConfirmationTitle, isPresented: $showConfirmation, titleVisibility: .visible) { Button("Clean", role: .destructive) { appState.cleanCategory(category) } @@ -88,6 +90,17 @@ struct CategoryDetailView: View { } } + private func cleanItemsLabel(count: Int) -> String { + String(format: String(localized: "Clean %lld items"), Int64(count)) + } + + private var cleanConfirmationTitle: String { + String( + format: String(localized: "Clean %@?"), + ByteCountFormatter.string(fromByteCount: appState.selectedSizeInCategory(category), countStyle: .file) + ) + } + // MARK: - Hero private var heroCard: some View { @@ -100,13 +113,13 @@ struct CategoryDetailView: View { IconTile(systemName: category.icon, tint: category.color, size: 56, corner: 14) VStack(alignment: .leading, spacing: 4) { - Text(category.rawValue) + Text(LocalizedStringKey(category.rawValue)) .font(.system(size: 22, weight: .bold)) - Text(category.description) + Text(LocalizedStringKey(category.description)) .font(.system(size: 12)) .foregroundStyle(.secondary) if itemCount > 0 { - Text("\(itemCount) items · \(ByteCountFormatter.string(fromByteCount: totalSize, countStyle: .file))") + Text(itemsAndSizeText(itemCount: itemCount, totalSize: totalSize)) .font(.system(size: 11.5, weight: .medium)) .foregroundStyle(category.color) .padding(.top, 2) @@ -118,9 +131,12 @@ struct CategoryDetailView: View { Button { appState.scanSingleCategory(category) } label: { - Label(isScanning ? "Scanning…" : (result == nil ? "Scan" : "Rescan"), - systemImage: "arrow.clockwise") - .font(.system(size: 12.5, weight: .semibold)) + Label { + Text(scanButtonLabel(isScanning: isScanning, hasResult: result != nil)) + } icon: { + Image(systemName: "arrow.clockwise") + } + .font(.system(size: 12.5, weight: .semibold)) } .buttonStyle(.bordered) .controlSize(.large) @@ -129,6 +145,20 @@ struct CategoryDetailView: View { } } + private func itemsAndSizeText(itemCount: Int, totalSize: Int64) -> String { + String( + format: String(localized: "%lld items · %@"), + Int64(itemCount), + ByteCountFormatter.string(fromByteCount: totalSize, countStyle: .file) + ) + } + + private func scanButtonLabel(isScanning: Bool, hasResult: Bool) -> LocalizedStringKey { + if isScanning { return "Scanning…" } + if hasResult { return "Rescan" } + return "Scan" + } + // MARK: - File List private func fileList(_ result: CategoryResult) -> some View { @@ -143,7 +173,13 @@ struct CategoryDetailView: View { } header: { let selectedCount = appState.selectedCountInCategory(category) let totalCount = result.itemCount - Text("\(selectedCount) of \(totalCount) selected") + Text( + String( + format: String(localized: "%lld of %lld selected"), + Int64(selectedCount), + Int64(totalCount) + ) + ) } } } diff --git a/PureMac/Views/Components/AppearancePill.swift b/PureMac/Views/Components/AppearancePill.swift index b6d48c8..ea14236 100644 --- a/PureMac/Views/Components/AppearancePill.swift +++ b/PureMac/Views/Components/AppearancePill.swift @@ -31,7 +31,7 @@ struct AppearancePill: View { .contentShape(Rectangle()) } .buttonStyle(.plain) - .help(mode.label) + .help(LocalizedStringKey(mode.label)) } } } diff --git a/PureMac/Views/DashboardView.swift b/PureMac/Views/DashboardView.swift index 262276c..5872f98 100644 --- a/PureMac/Views/DashboardView.swift +++ b/PureMac/Views/DashboardView.swift @@ -43,7 +43,7 @@ struct DashboardView: View { } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .confirmationDialog( - "Clean \(ByteCountFormatter.string(fromByteCount: appState.totalSelectedSize, countStyle: .file))?", + cleanConfirmationTitle, isPresented: $showConfirmation, titleVisibility: .visible ) { @@ -54,6 +54,13 @@ struct DashboardView: View { } } + private var cleanConfirmationTitle: String { + String( + format: String(localized: "Clean %@?"), + ByteCountFormatter.string(fromByteCount: appState.totalSelectedSize, countStyle: .file) + ) + } + // MARK: - Hero (idle) private var hero: some View { @@ -76,7 +83,7 @@ struct DashboardView: View { Text(ByteCountFormatter.string(fromByteCount: free, countStyle: .file)) .font(.system(size: 30, weight: .bold)) .monospacedDigit() - Text("free of \(ByteCountFormatter.string(fromByteCount: total, countStyle: .file))") + Text(freeOfText(total: total)) .font(.system(size: 12)) .foregroundStyle(.secondary) } @@ -98,9 +105,15 @@ struct DashboardView: View { } } + private func freeOfText(total: Int64) -> String { + String( + format: String(localized: "free of %@"), + ByteCountFormatter.string(fromByteCount: total, countStyle: .file) + ) + } + private func storageBreakdown(used: Int64, total: Int64) -> some View { let usedPct = total > 0 ? Double(used) / Double(total) : 0 - let purgePct = total > 0 ? Double(appState.diskInfo.purgeableSpace) / Double(total) : 0 let junkPct = total > 0 ? min(0.4, Double(appState.totalJunkSize) / Double(total)) : 0 return VStack(alignment: .leading, spacing: 8) { @@ -135,7 +148,7 @@ struct DashboardView: View { value: ByteCountFormatter.string(fromByteCount: appState.diskInfo.purgeableSpace, countStyle: .file)) } Spacer() - Text("\(Int(usedPct * 100))% used") + Text(percentUsedText(usedPct)) .font(.system(size: 11, weight: .medium)) .foregroundStyle(.secondary) .monospacedDigit() @@ -143,6 +156,10 @@ struct DashboardView: View { } } + private func percentUsedText(_ usedPct: Double) -> String { + String(format: String(localized: "%lld%% used"), Int64(usedPct * 100)) + } + // MARK: - Stats private var stats: some View { @@ -156,7 +173,7 @@ struct DashboardView: View { tint: Tint.blue, label: "Free Space", value: ByteCountFormatter.string(fromByteCount: free, countStyle: .file), - delta: total > 0 ? "of \(ByteCountFormatter.string(fromByteCount: total, countStyle: .file)) · \(Int(percentUsed * 100))% used" : nil + delta: total > 0 ? freeSpaceDelta(total: total, percentUsed: percentUsed) : nil ) StatCard( icon: "trash.circle.fill", @@ -165,14 +182,16 @@ struct DashboardView: View { value: appState.totalJunkSize > 0 ? ByteCountFormatter.string(fromByteCount: appState.totalJunkSize, countStyle: .file) : "—", - delta: appState.allResults.isEmpty ? "Run a scan" : "across \(appState.allResults.count) categories" + delta: appState.allResults.isEmpty + ? String(localized: "Run a scan") + : junkFoundDelta(count: appState.allResults.count) ) StatCard( icon: "square.grid.2x2.fill", tint: Tint.purple, label: "Apps", value: "\(appState.installedApps.count)", - delta: "installed" + delta: String(localized: "installed") ) StatCard( icon: "memorychip.fill", @@ -181,11 +200,23 @@ struct DashboardView: View { value: appState.diskInfo.purgeableSpace > 0 ? ByteCountFormatter.string(fromByteCount: appState.diskInfo.purgeableSpace, countStyle: .file) : "—", - delta: "APFS reclaimable" + delta: String(localized: "APFS reclaimable") ) } } + private func freeSpaceDelta(total: Int64, percentUsed: Double) -> String { + String( + format: String(localized: "of %@ · %lld%% used"), + ByteCountFormatter.string(fromByteCount: total, countStyle: .file), + Int64(percentUsed * 100) + ) + } + + private func junkFoundDelta(count: Int) -> String { + String(format: String(localized: "across %lld categories"), Int64(count)) + } + // MARK: - Suggestions private var suggestions: some View { @@ -200,11 +231,16 @@ struct DashboardView: View { var out: [Suggestion] = [] // Surface the largest pending category as a contextual nudge. if let biggest = appState.allResults.max(by: { $0.totalSize < $1.totalSize }), biggest.totalSize > 0 { + let title = String( + format: String(localized: "%@ is using %@"), + String(localized: String.LocalizationValue(biggest.category.rawValue)), + biggest.formattedSize + ) out.append(Suggestion( icon: biggest.category.icon, tint: biggest.category.color, - title: "\(biggest.category.rawValue) is using \(biggest.formattedSize)", - subtitle: biggest.category.description, + title: title, + subtitle: String(localized: String.LocalizationValue(biggest.category.description)), pill: biggest.formattedSize )) } @@ -212,9 +248,9 @@ struct DashboardView: View { out.append(Suggestion( icon: "lock.shield.fill", tint: Tint.orange, - title: "Grant Full Disk Access for full results", - subtitle: "Without it, most caches and uninstall flows fail.", - pill: "Action" + title: String(localized: "Grant Full Disk Access for full results"), + subtitle: String(localized: "Without it, most caches and uninstall flows fail."), + pill: String(localized: "Action") )) } return out @@ -230,7 +266,7 @@ struct DashboardView: View { VStack(alignment: .leading, spacing: 8) { Text("Scanning your Mac") .font(.system(size: 22, weight: .bold)) - Text("Currently in: \(appState.currentScanCategory)") + Text(currentlyInText) .font(.system(size: 13)) .foregroundStyle(.secondary) ProgressView(value: appState.scanProgress) @@ -243,13 +279,20 @@ struct DashboardView: View { } } + private var currentlyInText: String { + String( + format: String(localized: "Currently in: %@"), + String(localized: String.LocalizationValue(appState.currentScanCategory)) + ) + } + private var liveResults: some View { CardSurface(padding: 0) { VStack(spacing: 0) { ForEach(appState.allResults.prefix(8)) { result in HStack(spacing: 12) { IconTile(systemName: result.category.icon, tint: result.category.color, size: 26) - Text(result.category.rawValue) + Text(LocalizedStringKey(result.category.rawValue)) .font(.system(size: 13)) Spacer() Text(result.formattedSize) @@ -295,10 +338,11 @@ struct DashboardView: View { Button { showConfirmation = true } label: { - Label( - "Clean \(ByteCountFormatter.string(fromByteCount: appState.totalSelectedSize, countStyle: .file))", - systemImage: "sparkles" - ) + Label { + Text(cleanSelectedLabel) + } icon: { + Image(systemName: "sparkles") + } .font(.system(size: 13, weight: .semibold)) .padding(.horizontal, 6) } @@ -312,6 +356,13 @@ struct DashboardView: View { } } + private var cleanSelectedLabel: String { + String( + format: String(localized: "Clean %@"), + ByteCountFormatter.string(fromByteCount: appState.totalSelectedSize, countStyle: .file) + ) + } + private var resultsList: some View { CardSurface(padding: 0) { VStack(spacing: 0) { @@ -333,7 +384,7 @@ struct DashboardView: View { VStack(alignment: .leading, spacing: 8) { Text("Cleaning…") .font(.system(size: 22, weight: .bold)) - Text("\(Int(appState.cleanProgress * 100))% complete") + Text(percentCompleteText) .font(.system(size: 13)) .foregroundStyle(.secondary) } @@ -342,6 +393,10 @@ struct DashboardView: View { } } + private var percentCompleteText: String { + String(format: String(localized: "%lld%% complete"), Int64(appState.cleanProgress * 100)) + } + private var cleanedHero: some View { CardSurface(padding: 24) { HStack(alignment: .center, spacing: 28) { @@ -372,7 +427,7 @@ struct DashboardView: View { // MARK: - Helpers - private func sectionHeader(_ text: String) -> some View { + private func sectionHeader(_ text: LocalizedStringKey) -> some View { Text(text) .font(.system(size: 16, weight: .bold)) .padding(.top, 4) @@ -384,7 +439,7 @@ struct DashboardView: View { private struct StatCard: View { let icon: String let tint: Color - let label: String + let label: LocalizedStringKey let value: String let delta: String? @@ -492,7 +547,7 @@ private struct StorageGauge: View { private struct LegendDot: View { let color: Color - let label: String + let label: LocalizedStringKey let value: String var body: some View { @@ -567,7 +622,7 @@ private struct CategoryToggleRow: View { VStack(alignment: .leading, spacing: 1) { Text(LocalizedStringKey(result.category.rawValue)) .font(.system(size: 13.5, weight: .semibold)) - Text("\(result.itemCount) items") + Text(itemsCountText) .font(.system(size: 11.5)) .foregroundStyle(.secondary) } @@ -582,4 +637,8 @@ private struct CategoryToggleRow: View { .padding(.horizontal, 16) .padding(.vertical, 10) } + + private var itemsCountText: String { + String(format: String(localized: "%lld items"), Int64(result.itemCount)) + } } diff --git a/PureMac/Views/MainWindow.swift b/PureMac/Views/MainWindow.swift index 754d3c8..7511b15 100644 --- a/PureMac/Views/MainWindow.swift +++ b/PureMac/Views/MainWindow.swift @@ -58,7 +58,7 @@ struct MainWindow: View { Section { ForEach(CleaningCategory.scannable) { category in navRow(section: .cleaning(category), - label: category.rawValue, + label: LocalizedStringKey(category.rawValue), icon: category.icon, tint: category.color, badge: sizeBadge(for: category)) @@ -72,7 +72,7 @@ struct MainWindow: View { } } - private func sectionLabel(_ text: String) -> some View { + private func sectionLabel(_ text: LocalizedStringKey) -> some View { Text(text) .font(.system(size: 10.5, weight: .semibold)) .tracking(0.5) @@ -80,7 +80,7 @@ struct MainWindow: View { .textCase(.uppercase) } - private func navRow(section: AppSection, label: String, icon: String, + private func navRow(section: AppSection, label: LocalizedStringKey, icon: String, tint: Color, badge: String?) -> some View { HStack(spacing: 10) { IconTile(systemName: icon, tint: tint, size: 24) @@ -125,9 +125,9 @@ struct MainWindow: View { .frame(width: 18, height: 18) ) VStack(alignment: .leading, spacing: 1) { - Text(appState.hasFullDiskAccess ? "Ready to clean" : "Limited access") + Text(LocalizedStringKey(appState.hasFullDiskAccess ? "Ready to clean" : "Limited access")) .font(.system(size: 12, weight: .semibold)) - Text(appState.hasFullDiskAccess ? "Full Disk Access granted" : "Grant FDA in Settings") + Text(LocalizedStringKey(appState.hasFullDiskAccess ? "Full Disk Access granted" : "Grant FDA in Settings")) .font(.system(size: 10.5)) .foregroundStyle(.secondary) } diff --git a/PureMac/Views/OnboardingView.swift b/PureMac/Views/OnboardingView.swift index 68ef48f..f0ece19 100644 --- a/PureMac/Views/OnboardingView.swift +++ b/PureMac/Views/OnboardingView.swift @@ -139,7 +139,7 @@ struct OnboardingView: View { )) } - private func featureCard(icon: String, title: String, desc: String, delay: Double) -> some View { + private func featureCard(icon: String, title: LocalizedStringKey, desc: LocalizedStringKey, delay: Double) -> some View { VStack(spacing: 8) { Image(systemName: icon) .font(.title2) @@ -255,7 +255,7 @@ struct OnboardingView: View { refreshAccessChecks() } Divider() - Button(showDiagnostics ? "Hide diagnostics" : "Show diagnostics") { + Button(LocalizedStringKey(showDiagnostics ? "Hide diagnostics" : "Show diagnostics")) { showDiagnostics.toggle() } } label: { @@ -292,14 +292,14 @@ struct OnboardingView: View { .transition(.opacity.combined(with: .scale)) } - private func stepRow(number: Int, text: String) -> some View { + private func stepRow(number: Int, text: LocalizedStringKey) -> some View { HStack(alignment: .firstTextBaseline, spacing: 10) { Text("\(number)") .font(.callout.bold()) .foregroundStyle(.white) .frame(width: 22, height: 22) .background(Circle().fill(Color.accentColor)) - Text(.init(text)) + Text(text) .font(.callout) Spacer() } @@ -315,10 +315,10 @@ struct OnboardingView: View { Image(systemName: path.icon) .foregroundStyle(.secondary) .frame(width: 14) - Text(path.label) + Text(LocalizedStringKey(path.label)) .font(.caption) Spacer() - Text(path.accessible ? "OK" : "Blocked") + Text(LocalizedStringKey(path.accessible ? "OK" : "Blocked")) .font(.caption2) .foregroundStyle(path.accessible ? .green : .orange) } @@ -359,7 +359,7 @@ struct OnboardingView: View { HStack(spacing: 8) { Image(systemName: hasFullDiskAccess ? "checkmark.circle.fill" : "exclamationmark.triangle.fill") .foregroundStyle(hasFullDiskAccess ? .green : .orange) - Text("\(granted)/\(total) protected locations accessible") + Text(grantedSummary(granted: granted, total: total)) .foregroundStyle(.secondary) } @@ -381,6 +381,14 @@ struct OnboardingView: View { )) } + private func grantedSummary(granted: Int, total: Int) -> String { + String( + format: String(localized: "%lld/%lld protected locations accessible"), + Int64(granted), + Int64(total) + ) + } + // MARK: - Permission Checking private func refreshAccessChecks() { diff --git a/PureMac/Views/Orphans/OrphanListView.swift b/PureMac/Views/Orphans/OrphanListView.swift index 6305b26..0343838 100644 --- a/PureMac/Views/Orphans/OrphanListView.swift +++ b/PureMac/Views/Orphans/OrphanListView.swift @@ -10,7 +10,7 @@ struct OrphanListView: View { Group { if appState.isSearchingOrphans { VStack(spacing: 16) { - ProgressView("Scanning for orphaned files...") + ProgressView(LocalizedStringKey("Scanning for orphaned files...")) .progressViewStyle(.linear) .frame(maxWidth: 300) } @@ -53,11 +53,11 @@ struct OrphanListView: View { } } } - .navigationTitle("Orphaned Files (\(appState.orphanedFiles.count))") + .navigationTitle(orphanedFilesTitle) .toolbar { ToolbarItemGroup { if !appState.orphanedFiles.isEmpty { - Button(selectedOrphans.count == appState.orphanedFiles.count ? "Deselect All" : "Select All") { + Button(LocalizedStringKey(selectedOrphans.count == appState.orphanedFiles.count ? "Deselect All" : "Select All")) { if selectedOrphans.count == appState.orphanedFiles.count { selectedOrphans.removeAll() } else { @@ -71,7 +71,7 @@ struct OrphanListView: View { } if !selectedOrphans.isEmpty { - Button("Remove Selected (\(selectedOrphans.count))", role: .destructive) { + Button(removeSelectedLabel, role: .destructive) { Task { await removeSelectedOrphans() } @@ -92,6 +92,14 @@ struct OrphanListView: View { } } + private var orphanedFilesTitle: String { + String(format: String(localized: "Orphaned Files (%lld)"), Int64(appState.orphanedFiles.count)) + } + + private var removeSelectedLabel: String { + String(format: String(localized: "Remove Selected (%lld)"), Int64(selectedOrphans.count)) + } + private func orphanBinding(for url: URL) -> Binding { Binding( get: { selectedOrphans.contains(url) }, diff --git a/PureMac/Views/Settings/SettingsView.swift b/PureMac/Views/Settings/SettingsView.swift index 80996e0..71e2520 100644 --- a/PureMac/Views/Settings/SettingsView.swift +++ b/PureMac/Views/Settings/SettingsView.swift @@ -1,3 +1,4 @@ +import AppKit import SwiftUI import ServiceManagement @@ -13,7 +14,7 @@ struct SettingsView: View { AboutSettingsView() .tabItem { Label("About", systemImage: "info.circle") } } - .frame(width: 480, height: 360) + .frame(width: 480, height: 430) } } @@ -39,22 +40,21 @@ struct GeneralSettingsView: View { @AppStorage("settings.general.launchAtLogin") private var launchAtLogin = false @AppStorage("settings.general.searchSensitivity") private var sensitivity: SearchSensitivity = .enhanced @AppStorage("settings.general.confirmBeforeDelete") private var confirmBeforeDelete = true + @AppStorage(AppLanguage.preferenceKey) private var appLanguageRaw = AppLanguage.current.rawValue + @State private var languageNeedsRelaunch = false var body: some View { Form { Section("Startup") { - Toggle("Launch PureMac at login", isOn: $launchAtLogin) - .onChange(of: launchAtLogin) { newValue in - toggleLaunchAtLogin(newValue) - } + Toggle("Launch PureMac at login", isOn: launchAtLoginBinding) } Section("App Scanning") { Picker("Search sensitivity", selection: $sensitivity) { ForEach(SearchSensitivity.allCases) { level in VStack(alignment: .leading) { - Text(level.rawValue) - Text(level.description) + Text(LocalizedStringKey(level.rawValue)) + Text(LocalizedStringKey(level.description)) .font(.caption) .foregroundStyle(.secondary) } @@ -64,6 +64,24 @@ struct GeneralSettingsView: View { .pickerStyle(.radioGroup) } + Section("Language") { + Picker("Language", selection: appLanguageBinding) { + ForEach(AppLanguage.allCases) { language in + Text(LocalizedStringKey(language.displayName)).tag(language) + } + } + + if languageNeedsRelaunch { + Text("Restart PureMac to apply the selected language.") + .font(.caption) + .foregroundStyle(.secondary) + + Button("Relaunch Now") { + relaunchApp() + } + } + } + Section("Safety") { Toggle("Confirm before deleting files", isOn: $confirmBeforeDelete) } @@ -71,6 +89,26 @@ struct GeneralSettingsView: View { .formStyle(.grouped) } + private var launchAtLoginBinding: Binding { + Binding( + get: { launchAtLogin }, + set: { newValue in + launchAtLogin = newValue + toggleLaunchAtLogin(newValue) + } + ) + } + + private var appLanguageBinding: Binding { + Binding( + get: { AppLanguage(rawValue: appLanguageRaw) ?? .system }, + set: { newValue in + appLanguageRaw = newValue.rawValue + applyLanguage(newValue) + } + ) + } + private func toggleLaunchAtLogin(_ enabled: Bool) { do { if enabled { @@ -80,10 +118,32 @@ struct GeneralSettingsView: View { } } catch { Logger.shared.log("Failed to \(enabled ? "enable" : "disable") launch at login: \(error.localizedDescription)", level: .error) - // Revert the toggle if operation failed launchAtLogin = !enabled } } + + private func applyLanguage(_ language: AppLanguage) { + AppLanguagePreferences.apply(language) + languageNeedsRelaunch = true + } + + private func relaunchApp() { + guard Bundle.main.bundleURL.pathExtension == "app" else { + NSApp.terminate(nil) + return + } + + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/bin/open") + task.arguments = ["-n", Bundle.main.bundleURL.path] + + do { + try task.run() + NSApp.terminate(nil) + } catch { + Logger.shared.log("Failed to relaunch PureMac: \(error.localizedDescription)", level: .error) + } + } } // MARK: - Cleaning @@ -100,8 +160,17 @@ struct CleaningSettingsView: View { } Section("Large Files") { - Stepper("Minimum size: \(largeFileThresholdMB) MB", value: $largeFileThresholdMB, in: 10...1000, step: 10) - Stepper("Files older than: \(oldFileMonths) months", value: $oldFileMonths, in: 1...60) + Stepper( + String(format: String(localized: "Minimum size: %lld MB"), Int64(largeFileThresholdMB)), + value: $largeFileThresholdMB, + in: 10...1000, + step: 10 + ) + Stepper( + String(format: String(localized: "Files older than: %lld months"), Int64(oldFileMonths)), + value: $oldFileMonths, + in: 1...60 + ) } } .formStyle(.grouped) @@ -121,7 +190,7 @@ struct ScheduleSettingsView: View { if appState.scheduler.config.isEnabled { Picker("Scan interval", selection: $appState.scheduler.config.interval) { ForEach(ScheduleInterval.allCases) { interval in - Text(interval.rawValue).tag(interval) + Text(LocalizedStringKey(interval.rawValue)).tag(interval) } } @@ -157,7 +226,12 @@ struct AboutSettingsView: View { VStack(alignment: .leading, spacing: 4) { Text("PureMac") .font(.title2.bold()) - Text("Version \(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0")") + Text( + String( + format: String(localized: "Version %@"), + Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0" + ) + ) .foregroundStyle(.secondary) Text("Free, open-source macOS app manager.") .foregroundStyle(.secondary) diff --git a/PureMac/ar.lproj/Localizable.strings b/PureMac/ar.lproj/Localizable.strings index f25edfa..46505d3 100644 --- a/PureMac/ar.lproj/Localizable.strings +++ b/PureMac/ar.lproj/Localizable.strings @@ -1,116 +1,245 @@ -/* Full Disk Access Banner */ -"Full Disk Access Required" = "يلزم منح الوصول الكامل إلى القرص"; -"PureMac needs Full Disk Access to scan Trash, Mail, Desktop, Documents, and Homebrew cache." = "يحتاج PureMac إلى الوصول الكامل للقرص لفحص سلة المهملات والبريد وسطح المكتب والمستندات وذاكرة التخزين المؤقت لـ Homebrew."; -"Open Settings" = "فتح الإعدادات"; - -/* Top Bar */ -"Macintosh HD" = "Macintosh HD"; -"%@ free" = "%@ متاحة"; -"Auto-clean: " = "التنظيف التلقائي: "; - -/* Sidebar */ -"CLEANING" = "التنظيف"; -"Last cleaned: %@" = "آخر تنظيف: %@"; -"Home" = "الرئيسية"; +/* Sidebar (MainWindow) */ +"Overview" = "نظرة عامة"; "Applications" = "التطبيقات"; -"Cleaning" = "التنظيف"; +"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." = "اختر فئة من الشريط الجانبي للبدء."; -/* Smart Scan */ +/* 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" = "منح الوصول"; + +/* Dashboard */ +"Storage" = "التخزين"; "Smart Scan" = "الفحص الذكي"; -"Click Scan to start" = "انقر على فحص للبدء"; -"Total" = "الإجمالي"; +"free of %@" = "متاح من %@"; +"%lld%% used" = "%lld%% مستخدمة"; "Used" = "المستخدمة"; -"Free" = "المتاحة"; -"Purgeable" = "قابلة للتحرير"; -"junk found" = "ملفات غير مرغوبة"; -"Your Mac is clean!" = "جهاز Mac نظيف!"; -"freed up" = "تم تحريرها"; -"Cleaning..." = "جارٍ التنظيف..."; -"Scanning..." = "جارٍ الفحص..."; -"%lld/%lld items" = "%lld/%lld عنصر"; +"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" = "حسب الفئة"; +"%@ 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!" = "كل شيء نظيف!"; +"All Clean" = "كل شيء نظيف"; "No junk files found in this category." = "لا توجد ملفات غير مرغوبة في هذه الفئة."; -"Not scanned yet" = "لم يتم الفحص بعد"; -"Click Scan to analyze this category" = "انقر على فحص لتحليل هذه الفئة"; -"%lld of %lld selected" = "%lld من %lld محدد"; +"Not Scanned" = "لم يتم الفحص"; +"Run a scan to analyze this category." = "ابدأ فحصًا لتحليل هذه الفئة."; +"Scan Now" = "ابدأ الفحص الآن"; +"Filter files" = "تصفية الملفات"; +"Scan" = "فحص"; "Select All" = "تحديد الكل"; "Deselect All" = "إلغاء تحديد الكل"; -"%lld items" = "%lld عنصر"; +"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"; -/* Buttons */ -"Scan" = "فحص"; -"Re-scan" = "إعادة الفحص"; -"Scan Again" = "فحص مرة أخرى"; -"Done" = "تم"; -"Clean (%@)" = "تنظيف (%@)"; -"Clean %lld items (%@)" = "تنظيف %lld عنصر (%@)"; +/* 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." = "اختر تطبيقًا من القائمة لعرض جميع الملفات المرتبطة به في النظام."; -/* Settings - Schedule */ -"Schedule" = "الجدولة"; -"Automatic Cleaning" = "التنظيف التلقائي"; -"Automatically scan and clean your Mac on a schedule" = "فحص جهاز Mac وتنظيفه تلقائيًا وفق جدول زمني"; -"Scan Interval" = "الفاصل الزمني للفحص"; -"Automation" = "الأتمتة"; -"Auto-clean after scan" = "التنظيف التلقائي بعد الفحص"; -"Minimum junk size to trigger clean:" = "الحد الأدنى لحجم الملفات لبدء التنظيف:"; -"50 MB" = "50 ميغابايت"; -"100 MB" = "100 ميغابايت"; -"250 MB" = "250 ميغابايت"; -"500 MB" = "500 ميغابايت"; -"1 GB" = "1 غيغابايت"; -"Auto-purge purgeable space" = "تحرير المساحة القابلة للإزالة تلقائيًا"; -"Show notification on completion" = "إظهار إشعار عند الانتهاء"; -"Status" = "الحالة"; -"Last run" = "آخر تشغيل"; -"Next run" = "التشغيل التالي"; -"Never" = "أبدًا"; -"Not scheduled" = "لم تُجدول"; +/* 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." = "سيؤدي ذلك إلى حذف هذا الملف نهائيًا. لا يمكن التراجع عن هذا الإجراء."; -/* Settings - General */ -"General" = "عام"; -"App Behavior" = "سلوك التطبيق"; -"Launch at login" = "التشغيل عند تسجيل الدخول"; -"Show in Dock" = "إظهار في Dock"; -"Show menu bar icon" = "إظهار أيقونة في شريط القوائم"; -"Safety" = "الأمان"; -"PureMac will never delete system-critical files. Only caches, logs, temporary files, and user-selected items are removed." = "لن يحذف PureMac أبدًا ملفات النظام الأساسية. لا تُزال سوى ذاكرة التخزين المؤقت والسجلات والملفات المؤقتة والعناصر التي يحددها المستخدم."; +/* 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" = "تعذّرت إزالة بعض الملفات"; -/* Settings - About */ +/* 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. You can grant Full Disk Access later in System Settings." = "ستكون بعض الميزات محدودة. يمكنك منح الوصول الكامل للقرص لاحقًا من إعدادات النظام."; +"Trash" = "سلة المهملات"; +"Mail Data" = "بيانات البريد"; +"Safari Data" = "بيانات Safari"; +"Desktop" = "سطح المكتب"; +"Documents" = "المستندات"; +"TCC Database" = "قاعدة بيانات TCC"; +"Blocked" = "محظور"; + +/* Settings */ +"General" = "عام"; +"Cleaning" = "التنظيف"; +"Schedule" = "الجدولة"; "About" = "حول"; -"Version 1.0.0" = "الإصدار 1.0.0"; -"A free, open-source Mac cleaning utility.\nKeep your Mac fast, clean, and optimized." = "أداة مجانية ومفتوحة المصدر لتنظيف الـ Mac.\nحافظ على سرعة جهازك ونظافته وكفاءته."; +"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" = "العربية"; +"Portuguese (Brazil)" = "البرتغالية (البرازيل)"; +"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"; -"MIT License" = "رخصة MIT"; +"Report an Issue" = "الإبلاغ عن مشكلة"; +"MIT License" = "ترخيص MIT"; -/* Cleaning Categories */ -"System Junk" = "ملفات النظام غير المرغوبة"; +/* Cleaning categories (CleaningCategory.rawValue) */ +"System Junk" = "نفايات النظام"; "User Cache" = "ذاكرة التخزين المؤقت للمستخدم"; "AI Apps" = "تطبيقات الذكاء الاصطناعي"; "Mail Files" = "ملفات البريد"; -"Trash Bins" = "سلة المهملات"; +"Trash Bins" = "سلات المهملات"; "Large & Old Files" = "الملفات الكبيرة والقديمة"; -"Purgeable Space" = "المساحة القابلة للإزالة"; -"Xcode Junk" = "ملفات Xcode غير المرغوبة"; -"Brew Cache" = "ذاكرة التخزين المؤقت لـ Brew"; - -/* Category Descriptions */ -"Scan everything at once" = "فحص كل شيء مرة واحدة"; -"System caches, logs, and temporary files" = "ذاكرة التخزين المؤقت للنظام والسجلات والملفات المؤقتة"; -"Application caches and browser data" = "ذاكرة التخزين المؤقت للتطبيقات وبيانات المتصفح"; -"Logs, caches, and temporary files from local AI apps" = "السجلات وذاكرة التخزين المؤقت والملفات المؤقتة لتطبيقات الذكاء الاصطناعي المحلية"; +"Purgeable Space" = "المساحة القابلة للتحرير"; +"Xcode Junk" = "نفايات Xcode"; +"Brew Cache" = "ذاكرة Brew المؤقتة"; +"Node Cache" = "ذاكرة Node المؤقتة"; +"Docker Cache" = "ذاكرة Docker المؤقتة"; + +/* 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 ميغابايت أو الأقدم من سنة"; -"APFS purgeable disk space" = "مساحة القرص القابلة للإزالة في APFS"; +"Files over 100 MB or older than 1 year" = "الملفات التي يتجاوز حجمها 100 ميغابايت أو أقدم من سنة"; +"APFS purgeable disk space" = "مساحة القرص القابلة للتحرير في APFS"; "Derived data, archives, and simulators" = "البيانات المشتقة والأرشيفات والمحاكيات"; -"Homebrew download cache" = "ذاكرة التخزين المؤقت لتنزيلات Homebrew"; +"Homebrew download cache" = "ذاكرة تنزيلات Homebrew المؤقتة"; +"npm, yarn, and pnpm download caches" = "ذاكرات تنزيلات npm وyarn وpnpm المؤقتة"; +"Docker images, containers, and build cache" = "صور Docker والحاويات وذاكرة البناء المؤقتة"; -/* Schedule Intervals */ +/* 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`)"; + +/* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "كل ساعة"; "Every 3 Hours" = "كل 3 ساعات"; "Every 6 Hours" = "كل 6 ساعات"; @@ -120,17 +249,14 @@ "Every 2 Weeks" = "كل أسبوعين"; "Monthly" = "شهريًا"; +/* Schedule status */ +"Never" = "أبدًا"; +"Not scheduled" = "غير مجدول"; + +/* Appearance modes (AppearanceMode.label) */ +"System" = "النظام"; +"Light" = "فاتح"; +"Dark" = "داكن"; + /* Notifications */ "Found %@ of junk files." = "تم العثور على %@ من الملفات غير المرغوبة."; - -/* Node Cache */ -"Node Cache" = "ذاكرة التخزين المؤقت لـ Node"; -"npm, yarn, and pnpm download caches" = "ذاكرة التخزين المؤقت لتنزيلات npm وyarn وpnpm"; -"npm cache" = "ذاكرة التخزين المؤقت لـ npm"; -"yarn classic cache" = "ذاكرة التخزين المؤقت لـ yarn الكلاسيكي"; -"pnpm content-addressable store" = "مخزن المحتوى المُعنوَن لـ pnpm"; - -/* Docker Cache */ -"Docker Cache" = "ذاكرة التخزين المؤقت لـ Docker"; -"Docker images, containers, and build cache" = "صور Docker والحاويات وذاكرة التخزين المؤقت للبناء"; -"Reclaimable (run `docker system prune -af`)" = "قابلة للاسترداد (شغّل `docker system prune -af`)"; diff --git a/PureMac/en.lproj/Localizable.strings b/PureMac/en.lproj/Localizable.strings index 2dbf3a2..72c93ed 100644 --- a/PureMac/en.lproj/Localizable.strings +++ b/PureMac/en.lproj/Localizable.strings @@ -1,93 +1,210 @@ -/* Full Disk Access Banner */ -"Full Disk Access Required" = "Full Disk Access Required"; -"PureMac needs Full Disk Access to scan Trash, Mail, Desktop, Documents, and Homebrew cache." = "PureMac needs Full Disk Access to scan Trash, Mail, Desktop, Documents, and Homebrew cache."; -"Open Settings" = "Open Settings"; - -/* Top Bar */ -"Macintosh HD" = "Macintosh HD"; -"%@ free" = "%@ free"; -"Auto-clean: " = "Auto-clean: "; - -/* Sidebar */ -"CLEANING" = "CLEANING"; -"Last cleaned: %@" = "Last cleaned: %@"; -"Home" = "Home"; +/* Sidebar (MainWindow) */ +"Overview" = "Overview"; "Applications" = "Applications"; -"Cleaning" = "Cleaning"; +"Cleanup" = "Cleanup"; +"Dashboard" = "Dashboard"; "Installed Apps" = "Installed Apps"; "Orphaned Files" = "Orphaned Files"; +"PureMac" = "PureMac"; +"Ready to clean" = "Ready to clean"; +"Limited access" = "Limited access"; +"Full Disk Access granted" = "Full Disk Access granted"; +"Grant FDA in Settings" = "Grant FDA in Settings"; "Select a category from the sidebar to get started." = "Select a category from the sidebar to get started."; -/* Smart Scan */ +/* FDA toast + clean error alert (MainWindow) */ +"Couldn't clean everything" = "Couldn't clean everything"; +"Open System Settings" = "Open System Settings"; +"OK" = "OK"; +"Full Disk Access required" = "Full Disk Access required"; +"macOS blocks PureMac from cleaning caches and uninstalling apps until you grant access." = "macOS blocks PureMac from cleaning caches and uninstalling apps until you grant access."; +"Grant Access" = "Grant Access"; + +/* Dashboard */ +"Storage" = "Storage"; "Smart Scan" = "Smart Scan"; -"Click Scan to start" = "Click Scan to start"; -"Total" = "Total"; +"free of %@" = "free of %@"; +"%lld%% used" = "%lld%% used"; "Used" = "Used"; -"Free" = "Free"; +"Junk" = "Junk"; "Purgeable" = "Purgeable"; -"junk found" = "junk found"; -"Your Mac is clean!" = "Your Mac is clean!"; -"freed up" = "freed up"; -"Cleaning..." = "Cleaning..."; -"Scanning..." = "Scanning..."; -"%lld/%lld items" = "%lld/%lld items"; +"USED" = "USED"; +"SCANNING" = "SCANNING"; +"Free Space" = "Free Space"; +"Junk Found" = "Junk Found"; +"Apps" = "Apps"; +"of %@ · %lld%% used" = "of %@ · %lld%% used"; +"Run a scan" = "Run a scan"; +"across %lld categories" = "across %lld categories"; +"installed" = "installed"; +"APFS reclaimable" = "APFS reclaimable"; +"Suggested for you" = "Suggested for you"; +"Found so far" = "Found so far"; +"By category" = "By 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."; +"Action" = "Action"; +"Scanning your Mac" = "Scanning your Mac"; +"Currently in: %@" = "Currently in: %@"; +"found" = "found"; +"Your Mac is clean" = "Your Mac is clean"; +"Scan Again" = "Scan Again"; +"Clean %@" = "Clean %@"; +"Clean %@?" = "Clean %@?"; +"Cleaning…" = "Cleaning…"; +"%lld%% complete" = "%lld%% complete"; +"freed" = "freed"; +"Done" = "Done"; +"%lld items" = "%lld items"; + +/* Common destructive dialog */ +"Clean" = "Clean"; +"Cancel" = "Cancel"; +"This will permanently delete the selected files. This cannot be undone." = "This will permanently delete the selected files. This cannot be undone."; /* Category Detail */ -"All Clean!" = "All Clean!"; +"All Clean" = "All Clean"; "No junk files found in this category." = "No junk files found in this category."; -"Not scanned yet" = "Not scanned yet"; -"Click Scan to analyze this category" = "Click Scan to analyze this category"; -"%lld of %lld selected" = "%lld of %lld selected"; +"Not Scanned" = "Not Scanned"; +"Run a scan to analyze this category." = "Run a scan to analyze this category."; +"Scan Now" = "Scan Now"; +"Filter files" = "Filter files"; +"Scan" = "Scan"; "Select All" = "Select All"; "Deselect All" = "Deselect All"; -"%lld items" = "%lld items"; +"Largest First" = "Largest First"; +"Smallest First" = "Smallest First"; +"Sorted: Largest First" = "Sorted: Largest First"; +"Sorted: Smallest First" = "Sorted: Smallest First"; +"Clean %lld items" = "Clean %lld items"; +"%lld items · %@" = "%lld items · %@"; +"Scanning…" = "Scanning…"; +"Rescan" = "Rescan"; +"%lld of %lld selected" = "%lld of %lld selected"; +"Reveal in Finder" = "Reveal in Finder"; -/* Buttons */ -"Scan" = "Scan"; -"Re-scan" = "Re-scan"; -"Scan Again" = "Scan Again"; -"Done" = "Done"; -"Clean (%@)" = "Clean (%@)"; -"Clean %lld items (%@)" = "Clean %lld items (%@)"; +/* Apps (AppListView) */ +"Search apps" = "Search apps"; +"Refresh" = "Refresh"; +"Installed Apps (%lld)" = "Installed Apps (%lld)"; +"Uninstall (%lld files)" = "Uninstall (%lld files)"; +"Loading installed apps..." = "Loading installed apps..."; +"No Apps Found" = "No Apps Found"; +"Could not find any installed applications." = "Could not find any installed applications."; +"Retry" = "Retry"; +"Application" = "Application"; +"Size" = "Size"; +"Select an App" = "Select an App"; +"Select an app from the list to see all its related files across your system." = "Select an app from the list to see all its related files across your system."; -/* Settings - Schedule */ +/* App Files (AppFilesView) */ +"%lld files" = "%lld files"; +"Scanning for related files..." = "Scanning for related files..."; +"Checking %lld locations..." = "Checking %lld locations..."; +"No Related Files" = "No Related Files"; +"No additional files found for %@." = "No additional files found for %@."; +"Remove %lld files (%@)" = "Remove %lld files (%@)"; +"Removal Failed" = "Removal Failed"; +"Remove this file" = "Remove this file"; +"Remove %@?" = "Remove %@?"; +"Remove" = "Remove"; +"This will permanently delete this file. This action cannot be undone." = "This will permanently delete this file. This action cannot be undone."; + +/* Orphans */ +"Scanning for orphaned files..." = "Scanning for orphaned files..."; +"No Orphaned Files" = "No Orphaned Files"; +"No leftover files from uninstalled apps were found." = "No leftover files from uninstalled apps were found."; +"Scan for Orphans" = "Scan for Orphans"; +"Orphaned Files (%lld)" = "Orphaned Files (%lld)"; +"Remove Selected (%lld)" = "Remove Selected (%lld)"; +"Some files could not be removed" = "Some files could not be removed"; + +/* Onboarding */ +"Back" = "Back"; +"Next" = "Next"; +"Get Started" = "Get Started"; +"Welcome to PureMac" = "Welcome to PureMac"; +"Free, open-source macOS app manager and system cleaner." = "Free, open-source macOS app manager and system cleaner."; +"Find junk files across your system" = "Find junk files across your system"; +"App Uninstaller" = "App Uninstaller"; +"Remove apps and all their files" = "Remove apps and all their files"; +"Orphan Finder" = "Orphan Finder"; +"Find leftovers from deleted apps" = "Find leftovers from deleted apps"; +"Full Disk Access" = "Full Disk Access"; +"PureMac needs Full Disk Access to uninstall apps, find leftover files, and clean protected caches." = "PureMac needs Full Disk Access to uninstall apps, find leftover files, and clean protected caches."; +"We'll guide you through the next steps." = "We'll guide you through the next steps."; +"In System Settings, do this:" = "In System Settings, do this:"; +"Privacy & Security → Full Disk Access" = "Privacy & Security → Full Disk Access"; +"Find **PureMac** and turn the toggle on" = "Find **PureMac** and turn the toggle on"; +"Authenticate with Touch ID or your password" = "Authenticate with Touch ID or your password"; +"Reopen Settings" = "Reopen Settings"; +"PureMac isn't in the list — reveal it" = "PureMac isn't in the list — reveal it"; +"Reset permissions and re-prompt" = "Reset permissions and re-prompt"; +"Hide diagnostics" = "Hide diagnostics"; +"Show diagnostics" = "Show diagnostics"; +"Trouble?" = "Trouble?"; +"Waiting for permission…" = "Waiting for permission…"; +"Permission granted." = "Permission granted."; +"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."; +"Trash" = "Trash"; +"Mail Data" = "Mail Data"; +"Safari Data" = "Safari Data"; +"Desktop" = "Desktop"; +"Documents" = "Documents"; +"TCC Database" = "TCC Database"; +"Blocked" = "Blocked"; + +/* Settings */ +"General" = "General"; +"Cleaning" = "Cleaning"; "Schedule" = "Schedule"; -"Automatic Cleaning" = "Automatic Cleaning"; -"Automatically scan and clean your Mac on a schedule" = "Automatically scan and clean your Mac on a schedule"; -"Scan Interval" = "Scan Interval"; -"Automation" = "Automation"; +"About" = "About"; +"Startup" = "Startup"; +"Launch PureMac at login" = "Launch PureMac at login"; +"App Scanning" = "App Scanning"; +"Search sensitivity" = "Search sensitivity"; +"Strict" = "Strict"; +"Enhanced" = "Enhanced"; +"Deep" = "Deep"; +"Exact bundle ID and name matches only. Safest option." = "Exact bundle ID and name matches only. Safest option."; +"Includes partial name matching and bundle ID components." = "Includes partial name matching and bundle ID components."; +"Includes company name, entitlements, and team identifier matching." = "Includes company name, entitlements, and team identifier matching."; +"Language" = "Language"; +"Restart PureMac to apply the selected language." = "Restart PureMac to apply the selected language."; +"Relaunch Now" = "Relaunch Now"; +"System Default" = "System Default"; +"English" = "English"; +"Spanish" = "Spanish"; +"Japanese" = "Japanese"; +"Arabic" = "Arabic"; +"Portuguese (Brazil)" = "Portuguese (Brazil)"; +"Chinese (Simplified)" = "Chinese (Simplified)"; +"Chinese (Traditional)" = "Chinese (Traditional)"; +"Safety" = "Safety"; +"Confirm before deleting files" = "Confirm before deleting files"; +"File Discovery" = "File Discovery"; +"Skip hidden files during scan" = "Skip hidden files during scan"; +"Large Files" = "Large Files"; +"Minimum size: %lld MB" = "Minimum size: %lld MB"; +"Files older than: %lld months" = "Files older than: %lld months"; +"Automatic Scanning" = "Automatic Scanning"; +"Enable scheduled scanning" = "Enable scheduled scanning"; +"Scan interval" = "Scan interval"; "Auto-clean after scan" = "Auto-clean after scan"; -"Minimum junk size to trigger clean:" = "Minimum junk size to trigger clean:"; -"50 MB" = "50 MB"; -"100 MB" = "100 MB"; -"250 MB" = "250 MB"; -"500 MB" = "500 MB"; -"1 GB" = "1 GB"; "Auto-purge purgeable space" = "Auto-purge purgeable space"; -"Show notification on completion" = "Show notification on completion"; -"Status" = "Status"; +"Notify on completion" = "Notify on completion"; "Last run" = "Last run"; -"Next run" = "Next run"; -"Never" = "Never"; -"Not scheduled" = "Not scheduled"; - -/* Settings - General */ -"General" = "General"; -"App Behavior" = "App Behavior"; -"Launch at login" = "Launch at login"; -"Show in Dock" = "Show in Dock"; -"Show menu bar icon" = "Show menu bar icon"; -"Safety" = "Safety"; -"PureMac will never delete system-critical files. Only caches, logs, temporary files, and user-selected items are removed." = "PureMac will never delete system-critical files. Only caches, logs, temporary files, and user-selected items are removed."; - -/* Settings - About */ -"About" = "About"; -"Version 1.0.0" = "Version 1.0.0"; -"A free, open-source Mac cleaning utility.\nKeep your Mac fast, clean, and optimized." = "A free, open-source Mac cleaning utility.\nKeep your Mac fast, clean, and optimized."; +"Version %@" = "Version %@"; +"Free, open-source macOS app manager." = "Free, open-source macOS app manager."; "GitHub Repository" = "GitHub Repository"; +"Report an Issue" = "Report an Issue"; "MIT License" = "MIT License"; -/* Cleaning Categories */ +/* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "System Junk"; "User Cache" = "User Cache"; "AI Apps" = "AI Apps"; @@ -97,12 +214,13 @@ "Purgeable Space" = "Purgeable Space"; "Xcode Junk" = "Xcode Junk"; "Brew Cache" = "Brew Cache"; +"Node Cache" = "Node Cache"; +"Docker Cache" = "Docker Cache"; -/* Category Descriptions */ +/* Cleaning category descriptions */ "Scan everything at once" = "Scan everything at once"; "System caches, logs, and temporary files" = "System caches, logs, and temporary files"; "Application caches and browser data" = "Application caches and browser data"; -"Logs, caches, and temporary files from local AI apps" = "Logs, caches, and temporary files from local AI apps"; "Local AI app logs, caches, and optional history" = "Local AI app logs, caches, and optional history"; "Downloaded mail attachments" = "Downloaded mail attachments"; "Files in your Trash" = "Files in your Trash"; @@ -110,8 +228,18 @@ "APFS purgeable disk space" = "APFS purgeable disk space"; "Derived data, archives, and simulators" = "Derived data, archives, and simulators"; "Homebrew download cache" = "Homebrew download cache"; +"npm, yarn, and pnpm download caches" = "npm, yarn, and pnpm download caches"; +"Docker images, containers, and build cache" = "Docker images, containers, and build cache"; -/* Schedule Intervals */ +/* 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`)"; + +/* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "Every Hour"; "Every 3 Hours" = "Every 3 Hours"; "Every 6 Hours" = "Every 6 Hours"; @@ -121,17 +249,14 @@ "Every 2 Weeks" = "Every 2 Weeks"; "Monthly" = "Monthly"; -/* Notifications */ -"Found %@ of junk files." = "Found %@ of junk files."; +/* Schedule status */ +"Never" = "Never"; +"Not scheduled" = "Not scheduled"; -/* Node Cache */ -"Node Cache" = "Node Cache"; -"npm, yarn, and pnpm download caches" = "npm, yarn, and pnpm download caches"; -"npm cache" = "npm cache"; -"yarn classic cache" = "yarn classic cache"; -"pnpm content-addressable store" = "pnpm content-addressable store"; +/* Appearance modes (AppearanceMode.label) */ +"System" = "System"; +"Light" = "Light"; +"Dark" = "Dark"; -/* Docker Cache */ -"Docker Cache" = "Docker Cache"; -"Docker images, containers, and build cache" = "Docker images, containers, and build cache"; -"Reclaimable (run `docker system prune -af`)" = "Reclaimable (run `docker system prune -af`)"; +/* Notifications */ +"Found %@ of junk files." = "Found %@ of junk files."; diff --git a/PureMac/es.lproj/Localizable.strings b/PureMac/es.lproj/Localizable.strings index d88a2a1..f7079be 100644 --- a/PureMac/es.lproj/Localizable.strings +++ b/PureMac/es.lproj/Localizable.strings @@ -1,117 +1,245 @@ -/* Full Disk Access Banner */ -"Full Disk Access Required" = "Se requiere acceso total al disco"; -"PureMac needs Full Disk Access to scan Trash, Mail, Desktop, Documents, and Homebrew cache." = "PureMac necesita acceso total al disco para analizar la Papelera, Mail, Escritorio, Documentos y la caché de Homebrew."; -"Open Settings" = "Abrir Ajustes"; - -/* Top Bar */ -"Macintosh HD" = "Macintosh HD"; -"%@ free" = "%@ libres"; -"Auto-clean: " = "Limpieza automática: "; - -/* Sidebar */ -"CLEANING" = "LIMPIEZA"; -"Last cleaned: %@" = "Última limpieza: %@"; -"Home" = "Inicio"; +/* Sidebar (MainWindow) */ +"Overview" = "Resumen"; "Applications" = "Aplicaciones"; -"Cleaning" = "Limpieza"; +"Cleanup" = "Limpieza"; +"Dashboard" = "Panel"; "Installed Apps" = "Apps instaladas"; "Orphaned Files" = "Archivos huérfanos"; -"Select a category from the sidebar to get started." = "Selecciona una categoría en la barra lateral para comenzar."; +"PureMac" = "PureMac"; +"Ready to clean" = "Listo para limpiar"; +"Limited access" = "Acceso limitado"; +"Full Disk Access granted" = "Acceso total al disco concedido"; +"Grant FDA in Settings" = "Concede el acceso en Ajustes"; +"Select a category from the sidebar to get started." = "Selecciona una categoría en la barra lateral para empezar."; + +/* FDA toast + clean error alert (MainWindow) */ +"Couldn't clean everything" = "No se pudo limpiar todo"; +"Open System Settings" = "Abrir Ajustes del Sistema"; +"OK" = "OK"; +"Full Disk Access required" = "Se requiere acceso total al disco"; +"macOS blocks PureMac from cleaning caches and uninstalling apps until you grant access." = "macOS impide que PureMac limpie cachés y desinstale apps hasta que concedas el acceso."; +"Grant Access" = "Conceder acceso"; -/* Smart Scan */ +/* Dashboard */ +"Storage" = "Almacenamiento"; "Smart Scan" = "Análisis inteligente"; -"Click Scan to start" = "Haz clic en Analizar para empezar"; -"Total" = "Total"; -"Used" = "Usado"; -"Free" = "Libre"; -"Purgeable" = "Purgable"; -"junk found" = "archivos basura encontrados"; -"Your Mac is clean!" = "¡Tu Mac está limpio!"; -"freed up" = "liberados"; -"Cleaning..." = "Limpiando..."; -"Scanning..." = "Analizando..."; -"%lld/%lld items" = "%lld/%lld elementos"; +"free of %@" = "libres de %@"; +"%lld%% used" = "%lld%% en uso"; +"Used" = "En uso"; +"Junk" = "Basura"; +"Purgeable" = "Liberable"; +"USED" = "EN USO"; +"SCANNING" = "ANALIZANDO"; +"Free Space" = "Espacio libre"; +"Junk Found" = "Basura encontrada"; +"Apps" = "Apps"; +"of %@ · %lld%% used" = "de %@ · %lld%% en uso"; +"Run a scan" = "Realiza un análisis"; +"across %lld categories" = "en %lld categorías"; +"installed" = "instaladas"; +"APFS reclaimable" = "Recuperable por APFS"; +"Suggested for you" = "Sugerencias para ti"; +"Found so far" = "Encontrado hasta ahora"; +"By category" = "Por 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."; +"Action" = "Acción"; +"Scanning your Mac" = "Analizando tu Mac"; +"Currently in: %@" = "Actualmente en: %@"; +"found" = "encontrado"; +"Your Mac is clean" = "Tu Mac está limpio"; +"Scan Again" = "Volver a analizar"; +"Clean %@" = "Limpiar %@"; +"Clean %@?" = "¿Limpiar %@?"; +"Cleaning…" = "Limpiando…"; +"%lld%% complete" = "%lld%% completado"; +"freed" = "liberado"; +"Done" = "Listo"; +"%lld items" = "%lld elementos"; + +/* Common destructive dialog */ +"Clean" = "Limpiar"; +"Cancel" = "Cancelar"; +"This will permanently delete the selected files. This cannot be undone." = "Esto eliminará permanentemente los archivos seleccionados. No se puede deshacer."; /* Category Detail */ -"All Clean!" = "¡Todo limpio!"; +"All Clean" = "Todo limpio"; "No junk files found in this category." = "No se encontraron archivos basura en esta categoría."; -"Not scanned yet" = "Aún no analizado"; -"Click Scan to analyze this category" = "Haz clic en Analizar para revisar esta categoría"; -"%lld of %lld selected" = "%lld de %lld seleccionados"; +"Not Scanned" = "Sin analizar"; +"Run a scan to analyze this category." = "Realiza un análisis para evaluar esta categoría."; +"Scan Now" = "Analizar ahora"; +"Filter files" = "Filtrar archivos"; +"Scan" = "Analizar"; "Select All" = "Seleccionar todo"; "Deselect All" = "Deseleccionar todo"; -"%lld items" = "%lld elementos"; +"Largest First" = "Más grandes primero"; +"Smallest First" = "Más pequeños primero"; +"Sorted: Largest First" = "Orden: más grandes primero"; +"Sorted: Smallest First" = "Orden: más pequeños primero"; +"Clean %lld items" = "Limpiar %lld elementos"; +"%lld items · %@" = "%lld elementos · %@"; +"Scanning…" = "Analizando…"; +"Rescan" = "Volver a analizar"; +"%lld of %lld selected" = "%lld de %lld seleccionados"; +"Reveal in Finder" = "Mostrar en el Finder"; -/* Buttons */ -"Scan" = "Analizar"; -"Re-scan" = "Volver a analizar"; -"Scan Again" = "Analizar de nuevo"; -"Done" = "Listo"; -"Clean (%@)" = "Limpiar (%@)"; -"Clean %lld items (%@)" = "Limpiar %lld elementos (%@)"; +/* Apps (AppListView) */ +"Search apps" = "Buscar apps"; +"Refresh" = "Actualizar"; +"Installed Apps (%lld)" = "Apps instaladas (%lld)"; +"Uninstall (%lld files)" = "Desinstalar (%lld archivos)"; +"Loading installed apps..." = "Cargando apps instaladas..."; +"No Apps Found" = "No se encontraron apps"; +"Could not find any installed applications." = "No se pudo encontrar ninguna app instalada."; +"Retry" = "Reintentar"; +"Application" = "Aplicación"; +"Size" = "Tamaño"; +"Select an App" = "Selecciona una app"; +"Select an app from the list to see all its related files across your system." = "Selecciona una app de la lista para ver todos sus archivos relacionados en el sistema."; -/* Settings - Schedule */ -"Schedule" = "Programación"; -"Automatic Cleaning" = "Limpieza automática"; -"Automatically scan and clean your Mac on a schedule" = "Analiza y limpia tu Mac automáticamente según una programación"; -"Scan Interval" = "Intervalo de análisis"; -"Automation" = "Automatización"; -"Auto-clean after scan" = "Limpiar automáticamente tras el análisis"; -"Minimum junk size to trigger clean:" = "Tamaño mínimo de basura para iniciar la limpieza:"; -"50 MB" = "50 MB"; -"100 MB" = "100 MB"; -"250 MB" = "250 MB"; -"500 MB" = "500 MB"; -"1 GB" = "1 GB"; -"Auto-purge purgeable space" = "Purgar automáticamente el espacio purgable"; -"Show notification on completion" = "Mostrar notificación al finalizar"; -"Status" = "Estado"; -"Last run" = "Última ejecución"; -"Next run" = "Próxima ejecución"; -"Never" = "Nunca"; -"Not scheduled" = "Sin programar"; +/* App Files (AppFilesView) */ +"%lld files" = "%lld archivos"; +"Scanning for related files..." = "Buscando archivos relacionados..."; +"Checking %lld locations..." = "Revisando %lld ubicaciones..."; +"No Related Files" = "Sin archivos relacionados"; +"No additional files found for %@." = "No se encontraron archivos adicionales para %@."; +"Remove %lld files (%@)" = "Eliminar %lld archivos (%@)"; +"Removal Failed" = "No se pudo eliminar"; +"Remove this file" = "Eliminar este archivo"; +"Remove %@?" = "¿Eliminar %@?"; +"Remove" = "Eliminar"; +"This will permanently delete this file. This action cannot be undone." = "Esto eliminará permanentemente este archivo. Esta acción no se puede deshacer."; -/* Settings - General */ -"General" = "General"; -"App Behavior" = "Comportamiento de la app"; -"Launch at login" = "Abrir al iniciar sesión"; -"Show in Dock" = "Mostrar en el Dock"; -"Show menu bar icon" = "Mostrar ícono en la barra de menús"; -"Safety" = "Seguridad"; -"PureMac will never delete system-critical files. Only caches, logs, temporary files, and user-selected items are removed." = "PureMac nunca eliminará archivos críticos del sistema. Solo se eliminan cachés, registros, archivos temporales y elementos seleccionados por el usuario."; +/* Orphans */ +"Scanning for orphaned files..." = "Buscando archivos huérfanos..."; +"No Orphaned Files" = "Sin archivos huérfanos"; +"No leftover files from uninstalled apps were found." = "No se encontraron archivos sobrantes de apps desinstaladas."; +"Scan for Orphans" = "Buscar huérfanos"; +"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"; + +/* Onboarding */ +"Back" = "Atrás"; +"Next" = "Siguiente"; +"Get Started" = "Empezar"; +"Welcome to PureMac" = "Te damos la bienvenida a PureMac"; +"Free, open-source macOS app manager and system cleaner." = "Administrador de apps y limpiador de sistema gratuito y de código abierto para macOS."; +"Find junk files across your system" = "Encuentra archivos basura en todo el sistema"; +"App Uninstaller" = "Desinstalador de apps"; +"Remove apps and all their files" = "Elimina apps y todos sus archivos"; +"Orphan Finder" = "Buscador de huérfanos"; +"Find leftovers from deleted apps" = "Encuentra restos de apps eliminadas"; +"Full Disk Access" = "Acceso total al disco"; +"PureMac needs Full Disk Access to uninstall apps, find leftover files, and clean protected caches." = "PureMac necesita acceso total al disco para desinstalar apps, encontrar archivos sobrantes y limpiar cachés protegidas."; +"We'll guide you through the next steps." = "Te guiaremos por los siguientes pasos."; +"In System Settings, do this:" = "En Ajustes del Sistema, haz lo siguiente:"; +"Privacy & Security → Full Disk Access" = "Privacidad y seguridad → Acceso total al disco"; +"Find **PureMac** and turn the toggle on" = "Encuentra **PureMac** y activa el interruptor"; +"Authenticate with Touch ID or your password" = "Autentícate con Touch ID o tu contraseña"; +"Reopen Settings" = "Reabrir Ajustes"; +"PureMac isn't in the list — reveal it" = "PureMac no aparece en la lista: mostrarlo"; +"Reset permissions and re-prompt" = "Restablecer permisos y volver a solicitar"; +"Hide diagnostics" = "Ocultar diagnóstico"; +"Show diagnostics" = "Mostrar diagnóstico"; +"Trouble?" = "¿Problemas?"; +"Waiting for permission…" = "Esperando permiso…"; +"Permission granted." = "Permiso concedido."; +"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."; +"Trash" = "Papelera"; +"Mail Data" = "Datos de Mail"; +"Safari Data" = "Datos de Safari"; +"Desktop" = "Escritorio"; +"Documents" = "Documentos"; +"TCC Database" = "Base de datos TCC"; +"Blocked" = "Bloqueado"; -/* Settings - About */ +/* Settings */ +"General" = "General"; +"Cleaning" = "Limpieza"; +"Schedule" = "Programación"; "About" = "Acerca de"; -"Version 1.0.0" = "Versión 1.0.0"; -"A free, open-source Mac cleaning utility.\nKeep your Mac fast, clean, and optimized." = "Una utilidad de limpieza para Mac, gratuita y de código abierto.\nMantén tu Mac rápido, limpio y optimizado."; +"Startup" = "Inicio"; +"Launch PureMac at login" = "Abrir PureMac al iniciar sesión"; +"App Scanning" = "Análisis de apps"; +"Search sensitivity" = "Sensibilidad de búsqueda"; +"Strict" = "Estricta"; +"Enhanced" = "Mejorada"; +"Deep" = "Profunda"; +"Exact bundle ID and name matches only. Safest option." = "Solo coincidencias exactas de ID de paquete y nombre. La opción más segura."; +"Includes partial name matching and bundle ID components." = "Incluye coincidencias parciales de nombre y componentes del ID de paquete."; +"Includes company name, entitlements, and team identifier matching." = "Incluye coincidencias con nombre de empresa, autorizaciones e identificador de equipo."; +"Language" = "Idioma"; +"Restart PureMac to apply the selected language." = "Reinicia PureMac para aplicar el idioma seleccionado."; +"Relaunch Now" = "Reiniciar ahora"; +"System Default" = "Predeterminado del sistema"; +"English" = "Inglés"; +"Spanish" = "Español"; +"Japanese" = "Japonés"; +"Arabic" = "Árabe"; +"Portuguese (Brazil)" = "Portugués (Brasil)"; +"Chinese (Simplified)" = "Chino (simplificado)"; +"Chinese (Traditional)" = "Chino (tradicional)"; +"Safety" = "Seguridad"; +"Confirm before deleting files" = "Confirmar antes de eliminar archivos"; +"File Discovery" = "Detección de archivos"; +"Skip hidden files during scan" = "Omitir archivos ocultos al analizar"; +"Large Files" = "Archivos grandes"; +"Minimum size: %lld MB" = "Tamaño mínimo: %lld MB"; +"Files older than: %lld months" = "Archivos con más de: %lld meses"; +"Automatic Scanning" = "Análisis automático"; +"Enable scheduled scanning" = "Activar análisis programado"; +"Scan interval" = "Intervalo de análisis"; +"Auto-clean after scan" = "Limpiar automáticamente tras el análisis"; +"Auto-purge purgeable space" = "Purgar automáticamente el espacio liberable"; +"Notify on completion" = "Notificar al finalizar"; +"Last run" = "Última ejecución"; +"Version %@" = "Versión %@"; +"Free, open-source macOS app manager." = "Administrador de apps de macOS gratuito y de código abierto."; "GitHub Repository" = "Repositorio de GitHub"; +"Report an Issue" = "Informar de un problema"; "MIT License" = "Licencia MIT"; -/* Cleaning Categories */ +/* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "Basura del sistema"; "User Cache" = "Caché de usuario"; "AI Apps" = "Apps de IA"; "Mail Files" = "Archivos de Mail"; "Trash Bins" = "Papeleras"; "Large & Old Files" = "Archivos grandes y antiguos"; -"Purgeable Space" = "Espacio purgable"; +"Purgeable Space" = "Espacio liberable"; "Xcode Junk" = "Basura de Xcode"; "Brew Cache" = "Caché de Brew"; +"Node Cache" = "Caché de Node"; +"Docker Cache" = "Caché de Docker"; -/* Category Descriptions */ -"Scan everything at once" = "Analiza todo a la vez"; +/* Cleaning category descriptions */ +"Scan everything at once" = "Analizar todo a la vez"; "System caches, logs, and temporary files" = "Cachés del sistema, registros y archivos temporales"; "Application caches and browser data" = "Cachés de aplicaciones y datos del navegador"; -"Logs, caches, and temporary files from local AI apps" = "Registros, cachés y archivos temporales de apps de IA locales"; "Local AI app logs, caches, and optional history" = "Registros, cachés e historial opcional de apps de IA locales"; "Downloaded mail attachments" = "Adjuntos de correo descargados"; -"Files in your Trash" = "Archivos en tu Papelera"; +"Files in your Trash" = "Archivos en la Papelera"; "Files over 100 MB or older than 1 year" = "Archivos de más de 100 MB o con más de 1 año"; -"APFS purgeable disk space" = "Espacio purgable de disco APFS"; +"APFS purgeable disk space" = "Espacio liberable de disco APFS"; "Derived data, archives, and simulators" = "Datos derivados, archivos y simuladores"; "Homebrew download cache" = "Caché de descargas de Homebrew"; +"npm, yarn, and pnpm download caches" = "Cachés de descargas de npm, yarn y pnpm"; +"Docker images, containers, and build cache" = "Imágenes, contenedores y caché de compilación de Docker"; -/* Schedule Intervals */ +/* 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`)"; + +/* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "Cada hora"; "Every 3 Hours" = "Cada 3 horas"; "Every 6 Hours" = "Cada 6 horas"; @@ -121,12 +249,14 @@ "Every 2 Weeks" = "Cada 2 semanas"; "Monthly" = "Mensualmente"; +/* Schedule status */ +"Never" = "Nunca"; +"Not scheduled" = "Sin programar"; + +/* Appearance modes (AppearanceMode.label) */ +"System" = "Sistema"; +"Light" = "Claro"; +"Dark" = "Oscuro"; + /* Notifications */ "Found %@ of junk files." = "Se encontraron %@ de archivos basura."; - -/* Node Cache */ -"Node Cache" = "Caché de Node"; -"npm, yarn, and pnpm download caches" = "Cachés de descarga de npm, yarn y pnpm"; -"npm cache" = "caché de npm"; -"yarn classic cache" = "caché de yarn classic"; -"pnpm content-addressable store" = "almacén de contenido direccionable de pnpm"; diff --git a/PureMac/ja.lproj/Localizable.strings b/PureMac/ja.lproj/Localizable.strings index 35eed6e..7d815a8 100644 --- a/PureMac/ja.lproj/Localizable.strings +++ b/PureMac/ja.lproj/Localizable.strings @@ -1,117 +1,245 @@ -/* Full Disk Access Banner */ -"Full Disk Access Required" = "フルディスクアクセスが必要です"; -"PureMac needs Full Disk Access to scan Trash, Mail, Desktop, Documents, and Homebrew cache." = "PureMacがゴミ箱、メール、デスクトップ、書類、Homebrewキャッシュをスキャンするにはフルディスクアクセスが必要です。"; -"Open Settings" = "設定を開く"; - -/* Top Bar */ -"Macintosh HD" = "Macintosh HD"; -"%@ free" = "%@ 空き"; -"Auto-clean: " = "自動クリーン: "; - -/* Sidebar */ -"CLEANING" = "クリーニング"; -"Last cleaned: %@" = "最終クリーン: %@"; -"Home" = "ホーム"; +/* Sidebar (MainWindow) */ +"Overview" = "概要"; "Applications" = "アプリケーション"; -"Cleaning" = "クリーニング"; +"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." = "サイドバーからカテゴリを選択して始めてください。"; -/* Smart Scan */ +/* FDA toast + clean error alert (MainWindow) */ +"Couldn't clean everything" = "すべてを削除できませんでした"; +"Open System Settings" = "システム設定を開く"; +"OK" = "OK"; +"Full Disk Access required" = "フルディスクアクセスが必要です"; +"macOS blocks PureMac from cleaning caches and uninstalling apps until you grant access." = "アクセスを許可するまで、macOSはPureMacによるキャッシュの削除やアプリのアンインストールをブロックします。"; +"Grant Access" = "アクセスを許可"; + +/* Dashboard */ +"Storage" = "ストレージ"; "Smart Scan" = "スマートスキャン"; -"Click Scan to start" = "スキャンをクリックして開始"; -"Total" = "合計"; -"Used" = "使用済み"; -"Free" = "空き"; +"free of %@" = "%@中の空き容量"; +"%lld%% used" = "%lld%% 使用中"; +"Used" = "使用中"; +"Junk" = "ジャンク"; "Purgeable" = "削除可能"; -"junk found" = "ジャンクを検出"; -"Your Mac is clean!" = "お使いのMacはクリーンです!"; -"freed up" = "解放済み"; -"Cleaning..." = "クリーン中…"; -"Scanning..." = "スキャン中…"; -"%lld/%lld items" = "%lld/%lld 件"; +"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" = "カテゴリ別"; +"%@ 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 yet" = "未スキャン"; -"Click Scan to analyze this category" = "スキャンをクリックしてこのカテゴリを分析"; -"%lld of %lld selected" = "%lld / %lld 件を選択中"; +"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" = "すべて選択解除"; -"%lld items" = "%lld 件"; +"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で表示"; -/* Buttons */ -"Scan" = "スキャン"; -"Re-scan" = "再スキャン"; -"Scan Again" = "もう一度スキャン"; -"Done" = "完了"; -"Clean (%@)" = "クリーン(%@)"; -"Clean %lld items (%@)" = "%lld 件をクリーン(%@)"; +/* 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." = "リストからアプリを選択すると、システム全体の関連ファイルが表示されます。"; -/* Settings - Schedule */ -"Schedule" = "スケジュール"; -"Automatic Cleaning" = "自動クリーン"; -"Automatically scan and clean your Mac on a schedule" = "スケジュールに従ってお使いのMacを自動スキャン・クリーン"; -"Scan Interval" = "スキャン間隔"; -"Automation" = "自動化"; -"Auto-clean after scan" = "スキャン後に自動クリーン"; -"Minimum junk size to trigger clean:" = "クリーンを実行する最小ジャンクサイズ:"; -"50 MB" = "50 MB"; -"100 MB" = "100 MB"; -"250 MB" = "250 MB"; -"500 MB" = "500 MB"; -"1 GB" = "1 GB"; -"Auto-purge purgeable space" = "削除可能な領域を自動クリア"; -"Show notification on completion" = "完了時に通知を表示"; -"Status" = "ステータス"; -"Last run" = "最終実行"; -"Next run" = "次回実行"; -"Never" = "なし"; -"Not scheduled" = "未設定"; +/* 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" = "一部のファイルを削除できませんでした"; -/* Settings - General */ +/* 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." = "アプリのアンインストール、残存ファイルの検出、保護されたキャッシュのクリーンアップにはフルディスクアクセスが必要です。"; +"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. You can grant Full Disk Access later in System Settings." = "一部の機能は制限されます。フルディスクアクセスは後でシステム設定から許可できます。"; +"Trash" = "ゴミ箱"; +"Mail Data" = "メールデータ"; +"Safari Data" = "Safariデータ"; +"Desktop" = "デスクトップ"; +"Documents" = "書類"; +"TCC Database" = "TCCデータベース"; +"Blocked" = "ブロック"; + +/* Settings */ "General" = "一般"; -"App Behavior" = "アプリの動作"; -"Launch at login" = "ログイン時に起動"; -"Show in Dock" = "Dockに表示"; -"Show menu bar icon" = "メニューバーアイコンを表示"; -"Safety" = "安全性"; -"PureMac will never delete system-critical files. Only caches, logs, temporary files, and user-selected items are removed." = "PureMacはシステムの重要なファイルを削除しません。キャッシュ、ログ、一時ファイル、ユーザーが選択した項目のみ削除されます。"; - -/* Settings - About */ -"About" = "概要"; -"Version 1.0.0" = "バージョン 1.0.0"; -"A free, open-source Mac cleaning utility.\nKeep your Mac fast, clean, and optimized." = "無料のオープンソースMacクリーニングツール。\nお使いのMacを高速・クリーン・最適な状態に保ちます。"; -"GitHub Repository" = "GitHub リポジトリ"; -"MIT License" = "MIT License"; - -/* Cleaning Categories */ +"Cleaning" = "クリーニング"; +"Schedule" = "スケジュール"; +"About" = "PureMacについて"; +"Startup" = "起動"; +"Launch PureMac at login" = "ログイン時にPureMacを起動"; +"App Scanning" = "アプリスキャン"; +"Search sensitivity" = "検索の精度"; +"Strict" = "厳格"; +"Enhanced" = "拡張"; +"Deep" = "詳細"; +"Exact bundle ID and name matches only. Safest option." = "バンドルIDと名前が完全一致するもののみ。最も安全。"; +"Includes partial name matching and bundle ID components." = "部分的な名前一致やバンドルID要素も含めます。"; +"Includes company name, entitlements, and team identifier matching." = "会社名、エンタイトルメント、チームIDの一致を含めます。"; +"Language" = "言語"; +"Restart PureMac to apply the selected language." = "選択した言語を適用するにはPureMacを再起動してください。"; +"Relaunch Now" = "今すぐ再起動"; +"System Default" = "システムデフォルト"; +"English" = "英語"; +"Spanish" = "スペイン語"; +"Japanese" = "日本語"; +"Arabic" = "アラビア語"; +"Portuguese (Brazil)" = "ポルトガル語(ブラジル)"; +"Chinese (Simplified)" = "中国語(簡体字)"; +"Chinese (Traditional)" = "中国語(繁体字)"; +"Safety" = "安全"; +"Confirm before deleting files" = "ファイル削除前に確認"; +"File Discovery" = "ファイル検出"; +"Skip hidden files during scan" = "スキャン時に隠しファイルをスキップ"; +"Large Files" = "大きなファイル"; +"Minimum size: %lld MB" = "最小サイズ: %lld MB"; +"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ライセンス"; + +/* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "システムジャンク"; "User Cache" = "ユーザーキャッシュ"; "AI Apps" = "AIアプリ"; "Mail Files" = "メールファイル"; "Trash Bins" = "ゴミ箱"; -"Large & Old Files" = "大きいファイルと古いファイル"; -"Purgeable Space" = "削除可能な領域"; +"Large & Old Files" = "大きい・古いファイル"; +"Purgeable Space" = "削除可能領域"; "Xcode Junk" = "Xcodeジャンク"; "Brew Cache" = "Brewキャッシュ"; +"Node Cache" = "Nodeキャッシュ"; +"Docker Cache" = "Dockerキャッシュ"; -/* Category Descriptions */ +/* Cleaning category descriptions */ "Scan everything at once" = "すべてを一度にスキャン"; "System caches, logs, and temporary files" = "システムキャッシュ、ログ、一時ファイル"; -"Application caches and browser data" = "アプリキャッシュとブラウザデータ"; -"Logs, caches, and temporary files from local AI apps" = "ローカルAIアプリのログ、キャッシュ、一時ファイル"; +"Application caches and browser data" = "アプリケーションキャッシュとブラウザデータ"; "Local AI app logs, caches, and optional history" = "ローカルAIアプリのログ、キャッシュ、任意の履歴"; "Downloaded mail attachments" = "ダウンロードしたメール添付ファイル"; "Files in your Trash" = "ゴミ箱内のファイル"; -"Files over 100 MB or older than 1 year" = "100 MB超または1年以上前のファイル"; -"APFS purgeable disk space" = "APFSの削除可能なディスク領域"; +"Files over 100 MB or older than 1 year" = "100MB以上または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イメージ、コンテナ、ビルドキャッシュ"; + +/* Node Cache subcategories */ +"npm cache" = "npmキャッシュ"; +"yarn classic cache" = "yarnクラシックキャッシュ"; +"pnpm content-addressable store" = "pnpmコンテンツアドレッサブルストア"; -/* Schedule Intervals */ +/* Docker Cache helper */ +"Reclaimable (run `docker system prune -af`)" = "再利用可能 ( `docker system prune -af` を実行)"; + +/* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "1時間ごと"; "Every 3 Hours" = "3時間ごと"; "Every 6 Hours" = "6時間ごと"; @@ -121,5 +249,14 @@ "Every 2 Weeks" = "2週間ごと"; "Monthly" = "毎月"; +/* Schedule status */ +"Never" = "なし"; +"Not scheduled" = "未スケジュール"; + +/* Appearance modes (AppearanceMode.label) */ +"System" = "システム"; +"Light" = "ライト"; +"Dark" = "ダーク"; + /* Notifications */ -"Found %@ of junk files." = "%@ のジャンクファイルを検出しました。"; +"Found %@ of junk files." = "%@ のジャンクファイルが見つかりました。"; diff --git a/PureMac/pt-BR.lproj/Localizable.strings b/PureMac/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000..e00e328 --- /dev/null +++ b/PureMac/pt-BR.lproj/Localizable.strings @@ -0,0 +1,262 @@ +/* Sidebar (MainWindow) */ +"Overview" = "Visão geral"; +"Applications" = "Aplicativos"; +"Cleanup" = "Limpeza"; +"Dashboard" = "Painel"; +"Installed Apps" = "Apps instalados"; +"Orphaned Files" = "Arquivos órfãos"; +"PureMac" = "PureMac"; +"Ready to clean" = "Pronto para limpar"; +"Limited access" = "Acesso limitado"; +"Full Disk Access granted" = "Acesso total ao disco concedido"; +"Grant FDA in Settings" = "Conceda o acesso em Ajustes"; +"Select a category from the sidebar to get started." = "Selecione uma categoria na barra lateral para começar."; + +/* FDA toast + clean error alert (MainWindow) */ +"Couldn't clean everything" = "Não foi possível limpar tudo"; +"Open System Settings" = "Abrir Ajustes do Sistema"; +"OK" = "OK"; +"Full Disk Access required" = "Acesso total ao disco necessário"; +"macOS blocks PureMac from cleaning caches and uninstalling apps until you grant access." = "O macOS impede que o PureMac limpe caches e desinstale apps até que você conceda o acesso."; +"Grant Access" = "Conceder acesso"; + +/* Dashboard */ +"Storage" = "Armazenamento"; +"Smart Scan" = "Verificação inteligente"; +"free of %@" = "livres de %@"; +"%lld%% used" = "%lld%% em uso"; +"Used" = "Em uso"; +"Junk" = "Lixo"; +"Purgeable" = "Liberável"; +"USED" = "EM USO"; +"SCANNING" = "VERIFICANDO"; +"Free Space" = "Espaço livre"; +"Junk Found" = "Lixo encontrado"; +"Apps" = "Apps"; +"of %@ · %lld%% used" = "de %@ · %lld%% em uso"; +"Run a scan" = "Faça uma verificação"; +"across %lld categories" = "em %lld categorias"; +"installed" = "instalados"; +"APFS reclaimable" = "Recuperável pelo APFS"; +"Suggested for you" = "Sugestões para você"; +"Found so far" = "Encontrado até agora"; +"By category" = "Por 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."; +"Action" = "Ação"; +"Scanning your Mac" = "Verificando seu Mac"; +"Currently in: %@" = "No momento: %@"; +"found" = "encontrado"; +"Your Mac is clean" = "Seu Mac está limpo"; +"Scan Again" = "Verificar novamente"; +"Clean %@" = "Limpar %@"; +"Clean %@?" = "Limpar %@?"; +"Cleaning…" = "Limpando…"; +"%lld%% complete" = "%lld%% concluído"; +"freed" = "liberado"; +"Done" = "Concluído"; +"%lld items" = "%lld itens"; + +/* Common destructive dialog */ +"Clean" = "Limpar"; +"Cancel" = "Cancelar"; +"This will permanently delete the selected files. This cannot be undone." = "Isso apagará permanentemente os arquivos selecionados. Esta ação não pode ser desfeita."; + +/* Category Detail */ +"All Clean" = "Tudo limpo"; +"No junk files found in this category." = "Nenhum arquivo de lixo encontrado nesta categoria."; +"Not Scanned" = "Não verificado"; +"Run a scan to analyze this category." = "Faça uma verificação para analisar esta categoria."; +"Scan Now" = "Verificar agora"; +"Filter files" = "Filtrar arquivos"; +"Scan" = "Verificar"; +"Select All" = "Selecionar tudo"; +"Deselect All" = "Desmarcar tudo"; +"Largest First" = "Maiores primeiro"; +"Smallest First" = "Menores primeiro"; +"Sorted: Largest First" = "Ordem: maiores primeiro"; +"Sorted: Smallest First" = "Ordem: menores primeiro"; +"Clean %lld items" = "Limpar %lld itens"; +"%lld items · %@" = "%lld itens · %@"; +"Scanning…" = "Verificando…"; +"Rescan" = "Verificar novamente"; +"%lld of %lld selected" = "%lld de %lld selecionados"; +"Reveal in Finder" = "Mostrar no Finder"; + +/* Apps (AppListView) */ +"Search apps" = "Buscar apps"; +"Refresh" = "Atualizar"; +"Installed Apps (%lld)" = "Apps instalados (%lld)"; +"Uninstall (%lld files)" = "Desinstalar (%lld arquivos)"; +"Loading installed apps..." = "Carregando apps instalados..."; +"No Apps Found" = "Nenhum app encontrado"; +"Could not find any installed applications." = "Não foi possível encontrar nenhum aplicativo instalado."; +"Retry" = "Tentar novamente"; +"Application" = "Aplicativo"; +"Size" = "Tamanho"; +"Select an App" = "Selecione um app"; +"Select an app from the list to see all its related files across your system." = "Selecione um app da lista para ver todos os arquivos relacionados no sistema."; + +/* App Files (AppFilesView) */ +"%lld files" = "%lld arquivos"; +"Scanning for related files..." = "Buscando arquivos relacionados..."; +"Checking %lld locations..." = "Verificando %lld locais..."; +"No Related Files" = "Sem arquivos relacionados"; +"No additional files found for %@." = "Nenhum arquivo adicional encontrado para %@."; +"Remove %lld files (%@)" = "Remover %lld arquivos (%@)"; +"Removal Failed" = "Falha na remoção"; +"Remove this file" = "Remover este arquivo"; +"Remove %@?" = "Remover %@?"; +"Remove" = "Remover"; +"This will permanently delete this file. This action cannot be undone." = "Isso apagará permanentemente este arquivo. Esta ação não pode ser desfeita."; + +/* Orphans */ +"Scanning for orphaned files..." = "Buscando arquivos órfãos..."; +"No Orphaned Files" = "Sem arquivos órfãos"; +"No leftover files from uninstalled apps were found." = "Nenhum arquivo restante de apps desinstalados foi encontrado."; +"Scan for Orphans" = "Buscar órfãos"; +"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"; + +/* Onboarding */ +"Back" = "Voltar"; +"Next" = "Próximo"; +"Get Started" = "Começar"; +"Welcome to PureMac" = "Boas-vindas ao PureMac"; +"Free, open-source macOS app manager and system cleaner." = "Gerenciador de apps e limpador de sistema gratuito e de código aberto para macOS."; +"Find junk files across your system" = "Encontre arquivos de lixo no sistema todo"; +"App Uninstaller" = "Desinstalador de apps"; +"Remove apps and all their files" = "Remova apps e todos os seus arquivos"; +"Orphan Finder" = "Localizador de órfãos"; +"Find leftovers from deleted apps" = "Encontre restos de apps apagados"; +"Full Disk Access" = "Acesso total ao disco"; +"PureMac needs Full Disk Access to uninstall apps, find leftover files, and clean protected caches." = "O PureMac precisa de acesso total ao disco para desinstalar apps, encontrar arquivos restantes e limpar caches protegidos."; +"We'll guide you through the next steps." = "Vamos guiá-lo pelos próximos passos."; +"In System Settings, do this:" = "Em Ajustes do Sistema, faça o seguinte:"; +"Privacy & Security → Full Disk Access" = "Privacidade e Segurança → Acesso total ao disco"; +"Find **PureMac** and turn the toggle on" = "Encontre o **PureMac** e ative o interruptor"; +"Authenticate with Touch ID or your password" = "Autentique-se com Touch ID ou sua senha"; +"Reopen Settings" = "Reabrir Ajustes"; +"PureMac isn't in the list — reveal it" = "O PureMac não está na lista — mostrar"; +"Reset permissions and re-prompt" = "Redefinir permissões e perguntar novamente"; +"Hide diagnostics" = "Ocultar diagnóstico"; +"Show diagnostics" = "Mostrar diagnóstico"; +"Trouble?" = "Algum problema?"; +"Waiting for permission…" = "Aguardando permissão…"; +"Permission granted." = "Permissão concedida."; +"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."; +"Trash" = "Lixo"; +"Mail Data" = "Dados do Mail"; +"Safari Data" = "Dados do Safari"; +"Desktop" = "Mesa"; +"Documents" = "Documentos"; +"TCC Database" = "Banco de dados TCC"; +"Blocked" = "Bloqueado"; + +/* Settings */ +"General" = "Geral"; +"Cleaning" = "Limpeza"; +"Schedule" = "Agendamento"; +"About" = "Sobre"; +"Startup" = "Inicialização"; +"Launch PureMac at login" = "Abrir o PureMac ao iniciar sessão"; +"App Scanning" = "Verificação de apps"; +"Search sensitivity" = "Sensibilidade da busca"; +"Strict" = "Restrita"; +"Enhanced" = "Aprimorada"; +"Deep" = "Profunda"; +"Exact bundle ID and name matches only. Safest option." = "Apenas correspondências exatas de ID e nome do pacote. Opção mais segura."; +"Includes partial name matching and bundle ID components." = "Inclui correspondências parciais de nome e componentes do ID do pacote."; +"Includes company name, entitlements, and team identifier matching." = "Inclui correspondências com o nome da empresa, entitlements e identificador da equipe."; +"Language" = "Idioma"; +"Restart PureMac to apply the selected language." = "Reinicie o PureMac para aplicar o idioma selecionado."; +"Relaunch Now" = "Reiniciar agora"; +"System Default" = "Padrão do sistema"; +"English" = "Inglês"; +"Spanish" = "Espanhol"; +"Japanese" = "Japonês"; +"Arabic" = "Árabe"; +"Portuguese (Brazil)" = "Português (Brasil)"; +"Chinese (Simplified)" = "Chinês (simplificado)"; +"Chinese (Traditional)" = "Chinês (tradicional)"; +"Safety" = "Segurança"; +"Confirm before deleting files" = "Confirmar antes de apagar arquivos"; +"File Discovery" = "Detecção de arquivos"; +"Skip hidden files during scan" = "Ignorar arquivos ocultos durante a verificação"; +"Large Files" = "Arquivos grandes"; +"Minimum size: %lld MB" = "Tamanho mínimo: %lld MB"; +"Files older than: %lld months" = "Arquivos com mais de: %lld meses"; +"Automatic Scanning" = "Verificação automática"; +"Enable scheduled scanning" = "Ativar verificação agendada"; +"Scan interval" = "Intervalo da verificação"; +"Auto-clean after scan" = "Limpar automaticamente após a verificação"; +"Auto-purge purgeable space" = "Liberar espaço purgable automaticamente"; +"Notify on completion" = "Notificar ao concluir"; +"Last run" = "Última execução"; +"Version %@" = "Versão %@"; +"Free, open-source macOS app manager." = "Gerenciador de apps de macOS gratuito e de código aberto."; +"GitHub Repository" = "Repositório no GitHub"; +"Report an Issue" = "Relatar um problema"; +"MIT License" = "Licença MIT"; + +/* Cleaning categories (CleaningCategory.rawValue) */ +"System Junk" = "Lixo do sistema"; +"User Cache" = "Cache do usuário"; +"AI Apps" = "Apps de IA"; +"Mail Files" = "Arquivos do Mail"; +"Trash Bins" = "Lixeiras"; +"Large & Old Files" = "Arquivos grandes e antigos"; +"Purgeable Space" = "Espaço liberável"; +"Xcode Junk" = "Lixo do Xcode"; +"Brew Cache" = "Cache do Brew"; +"Node Cache" = "Cache do Node"; +"Docker Cache" = "Cache do Docker"; + +/* Cleaning category descriptions */ +"Scan everything at once" = "Verificar tudo de uma vez"; +"System caches, logs, and temporary files" = "Caches do sistema, logs e arquivos temporários"; +"Application caches and browser data" = "Caches de aplicativos e dados do navegador"; +"Local AI app logs, caches, and optional history" = "Logs, caches e histórico opcional de apps de IA locais"; +"Downloaded mail attachments" = "Anexos de e-mail baixados"; +"Files in your Trash" = "Arquivos na sua Lixeira"; +"Files over 100 MB or older than 1 year" = "Arquivos com mais de 100 MB ou mais de 1 ano"; +"APFS purgeable disk space" = "Espaço de disco liberável do APFS"; +"Derived data, archives, and simulators" = "Derived data, arquivos e simuladores"; +"Homebrew download cache" = "Cache de downloads do Homebrew"; +"npm, yarn, and pnpm download caches" = "Caches de download do npm, yarn e pnpm"; +"Docker images, containers, and build cache" = "Imagens, containers e cache de build do Docker"; + +/* 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`)"; + +/* Schedule intervals (ScheduleInterval.rawValue) */ +"Every Hour" = "A cada hora"; +"Every 3 Hours" = "A cada 3 horas"; +"Every 6 Hours" = "A cada 6 horas"; +"Every 12 Hours" = "A cada 12 horas"; +"Daily" = "Diariamente"; +"Weekly" = "Semanalmente"; +"Every 2 Weeks" = "A cada 2 semanas"; +"Monthly" = "Mensalmente"; + +/* Schedule status */ +"Never" = "Nunca"; +"Not scheduled" = "Não agendado"; + +/* Appearance modes (AppearanceMode.label) */ +"System" = "Sistema"; +"Light" = "Claro"; +"Dark" = "Escuro"; + +/* Notifications */ +"Found %@ of junk files." = "Foram encontrados %@ de arquivos de lixo."; diff --git a/PureMac/zh-Hans.lproj/Localizable.strings b/PureMac/zh-Hans.lproj/Localizable.strings index 3d721f7..4a17151 100644 --- a/PureMac/zh-Hans.lproj/Localizable.strings +++ b/PureMac/zh-Hans.lproj/Localizable.strings @@ -1,137 +1,262 @@ -/* Full Disk Access Banner */ -"Full Disk Access Required" = "需要完整磁盘访问权限"; -"PureMac needs Full Disk Access to scan Trash, Mail, Desktop, Documents, and Homebrew cache." = "PureMac 需要完整磁盘访问权限才能扫描废纸篓、邮件、桌面、文稿和 Homebrew 缓存。"; -"Open Settings" = "打开设置"; - -/* Top Bar */ -"Macintosh HD" = "Macintosh HD"; -"%@ free" = "%@ 可用"; -"Auto-clean: " = "自动清理:"; - -/* Sidebar */ -"CLEANING" = "清理"; -"Last cleaned: %@" = "上次清理:%@"; -"Home" = "主页"; +/* Sidebar (MainWindow) */ +"Overview" = "概览"; "Applications" = "应用程序"; -"Cleaning" = "清理"; -"Installed Apps" = "已安装应用"; +"Cleanup" = "清理"; +"Dashboard" = "仪表盘"; +"Installed Apps" = "已安装的应用"; "Orphaned Files" = "孤立文件"; -"Select a category from the sidebar to get started." = "从侧边栏选择一个类别开始。"; +"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" = "授予访问"; -/* Smart Scan */ +/* Dashboard */ +"Storage" = "存储"; "Smart Scan" = "智能扫描"; -"Click Scan to start" = "点击扫描开始"; -"Total" = "总计"; +"free of %@" = "%@ 中的可用空间"; +"%lld%% used" = "已使用 %lld%%"; "Used" = "已用"; -"Free" = "可用"; -"Purgeable" = "可清除"; -"junk found" = "垃圾文件"; -"Your Mac is clean!" = "您的 Mac 很干净!"; -"freed up" = "已释放"; -"Cleaning..." = "正在清理..."; -"Scanning..." = "正在扫描..."; -"%lld/%lld items" = "%lld/%lld 个项目"; +"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" = "按类别"; +"%@ 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 yet" = "尚未扫描"; -"Click Scan to analyze this category" = "点击扫描分析此类别"; -"%lld of %lld selected" = "已选 %lld / %lld"; +"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" = "取消全选"; -"%lld items" = "%lld 个项目"; +"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" = "在访达中显示"; -/* Buttons */ -"Scan" = "扫描"; -"Re-scan" = "重新扫描"; -"Scan Again" = "再次扫描"; -"Done" = "完成"; -"Clean (%@)" = "清理(%@)"; -"Clean %lld items (%@)" = "清理 %lld 个项目(%@)"; +/* 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." = "从列表中选择一个应用以查看系统中所有相关文件。"; -/* Settings - Schedule */ -"Schedule" = "计划"; -"Automatic Cleaning" = "自动清理"; -"Automatically scan and clean your Mac on a schedule" = "按计划自动扫描和清理您的 Mac"; -"Scan Interval" = "扫描间隔"; -"Automation" = "自动化"; -"Auto-clean after scan" = "扫描后自动清理"; -"Minimum junk size to trigger clean:" = "触发清理的最小垃圾大小:"; -"50 MB" = "50 MB"; -"100 MB" = "100 MB"; -"250 MB" = "250 MB"; -"500 MB" = "500 MB"; -"1 GB" = "1 GB"; -"Auto-purge purgeable space" = "自动清除可清除空间"; -"Show notification on completion" = "完成时显示通知"; -"Status" = "状态"; -"Last run" = "上次运行"; -"Next run" = "下次运行"; -"Never" = "从未"; -"Not scheduled" = "未计划"; +/* 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." = "这将永久删除此文件。此操作无法撤销。"; -/* Settings - General */ -"General" = "通用"; -"App Behavior" = "应用程序行为"; -"Launch at login" = "登录时启动"; -"Show in Dock" = "在程序坞中显示"; -"Show menu bar icon" = "显示菜单栏图标"; -"Safety" = "安全"; -"PureMac will never delete system-critical files. Only caches, logs, temporary files, and user-selected items are removed." = "PureMac 永远不会删除系统关键文件。只会删除缓存、日志、临时文件和用户选择的项目。"; +/* 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" = "部分文件无法删除"; + +/* 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" = "使用触控 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. You can grant Full Disk Access later in System Settings." = "部分功能将受限。你可以稍后在“系统设置”中授予完整磁盘访问。"; +"Trash" = "废纸篓"; +"Mail Data" = "邮件数据"; +"Safari Data" = "Safari 数据"; +"Desktop" = "桌面"; +"Documents" = "文稿"; +"TCC Database" = "TCC 数据库"; +"Blocked" = "已阻止"; -/* Settings - About */ +/* Settings */ +"General" = "通用"; +"Cleaning" = "清理"; +"Schedule" = "计划"; "About" = "关于"; -"Version 1.0.0" = "版本 1.0.0"; -"A free, open-source Mac cleaning utility.\nKeep your Mac fast, clean, and optimized." = "免费、开源的 Mac 清理工具。\n让您的 Mac 保持快速、干净和优化。"; +"Startup" = "启动"; +"Launch PureMac at login" = "登录时启动 PureMac"; +"App Scanning" = "应用扫描"; +"Search sensitivity" = "搜索灵敏度"; +"Strict" = "严格"; +"Enhanced" = "增强"; +"Deep" = "深度"; +"Exact bundle ID and name matches only. Safest option." = "仅精确匹配套件 ID 和名称。最安全。"; +"Includes partial name matching and bundle ID components." = "包含部分名称匹配和套件 ID 组件。"; +"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" = "阿拉伯语"; +"Portuguese (Brazil)" = "葡萄牙语(巴西)"; +"Chinese (Simplified)" = "简体中文"; +"Chinese (Traditional)" = "繁体中文"; +"Safety" = "安全"; +"Confirm before deleting files" = "删除文件前确认"; +"File Discovery" = "文件发现"; +"Skip hidden files during scan" = "扫描时跳过隐藏文件"; +"Large Files" = "大文件"; +"Minimum size: %lld MB" = "最小大小: %lld MB"; +"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 仓库"; -"MIT License" = "MIT 许可证"; +"Report an Issue" = "报告问题"; +"MIT License" = "MIT 许可"; -/* Cleaning Categories */ +/* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "系统垃圾"; "User Cache" = "用户缓存"; "AI Apps" = "AI 应用"; "Mail Files" = "邮件文件"; "Trash Bins" = "废纸篓"; -"Large & Old Files" = "大型旧文件"; -"Purgeable Space" = "可清除空间"; +"Large & Old Files" = "大文件和旧文件"; +"Purgeable Space" = "可清理空间"; "Xcode Junk" = "Xcode 垃圾"; "Brew Cache" = "Brew 缓存"; +"Node Cache" = "Node 缓存"; +"Docker Cache" = "Docker 缓存"; -/* Category Descriptions */ -"Scan everything at once" = "一次性扫描所有内容"; +/* Cleaning category descriptions */ +"Scan everything at once" = "一次扫描所有内容"; "System caches, logs, and temporary files" = "系统缓存、日志和临时文件"; "Application caches and browser data" = "应用程序缓存和浏览器数据"; -"Logs, caches, and temporary files from local AI apps" = "本地 AI 应用的日志、缓存和临时文件"; -"Local AI app logs, caches, and optional history" = "本地 AI 应用的日志、缓存和可选历史记录"; -"Downloaded mail attachments" = "下载的邮件附件"; +"Local AI app logs, caches, and optional history" = "本地 AI 应用的日志、缓存和可选历史"; +"Downloaded mail attachments" = "已下载的邮件附件"; "Files in your Trash" = "废纸篓中的文件"; -"Files over 100 MB or older than 1 year" = "超过 100 MB 或一年以上的文件"; -"APFS purgeable disk space" = "APFS 可清除磁盘空间"; -"Derived data, archives, and simulators" = "衍生数据、归档和模拟器"; +"Files over 100 MB or older than 1 year" = "大于 100 MB 或超过 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 镜像、容器和构建缓存"; + +/* 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`)"; -/* Schedule Intervals */ +/* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "每小时"; "Every 3 Hours" = "每 3 小时"; "Every 6 Hours" = "每 6 小时"; "Every 12 Hours" = "每 12 小时"; "Daily" = "每天"; "Weekly" = "每周"; -"Every 2 Weeks" = "每两周"; +"Every 2 Weeks" = "每 2 周"; "Monthly" = "每月"; -/* Notifications */ -"Found %@ of junk files." = "发现了 %@ 的垃圾文件。"; +/* Schedule status */ +"Never" = "从不"; +"Not scheduled" = "未计划"; -/* Node Cache */ -"Node Cache" = "Node 缓存"; -"npm, yarn, and pnpm download caches" = "npm、yarn 和 pnpm 下载缓存"; -"npm cache" = "npm 缓存"; -"yarn classic cache" = "yarn 经典缓存"; -"pnpm content-addressable store" = "pnpm 内容寻址存储"; +/* Appearance modes (AppearanceMode.label) */ +"System" = "系统"; +"Light" = "浅色"; +"Dark" = "深色"; -/* Docker Cache */ -"Docker Cache" = "Docker 缓存"; -"Docker images, containers, and build cache" = "Docker 镜像、容器和构建缓存"; -"Reclaimable (run `docker system prune -af`)" = "可回收空间(运行 `docker system prune -af`)"; +/* Notifications */ +"Found %@ of junk files." = "发现 %@ 的垃圾文件。"; diff --git a/PureMac/zh-Hant.lproj/Localizable.strings b/PureMac/zh-Hant.lproj/Localizable.strings index 4f596fb..4b1ef89 100644 --- a/PureMac/zh-Hant.lproj/Localizable.strings +++ b/PureMac/zh-Hant.lproj/Localizable.strings @@ -1,125 +1,262 @@ -/* Full Disk Access Banner */ -"Full Disk Access Required" = "需要完整磁碟存取權限"; -"PureMac needs Full Disk Access to scan Trash, Mail, Desktop, Documents, and Homebrew cache." = "PureMac 需要完整磁碟存取權限才能掃描垃圾桶、郵件、桌面、文件及 Homebrew 快取。"; -"Open Settings" = "開啟設定"; - -/* Top Bar */ -"Macintosh HD" = "Macintosh HD"; -"%@ free" = "%@ 可用"; -"Auto-clean: " = "自動清理:"; - -/* Sidebar */ -"CLEANING" = "清理"; -"Last cleaned: %@" = "上次清理:%@"; -"Home" = "首頁"; +/* Sidebar (MainWindow) */ +"Overview" = "概覽"; "Applications" = "應用程式"; -"Cleaning" = "清理"; -"Installed Apps" = "已安裝應用程式"; +"Cleanup" = "清理"; +"Dashboard" = "儀表板"; +"Installed Apps" = "已安裝的應用程式"; "Orphaned Files" = "孤立檔案"; -"Select a category from the sidebar to get started." = "從側邊欄選擇一個類別開始。"; +"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" = "授予取用"; -/* Smart Scan */ +/* Dashboard */ +"Storage" = "儲存空間"; "Smart Scan" = "智慧掃描"; -"Click Scan to start" = "點一下「掃描」開始"; -"Total" = "總計"; -"Used" = "已使用"; -"Free" = "可用"; +"free of %@" = "%@ 中的可用空間"; +"%lld%% used" = "已使用 %lld%%"; +"Used" = "已用"; +"Junk" = "垃圾"; "Purgeable" = "可清除"; -"junk found" = "發現垃圾檔案"; -"Your Mac is clean!" = "您的 Mac 很乾淨!"; -"freed up" = "已釋出"; -"Cleaning..." = "正在清理…"; -"Scanning..." = "正在掃描…"; -"%lld/%lld items" = "%lld/%lld 個項目"; +"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" = "依類別"; +"%@ 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 yet" = "尚未掃描"; -"Click Scan to analyze this category" = "點一下「掃描」以分析此類別"; -"%lld of %lld selected" = "已選取 %lld / %lld"; +"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" = "取消全選"; -"%lld items" = "%lld 個項目"; +"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 中顯示"; -/* Buttons */ -"Scan" = "掃描"; -"Re-scan" = "重新掃描"; -"Scan Again" = "再次掃描"; -"Done" = "完成"; -"Clean (%@)" = "清理(%@)"; -"Clean %lld items (%@)" = "清理 %lld 個項目(%@)"; +/* 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." = "這會永久刪除此檔案。此動作無法復原。"; -/* Settings - Schedule */ +/* 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" = "部分檔案無法移除"; + +/* 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" = "使用觸控 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. You can grant Full Disk Access later in System Settings." = "部分功能將受到限制。你可以稍後在「系統設定」中授予完整磁碟取用權。"; +"Trash" = "垃圾桶"; +"Mail Data" = "Mail 資料"; +"Safari Data" = "Safari 資料"; +"Desktop" = "桌面"; +"Documents" = "文件"; +"TCC Database" = "TCC 資料庫"; +"Blocked" = "已阻擋"; + +/* Settings */ +"General" = "一般"; +"Cleaning" = "清理"; "Schedule" = "排程"; -"Automatic Cleaning" = "自動清理"; -"Automatically scan and clean your Mac on a schedule" = "依排程自動掃描與清理您的 Mac"; -"Scan Interval" = "掃描間隔"; -"Automation" = "自動化"; +"About" = "關於"; +"Startup" = "啟動"; +"Launch PureMac at login" = "登入時啟動 PureMac"; +"App Scanning" = "應用程式掃描"; +"Search sensitivity" = "搜尋敏感度"; +"Strict" = "嚴格"; +"Enhanced" = "加強"; +"Deep" = "深度"; +"Exact bundle ID and name matches only. Safest option." = "僅精確比對套件 ID 與名稱。最安全選項。"; +"Includes partial name matching and bundle ID components." = "包含部分名稱比對與套件 ID 組件。"; +"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" = "阿拉伯文"; +"Portuguese (Brazil)" = "葡萄牙文(巴西)"; +"Chinese (Simplified)" = "簡體中文"; +"Chinese (Traditional)" = "繁體中文"; +"Safety" = "安全"; +"Confirm before deleting files" = "刪除檔案前確認"; +"File Discovery" = "檔案探索"; +"Skip hidden files during scan" = "掃描時略過隱藏檔案"; +"Large Files" = "大型檔案"; +"Minimum size: %lld MB" = "最小大小: %lld MB"; +"Files older than: %lld months" = "早於: %lld 個月的檔案"; +"Automatic Scanning" = "自動掃描"; +"Enable scheduled scanning" = "啟用排程掃描"; +"Scan interval" = "掃描間隔"; "Auto-clean after scan" = "掃描後自動清理"; -"Minimum junk size to trigger clean:" = "觸發清理的最小垃圾大小:"; -"50 MB" = "50 MB"; -"100 MB" = "100 MB"; -"250 MB" = "250 MB"; -"500 MB" = "500 MB"; -"1 GB" = "1 GB"; "Auto-purge purgeable space" = "自動清除可清除空間"; -"Show notification on completion" = "完成時顯示通知"; -"Status" = "狀態"; +"Notify on completion" = "完成時通知"; "Last run" = "上次執行"; -"Next run" = "下次執行"; -"Never" = "從未"; -"Not scheduled" = "未排程"; - -/* Settings - General */ -"General" = "一般"; -"App Behavior" = "應用程式行為"; -"Launch at login" = "登入時啟動"; -"Show in Dock" = "於 Dock 中顯示"; -"Show menu bar icon" = "顯示選單列圖示"; -"Safety" = "安全性"; -"PureMac will never delete system-critical files. Only caches, logs, temporary files, and user-selected items are removed." = "PureMac 絕不會刪除系統關鍵檔案。僅會移除快取、記錄檔、暫存檔案及使用者選取的項目。"; - -/* Settings - About */ -"About" = "關於"; -"Version 1.0.0" = "版本 1.0.0"; -"A free, open-source Mac cleaning utility.\nKeep your Mac fast, clean, and optimized." = "免費、開放原始碼的 Mac 清理工具。\n讓您的 Mac 保持快速、乾淨且最佳化。"; +"Version %@" = "版本 %@"; +"Free, open-source macOS app manager." = "免費、開放原始碼的 macOS 應用程式管理工具。"; "GitHub Repository" = "GitHub 儲存庫"; +"Report an Issue" = "回報問題"; "MIT License" = "MIT 授權"; -/* Cleaning Categories */ +/* Cleaning categories (CleaningCategory.rawValue) */ "System Junk" = "系統垃圾"; "User Cache" = "使用者快取"; "AI Apps" = "AI 應用程式"; -"Mail Files" = "郵件檔案"; +"Mail Files" = "Mail 檔案"; "Trash Bins" = "垃圾桶"; "Large & Old Files" = "大型與舊檔案"; "Purgeable Space" = "可清除空間"; "Xcode Junk" = "Xcode 垃圾"; "Brew Cache" = "Brew 快取"; +"Node Cache" = "Node 快取"; +"Docker Cache" = "Docker 快取"; -/* Category Descriptions */ +/* Cleaning category descriptions */ "Scan everything at once" = "一次掃描所有項目"; -"System caches, logs, and temporary files" = "系統快取、記錄檔及暫存檔案"; +"System caches, logs, and temporary files" = "系統快取、紀錄與暫存檔案"; "Application caches and browser data" = "應用程式快取與瀏覽器資料"; -"Logs, caches, and temporary files from local AI apps" = "來自本機 AI 應用程式的日誌、快取和暫存檔案"; -"Local AI app logs, caches, and optional history" = "本機 AI 應用程式的日誌、快取與可選歷史記錄"; +"Local AI app logs, caches, and optional history" = "本地 AI 應用程式的紀錄、快取與選用記錄"; "Downloaded mail attachments" = "已下載的郵件附件"; "Files in your Trash" = "垃圾桶中的檔案"; -"Files over 100 MB or older than 1 year" = "超過 100 MB 或超過一年未使用的檔案"; -"APFS purgeable disk space" = "APFS 可清除磁碟空間"; -"Derived data, archives, and simulators" = "衍生資料、封存檔與模擬器"; +"Files over 100 MB or older than 1 year" = "超過 100 MB 或一年以上的檔案"; +"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 映像檔、容器與建置快取"; -/* Schedule Intervals */ +/* 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`)"; + +/* Schedule intervals (ScheduleInterval.rawValue) */ "Every Hour" = "每小時"; "Every 3 Hours" = "每 3 小時"; "Every 6 Hours" = "每 6 小時"; "Every 12 Hours" = "每 12 小時"; "Daily" = "每天"; "Weekly" = "每週"; -"Every 2 Weeks" = "每兩週"; +"Every 2 Weeks" = "每 2 週"; "Monthly" = "每月"; +/* Schedule status */ +"Never" = "從未"; +"Not scheduled" = "未排程"; + +/* Appearance modes (AppearanceMode.label) */ +"System" = "系統"; +"Light" = "淺色"; +"Dark" = "深色"; + /* Notifications */ -"Found %@ of junk files." = "發現 %@ 的垃圾檔案。"; +"Found %@ of junk files." = "找到 %@ 的垃圾檔案。"; diff --git a/PureMacTests/AppLanguagePreferencesTests.swift b/PureMacTests/AppLanguagePreferencesTests.swift new file mode 100644 index 0000000..1dcb320 --- /dev/null +++ b/PureMacTests/AppLanguagePreferencesTests.swift @@ -0,0 +1,34 @@ +import XCTest +@testable import PureMac + +final class AppLanguagePreferencesTests: XCTestCase { + func testApplyCustomLanguageSetsAppleLanguagesAndPreservesLocale() { + let context = makeDefaults() + let defaults = context.defaults + defaults.set("pt_BR", forKey: "AppleLocale") + + AppLanguagePreferences.apply(.english, defaults: defaults) + + XCTAssertEqual(defaults.array(forKey: "AppleLanguages") as? [String], ["en"]) + XCTAssertEqual(defaults.string(forKey: "AppleLocale"), "pt_BR") + } + + func testApplySystemLanguageRemovesAppleLanguagesAndPreservesLocale() { + let context = makeDefaults() + let defaults = context.defaults + defaults.set(["en"], forKey: "AppleLanguages") + defaults.set("pt_BR", forKey: "AppleLocale") + + AppLanguagePreferences.apply(.system, defaults: defaults) + + XCTAssertNil(defaults.persistentDomain(forName: context.suiteName)?["AppleLanguages"]) + XCTAssertEqual(defaults.string(forKey: "AppleLocale"), "pt_BR") + } + + private func makeDefaults() -> (defaults: UserDefaults, suiteName: String) { + let suiteName = "PureMacTests.AppLanguagePreferences.\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: suiteName)! + defaults.removePersistentDomain(forName: suiteName) + return (defaults, suiteName) + } +} diff --git a/PureMacTests/AppStateTests.swift b/PureMacTests/AppStateTests.swift new file mode 100644 index 0000000..f87c6c4 --- /dev/null +++ b/PureMacTests/AppStateTests.swift @@ -0,0 +1,61 @@ +import AppKit +import XCTest +@testable import PureMac + +@MainActor +final class AppStateTests: XCTestCase { + func testScanForAppFilesTracksLocationsWhileResultsArePending() throws { + var completion: ((Set) -> Void)? + let expectedLocations = ["/one", "/two", "/three"] + let appState = AppState( + performStartupTasks: false, + locationsProvider: { + StubLocations(paths: expectedLocations) + }, + appFileScanner: { _, locations, pendingCompletion in + XCTAssertEqual(locations.appSearch.paths, expectedLocations) + completion = pendingCompletion + } + ) + + appState.scanForAppFiles(makeApp()) + + XCTAssertTrue(appState.isScanningAppFiles) + XCTAssertTrue(appState.discoveredFiles.isEmpty) + XCTAssertEqual(appState.currentAppFileSearchLocationCount, expectedLocations.count) + + let pendingCompletion = try XCTUnwrap(completion) + let urls: Set = [ + URL(fileURLWithPath: "/tmp/B"), + URL(fileURLWithPath: "/tmp/A") + ] + + pendingCompletion(urls) + + XCTAssertFalse(appState.isScanningAppFiles) + XCTAssertEqual( + appState.discoveredFiles, + urls.sorted { $0.path < $1.path } + ) + XCTAssertEqual(appState.selectedFiles, urls) + XCTAssertEqual(appState.currentAppFileSearchLocationCount, urls.count) + } + + private func makeApp() -> InstalledApp { + InstalledApp( + id: UUID(), + appName: "PureMac", + bundleIdentifier: "com.puremac.app", + path: URL(fileURLWithPath: "/Applications/PureMac.app"), + icon: NSImage(size: NSSize(width: 32, height: 32)), + size: 1 + ) + } +} + +private final class StubLocations: Locations { + init(paths: [String]) { + super.init() + appSearch = SearchCategory(name: "Apps", paths: paths) + } +} diff --git a/PureMacTests/LocalizationFilesTests.swift b/PureMacTests/LocalizationFilesTests.swift new file mode 100644 index 0000000..949abbb --- /dev/null +++ b/PureMacTests/LocalizationFilesTests.swift @@ -0,0 +1,60 @@ +import XCTest + +final class LocalizationFilesTests: XCTestCase { + func testAllLocalizableStringsFilesHaveEnglishKeyParity() throws { + let localizationFiles = try localizableStringsFiles() + let englishURL = try XCTUnwrap( + localizationFiles["en"], + "Expected en.lproj/Localizable.strings to exist" + ) + let englishKeys = try localizedKeys(in: englishURL) + + for (language, fileURL) in localizationFiles where language != "en" { + let languageKeys = try localizedKeys(in: fileURL) + let missingKeys = englishKeys.subtracting(languageKeys).sorted() + let extraKeys = languageKeys.subtracting(englishKeys).sorted() + + XCTAssertTrue( + missingKeys.isEmpty, + "\(language).lproj/Localizable.strings is missing keys:\n\(missingKeys.joined(separator: "\n"))" + ) + XCTAssertTrue( + extraKeys.isEmpty, + "\(language).lproj/Localizable.strings has extra keys:\n\(extraKeys.joined(separator: "\n"))" + ) + } + } + + private func localizableStringsFiles() throws -> [String: URL] { + let sourceRoot = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + let appSourceDirectory = sourceRoot.appendingPathComponent("PureMac") + let contents = try FileManager.default.contentsOfDirectory( + at: appSourceDirectory, + includingPropertiesForKeys: [.isDirectoryKey] + ) + + return contents.reduce(into: [String: URL]()) { result, url in + guard url.pathExtension == "lproj", + FileManager.default.fileExists(atPath: url.appendingPathComponent("Localizable.strings").path) + else { + return + } + + result[url.deletingPathExtension().lastPathComponent] = url.appendingPathComponent("Localizable.strings") + } + } + + private func localizedKeys(in fileURL: URL) throws -> Set { + 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 Set(strings.keys) + } +} diff --git a/project.yml b/project.yml index 41c7d21..44520b3 100644 --- a/project.yml +++ b/project.yml @@ -40,3 +40,25 @@ targets: Debug: CODE_SIGNING_ALLOWED: "NO" CODE_SIGNING_REQUIRED: "NO" + + PureMacTests: + type: bundle.unit-test + platform: macOS + sources: + - PureMacTests + dependencies: + - target: PureMac + settings: + base: + CODE_SIGNING_ALLOWED: "NO" + CODE_SIGNING_REQUIRED: "NO" + +schemes: + PureMac: + build: + targets: + PureMac: all + PureMacTests: [test] + test: + targets: + - name: PureMacTests