Skip to content

Commit ff2fc13

Browse files
authored
feat: localize popup and provider settings UI (#1181)
Localize popup panels, menu labels, and provider settings across supported languages. Maintainer follow-up preserves provider-supplied organization names and localizes dynamic cookie-source subtitles through shared templates. Co-authored-by: Shun Min Chang <ji394m6y7@gmail.com>
1 parent c3bc1ad commit ff2fc13

51 files changed

Lines changed: 2653 additions & 398 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Changed
66
- Tests: document and audit that routine validation must not trigger macOS Keychain prompts.
7+
- Localization: localize popup panels and provider settings UI across supported languages (#1181). Thanks @jack24254029!
78

89
### Added
910
- AWS Bedrock: support resolving usage and cost-history credentials from a named AWS profile via the AWS CLI (#1190). Thanks @oleksandr-soldatov!

Sources/CodexBar/CodexAccountPromotionCoordinator.swift

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,36 +73,37 @@ final class CodexAccountPromotionCoordinator {
7373

7474
private static func interactionBlockedError() -> CodexSystemAccountPromotionUserFacingError {
7575
CodexSystemAccountPromotionUserFacingError(
76-
title: "Could not switch system account",
77-
message: "Finish the current managed account change before switching the system account.")
76+
title: L("Could not switch system account"),
77+
message: L("Finish the current managed account change before switching the system account."))
7878
}
7979

8080
static func mapUserFacingError(_ error: Error) -> CodexSystemAccountPromotionUserFacingError {
81-
let title = "Could not switch system account"
81+
let title = L("Could not switch system account")
8282

8383
if let error = error as? CodexAccountPromotionError {
8484
let message = switch error {
8585
case .targetManagedAccountNotFound:
86-
"That account is no longer available in CodexBar. Refresh the account list and try again."
86+
L("That account is no longer available in CodexBar. Refresh the account list and try again.")
8787
case .targetManagedAccountAuthMissing:
88-
"CodexBar could not find saved auth for that account. Re-authenticate it and try again."
88+
L("CodexBar could not find saved auth for that account. Re-authenticate it and try again.")
8989
case .targetManagedAccountAuthUnreadable:
90-
"CodexBar could not read saved auth for that account. Re-authenticate it and try again."
90+
L("CodexBar could not read saved auth for that account. Re-authenticate it and try again.")
9191
case .liveAccountUnreadable:
92-
"CodexBar could not read the current system account on this Mac."
92+
L("CodexBar could not read the current system account on this Mac.")
9393
case .liveAccountMissingIdentityForPreservation:
94-
"CodexBar could not safely preserve the current system account before switching."
94+
L("CodexBar could not safely preserve the current system account before switching.")
9595
case .liveAccountAPIKeyOnlyUnsupported:
96-
"CodexBar can't replace a system account that is signed in with an API key only setup."
96+
L("CodexBar can't replace a system account that is signed in with an API key only setup.")
9797
case .displacedLiveManagedAccountConflict:
98-
"CodexBar found another managed account that already uses the current system account. "
99-
+ "Resolve the duplicate account before switching."
98+
L(
99+
"CodexBar found another managed account that already uses the current system account. " +
100+
"Resolve the duplicate account before switching.")
100101
case .displacedLiveImportFailed:
101-
"CodexBar could not save the current system account before switching."
102+
L("CodexBar could not save the current system account before switching.")
102103
case .managedStoreCommitFailed:
103-
"CodexBar could not update managed account storage."
104+
L("CodexBar could not update managed account storage.")
104105
case .liveAuthSwapFailed:
105-
"CodexBar could not replace the live Codex auth on this Mac."
106+
L("CodexBar could not replace the live Codex auth on this Mac.")
106107
}
107108

108109
return CodexSystemAccountPromotionUserFacingError(title: title, message: message)

Sources/CodexBar/CodexLoginAlertPresentation.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,25 @@ enum CodexLoginAlertPresentation {
1212
return nil
1313
case .missingBinary:
1414
return CodexLoginAlertInfo(
15-
title: "Codex CLI not found",
16-
message: "Install the Codex CLI (npm i -g @openai/codex) and try again.")
15+
title: L("Codex CLI not found"),
16+
message: L("Install the Codex CLI (npm i -g @openai/codex) and try again."))
1717
case let .launchFailed(message):
18-
return CodexLoginAlertInfo(title: "Could not start codex login", message: message)
18+
return CodexLoginAlertInfo(title: L("Could not start codex login"), message: message)
1919
case .timedOut:
2020
return CodexLoginAlertInfo(
21-
title: "Codex login timed out",
21+
title: L("Codex login timed out"),
2222
message: self.trimmedOutput(result.output))
2323
case let .failed(status):
24-
let statusLine = "codex login exited with status \(status)."
24+
let statusLine = String(format: L("codex login exited with status %d."), status)
2525
let message = self.trimmedOutput(result.output.isEmpty ? statusLine : result.output)
26-
return CodexLoginAlertInfo(title: "Codex login failed", message: message)
26+
return CodexLoginAlertInfo(title: L("Codex login failed"), message: message)
2727
}
2828
}
2929

3030
private static func trimmedOutput(_ text: String) -> String {
3131
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
3232
let limit = 600
33-
if trimmed.isEmpty { return "No output captured." }
33+
if trimmed.isEmpty { return L("No output captured.") }
3434
if trimmed.count <= limit { return trimmed }
3535
let idx = trimmed.index(trimmed.startIndex, offsetBy: limit)
3636
return "\(trimmed[..<idx])"

Sources/CodexBar/CodexLoginRunner.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ struct CodexLoginRunner {
124124
}
125125
let trimmed = merged.trimmingCharacters(in: .whitespacesAndNewlines)
126126
let limited = trimmed.prefix(4000)
127-
return limited.isEmpty ? "No output captured." : String(limited)
127+
return limited.isEmpty ? L("No output captured.") : String(limited)
128128
}
129129

130130
private static func readToEnd(_ pipe: Pipe, timeout: TimeInterval = 3.0) async -> String {

Sources/CodexBar/CostHistoryChartMenuView.swift

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,16 @@ struct CostHistoryChartMenuView: View {
7474
Chart {
7575
ForEach(model.points) { point in
7676
BarMark(
77-
x: .value("Day", point.date, unit: .day),
78-
y: .value("Cost", point.costUSD))
77+
x: .value(L("Day"), point.date, unit: .day),
78+
y: .value(L("Cost"), point.costUSD))
7979
.foregroundStyle(model.barColor)
8080
}
8181
if let peak = Self.peakPoint(model: model) {
8282
let capStart = max(peak.costUSD - Self.capHeight(maxValue: model.maxCostUSD), 0)
8383
BarMark(
84-
x: .value("Day", peak.date, unit: .day),
85-
yStart: .value("Cap start", capStart),
86-
yEnd: .value("Cap end", peak.costUSD))
84+
x: .value(L("Day"), peak.date, unit: .day),
85+
yStart: .value(L("Cap start"), capStart),
86+
yEnd: .value(L("Cap end"), peak.costUSD))
8787
.foregroundStyle(Color(nsColor: .systemYellow))
8888
}
8989
}
@@ -99,8 +99,11 @@ struct CostHistoryChartMenuView: View {
9999
}
100100
.chartLegend(.hidden)
101101
.frame(height: 130)
102-
.accessibilityLabel("Cost history chart")
103-
.accessibilityValue(model.points.isEmpty ? "No data" : "\(model.points.count) days of cost data")
102+
.accessibilityLabel(L("Cost history chart"))
103+
.accessibilityValue(
104+
model.points.isEmpty
105+
? L("No data")
106+
: String(format: L("%d days of cost data"), model.points.count))
104107
.chartOverlay { proxy in
105108
GeometryReader { geo in
106109
ZStack(alignment: .topLeading) {

Sources/CodexBar/CreditsHistoryChartMenuView.swift

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,24 @@ struct CreditsHistoryChartMenuView: View {
2929
let model = Self.makeModel(from: self.breakdown)
3030
VStack(alignment: .leading, spacing: 10) {
3131
if model.points.isEmpty {
32-
Text("No credits history data.")
32+
Text(L("No credits history data."))
3333
.font(.footnote)
3434
.foregroundStyle(.secondary)
35-
.accessibilityLabel("No credits history data available.")
35+
.accessibilityLabel(L("No credits history data available."))
3636
} else {
3737
Chart {
3838
ForEach(model.points) { point in
3939
BarMark(
40-
x: .value("Day", point.date, unit: .day),
41-
y: .value("Credits used", point.creditsUsed))
40+
x: .value(L("Day"), point.date, unit: .day),
41+
y: .value(L("Credits used"), point.creditsUsed))
4242
.foregroundStyle(Self.barColor)
4343
}
4444
if let peak = Self.peakPoint(model: model) {
4545
let capStart = max(peak.creditsUsed - Self.capHeight(maxValue: model.maxCreditsUsed), 0)
4646
BarMark(
47-
x: .value("Day", peak.date, unit: .day),
48-
yStart: .value("Cap start", capStart),
49-
yEnd: .value("Cap end", peak.creditsUsed))
47+
x: .value(L("Day"), peak.date, unit: .day),
48+
yStart: .value(L("Cap start"), capStart),
49+
yEnd: .value(L("Cap end"), peak.creditsUsed))
5050
.foregroundStyle(Color(nsColor: .systemYellow))
5151
}
5252
}
@@ -62,8 +62,11 @@ struct CreditsHistoryChartMenuView: View {
6262
}
6363
.chartLegend(.hidden)
6464
.frame(height: 130)
65-
.accessibilityLabel("Credits history chart")
66-
.accessibilityValue(model.points.isEmpty ? "No data" : "\(model.points.count) days of credits data")
65+
.accessibilityLabel(L("Credits history chart"))
66+
.accessibilityValue(
67+
model.points.isEmpty
68+
? L("No data")
69+
: String(format: L("%d days of credits data"), model.points.count))
6770
.chartOverlay { proxy in
6871
GeometryReader { geo in
6972
ZStack(alignment: .topLeading) {
@@ -101,7 +104,9 @@ struct CreditsHistoryChartMenuView: View {
101104
}
102105

103106
if let total = model.totalCreditsUsed {
104-
Text("Total (30d): \(total.formatted(.number.precision(.fractionLength(0...2)))) credits")
107+
Text(String(
108+
format: L("Total (30d): %@ credits"),
109+
total.formatted(.number.precision(.fractionLength(0...2)))))
105110
.font(.caption)
106111
.foregroundStyle(.secondary)
107112
}
@@ -308,11 +313,11 @@ struct CreditsHistoryChartMenuView: View {
308313
let dayLabel = date.formatted(.dateTime.month(.abbreviated).day())
309314
let total = day.totalCreditsUsed.formatted(.number.precision(.fractionLength(0...2)))
310315
if day.services.isEmpty {
311-
return ("\(dayLabel): \(total) credits", nil)
316+
return (String(format: L("%@: %@ credits"), dayLabel, total), nil)
312317
}
313318
if day.services.count <= 1, let first = day.services.first {
314319
let used = first.creditsUsed.formatted(.number.precision(.fractionLength(0...2)))
315-
return ("\(dayLabel): \(used) credits", first.service)
320+
return (String(format: L("%@: %@ credits"), dayLabel, used), first.service)
316321
}
317322

318323
let services = day.services
@@ -324,6 +329,6 @@ struct CreditsHistoryChartMenuView: View {
324329
.map { "\($0.service) \($0.creditsUsed.formatted(.number.precision(.fractionLength(0...2))))" }
325330
.joined(separator: " · ")
326331

327-
return ("\(dayLabel): \(total) credits", services)
332+
return (String(format: L("%@: %@ credits"), dayLabel, total), services)
328333
}
329334
}

Sources/CodexBar/CursorLoginRunner.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ final class CursorLoginRunner {
6666
await self.resetSessionCache()
6767

6868
guard self.openURL(Self.authURL) else {
69-
let message = "Could not open Cursor login in your browser."
69+
let message = L("Could not open Cursor login in your browser.")
7070
onPhaseChange(.failed(message))
7171
self.logger.error("Cursor login browser launch failed")
7272
return Result(outcome: .failed(message), email: nil)
@@ -103,10 +103,13 @@ final class CursorLoginRunner {
103103
}
104104

105105
private static func timeoutMessage(lastError: Error?) -> String {
106-
let hint = "Sign in to cursor.com in your browser, then refresh Cursor in CodexBar."
106+
let hint = L("Sign in to cursor.com in your browser, then refresh Cursor in CodexBar.")
107107
guard let lastError else {
108-
return "Timed out waiting for Cursor login. \(hint)"
108+
return String(format: L("Timed out waiting for Cursor login. %@"), hint)
109109
}
110-
return "Timed out waiting for Cursor login. \(hint) Last error: \(lastError.localizedDescription)"
110+
return String(
111+
format: L("Timed out waiting for Cursor login. %@ Last error: %@"),
112+
hint,
113+
lastError.localizedDescription)
111114
}
112115
}

0 commit comments

Comments
 (0)