codeburn/mac/Sources/CodeBurnMenubar/Data/QuotaPacePresentation.swift
ozymandiashh a208c25f86
Some checks are pending
CI / semgrep (push) Waiting to run
macOS Menubar CI / test (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
Tests / test (24) (push) Waiting to run
Tests / test (26) (push) Waiting to run
Tests / test-platforms (macos-latest) (push) Waiting to run
Tests / test-platforms (windows-latest) (push) Waiting to run
Tests / desktop (push) Waiting to run
fix(menubar): pin multi-argument catalog keys to positional specifiers
The catalog comparison matched specifier lists, so a key whose
placeholders are all %@ passed even when the Chinese value bound the
arguments into the wrong slots — the drift that shipped in three
early-reset keys and was only caught by rendering real data bilingually
(#1331).

All 42 multi-argument keys in both tables now use positional specifiers
(%1$@, %2$lld, ...); the parity check compares slot to verb so Chinese
word order stays free while each slot keeps its type; a new test fails
on any multi-argument key that is not positional.

The source scanner closes its three expression gaps: ternary branches,
concatenated literals, and let-bound literals passed by name.
Text(verbatim:) stays the explicit opt-out; the four provider-name
fallbacks the new rules surfaced in HeatmapSection route through the
catalog as verbatim entries.

Closes #1331
2026-09-15 01:33:28 +03:00

30 lines
1.3 KiB
Swift

import Foundation
/// Countdown wording shared by the Capacity Dock, the popover quota rows and
/// the reset notices, plus Claude's fixed window lengths.
enum QuotaPacePresentation {
/// Claude's rate-limit windows are fixed lengths (the same values the
/// plan popover projects with), so they are validated durations.
static let claudeFiveHourSeconds = 5 * 3600
static let claudeSevenDaySeconds = 7 * 24 * 3600
/// "2d 3h" / "3h 20m" / "45m" / "<1m", mirroring the window column's own
/// countdown shape. Computed against the passed dates, never a hidden
/// wall clock, so fixtures stay deterministic.
static func countdownLabel(from now: Date, to date: Date) -> String {
countdownLabel(seconds: date.timeIntervalSince(now))
}
/// Countdown label for an already-computed interval (clamped at zero).
static func countdownLabel(seconds: TimeInterval) -> String {
let value = max(0, seconds)
if value < 60 { return L("<1m") }
let minutes = Int(value / 60)
let hours = minutes / 60
let days = hours / 24
// Same three keys the popover's reset countdown uses.
if days > 0 { return L("%1$lldd %2$lldh", days, hours % 24) }
if hours > 0 { return L("%1$lldh %2$lldm", hours, minutes % 60) }
return L("%lldm", minutes)
}
}