diff --git a/.gitignore b/.gitignore
index 32f60fb..43b9cec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ CodexSwitcher.app/
# Swift Package Manager
Package.resolved
Package.repos
+.swiftpm/
diff --git a/Info.plist b/Info.plist
index 1376e9e..c6da28e 100644
--- a/Info.plist
+++ b/Info.plist
@@ -29,6 +29,6 @@
SUPublicEDKey
7lqmueyvl3O06tlzS0bafDBXiWwB0fkyW0/xScLU/Fo=
SUEnableAutomaticChecks
-
+
diff --git a/Package.swift b/Package.swift
index 498f46f..c598374 100644
--- a/Package.swift
+++ b/Package.swift
@@ -3,7 +3,8 @@ import PackageDescription
let package = Package(
name: "CodexSwitcher",
- platforms: [.macOS(.v26)],
+ defaultLocalization: "en",
+ platforms: [.macOS(.v15)],
dependencies: [
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.6.0")
],
@@ -15,7 +16,7 @@ let package = Package(
],
path: "Sources/CodexSwitcher",
resources: [
- .copy("Resources/codex.icns")
+ .process("Resources")
]
)
]
diff --git a/Sources/CodexSwitcher/AddAccountInlineView.swift b/Sources/CodexSwitcher/AddAccountInlineView.swift
index c683e6e..8a9b032 100644
--- a/Sources/CodexSwitcher/AddAccountInlineView.swift
+++ b/Sources/CodexSwitcher/AddAccountInlineView.swift
@@ -84,7 +84,7 @@ struct AddAccountInlineView: View {
Circle()
.stroke(gw.opacity(0.06), lineWidth: 2)
.frame(width: 52 + pulsePhase * 20, height: 52 + pulsePhase * 20)
- .opacity(1 - pulsePhase * 0.8)
+ .opacity(1 - Double(pulsePhase) * 0.8)
// Inner glass circle
Circle()
@@ -224,7 +224,11 @@ struct AddAccountInlineView: View {
.font(.system(size: 14, weight: .semibold))
.foregroundStyle(gw.opacity(0.9))
- glassButton(Str.close, icon: "xmark") { store.closeAddAccountWindow() }
+ glassButton(Str.close, icon: "xmark") {
+ store.closeAddAccountWindow()
+ store.addingStep = .idle
+ store.isAddingAccount = false
+ }
}
}
diff --git a/Sources/CodexSwitcher/AppStore.swift b/Sources/CodexSwitcher/AppStore.swift
index 9b6d88f..86c500a 100644
--- a/Sources/CodexSwitcher/AppStore.swift
+++ b/Sources/CodexSwitcher/AppStore.swift
@@ -179,12 +179,13 @@ final class AppStore: ObservableObject {
}
}
- // Consecutive failure gating
+ // Consecutive failure gating — back off for up to 3 skipped polls, then retry
if successCount == 0 && !credPairs.isEmpty {
consecutiveFetchFailures += 1
- if consecutiveFetchFailures >= 3 {
- // Back off: skip next few polls
+ if consecutiveFetchFailures < 6 && consecutiveFetchFailures >= 3 {
return
+ } else if consecutiveFetchFailures >= 6 {
+ consecutiveFetchFailures = 0
}
} else {
consecutiveFetchFailures = 0
@@ -605,7 +606,7 @@ final class AppStore: ObservableObject {
stopAuthWatcher()
closeAddAccountWindow()
notifyProfileChanged()
- sendNotification(title: "Hesap eklendi", body: profile.displayName)
+ sendNotification(title: L("Hesap eklendi", "Account added"), body: profile.displayName)
Task { await fetchAllRateLimits() }
}
@@ -733,7 +734,7 @@ final class AppStore: ObservableObject {
let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let tokens = dict["tokens"] as? [String: Any],
let access = tokens["access_token"] as? String else { return }
- pendingProfileEmail = profileManager.extractEmail(from: access) ?? "bilinmeyen"
+ pendingProfileEmail = profileManager.extractEmail(from: access) ?? "unknown"
addingStep = .confirmProfile
} else {
// External modification detected — verify
diff --git a/Sources/CodexSwitcher/L10n.swift b/Sources/CodexSwitcher/L10n.swift
index 89666cf..5ec52da 100644
--- a/Sources/CodexSwitcher/L10n.swift
+++ b/Sources/CodexSwitcher/L10n.swift
@@ -1,17 +1,20 @@
import Foundation
-/// Basit TR/EN yerelleştirme.
-/// Önce UserDefaults'taki "appLanguage" key'ine bakar ("tr" / "en" / "system").
-/// "system" veya ayarlanmamışsa sistem dilini kullanır.
+/// TR/EN localisation. English is the base language; Turkish ships as a standard
+/// tr.lproj/Localizable.strings bundle loaded via NSLocalizedString.
+/// UserDefaults key "appLanguage": "en" (default) | "tr" | "system"
func L(_ tr: String, _ en: String) -> String {
- let stored = UserDefaults.standard.string(forKey: "appLanguage") ?? "system"
+ let stored = UserDefaults.standard.string(forKey: "appLanguage") ?? "en"
let code: String
if stored == "system" {
code = Locale.current.language.languageCode?.identifier ?? "en"
} else {
code = stored
}
- return code == "tr" ? tr : en
+ guard code == "tr" else { return en }
+ // Look up Turkish from the bundle; fall back to the hardcoded tr string
+ // for interpolated strings that can't be static .strings keys.
+ return NSLocalizedString(en, tableName: "Localizable", bundle: .module, value: tr, comment: "")
}
enum Str {
diff --git a/Sources/CodexSwitcher/ProfileManager.swift b/Sources/CodexSwitcher/ProfileManager.swift
index f3df6b9..25f74f6 100644
--- a/Sources/CodexSwitcher/ProfileManager.swift
+++ b/Sources/CodexSwitcher/ProfileManager.swift
@@ -155,7 +155,7 @@ final class ProfileManager: @unchecked Sendable {
return nil
}
- let email = extractEmail(from: accessToken) ?? "bilinmeyen@hesap.com"
+ let email = extractEmail(from: accessToken) ?? "unknown@account.com"
let profile = Profile(
id: UUID(),
alias: alias,
@@ -270,13 +270,13 @@ enum SwitcherError: LocalizedError {
var errorDescription: String? {
switch self {
case .missingAuthFile(let email):
- return "Auth dosyası bulunamadı: \(email)"
+ return L("Auth dosyası bulunamadı: \(email)", "Auth file not found: \(email)")
case .noProfilesAvailable:
- return "Henüz hesap eklenmedi."
+ return L("Henüz hesap eklenmedi.", "No accounts added yet.")
case .allProfilesExhausted:
- return "Tüm hesapların limiti doldu!"
+ return L("Tüm hesapların limiti doldu!", "All accounts have reached their limit!")
case .activationFailed(let email):
- return "Aktivasyon başarısız: \(email)"
+ return L("Aktivasyon başarısız: \(email)", "Activation failed: \(email)")
}
}
}
diff --git a/Sources/CodexSwitcher/RateLimitFetcher.swift b/Sources/CodexSwitcher/RateLimitFetcher.swift
index 5a54de7..68ca317 100644
--- a/Sources/CodexSwitcher/RateLimitFetcher.swift
+++ b/Sources/CodexSwitcher/RateLimitFetcher.swift
@@ -32,7 +32,7 @@ struct RateLimitInfo: Sendable {
var weeklyResetLabel: String {
guard let date = weeklyResetAt else { return "" }
let fmt = DateFormatter()
- fmt.locale = Locale(identifier: "tr_TR")
+ fmt.locale = Locale(identifier: "en_US_POSIX")
fmt.dateFormat = "d MMM"
return fmt.string(from: date)
}
@@ -40,7 +40,7 @@ struct RateLimitInfo: Sendable {
var fiveHourResetLabel: String {
guard let date = fiveHourResetAt else { return "" }
let fmt = DateFormatter()
- fmt.locale = Locale(identifier: "tr_TR")
+ fmt.locale = Locale(identifier: "en_US_POSIX")
fmt.dateFormat = "HH:mm"
return fmt.string(from: date)
}
diff --git a/Sources/CodexSwitcher/Resources/tr.lproj/Localizable.strings b/Sources/CodexSwitcher/Resources/tr.lproj/Localizable.strings
new file mode 100644
index 0000000..298cd9b
--- /dev/null
+++ b/Sources/CodexSwitcher/Resources/tr.lproj/Localizable.strings
@@ -0,0 +1,70 @@
+/* Str enum */
+"Add Account" = "Hesap Ekle";
+"Switch Now" = "Şimdi Geç";
+"Quit" = "Çıkış";
+"Weekly" = "Haftalık";
+"5 Hour" = "5 Saat";
+"Active Account" = "Aktif Hesap";
+"No accounts added" = "Hesap eklenmedi";
+"Rename" = "Yeniden Adlandır";
+"Delete" = "Sil";
+"Cancel" = "İptal";
+"Save" = "Kaydet";
+"Close" = "Kapat";
+"Start" = "Başlat";
+"Add New Account" = "Yeni Hesap Ekle";
+"Waiting for Login" = "Login Bekleniyor";
+"Account Detected!" = "Hesap Algılandı!";
+"Account Added!" = "Hesap Eklendi!";
+"Alias (optional)" = "Takma ad (opsiyonel)";
+"e.g. Work account" = "örn: İş hesabım";
+"Terminal will run `codex login`.\nSign in with your new account in the browser." = "Terminal'de `codex login` çalıştırılacak.\nBrowser'da yeni hesabınla giriş yap.";
+"Sign in with your account in the browser.\nWill continue automatically when done." = "Browser'da hesabınla giriş yap.\nTamamlanınca otomatik devam eder.";
+"Rename Account" = "Hesap Adını Değiştir";
+"All accounts exhausted!" = "Tüm hesaplar doldu!";
+"Switch anyway" = "Yine de geç";
+"last" = "son";
+"usage unknown" = "kullanım bilinmiyor";
+"Resets" = "Sıfırlanma";
+"History" = "Geçmiş";
+"No switches yet" = "Henüz geçiş yok";
+"Dark" = "Koyu";
+"Light" = "Açık";
+"Hide" = "Gizle";
+"Show" = "Göster";
+"Auto" = "Oto";
+"Back" = "Geri";
+"Limit reset" = "Limit sıfırlandı";
+"is ready to use again" = "kullanıma hazır";
+
+/* MenuContentView */
+"Manual override" = "Manuel override";
+"Chart" = "Grafik";
+"Re-login" = "Girişi Yenile";
+"Update" = "Güncelle";
+"Reset" = "Sıfırla";
+"No usage data in the last 7 days" = "Son 7 günde kullanım verisi yok";
+"Last 7 days — Token usage" = "Son 7 gün — Token kullanımı";
+
+/* AppStore */
+"Account" = "Hesap";
+"Limit warning" = "Limit uyarısı";
+"Manual switch" = "Manuel geçiş";
+"Will resume when limits reset." = "Limitler sıfırlanınca devam eder.";
+"Manual selection" = "Manuel seçim";
+"Switch failed" = "Geçiş başarısız";
+"Account verification failed. Please try again." = "Hesap doğrulanamadı. Lütfen tekrar deneyin.";
+"Account switched" = "Hesap değiştirildi";
+"Account Switched" = "Hesap değiştirildi";
+"Codex is restarting. New account is now active." = "Codex yeniden başlatılıyor. Yeni hesap aktif.";
+"Limit reached" = "Limit doldu";
+"Account added" = "Hesap eklendi";
+"Re-login successful" = "Giriş yenilendi";
+"Wrong account" = "Hatalı hesap";
+"A different account was detected. Please try again." = "Farklı bir hesaba giriş yapıldı. Tekrar deneyin.";
+"Auth issue" = "Auth sorunu";
+"Auth file corrupted. You may need to re-login to your accounts." = "Auth dosyası bozuldu. Hesapları yeniden giriş yapmanız gerekebilir.";
+
+/* ProfileManager errors */
+"No accounts added yet." = "Henüz hesap eklenmedi.";
+"All accounts have reached their limit!" = "Tüm hesapların limiti doldu!";
diff --git a/build.sh b/build.sh
index 8926032..4e2949b 100755
--- a/build.sh
+++ b/build.sh
@@ -17,6 +17,13 @@ cp "${BUILD_DIR}/${APP_NAME}" "${APP_BUNDLE}/Contents/MacOS/"
cp "Info.plist" "${APP_BUNDLE}/Contents/"
cp "Sources/CodexSwitcher/Resources/AppIcon.icns" "${APP_BUNDLE}/Contents/Resources/"
+# Bundle Sparkle framework
+mkdir -p "${APP_BUNDLE}/Contents/Frameworks"
+cp -R "${BUILD_DIR}/Sparkle.framework" "${APP_BUNDLE}/Contents/Frameworks/"
+
+# Fix rpath so the binary finds Sparkle in Contents/Frameworks
+install_name_tool -add_rpath "@executable_path/../Frameworks" "${APP_BUNDLE}/Contents/MacOS/${APP_NAME}" 2>/dev/null || true
+
# Ad-hoc sign — Gatekeeper "damaged" hatasını önler
codesign --force --deep --sign - "${APP_BUNDLE}"