Commit graph

6 commits

Author SHA1 Message Date
Resham Joshi
43608c2558
desktop and menu bar: six-language localization (#1493)
The desktop app and the macOS menu bar can now run in English, French, Japanese, Korean, Simplified Chinese and Traditional Chinese. A Language setting in the desktop Settings picks the language or follows the system, and the choice is shared so the menu bar switches with it, relaunching to pick up the language with a retry so a switch never leaves it down.

Every user-facing string in the desktop renderer goes through a local lookup table, 1,249 keys per language, no framework and no network. The menu bar gains four .lproj to join English and Simplified Chinese, 626 keys each. A completeness gate fails the build if any language is missing a key, in both the desktop catalogs and the menu bar coverage suite. Fonts fall back per language so Japanese, Korean and both Chinese render their correct glyph forms, and dates and numbers format for the locale with values unchanged. English output is byte-identical.

Strings produced by the CLI engine stay English until the CLI is localized, a separate effort. Validated live in all six languages: no missing text, no clipping, correct fonts, numbers unchanged, and the menu bar switching for every language.
2026-09-19 03:25:46 -07:00
ozymandiashh
9eebfda627 feat(menubar): add Simplified Chinese (zh-Hans) localization
All 533 catalog keys translated into zh-Hans, so the menubar app now follows
the system language with no setting to find and no third-party library. AppKit
picks the table: `CFBundleLocalizations` in both packaging scripts advertises
`en` and `zh-Hans`, which is also what puts the app under System Settings >
Language & Region so a user can override the language for CodeBurn alone.

Wording follows macOS system apps rather than a literal gloss: 立即刷新,
断开连接, 存储并连接, 载入配额, 再试一次. Product nouns stay as users
already know them: provider, model and plan names (Claude, Codex, Gemini,
Opus, Sonnet, Antigravity), Token, Dock, API, OAuth, shell commands and file
paths, currency codes.

Format specifiers are identical to the English entry in count and argument
order. `%%` is a literal percent sign and not an argument, so it may move where
Chinese word order demands it ("%@ · %@ 达到 100%%"); the argument-consuming
specifiers may not, because String(format:) binds them positionally. The next
commit's test enforces exactly that distinction.

Numbers, dates and currency are untouched: they already went through
NumberFormatter / DateFormatter / asCurrency(), and `L(_:_:)` only substitutes
values those produced.
2026-09-12 15:21:49 -07:00
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
AgentSeal
d349a01e99 harden(mac): fail-closed minos guard, weak-link errno check, strip em-dashes
Review follow-up on the Sonoma deployment-target fix:
- The release minos guard was fail-open: if vtool returned nothing (missing
  binary, tool change) the check passed vacuously and could green-light a bad
  release. Now it fails closed when no minos is reported.
- The libswift_errno check now excludes weak links, so it only fails on a
  breaking STRONG link. dyld tolerates a missing weakly-linked dylib, so a
  macOS 15.x SDK runner that weak-links errno at the .v14 target no longer
  causes a spurious release failure.
- Removed em-dashes from the changed files per repo style.

Verified: fresh .v14 build is minos 14.0 with no strong errno link; the guard
now fails closed on an unreadable binary and passes on the real one; 71 Swift
tests pass.
2026-07-10 09:20:15 +02:00
wanghao20
c8bddb52c2 fix(mac): address review notes on PR #577 — correct SDK/deployment-target wording, universal build-local.sh, CI minos guard
- README.md / build-local.sh: the -10825 fix is the Package.swift deployment
  target, not the SDK used to build (ld64's $ld$previous drops
  libswift_errno.dylib based on minos, so it already applies to the
  CI-distributed release too). build-local.sh exists only for building on a
  Sonoma machine with just the Command Line Tools.
- build-local.sh: build arm64 and x86_64 separately and lipo them into a
  universal binary (`--arch arm64 --arch x86_64` together needs xcbuild,
  which CLT doesn't ship); assert the active SDK is actually 14.x instead of
  silently trusting `xcrun`; use `pkill -x` instead of `-f` to avoid matching
  unrelated processes; broaden the @MainActor patch regex to slurp mode so it
  also covers multi-line struct headers and `extension X: View`.
- package-app.sh: fail the build if the packaged binary's minos isn't 14.0
  for every arch slice, or if it links libswift_errno.dylib, so a future
  deployment-target regression is caught in CI instead of a user's crash report.
2026-07-10 09:19:18 +02:00
wanghao20
a91c1c9f2a fix(mac): target macOS 14 so the menubar app runs on Sonoma
The packaged app set Package.swift to .macOS(.v15), producing a binary with
minos 15.0 that LaunchServices refuses on macOS 14.x with
kLSIncompatibleSystemVersionErr (-10825) — even though Info.plist, the CLI
install guard (MIN_MACOS_MAJOR=14), and the README all advertise macOS 14+.
The .v15 bump was attributed to NSAttributedString(attachment:), which is
actually AppKit since macOS 10.0, so the floor must not exclude Sonoma.

Separately, a stock macOS-15-SDK CI build hard-links libswift_errno.dylib
(macOS 15 only), which dyld cannot resolve on Sonoma even once minos passes.
Building against the macOS 14 SDK avoids that dependency.

- Package.swift: .macOS(.v15) -> .macOS(.v14)
- Scripts/build-local.sh: build on a Sonoma machine (which only has the
  macOS 14 SDK, lacking the SwiftUI @MainActor inference the macOS 15 SDK
  adds to the View protocol) using a standalone swift.org Swift 6.x toolchain,
  patching @MainActor onto views in a scratch copy so repo sources stay clean.
- README: document the Sonoma local-build path.
2026-07-10 09:19:18 +02:00