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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ show an incident indicator.
- Optional Codex web dashboard enrichments (code review remaining, usage breakdown, credits history).
- Inline spend and usage charts for API-backed providers such as OpenAI, Claude Admin API, OpenRouter, LiteLLM, z.ai, MiniMax, Mistral, and AWS Bedrock.
- Configurable cost-usage scans for Codex + Claude, plus reused chart UI for supported provider histories.
- A persistent Settings → Usage & Spend view for local 7/30-day estimates, grouped by native currency and limited to providers that expose cost history.
- Provider status polling with incident badges in the menu and icon overlay.
- Merge Icons mode to combine providers into one status item + switcher.
- Display controls for provider icons, labels, bars, reset-time style, and highest-usage auto-selection.
Expand Down
22 changes: 18 additions & 4 deletions Sources/CodexBar/Localization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@ private func appLanguageDefaults() -> UserDefaults {

private let isRunningTestsProcessAtStartup: Bool = {
let env = ProcessInfo.processInfo.environment
if env["XCTestConfigurationFilePath"] != nil { return true }
if env["TESTING_LIBRARY_VERSION"] != nil { return true }
if env["SWIFT_TESTING"] != nil { return true }
if env["XCTestConfigurationFilePath"] != nil {
return true
}
if env["TESTING_LIBRARY_VERSION"] != nil {
return true
}
if env["SWIFT_TESTING"] != nil {
return true
}
return NSClassFromString("XCTestCase") != nil
}()

Expand Down Expand Up @@ -210,7 +216,11 @@ func L(_ key: String, language: String) -> String {
func codexBarLocalizedLocale() -> Locale {
let language = resolvedAppLanguage()
guard !language.isEmpty else { return .current }
switch language.lowercased() {
let normalized = language.lowercased()
if normalized == "ar" || normalized.hasPrefix("ar-") {
return Locale(identifier: "\(language)@numbers=arab")
}
switch normalized {
case "zh-hans":
return Locale(identifier: "zh-Hans")
case "zh-hant":
Expand All @@ -222,6 +232,10 @@ func codexBarLocalizedLocale() -> Locale {
}
}

func codexBarLocalizedInteger(_ value: Int) -> String {
value.formatted(.number.locale(codexBarLocalizedLocale()))
}

func codexBarLocalizedString(_ key: String, bundle: Bundle, resourceBundle: Bundle) -> String {
let value = bundle.localizedString(forKey: key, value: nil, table: nil)
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
Expand Down
11 changes: 10 additions & 1 deletion Sources/CodexBar/MenuCardView+Costs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,16 @@ extension UsageMenuCardView.Model {
let fallbackTokens = snapshot.daily.compactMap(\.totalTokens).reduce(0, +)
let monthTokensValue = snapshot.last30DaysTokens ?? (fallbackTokens > 0 ? fallbackTokens : nil)
let monthTokens = monthTokensValue.map { UsageFormatter.tokenCountString($0) }
let windowLabel = snapshot.historyLabel ?? Self.costHistoryWindowLabel(days: snapshot.historyDays)
let windowLabel = if let historyLabel = snapshot.historyLabel {
historyLabel
} else if provider == .mistral,
snapshot.historyDays == 1,
Self.bedrockLatestBillingDay(from: snapshot.daily) != nil
{
L("Latest billing day")
} else {
Self.costHistoryWindowLabel(days: snapshot.historyDays)
}
let monthLine: String = {
if let monthTokens {
return String(format: L("%@: %@ · %@ tokens"), windowLabel, monthCost, monthTokens)
Expand Down
2 changes: 2 additions & 0 deletions Sources/CodexBar/PreferencesSelection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extension SettingsPane {
var persistenceToken: String {
switch self {
case .general: "general"
case .usageSpend: "usageSpend"
case .notifications: "notifications"
case .menuBar: "menuBar"
case .menu: "menu"
Expand All @@ -20,6 +21,7 @@ extension SettingsPane {
init?(persistenceToken: String) {
switch persistenceToken {
case "general": self = .general
case "usageSpend": self = .usageSpend
case "notifications": self = .notifications
case "menuBar": self = .menuBar
// Pre-0.41.1 releases persisted the retired Display pane; its contents moved to Menu Bar.
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/PreferencesSidebar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct SettingsSidebarView: View {
private var appPanesSection: some View {
Section {
SettingsSidebarPaneRow(pane: .general, systemImage: "gearshape.fill", color: .gray)
SettingsSidebarPaneRow(pane: .usageSpend, systemImage: "chart.bar.fill", color: .green)
SettingsSidebarPaneRow(pane: .notifications, systemImage: "bell.badge.fill", color: .red)
SettingsSidebarPaneRow(pane: .menuBar, systemImage: "menubar.rectangle", color: .blue)
SettingsSidebarPaneRow(pane: .menu, systemImage: "filemenu.and.selection", color: .teal)
Expand Down
Loading