diff --git a/CHANGELOG.md b/CHANGELOG.md index bb7afde072..6bf3cac7bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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! diff --git a/Sources/CodexBar/PreferencesProviderDetailView.swift b/Sources/CodexBar/PreferencesProviderDetailView.swift index e0e184158e..d6a850c9cf 100644 --- a/Sources/CodexBar/PreferencesProviderDetailView.swift +++ b/Sources/CodexBar/PreferencesProviderDetailView.swift @@ -60,7 +60,7 @@ struct ProviderDetailView: 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)? { @@ -70,7 +70,7 @@ struct ProviderDetailView: 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:" @@ -78,10 +78,10 @@ struct ProviderDetailView: View { 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 { @@ -114,7 +114,7 @@ struct ProviderDetailView: View { } if self.hasSettings { - ProviderSettingsSection(title: "Settings") { + ProviderSettingsSection(title: L("Settings")) { ForEach(self.settingsPickers) { picker in ProviderSettingsPickerRowView(picker: picker) } @@ -142,7 +142,7 @@ struct ProviderDetailView: 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) } @@ -165,17 +165,17 @@ struct ProviderDetailView: 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) @@ -185,13 +185,13 @@ struct ProviderDetailView: 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( @@ -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.planRow( @@ -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") } } @@ -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) @@ -407,7 +407,7 @@ struct ProviderMetricsInlineView: View { if let credits = self.model.creditsText { ProviderMetricInlineTextRow( - title: "Credits", + title: L("Credits"), value: credits, labelWidth: self.labelWidth) } @@ -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( @@ -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") } } diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index f489d80f8b..a2373e3d4b 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -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" = ""; @@ -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." = ""; @@ -42,7 +45,7 @@ "Average (\\(label1) + \\(label2))" = ""; "Average (\\(metadata.sessionLabel) + \\(metadata.weeklyLabel))" = ""; "Avoid Keychain prompts" = ""; -"Balance" = ""; +"Balance" = "余额"; "Battery Saver" = ""; "Bordered" = ""; "Build" = ""; @@ -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" = ""; @@ -107,7 +110,8 @@ "Default" = ""; "Designs" = ""; "Disable Keychain access" = ""; -"Disabled" = ""; +"Disabled" = "已禁用"; +"Disabled — no recent data" = "已禁用 — 无近期数据"; "Disconnected" = ""; "Display" = ""; "Display mode" = ""; @@ -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." = ""; @@ -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…" = ""; @@ -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" = ""; @@ -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" = ""; @@ -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." = ""; @@ -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." = ""; @@ -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" = ""; @@ -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" = ""; @@ -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." = ""; @@ -314,17 +325,18 @@ "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)" = ""; @@ -332,6 +344,7 @@ "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" = ""; @@ -339,6 +352,7 @@ "Window: \\(window)" = ""; "Write logs to \\(self.fileLogPath) for debugging." = "将日志写入 \\(self.fileLogPath) 用于调试。"; "Yes" = ""; +"not detected" = "未检测到"; "\\(detail.modelCode): \\(usage)" = ""; "\\(name): \\(truncated)" = ""; "\\(name): \\(updated) · 30d \\(cost)" = "";