Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let sweetCookieKitDependency: Package.Dependency =

let package = Package(
name: "CodexBar",
defaultLocalization: "en",
platforms: [
.macOS(.v14),
],
Expand Down
10 changes: 10 additions & 0 deletions Sources/CodexBar/CodexbarApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct CodexBarApp: App {

let preferencesSelection = PreferencesSelection()
let settings = SettingsStore()
Self.applyLanguagePreference(from: settings)
let managedCodexAccountCoordinator = ManagedCodexAccountCoordinator()
managedCodexAccountCoordinator.onManagedAccountsDidChange = {
_ = settings.persistResolvedCodexActiveSourceCorrectionIfNeeded()
Expand Down Expand Up @@ -100,6 +101,15 @@ struct CodexBarApp: App {
NSApp.activate(ignoringOtherApps: true)
_ = NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil)
}

private static func applyLanguagePreference(from settings: SettingsStore) {
let language = settings.appLanguage
if language.isEmpty {
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
} else {
UserDefaults.standard.set([language], forKey: "AppleLanguages")
}
}
}

// MARK: - Updater abstraction
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/KeychainPromptCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ enum KeychainPromptCoordinator {
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: L("OK"))
_ = alert.runModal()
}
}
40 changes: 40 additions & 0 deletions Sources/CodexBar/Localization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Foundation

private func appLanguageDefaults() -> UserDefaults {
if Bundle.main.bundleIdentifier != nil {
return .standard
}
// Fallback for running outside a .app bundle (swift run / debug builds)
return UserDefaults(suiteName: "CodexBar") ?? .standard
}

private func localizedBundle() -> Bundle {
let language = appLanguageDefaults().string(forKey: "appLanguage") ?? ""
if !language.isEmpty {
if let path = Bundle.module.path(forResource: language, ofType: "lproj"),
let bundle = Bundle(path: path) {
return bundle
}
} else {
// System mode: follow macOS language preferences
if let preferred = Bundle.module.preferredLocalizations.first,
let path = Bundle.module.path(forResource: preferred, ofType: "lproj"),
let bundle = Bundle(path: path) {
return bundle
}
}
// Fallback to en.lproj
if let path = Bundle.module.path(forResource: "en", ofType: "lproj"),
let bundle = Bundle(path: path) {
return bundle
}
return Bundle.module
}

func L(_ key: String) -> String {
localizedBundle().localizedString(forKey: key, value: nil, table: nil)
}

func L(_ key: String, _ arguments: CVarArg...) -> String {
String(format: localizedBundle().localizedString(forKey: key, value: nil, table: nil), arguments: arguments)
}
8 changes: 4 additions & 4 deletions Sources/CodexBar/ManagedCodexAccountService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,12 @@ struct CodexWorkspaceAlertSelector: ManagedCodexWorkspaceSelecting {
}

let alert = NSAlert()
alert.messageText = "Choose Codex workspace"
alert.informativeText = "CodexBar found multiple workspaces for \(email). Choose the one to add."
alert.messageText = L("Choose Codex workspace")
alert.informativeText = String(format: L("multiple_workspaces_found"), email)
alert.alertStyle = .informational
alert.accessoryView = popup
alert.addButton(withTitle: "Add Workspace")
alert.addButton(withTitle: "Cancel")
alert.addButton(withTitle: L("Add Workspace"))
alert.addButton(withTitle: L("Cancel"))

