From 655bd35691ee30705f1ca3ae5e7940e88c5ddbe1 Mon Sep 17 00:00:00 2001 From: Yunyue Li Date: Wed, 19 Aug 2026 17:58:56 +0800 Subject: [PATCH 1/2] Clarify Simplified Chinese five-hour quota label --- .../zh-Hans.lproj/Localizable.strings | 4 +- .../PopupLocalizationTests.swift | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index 59b95da728..79f7d34c2b 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -6,7 +6,7 @@ "Fast" = "高速"; "iCloud Sync" = "iCloud Sync"; "Projects" = "项目"; -"Session %@" = "会话 %@"; +"Session %@" = "5 小时 %@"; "Std" = "标准"; "Show all (%d)" = "显示全部(%d)"; "Show less" = "收起"; @@ -332,7 +332,7 @@ "Secondary (\\(metadata.weeklyLabel))" = "次要(\\(metadata.weeklyLabel))"; "Select a provider" = "选择一个提供商"; "Select the IDE to monitor" = "选择要监控的 IDE"; -"Session" = "会话"; +"Session" = "5 小时"; "Session quota notifications" = "会话配额通知"; "Session tokens" = "会话令牌"; "provider_section_connection" = "连接"; diff --git a/Tests/CodexBarTests/PopupLocalizationTests.swift b/Tests/CodexBarTests/PopupLocalizationTests.swift index 97b46c327d..058961530d 100644 --- a/Tests/CodexBarTests/PopupLocalizationTests.swift +++ b/Tests/CodexBarTests/PopupLocalizationTests.swift @@ -7,6 +7,43 @@ import Testing @MainActor @Suite(.serialized) struct PopupLocalizationTests { + @Test + func `simplified Chinese identifies Claude session quota as five hours`() throws { + try CodexBarLocalizationOverride.$appLanguage.withValue("zh-Hans") { + let now = Date(timeIntervalSince1970: 1_700_000_000) + let metadata = try #require(ProviderDefaults.metadata[.claude]) + let snapshot = UsageSnapshot( + primary: RateWindow( + usedPercent: 10, + windowMinutes: 300, + resetsAt: now.addingTimeInterval(3600), + resetDescription: nil), + secondary: nil, + updatedAt: now) + let model = UsageMenuCardView.Model.make(.init( + provider: .claude, + metadata: metadata, + snapshot: snapshot, + credits: nil, + creditsError: nil, + dashboard: nil, + dashboardError: nil, + tokenSnapshot: nil, + tokenError: nil, + account: AccountInfo(email: nil, plan: nil), + isRefreshing: false, + lastError: nil, + usageBarsShowUsed: false, + resetTimeDisplayStyle: .countdown, + tokenCostUsageEnabled: false, + showOptionalCreditsAndExtraUsage: true, + hidePersonalInfo: false, + now: now)) + + #expect(model.metrics.first?.title == "5 小时") + } + } + @Test func `descriptor account labels use selected localization`() throws { try CodexBarLocalizationOverride.$appLanguage.withValue("zh-Hant") { From fe1195dae528f3677eea66f6003303364568b627 Mon Sep 17 00:00:00 2001 From: UNGETSU Date: Thu, 20 Aug 2026 15:11:09 +0800 Subject: [PATCH 2/2] Derive Simplified Chinese session quota labels from duration --- Sources/CodexBar/Localization.swift | 18 ++++ .../CodexBar/MenuCardView+ModelHelpers.swift | 2 +- .../PlanUtilizationHistoryChartMenuView.swift | 12 ++- .../zh-Hans.lproj/Localizable.strings | 4 +- .../PopupLocalizationTests.swift | 97 ++++++++++++++----- 5 files changed, 104 insertions(+), 29 deletions(-) diff --git a/Sources/CodexBar/Localization.swift b/Sources/CodexBar/Localization.swift index d9fc9a3352..7266c737ef 100644 --- a/Sources/CodexBar/Localization.swift +++ b/Sources/CodexBar/Localization.swift @@ -213,6 +213,24 @@ func L(_ key: String, language: String) -> String { return codexBarLocalizedString(key, bundle: bundle, resourceBundle: resourceBundle) } +/// Uses an explicit duration for Simplified Chinese quota surfaces while preserving the generic +/// `Session` translation for conversations and other non-quota UI. +func localizedSessionQuotaLabel(_ label: String, windowMinutes: Int?) -> String { + let localizedLabel = L(label) + guard label == "Session", + localizedBundle().bundleURL.lastPathComponent.caseInsensitiveCompare("zh-Hans.lproj") == .orderedSame, + let windowMinutes + else { return localizedLabel } + + if windowMinutes == 7 * 24 * 60 { + return L("Weekly") + } + guard (60...(12 * 60)).contains(windowMinutes), windowMinutes.isMultiple(of: 60) else { + return localizedLabel + } + return "\(codexBarLocalizedInteger(windowMinutes / 60)) \(L("Hour"))" +} + func codexBarLocalizedLocale() -> Locale { codexBarLocale(forLanguage: resolvedAppLanguage()) } diff --git a/Sources/CodexBar/MenuCardView+ModelHelpers.swift b/Sources/CodexBar/MenuCardView+ModelHelpers.swift index dfa70bc44f..3f453a065b 100644 --- a/Sources/CodexBar/MenuCardView+ModelHelpers.swift +++ b/Sources/CodexBar/MenuCardView+ModelHelpers.swift @@ -559,7 +559,7 @@ extension UsageMenuCardView.Model { ? L("Monthly") : input.metadata.opusLabel.map(L) ?? L("Sonnet") return ( - L(primaryLabel), + localizedSessionQuotaLabel(primaryLabel, windowMinutes: snapshot.primary?.windowMinutes), L(secondaryLabel), tertiaryLabel, input.metadata.supportsOpus) diff --git a/Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift b/Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift index 8d1eca3a08..b4af4d6d13 100644 --- a/Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift +++ b/Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift @@ -223,7 +223,10 @@ struct PlanUtilizationHistoryChartMenuView: View { .map { history in VisibleSeries( selection: SeriesSelection(name: history.name, windowMinutes: history.windowMinutes), - title: self.seriesTitle(name: history.name, metadata: metadata), + title: self.seriesTitle( + name: history.name, + metadata: metadata, + windowMinutes: history.windowMinutes), history: history) } } @@ -621,11 +624,12 @@ struct PlanUtilizationHistoryChartMenuView: View { private nonisolated static func seriesTitle( name: PlanUtilizationSeriesName, - metadata: ProviderMetadata?) -> String + metadata: ProviderMetadata?, + windowMinutes: Int) -> String { switch name { case .session: - L(metadata?.sessionLabel ?? "Session") + localizedSessionQuotaLabel(metadata?.sessionLabel ?? "Session", windowMinutes: windowMinutes) case .weekly: L(metadata?.weeklyLabel ?? "Weekly") case .monthly: @@ -673,6 +677,7 @@ struct PlanUtilizationHistoryChartMenuView: View { let xDomain: ClosedRange? let selectedSeries: String? let visibleSeries: [String] + let visibleSeriesTitles: [String] let usedPercents: [Double] let pointDates: [String] } @@ -696,6 +701,7 @@ struct PlanUtilizationHistoryChartMenuView: View { xDomain: model.xDomain, selectedSeries: selectedSeries?.id, visibleSeries: visibleSeries.map(\.id), + visibleSeriesTitles: visibleSeries.map(\.title), usedPercents: model.points.map(\.usedPercent), pointDates: model.points.map { point in let formatter = DateFormatter() diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index 79f7d34c2b..59b95da728 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -6,7 +6,7 @@ "Fast" = "高速"; "iCloud Sync" = "iCloud Sync"; "Projects" = "项目"; -"Session %@" = "5 小时 %@"; +"Session %@" = "会话 %@"; "Std" = "标准"; "Show all (%d)" = "显示全部(%d)"; "Show less" = "收起"; @@ -332,7 +332,7 @@ "Secondary (\\(metadata.weeklyLabel))" = "次要(\\(metadata.weeklyLabel))"; "Select a provider" = "选择一个提供商"; "Select the IDE to monitor" = "选择要监控的 IDE"; -"Session" = "5 小时"; +"Session" = "会话"; "Session quota notifications" = "会话配额通知"; "Session tokens" = "会话令牌"; "provider_section_connection" = "连接"; diff --git a/Tests/CodexBarTests/PopupLocalizationTests.swift b/Tests/CodexBarTests/PopupLocalizationTests.swift index 058961530d..7976d128b8 100644 --- a/Tests/CodexBarTests/PopupLocalizationTests.swift +++ b/Tests/CodexBarTests/PopupLocalizationTests.swift @@ -8,39 +8,58 @@ import Testing @Suite(.serialized) struct PopupLocalizationTests { @Test - func `simplified Chinese identifies Claude session quota as five hours`() throws { + func `simplified Chinese derives session quota titles from their duration`() throws { try CodexBarLocalizationOverride.$appLanguage.withValue("zh-Hans") { + for (windowMinutes, expectedTitle) in [(60, "1 小时"), (300, "5 小时"), (720, "12 小时")] { + let model = try Self.makeClaudeMenuCardModel(primaryWindowMinutes: windowMinutes) + + #expect(model.metrics.first?.title == expectedTitle) + } + } + } + + @Test + func `simplified Chinese labels a Claude weekly primary fallback accurately`() throws { + try CodexBarLocalizationOverride.$appLanguage.withValue("zh-Hans") { + let model = try Self.makeClaudeMenuCardModel(primaryWindowMinutes: 7 * 24 * 60) + + #expect(model.metrics.first?.title == "每周") + } + } + + @Test + func `simplified Chinese history selector uses quota duration without changing conversations`() { + CodexBarLocalizationOverride.$appLanguage.withValue("zh-Hans") { let now = Date(timeIntervalSince1970: 1_700_000_000) - let metadata = try #require(ProviderDefaults.metadata[.claude]) + let histories = [ + PlanUtilizationSeriesHistory( + name: .session, + windowMinutes: 300, + entries: [PlanUtilizationHistoryEntry(capturedAt: now, usedPercent: 10, resetsAt: nil)]), + PlanUtilizationSeriesHistory( + name: .weekly, + windowMinutes: 7 * 24 * 60, + entries: [PlanUtilizationHistoryEntry(capturedAt: now, usedPercent: 20, resetsAt: nil)]), + ] let snapshot = UsageSnapshot( primary: RateWindow( usedPercent: 10, windowMinutes: 300, - resetsAt: now.addingTimeInterval(3600), + resetsAt: nil, + resetDescription: nil), + secondary: RateWindow( + usedPercent: 20, + windowMinutes: 7 * 24 * 60, + resetsAt: nil, resetDescription: nil), - secondary: nil, updatedAt: now) - let model = UsageMenuCardView.Model.make(.init( + let model = PlanUtilizationHistoryChartMenuView._modelSnapshotForTesting( + histories: histories, provider: .claude, - metadata: metadata, - snapshot: snapshot, - credits: nil, - creditsError: nil, - dashboard: nil, - dashboardError: nil, - tokenSnapshot: nil, - tokenError: nil, - account: AccountInfo(email: nil, plan: nil), - isRefreshing: false, - lastError: nil, - usageBarsShowUsed: false, - resetTimeDisplayStyle: .countdown, - tokenCostUsageEnabled: false, - showOptionalCreditsAndExtraUsage: true, - hidePersonalInfo: false, - now: now)) + snapshot: snapshot) - #expect(model.metrics.first?.title == "5 小时") + #expect(model.visibleSeriesTitles == ["5 小时", "每周"]) + #expect(String(format: L("Session %@"), "abc123") == "会话 abc123") } } @@ -231,4 +250,36 @@ struct PopupLocalizationTests { return text } } + + private static func makeClaudeMenuCardModel(primaryWindowMinutes: Int) throws -> UsageMenuCardView.Model { + let now = Date(timeIntervalSince1970: 1_700_000_000) + let metadata = try #require(ProviderDefaults.metadata[.claude]) + let snapshot = UsageSnapshot( + primary: RateWindow( + usedPercent: 10, + windowMinutes: primaryWindowMinutes, + resetsAt: now.addingTimeInterval(3600), + resetDescription: nil), + secondary: nil, + updatedAt: now) + return UsageMenuCardView.Model.make(.init( + provider: .claude, + metadata: metadata, + snapshot: snapshot, + credits: nil, + creditsError: nil, + dashboard: nil, + dashboardError: nil, + tokenSnapshot: nil, + tokenError: nil, + account: AccountInfo(email: nil, plan: nil), + isRefreshing: false, + lastError: nil, + usageBarsShowUsed: false, + resetTimeDisplayStyle: .countdown, + tokenCostUsageEnabled: false, + showOptionalCreditsAndExtraUsage: true, + hidePersonalInfo: false, + now: now)) + } }