codeburn/mac/Sources/CodeBurnMenubar/Data/SubscriptionRefreshCadence.swift
ozymandiashh d62857ea4e feat(menubar): route every user-facing string through a strings catalog
Groundwork for #1219. The menubar app hardcoded ~530 English strings across
Views/, the presentation structs, the status-item menu and the update alerts,
so there was nothing for a translator to translate.

Every user-facing literal now goes through `L(_:)` / `L(_:_:)`, which look the
string up in `Localizable.strings` in the SwiftPM target resource bundle. The
key *is* the English copy, so English stays the development language and a
missing translation degrades to correct English instead of a dotted
identifier. `en.lproj` is therefore an identity table; it exists so the bundle
advertises `en` and so a translation can be diffed against it.

Why the lookups are explicit rather than relying on SwiftUI's implicit
`LocalizedStringKey`: SwiftPM emits target resources into a sibling bundle
(`CodeBurnMenubar_CodeBurnMenubar.bundle`) that the packaging scripts copy into
`Contents/Resources`. `Bundle.main` has no `.lproj` at all, so `Text("literal")`
would always miss. Naming `Bundle.module` is the one form that resolves the
same way in `swift run`, in `swift test` and in the packaged `.app`.

Enum raw values that double as identity (`Period`, `MenubarScope`,
`InsightMode`, `AccentPreset`, `ProviderFilter`) keep their raw value and gain
a `displayLabel`, so persistence and cache keys are untouched by translation.

Three display-only date formatters that were pinned to `en_US_POSIX` with fixed
patterns now use `setLocalizedDateFormatFromTemplate`, and the calendar
popover's weekday row comes from the locale's own symbols. The `yyyy-MM-dd`
formatter stays POSIX: it parses and builds data keys, not display text.

Not extracted, deliberately: provider, model and plan names; units; currency
codes; shell commands and paths; quota window labels that policy code matches
on by English substring (`QuotaSummary.headlineWindow`); and anything the
`codeburn` CLI produces.

Also copies the SwiftPM resource bundle into the app in build-local.sh, using
the named-bundle + `[[ -d ]]` pattern package-app.sh already uses. Without it
the assembled app finds no strings table (and already trapped on first icon
load, the resource-bundle half of #1262).
2026-09-12 15:21:49 -07:00

42 lines
1.7 KiB
Swift

import Foundation
/// User-configurable cadence for /api/oauth/usage polling. Uses
/// "manual / 1m / 2m / 5m / 15m" preset set so users on tight rate-limit
/// budgets can dial it down and power users can dial it up. Stored as the raw
/// number of seconds in UserDefaults; `manual = 0` means "never auto-refresh".
enum SubscriptionRefreshCadence: Int, CaseIterable, Identifiable {
case manual = 0
case oneMinute = 60
case twoMinutes = 120
case fiveMinutes = 300
case fifteenMinutes = 900
var id: Int { rawValue }
var label: String {
switch self {
case .manual: return L("Manual")
case .oneMinute: return L("1 minute")
case .twoMinutes: return L("2 minutes")
case .fiveMinutes: return L("5 minutes")
case .fifteenMinutes: return L("15 minutes")
}
}
static let defaultsKey = "codeburn.claude.refreshCadenceSeconds"
static let `default`: SubscriptionRefreshCadence = .twoMinutes
static var current: SubscriptionRefreshCadence {
get {
// UserDefaults.integer returns 0 when the key is missing — that
// happens to alias `manual`, which is wrong for a fresh install.
// Probe with object(forKey:) so we can distinguish "never set"
// from "set to manual" and seed the default on first run.
if UserDefaults.standard.object(forKey: defaultsKey) == nil {
return .default
}
return SubscriptionRefreshCadence(rawValue: UserDefaults.standard.integer(forKey: defaultsKey)) ?? .default
}
set { UserDefaults.standard.set(newValue.rawValue, forKey: defaultsKey) }
}
}