Skip to content
Merged
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
6 changes: 2 additions & 4 deletions gitrelay.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,6 @@
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = gitrelay/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
Expand Down Expand Up @@ -645,7 +644,6 @@
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = gitrelay/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
Expand Down Expand Up @@ -799,7 +797,7 @@
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
Expand Down Expand Up @@ -829,7 +827,7 @@
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
Expand Down
2 changes: 1 addition & 1 deletion gitrelay/Models/RepoAccountLine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ nonisolated struct RepoAccountLine: Equatable, Sendable {
let names: [String]

var text: String {
String.loc("Account · \(names.joined(separator: " · "))")
String(format: String.loc("Account · %@"), names.joined(separator: " · "))
}

/// Nil when nothing was resolved, which is the quiet case.
Expand Down
14 changes: 7 additions & 7 deletions gitrelay/Services/AppLocalization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ enum AppLocalization {
private static let lock = NSLock()
nonisolated(unsafe) private static var activeOverride: Override?

static func apply(_ preference: AppLanguagePreference, in bundle: Bundle = .main) {
nonisolated static func apply(_ preference: AppLanguagePreference, in bundle: Bundle = .main) {
let resolved = resolveOverride(preference, in: bundle)
lock.lock()
activeOverride = resolved
lock.unlock()
}

static func string(for key: String.LocalizationValue) -> String {
nonisolated static func string(for key: String.LocalizationValue) -> String {
lock.lock()
let active = activeOverride
lock.unlock()
guard let active else { return String(localized: key) }
return String(localized: key, bundle: active.bundle, locale: active.locale)
}

private static func resolveOverride(
private nonisolated static func resolveOverride(
_ preference: AppLanguagePreference,
in bundle: Bundle
) -> Override? {
Expand All @@ -48,7 +48,7 @@ enum AppLocalization {
return Override(locale: preference.locale, bundle: languageBundle)
}

private static func targetLocalization(
private nonisolated static func targetLocalization(
_ preference: AppLanguagePreference,
in bundle: Bundle
) -> String? {
Expand All @@ -61,7 +61,7 @@ enum AppLocalization {
).first
}

static func localizationName(for code: String?, available: [String]) -> String? {
nonisolated static func localizationName(for code: String?, available: [String]) -> String? {
guard let code, !code.isEmpty else { return nil }
if let exact = available.first(where: { $0.caseInsensitiveCompare(code) == .orderedSame }) {
return exact
Expand All @@ -78,7 +78,7 @@ enum AppLocalization {

/// Clears any in-session catalog override. Unit tests should call this after
/// exercising ``apply(_:)`` so later tests see the default catalog.
static func resetOverride() {
nonisolated static func resetOverride() {
lock.lock()
activeOverride = nil
lock.unlock()
Expand All @@ -91,7 +91,7 @@ extension String {
/// Prefer this over `String(localized:)` for anything a running window can
/// show, so switching language in Settings does not leave the sentence in
/// the previous language until the next launch.
static func loc(_ key: String.LocalizationValue) -> String {
nonisolated static func loc(_ key: String.LocalizationValue) -> String {
AppLocalization.string(for: key)
}
}
2 changes: 1 addition & 1 deletion gitrelay/Services/GitLabTargetAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ struct GitLabTargetAPIClient: TargetProviderAPIClient {
)
guard let match = namespaces.first(where: { $0.full_path.lowercased() == owner.lowercased() }) else {
throw TargetProviderAPIError.validation(
String.loc("No GitLab group or user namespace named \(owner) is visible to this token.")
String(format: String.loc("No GitLab group or user namespace named %@ is visible to this token."), owner)
)
}
return match.id
Expand Down
22 changes: 11 additions & 11 deletions gitrelay/Services/IntegrityVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class IntegrityVerifier {
func run() async {
emit(.started)
let branch = RepoConfig.normalizedBranch(repo.defaultBranch)
log(String.loc("Integrity verification started (branch: \(branch))..."))
log(String(format: String.loc("Integrity verification started (branch: %@)..."), branch))

let srcURL = authenticatedURL(url: repo.srcURL, auth: repo.srcAuth)
let srcEnv = buildEnv(for: repo.srcAuth)
Expand All @@ -36,14 +36,14 @@ final class IntegrityVerifier {
let message = enabledTargets.isEmpty
? SyncEngineError.noEnabledTargets.localizedDescription
: "No git remote targets to verify (filesystem archive targets skipped)"
log(String.loc("Error: \(message)"))
log(String(format: String.loc("Error: %@"), message))
record.finishedAt = Date()
emit(.failed(message, record))
return
}

if verifiableTargets.count < enabledTargets.count {
log(String.loc("Skipped \(enabledTargets.count - verifiableTargets.count) filesystem archive targets."))
log(String(format: String.loc("Skipped %lld filesystem archive targets."), enabledTargets.count - verifiableTargets.count))
}

do {
Expand Down Expand Up @@ -115,7 +115,7 @@ final class IntegrityVerifier {
case .diverged(let detail):
targetResult.succeeded = false
targetResult.error = detail.summary
targetLog(String.loc("⚠ Content divergence detected: \(detail.summary)"))
targetLog(String(format: String.loc("⚠ Content divergence detected: %@"), detail.summary))
targetLog(" src tree: \(detail.srcTreeHash.truncatingSHA)")
targetLog(" dst tree: \(detail.dstTreeHash.truncatingSHA)")
divergedDetails.append(detail)
Expand All @@ -124,7 +124,7 @@ final class IntegrityVerifier {
targetResult.succeeded = false
let redacted = SyncEngine.redactCredentials(message)
targetResult.error = redacted
targetLog(String.loc("Inconclusive: \(redacted)"))
targetLog(String(format: String.loc("Inconclusive: %@"), redacted))
inconclusiveMessages.append("\(target.displayLabel): \(redacted)")
}

Expand All @@ -136,7 +136,7 @@ final class IntegrityVerifier {
if let firstDiverged = divergedDetails.first {
record.succeeded = false
let summary = multiTargetDivergenceSummary(details: divergedDetails)
log(String.loc("⚠ Content divergence detected: \(summary)"))
log(String(format: String.loc("⚠ Content divergence detected: %@"), summary))
var detail = firstDiverged
if divergedDetails.count > 1 {
detail.summaryOverride = summary
Expand All @@ -148,13 +148,13 @@ final class IntegrityVerifier {
if !inconclusiveMessages.isEmpty {
record.succeeded = false
let message = inconclusiveMessages.joined(separator: "; ")
log(String.loc("Inconclusive: \(message)"))
log(String(format: String.loc("Inconclusive: %@"), message))
emit(.failed(message, record))
return
}

record.succeeded = matchedCount == verifiableTargets.count
log(String.loc("All \(matchedCount) targets passed verification."))
log(String(format: String.loc("All %lld targets passed verification."), matchedCount))
emit(.completed(.matched(reason: .identicalCommitSHA), record))

} catch GitError.cancelled {
Expand All @@ -164,7 +164,7 @@ final class IntegrityVerifier {

} catch {
let message = SyncEngine.redactCredentials(error.localizedDescription)
log(String.loc("Error: \(message)"))
log(String(format: String.loc("Error: %@"), message))
record.finishedAt = Date()
emit(.failed(message, record))
}
Expand All @@ -178,7 +178,7 @@ final class IntegrityVerifier {

private func multiTargetDivergenceSummary(details: [VerificationDecision.Detail]) -> String {
guard details.count > 1 else { return details[0].summary }
return String.loc("\(details.count) targets have content divergence: \(details[0].summary)")
return String(format: String.loc("%lld targets have content divergence: %@"), details.count, details[0].summary)
}

private func prepareWorkRepo() async throws -> String {
Expand All @@ -200,7 +200,7 @@ final class IntegrityVerifier {
label: String,
log: (String) -> Void
) async throws -> String {
log(String.loc("Fetching \(label) commit \(commitSHA.truncatingSHA)..."))
log(String(format: String.loc("Fetching %@ commit %@..."), label, commitSHA.truncatingSHA))
try await runner.fetchCommit(
repoPath: workPath,
remoteURL: remoteURL,
Expand Down
14 changes: 8 additions & 6 deletions gitrelay/Services/ProviderAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ nonisolated enum ProviderAPIError: LocalizedError {
switch self {
case .unauthorized(let msg):
let base = String.loc("Authentication failed (401): Make sure the token has not expired and has the correct scopes (repo + read:org for GitHub, read_api for GitLab)")
return msg.map { String.loc("\(base). Server message: \($0)") } ?? base
return msg.map { String(format: String.loc("%@. Server message: %@"), base, $0) } ?? base
case .forbidden(let msg):
return msg.map { String.loc("Permission denied or rate limited (403): \($0)") } ?? String.loc("Permission denied or rate limited (403)")
return msg.map { String(format: String.loc("Permission denied or rate limited (403): %@"), $0) }
?? String.loc("Permission denied or rate limited (403)")
case .notFound(let msg):
let base = String.loc("Resource not found (404): Check that the username, organization, or group name is correct")
return msg.map { String.loc("\(base). Server message: \($0)") } ?? base
return msg.map { String(format: String.loc("%@. Server message: %@"), base, $0) } ?? base
case .network(let e):
return String.loc("Network request failed: \(e.localizedDescription)")
return String(format: String.loc("Network request failed: %@"), e.localizedDescription)
case .decoding(let e):
return String.loc("Failed to parse response: \(e.localizedDescription)")
return String(format: String.loc("Failed to parse response: %@"), e.localizedDescription)
case .http(let s, let m):
return m.map { String.loc("HTTP \(s): \($0)") } ?? String.loc("HTTP \(s)")
return m.map { String(format: String.loc("HTTP %@: %@"), String(s), $0) }
?? String(format: String.loc("HTTP %@"), String(s))
}
}
}
Expand Down
26 changes: 18 additions & 8 deletions gitrelay/Services/TargetProviderAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ nonisolated enum TargetNamespace: Hashable, Sendable {
var displayLabel: String {
switch self {
case .currentUser: String.loc("Current User")
case .organization(let org): String.loc("Organization: \(org)")
case .adminForUser(let user): String.loc("Administrator → User: \(user)")
case .organization(let org): String(format: String.loc("Organization: %@"), org)
case .adminForUser(let user): String(format: String.loc("Administrator → User: %@"), user)
}
}
}
Expand All @@ -36,12 +36,22 @@ nonisolated enum TargetProviderAPIError: LocalizedError {

var errorDescription: String? {
switch self {
case .unauthorized(let m): return m.map { String.loc("Authentication failed (401): \($0)") } ?? String.loc("Authentication failed (401)")
case .forbidden(let m): return m.map { String.loc("Permission denied (403): \($0)") } ?? String.loc("Permission denied (403)")
case .validation(let m): return m.map { String.loc("Invalid parameters: \($0)") } ?? String.loc("Invalid parameters (422)")
case .network(let e): return String.loc("Network error: \(e.localizedDescription)")
case .decoding(let e): return String.loc("Failed to parse response: \(e.localizedDescription)")
case .http(let s, let m): return m.map { String.loc("HTTP \(s): \($0)") } ?? String.loc("HTTP \(s)")
case .unauthorized(let m):
return m.map { String(format: String.loc("Authentication failed (401): %@"), $0) }
?? String.loc("Authentication failed (401)")
case .forbidden(let m):
return m.map { String(format: String.loc("Permission denied (403): %@"), $0) }
?? String.loc("Permission denied (403)")
case .validation(let m):
return m.map { String(format: String.loc("Invalid parameters: %@"), $0) }
?? String.loc("Invalid parameters (422)")
case .network(let e):
return String(format: String.loc("Network error: %@"), e.localizedDescription)
case .decoding(let e):
return String(format: String.loc("Failed to parse response: %@"), e.localizedDescription)
case .http(let s, let m):
return m.map { String(format: String.loc("HTTP %@: %@"), String(s), $0) }
?? String(format: String.loc("HTTP %@"), String(s))
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions gitrelay/ViewModels/AppViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ final class AppViewModel {
try MirrorStore.ensureBaseDirectoryExists()
repos = try RepoStore.load()
} catch {
errorMessage = String.loc("Failed to load repository configuration: \(error.localizedDescription)")
errorMessage = String(format: String.loc("Failed to load repository configuration: %@"), error.localizedDescription)
}

self.windowLayout.reconcileSelection(withExistingIDs: Set(repos.map(\.id)))
Expand Down Expand Up @@ -709,7 +709,7 @@ final class AppViewModel {
}
try webhookListener.start()
} catch {
errorMessage = String.loc("Failed to start the webhook listener: \(error.localizedDescription)")
errorMessage = String(format: String.loc("Failed to start the webhook listener: %@"), error.localizedDescription)
webhookListener.stop()
}
}
Expand Down Expand Up @@ -1423,7 +1423,7 @@ final class AppViewModel {
try RepoStore.save(repos)
refreshWidgetSnapshot()
} catch {
errorMessage = String.loc("Failed to save repository configuration: \(error.localizedDescription)")
errorMessage = String(format: String.loc("Failed to save repository configuration: %@"), error.localizedDescription)
}
}

Expand Down
2 changes: 1 addition & 1 deletion gitrelay/Views/About/AboutView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct AboutView: View {
Text(String.loc("GitRelay"))
.font(.title3.weight(.semibold))

Text(String.loc("Version \(version) (\(build))"))
Text(String(format: String.loc("Version %@ (%@)"), version, build))
.font(.callout)
.foregroundStyle(.secondary)
.textSelection(.enabled)
Expand Down
Loading
Loading