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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Localizations: add Spanish and Catalan language packs and fill missing localization keys (#1041). Thanks @seifreed!

### Fixed
- Localization: translate provider-detail labels and empty states when Simplified Chinese is selected (#1051). Thanks @wang93wei!
- Antigravity: discover OAuth credentials from the bundled extension language server in newer IDE builds so Add Account works again (#1076). Thanks @xARSENICx!
- Menu bar: wait for display changes to settle before recovering status items and retry if macOS still leaves the icon detached (#1074). Thanks @yipjunkai!
- Menu: keep lower action rows stable when Refresh is highlighted or pressed (#1071). Thanks @MadanChaollaPark!
Expand Down
58 changes: 29 additions & 29 deletions Sources/CodexBar/PreferencesProviderDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
}

static func metricTitle(provider: UsageProvider, metric: UsageMenuCardView.Model.Metric) -> String {
UsageMenuCardView.popupMetricTitle(provider: provider, metric: metric)
L(UsageMenuCardView.popupMetricTitle(provider: provider, metric: metric))
}

static func planRow(provider: UsageProvider, planText: String?) -> (label: String, value: String)? {
Expand All @@ -70,18 +70,18 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
return nil
}
guard provider == .openrouter || provider == .mimo || provider == .moonshot else {
return (label: "Plan", value: rawPlan)
return (label: L("Plan"), value: rawPlan)
}

let prefix = "Balance:"
if rawPlan.hasPrefix(prefix) {
let valueStart = rawPlan.index(rawPlan.startIndex, offsetBy: prefix.count)
let trimmedValue = rawPlan[valueStart...].trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedValue.isEmpty {
return (label: "Balance", value: trimmedValue)
return (label: L("Balance"), value: trimmedValue)
}
}
return (label: "Balance", value: rawPlan)
return (label: L("Balance"), value: rawPlan)
}

var body: some View {
Expand Down Expand Up @@ -114,7 +114,7 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
}

if self.hasSettings {
ProviderSettingsSection(title: "Settings") {
ProviderSettingsSection(title: L("Settings")) {
ForEach(self.settingsPickers) { picker in
ProviderSettingsPickerRowView(picker: picker)
}
Expand Down Expand Up @@ -142,7 +142,7 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
ProviderQuotaWarningSettingsView(provider: self.provider, settings: self.store.settings)

if !self.settingsToggles.isEmpty {
ProviderSettingsSection(title: "Options") {
ProviderSettingsSection(title: L("Options")) {
ForEach(self.settingsToggles) { toggle in
ProviderSettingsToggleRowView(toggle: toggle)
}
Expand All @@ -165,17 +165,17 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
}

private var detailLabelWidth: CGFloat {
var infoLabels = ["State", "Source", "Version", "Updated"]
var infoLabels = [L("State"), L("Source"), L("Version"), L("Updated")]
if self.store.status(for: self.provider) != nil {
infoLabels.append("Status")
infoLabels.append(L("Status"))
}
if !self.model.email.isEmpty {
infoLabels.append("Account")
infoLabels.append(L("Account"))
}
if self.provider == .kiro,
self.model.metrics.isEmpty == false
{
infoLabels.append("Auth")
infoLabels.append(L("Auth"))
}
if let planRow = Self.planRow(provider: self.provider, planText: self.model.planText) {
infoLabels.append(planRow.label)
Expand All @@ -185,13 +185,13 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
Self.metricTitle(provider: self.provider, metric: metric)
}
if self.model.creditsText != nil {
metricLabels.append("Credits")
metricLabels.append(L("Credits"))
}
if let providerCost = self.model.providerCost {
metricLabels.append(providerCost.title)
}
if self.model.tokenUsage != nil {
metricLabels.append("Cost")
metricLabels.append(L("Cost"))
}

let infoWidth = ProviderSettingsMetrics.labelWidth(
Expand Down Expand Up @@ -297,33 +297,33 @@ private struct ProviderDetailInfoGrid: View {
var body: some View {
let status = self.store.status(for: self.provider)
let source = self.store.sourceLabel(for: self.provider)
let version = self.store.version(for: self.provider) ?? "not detected"
let version = self.store.version(for: self.provider) ?? L("not detected")
let updated = self.updatedText
let email = self.model.email
let enabledText = self.isEnabled ? "Enabled" : "Disabled"
let enabledText = self.isEnabled ? L("Enabled") : L("Disabled")

Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 6) {
ProviderDetailInfoRow(label: "State", value: enabledText, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: "Source", value: source, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: "Version", value: version, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: "Updated", value: updated, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: L("State"), value: enabledText, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: L("Source"), value: source, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: L("Version"), value: version, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: L("Updated"), value: updated, labelWidth: self.labelWidth)

if let status {
ProviderDetailInfoRow(
label: "Status",
label: L("Status"),
value: status.description ?? status.indicator.label,
labelWidth: self.labelWidth)
}

if !email.isEmpty {
ProviderDetailInfoRow(label: "Account", value: email, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: L("Account"), value: email, labelWidth: self.labelWidth)
}

if self.provider == .kiro,
let authMethod = self.store.snapshot(for: self.provider)?.loginMethod(for: .kiro),
!authMethod.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
ProviderDetailInfoRow(label: "Auth", value: authMethod, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: L("Auth"), value: authMethod, labelWidth: self.labelWidth)
}

if let planRow = ProviderDetailView<EmptyView>.planRow(
Expand All @@ -342,12 +342,12 @@ private struct ProviderDetailInfoGrid: View {
return UsageFormatter.updatedString(from: updated)
}
if self.store.refreshingProviders.contains(self.provider) {
return "Refreshing"
return L("Refreshing")
}
if self.store.unavailableMessage(for: self.provider) != nil {
return "Unavailable"
return L("Unavailable")
}
return "Not fetched yet"
return L("Not fetched yet")
}
}

Expand Down Expand Up @@ -380,7 +380,7 @@ struct ProviderMetricsInlineView: View {
let hasProviderCost = self.model.providerCost != nil
let hasTokenUsage = self.model.tokenUsage != nil
ProviderSettingsSection(
title: "Usage",
title: L("Usage"),
spacing: 8,
verticalPadding: 6,
horizontalPadding: 0)
Expand All @@ -407,7 +407,7 @@ struct ProviderMetricsInlineView: View {

if let credits = self.model.creditsText {
ProviderMetricInlineTextRow(
title: "Credits",
title: L("Credits"),
value: credits,
labelWidth: self.labelWidth)
}
Expand All @@ -421,7 +421,7 @@ struct ProviderMetricsInlineView: View {

if let tokenUsage = self.model.tokenUsage {
ProviderMetricInlineTextRow(
title: "Cost",
title: L("Cost"),
value: tokenUsage.sessionLine,
labelWidth: self.labelWidth)
ProviderMetricInlineTextRow(
Expand All @@ -435,9 +435,9 @@ struct ProviderMetricsInlineView: View {

private var placeholderText: String {
if !self.isEnabled {
return "Disabled — no recent data"
return L("Disabled — no recent data")
}
return self.model.placeholder ?? "No usage yet"
return self.model.placeholder.map(L) ?? L("No usage yet")
}
}

Expand Down
48 changes: 31 additions & 17 deletions Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
"30d" = "";
"A managed Codex login is already running. Wait for it to finish before adding " = "";
"API key" = "";
"API key limit" = "API 密钥限制";
"API region" = "";
"API token" = "";
"API tokens" = "";
"About" = "";
"Account" = "";
"Account" = "账户";
"Accounts" = "";
"Accounts subtitle" = "";
"Active" = "";
Expand All @@ -21,12 +22,14 @@
"Animation pattern" = "";
"Antigravity login is managed in the app" = "";
"Applies only to the Security.framework OAuth keychain reader." = "";
"Auto falls back to the next source if the preferred one fails." = "";
"Auth" = "认证";
"Auto" = "自动";
"Auto falls back to the next source if the preferred one fails." = "自动模式会在首选来源失败时回退到下一个来源。";
"Auto uses API first, then falls back to CLI on auth failures." = "";
"Auto-detect" = "";
"Auto-refresh is off; use the menu's Refresh command." = "";
"Auto-refresh: hourly · Timeout: 10m" = "";
"Automatic" = "";
"Automatic" = "自动";
"Automatic imports browser cookies and WorkOS tokens." = "";
"Automatic imports browser cookies and local storage tokens." = "";
"Automatic imports browser cookies for dashboard extras." = "";
Expand All @@ -42,7 +45,7 @@
"Average (\\(label1) + \\(label2))" = "";
"Average (\\(metadata.sessionLabel) + \\(metadata.weeklyLabel))" = "";
"Avoid Keychain prompts" = "";
"Balance" = "";
"Balance" = "余额";
"Battery Saver" = "";
"Bordered" = "";
"Build" = "";
Expand Down Expand Up @@ -91,13 +94,13 @@
"Cookie: \\u{2026}\\\n\\\nor paste the kimi-auth token value" = "";
"Cookie: …" = "";
"CopilotDeviceFlow" = "";
"Cost" = "";
"Cost" = "费用";
"Could not add Codex account" = "";
"Could not open Terminal for Gemini" = "";
"Could not start claude /login" = "";
"Could not start codex login" = "";
"Could not switch system account" = "";
"Credits" = "";
"Credits" = "积分";
"Credits history" = "";
"Cursor login failed" = "";
"Custom" = "";
Expand All @@ -107,7 +110,8 @@
"Default" = "";
"Designs" = "";
"Disable Keychain access" = "";
"Disabled" = "";
"Disabled" = "已禁用";
"Disabled — no recent data" = "已禁用 — 无近期数据";
"Disconnected" = "";
"Display" = "";
"Display mode" = "";
Expand All @@ -117,7 +121,7 @@
"Email" = "";
"Enable Merge Icons to configure Overview tab providers." = "";
"Enable file logging" = "";
"Enabled" = "";
"Enabled" = "已启用";
"Error" = "";
"Error simulation" = "";
"Expose troubleshooting tools in the Debug tab." = "";
Expand Down Expand Up @@ -154,6 +158,7 @@
"Last \\(name) fetch failed:" = "";
"Last \\(self.store.metadata(for: self.provider).displayName) fetch failed:" = "";
"Last attempt" = "";
"Limits not available" = "限制不可用";
"Link" = "";
"Loading animations" = "";
"Loading…" = "";
Expand All @@ -169,7 +174,7 @@
"May your tokens never run out—keep agent limits in view." = "";
"Menu bar" = "";
"Menu bar auto-shows the provider closest to its rate limit." = "";
"Menu bar metric" = "";
"Menu bar metric" = "菜单栏指标";
"Menu bar shows percent" = "";
"Menu content" = "";
"Merge Icons" = "";
Expand All @@ -178,6 +183,8 @@
"No Codex accounts detected yet." = "";
"No JetBrains IDE detected" = "";
"No cost history data." = "";
"No usage yet" = "尚无用量";
"Not fetched yet" = "尚未获取";
"No credits history data." = "";
"No data available" = "";
"No data yet" = "";
Expand Down Expand Up @@ -215,7 +222,7 @@
"Option A" = "";
"Option B" = "";
"Optional override if workspace lookup fails." = "";
"Options" = "";
"Options" = "选项";
"Override auto-detection with a custom IDE base path" = "";
"Overview" = "";
"Overview rows always follow provider order." = "";
Expand All @@ -230,7 +237,7 @@
"Picker" = "";
"Picker subtitle" = "";
"Placeholder" = "";
"Plan" = "";
"Plan" = "套餐";
"Play full-screen confetti when weekly usage resets." = "";
"Polls OpenAI/Claude status pages and Google Workspace for " = "";
"Prevents any Keychain access while enabled." = "";
Expand All @@ -245,6 +252,7 @@
"Random (default)" = "";
"Reads local usage logs. Shows today + last 30 days cost in the menu." = "";
"Refresh" = "";
"Refreshing" = "正在刷新";
"Refresh cadence" = "";
"Remote" = "";
"Remove" = "";
Expand All @@ -262,9 +270,10 @@
"Secondary (\\(metadata.weeklyLabel))" = "";
"Select a provider" = "";
"Select the IDE to monitor" = "";
"Session" = "会话";
"Session quota notifications" = "";
"Session tokens" = "";
"Settings" = "";
"Settings" = "设置";
"Show Codex Credits and Claude Extra usage sections in the menu." = "";
"Show Debug Settings" = "";
"Show all token accounts" = "";
Expand All @@ -277,9 +286,11 @@
"Show usage as used" = "";
"Sign in via button below" = "";
"Skip teardown between probes (debug-only)." = "";
"Source" = "来源";
"Stack token accounts in the menu (otherwise show an account switcher bar)." = "";
"Start at Login" = "";
"Status" = "";
"State" = "状态";
"Status" = "状态";
"Store Claude sessionKey cookies or OAuth access tokens." = "";
"Store multiple Abacus AI Cookie headers." = "";
"Store multiple Augment Cookie headers." = "";
Expand Down Expand Up @@ -314,31 +325,34 @@
"True" = "";
"Twitter" = "";
"Unsupported" = "";
"Unavailable" = "不可用";
"Update Channel" = "";
"Updated" = "";
"Updated" = "已更新";
"Updates unavailable in this build." = "";
"Usage" = "";
"Usage" = "用量";
"Usage breakdown" = "";
"Usage history (30 days)" = "";
"Usage source" = "";
"Usage source" = "用量来源";
"Use BigModel for the China mainland endpoints (open.bigmodel.cn)." = "";
"Use a single menu bar icon with a provider switcher." = "";
"Use international or China mainland console gateways for quota fetches." = "";
"Version" = "";
"Version" = "版本";
"Version \\(self.versionString)" = "";
"Version \\(version)" = "";
"Version \\(versionString)" = "";
"Vertex AI Login" = "Vertex AI 登录";
"Wait for the current managed Codex login to finish before adding another account." = "";
"Waiting for Authentication..." = "等待认证…";
"Website" = "";
"Weekly" = "每周";
"Weekly limit confetti" = "";
"Weekly token limit" = "";
"Weekly usage" = "";
"Weekly usage unavailable for this account." = "此账户的每周用量不可用。";
"Window: \\(window)" = "";
"Write logs to \\(self.fileLogPath) for debugging." = "将日志写入 \\(self.fileLogPath) 用于调试。";
"Yes" = "";
"not detected" = "未检测到";
"\\(detail.modelCode): \\(usage)" = "";
"\\(name): \\(truncated)" = "";
"\\(name): \\(updated) · 30d \\(cost)" = "";
Expand Down