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: 4 additions & 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 Expand Up @@ -98,6 +99,9 @@ let package = Package(
name: "CodexBarWidget",
dependencies: ["CodexBarCore"],
path: "Sources/CodexBarWidget",
resources: [
.process("Resources"),
],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
Expand Down
18 changes: 18 additions & 0 deletions Sources/CodexBar/AppStrings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import CodexBarCore
import Foundation
import SwiftUI

@MainActor
enum AppStrings {
static func string(_ entry: LocalizationEntry) -> String {
NSLocalizedString(entry.key, tableName: nil, bundle: .module, value: entry.fallback, comment: "")
}

static func text(_ entry: LocalizationEntry) -> Text {
Text(self.string(entry))
}

static func formatted(_ entry: LocalizationEntry, _ arguments: CVarArg...) -> String {
String(format: self.string(entry), locale: Locale.current, arguments: arguments)
}
}
56 changes: 28 additions & 28 deletions Sources/CodexBar/PreferencesDisplayPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,40 @@ struct DisplayPane: View {
ScrollView(.vertical, showsIndicators: true) {
VStack(alignment: .leading, spacing: 16) {
SettingsSection(contentSpacing: 12) {
Text("Menu bar")
AppStrings.text(LocalizationCatalog.Preferences.Display.menuBarSection)
.font(.caption)
.foregroundStyle(.secondary)
.textCase(.uppercase)
PreferenceToggleRow(
title: "Merge Icons",
subtitle: "Use a single menu bar icon with a provider switcher.",
title: AppStrings.string(LocalizationCatalog.Preferences.Display.mergeIconsTitle),
subtitle: AppStrings.string(LocalizationCatalog.Preferences.Display.mergeIconsSubtitle),
binding: self.$settings.mergeIcons)
PreferenceToggleRow(
title: "Switcher shows icons",
subtitle: "Show provider icons in the switcher (otherwise show a weekly progress line).",
title: AppStrings.string(LocalizationCatalog.Preferences.Display.switcherShowsIconsTitle),
subtitle: AppStrings.string(LocalizationCatalog.Preferences.Display.switcherShowsIconsSubtitle),
binding: self.$settings.switcherShowsIcons)
.disabled(!self.settings.mergeIcons)
.opacity(self.settings.mergeIcons ? 1 : 0.5)
PreferenceToggleRow(
title: "Show most-used provider",
subtitle: "Menu bar auto-shows the provider closest to its rate limit.",
title: AppStrings.string(LocalizationCatalog.Preferences.Display.showMostUsedProviderTitle),
subtitle: AppStrings.string(LocalizationCatalog.Preferences.Display.showMostUsedProviderSubtitle),
binding: self.$settings.menuBarShowsHighestUsage)
.disabled(!self.settings.mergeIcons)
.opacity(self.settings.mergeIcons ? 1 : 0.5)
PreferenceToggleRow(
title: "Menu bar shows percent",
subtitle: "Replace critter bars with provider branding icons and a percentage.",
title: AppStrings.string(LocalizationCatalog.Preferences.Display.showPercentTitle),
subtitle: AppStrings.string(LocalizationCatalog.Preferences.Display.showPercentSubtitle),
binding: self.$settings.menuBarShowsBrandIconWithPercent)
HStack(alignment: .top, spacing: 12) {
VStack(alignment: .leading, spacing: 4) {
Text("Display mode")
AppStrings.text(LocalizationCatalog.Preferences.Display.modeTitle)
.font(.body)
Text("Choose what to show in the menu bar (Pace shows usage vs. expected).")
AppStrings.text(LocalizationCatalog.Preferences.Display.modeSubtitle)
.font(.footnote)
.foregroundStyle(.tertiary)
}
Spacer()
Picker("Display mode", selection: self.$settings.menuBarDisplayMode) {
Picker(AppStrings.string(LocalizationCatalog.Preferences.Display.modeTitle), selection: self.$settings.menuBarDisplayMode) {
ForEach(MenuBarDisplayMode.allCases) { mode in
Text(mode.label).tag(mode)
}
Expand All @@ -62,25 +62,25 @@ struct DisplayPane: View {
Divider()

SettingsSection(contentSpacing: 12) {
Text("Menu content")
AppStrings.text(LocalizationCatalog.Preferences.Display.menuContentSection)
.font(.caption)
.foregroundStyle(.secondary)
.textCase(.uppercase)
PreferenceToggleRow(
title: "Show usage as used",
subtitle: "Progress bars fill as you consume quota (instead of showing remaining).",
title: AppStrings.string(LocalizationCatalog.Preferences.Display.usageAsUsedTitle),
subtitle: AppStrings.string(LocalizationCatalog.Preferences.Display.usageAsUsedSubtitle),
binding: self.$settings.usageBarsShowUsed)
PreferenceToggleRow(
title: "Show reset time as clock",
subtitle: "Display reset times as absolute clock values instead of countdowns.",
title: AppStrings.string(LocalizationCatalog.Preferences.Display.resetTimeClockTitle),
subtitle: AppStrings.string(LocalizationCatalog.Preferences.Display.resetTimeClockSubtitle),
binding: self.$settings.resetTimesShowAbsolute)
PreferenceToggleRow(
title: "Show credits + extra usage",
subtitle: "Show Codex Credits and Claude Extra usage sections in the menu.",
title: AppStrings.string(LocalizationCatalog.Preferences.Display.showCreditsExtraTitle),
subtitle: AppStrings.string(LocalizationCatalog.Preferences.Display.showCreditsExtraSubtitle),
binding: self.$settings.showOptionalCreditsAndExtraUsage)
PreferenceToggleRow(
title: "Show all token accounts",
subtitle: "Stack token accounts in the menu (otherwise show an account switcher bar).",
title: AppStrings.string(LocalizationCatalog.Preferences.Display.showAllTokenAccountsTitle),
subtitle: AppStrings.string(LocalizationCatalog.Preferences.Display.showAllTokenAccountsSubtitle),
binding: self.$settings.showAllTokenAccountsInMenu)
self.overviewProviderSelector
}
Expand Down Expand Up @@ -110,11 +110,11 @@ struct DisplayPane: View {
private var overviewProviderSelector: some View {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .center, spacing: 12) {
Text("Overview tab providers")
AppStrings.text(LocalizationCatalog.Preferences.Display.overviewTitle)
.font(.body)
Spacer(minLength: 0)
if self.showsOverviewConfigureButton {
Button("Configure…") {
Button(AppStrings.string(LocalizationCatalog.Preferences.Display.overviewConfigure)) {
self.isOverviewProviderPopoverPresented = true
}
.offset(y: 1)
Expand All @@ -125,11 +125,11 @@ struct DisplayPane: View {
}

if !self.settings.mergeIcons {
Text("Enable Merge Icons to configure Overview tab providers.")
AppStrings.text(LocalizationCatalog.Preferences.Display.overviewEnableMergeIcons)
.font(.footnote)
.foregroundStyle(.tertiary)
} else if self.activeProvidersInOrder.isEmpty {
Text("No enabled providers available for Overview.")
AppStrings.text(LocalizationCatalog.Preferences.Display.overviewNoEnabledProviders)
.font(.footnote)
.foregroundStyle(.tertiary)
} else {
Expand All @@ -144,9 +144,9 @@ struct DisplayPane: View {

private var overviewProviderPopover: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Choose up to \(Self.maxOverviewProviders) providers")
Text(AppStrings.formatted(LocalizationCatalog.Preferences.Display.overviewChooseLimitFormat, Self.maxOverviewProviders))
.font(.headline)
Text("Overview rows always follow provider order.")
AppStrings.text(LocalizationCatalog.Preferences.Display.overviewRowsFollowOrder)
.font(.footnote)
.foregroundStyle(.tertiary)

Expand Down Expand Up @@ -191,7 +191,7 @@ struct DisplayPane: View {

private var overviewProviderSelectionSummary: String {
let selectedNames = self.overviewSelectedProviders.map(self.providerDisplayName)
guard !selectedNames.isEmpty else { return "No providers selected" }
guard !selectedNames.isEmpty else { return AppStrings.string(LocalizationCatalog.Preferences.Display.overviewNoneSelected) }
return selectedNames.joined(separator: ", ")
}

Expand Down
48 changes: 48 additions & 0 deletions Sources/CodexBar/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"menu.settings" = "Settings…";
"preferences.general.title" = "General";
"preferences.display.menu_bar.section" = "Menu bar";
"preferences.display.merge_icons.title" = "Merge Icons";
"preferences.display.merge_icons.subtitle" = "Use a single menu bar icon with a provider switcher.";
"preferences.display.switcher_shows_icons.title" = "Switcher shows icons";
"preferences.display.switcher_shows_icons.subtitle" = "Show provider icons in the switcher (otherwise show a weekly progress line).";
"preferences.display.show_most_used_provider.title" = "Show most-used provider";
"preferences.display.show_most_used_provider.subtitle" = "Menu bar auto-shows the provider closest to its rate limit.";
"preferences.display.show_percent.title" = "Menu bar shows percent";
"preferences.display.show_percent.subtitle" = "Replace critter bars with provider branding icons and a percentage.";
"preferences.display.mode.title" = "Display mode";
"preferences.display.mode.subtitle" = "Choose what to show in the menu bar (Pace shows usage vs. expected).";
"preferences.display.menu_content.section" = "Menu content";
"preferences.display.usage_as_used.title" = "Show usage as used";
"preferences.display.usage_as_used.subtitle" = "Progress bars fill as you consume quota (instead of showing remaining).";
"preferences.display.reset_time_clock.title" = "Show reset time as clock";
"preferences.display.reset_time_clock.subtitle" = "Display reset times as absolute clock values instead of countdowns.";
"preferences.display.show_credits_extra.title" = "Show credits + extra usage";
"preferences.display.show_credits_extra.subtitle" = "Show Codex Credits and Claude Extra usage sections in the menu.";
"preferences.display.show_all_token_accounts.title" = "Show all token accounts";
"preferences.display.show_all_token_accounts.subtitle" = "Stack token accounts in the menu (otherwise show an account switcher bar).";
"preferences.display.overview.title" = "Overview tab providers";
"preferences.display.overview.configure" = "Configure…";
"preferences.display.overview.enable_merge_icons" = "Enable Merge Icons to configure Overview tab providers.";
"preferences.display.overview.no_enabled_providers" = "No enabled providers available for Overview.";
"preferences.display.overview.none_selected" = "No providers selected";
"preferences.display.overview.choose_limit" = "Choose up to %lld providers";
"preferences.display.overview.rows_follow_order" = "Overview rows always follow provider order.";
"alert.login.codex_missing_cli.title" = "Codex CLI not found";
"alert.login.codex_missing_cli.message" = "Install the Codex CLI (npm i -g @openai/codex) and try again.";
"alert.login.codex_launch_failed.title" = "Could not start codex login";
"alert.login.codex_timed_out.title" = "Codex login timed out";
"alert.login.codex_failed.title" = "Codex login failed";
"widget.empty.open_app" = "Open CodexBar";
"widget.empty.usage_refresh_hint" = "Usage data will appear once the app refreshes.";
"widget.empty.history_refresh_hint" = "Usage history will appear after a refresh.";
"widget.empty.switcher_refresh_hint" = "Usage data appears after a refresh.";
"widget.metric.credits_left" = "Credits left";
"widget.metric.today_cost" = "Today cost";
"widget.metric.last_30_days_cost" = "30d cost";
"widget.intent.provider.title" = "Provider";
"widget.intent.provider.description" = "Select the provider to display in the widget.";
"widget.intent.switch_provider.title" = "Switch Provider";
"widget.intent.switch_provider.description" = "Switch the provider shown in the widget.";
"widget.intent.provider_metric.title" = "Provider + Metric";
"widget.intent.provider_metric.description" = "Select the provider and metric to display.";
"widget.intent.metric.title" = "Metric";
48 changes: 48 additions & 0 deletions Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"menu.settings" = "设置…";
"preferences.general.title" = "通用";
"preferences.display.menu_bar.section" = "菜单栏";
"preferences.display.merge_icons.title" = "合并图标";
"preferences.display.merge_icons.subtitle" = "使用单个菜单栏图标,并提供服务切换器。";
"preferences.display.switcher_shows_icons.title" = "切换器显示图标";
"preferences.display.switcher_shows_icons.subtitle" = "在切换器中显示服务图标(否则显示每周进度线)。";
"preferences.display.show_most_used_provider.title" = "显示使用最多的服务";
"preferences.display.show_most_used_provider.subtitle" = "菜单栏会自动显示最接近速率限制的服务。";
"preferences.display.show_percent.title" = "菜单栏显示百分比";
"preferences.display.show_percent.subtitle" = "用服务品牌图标和百分比替换小动物进度条。";
"preferences.display.mode.title" = "显示模式";
"preferences.display.mode.subtitle" = "选择菜单栏显示的内容(Pace 表示使用量与预期的对比)。";
"preferences.display.menu_content.section" = "菜单内容";
"preferences.display.usage_as_used.title" = "按已用量显示使用情况";
"preferences.display.usage_as_used.subtitle" = "进度条会随着配额消耗而填充(而不是显示剩余量)。";
"preferences.display.reset_time_clock.title" = "将重置时间显示为时钟";
"preferences.display.reset_time_clock.subtitle" = "将重置时间显示为绝对时刻,而不是倒计时。";
"preferences.display.show_credits_extra.title" = "显示 Credits 与额外用量";
"preferences.display.show_credits_extra.subtitle" = "在菜单中显示 Codex Credits 和 Claude Extra usage 区块。";
"preferences.display.show_all_token_accounts.title" = "显示所有 Token 账户";
"preferences.display.show_all_token_accounts.subtitle" = "在菜单中堆叠所有 Token 账户(否则显示账户切换条)。";
"preferences.display.overview.title" = "概览标签页服务";
"preferences.display.overview.configure" = "配置…";
"preferences.display.overview.enable_merge_icons" = "启用 Merge Icons 后才能配置概览标签页服务。";
"preferences.display.overview.no_enabled_providers" = "没有可用于概览的已启用服务。";
"preferences.display.overview.none_selected" = "未选择任何服务";
"preferences.display.overview.choose_limit" = "最多选择 %lld 个服务";
"preferences.display.overview.rows_follow_order" = "概览行始终遵循服务顺序。";
"alert.login.codex_missing_cli.title" = "未找到 Codex CLI";
"alert.login.codex_missing_cli.message" = "请先安装 Codex CLI(npm i -g @openai/codex),然后重试。";
"alert.login.codex_launch_failed.title" = "无法启动 codex login";
"alert.login.codex_timed_out.title" = "Codex 登录超时";
"alert.login.codex_failed.title" = "Codex 登录失败";
"widget.empty.open_app" = "打开 CodexBar";
"widget.empty.usage_refresh_hint" = "应用刷新后,这里会显示使用数据。";
"widget.empty.history_refresh_hint" = "刷新后,这里会显示使用历史。";
"widget.empty.switcher_refresh_hint" = "刷新后会显示使用数据。";
"widget.metric.credits_left" = "剩余 Credits";
"widget.metric.today_cost" = "今日成本";
"widget.metric.last_30_days_cost" = "30 天成本";
"widget.intent.provider.title" = "服务";
"widget.intent.provider.description" = "选择要在小组件中显示的服务。";
"widget.intent.switch_provider.title" = "切换服务";
"widget.intent.switch_provider.description" = "切换小组件中显示的服务。";
"widget.intent.provider_metric.title" = "服务 + 指标";
"widget.intent.provider_metric.description" = "选择要显示的服务和指标。";
"widget.intent.metric.title" = "指标";
10 changes: 5 additions & 5 deletions Sources/CodexBar/StatusItemController+Actions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,18 +215,18 @@ extension StatusItemController {
return
case .missingBinary:
self.presentLoginAlert(
title: "Codex CLI not found",
message: "Install the Codex CLI (npm i -g @openai/codex) and try again.")
title: AppStrings.string(LocalizationCatalog.Alert.Login.codexMissingCLITitle),
message: AppStrings.string(LocalizationCatalog.Alert.Login.codexMissingCLIMessage))
case let .launchFailed(message):
self.presentLoginAlert(title: "Could not start codex login", message: message)
self.presentLoginAlert(title: AppStrings.string(LocalizationCatalog.Alert.Login.codexLaunchFailedTitle), message: message)
case .timedOut:
self.presentLoginAlert(
title: "Codex login timed out",
title: AppStrings.string(LocalizationCatalog.Alert.Login.codexTimedOutTitle),
message: self.trimmedLoginOutput(result.output))
case let .failed(status):
let statusLine = "codex login exited with status \(status)."
let message = self.trimmedLoginOutput(result.output.isEmpty ? statusLine : result.output)
self.presentLoginAlert(title: "Codex login failed", message: message)
self.presentLoginAlert(title: AppStrings.string(LocalizationCatalog.Alert.Login.codexFailedTitle), message: message)
}
}

Expand Down
Loading