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
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let sweetCookieKitDependency: Package.Dependency =

let package = Package(
name: "CodexBar",
defaultLocalization: "en",
platforms: [
.macOS(.v14),
],
Expand Down
12 changes: 6 additions & 6 deletions Sources/CodexBar/CostHistoryChartMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct CostHistoryChartMenuView: View {
let model = Self.makeModel(provider: self.provider, daily: self.daily)
VStack(alignment: .leading, spacing: 10) {
if model.points.isEmpty {
Text("No cost history data.")
Text(L10n.tr("No cost history data."))
.font(.footnote)
.foregroundStyle(.secondary)
} else {
Expand Down Expand Up @@ -107,7 +107,7 @@ struct CostHistoryChartMenuView: View {
}

if let total = self.totalCostUSD {
Text("Total (30d): \(UsageFormatter.usdString(total))")
Text(L10n.format("Total (30d): %@", UsageFormatter.usdString(total)))
.font(.caption)
.foregroundStyle(.secondary)
}
Expand Down Expand Up @@ -291,17 +291,17 @@ struct CostHistoryChartMenuView: View {
let point = model.pointsByDateKey[key],
let date = Self.dateFromDayKey(key)
else {
return ("Hover a bar for details", nil)
return (L10n.tr("Hover a bar for details"), nil)
}

let dayLabel = date.formatted(.dateTime.month(.abbreviated).day())
let cost = UsageFormatter.usdString(point.costUSD)
if let tokens = point.totalTokens {
let primary = "\(dayLabel): \(cost) · \(UsageFormatter.tokenCountString(tokens)) tokens"
let primary = L10n.format("%@: %@ · %@ tokens", dayLabel, cost, UsageFormatter.tokenCountString(tokens))
let secondary = self.topModelsText(key: key, model: model)
return (primary, secondary)
}
let primary = "\(dayLabel): \(cost)"
let primary = L10n.format("%@: %@", dayLabel, cost)
let secondary = self.topModelsText(key: key, model: model)
return (primary, secondary)
}
Expand All @@ -321,6 +321,6 @@ struct CostHistoryChartMenuView: View {
.prefix(3)
.map { "\($0.name) \(UsageFormatter.usdString($0.costUSD))" }
guard !parts.isEmpty else { return nil }
return "Top: \(parts.joined(separator: " · "))"
return L10n.format("Top: %@", parts.joined(separator: " · "))
}
}
2 changes: 1 addition & 1 deletion Sources/CodexBar/Date+RelativeDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension Date {
func relativeDescription(now: Date = .now) -> String {
let seconds = abs(now.timeIntervalSince(self))
if seconds < 15 {
return "just now"
return L10n.tr("just now")
}
return RelativeTimeFormatters.full.localizedString(for: self, relativeTo: now)
}
Expand Down
12 changes: 12 additions & 0 deletions Sources/CodexBar/Localization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation

enum L10n {
static func tr(_ key: String) -> String {
NSLocalizedString(key, bundle: .module, comment: "")
}

static func format(_ key: String, _ arguments: CVarArg...) -> String {
let format = Self.tr(key)
return String(format: format, locale: .current, arguments: arguments)
}
}
12 changes: 6 additions & 6 deletions Sources/CodexBar/MenuBarDisplayMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ enum MenuBarDisplayMode: String, CaseIterable, Identifiable {

var label: String {
switch self {
case .percent: "Percent"
case .pace: "Pace"
case .both: "Both"
case .percent: L10n.tr("Percent")
case .pace: L10n.tr("Pace")
case .both: L10n.tr("Both")
}
}

var description: String {
switch self {
case .percent: "Show remaining/used percentage (e.g. 45%)"
case .pace: "Show pace indicator (e.g. +5%)"
case .both: "Show both percentage and pace (e.g. 45% · +5%)"
case .percent: L10n.tr("Show remaining/used percentage (e.g. 45%)")
case .pace: L10n.tr("Show pace indicator (e.g. +5%)")
case .both: L10n.tr("Show both percentage and pace (e.g. 45% · +5%)")
}
}
}
74 changes: 41 additions & 33 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ struct UsageMenuCardView: View {

var labelSuffix: String {
switch self {
case .left: "left"
case .used: "used"
case .left: L10n.tr("left")
case .used: L10n.tr("used")
}
}

var accessibilityLabel: String {
switch self {
case .left: "Usage remaining"
case .used: "Usage used"
case .left: L10n.tr("Usage remaining")
case .used: L10n.tr("Usage used")
}
}
}
Expand All @@ -37,7 +37,13 @@ struct UsageMenuCardView: View {
let paceOnTop: Bool

var percentLabel: String {
String(format: "%.0f%% %@", self.percent, self.percentStyle.labelSuffix)
let percentText = String(format: "%.0f%%", self.percent)
switch self.percentStyle {
case .left:
return L10n.format("%@ left", percentText)
case .used:
return L10n.format("%@ used", percentText)
}
}
}

Expand Down Expand Up @@ -135,7 +141,7 @@ struct UsageMenuCardView: View {
}
if let tokenUsage = self.model.tokenUsage {
VStack(alignment: .leading, spacing: 6) {
Text("Cost")
Text(L10n.tr("Cost"))
.font(.body)
.fontWeight(.medium)
Text(tokenUsage.sessionLine)
Expand Down Expand Up @@ -267,7 +273,7 @@ private struct CopyIconButton: View {
.frame(width: 18, height: 18)
}
.buttonStyle(CopyIconButtonStyle(isHighlighted: self.isHighlighted))
.accessibilityLabel(self.didCopy ? "Copied" : "Copy error")
.accessibilityLabel(self.didCopy ? L10n.tr("Copied") : L10n.tr("Copy error"))
}

private func copyToPasteboard() {
Expand All @@ -290,12 +296,12 @@ private struct ProviderCostContent: View {
UsageProgressBar(
percent: self.section.percentUsed,
tint: self.progressColor,
accessibilityLabel: "Extra usage spent")
accessibilityLabel: L10n.tr("Extra usage spent"))
HStack(alignment: .firstTextBaseline) {
Text(self.section.spendLine)
.font(.footnote)
Spacer()
Text(String(format: "%.0f%% used", min(100, max(0, self.section.percentUsed))))
Text(L10n.format("%d%% used", Int(min(100, max(0, self.section.percentUsed)).rounded())))
.font(.footnote)
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
}
Expand Down Expand Up @@ -460,19 +466,19 @@ private struct CreditsBarContent: View {

private var scaleText: String {
let scale = UsageFormatter.tokenCountString(Int(Self.fullScaleTokens))
return "\(scale) tokens"
return L10n.format("%@ tokens", scale)
}

var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text("Credits")
Text(L10n.tr("Credits"))
.font(.body)
.fontWeight(.medium)
if let percentLeft {
UsageProgressBar(
percent: percentLeft,
tint: self.progressColor,
accessibilityLabel: "Credits remaining")
accessibilityLabel: L10n.tr("Credits remaining"))
HStack(alignment: .firstTextBaseline) {
Text(self.creditsText)
.font(.caption)
Expand Down Expand Up @@ -513,7 +519,7 @@ struct UsageMenuCardCostSectionView: View {
VStack(alignment: .leading, spacing: 10) {
if let tokenUsage = self.model.tokenUsage {
VStack(alignment: .leading, spacing: 6) {
Text("Cost")
Text(L10n.tr("Cost"))
.font(.body)
.fontWeight(.medium)
Text(tokenUsage.sessionLine)
Expand Down Expand Up @@ -621,7 +627,9 @@ extension UsageMenuCardView.Model {
isRefreshing: input.isRefreshing,
lastError: input.lastError)
let redacted = Self.redactedText(input: input, subtitle: subtitle)
let placeholder = input.snapshot == nil && !input.isRefreshing && input.lastError == nil ? "No usage yet" : nil
let placeholder = input.snapshot == nil && !input.isRefreshing && input.lastError == nil
? L10n.tr("No usage yet")
: nil

return UsageMenuCardView.Model(
providerName: input.metadata.displayName,
Expand Down Expand Up @@ -687,14 +695,14 @@ extension UsageMenuCardView.Model {
}

if isRefreshing, snapshot == nil {
return ("Refreshing...", .loading)
return (L10n.tr("Refreshing..."), .loading)
}

if let updated = snapshot?.updatedAt {
return (UsageFormatter.updatedString(from: updated), .info)
}

return ("Not fetched yet", .info)
return (L10n.tr("Not fetched yet"), .info)
}

private struct RedactedText {
Expand Down Expand Up @@ -756,7 +764,7 @@ extension UsageMenuCardView.Model {
}
metrics.append(Metric(
id: "primary",
title: input.metadata.sessionLabel,
title: L10n.tr(input.metadata.sessionLabel),
percent: Self.clamped(
input.usageBarsShowUsed ? primary.usedPercent : primary.remainingPercent),
percentStyle: percentStyle,
Expand Down Expand Up @@ -784,7 +792,7 @@ extension UsageMenuCardView.Model {
}
metrics.append(Metric(
id: "secondary",
title: input.metadata.weeklyLabel,
title: L10n.tr(input.metadata.weeklyLabel),
percent: Self.clamped(input.usageBarsShowUsed ? weekly.usedPercent : weekly.remainingPercent),
percentStyle: percentStyle,
resetText: weeklyResetText,
Expand All @@ -797,7 +805,7 @@ extension UsageMenuCardView.Model {
if input.metadata.supportsOpus, let opus = snapshot.tertiary {
metrics.append(Metric(
id: "tertiary",
title: input.metadata.opusLabel ?? "Sonnet",
title: L10n.tr(input.metadata.opusLabel ?? "Sonnet"),
percent: Self.clamped(input.usageBarsShowUsed ? opus.usedPercent : opus.remainingPercent),
percentStyle: percentStyle,
resetText: Self.resetText(for: opus, style: input.resetTimeDisplayStyle, now: input.now),
Expand All @@ -812,7 +820,7 @@ extension UsageMenuCardView.Model {
let percent = input.usageBarsShowUsed ? (100 - remaining) : remaining
metrics.append(Metric(
id: "code-review",
title: "Code review",
title: L10n.tr("Code review"),
percent: Self.clamped(percent),
percentStyle: percentStyle,
resetText: nil,
Expand All @@ -835,7 +843,7 @@ extension UsageMenuCardView.Model {
let currentStr = UsageFormatter.tokenCountString(currentValue)
let usageStr = UsageFormatter.tokenCountString(usage)
let remainingStr = UsageFormatter.tokenCountString(remaining)
return "\(currentStr) / \(usageStr) (\(remainingStr) remaining)"
return L10n.format("%@ / %@ (%@ remaining)", currentStr, usageStr, remainingStr)
}

return nil
Expand Down Expand Up @@ -881,13 +889,13 @@ extension UsageMenuCardView.Model {
if let error, !error.isEmpty {
return error.trimmingCharacters(in: .whitespacesAndNewlines)
}
return metadata.creditsHint
return L10n.tr(metadata.creditsHint)
}

private static func dashboardHint(provider: UsageProvider, error: String?) -> String? {
guard provider == .codex else { return nil }
guard let error, !error.isEmpty else { return nil }
return error
return L10n.tr(error)
}

private static func tokenUsageSection(
Expand All @@ -900,24 +908,24 @@ extension UsageMenuCardView.Model {
guard enabled else { return nil }
guard let snapshot else { return nil }

let sessionCost = snapshot.sessionCostUSD.map { UsageFormatter.usdString($0) } ?? "—"
let sessionCost = snapshot.sessionCostUSD.map { UsageFormatter.usdString($0) } ?? L10n.tr("—")
let sessionTokens = snapshot.sessionTokens.map { UsageFormatter.tokenCountString($0) }
let sessionLine: String = {
if let sessionTokens {
return "Today: \(sessionCost) · \(sessionTokens) tokens"
return L10n.format("Today: %@ · %@ tokens", sessionCost, sessionTokens)
}
return "Today: \(sessionCost)"
return L10n.format("Today: %@", sessionCost)
}()

let monthCost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? "—"
let monthCost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? L10n.tr("—")
let fallbackTokens = snapshot.daily.compactMap(\.totalTokens).reduce(0, +)
let monthTokensValue = snapshot.last30DaysTokens ?? (fallbackTokens > 0 ? fallbackTokens : nil)
let monthTokens = monthTokensValue.map { UsageFormatter.tokenCountString($0) }
let monthLine: String = {
if let monthTokens {
return "Last 30 days: \(monthCost) · \(monthTokens) tokens"
return L10n.format("Last 30 days: %@ · %@ tokens", monthCost, monthTokens)
}
return "Last 30 days: \(monthCost)"
return L10n.format("Last 30 days: %@", monthCost)
}()
let err = (error?.isEmpty ?? true) ? nil : error
return TokenUsageSection(
Expand All @@ -940,22 +948,22 @@ extension UsageMenuCardView.Model {
let title: String

if cost.currencyCode == "Quota" {
title = "Quota usage"
title = L10n.tr("Quota usage")
used = String(format: "%.0f", cost.used)
limit = String(format: "%.0f", cost.limit)
} else {
title = "Extra usage"
title = L10n.tr("Extra usage")
used = UsageFormatter.currencyString(cost.used, currencyCode: cost.currencyCode)
limit = UsageFormatter.currencyString(cost.limit, currencyCode: cost.currencyCode)
}

let percentUsed = Self.clamped((cost.used / cost.limit) * 100)
let periodLabel = cost.period ?? "This month"
let periodLabel = cost.period.map(L10n.tr) ?? L10n.tr("This month")

return ProviderCostSection(
title: title,
percentUsed: percentUsed,
spendLine: "\(periodLabel): \(used) / \(limit)")
spendLine: L10n.format("%@: %@ / %@", periodLabel, used, limit))
}

private static func clamped(_ value: Double) -> Double {
Expand Down
10 changes: 5 additions & 5 deletions Sources/CodexBar/MenuContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ struct MenuContent: View {
case let .text(text, style):
switch style {
case .headline:
Text(text).font(.headline)
Text(L10n.tr(text)).font(.headline)
case .primary:
Text(text)
Text(L10n.tr(text))
case .secondary:
Text(text).foregroundStyle(.secondary).font(.footnote)
Text(L10n.tr(text)).foregroundStyle(.secondary).font(.footnote)
}
case let .action(title, action):
Button {
Expand All @@ -57,11 +57,11 @@ struct MenuContent: View {
Image(systemName: icon)
.imageScale(.medium)
.frame(width: 18, alignment: .center)
Text(title)
Text(L10n.tr(title))
}
.foregroundStyle(.primary)
} else {
Text(title)
Text(L10n.tr(title))
}
}
.buttonStyle(.plain)
Expand Down
Loading