guard alert.runModal() == .alertFirstButtonReturn else {
return nil
Expand Down
12 changes: 6 additions & 6 deletions Sources/CodexBar/MenuBarDisplayMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ enum MenuBarDisplayMode: String, CaseIterable, Identifiable {

var label: String {
switch self {
case .percent: "Percent"
case .pace: "Pace"
case .both: "Both"
case .percent: L("display_mode_percent")
case .pace: L("display_mode_pace")
case .both: L("display_mode_both")
}
}

var description: String {
switch self {
case .percent: "Show remaining/used percentage (e.g. 45%)"
case .pace: "Show pace indicator (e.g. +5%)"
case .both: "Show both percentage and pace (e.g. 45% · +5%)"
case .percent: L("display_mode_percent_desc")
case .pace: L("display_mode_pace_desc")
case .both: L("display_mode_both_desc")
}
}
}
24 changes: 12 additions & 12 deletions Sources/CodexBar/PreferencesAboutPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,26 +51,26 @@ struct AboutPane: View {
VStack(spacing: 2) {
Text("CodexBar")
.font(.title3).bold()
Text("Version \(self.versionString)")
Text(String(format: L("version_format"), self.versionString))
.foregroundStyle(.secondary)
if let buildTimestamp {
Text("Built \(buildTimestamp)")
Text(String(format: L("built_format"), buildTimestamp))
.font(.footnote)
.foregroundStyle(.secondary)
}
Text("May your tokens never run out—keep agent limits in view.")
Text(L("about_tagline"))
.font(.footnote)
.foregroundStyle(.secondary)
}

VStack(alignment: .center, spacing: 10) {
AboutLinkRow(
icon: "chevron.left.slash.chevron.right",
title: "GitHub",
title: L("link_github"),
url: "https://github.com/steipete/CodexBar")
AboutLinkRow(icon: "globe", title: "Website", url: "https://steipete.me")
AboutLinkRow(icon: "bird", title: "Twitter", url: "https://twitter.com/steipete")
AboutLinkRow(icon: "envelope", title: "Email", url: "mailto:peter@steipete.me")
AboutLinkRow(icon: "globe", title: L("link_website"), url: "https://steipete.me")
AboutLinkRow(icon: "bird", title: L("link_twitter"), url: "https://twitter.com/steipete")
AboutLinkRow(icon: "envelope", title: L("link_email"), url: "mailto:peter@steipete.me")
}
.padding(.top, 8)
.frame(maxWidth: .infinity)
Expand All @@ -80,12 +80,12 @@ struct AboutPane: View {

if self.updater.isAvailable {
VStack(spacing: 10) {
Toggle("Check for updates automatically", isOn: self.$autoUpdateEnabled)
Toggle(L("check_updates_auto"), isOn: self.$autoUpdateEnabled)
.toggleStyle(.checkbox)
.frame(maxWidth: .infinity, alignment: .center)
VStack(spacing: 6) {
HStack(spacing: 12) {
Text("Update Channel")
Text(L("update_channel"))
Spacer()
Picker("", selection: self.updateChannelBinding) {
ForEach(UpdateChannel.allCases) { channel in
Expand All @@ -102,14 +102,14 @@ struct AboutPane: View {
.multilineTextAlignment(.center)
.frame(maxWidth: 280)
}
Button("Check for Updates…") { self.updater.checkForUpdates(nil) }
Button(L("check_for_updates")) { self.updater.checkForUpdates(nil) }
}
} else {
Text(self.updater.unavailableReason ?? "Updates unavailable in this build.")
Text(self.updater.unavailableReason ?? L("updates_unavailable"))
.foregroundStyle(.secondary)
}

Text("© 2026 Peter Steinberger. MIT License.")
Text(L("copyright"))
.font(.footnote)
.foregroundStyle(.secondary)
.padding(.top, 4)
Expand Down
41 changes: 19 additions & 22 deletions Sources/CodexBar/PreferencesAdvancedPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ struct AdvancedPane: View {
ScrollView(.vertical, showsIndicators: true) {
VStack(alignment: .leading, spacing: 16) {
SettingsSection(contentSpacing: 8) {
Text("Keyboard shortcut")
Text(L("section_keyboard_shortcut"))
.font(.caption)
.foregroundStyle(.secondary)
.textCase(.uppercase)
HStack(alignment: .center, spacing: 12) {
Text("Open menu")
Text(L("open_menu_shortcut_title"))
.font(.body)
Spacer()
KeyboardShortcuts.Recorder(for: .openMenu)
}
Text("Trigger the menu bar menu from anywhere.")
Text(L("open_menu_shortcut_subtitle"))
.font(.footnote)
.foregroundStyle(.tertiary)
}
Expand All @@ -36,7 +36,7 @@ struct AdvancedPane: View {
if self.isInstallingCLI {
ProgressView().controlSize(.small)
} else {
Text("Install CLI")
Text(L("install_cli"))
}
}
.disabled(self.isInstallingCLI)
Expand All @@ -48,7 +48,7 @@ struct AdvancedPane: View {
.lineLimit(2)
}
}
Text("Symlink CodexBarCLI to /usr/local/bin and /opt/homebrew/bin as codexbar.")
Text(L("install_cli_subtitle"))
.font(.footnote)
.foregroundStyle(.tertiary)
}
Expand All @@ -57,39 +57,36 @@ struct AdvancedPane: View {

SettingsSection(contentSpacing: 10) {
PreferenceToggleRow(
title: "Show Debug Settings",
subtitle: "Expose troubleshooting tools in the Debug tab.",
title: L("show_debug_settings_title"),
subtitle: L("show_debug_settings_subtitle"),
binding: self.$settings.debugMenuEnabled)
PreferenceToggleRow(
title: "Surprise me",
subtitle: "Check if you like your agents having some fun up there.",
title: L("surprise_me_title"),
subtitle: L("surprise_me_subtitle"),
binding: self.$settings.randomBlinkEnabled)
PreferenceToggleRow(
title: "Weekly limit confetti",
subtitle: "Play full-screen confetti when weekly usage resets.",
title: L("weekly_limit_confetti_title"),
subtitle: L("weekly_limit_confetti_subtitle"),
binding: self.$settings.confettiOnWeeklyLimitResetsEnabled)
}

Divider()

SettingsSection(contentSpacing: 10) {
PreferenceToggleRow(
title: "Hide personal information",
subtitle: "Obscure email addresses in the menu bar and menu UI.",
title: L("hide_personal_info_title"),
subtitle: L("hide_personal_info_subtitle"),
binding: self.$settings.hidePersonalInfo)
}

Divider()

SettingsSection(
title: "Keychain access",
caption: """
Disable all Keychain reads and writes. Browser cookie import is unavailable; paste Cookie \
headers manually in Providers.
""") {
title: L("section_keychain_access"),
caption: L("keychain_access_caption")) {
PreferenceToggleRow(
title: "Disable Keychain access",
subtitle: "Prevents any Keychain access while enabled.",
title: L("disable_keychain_access_title"),
subtitle: L("disable_keychain_access_subtitle"),
binding: self.$settings.debugDisableKeychainAccess)
}
}
Expand All @@ -109,7 +106,7 @@ extension AdvancedPane {
let helperURL = Bundle.main.bundleURL.appendingPathComponent("Contents/Helpers/CodexBarCLI")
let fm = FileManager.default
guard fm.fileExists(atPath: helperURL.path) else {
self.cliStatus = "CodexBarCLI not found in app bundle."
self.cliStatus = L("cli_not_found")
return
}

Expand Down Expand Up @@ -145,7 +142,7 @@ extension AdvancedPane {
}

self.cliStatus = results.isEmpty
? "No writable bin dirs found."
? L("no_writable_bin_dirs")
: results.joined(separator: " · ")
}

Expand Down
Loading