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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions PureMac/Logic/Utilities/SimulatorRuntimeSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ enum SimulatorRuntimeSupport {
static let xcrunPath = "/usr/bin/xcrun"
static let xcodeSelectPath = "/usr/bin/xcode-select"
static let missingXcrunMessage =
"Xcode command-line tools not found — install Xcode or run xcode-select --install"
String(localized: "Xcode command-line tools not found — install Xcode or run xcode-select --install")

struct RuntimeInfo: Equatable {
let identifier: String
Expand Down Expand Up @@ -57,7 +57,7 @@ enum SimulatorRuntimeSupport {
var parts: [String] = []
if let platform { parts.append(platform) }
if let version { parts.append(version) }
let base = parts.isEmpty ? "Simulator Runtime" : parts.joined(separator: " ")
let base = parts.isEmpty ? String(localized: "Simulator runtime") : parts.joined(separator: " ")
if let build { return "\(base) (\(build))" }
return base
}()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ enum XcodeBuildMCPDerivedDataSupport {
let tool = root.lastPathComponent
let legacyDerivedData = root.appendingPathComponent("DerivedData", isDirectory: true)
if isRealDirectory(legacyDerivedData, fileManager: fileManager) {
results.append((name: "\(tool): Legacy DerivedData", url: legacyDerivedData))
results.append((name: String(localized: "\(tool): Legacy DerivedData"), url: legacyDerivedData))
}

let workspacesRoot = root.appendingPathComponent("workspaces", isDirectory: true)
Expand Down
12 changes: 6 additions & 6 deletions PureMac/Models/ScanError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ enum ScanError: LocalizedError {
var errorDescription: String? {
switch self {
case .permissionDenied(let path):
return "Permission denied when accessing '\(path)'. Grant Full Disk Access in System Settings."
return String(localized: "Permission denied when accessing '\(path)'. Grant Full Disk Access in System Settings.")
case .directoryEnumerationFailed(let path, let underlying):
return "Failed to enumerate directory '\(path)': \(underlying.localizedDescription)"
return String(localized: "Failed to enumerate directory '\(path)': \(underlying.localizedDescription)")
case .processExecutionFailed(let tool, let underlying):
return "Failed to execute '\(tool)': \(underlying.localizedDescription)"
return String(localized: "Failed to execute '\(tool)': \(underlying.localizedDescription)")
case .invalidData(let context):
return "Invalid data encountered: \(context)"
return String(localized: "Invalid data encountered: \(context)")
case .helperToolUnavailable:
return "The privileged helper tool is not installed or unavailable."
return String(localized: "The privileged helper tool is not installed or unavailable.")
case .operationCancelled:
return "The operation was cancelled."
return String(localized: "The operation was cancelled.")
}
}
}
2 changes: 1 addition & 1 deletion PureMac/Services/ApplicationUpdater.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ final class ApplicationUpdater: ObservableObject {
let stdout = String(decoding: output.stdout, as: UTF8.self)
.trimmingCharacters(in: .whitespacesAndNewlines)
let detail = stderr.isEmpty ? stdout : stderr
return detail.isEmpty ? "Homebrew exited with status \(output.status)." : detail
return detail.isEmpty ? String(localized: "Homebrew exited with status \(output.status).") : detail
}

nonisolated private static func fetchUpdates(using brewURL: URL) async throws -> [ManagedAppUpdate] {
Expand Down
24 changes: 12 additions & 12 deletions PureMac/Services/BinaryThinner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,21 @@ actor BinaryThinner {
var errorDescription: String? {
switch self {
case .needsAdmin(let path):
return "Not writable by current user: \(path)"
return String(localized: "Not writable by current user: \(path)")
case .restrictedEntitlements(let app):
return "Cannot modify \(app): its entitlements require the original developer signature"
return String(localized: "Cannot modify \(app): its entitlements require the original developer signature")
case .signatureProtected(let app):
return "Cannot thin \(app): it is signed and notarized, and stripping a slice would break its signature"
return String(localized: "Cannot thin \(app): it is signed and notarized, and stripping a slice would break its signature")
case .lipoFailed(let path, let detail):
return "lipo failed for \(path): \(detail)"
return String(localized: "lipo failed for \(path): \(detail)")
case .swapFailed(let path, let detail):
return "Could not swap modified bundle into place at \(path): \(detail)"
return String(localized: "Could not swap modified bundle into place at \(path): \(detail)")
case .codesignFailed(let app, let detail):
return "Re-signing failed for \(app): \(detail)"
return String(localized: "Re-signing failed for \(app): \(detail)")
case .verificationFailed(let app, let detail):
return "Signature verification failed for \(app): \(detail)"
return String(localized: "Signature verification failed for \(app): \(detail)")
case .nothingToThin(let app):
return "No removable slices in \(app)"
return String(localized: "No removable slices in \(app)")
}
}
}
Expand Down Expand Up @@ -125,7 +125,7 @@ actor BinaryThinner {
let app = (appPath as NSString).standardizingPath
let lproj = (lprojPath as NSString).standardizingPath
guard lproj.hasPrefix(app + "/") else {
return .failure(ThinningError.swapFailed(lprojPath, "localization is outside the app bundle"))
return .failure(ThinningError.swapFailed(lprojPath, String(localized: "localization is outside the app bundle")))
}

let appName = ((app as NSString).lastPathComponent as NSString).deletingPathExtension
Expand Down Expand Up @@ -199,7 +199,7 @@ actor BinaryThinner {
try fileManager.copyItem(atPath: appPath, toPath: stagingPath)
} catch {
try? fileManager.removeItem(atPath: stagingPath)
return .failure(ThinningError.swapFailed(appPath, "could not stage a working copy: \(error.localizedDescription)"))
return .failure(ThinningError.swapFailed(appPath, String(localized: "could not stage a working copy: \(error.localizedDescription)")))
}

let freedBytes: Int64
Expand Down Expand Up @@ -275,7 +275,7 @@ actor BinaryThinner {
try fileManager.moveItem(atPath: oldPath, toPath: appPath)
} catch {
Logger.shared.log("Could not undo bundle swap for \(appPath); original remains at \(oldPath): \(error.localizedDescription)", level: .error)
return .failure(ThinningError.swapFailed(appPath, "original bundle left at \(oldPath)"))
return .failure(ThinningError.swapFailed(appPath, String(localized: "original bundle left at \(oldPath)")))
}
return .failure(ThinningError.needsAdmin(appPath))
}
Expand All @@ -288,7 +288,7 @@ actor BinaryThinner {
private func thinBinary(_ binary: FatBinary, appPath: String, stagedPath: String) throws -> Int64 {
let binaryPath = (binary.path as NSString).standardizingPath
guard binaryPath.hasPrefix(appPath + "/") else {
throw ThinningError.lipoFailed(binary.path, "binary is outside the app bundle")
throw ThinningError.lipoFailed(binary.path, String(localized: "binary is outside the app bundle"))
}
let stagedBinary = stagedPath + String(binaryPath.dropFirst(appPath.count))

Expand Down
40 changes: 20 additions & 20 deletions PureMac/Services/CleaningEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ actor CleaningEngine {
let progress = Double(index + 1) / Double(total)
defer { progressHandler(progress) }
if CleanupExclusions.excludes(item.path, paths: exclusions) {
result.errors.append("Excluded from cleanup: \(item.name)")
result.errors.append(String(localized: "Excluded from cleanup: \(item.name)"))
continue
}

Expand Down Expand Up @@ -126,7 +126,7 @@ actor CleaningEngine {
do {
let itemURL = URL(fileURLWithPath: item.path)
guard !hasUnexpectedSymlink(in: item.path) else {
let msg = "Skipped symlink or unsafe path: \(item.path)"
let msg = String(localized: "Skipped symlink or unsafe path: \(item.path)")
Logger.shared.log(msg, level: .warning)
result.errors.append(msg)
continue
Expand All @@ -145,7 +145,7 @@ actor CleaningEngine {
// the staged re-sign flow, not the delete path.
let pathAccepted = isSafeToDelete(item: item, resolvedPath: resolved)
guard pathAccepted else {
let msg = "Skipped symlink or unsafe path: \(item.path) -> \(resolved)"
let msg = String(localized: "Skipped symlink or unsafe path: \(item.path) -> \(resolved)")
Logger.shared.log(msg, level: .warning)
result.errors.append(msg)
continue
Expand All @@ -156,7 +156,7 @@ actor CleaningEngine {
// swap between check and delete aborts the operation.
let reResolved = itemURL.resolvingSymlinksInPath().path
guard reResolved == resolved, !hasUnexpectedSymlink(in: item.path) else {
let msg = "Aborting delete: path resolution changed between check and unlink for \(item.path)"
let msg = String(localized: "Aborting delete: path resolution changed between check and unlink for \(item.path)")
Logger.shared.log(msg, level: .warning)
result.errors.append(msg)
continue
Expand All @@ -183,7 +183,7 @@ actor CleaningEngine {
homeDirectory: homeDirectory,
fileManager: fileManager
) else {
let detail = "Skipped XcodeBuildMCP DerivedData: lifecycle lock is unavailable: \(item.path)"
let detail = String(localized: "Skipped XcodeBuildMCP DerivedData: lifecycle lock is unavailable: \(item.path)")
Logger.shared.log(detail, level: .warning)
result.errors.append(detail)
continue
Expand Down Expand Up @@ -224,7 +224,7 @@ actor CleaningEngine {
item.path,
homeDirectory: homeDirectory
) {
let detail = "Refusing administrator escalation for XcodeBuildMCP DerivedData: \(item.path)"
let detail = String(localized: "Refusing administrator escalation for XcodeBuildMCP DerivedData: \(item.path)")
result.errors.append(detail)
Logger.shared.log(detail, level: .warning)
continue
Expand All @@ -242,7 +242,7 @@ actor CleaningEngine {
result.requiresAdmin.append(item)
Logger.shared.log("Deferring to admin pass: \(item.path)", level: .info)
} else {
let detail = "\(item.name) at \(item.path): \(error.localizedDescription)"
let detail = String(localized: "\(item.name) at \(item.path): \(error.localizedDescription)")
result.errors.append(detail)
Logger.shared.log("Clean failed: \(detail)", level: .error)
}
Expand Down Expand Up @@ -389,7 +389,7 @@ actor CleaningEngine {
result.freedSpace += item.size
Logger.shared.log("Deleted and recreated by its daemon (live log): \(item.path)", level: .info)
} else {
let detail = "\(item.name) at \(item.path) survived admin removal"
let detail = String(localized: "\(item.name) at \(item.path) survived admin removal")
result.errors.append(detail)
Logger.shared.log("Admin pass survivor: \(detail)", level: .error)
}
Expand All @@ -410,7 +410,7 @@ actor CleaningEngine {
func pruneDockerSystem() async -> (freed: Int64, error: String?) {
let dockerBinPaths = ["/usr/local/bin/docker", "/opt/homebrew/bin/docker"]
guard let dockerBin = dockerBinPaths.first(where: { fileManager.isExecutableFile(atPath: $0) }) else {
return (0, "Docker CLI not found — install Docker Desktop or OrbStack")
return (0, String(localized: "Docker CLI not found — install Docker Desktop or OrbStack"))
}

let task = Process()
Expand All @@ -424,7 +424,7 @@ actor CleaningEngine {
try task.run()
} catch {
Logger.shared.log("docker system prune failed to launch: \(error.localizedDescription)", level: .error)
return (0, "Couldn't run docker system prune: \(error.localizedDescription)")
return (0, String(localized: "Couldn't run docker system prune: \(error.localizedDescription)"))
}
// Drain both pipes BEFORE waiting: prune lists every deleted object
// and can overflow the 64 KB pipe buffer, deadlocking waitUntilExit.
Expand All @@ -436,10 +436,10 @@ actor CleaningEngine {
let stderrText = String(data: errData, encoding: .utf8) ?? ""
if stderrText.contains("Cannot connect") || stderrText.contains("dial unix")
|| stderrText.contains("daemon") {
return (0, "Docker isn't running — start Docker Desktop or OrbStack, then clean again")
return (0, String(localized: "Docker isn't running — start Docker Desktop or OrbStack, then clean again"))
}
Logger.shared.log("docker system prune exited \(task.terminationStatus): \(stderrText)", level: .error)
return (0, "docker system prune failed (exit \(task.terminationStatus))")
return (0, String(localized: "docker system prune failed (exit \(task.terminationStatus))"))
}

// Final line reads "Total reclaimed space: 1.234GB" ("0B" when idle).
Expand Down Expand Up @@ -473,10 +473,10 @@ actor CleaningEngine {
}
if detail.localizedCaseInsensitiveContains("CoreSimulator")
|| detail.localizedCaseInsensitiveContains("simdiskimaged") {
return (0, false, "Simulator services unavailable — open Xcode once, then try again")
return (0, false, String(localized: "Simulator services unavailable — open Xcode once, then try again"))
}
Logger.shared.log("simctl runtime delete \(identifier) exited \(result.status): \(detail)", level: .error)
return (0, false, "Couldn't delete simulator runtime \(identifier)")
return (0, false, String(localized: "Couldn't delete simulator runtime \(identifier)"))
}

Logger.shared.log("Deleted simulator runtime \(identifier) (~\(reportedSize) bytes)", level: .info)
Expand Down Expand Up @@ -722,7 +722,7 @@ actor CleaningEngine {
let resolved = URL(fileURLWithPath: item.path).resolvingSymlinksInPath().path
guard isAppBundlePath((resolved as NSString).standardizingPath, rootedAt: "/Applications")
|| isAppBundlePath((resolved as NSString).standardizingPath, rootedAt: "\(fileManager.homeDirectoryForCurrentUser.path)/Applications") else {
let msg = "Skipped unsafe path for thinning: \(item.path) -> \(resolved)"
let msg = String(localized: "Skipped unsafe path for thinning: \(item.path) -> \(resolved)")
Logger.shared.log(msg, level: .warning)
return (0, false, msg)
}
Expand All @@ -739,11 +739,11 @@ actor CleaningEngine {
return (freed, true, nil)
case .failure(let error):
if case BinaryThinner.ThinningError.needsAdmin = error {
let msg = "\(item.name): needs administrator access to thin; skipped"
let msg = String(localized: "\(item.name): needs administrator access to thin; skipped")
Logger.shared.log(msg, level: .warning)
return (0, false, msg)
}
let msg = "Couldn't thin \(item.name): \(error.localizedDescription)"
let msg = String(localized: "Couldn't thin \(item.name): \(error.localizedDescription)")
Logger.shared.log(msg, level: .error)
return (0, false, msg)
}
Expand All @@ -758,7 +758,7 @@ actor CleaningEngine {
let resolved = URL(fileURLWithPath: item.path).resolvingSymlinksInPath().path
let normalized = (resolved as NSString).standardizingPath
guard isRemovableLprojPath(normalized) else {
let msg = "Skipped symlink or unsafe path: \(item.path) -> \(resolved)"
let msg = String(localized: "Skipped symlink or unsafe path: \(item.path) -> \(resolved)")
Logger.shared.log(msg, level: .warning)
return (0, false, msg)
}
Expand All @@ -779,11 +779,11 @@ actor CleaningEngine {
return (item.size, true, nil)
case .failure(let error):
if case BinaryThinner.ThinningError.needsAdmin = error {
let msg = "\(item.name): needs administrator access to modify the app; skipped"
let msg = String(localized: "\(item.name): needs administrator access to modify the app; skipped")
Logger.shared.log(msg, level: .warning)
return (0, false, msg)
}
let msg = "Couldn't remove localization \(item.name): \(error.localizedDescription)"
let msg = String(localized: "Couldn't remove localization \(item.name): \(error.localizedDescription)")
Logger.shared.log(msg, level: .error)
return (0, false, msg)
}
Expand Down
2 changes: 1 addition & 1 deletion PureMac/Services/DuplicateFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ actor DuplicateFinder {
}
let selectedFiles = group.files.filter { selectedIDs.contains($0.url) }
guard selectedFiles.count == selectedIDs.count else {
let unknownPath = selectedIDs.first { !group.contains($0) }?.path ?? "Unknown file"
let unknownPath = selectedIDs.first { !group.contains($0) }?.path ?? String(localized: "Unknown file")
throw DuplicateFinderError.invalidSelection(unknownPath)
}
guard !selectedFiles.isEmpty else {
Expand Down
2 changes: 1 addition & 1 deletion PureMac/Services/PerformanceInspector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ final class PerformanceInspector {
}
let executable = URL(fileURLWithPath: "/usr/bin/tmutil")
guard FileManager.default.isExecutableFile(atPath: executable.path) else {
throw PerformanceInspectorError.commandFailed("tmutil deletelocalsnapshots", -1, "tmutil is unavailable")
throw PerformanceInspectorError.commandFailed("tmutil deletelocalsnapshots", -1, String(localized: "tmutil is not available on this Mac."))
}
let result = try await Self.runCommand(
executable: executable,
Expand Down
Loading
Loading