From 0dd295839c464beff06dcc849b8d2bbda0362013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:32:23 +0300 Subject: [PATCH 01/16] docs: add Turkish README translation (README.tr.md) --- README.md | 2 +- readme/README.ja.md | 2 +- readme/README.ko.md | 2 +- readme/README.ru.md | 2 +- readme/README.tr.md | 168 +++++++++++++++++++++++++++++++++++++++++ readme/README.zh-CN.md | 2 +- 6 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 readme/README.tr.md diff --git a/README.md b/README.md index d618790973..0ef20a4c6a 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ ocx start # proxy + dashboard on localhost:10100

- English · 한국어 · 简体中文 · Русский · 日本語 · 📖 Full documentation → + English · 한국어 · 简体中文 · Русский · 日本語 · Türkçe · 📖 Full documentation →

opencodex is a lightweight local proxy that translates Codex's Responses API into whatever your diff --git a/readme/README.ja.md b/readme/README.ja.md index de3a8d4b86..ea59578eaa 100644 --- a/readme/README.ja.md +++ b/readme/README.ja.md @@ -25,7 +25,7 @@ ocx start # プロキシ + ダッシュボード: localhost:10100

- English · 한국어 · 简体中文 · Русский · 日本語 · 📖 完全なドキュメント → + English · 한국어 · 简体中文 · Русский · 日本語 · Türkçe · 📖 完全なドキュメント →

diff --git a/readme/README.ko.md b/readme/README.ko.md index c76233c920..82c4e9ae98 100644 --- a/readme/README.ko.md +++ b/readme/README.ko.md @@ -25,7 +25,7 @@ ocx start # 프록시 + 대시보드: localhost:10100

- English · 한국어 · 简体中文 · Русский · 日本語 · 📖 전체 문서 → + English · 한국어 · 简体中文 · Русский · 日本語 · Türkçe · 📖 전체 문서 →

diff --git a/readme/README.ru.md b/readme/README.ru.md index 860d8c1d09..a0d8721152 100644 --- a/readme/README.ru.md +++ b/readme/README.ru.md @@ -25,7 +25,7 @@ ocx start # прокси + дашборд: localhost:10100

- English · 한국어 · 简体中文 · Русский · 日本語 · 📖 Полная документация → + English · 한국어 · 简体中文 · Русский · 日本語 · Türkçe · 📖 Полная документация →

diff --git a/readme/README.tr.md b/readme/README.tr.md new file mode 100644 index 0000000000..d7b06ce66b --- /dev/null +++ b/readme/README.tr.md @@ -0,0 +1,168 @@ +

make codex open!

+

OpenAI Codex, Claude Code, Claude Desktop & Grok Build için Evrensel Sağlayıcı Proxy'si
+İki komut ile yönlendirdiğiniz her LLM'i çalıştırın.

+ +

+ X üzerinde @claudeebum takip edin + npm sürümü + lisans + node sürümü +

+ +```bash +npm install -g @bitkyc08/opencodex +ocx start # localhost:10100 üzerinde proxy + panel +``` + + + + + + + + + + +
+ opencodex üzerinden yönlendirilen model ile çalışan Claude Code
+ Claude Code, herhangi bir modeli çalıştırıyor.
Seçici standart Claude Code'dur, arkasındaki beyin ise sizinki.
+
+ opencodex demosu
+ Codex, herhangi bir modeli çalıştırıyor.
Bir sağlayıcı seçin ve başlayın — aynı iş akışı, farklı beyin.
+
+ Claude Desktop demosu
+ Claude Desktop, herhangi bir modeli çalıştırıyor.
Opus yanıtlar, ardından görevi GPT-5.6 Sol alt ajanına devreder.
+
+ Grok Build demosu
+ Grok Build, herhangi bir modeli çalıştırıyor.
Sol oturumu yönetir ve Kimi K3 alt ajanını çağırır.
+
+ +

+ English · 한국어 · 简体中文 · Русский · 日本語 · Türkçe · 📖 Tam dokümantasyon → +

+ +opencodex, Codex'in Responses API'sini sağlayıcınızın desteklediği formata (streaming, araç çağrıları, reasoning jetonları, görseller) her iki yönde çeviren hafif bir yerel proxy'dir. Claude, Gemini, Grok, GLM, DeepSeek, Kimi, Qwen, Ollama veya diğer tüm LLM'leri Codex, Claude Code, Claude Desktop ve Grok Build ile kullanın. Ayrıca Codex kimlik doğrulaması için bir **ChatGPT hesap havuzu** yönetebilir: hesap ekleyin, panelden kotaları yenileyin ve mevcut iş parçacıkları başlayan hesaba sabit kalırken yeni oturumların en az kullanılan sağlıklı hesaba otomatik yönlendirilmesini sağlayın. + +## Hızlı başlangıç + +### İnsanlar için + +```bash +npm install -g @bitkyc08/opencodex # Node 18+; Bun çalışma zamanı otomatik olarak paketlenmiştir +ocx start # veya arka planda çalıştırmak için `ocx service` +``` + +**http://localhost:10100** adresini açın ve web panelinde her şeyi yapılandırın — sağlayıcılar ekleyin (40+ hazır sağlayıcı veya herhangi bir OpenAI uyumlu uç nokta), modeller seçin, hesapları yönetin. `ocx gui` paneli istediğiniz zaman tekrar açar. + +### Ajanlar için + +```bash +npm install -g @bitkyc08/opencodex +ocx start # veya `ocx service` +ocx init # etkileşimli kurulum: ~/.opencodex/config.json yazar ve Codex'i bağlar +``` + +`ocx init` proxy'yi asla başlatmaz; önce başlatın (veya sonra — her iki sıra da çalışır, ancak `ocx provider add` ve `ocx combo set` gibi headless komutlar **canlı** proxy ile konuşur ve ulaşılamadığında sıfır dışı kodla çıkar). `ocx status` / `ocx doctor` / `ocx health` çalışan durumu raporlar. + +> **opencodex yükleyen veya çalıştıran ajanlar:** [`AGENTS_INSTALL.md`](../AGENTS_INSTALL.md) dosyasını okuyun. Etkileşimli bir `ocx start` bu depoya yıldız verilip verilmeyeceğini bir kez sorabilir — bu kullanıcının kararıdır, asla ajanın değil. CLI, ajan kaynaklı çalıştırmalarda istemi bastırır ve API bunları `403 agent_consent_required` ile reddeder. + +## Desteklenen platformlar + +| İşletim Sistemi | Durum | Servis Yöneticisi | +|---|---|---| +| macOS (arm64 / x64) | Tam destekleniyor | launchd | +| Linux (x64 / arm64) | Tam destekleniyor | systemd (user unit) | +| Windows (x64) | Tam destekleniyor | Görev Zamanlayıcı (gizli) / isteğe bağlı yerel servis (`--native`, WinSW) | + +[Node](https://nodejs.org) 18+ gerektirir. Bun çalışma zamanı `npm install` sırasında paketlenmiş olarak gelir — ayrı bir Bun kurulumu veya Windows'ta WSL gerekmez. npm paketlenmiş çalışma zamanının yükleme betiklerini engellediyse [kurulum dokümantasyonuna](https://opencodex.me/getting-started/installation/) bakın. + +## Öne Çıkan Özellikler + +- **Codex, Claude Code, Claude Desktop ve Grok Build ile herhangi bir LLM kullanın** — Kutudan çıktığı haliyle 40+ sağlayıcı, her biri kendi yerel arayüzünü korur. +- **ChatGPT hesaplarını güvenle havuzlayın** — İş parçacığı bağlılığı (thread affinity), kota duyarlı otomatik geçiş, soğuma süresi ve hatada kapalı yetkilendirme yönetimi. +- **Kombolar (Combos)** — Sağlayıcılar arasında yedekli (failover) veya ağırlıklı round-robin ile çalışan tek bir sanal model kimliği. [Kombo rehberine](https://opencodex.me/guides/combos/) bakın. +- **Herhangi bir modelde alt ajanlar** — v1/v2 yüzey kontrolü ve yedekleme zincirleriyle Codex'in alt ajan seçicisinde yönlendirilen modelleri öne çıkarın. [Alt ajan rehberine](https://opencodex.me/guides/sub-agent-surface/) bakın. +- **Bir kez giriş yapın, API anahtarını atlayın** — xAI, Anthropic ve Kimi için OAuth; veya `codex login` iletme, anahtar yapıştırma veya `${ENV_VAR}` referansları kullanma. +- **Web araması ve görsel yan araçları (sidecars)** — OpenAI dışı modeller, ChatGPT girişiniz üzerinden bir sidecar aracılığıyla gerçek web araması ve görsel anlama yeteneği kazanır. +- **Ne olduğunu görün** — Panel; sağlayıcıları, OAuth durumunu, model seçimini ve önbellek jeton sayılarıyla canlı istek günlüğünü gösterir. +- **Temiz çıkış, sıfır kalıntı** — `ocx stop` Codex'i orijinal yapılandırmasına geri döndürür. + +## Model yönlendirme + +`sağlayıcı/model` sözdizimi ile yapılandırılmış herhangi bir sağlayıcıyı ve modeli hedefleyin: + +```bash +codex -m "anthropic/claude-opus-5" "Bu hatayı açıkla" +codex -m "google/gemini-3-pro" "auth.ts için birim testleri yaz" +codex -m "ollama/llama3" "Bu fonksiyonu refactor et" +``` + +Varsayılan sağlayıcıyı kullanmak veya model adı desenine göre otomatik eşleştirmek için `sağlayıcı/` ön ekini çıkarın. `/` içeren sağlayıcı model kimlikleri iç takma adlarla `-` olarak sunulur; ham tam eğik çizgili form da çalışmaya devam eder. Detaylar: [model yönlendirme dokümantasyonu](https://opencodex.me/guides/model-routing/). + +## Sağlayıcılar ve adaptörler + +OpenAI (ChatGPT girişi veya API anahtarı), Anthropic, Google Gemini, xAI, Kimi, Azure OpenAI, Ollama (yerel + Bulut), Cursor (deneysel) ve tüm OpenAI uyumlu uç noktalar — ayrıca DeepSeek, Groq, OpenRouter, Together, Fireworks, Cerebras, Mistral, Hugging Face, NVIDIA NIM, MiniMax, Qwen Cloud, SiliconFlow ve daha fazlası. Tam liste: `ocx init` veya [sağlayıcı dokümantasyonu](https://opencodex.me/guides/providers/). + +## CLI + +```bash +ocx init # etkileşimli kurulum (yapılandırma yazar, Codex'i bağlar, shim sunar) +ocx start [--port 10100] # proxy'yi ön planda başlatır +ocx stop # durdurur + yerel Codex'i geri yükler +ocx service [install|start|stop|status|uninstall|remove] # arka plan servisi +ocx codex-shim install # `codex` her başlatıldığında proxy'yi isteğe bağlı başlatır +ocx status # proxy çalışıyor mu? +ocx gui # web panelini açar +ocx provider <...> # sağlayıcıları yönetir (listele/ekle/düzenle/test et/sil) +ocx account <...> # ChatGPT hesaplarını ve API anahtar havuzlarını yönetir +ocx combo <...> # failover / round-robin kombolarını yönetir +ocx v2 <...> # çoklu ajan v1/v2 yüzey kontrolleri +ocx update [--tag preview] # opencodex'i günceller +``` + +Sabitlenmemiş başlatmalar, tercih edilen bağlantı noktası meşgulse başka bir boş bağlantı noktası seçebilir; açık bir `--port` asla atlamaz. Tam referans: [CLI dokümantasyonu](https://opencodex.me/reference/cli/). + +### Otomatik başlatma: servis mi shim mi? + +Çökme durumunda yeniden başlayan ve her zaman açık bir proxy için **servisi** (`ocx service`) kullanın. Arka plan arka plan işlemi (daemon) olmadan hafif, isteğe bağlı başlatma için **shim**'i (`ocx codex-shim install`) kullanın. `ocx service uninstall` / `ocx codex-shim uninstall` ile kaldırın. + +### Kaldırma + +```bash +ocx uninstall # durdurur, servis/shim kaldırır, yerel Codex'i geri yükler, durumu temizler +npm uninstall -g @bitkyc08/opencodex +``` + +## Uzaktan erişim + +Varsayılan olarak opencodex `127.0.0.1` adresine bağlanır ve ekstra kimlik doğrulaması gerektirmez. Loopback ötesine bağlanmak (`"hostname": "0.0.0.0"`), bir taşıyıcı jeton (bearer token) **gerektirir** — proxy `OPENCODEX_API_AUTH_TOKEN` olmadan başlamayı reddeder ve her istemci isteği bunu `x-opencodex-api-key` olarak taşımalıdır. Detaylar: [yapılandırma referansı](https://opencodex.me/reference/configuration/). + +## Dokümantasyon + +Halka açık dokümanlar — kurulum, sağlayıcılar, yönlendirme, kombolar, alt ajanlar, sidecar'lar, entegrasyonlar ve CLI/yapılandırma/yönetim API referansları — [`docs-site/`](../docs-site) klasöründen oluşturulur ve **[opencodex.me](https://opencodex.me/)** üzerinde yayınlanır. + +Maintainer temel doğruluk notları [`structure/`](../structure) altında, katkıda bulunan kurulum rehberi [`CONTRIBUTING.md`](../CONTRIBUTING.md) dosyasında ve güvenlik raporlama [`SECURITY.md`](../SECURITY.md) dosyasındadır. Açıklanmamış güvenlik açıklarını herkese açık bir issue yerine [GitHub özel güvenlik açığı raporlaması](https://github.com/lidge-jun/opencodex/security/advisories/new) aracılığıyla gizlice bildirin. + +## Geliştirme + +Kaynak kod geliştirmesi `PATH` dizininizde `bun` CLI'ını gerektirir. Bu, yalnızca yüklü `ocx` komutları tarafından kullanılan yayınlanmış npm paketinin paketlenmiş Bun çalışma zamanından ayrıdır. + +```bash +git clone https://github.com/lidge-jun/opencodex.git +cd opencodex +bun install +bun run typecheck +bun run test +``` + +Bkz. **[Katkıda Bulunma (Contributing)](../CONTRIBUTING.md)**. + +## Sorumluluk Reddi (Disclaimer) + +opencodex bağımsız, topluluk tarafından sürdürülen bir projedir ve **OpenAI, Anthropic veya başka herhangi bir sağlayıcı ile bağlı değildir veya onlar tarafından onaylanmamıştır**. + +Bazı sağlayıcılar — özellikle Anthropic (Claude) — API trafiğini üçüncü taraf proxy'ler üzerinden yönlendiren hesapları askıya alabilir veya kısıtlayabilir. **Kullanım riski tamamen size aittir (UAYOR).** Bir sağlayıcıyı bağlamadan önce, proxy tabanlı erişime izin verildiğini doğrulamak için Hizmet Şartlarını inceleyin. opencodex maintainer'ları, yukarı akış sağlayıcıları tarafından alınan herhangi bir hesap işleminden sorumlu değildir. + +## Lisans + +MIT diff --git a/readme/README.zh-CN.md b/readme/README.zh-CN.md index 35fa65eb37..0bf5a84fc2 100644 --- a/readme/README.zh-CN.md +++ b/readme/README.zh-CN.md @@ -25,7 +25,7 @@ ocx start # 代理 + 仪表盘: localhost:10100

- English · 한국어 · 简体中文 · Русский · 日本語 · 📖 完整文档 → + English · 한국어 · 简体中文 · Русский · 日本語 · Türkçe · 📖 完整文档 →

From e0d156f55c562b5bff81fa0d15f7bc7bb90d2b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:35:11 +0300 Subject: [PATCH 02/16] feat(gui): add Turkish translation for Web Dashboard UI --- gui/src/i18n/shared.ts | 9 +- gui/src/i18n/tr.ts | 1908 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1914 insertions(+), 3 deletions(-) create mode 100644 gui/src/i18n/tr.ts diff --git a/gui/src/i18n/shared.ts b/gui/src/i18n/shared.ts index 982dd9f9dd..3586b8b3ba 100644 --- a/gui/src/i18n/shared.ts +++ b/gui/src/i18n/shared.ts @@ -5,11 +5,12 @@ import { ko } from "./ko"; import { zh } from "./zh"; import { ru } from "./ru"; import { ja } from "./ja"; +import { tr } from "./tr"; -export type Locale = "en" | "de" | "ko" | "zh" | "ru" | "ja"; +export type Locale = "en" | "de" | "ko" | "zh" | "ru" | "ja" | "tr"; export type { TKey }; -export const DICTS: Record> = { en, de, ko, zh, ru, ja }; +export const DICTS: Record> = { en, de, ko, zh, ru, ja, tr }; export const LOCALES: { code: Locale; name: string; htmlLang: string }[] = [ { code: "en", name: "English", htmlLang: "en" }, @@ -18,6 +19,7 @@ export const LOCALES: { code: Locale; name: string; htmlLang: string }[] = [ { code: "zh", name: "中文", htmlLang: "zh-CN" }, { code: "ru", name: "Русский", htmlLang: "ru" }, { code: "ja", name: "日本語", htmlLang: "ja" }, + { code: "tr", name: "Türkçe", htmlLang: "tr" }, ]; const LANG_KEY = "ocx-lang"; @@ -27,7 +29,7 @@ let activeLocale: Locale | null = null; export function detectInitial(): Locale { try { const stored = localStorage.getItem(LANG_KEY); - if (stored === "en" || stored === "de" || stored === "ko" || stored === "zh" || stored === "ru" || stored === "ja") return stored; + if (stored === "en" || stored === "de" || stored === "ko" || stored === "zh" || stored === "ru" || stored === "ja" || stored === "tr") return stored; } catch { /* ignore */ } const nav = typeof navigator !== "undefined" ? navigator.language.toLowerCase() : "en"; if (nav.startsWith("de")) return "de"; @@ -35,6 +37,7 @@ export function detectInitial(): Locale { if (nav.startsWith("zh")) return "zh"; if (nav.startsWith("ru")) return "ru"; if (nav.startsWith("ja")) return "ja"; + if (nav.startsWith("tr")) return "tr"; return "en"; } diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts new file mode 100644 index 0000000000..b364ff1ed9 --- /dev/null +++ b/gui/src/i18n/tr.ts @@ -0,0 +1,1908 @@ +// Turkish — generated from en.ts. Must match TKey set (compile-checked). +import type { TKey } from "./en"; + +/** + * Turkish i18n catalog. Must match the `TKey` set (compile-checked). + */ +export const tr: Record = { + // sidebar / nav / common + "nav.dashboard": "Gösterge Paneli", + "nav.startup": "Başlatma Güvenliği", + "nav.providers": "Sağlayıcılar", + "nav.models": "Modeller", + "nav.combos": "Kombolar", + "nav.subagents": "Alt Ajanlar", + "nav.logs": "Günlükler & Hata Ayıklama", + "nav.usage": "Kullanım", + "common.github": "GitHub", + "sidebar.star": "GitHub'da Yıldız Ver", + "sidebar.starred": "GitHub'da Yıldız Verildi", + "sidebar.starUnauthenticated": "Yıldız vermek için GitHub'ı açın (gh CLI oturum açmamış)", + "sidebar.starFailed": "gh üzerinden yıldız verilemedi. Bunun yerine GitHub açılıyor.", + "sidebar.updateAvailable": "Güncelleme mevcut: {version}", + "sidebar.checkUpdate": "Güncellemeleri kontrol et", + "common.save": "Kaydet", + "common.saving": "Kaydediliyor…", + "common.cancel": "İptal", + "common.discard": "Vazgeç", + "common.close": "Kapat", + "common.ok": "Tamam", + "common.remove": "Kaldır", + "common.loading": "Yükleniyor…", + "common.retry": "Tekrar Dene", + "auth.adminTokenTitle": "OpenCodex yönetici jetonu (OPENCODEX_ADMIN_AUTH_TOKEN)", + "auth.adminAccountLabel": "Hesap", + "auth.adminTokenFieldLabel": "Yönetici jetonu", + "auth.adminTokenRejected": "Bu yönetici jetonu reddedildi. Kontrol edip tekrar deneyin.", + "auth.adminTokenUnavailable": "Yönetici jetonu doğrulanamadı. Tekrar deneyin.", + "app.logoAria": "opencodex logosu", + "app.claudeOn": "Claude AÇIK", + "app.claudeOff": "Claude KAPALI", + "theme.label": "Tema", + "theme.light": "Açık", + "theme.dark": "Koyu", + "theme.system": "Sistem", + "lang.label": "Dil", + "provider.name.commandCodeAuth": "Command Code - Auth", + "provider.name.commandCodeApi": "Command Code - API", + "provider.name.volcengine": "Volcengine Ark", + "provider.name.volcengineCodingPlan": "Volcengine Ark Coding Plan", + "provider.name.volcengineAgentPlan": "Volcengine Ark Agent Plan", + "errorBoundary.title": "Sayfa yüklenemedi", + "errorBoundary.message": "Bu bölümde bir işleme hatası oluştu. Yeniden denemek için sayfayı yenileyin.", + "errorBoundary.details": "Hata", + "errorBoundary.reload": "Yeniden Yükle", + + // routing intelligence + "routing.title": "Yönlendirme Zekası (beta)", + "routing.subtitle": "Politika profilleri, simülasyon değerlendirmesi ve kaynak destekli yönlendirme analitiği.", + "routing.loadFailed": "Yönlendirme verileri yüklenemedi", + "routing.empty": "Yapılandırılmış yönlendirme profili yok. config.json dosyasına `routingProfiles` ekleyin.", + "routing.revision": "revizyon", + "routing.detail": "Profil", + "routing.createProfile": "Profil oluştur", + "routing.dryRunError": "Simülasyon başarısız oldu (HTTP {status})", + "routing.removeConfirm": "{id} profili kaldırılsın mı?", + "routing.unknownEvidence.allow": "izin ver", + "routing.unknownEvidence.penalize": "cezalandır", + "routing.unknownEvidence.exclude": "hariç tut", + "routing.removeCandidate": "{provider}/{model} adayı kaldırılsın mı", + "routing.candidates": "Adaylar", + "routing.require": "Katı gereksinimler", + "routing.optimize": "Optimizasyon ağırlıkları", + "routing.limits": "Limitler", + "routing.unknownEvidence": "Bilinmeyen kanıt politikası", + "routing.none": "yok", + "routing.unavailable": "–", + "routing.dryRun": "Simülasyon değerlendirmesi", + "routing.dryRunContext": "İstek bağlam penceresi (jetonlar)", + "routing.dryRunTools": "İstek araç gerektiriyor", + "routing.dryRunImage": "İstek görsel girdisi gerektiriyor", + "routing.dryRunStructured": "İstek yapılandırılmış çıktı gerektiriyor", + "routing.dryRunRun": "Adayları değerlendir", + "routing.candidate": "Aday", + "routing.eligible": "Uygun", + "routing.exclusions": "Hariç Tutulanlar", + "routing.score": "Puan", + "routing.selected": "seçildi", + "routing.yes": "evet", + "routing.no": "hayır", + "routing.analytics": "Yönlendirme analitiği", + "routing.analyticsTotal": "İstekler", + "routing.analyticsSuccessRate": "Başarı", + "routing.analyticsFallbackRate": "Yedekleme", + "routing.analyticsP50": "p50", + "routing.analyticsP95": "p95", + "routing.analyticsP99": "p99", + "routing.analyticsCooldown": "Soğuma süresi hataları", + "routing.analyticsConfidence": "Güvenilirlik", + "routing.analyticsTruncated": "kısaltılmış geçmiş", + "routing.analyticsRequests": "İstekler", + "routing.analyticsEmpty": "Henüz analitik verisi yok — önce birkaç istek gönderin.", + + // startup health + "startup.title": "Başlatma güvenliği", + "startup.subtitle": "Yerel proxy yönlendirmesi bir yeniden bağlanma döngüsüne girmeden önce Codex'in yeniden başlatmanın ardından opencodex'e erişebildiğini doğrulayın.", + "startup.refresh": "Yenile", + "startup.backToDashboard": "Gösterge Paneline Dön", + "startup.loading": "Başlatma koruması kontrol ediliyor…", + "startup.error": "Başlatma koruması okunamadı.", + "startup.staleData": "Son başlatma kontrolü başarısız oldu. Aşağıdaki değerler güncel değildir ve koruma kanıtı olarak kabul edilmemelidir.", + "startup.status.native": "Yerel yönlendirme", + "startup.status.protected": "Yeniden başlatma korumalı", + "startup.status.atRisk": "Eylem gerekiyor", + "startup.summary.native": "Codex yerel proxy'ye bağımlı değildir", + "startup.summary.protected": "opencodex yeniden başlatmanın ardından kullanılabilir olacaktır", + "startup.summary.atRisk": "Codex yeniden başlatmanın ardından model erişimini kaybedebilir", + "startup.riskDetail": "Codex yerel proxy'ye sabitlenmiş, ancak kalıcı bir servis veya sağlıklı başlatıcı shim bunu tekrar başlatmayacak.", + "startup.riskDetailCustomLocal": "Codex özel bir yerel ağ geçidine işaret ediyor. opencodex bu ağ geçidinin yeniden başlatma yaşam döngüsünü yönetemez veya doğrulayamaz.", + "startup.riskDetailWindowsShim": "Başlatıcı shim desteklenen CLI betiklerini korur, ancak Codex Desktop ve doğrudan codex.exe başlatmaları Windows'ta bunu atlayabilir.", + "startup.safeDetail": "Mevcut yönlendirme ve başlatma mekanizması tutarlıdır. Yeniden başlatmanın ardından manuel `ocx start` komutuna gerek yoktur.", + "startup.routing": "Codex yönlendirmesi", + "startup.routing.proxy": "Yerel proxy", + "startup.routing.native": "Yerel OpenAI", + "startup.routing.customLocal": "Özel yerel ağ geçidi", + "startup.routing.customRemote": "Özel uzak ağ geçidi", + "startup.routing.unknown": "Bilinmeyen veya geçersiz yönlendirme", + "startup.restartProtection": "Yeniden başlatma koruması", + "startup.preference": "İsteğe bağlı başlatma", + "startup.enabled": "Etkin", + "startup.disabled": "Devre dışı", + "startup.protection.service": "Arka plan servisi", + "startup.protection.shim": "Başlatıcı shim", + "startup.protection.none": "Yüklü değil", + "startup.details": "Koruma detayları", + "startup.service": "Arka plan servisi", + "startup.serviceHint": "Oturum açmada başlar ve çökmenin ardından proxy'yi yeniden başlatır.", + "startup.installed": "Yüklü", + "startup.notInstalled": "Yüklü değil", + "startup.unsupported": "Desteklenmiyor", + "startup.shim": "Codex başlatıcı shim", + "startup.shimHint": "Desteklenen bir Codex betik başlatıcısı başladığında `ocx ensure` çalıştırır.", + "startup.healthy": "Sağlıklı", + "startup.cliOnly": "Yalnızca CLI", + "startup.stale": "Eski", + "startup.viable": "Hazır", + "startup.unhealthy": "Yüklü ama sağlıksız", + "startup.conflict": "Servis çakışması", + "startup.installedDisabled": "Yüklü ama devre dışı", + "startup.install": "Yükle", + "startup.installing": "Yükleniyor…", + "startup.repair": "Onar", + "startup.repairing": "Onarılıyor…", + "startup.serviceInstalled": "Arka plan servisi başarıyla yüklendi.", + "startup.serviceRepaired": "Arka plan servisi başarıyla onarıldı.", + "startup.shimInstalled": "Codex başlatıcı shim başarıyla yüklendi.", + "startup.shimRepaired": "Codex başlatıcı shim başarıyla onarıldı.", + "startup.installFailed": "Yükleme başarısız oldu:", + "startup.tray.title": "Windows sistem tepsisi", + "startup.tray.hint": "Tek tıkla proxy başlatma, durdurma, yeniden başlatma, panel ve durum kontrolleri için oturum tepsisi simgesi yükleyin.", + "startup.tray.login": "Windows oturum açılışında tepsiyi başlat", + "startup.tray.notProtection": "Tepsi bir denetleyicidir, yeniden başlatma koruması değildir. İnsansız proxy kurtarma için hâlâ geçerli bir arka plan servisi gereklidir.", + "startup.tray.running": "Çalışıyor", + "startup.tray.stopped": "Yüklü, gizli", + "startup.tray.stale": "Onarım gerekiyor", + "startup.tray.notInstalled": "Yüklü değil", + "startup.tray.loading": "Kontrol ediliyor…", + "startup.tray.unavailable": "Durum kullanılamıyor", + "startup.tray.install": "Yükle ve tepsiyi göster", + "startup.tray.start": "Tepsi simgesini göster", + "startup.tray.stop": "Tepsi simgesinden çık", + "startup.tray.uninstall": "Oturum tepsisini kaldır", + "startup.tray.error": "Windows tepsi eylemi başarısız oldu. Detaylar için `ocx tray status` kontrol edin.", + "startup.recovery": "Onarım seçenekleri", + "startup.recoveryHint": "Yukarıdaki tek tıkla yükleyicileri kullanın veya manuel onarım için komutu kopyalayın. Codex Desktop ve Windows yürütülebilir dosyaları için arka plan servisi önerilir.", + "startup.command.service": "Önerilen: kalıcı arka plan servisi", + "startup.command.shim": "Alternatif: CLI başlatıcı shim", + "startup.command.native": "Güvenli mod: yerel Codex yönlendirmesini geri yükle", + "startup.copy": "Kopyala", + "startup.copied": "Kopyalandı", + "startup.recommended": "Önerilen onarım: {cmd}", + "startup.navRisk": "Başlatma koruması dikkat gerektiriyor", + "startup.codexRuntime.clampHidden": "OpenCodex, Codex {version} kullandığı için bazı akıl yürütme çabası seçenekleri gizlendi.", + "startup.codexRuntime.clampHiddenWithEfforts": "OpenCodex, Codex {version} kullandığı için bazı akıl yürütme çabası seçenekleri gizlendi (kaldırılanlar: {efforts}).", + "startup.codexRuntime.olderBinary": "OpenCodex eski bir Codex ikili dosyası ({version}) kullanıyor. Daha yeni bir kurulum mevcut.", + + // dashboard + "dash.subtitle": "Yerel opencodex proxy'sinin, sağlayıcılarının ve Codex'e yönlendirilen modellerin canlı durumu.", + "dash.workspace.overview": "Genel Bakış", + "dash.workspace.sections": "Bölümler", + "dash.status": "Durum", + "dash.online": "Çevrimiçi", + "dash.offline": "Çevrimdışı", + "dash.version": "Sürüm", + "dash.uptime": "Çalışma Süresi", + "dash.providers": "Sağlayıcılar", + "dash.tokens30d": "Jetonlar (30 gün)", + "dash.coverage": "%{pct} kapsam", + "dash.mem.title": "Bellek izlenebilirliği", + "dash.mem.hint": "Salt okunur çalışma zamanı tanılamaları. Gözlemlenen bellek max(RSS, harici, ArrayBuffers) değeridir.", + "dash.mem.rss": "Yerleşik küme (RSS)", + "dash.mem.jsHeap": "Kullanımdaki JS yığını", + "dash.mem.jsHeapArena": "arena {total}", + "dash.mem.pressure": "Uyarı eşiğine göre", + "dash.mem.pressureOf": "Eşiğin %{pct}'si", + "dash.mem.pressureUnknown": "Bildirilen eşik yok", + "dash.mem.jscHeap": "JSC yığını", + "dash.mem.external": "Harici", + "dash.mem.arrayBuffers": "ArrayBuffers", + "dash.mem.observed": "Gözlemlenen", + "dash.mem.runtime": "Çalışma zamanı sayaçları", + "dash.mem.growth": "Saatlik gözlemlenen kayma", + "dash.mem.perHour": "/saat", + "dash.mem.store": "Devamlılık deposu", + "dash.mem.storeHint": "Proxy previous_response_id önbelleği.", + "dash.mem.storeEntries": "Girdiler", + "dash.mem.storeTotal": "Toplam", + "dash.mem.storeLargest": "En büyük", + "dash.mem.storeOldest": "En eski", + "dash.mem.threshold": "Uyarı eşiği", + "dash.mem.lastWarn": "Son uyarı", + "dash.mem.never": "Hiçbir zaman", + "dash.mem.details": "Detaylar", + "dash.mem.unavailable": "Bellek tanılaması kullanılamıyor (eski proxy).", + "dash.mem.inFlight": "İşlemdeki istekler", + "dash.mem.restart": "Boşalt & yeniden başlat", + "dash.mem.restartConfirm": "{count} işlemdeki isteğin tamamlanmasını bekleyin, ardından yeniden başlatın ({seconds} saniyeye kadar).", + "dash.mem.draining": "{count} istek boşaltılıyor… tamamlandığında yeniden başlatılacak", + "dash.mem.reconnecting": "Proxy yeniden başlatılıyor… yeniden bağlanması bekleniyor", + "dash.mem.restartFailed": "Boşaltma ve yeniden başlatma başarısız oldu. Proxy'nin çalıştığını kontrol edin.", + "dash.mem.restartNoSupervisor": "Yeniden başlatma koruması algılanmadı.", + "dash.activeProviders": "Aktif sağlayıcılar", + "dash.noProviders": "Yapılandırılmış sağlayıcı yok. {cmd} komutunu çalıştırın.", + "dash.col.name": "İsim", + "dash.col.adapter": "Adaptör", + "dash.col.baseUrl": "Taban URL", + "dash.col.model": "Model", + "dash.modelsNoResults": "Aramanızla eşleşen model bulunamadı.", + "dash.availableModels": "Kullanılabilir modeller", + "dash.noModels": "Model bulunamadı. Sağlayıcı API anahtarlarını kontrol edin.", + "dash.cannotConnect": "Proxy'ye bağlanılamıyor. Çalışıyor mu?", + "dash.runStart": "Proxy'yi başlatmak için {cmd} çalıştırın.", + "dash.stop": "Proxy'yi Durdur", + "dash.stopConfirm": "Proxy durdurulsun ve yerel Codex geri yüklensin mi?", + "dash.stopFailed": "Proxy durdurulamadı (HTTP {status}).", + "dash.stopping": "Durduruluyor…", + "dash.codexAutoStart": "opencodex'i Codex ile başlat", + "dash.codexAutoStartHint": "Yüklü bir shim'in ocx ensure çalıştırmasına izin verir.", + "dash.searchModel": "Arama yan araç modeli", + "dash.searchModelHint": "OpenAI dışı yönlendirilen modellerde web_search için kullanılan model. ChatGPT girişi gerektirir.", + "dash.searchReasoning": "Arama akıl yürütme çabası", + "dash.visionModel": "Görsel yan araç modeli", + "dash.visionModelHint": "Salt metin yönlendirilen modeller için görselleri tanımlamakta kullanılan model. ChatGPT girişi gerektirir.", + "dash.webSearchSidecar": "Web arama yan aracı (sidecar)", + "dash.webSearchSidecarHint": "Yönlendirilen modellerde web araması için kullanılan arka ucu ve modeli seçin.", + "dash.visionSidecar": "Görsel yan aracı (sidecar)", + "dash.visionSidecarHint": "Salt metin modeller için görselleri tanımlamakta kullanılan arka ucu ve modeli seçin.", + "dash.shadowCallIntercept": "Gölge Çağrı Yakalama", + "dash.shadowCallInterceptHint": "Codex App'in arka plan yardımcı çağrılarını ({models}) başlık oluşturma ve commit mesajları için yakalar ve seçtiğiniz modele yönlendirir.", + "dash.shadowCallWarning": "⚠ Etkinleştirildiğinde, {models} için olan TÜM istekler seçilen modelle değiştirilecektir.", + "dash.shadowCallOriginal": "Orijinal", + "dash.shadowCallModel": "Yedek model", + "dash.shadowCallTooltip": "Codex App arka planda başlık ve commit mesajı oluşturmak için yardımcı çağrılar yapar. opencodex bu istekleri seçtiğiniz modele yönlendirir.", + "models.shadowCallIntercept": "Gölge Çağrı Yakalama", + "models.shadowCallInterceptHint": "Codex App'in başlıklar ve commit mesajları için yaptığı arka plan çağrılarını ({models}) yakalar ve seçtiğiniz modele yönlendirir.", + "dash.sidecarBackend": "Arka uç", + "dash.sidecarModel": "Model", + "dash.backendAuto": "Otomatik", + "dash.backendOpenAI": "OpenAI", + "dash.backendAnthropic": "Anthropic", + "dash.sidecarSaved": "Yan araç ayarları kaydedildi. Sonraki istekte uygulanacak.", + "dash.sidecarSaveFailed": "Yan araç ayarları kaydedilemedi.", + "dash.injectionLabel": "Alt ajan devri", + "dash.injectionHint": "Codex'in alt ajan işlerini devredeceği modeli seçin.", + "dash.injectionManage": "Ayarları aç", + "dash.syncCodexSubagentDefaults": "Ayrıca Codex varsayılanı olarak kaydet", + "dash.syncCodexSubagentDefaultsHint": "Açık olduğunda, yukarıdaki seçim Codex'in kendi yapılandırmasına yazılır.", + "dash.multiAgentGuidance": "Codex'e işi nasıl böleceğini söyle", + "dash.multiAgentGuidanceHint": "Codex'e işleri alt ajanlara nasıl devredeceğini söyleyen kısa bir not gönderir.", + "dash.injectionNone": "Yok", + "dash.injectionEffortLabel": "Akıl yürütme çabası", + "dash.injectionEffortNone": "Model varsayılanı", + "dash.effortCapLabel": "V2 ultra çaba limiti", + "dash.subagentEffortCapLabel": "V2 alt ajan çaba limiti", + "dash.effortCapHelp": "V2 ultra modu için akıl yürütme çabasını sınırlar.", + "dash.effortCapNone": "Limit yok", + "dash.maintenance": "Bakım", + "dash.maintenanceHint": "Codex'in model kataloğunu yenileyin veya daha yeni bir opencodex sürümü yükleyin.", + "dash.syncModels": "Modelleri senkronize et", + "dash.syncModelsHint": "Bağladığınız sağlayıcılardan Codex'in model kataloğunu yeniden yazın.", + "dash.syncRun": "Şimdi senkronize et", + "dash.syncing": "Senkronize ediliyor…", + "dash.syncOk": "Senkronizasyon tamamlandı. {count} model eklendi.", + "dash.syncStaleHint": "Codex hâlâ eski bir liste gösteriyorsa uygulama sunucusunu yeniden başlatın ({cmd}).", + "dash.syncFailed": "Senkronizasyon başarısız oldu: {error}", + "dash.projectConfigTitle": "Proje Codex konfigürasyonu OpenCodex'i atlıyor", + "dash.projectConfigHint": "Bu depoya özel ayarlar OpenCodex proxy'sini geçersiz kılar.", + "dash.checkUpdate": "Güncellemeyi kontrol et", + "dash.updateTitle": "opencodex'i güncelle", + "dash.updateDesc": "Seçilen kanal için npm'i kontrol edin.", + "dash.updateChannel": "Kanal", + "dash.updateChecking": "Güncellemeler kontrol ediliyor…", + "dash.updateInstalled": "Yüklü", + "dash.updateLatest": "En son", + "dash.updateAvailable": "Güncelleme mevcut", + "dash.updateCurrent": "Güncel", + "dash.updateCommand": "Komut", + "dash.updateSource": "Bu bir kaynak kod kopyasıdır. Terminalden güncelleyin.", + "dash.updateUnavailable": "npm'den en son sürüm okunamadı.", + "dash.updateRetry": "Tekrar dene", + "dash.updateRecheck": "Yeniden kontrol et", + "dash.updateCannotAuto": "Tek tıkla güncelleme kullanılamıyor ({reason}).", + "dash.updateReason.source_checkout": "kaynak kod kopyası", + "dash.updateReason.latest_unavailable": "npm sunucusuna ulaşılamıyor", + "dash.updateReason.already_latest": "zaten en son sürümde", + "dash.updateReason.unknown": "güncelleme kullanılamıyor", + "dash.updateRestart": "Güncellemeden sonra yeniden başlat", + "dash.updateRestartHint": "Önerilir. Proxy yeniden başlayana kadar mevcut GUI eski kodu çalıştırmaya devam eder.", + "dash.runUpdate": "Güncelle", + "dash.updateReconnecting": "Yeniden başlatılan proxy bekleniyor…", + "dash.updateStatus.running": "opencodex güncelleniyor.", + "dash.updateStatus.restarting": "Güncelleme yüklendi. Proxy yeniden başlatılıyor.", + "dash.updateStatus.succeeded": "Güncelleme tamamlandı.", + "dash.updateVersionTransition": "{currentVersion} -> {latestVersion}.", + "dash.updateStatus.failed": "Güncelleme başarısız oldu.", + + // providers + "prov.subtitle": "opencodex'in Codex'e yönlendirdiği sağlayıcıları yapılandırın.", + "prov.add": "Sağlayıcı Ekle", + "prov.editJson": "JSON Düzenle", + "prov.accountLogin": "Hesap girişi", + "prov.noOauth": "Kullanılabilir OAuth sağlayıcısı yok.", + "prov.loggedIn": "giriş yapıldı", + "prov.notLoggedIn": "giriş yapılmadı", + "prov.logout": "Çıkış Yap", + "prov.login": "Giriş Yap", + "prov.loginWith": "{provider} ile Giriş Yap", + "prov.waitingBrowser": "Tarayıcı bekleniyor…", + "prov.didntOpen": "Açılmadı mı? Buraya tıklayın", + "prov.copyLink": "Bağlantıyı kopyala", + "prov.linkCopied": "Kopyalandı", + "prov.linkCopyUnavailable": "Pano kullanılamıyor", + "prov.deviceCode": "Cihaz kodu", + "prov.copyCode": "Kodu kopyala", + "prov.codeCopied": "Kod kopyalandı", + "prov.editAlias": "Takma adı düzenle", + "prov.aliasPrompt": "Görüntülenen ad (temizlemek için boş bırakın)", + "prov.aliasSaved": "Takma ad kaydedildi", + "prov.aliasSaveFailed": "Takma ad kaydedilemedi", + "prov.accountId": "ID", + "prov.pasteRedirect": "Yönlendirme URL'sini veya kodu yapıştırın", + "prov.pasteRedirectHint": "Tarayıcı localhost hatası gösterirse adres çubuğundaki URL'yi kopyalayıp buraya yapıştırın.", + "prov.pasteSubmit": "Gönder", + "prov.pasteSubmitting": "Gönderiliyor…", + "prov.pasteOk": "Kod gönderildi — giriş tamamlanıyor…", + "prov.pasteFail": "Kod gönderilemedi: {error}", + "prov.port": "Port", + "prov.default": "Varsayılan", + "prov.loadingConfig": "Yükleniyor…", + "prov.saved": "Kaydedildi! Uygulamak için proxy'yi yeniden başlatın.", + "prov.loadConfigFail": "Konfigürasyon yüklenemedi", + "prov.invalidJson": "Geçersiz JSON", + "prov.saveFailed": "Kaydetme başarısız", + "prov.loginFailStart": "{provider} girişi başlatılamadı", + "prov.loginError": "{provider} giriş hatası: {error}", + "prov.loginRequestFail": "{provider} giriş isteği başarısız oldu", + "prov.loginCancelled": "{provider} girişi iptal edildi", + "prov.loginTimeout": "{provider} girişi zaman aşımına uğradı.", + "prov.loginOk": "{provider} hesabına giriş yapıldı.", + "oauthTos.highTitle": "{provider}: abonelik OAuth riski", + "oauthTos.elevatedTitle": "{provider}: gayri resmi OAuth köprüsü", + "oauthTos.anthropicBody": "Claude abonelik OAuth jetonlarını OpenCodex gibi bir proxy üzerinden kullanmak hesabın kısıtlanmasına yol açabilir.", + "oauthTos.highBody": "OpenCodex {provider} bağlantısını üçüncü taraf bir OAuth yolu üzerinden sağlar.", + "oauthTos.elevatedBody": "OpenCodex {provider} bağlantısını resmi olmayan bir yoldan kurar.", + "oauthTos.saferPath": "Daha güvenli seçenek: OpenCodex'te bir API anahtarı yapılandırın.", + "oauthTos.acknowledge": "Riski anlıyorum ve OAuth ile devam etmek istiyorum.", + "oauthTos.continue": "OAuth ile devam et", + "prov.logoutOk": "{provider} çıkışı yapıldı.", + "prov.logoutFail": "{provider} çıkışı yapılamadı.", + "prov.removed": "\"{name}\" kaldırıldı.", + "prov.removedDefault": "\"{name}\" kaldırıldı. Varsayılan sağlayıcı artık \"{defaultProvider}\".", + "prov.removeFail": "\"{name}\" kaldırılamadı.", + "prov.removeLastProvider": "Başka etkin sağlayıcı yokken bu sağlayıcıyı kaldıramazsınız.", + "prov.removeHasDependentCombos": "Önce bağımlı komboları kaldırın veya güncelleyin: {combos}.", + "prov.setDefault": "Varsayılan olarak ayarla", + "prov.setDefaultSuccess": "\"{name}\" artık varsayılan sağlayıcı.", + "prov.setDefaultFail": "\"{name}\" varsayılan yapılamadı.", + "prov.defaultDisabled": "Varsayılan yapmadan önce bu sağlayıcıyı etkinleştirin.", + "prov.updateFail": "Sağlayıcı güncellenemedi.", + "prov.networkError": "Ağ hatası. Proxy'nin çalıştığını kontrol edin.", + "prov.added": "\"{name}\" eklendi.", + "prov.removeConfirm": "\"{name}\" sağlayıcısı kaldırılsın mı?", + "prov.hasApiKey": "API anahtarı yapılandırıldı", + "prov.hasHeaders": "özel başlıklar yapılandırıldı", + "prov.accounts": "Hesaplar ({n})", + "prov.accountsAria": "{name} hesaplarını aç/kapat", + "prov.accountActive": "Aktif", + "prov.accountReauth": "Tekrar giriş yap", + "prov.reauthenticate": "Yeniden doğrula", + "prov.reauthAccountMissing": "Seçilen hesap bulunamadı", + "prov.reauthIdentityMismatch": "Giriş yapılan hesap seçilen hesapla eşleşmedi", + "prov.accountAdd": "Hesap ekle", + "prov.accountNoLabel": "hesap {id}", + "prov.accountSwitchTitle": "Bu hesabı kullan", + "prov.accountSwitched": "{email} hesabına geçildi.", + "prov.accountSwitchFail": "Hesap değiştirilemedi", + "prov.accountRemoved": "{email} kaldırıldı.", + "prov.accountRemoveFail": "{email} kaldırılamadı.", + "prov.accountRemoveAria": "{email} hesabını kaldır", + "prov.accountRemoveConfirm": "{email} hesabı kaldırılsın mı?", + "prov.keyAdd": "API anahtarı ekle", + "prov.keyAdded": "{name} için API anahtarı eklendi.", + "prov.keyAddFail": "API anahtarı eklenemedi", + "prov.keyPlaceholder": "API anahtarını yapıştırın", + "prov.keySwitchTitle": "Bu anahtarı kullan", + "prov.keySwitched": "{key} anahtarına geçildi.", + "prov.keySwitchFail": "Anahtar değiştirilemedi", + "prov.keyRemoved": "{key} anahtarı kaldırıldı.", + "prov.keyRemoveAria": "{key} anahtarını kaldır", + "prov.keyRemoveConfirm": "{key} API anahtarı silinsin mi?", + "prov.activeBadge": "Aktif", + "prov.disabledBadge": "Devre Dışı", + "prov.defaultBadge": "Varsayılan", + "prov.enable": "Etkinleştir", + "prov.disable": "Devre Dışı Bırak", + "prov.enabled": "\"{name}\" etkinleştirildi.", + "prov.disabled": "\"{name}\" devre dışı bırakıldı.", + "prov.enableFail": "\"{name}\" etkinleştirilemedi.", + "prov.disableFail": "\"{name}\" devre dışı bırakılamadı.", + "prov.enableAria": "{name} sağlayıcısını etkinleştir", + "prov.disableAria": "{name} sağlayıcısını devre dışı bırak", + "prov.defaultCannotDisable": "Varsayılan sağlayıcı devre dışı bırakılamaz", + "prov.openaiAccountMode": "Codex hesap modu", + "prov.openaiModePool": "Havuz (Pool)", + "prov.openaiModeDirect": "Doğrudan (Direct)", + "prov.openaiPoolDesc": "Varsayılan. Ana girişi ve eklenen hesapları havuzda döndürür.", + "prov.openaiDirectDesc": "Yalnızca ana Codex girişini kullanır.", + "prov.openaiModeSaved": "OpenAI hesap modu {mode} olarak değiştirildi.", + "prov.openaiModeSaveFailed": "OpenAI hesap modu değiştirilemedi.", + "prov.openaiApiDesc": "OpenAI API anahtarı kullanır.", + "prov.manageCodexAccounts": "Codex hesaplarını yönet", + "prov.openaiApiMissing": "API anahtarı gerekli", + "prov.openaiApiSetup": "API anahtarı ayarla", + + // models + "models.tab.catalog": "Modeller", + "models.tab.combos": "Kombolar", + "models.tab.routing": "Yönlendirme (beta)", + "models.tabsLabel": "Model yüzeyleri", + "models.subtitle.combos": "Tek bir kimlik olarak yanıt veren sıralı model grupları.", + "models.subtitle.routing": "Politika profilleri ve simülasyon değerlendirmesi.", + "models.subtitle": "Codex'in göreceği modelleri açıp kapatın.", + "models.nativeGroupLabel": "Yerel OpenAI", + "models.nativeHint": "Doğrudan geçiş modelleri Sağlayıcılar bölümünde seçilen Havuz veya Doğrudan seçeneğini kullanır.", + "models.active": "{active}/{total} görünür", + "models.workspace.providers": "Sağlayıcılar", + "models.workspace.allProviders": "Tüm sağlayıcılar", + "models.workspace.mainAria": "Model detayları", + "models.allOn": "Tümünü aç", + "models.allOff": "Tümünü kapat", + "models.cap350k": "350k Sınırı", + "models.capApplied": "Bağlam sınırı uygulandı.", + "models.capSaveFailed": "Bağlam sınırı kaydedilemedi", + "models.contextCapped": "350k sınırı", + "models.contextCapLabel": "Bağlam sınırı", + "models.v2Label": "Alt Ajan", + "models.v2DocsLink": "v1 / v2 nedir?", + "models.v2Mode_v1": "v1", + "models.v2Mode_default": "taban", + "models.v2Mode_v2": "v2", + "models.v2ModeDesc_v1": "Tüm modeller → v1 yüzeyi", + "models.v2ModeDesc_default": "Yukarı akış varsayılanları", + "models.v2ModeDesc_v2": "Tüm modeller → v2 yüzeyi", + "models.v2Help": "Tüm modeller için çoklu ajan yüzeyini kontrol eder.", + "dash.multiAgent": "Alt Ajan", + "models.v2Conflict": "[agents] max_threads ayarlanmış — config.toml dosyasından kaldırın", + "models.v2Applied": "Alt ajan modu güncellendi", + "models.v2ThreadsLabel": "Maksimum iş parçacığı", + "models.v2ThreadsDefault": "varsayılan (4)", + "models.v2ThreadsApplied": "İş parçacığı limiti güncellendi", + "models.v2ThreadsInvalid": "İş parçacığı limiti >= 1 tamsayı olmalıdır", + "models.v2ThreadsApply": "Uygula", + "models.capValue": "Sınır {value}", + "models.contextSettings": "Bağlam pencereleri", + "models.contextSettingsTitle": "Bağlam pencereleri — {provider}", + "models.contextDefault": "Sağlayıcı varsayılanı", + "models.contextModel": "Model", + "models.contextModelOverride": "Model geçersiz kılma", + "models.contextHint": "Yukarı akış meta verileri eksik olduğunda kullanılır.", + "models.contextAutomatic": "Otomatik keşif", + "models.contextSaved": "Bağlam pencereleri güncellendi.", + "models.contextUnchanged": "Kaydedilecek bağlam penceresi değişikliği yok.", + "models.contextSaveFailed": "Bağlam pencereleri kaydedilemedi", + "models.contextInvalid": "Bağlam pencereleri pozitif tam sayılar olmalıdır", + "models.contextCappedValue": "{value} sınırı", + "models.setAll": "Tümünü ayarla", + "models.setAllHint": "{value} bağlam sınırını her yönlendirilen sağlayıcıya uygulayın.", + "models.collapseAll": "Tümünü daralt", + "models.expandAll": "Tümünü genişlet", + "models.orderHint": "Seçici sırası: Alt ajan seçimleri → kalan modeller.", + "models.custom": "Özel…", + "models.customApply": "Uygula", + "models.customPlaceholder": "Jetonlar (örn. 420000)", + "models.customAdd": "Özel model ekle", + "models.customAddTitle": "Özel model ekle — {provider}", + "models.customEditTitle": "Özel modeli düzenle — {provider}", + "models.customAdded": "Özel model eklendi", + "models.customUpdated": "Özel model güncellendi", + "models.customDeleted": "Özel model silindi", + "models.customSaveFailed": "Özel model kaydedilemedi", + "models.customSaving": "Kaydediliyor…", + "models.customAddBtn": "Ekle", + "models.customEditBtn": "Güncelle", + "models.customEdit": "Düzenle", + "models.customDelete": "Sil", + "models.customDeleteConfirm": "{name} modeli silinsin mi?", + "models.customBadge": "Özel", + "models.customSummary": "{count} özel", + "models.customFieldModelId": "Model ID", + "models.customFieldModelIdPlaceholder": "örn. qwen4-max-preview", + "models.customFieldDisplayName": "Görüntülenen ad (isteğe bağlı)", + "models.customFieldDisplayNamePlaceholder": "örn. Qwen 4 Max Preview", + "models.customFieldContext": "Bağlam penceresi", + "models.customFieldModalities": "Girdi türleri", + "models.tipProvider": "Sağlayıcı", + "models.tipContext": "Bağlam", + "models.tipModalities": "Girdi Türleri", + "models.tipStatus": "Durum", + "models.tipActive": "Aktif", + "models.tipDisabled": "Devre Dışı", + "models.applied": "Uygulandı.", + "models.saveFailed": "Kaydetme başarısız", + "models.networkError": "Ağ hatası — proxy çalışıyor mu?", + "models.loadFail": "Modeller yüklenemedi — proxy çalışıyor mu?", + "models.noRouted": "Yönlendirilen model yok", + "models.noRoutedHint": "Önce bir sağlayıcıya giriş yapın veya ekleyin.", + "models.emptyDiscovery": "Keşfedilen model yok.", + "models.emptyDiscoveryDisabled": "Canlı model keşfi kapalı.", + "models.discoveryFailedBadge": "Keşif başarısız", + "models.discoveryFailedHttp": "Model keşfi başarısız oldu (HTTP {status}).", + "models.discoveryFailedBlocked": "Model keşfi engellendi.", + "models.discoveryFailedInvalidResponse": "Model keşfi geçersiz bir yanıt döndürdü.", + "models.discoveryFailedNetwork": "Model keşfi ağ hatası nedeniyle başarısız oldu.", + "models.discoveryFailedProvider": "Sağlayıcı bir model keşfi hatası bildirdi.", + "models.discoveryFailedGeneric": "Model keşfi başarısız oldu.", + "models.openProviderSettings": "Sağlayıcı ayarlarını aç", + "models.loading": "Yükleniyor…", + "models.search": "Modellerde ara…", + "models.showMore": "{n} tane daha göster", + "models.allowlistLabel": "Sadece seçilenler", + "models.allowlistHint": "Sadece işaretli modeller kataloğa gönderilir.", + "models.selectedCount": "{n} seçildi", + + // subagents + "sub.subtitle": "Codex'in alt ajanlarında öne çıkacak modelleri seçin.", + "sub.featured": "Öne Çıkarılanlar", + "sub.orderHint": "Burada gösterilen sıra Codex model seçicisinin en üstünü belirler.", + "sub.noneSelected": "Hiçbiri seçilmedi — aşağıdaki listeden seçin.", + "sub.models": "Modeller", + "sub.search": "Modellerde ara…", + "sub.settings": "Ayarlar", + "sub.sections": "Alt ajan bölümleri", + "sub.delegation.model": "İlk çağrılacak model", + "sub.delegation.modelHint": "Codex'in iş devrederken ilk ulaştığı model.", + "sub.noModels": "Model yok — önce bir sağlayıcı ekleyin.", + "sub.saved": "{n} model kaydedildi.", + "sub.saveFailed": "Kaydetme başarısız", + "sub.networkError": "Ağ hatası — proxy çalışıyor mu?", + "sub.loadFail": "Modeller yüklenemedi", + "sub.loading": "Yükleniyor…", + "sub.moveUp": "{m} modelini yukarı taşı", + "sub.moveDown": "{m} modelini aşağı taşı", + "sub.removeAria": "{m} modelini kaldır", + "sub.workspace.addToFeatured": "{m} modelini öne çıkarılanlara ekle", + "sub.workspace.allModels": "Tüm modeller", + "sub.workspace.featuredFull": "Öne çıkarılanlar listesi dolu (maksimum 5)", + "sub.workspace.mainAria": "Alt ajan model detayları", + "sub.workspace.notFeatured": "Öne çıkarılmadı", + "sub.workspace.priority": "Öncelik", + "sub.workspace.removeFromFeatured": "{m} modelini öne çıkarılanlardan kaldır", + "sub.workspace.selectModel": "Bir model seçin", + "sub.workspace.selectModelDesc": "Detayları görmek için listeden bir model seçin.", + "sub.workspace.selector": "Genel seçici", + + // logs + "logs.title": "İstek Günlükleri", + "logs.tabLogs": "Günlükler", + "logs.tabDebug": "Hata Ayıklama", + "logs.subtitle": "Proxy üzerinden yönlendirilen son istekler.", + "logs.autoRefresh": "Otomatik yenile", + "logs.noRequests": "Henüz istek yok.", + "logs.loadError": "İstek günlükleri yüklenemedi.", + "logs.filter.surface.label": "Yüzey", + "logs.filter.surface.all": "Tümü", + "logs.filter.surface.claude": "Claude", + "logs.filter.surface.codex": "Codex", + "logs.filter.surface.grok": "Grok", + "logs.filter.conversation.label": "Sohbet", + "logs.filter.conversation.placeholder": "Sohbet ID'sini yapıştırın", + "logs.filter.conversation.clear": "Temizle", + "logs.filter.conversation.apply": "Günlükleri filtrele", + "logs.conversation.totals": "{requests} istek · {tokens} jeton · {cost}", + "logs.conversation.scope": "Toplamlar yalnızca yüklü günlükleri kapsar.", + "logs.conversation.excluded": "({unpriced} fiyatlandırılmamış hariç)", + "logs.detail.conversation": "Sohbet", + "logs.badge.claude": "Claude", + "logs.badge.grok": "Grok", + "logs.col.time": "Zaman", + "logs.col.request": "İstek", + "logs.col.model": "Model", + "logs.col.effort": "Çaba", + "logs.col.provider": "Sağlayıcı", + "logs.col.status": "Durum", + "logs.col.tokens": "Jetonlar", + "logs.col.tokPerSec": "jeton/sn", + "logs.col.estimatedCost": "~$", + "logs.metric.tokPerSecTitle": "Çıktı jetonu / saniye", + "logs.metric.estimatedCostTitle": "Tahmini API liste fiyatı", + "usage.cost.total": "API liste fiyatı eşdeğeri", + "usage.cost.disclaimer": "Fatura makbuzu değildir.", + "usage.cost.unpricedNote": "{count} istek hariç tutuldu", + "logs.detail.section.basic": "Temel bilgiler", + "logs.detail.route.section": "Yönlendirme kararı", + "logs.detail.route.kind": "Yönlendirme türü", + "logs.detail.route.profile": "Profil", + "logs.detail.route.selected": "Seçilen", + "logs.detail.route.candidates": "Adaylar", + "logs.detail.route.unknown": "Kayıtlı yönlendirme izi yok.", + "logs.detail.section.performance": "Performans", + "logs.detail.section.cost": "Tahmini maliyet", + "logs.detail.section.attempts": "Kombo denemeleri", + "logs.detail.section.usage": "Ham kullanım", + "logs.detail.ttft": "TTFT", + "logs.detail.costTotal": "Liste fiyatı eşdeğeri", + "logs.detail.totalTokens": "Toplam jeton", + "logs.detail.matchedKey": "Eşleşen anahtar", + "logs.detail.priceSource": "Fiyat kaynağı", + "logs.detail.unavailableReason": "Kullanılamama nedeni", + "logs.detail.copyRequestId": "İstek ID kopyala", + "logs.detail.copied": "Kopyalandı", + "logs.detail.source.jawcode": "katalog", + "logs.detail.source.expected": "Beklenen fiyat", + "logs.detail.verification.verified": "Doğrulandı", + "logs.detail.verification.derived": "Taban modelden türetildi", + "logs.detail.attempt.target": "Sağlayıcı / model", + "logs.detail.attempt.reason": "Sonuç / neden", + "logs.detail.attempt.completed": "Tamamlandı", + "logs.detail.attempt.e2eNote": "Süreç bilgisi", + "logs.detail.attempt.recovery.transient5xx": "Geçici 5xx", + "logs.detail.attempt.recovery.connectionReset": "Bağlantı sıfırlandı", + "logs.detail.attempt.recovery.oauth401": "OAuth yeniden doğrulaması", + "logs.detail.attempt.recovery.key429": "Anahtar oranı kısıtlandı (429)", + "logs.detail.attempt.recovery.rateLimit429": "Oran kısıtlandı (429)", + "logs.detail.attempt.recovery.anthropicOauth429": "Anthropic OAuth kısıtlandı (429)", + "logs.detail.attempt.recovery.image413": "Görsel boyutu çok büyük (413)", + "logs.detail.attempt.recovery.unknown": "Bilinmeyen kurtarma nedeni", + "logs.detail.reason.usage_missing": "Kullanım bildirilmedi.", + "logs.detail.reason.usage_unsupported": "Bu sağlayıcı kullanım bildirmeyebilir.", + "logs.detail.reason.output_missing": "Çıktı jeton sayısı bildirilmedi.", + "logs.detail.reason.invalid_duration": "İstek süresi geçersiz.", + "logs.detail.reason.price_unmatched": "Eşleşen fiyat bulunamadı.", + "logs.detail.reason.invalid_cache_breakdown": "Önbellek detayları çakışıyor.", + "logs.detail.reason.invalid_usage": "Kullanım geçersiz bir jeton değeri içeriyor.", + "logs.detail.reason.combo_attempt_unavailable": "Kombo denemesi fiyatlandırılamadı.", + "logs.detail.estimate.usage_estimated": "Sağlayıcı kullanımı tahminidir.", + "logs.detail.estimate.cache_detail_missing": "Önbellek detayları eksik.", + "logs.detail.estimate.expected_price_overlay": "Doğrulanmış liste fiyatı kullanıldı.", + "logs.col.error": "Hata", + "logs.col.upstreamReason": "Yukarı akış nedeni", + "logs.col.duration": "Süre", + "logs.tokens.reported": "bildirilen", + "logs.tokens.unreported": "bildirilmeyen", + "logs.tokens.unsupported": "desteklenmeyen", + "logs.tokens.estimated": "tahmini", + "logs.tokens.input": "girdi", + "logs.tokens.output": "çıktı", + "logs.tokens.cacheRead": "önbellek okuma", + "logs.tokens.cacheWrite": "önbellek yazma", + "logs.tokens.reasoning": "akıl yürütme", + "logs.tokens.noCache": "önbellek verisi yok", + "logs.tokens.contextTotal": "aktif bağlam", + "logs.tokens.noCacheNote": "bu sağlayıcı önbellek jetonlarını bildirmiyor", + "logs.tokens.noCacheCursor": "Cursor önbellek detayı bildirilmedi", + "logs.tokens.noCacheCursorNote": "Cursor önbellek jeton sayılarını sunmaz", + "logs.tokens.estimatedNote": "tahmini kullanım", + "logs.details": "Detaylar", + "logs.detailTitle": "İstek detayları", + "logs.detailRaw": "Ham günlük kaydı", + + // debug page + "debug.title": "Hata Ayıklama", + "debug.subtitle": "Sağlayıcı taşıma ve kullanım çıkarma tanılamaları.", + "debug.debug": "Sağlayıcı hata ayıklama", + "debug.usage": "Kullanım çıkarma", + "debug.injection": "Enjeksiyon günlüğü", + "debug.claude": "Claude gelen istekler", + "debug.claudeInbound.title": "Claude gelen istekleri", + "debug.claudeInbound.sub": "Claude Code/Desktop tarafından gönderilen istekler.", + "debug.claudeInbound.empty": "Henüz istek yakalanmadı.", + "debug.claudeInbound.time": "Zaman", + "debug.claudeInbound.endpoint": "Uç nokta", + "debug.claudeInbound.model": "Model", + "debug.claudeInbound.none": "yok", + "debug.reset": "Sıfırla", + "debug.refresh": "Yenile", + "debug.follow": "Takip Et", + "debug.streamProvider": "Sağlayıcı", + "debug.streamUsage": "Kullanım", + "debug.streamInjection": "Enjeksiyon", + "debug.loading": "Ayarlar yükleniyor…", + "debug.loadFailed": "Ayarlar yüklenemedi.", + "debug.emptyTitle": "Hata ayıklama günlüğü kapalı", + "debug.empty": "Hata ayıklama seçeneğini açın.", + "debug.noLinesTitle": "Satırlar bekleniyor", + "debug.noLines.provider": "Sağlayıcı hata ayıklama açık.", + "debug.noLines.usage": "Kullanım çıkarma açık.", + "debug.noLines.injection": "Enjeksiyon günlüğü açık.", + + // usage page + "usage.title": "Kullanım", + "usage.subtitle": "Proxy'nizden yerel jeton muhasebesi.", + "usage.loading": "Kullanım verileri yükleniyor…", + "usage.empty": "Henüz kullanım kaydedilmedi.", + "usage.loadError": "Kullanım verileri yüklenemedi.", + "usage.range.all": "Tümü", + "usage.range.available": "Mevcut geçmiş", + "usage.historyTruncated": "Toplamlar yalnızca mevcut geçmişi kapsar.", + "usage.range.30d": "30 gün", + "usage.range.7d": "7 gün", + "usage.card.requests": "İstekler", + "usage.card.measured": "Ölçülen", + "usage.card.reported": "Bildirilen", + "usage.card.totalTokens": "Toplam jeton", + "usage.card.cachedTokens": "Önbellek okuma", + "usage.card.cachedTokensHint": "Sağlayıcı önbelleğinden sunulan jetonlar.", + "usage.card.cacheWriteTokens": "önbellek yazma", + "usage.card.coverage": "Kapsam", + "usage.card.activeDays": "Aktif günler", + "usage.section.heatmap": "Günlük aktivite", + "usage.section.overview": "Genel bakış", + "usage.section.models": "Modeller", + "usage.section.providers": "Sağlayıcılar", + "usage.section.coverage": "Kapsam dağılımı", + "usage.workspace.report": "Kullanım raporu", + "usage.workspace.sections": "Kullanım bölümleri", + "usage.coverage.measured": "Ölçülen", + "usage.coverage.reported": "Bildirilen", + "usage.coverage.estimated": "Tahmin edilen", + "usage.coverage.note": "Ölçülen girdiler bildirilen ve tahmin edilen jeton sayılarını içerir.", + "usage.search.models": "Modellerde ara…", + "usage.col.requests": "İstekler", + "usage.col.measured": "Ölçülen", + "usage.col.reported": "Bildirilen", + "usage.col.tokens": "Jetonlar", + "usage.col.share": "Pay", + "usage.heatmap.less": "Daha az", + "usage.heatmap.more": "Daha fazla", + "usage.dayMon": "Pzt", + "usage.dayWed": "Çar", + "usage.dayFri": "Cum", + "usage.heatmap.tooltipTokens": "{tokens} jeton", + "usage.heatmap.tooltipRequests": "{requests} istek", + + "nav.storage": "Depolama", + "storage.title": "Depolama", + "storage.subtitle": "CODEX_HOME dizinini nelerin kullandığını görün.", + "storage.loading": "Depolama taranıyor…", + "storage.empty": "CODEX_HOME boş veya eksik.", + "storage.error": "Depolama taraması başarısız oldu.", + "storage.refresh": "Yeniden tara", + "storage.rescanned": "Tarama tamamlandı.", + "storage.card.total": "Toplam boyut", + "storage.card.files": "Dosyalar", + "storage.card.home": "CODEX_HOME", + "storage.snapshot.lastScan": "Son tarama", + "storage.snapshot.scanning": "Taranıyor…", + "storage.snapshot.unavailable": "Henüz tarama yok.", + "storage.cleanupCard.title": "Alan açın", + "storage.cleanupCard.tabs": "Temizleme seçenekleri", + "storage.cleanupCard.tab.policy": "Politika", + "storage.cleanupCard.tab.quarantine": "Karantina", + "storage.cleanup.noArchives": "Temizlenecek arşivlenmiş oturum yok.", + "storage.section.buckets": "Kovalar", + "storage.section.largest": "En büyük dosyalar", + "storage.workspace.overview": "Genel Bakış", + "storage.workspace.selectBucket": "Ayrıntıları görmek için listeden bir kova seçin.", + "storage.col.bucket": "Kova", + "storage.col.size": "Boyut", + "storage.col.files": "Dosyalar", + "storage.col.oldest": "En eski", + "storage.col.newest": "En yeni", + "storage.col.rows": "DB satırları", + "storage.rows.unknown": "bilinmiyor (kilitli)", + "storage.bucket.sessions": "Aktif oturumlar", + "storage.bucket.archived_sessions": "Arşivlenmiş oturumlar", + "storage.bucket.logs_db": "Günlük veritabanı", + "storage.bucket.state_db": "Durum veritabanı", + "storage.bucket.attachments": "Eklentiler", + "storage.bucket.deletion_manifests": "Silme bildirimleri", + "storage.bucket.other": "Diğer", + "storage.cleanup.title": "Arşiv temizleme", + "storage.cleanup.help": "En eski arşivlenmiş oturumları yüzdeye göre kaldırın.", + "storage.cleanup.slider": "En eski arşivlenen yüzde", + "storage.cleanup.percent": "%{percent}", + "storage.cleanup.preset": "{percent}", + "storage.cleanup.preview": "Önizleme", + "storage.cleanup.confirmTitle": "Arşiv temizliğini onayla", + "storage.cleanup.confirmBody": "{count} arşivlenmiş dosya işlenecek (~{size}).", + "storage.cleanup.moreFiles": "…ve {n} tane daha", + "storage.cleanup.permanent": "Kalıcı olarak sil (karantinayı atla)", + "storage.cleanup.permanentWarn": "Kalıcı silme geri alınamaz.", + "storage.cleanup.quarantineNote": "Dosyalar CODEX_HOME/.trash altına taşınır.", + "storage.cleanup.cancel": "İptal", + "storage.cleanup.confirmQuarantine": "Karantinaya Al", + "storage.cleanup.confirmPermanent": "Kalıcı Olarak Sil", + "storage.cleanup.doneQuarantine": "{count} dosya karantinaya alındı ({size}).", + "storage.cleanup.donePermanent": "{count} dosya kalıcı olarak silindi ({size}).", + "storage.cleanup.previewFailed": "Önizleme başarısız oldu.", + "storage.cleanup.cleanupFailed": "Temizleme başarısız oldu.", + "storage.cleanup.err.codex_busy": "Codex state.sqlite dosyasını kullanıyor.", + "storage.cleanup.err.stale_preview": "Önizlemeden sonra arşivlenmiş dosyalar değişti.", + "storage.cleanup.err.restore_pending_overlap": "Seçilen arşivler tamamlanmamış bir geri yükleme ile çakışıyor.", + "storage.cleanup.err.referenced_history": "Seçilen arşivler hâlâ geçmiş tarafından referans gösteriliyor.", + "storage.cleanup.err.invalid_digest": "Önizleme özeti eksik veya geçersiz.", + "storage.cleanup.err.invalid_mode": "Temizleme modu karantina veya kalıcı olmalıdır.", + "storage.cleanup.err.fs_failed": "Dosya sistemi temizliği başarısız oldu.", + "storage.cleanup.err.fs_failed_trash": "Dosya sistemi temizliği başarısız oldu.", + "storage.cleanup.err.db_reconcile_failed": "Codex durum veritabanı güncellenemedi.", + "storage.cleanup.err.cleanup_failed": "Temizleme başarısız oldu.", + + "storage.trash.title": "Karantina", + "storage.trash.help": "CODEX_HOME/.trash altına taşınan arşivlenmiş oturumlar.", + "storage.trash.empty": "Karantinaya alınmış öğe yok.", + "storage.trash.loading": "Karantina yükleniyor…", + "storage.trash.col.when": "Karantinaya Alındı", + "storage.trash.col.files": "Dosyalar", + "storage.trash.col.size": "Boyut", + "storage.trash.col.mode": "Mod", + "storage.trash.col.id": "Girdi", + "storage.trash.restore": "Geri Yükle", + "storage.trash.confirmTitle": "Karantina girdisi geri yüklensin mi?", + "storage.trash.confirmBody": "{id} kimliğinden {count} dosya (~{size}) geri yüklensin mi?", + "storage.trash.cancel": "İptal", + "storage.trash.confirmRestore": "Geri Yükle", + "storage.trash.done": "{count} dosya geri yüklendi ({size}).", + "storage.trash.restoreFailed": "Geri yükleme başarısız oldu.", + "storage.trash.listFailed": "Karantina girdileri listelenemedi.", + "storage.trash.mode.quarantine": "karantina", + "storage.trash.mode.permanent": "kalıcı (tamamlanmamış)", + "storage.trash.err.codex_busy": "Codex state.sqlite dosyasını kullanıyor.", + "storage.trash.err.invalid_trash": "Çöp girdisi ID'si eksik veya geçersiz.", + "storage.trash.err.missing_trash": "Çöp girdisi bulunamadı.", + "storage.trash.err.dest_exists": "Geri yükleme hedefi zaten mevcut.", + "storage.trash.err.fs_failed": "Dosya sistemi geri yüklemesi başarısız oldu.", + "storage.trash.err.db_reconcile_failed": "Veritabanı satırları geri yüklenemedi.", + "storage.trash.err.storage_mutation_busy": "Başka bir depolama eylemi devam ediyor.", + "storage.trash.err.restore_failed": "Geri yükleme başarısız oldu.", + "storage.trash.err.restore_worker_timeout": "Geri yükleme çok uzun sürdü.", + "storage.trash.err.restore_worker_aborted": "Geri yükleme iptal edildi.", + "storage.trash.err.restore_worker_failed": "Geri yükleme işleyicisi çöktü.", + "storage.policy.title": "Otomatik temizleme politikası", + "storage.policy.help": "İsteğe bağlı toplu temizleme politikası.", + "storage.policy.loading": "Politika yükleniyor…", + "storage.policy.loadFailed": "Temizleme politikası yüklenemedi.", + "storage.policy.saveFailed": "Temizleme politikası kaydedilemedi.", + "storage.policy.runFailed": "Politika çalıştırması başarısız oldu.", + "storage.policy.alreadyRunning": "Bir politika çalıştırması zaten devam ediyor.", + "storage.policy.invalid": "Geçersiz politika değerleri.", + "storage.policy.enabled": "Otomatik temizlemeyi etkinleştir", + "storage.policy.enabledHint": "Varsayılan olarak kapalıdır.", + "storage.policy.threshold": "Arşivlenen boyut aşıldığında (GiB)", + "storage.policy.trigger": "Tetikleyici", + "storage.policy.target": "Temizleme hedefi", + "storage.policy.targetPercent": "En eski arşivlenenleri kaldır (%)", + "storage.policy.targetReduce": "Arşiv boyutunu düşür (GiB)", + "storage.policy.thresholdInc": "Eşiği artır", + "storage.policy.thresholdDec": "Eşiği azalt", + "storage.policy.percentInc": "Yüzdeyi artır", + "storage.policy.percentDec": "Yüzdeyi azalt", + "storage.policy.reduceInc": "Hedef boyutu artır", + "storage.policy.reduceDec": "Hedef boyutu azalt", + "storage.policy.schedule": "Zamanlama", + "storage.policy.schedule.manual": "Yalnızca manuel", + "storage.policy.schedule.startup": "Proxy başlangıcında", + "storage.policy.schedule.daily": "Günlük", + "storage.policy.schedule.weekly": "Haftalık", + "storage.policy.mode": "Silme modu", + "storage.policy.mode.quarantine": "Karantina (varsayılan)", + "storage.policy.mode.permanent": "Kalıcı silme", + "storage.policy.permanentWarn": "Kalıcı mod geri alınamaz.", + "storage.policy.lastRun": "Son çalıştırma", + "storage.policy.lastRunDetail": "{count} kaldırıldı · {size} alan açıldı", + "storage.policy.nextRun": "Sonraki çalıştırma", + "storage.policy.never": "Hiçbir zaman", + "storage.policy.save": "Kaydet", + "storage.policy.runNow": "Şimdi çalıştır", + "storage.policy.running": "Çalıştırılıyor…", + "storage.policy.saved": "Politika kaydedildi.", + "storage.policy.skippedDisabled": "Politika devre dışı.", + "storage.policy.skippedUnder": "Arşiv boyutu eşiğin altında.", + "storage.policy.skippedEmpty": "Hedefle eşleşen aday yok.", + "storage.policy.doneQuarantine": "Politika {count} dosyayı karantinaya aldı ({size}).", + "storage.policy.donePermanent": "Politika {count} dosyayı kalıcı olarak sildi ({size}).", + + // add-provider modal + "modal.addNamed": "Ekle: {label}", + "modal.add": "Sağlayıcı ekle", + "modal.search": "Sağlayıcılarda ara…", + "modal.logInWith": "{label} ile giriş yap", + "modal.waitingBrowser": "Tarayıcı bekleniyor…", + "modal.providerName": "Sağlayıcı adı", + "modal.adapter": "Adaptör", + "modal.baseUrl": "Taban URL", + "modal.endpoint": "Uç nokta", + "modal.endpoint.tokenPlan": "Jeton planı", + "modal.endpoint.payAsYouGo": "Kullandıkça öde", + "modal.endpoint.custom": "Özel", + "modal.defaultModel": "Varsayılan model (isteğe bağlı)", + "modal.allowPrivateNetwork": "Yerel/özel ağa izin ver", + "modal.allowPrivateNetworkHint": "Yalnızca yerel barındırılan sağlayıcılar için etkinleştirin.", + "modal.nameRequired": "Sağlayıcı adı gereklidir", + "modal.baseUrlRequired": "Taban URL gereklidir", + "modal.networkError": "Ağ hatası — proxy çalışıyor mu?", + "modal.loginFailStart": "Giriş başlatılamadı", + "modal.waitingLogin": "Tarayıcı girişi bekleniyor…", + "modal.loggingIn": "Giriş yapılıyor…", + "modal.loginTimeout": "Giriş zaman aşılanına uğradı.", + "modal.back": "Geri", + "modal.badge.oauth": "OAuth", + "modal.customProvider": "Özel sağlayıcı", + "modal.failedStatus": "Başarısız ({status})", + "modal.loginError": "Giriş hatası: {error}", + "modal.badge.codexLogin": "Codex girişi", + "modal.badge.local": "Yerel", + "modal.badge.apiKey": "API anahtarı", + "modal.badge.direct": "Doğrudan", + "modal.badge.pool": "Havuz", + "modal.badge.free": "Ücretsiz", + "modal.invalidPreset": "Bu yerleşik sağlayıcı ayarı eksik.", + "modal.freeTierTitle": "Ücretsiz katman", + "modal.freeTierDefault": "API anahtarı gerekmez. Doğrudan çalışır.", + "modal.tab.accounts": "Hesaplar", + "modal.tab.free": "Ücretsiz", + "modal.tab.paid": "Ücretli", + "modal.accountsHint": "ChatGPT/Codex ve OAuth hesaplarına buradan giriş yapın.", + "modal.accountsCodexAuthLink": "Codex Kimlik Doğrulaması", + "modal.notListed": "Sağlayıcı listede yok mu? Özel sağlayıcı ekleyin", + "modal.catalogLoading": "Katalog yükleniyor…", + "modal.accountLogin": "Giriş yap", + "modal.accountLogout": "Çıkış yap", + "modal.accountAdd": "Hesap ekle", + "modal.accountManage": "Yönet", + "modal.accountCodexPool": "ChatGPT hesap havuzu", + "modal.accountLoggedIn": "Giriş yapıldı", + "modal.accountLoggedOut": "Giriş yapılmadı", + "quota.fiveHourLimit": "5 saatlik limit", + "quota.weeklyLimit": "Haftalık limit", + "quota.monthlyLimit": "30 günlük limit", + "quota.cursorFirstParty": "Birinci taraf modeller", + "quota.cursorApiUsage": "API kullanımı", + "quota.totalSubscriptionCredits": "Toplam abonelik kredileri", + "quota.usedPercent": "%{pct} kullanıldı", + "quota.limitReached": "Limite ulaşıldı", + "quota.resetsToday": "Bugün {time} saatinde sıfırlanır", + "quota.resetsTomorrow": "Yarın {time} saatinde sıfırlanır", + "quota.resetsAt": "{when} sıfırlanır", + "quota.resetsRelativeMinutes": "{n} dakika içinde sıfırlanır", + "quota.resetsRelativeHours": "{n} saat içinde sıfırlanır", + "pws.status.ready": "Hazır", + "pws.status.needsSetup": "Kurulum gerekiyor", + "pws.status.needsAttention": "Dikkat gerekiyor", + "pws.auth.chatgptPassthrough": "ChatGPT doğrudan geçiş", + "pws.auth.noKey": "Anahtar gerekmiyor", + "pws.freeTitle": "Ücretsiz fiyatlandırma", + "pws.localTitle": "Yerel çalışma zamanı", + "pws.modelCountOne": "1 model", + "pws.modelCount": "{count} model", + "pws.rail.suffixDefault": " · varsayılan", + "pws.rail.suffixLocal": " · yerel", + "pws.rail.suffixFree": " · ücretsiz", + "pws.rail.selectAria": "{name} seç — {status}{suffix}", + "pws.searchPlaceholder": "Sağlayıcılarda ara…", + "pws.filterAria": "Sağlayıcıları filtrele", + "pws.providerFiltersAria": "Sağlayıcı filtreleri", + "pws.filters": "Filtreler", + "pws.filterStatus": "Durum", + "pws.pricing": "Fiyatlandırma", + "pws.paid": "Ücretli", + "pws.filterType": "Tür", + "pws.type.cloud": "Bulut", + "pws.type.local": "Yerel", + "pws.type.selfHosted": "Kendi barındırdığı", + "pws.type.login": "Giriş yap", + "pws.sort": "Sırala", + "pws.sortProvidersAria": "Sağlayıcıları sırala", + "pws.sort.az": "A–Z", + "pws.sort.za": "Z–A", + "pws.sort.freePaid": "Önce ücretsizler", + "pws.sort.paidFree": "Önce ücretliler", + "pws.sort.accountsFirst": "Önce hesaplar", + "pws.resetAll": "Tümünü sıfırla", + "pws.providerList": "Sağlayıcı listesi", + "pws.providersAria": "Sağlayıcılar", + "pws.groupReady": "Hazır ({count})", + "pws.groupNeedsSetup": "Kurulum gerekiyor ({count})", + "pws.groupDisabled": "Devre dışı ({count})", + "pws.noSearchResults": "Aramanızla eşleşen sağlayıcı yok.", + "pws.noMatchFilters": "Filtrelerle eşleşen sağlayıcı yok.", + "pws.noProvidersConfigured": "Yapılandırılmış sağlayıcı yok.", + "pws.workspaceMainAria": "Sağlayıcı detayları", + "pws.detailComingSoon": "Detay görünümü yakında geliyor.", + "pws.selectPrompt": "Listeden bir sağlayıcı seçin.", + "pws.connectFirst": "İlk sağlayıcınızı bağlayın", + "pws.empty.browseFree": "Ücretsiz sağlayıcılara göz atın", + "pws.empty.browseFreeDesc": "Abonelik olmadan başlayın", + "pws.empty.connectAccount": "Bir hesap bağlayın", + "pws.empty.connectAccountDesc": "ChatGPT veya sağlayıcı girişinizi kullanın", + "pws.empty.addEndpoint": "Bir uç nokta ekleyin", + "pws.empty.addEndpointDesc": "Özel taban URL ve API anahtarı", + "pws.tab.overview": "Genel Bakış", + "pws.tab.models": "Modeller", + "pws.tab.usage": "Kullanım", + "pws.tab.accounts": "Hesaplar", + "pws.tab.settings": "Ayarlar", + "pws.connection": "Bağlantı", + "pws.status.connected": "Bağlandı", + "pws.attentionTitle": "Dikkat gerekiyor", + "pws.attention.reauth": "Aktif hesap yeniden doğrulama gerektiriyor", + "pws.attention.reauthForward": "Aktif Codex hesabı yeniden doğrulama gerektiriyor", + "pws.attention.missingCredentials": "Kimlik bilgileri eksik", + "pws.cell.auth": "Kimlik Doğrulama", + "pws.cell.note": "Not", + "pws.cell.defaultModel": "Varsayılan model", + "pws.statsAria": "Sağlayıcı istatistikleri", + "pws.statsTitle": "İstatistikler", + "pws.stats.totalRequests": "İstekler (30 gün)", + "pws.stats.totalTokens": "Jetonlar (30 gün)", + "pws.stats.quotaUpdated": "Kota güncellendi", + "pws.stats.quotaTracked": "Oran limitleri Kullanım sekmesinde takip edilir.", + "pws.stats.source": "Kaynak", + "pws.usageLast30d": "Kullanım (son 30 gün)", + "pws.estimatedCost": "Tahmini maliyet", + "pws.costDisclaimer": "API liste fiyatı tahminidir.", + "pws.modelBreakdown": "Model dağılımı", + "pws.col.model": "Model", + "pws.col.cost": "Tahm. maliyet", + "pws.col.tokens": "Jetonlar", + "pws.col.requests": "İst.", + "pws.col.share": "Pay", + "pws.tokenInput": "Girdi", + "pws.tokenOutput": "Çıktı", + "pws.metricRequests": "istek", + "pws.metricTokens": "jeton", + "pws.usageUnavailable": "Henüz kullanım kaydedilmedi.", + "pws.rateLimits": "Oran limitleri", + "pws.quotaUnavailable": "Bu sağlayıcı için kota verisi yok.", + "pws.accountQuotaUnavailable": "Oran limiti verisi geçici olarak kullanılamıyor.", + "pws.selected": "Seçilen", + "pws.copyModelId": "ID Kopyala", + "pws.modelCopied": "Kopyalandı!", + "pws.modelsAvailable": "{count} kullanılabilir", + "pws.modelSearchPlaceholder": "Modelleri filtrele…", + "pws.modelsLoading": "Modeller yükleniyor…", + "pws.modelsLoadFailed": "Modeller yüklenemedi.", + "pws.modelsNeedsReauth": "Hesabın yeniden giriş yapması gerekiyor.", + "pws.modelsConfiguredFallback": "Yapılandırılmış modeller gösteriliyor.", + "pws.modelsTruncated": "{total} modelden ilk {shown} tanesi gösteriliyor.", + "pws.retry": "Tekrar Dene", + "pws.noModels": "Bu sağlayıcı için keşfedilen model yok.", + "pws.noModelMatch": "Filtreyle eşleşen model yok.", + "pws.adapterBaseRequired": "Adaptör ve taban URL gereklidir.", + "pws.addAccount": "Hesap ekle", + "pws.addKey": "API anahtarı ekle", + "pws.apiKeys": "API Anahtarları", + "pws.authMode": "Kimlik doğrulama modu", + "pws.availableAccounts": "Mevcut hesaplar", + "pws.accountOrdinal": "Hesap {count}", + "pws.accountsLoading": "Hesaplar yükleniyor…", + "pws.accountsLoadFailed": "Hesaplar yüklenemedi.", + "pws.retryAccounts": "Tekrar Dene", + "pws.noAccounts": "Henüz bağlı hesap yok.", + "pws.accountSwitching": "Değiştiriliyor…", + "pws.accountCurrent": "Mevcut hesap", + "pws.defaultModelNone": "Yok (sağlayıcı varsayılanını kullan)", + "pws.discardSettings": "Vazgeç", + "pws.jsonEditorDesc": "Ham sağlayıcı JSON konfigürasyonunu düzenleyin.", + "pws.jsonEditorTitle": "JSON düzenleyici — {name}", + "pws.jsonRestore": "Geri Yükle", + "pws.jsonSave": "Kaydet", + "pws.loggedInTitle": "Giriş yapıldı", + "pws.notLoggedInTitle": "Giriş yapılmadı", + "pws.note": "Not", + "pws.allowPrivateNetwork": "Yerel/özel ağa izin ver", + "pws.liveModels": "Sağlayıcıdan canlı model keşfet", + "pws.liveModelsDesc": "Sağlayıcının canlı model kataloğunu çekin.", + "pws.optionalPlaceholder": "İsteğe bağlı", + "pws.providerId": "Sağlayıcı ID", + "pws.reauth": "Yeniden doğrulama gerekiyor", + "pws.reauthenticate": "Yeniden doğrula", + "pws.copyDoctor": "ocx doctor kopyala", + "pws.doctorCopied": "Kopyalandı", + "pws.doctorCopyUnavailable": "Pano kullanılamıyor", + "pws.healthCooldownHint": "Soğuma süresi bitene kadar bekleyin.", + "pws.healthLabel.rateLimited": "Oran kısıtlandı", + "pws.healthLabel.quotaLimited": "Kota kısıtlandı", + "pws.healthLabel.reauthRequired": "Yeniden doğrulama gerekli", + "pws.healthLabel.refreshFailed": "Yenileme başarısız oldu", + "pws.healthLabel.metadataMismatch": "Meta veri uyuşmazlığı", + "pws.healthLabel.credentialConflict": "Kimlik bilgisi çakışması", + "pws.healthSummary.rateLimited": "{provider} {account}: {until} tarihine kadar oran kısıtlandı.", + "pws.healthSummary.quotaLimited": "{provider} {account}: {until} tarihine kadar kota kısıtlandı.", + "pws.healthSummary.reauthRequired": "{provider} {account}: yeniden doğrulama gerekli.", + "pws.healthSummary.credentialConflict": "{provider} {account}: kimlik bilgisi çakışması.", + "pws.healthSummary.metadataMismatch": "{provider} {account}: meta veri uyuşmazlığı.", + "pws.healthSummary.staleCredentials": "{provider} {account}: eksik kimlik bilgileri.", + "pws.removeConfirm": "Kaldır", + "pws.removeConfirmBody": "\"{name}\" sağlayıcısı kaldırılsın mı?", + "pws.removeDefaultConfirmBody": "Varsayılan sağlayıcı \"{name}\" kaldırılsın mı?", + "pws.removeConfirmTitle": "Sağlayıcıyı kaldır", + "pws.saveSettings": "Kaydet", + "pws.saving": "Kaydediliyor…", + "pws.settingsSaved": "Ayarlar kaydedildi.", + "pws.accountModeSaved": "Hesap modu kaydedildi.", + "pws.accountModeFailed": "Hesap modu değiştirilemedi.", + "pws.accountModeConfirm": "OpenAI hesap modunu değiştirmek istediğinize emin misiniz?", + "pws.settingsUnsavedBar": "Kaydedilmemiş değişiklikleriniz var.", + "pws.unsavedLeaveBody": "Kaydedilmemiş değişiklikleriniz var. Ayrılmadan önce kaydetmek ister misiniz?", + "pws.unsavedLeaveTitle": "Kaydedilmemiş değişiklikler", + "pws.attentionRequired": "Dikkat gerekiyor", + "pws.attentionAria": "{name}: {reason}", + "pws.missingCredentials": "Kimlik bilgileri eksik", + "pws.editJsonDesc": "Proxy konfigürasyonunu JSON olarak düzenleyin", + "pws.updatesUnavailable": "Sağlayıcı güncellemeleri mevcut değil.", + "pws.dashboard.title": "Sağlayıcılara genel bakış", + "pws.dashboard.subtitle": "Tüm model sağlayıcılarınızı tek bir yerden yönetin.", + "pws.dashboard.rateLimits": "ORAN LİMİTLERİ", + "pws.capacity.estimate": "Havuz kapasite tahmini", + "pws.capacity.currentAccount": "Mevcut geçerli hesap", + "pws.capacity.nextRecovery": "Sonraki kapasite yenilenmesi", + "pws.capacity.recoveryShare": "+%{percent} havuz kapasitesi", + "pws.capacity.incomplete": "Eksik kapsam: {excluded} hesap hariç tutuldu", + "pws.capacity.partial": "Kısmi pencere kapsamı", + "pws.capacity.windowPartial": "Kısmi", + "pws.capacity.windowPartialA11y": "{window}: eksik hesap kapsamı", + "pws.dashboard.recentlyUsed": "SON KULLANILANLAR", + "pws.dashboard.requests": "{count} istek", + "pws.dashboard.checkedAgo": "{time} önce kontrol edildi", + "pws.dashboard.noQuota": "Kota verisi yok", + "pws.dashboard.noUsage": "Henüz kullanım verisi yok", + "pws.dashboard.noRateLimits": "Henüz oran limiti verisi yok", + "pws.allProviders": "Sağlayıcı Genel Bakışı", + "pws.enabledLabel": "Etkin", + "pws.testConnection": "Bağlantıyı test et", + "pws.testing": "Test ediliyor…", + "pws.connectionOk": "Bağlantı Başarılı", + "pws.connectionFailed": "Bağlantı Başarısız", + "pws.connectionNotApplicable": "Uygulanamaz — bu sağlayıcı statik katalog kullanıyor.", + "pws.editSettings": "Ayarları düzenle", + "pws.viewUsage": "Detaylı kullanımı gör", + "pws.allSystemsOk": "Tüm sistemler çalışır durumda", + "pws.apiKeyConfigured": "API anahtarı yapılandırıldı", + "pws.addApiKey": "API anahtarı ekle", + "pws.loggedInAs": "{email} olarak giriş yapıldı", + "pws.notLoggedIn": "Giriş yapılmadı", + "pws.passthrough": "Codex doğrudan geçiş", + "pws.notes": "NOTLAR", + "pws.notePlaceholder": "Bu sağlayıcı hakkında bir not ekleyin...", + "pws.noteSaved": "Not kaydedildi", + "pws.authSummary": "KİMLİK DOĞRULAMA", + "time.justNow": "Az önce", + "time.notChecked": "Kontrol edilmedi", + "time.minutesAgo": "{n}dk önce", + "time.hoursAgo": "{n}sa önce", + "time.daysAgo": "{n}gün önce", + "modal.noMatch": "Eşleşme yok.", + "modal.oauthDefaultNote": "Hesabınızla giriş yapın — API anahtarı gerekmez.", + "modal.oauthComingSoon": "{label} için OAuth girişi gelecektir. Şimdilik API anahtarı kullanın.", + "modal.oauthComingSoonShort": "Bu sağlayıcı için OAuth girişi gelecektir — şimdilik API anahtarı kullanın.", + "modal.useApiKeyInstead": "Bunun yerine API anahtarı kullanın", + "modal.setupGuide": "Kurulum rehberi", + "modal.setupStep1Prefix": "Adresine gidin:", + "modal.setupDashboardLink": "{label} paneli", + "modal.setupStep1Suffix": "ve API anahtarınızı kopyalayın", + "modal.setupStep2": "Aşağıdaki API anahtarı alanına yapıştırın", + "modal.setupStep3": "Sağlayıcı ekle'ye tıklayın — modeller otomatik keşfedilir", + "modal.namePlaceholder": "örn. openrouter", + "modal.duplicateWarn": "\"{name}\" sağlayıcısı mevcut ve üzerine yazılacak.", + "modal.forwardHintPrefix": "Anahtar gerekmez — proxy kimlik bilgilerinizi iletir", + "modal.forwardCredentials": "codex girişi", + "modal.forwardHintSuffix": "kimlik bilgilerini bu sağlayıcıya iletir.", + "modal.localHint": "API anahtarı saklanmaz.", + "modal.getApiKey": "{label} API anahtarınızı alın", + "modal.apiKey": "API anahtarı", + "modal.apiKeyTransport": "API anahtar başlığı", + "modal.apiKeyTransportNative": "x-api-key (Anthropic yerel)", + "modal.apiKeyTransportBearer": "Authorization: Bearer", + "modal.apiKeyPlaceholder": "sk-… (veya $ENV_VAR)", + "modal.defaultModelPlaceholder": "örn. gpt-5.5", + "modal.baseUrlPlaceholder": "https://...", + "modal.baseUrlPlaceholderError": "Taban URL çözümlenmemiş bir {placeholder} içeriyor.", + "modal.baseUrlPlaceholderHint": "Eklemeden önce {placeholder} değerini gerçek Hesap ID'nizle değiştirin.", + "modal.adding": "Ekleniyor…", + "modal.useOauthLogin": "← OAuth girişini kullan", + "nav.codexAuth": "Codex Kimlik Doğrulama", + "nav.api": "API", + "nav.integrations": "Entegrasyonlar", + "nav.openMenu": "Menüyü aç", + "nav.closeMenu": "Menüyü kapat", + "integrations.subtitle": "İstemcileri opencodex'e bağlayın, kimlik bilgilerini yönetin.", + "integrations.tabsLabel": "Entegrasyon yüzeyleri", + "integrations.tab.overview": "Genel Bakış", + "integrations.tab.keys": "API Anahtarları", + "integrations.tab.codex": "Codex CLI", + "integrations.tab.claude": "Claude", + "integrations.tab.grok": "Grok Build", + "integrations.tab.opencode": "OpenCode", + "integrations.tab.pi": "Pi", + "integrations.tab.hermes": "Hermes", + "integrations.tab.openclaw": "OpenClaw", + "integrations.tab.kimi": "Kimi Code", + "integrations.tab.gajae": "Gajae Code", + "integrations.codex.title": "Codex CLI", + "integrations.codex.body": "Codex bağlantısı proxy servisine aittir.", + "integrations.codex.openService": "Servis kontrollerini aç", + "integrations.state.notInstalled": "Yüklü değil", + "integrations.state.unknown": "Kontrol ediliyor…", + "integrations.detail.codexRouted": "Codex istekleri bu proxy üzerinden geçer", + "integrations.detail.codexAbsent": "Codex henüz bu proxy üzerinden yönlendirilmedi", + "integrations.detail.keyCount": "{count} anahtar oluşturuldu", + "integrations.detail.keyNone": "Oluşturulmuş anahtar yok", + "integrations.detail.keyChecking": "Kontrol ediliyor…", + "integrations.detail.keyUnavailable": "Anahtar durumu kullanılamıyor", + "integrations.detail.claudeOff": "Bağlantı kapalı", + "integrations.detail.desktopCurrent": "Desktop bu profili çalıştırıyor", + "integrations.detail.desktopStale": "Profil dosyası değiştirildi", + "integrations.detail.desktopNotServed": "Profil mevcut ancak Desktop başkasını kullanıyor", + "integrations.detail.desktopAbsent": "Uygulanan profil yok", + "integrations.detail.desktopDesiredOff": "Claude Desktop entegrasyonu kapalı", + "integrations.detail.desktopDesiredOnNotApplied": "Entegrasyon açık ancak Desktop kullanmıyor", + "integrations.detail.desktopSelectedElsewhere": "Desktop başka bir profil kullanıyor", + "integrations.detail.desktopProfileDrift": "Seçilen Desktop profili değişti", + "integrations.detail.desktopObservedUnsafe": "Desktop profili güvenle değiştirilemiyor", + "integrations.detail.desktopNotInstalled": "Claude Desktop kütüphanesi yüklü değil", + "integrations.detail.grokModels": "{count} model bağlandı", + "integrations.detail.grokAbsent": "Konfigürasyonda opencodex bloğu yok", + "integrations.dialog.grok.title": "Grok Build entegrasyonu devre dışı bırakılsın mı?", + "integrations.dialog.grok.changes": "Yalnızca opencodex tarafından işaretlenen blok kaldırılacak.", + "integrations.dialog.grok.breakage": "Devre dışı bırakmak opencodex model takma adlarını kaldırır.", + "integrations.dialog.grok.undo": "Tekrar açmak yeni bir blok yazar.", + "integrations.dialog.grok.confirm": "Devre Dışı Bırak", + "integrations.dialog.desktop.title": "Claude Desktop entegrasyonu devre dışı bırakılsın mı?", + "integrations.dialog.desktop.changes": "Desktop orijinal profiline dönecek.", + "integrations.dialog.desktop.breakage": "Claude Desktop standart modele geri dönecektir.", + "integrations.dialog.desktop.undo": "Tekrar açmak profili yeniden oluşturur.", + "integrations.dialog.desktop.restart": "Claude Desktop için yeniden başlatma gereklidir.", + "integrations.dialog.desktop.confirm": "Devre Dışı Bırak", + "integrations.native.msg.nonLoopbackRemoved": "Grok Build otomatik olarak kaldırıldı.", + "integrations.native.msg.nonLoopbackRemovedNoop": "Kaldırılacak blok yoktu.", + "integrations.native.msg.nonLoopbackSuperseded": "Başka bir işlem yeni bir blok yazdı.", + "integrations.native.error.orphanedMarker": "Bitiş işareti eksik.", + "integrations.native.error.homeMismatch": "Dizin eşleşmedi.", + "integrations.native.error.notInstalled": "Grok Build yüklü değil.", + "integrations.native.error.configBusy": "Konfigürasyon meşgul.", + "integrations.native.error.desktopUnsafeMetadata": "Meta veri okunamadı.", + "integrations.native.error.desktopCleanupIncomplete": "Eski kimlik dosyaları kaldı.", + "integrations.native.msg.desktopDisabled": "Claude Desktop entegrasyonu devre dışı bırakıldı.", + "integrations.native.msg.desktopEnabled": "Claude Desktop entegrasyonu etkinleştirildi.", + "integrations.state.absent": "Uygulanmadı", + "integrations.state.current": "Uygulandı", + "integrations.state.stale": "Güncelleme gerekiyor", + "integrations.state.conflict": "Çakışma", + "integrations.state.unsafe": "Doğrulanamadı", + "integrations.summary.detected": "Algılanan istemciler", + "integrations.summary.applied": "Yapılandırılan istemciler", + "integrations.summary.stale": "Güncelleme gerekiyor", + "integrations.summary.lastChange": "Son değişiklik", + "integrations.summary.disableAll": "Tümünü devre dışı bırak…", + "integrations.onboarding": "Uygulama işlemi bir yedek aldıktan sonra yazar.", + "integrations.empty.title": "Yüklü istemci algılanmadı", + "integrations.empty.body": "Desteklenen bir istemci yükleyin.", + "integrations.action.apply": "Uygula", + "integrations.action.disable": "Devre Dışı Bırak", + "integrations.action.refresh": "Güncelle", + "integrations.action.settings": "Ayarlar", + "integrations.action.manageKeys": "Anahtarları yönet", + "integrations.action.restore": "Geri Yükle…", + "integrations.action.undo": "Geri Al", + "integrations.action.restorePoint": "Bu noktayı geri yükle…", + "integrations.action.snapshotExpired": "Yedek süresi doldu", + "integrations.rollback.title": "Geri alma merkezi", + "integrations.rollback.empty": "Henüz uygulama geçmişi yok", + "integrations.rollback.emptyBody": "Her başarılı yazma bir anlık görüntü saklar.", + "integrations.restore.title": "Bu anlık görüntü geri yüklensin mi?", + "integrations.restore.body": "Mevcut dosya önce yedeklenir.", + "integrations.restore.driftTitle": "Daha yeni düzenlemeler algılandı", + "integrations.restore.driftBody": "Bu anlık görüntüden sonra yapılan değişiklikler yedeklenecektir.", + "integrations.restore.confirm": "Geri Yükle", + "integrations.restore.confirmDrift": "Yedekle ve geri yükle", + "integrations.restore.pending": "Geri yükleniyor…", + "integrations.restore.manual": "Otomatik geri yükleme başarısız oldu.", + "integrations.error.load": "Entegrasyon durumu yüklenemedi.", + "integrations.error.stale": "Son yenileme başarısız oldu.", + "integrations.error.busy": "İşlem devam ediyor.", + "integrations.error.conflict": "Konfigürasyon yazıldıktan sonra değişti.", + "integrations.error.unsafe": "Konfigürasyon güvenle değiştirilemiyor.", + "integrations.error.generic": "Entegrasyon değişikliği başarısız oldu.", + "integrations.error.nonLoopback": "Yalnızca localhost üzerinden ulaşabilir.", + "integrations.status.installed": "Yüklü", + "integrations.status.notInstalled": "Yüklü değil", + "integrations.status.appliedAt": "Uygulandı", + "integrations.status.backup": "Yedek", + "integrations.status.lastRestore": "Son geri yükleme", + "integrations.status.unknown": "Bilinmiyor", + "integrations.bulk.title": "Uygulanan istemci entegrasyonları devre dışı bırakılsın mı?", + "integrations.bulk.body": "Yalnızca opencodex bloğu kaldırılır.", + "integrations.bulk.partial": "Bazı istemciler devre dışı bırakılamadı: {clients}", + "integrations.bulk.success": "Uygulanan istemci entegrasyonları devre dışı bırakıldı.", + "integrations.retention.degraded": "Yedek temizliği geride kaldı.", + "integrations.error.residual": "Dosya ara durumda olabilir.", + "integrations.error.recover": "Yedek konumda.", + "integrations.kind.apply": "Uygulandı", + "integrations.kind.disable": "Devre Dışı Bırakıldı", + "integrations.kind.refresh": "Güncellendi", + "integrations.kind.restore": "Geri Yüklendi", + "integrations.semantics.opencode": "Doğrudan disk başlatmaları.", + "integrations.semantics.pi": "Yeni oturumlara uygulanır.", + "integrations.semantics.hermes": "Yeni oturumlara uygulanır.", + "integrations.semantics.openclaw": "Anında uygulanır.", + "integrations.semantics.kimi": "Uygulamak için yeniden başlatın.", + "integrations.semantics.gajae": "Yeni oturuma uygulanır.", + "codexAuth.mainAccount": "Ana Hesap", + "codexAuth.codexApp": "Codex Uygulaması", + "codexAuth.appLogin": "Uygulama girişi", + "codexAuth.accountPool": "Hesap Havuzu", + "codexAuth.accountModeTitle": "OpenAI hesap modu", + "codexAuth.accountModePool": "Havuz modu", + "codexAuth.accountModePoolDesc": "Ana giriş ve eklenen hesaplar burada döner.", + "codexAuth.accountModeDirect": "Doğrudan mod", + "codexAuth.accountModeDirectDesc": "İstekler yalnızca ana girişi kullanır.", + "codexAuth.openaiMissing": "Yerleşik OpenAI sağlayıcısı yapılandırılmamış.", + "codexAuth.openaiDisabled": "Yerleşik OpenAI sağlayıcısı devre dışı.", + "codexAuth.openaiUnavailableDesc": "OpenAI hesaplarınız kullanılabilir.", + "codexAuth.enableOpenai": "OpenAI'yi Etkinleştir", + "codexAuth.enablingOpenai": "Etkinleştiriliyor...", + "codexAuth.enableOpenaiFailed": "OpenAI sağlayıcısı etkinleştirilemedi.", + "codexAuth.openaiPresetLoadFailed": "Ayar yüklenemedi.", + "codexAuth.openaiPresetUnavailable": "Ayar kullanılamıyor.", + "codexAuth.openProviders": "Sağlayıcıları Aç", + "codexAuth.add": "Ekle", + "codexAuth.refreshQuota": "Kotaları yenile", + "codexAuth.refreshingQuota": "Yenileniyor...", + "codexAuth.quotaRefreshed": "Kotalar yenilendi", + "codexAuth.quotaRefreshFailed": "Kotalar yenilenemedi", + "codexAuth.pauseExhausted": "Tükenenleri duraklat", + "codexAuth.pausingExhausted": "Kotalar kontrol ediliyor...", + "codexAuth.pauseExhaustedSucceeded": "Limitteki hesaplar duraklatıldı: {count}", + "codexAuth.pauseExhaustedNone": "%100 kullanımı doğrulanmış hesap yok.", + "codexAuth.pauseExhaustedFailed": "Tükenen hesaplar kontrol edilemedi.", + "codexAuth.noPool": "Henüz havuz hesabı eklenmedi.", + "codexAuth.pause": "Duraklat", + "codexAuth.resume": "Devam Ettir", + "codexAuth.paused": "DURAKLATILDI", + "codexAuth.pauseSucceeded": "{email} duraklatıldı", + "codexAuth.resumeSucceeded": "{email} tekrar havuza alındı", + "codexAuth.pauseFailed": "{email} duraklatılamadı.", + "codexAuth.resumeFailed": "{email} devam ettirilemedi.", + "codexAuth.pausedHint": "Devam ettirilene kadar otomatik seçimden hariç tutulur.", + "codexAuth.pinned": "SABİTLENDİ", + "codexAuth.pinnedHint": "Bu hesabı elle seçtiniz.", + "codexAuth.fiveHour": "5saat", + "codexAuth.weekly": "Hafta", + "codexAuth.monthly": "30gün", + "codexAuth.resets": "sıfırlanma", + "codexAuth.today": "Bugün", + "codexAuth.current": "MEVCUT", + "codexAuth.nextSession": "SEÇİLEN", + "codexAuth.poolPrepared": "HAVUZ İÇİN HAZIRLANDI", + "codexAuth.preparePoolTitle": "Bu hesap Havuz modu için hazırlansın mı?", + "codexAuth.preparePoolDesc": "Doğrudan istekler ana girişi kullanmaya devam eder.", + "codexAuth.prepareForPool": "Havuz İçin Hazırla", + "codexAuth.poolPreparedToast": "{email} Havuz modu için hazırlandı", + "codexAuth.switchTitle": "Aktif hesap değiştirilsin mi?", + "codexAuth.switchDesc": "Anında yürürlüğe girer.", + "codexAuth.cacheWarning": "Hesap değiştirildiğinde önbellek sıfırlanır.", + "codexAuth.setAsNext": "Sonraki istekte bu hesabı kullan", + "codexAuth.cancel": "İptal", + "codexAuth.switchBack": "Ana hesaba geri dönülsün mü?", + "codexAuth.switchBackDesc": "Anında yürürlüğe girer.", + "codexAuth.autoSwitch": "Kullanıma dayalı proaktif geçiş", + "codexAuth.autoSwitchQuotaDesc": "Kota: %{threshold} veya üzeri kullanımda sonraki istek daha az kullanılan bir hesaba geçebilir.", + "codexAuth.autoSwitchQuotaOffDesc": "Kullanıma dayalı proaktif geçiş kapalı.", + "codexAuth.autoSwitchRoundRobinDesc": "Round-robin bu eşiği kullanmaz.", + "codexAuth.autoSwitchFillFirstDesc": "İlk doldurma modu.", + "codexAuth.autoSwitchFillFirstOffDesc": "İlk doldurma modunda kullanım boşaltma noktası yok.", + "codexAuth.failureRecoveryNote": "Hata kurtarma ayrıdır.", + "codexAuth.autoSwitchThreshold": "Kullanım eşiği", + "codexAuth.autoSwitchThresholdAria": "Kullanım eşiği, yüzde", + "codexAuth.autoSwitchThresholdInc": "Kullanım eşiğini artır", + "codexAuth.autoSwitchThresholdDec": "Kullanım eşiğini azalt", + "codexAuth.autoSwitchLoadFailed": "Ayarlar yüklenemedi.", + "codexAuth.autoSwitchThresholdInvalid": "1 ile 100 arasında bir tam sayı girin", + "codexAuth.autoSwitchUpdated": "Proaktif geçiş güncellendi", + "codexAuth.autoSwitchUpdateFailed": "Geçiş güncellemesi doğrulanamadı.", + "codexAuth.requestUserInput": "Varsayılan modda kullanıcı girdisi iste", + "codexAuth.requestUserInputDesc": "Codex'in soru sormasına izin verir.", + "codexAuth.requestUserInputUpdated": "Özellik güncellendi.", + "codexAuth.requestUserInputUpdatedRestart": "Özellik güncellendi. Codex uygulamasını yeniden başlatın.", + "codexAuth.requestUserInputUpdateFailed": "Özellik güncellenemedi.", + "codexAuth.requestUserInputLoadFailed": "Özellik okunamadı.", + + "anthropicPool.title": "Claude hesap havuzu (deneysel)", + "anthropicPool.enabledDesc": "429 durumunda hesabı soğutur ve yedek hesaba geçer.", + "anthropicPool.disabledDesc": "Yalnızca aktif Claude hesabını kullanır.", + "anthropicPool.experimentalWarning": "Deneysel özelliktir.", + "anthropicPool.needTwoAccounts": "Havuzu etkinleştirmeden önce en az iki Claude OAuth hesabı ekleyin.", + "anthropicPool.threshold": "Yeni oturum kullanım eşiği", + "anthropicPool.thresholdAria": "Yeni oturum kullanım eşiği, yüzde", + "anthropicPool.thresholdHelp": "0 kota bazlı seçimi devre dışı bırakır. Varsayılan 80.", + "anthropicPool.thresholdInvalid": "0 ile 100 arasında bir tam sayı girin", + "anthropicPool.loadFailed": "Claude havuz ayarları yüklenemedi.", + "anthropicPool.saveFailed": "Claude havuz ayarları kaydedilemedi.", + "anthropicPool.on": "Açık", + "anthropicPool.off": "Kapalı", + + "accountPool.strategy": "Rotasyon stratejisi", + "accountPool.strategyDesc": "OpenCodex'in yeni bir göreve nasıl hesap atayacağı.", + "accountPool.strategyQuota": "Kota", + "accountPool.strategyRoundRobin": "Round-robin", + "accountPool.strategyFillFirst": "İlk doldurma", + "accountPool.strategyHintQuota": "Kota kullanımı eşik aşıldığında hesabı değiştirebilir.", + "accountPool.strategyHintRoundRobin": "Round-robin görevi olmayan hesapları döndürür.", + "accountPool.strategyHintFillFirst": "İlk doldurma eşiği boşaltma noktası olarak kullanır.", + "accountPool.unboundDefinition": "Bağlı olmayan yeni görev.", + "accountPool.stickyLimit": "Döndürmeden önceki sabit atamalar", + "accountPool.stickyLimitAria": "Döndürmeden önceki sabit atamalar", + "accountPool.stickyLimitInc": "Sabit limiti artır", + "accountPool.stickyLimitDec": "Sabit limiti azalt", + "accountPool.stickyLimitHelp": "Seçilen hesabı bu kadar yeni atama boyunca tutun.", + "accountPool.stickyLimitInvalid": "1 ile 100 arasında bir tam sayı girin", + "accountPool.strategyLoadFailed": "Strateji yüklenemedi.", + "accountPool.strategyUpdateFailed": "Strateji kaydedilemedi.", + + "accountPool.priority": "Seçim sırası", + "accountPool.priorityAria": "Bu hesap için seçim sırası", + "accountPool.priorityHint": "Yüksek sayılar önce kullanılır.", + "accountPool.priorityFirst": "İlk", + "accountPool.priorityEarlier": "Daha önce", + "accountPool.priorityNormal": "Normal", + "accountPool.priorityLater": "Daha sonra", + "accountPool.priorityLast": "Son", + "accountPool.priorityOption": "{name} ({value})", + "accountPool.priorityCustom": "Özel", + "accountPool.priorityUpdated": "{email} için seçim sırası güncellendi", + "accountPool.priorityUpdateFailed": "Seçim sırası kaydedilemedi.", + + "codexAuth.switched": "Sonraki istek için {email} seçildi", + "codexAuth.loadFailed": "Codex hesap ayarları yüklenemedi.", + "codexAuth.switchFailed": "Hesap değiştirilemedi.", + "codexAuth.removeConfirm": "{id} kaldırılsın mı?", + "codexAuth.removeFailed": "Hesap kaldırılamadı.", + "codexAuth.addTitle": "Codex Hesabı Ekle", + "codexAuth.addIdLabel": "Hesap ID", + "codexAuth.addIdPlaceholder": "codex-is, codex-yedek...", + "codexAuth.resetCreditsAria": "{count} sıfırlama kredisi", + "codexAuth.addJsonLabel": "auth.json içeriği", + "codexAuth.addHelp": "Başka bir makineden kopyalayın.", + "codexAuth.importBtn": "İçe Aktar", + "codexAuth.importInvalidJson": "Geçersiz JSON", + "codexAuth.importMissingTokens": "Eksik jetonlar", + "codexAuth.importMissingId": "Hesap ID gereklidir", + "codexAuth.accountAdded": "Hesap havuza eklendi", + "codexAuth.addPickDesc": "Havuza eklemek için başka bir ChatGPT hesabı ile giriş yapın.", + "codexAuth.oauthLogin": "OAuth Girişi", + "codexAuth.oauthDesc": "Tarayıcıda ChatGPT girişini açar", + "codexAuth.importAuthJson": "auth.json İçe Aktar", + "codexAuth.importAuthJsonDesc": "Başka bir kurulumdan veya dışa aktarımdan", + "codexAuth.back": "Geri", + "codexAuth.oauthAlreadyInProgress": "Giriş zaten devam ediyor.", + "codexAuth.oauthWaiting": "Tarayıcıda girişin tamamlanması bekleniyor...", + "codexAuth.oauthSubmittingCode": "Kod gönderiliyor…", + "codexAuth.oauthCodeSubmitted": "Kod gönderildi — giriş bitmesi bekleniyor…", + "codexAuth.oauthStatusRetrying": "Durum kontrol edilirken hata — tekrar deneniyor…", + "codexAuth.oauthCancelled": "Giriş iptal edildi.", + "codexAuth.loginFailed": "Giriş başarısız oldu", + "codexAuth.needsReauth": "Tekrar Giriş Yap", + "codexAuth.reauthenticate": "Yeniden Doğrula", + "codexAuth.tokenExpired": "Jeton süresi doldu — hesabı yeniden doğrulayın", + "codexAuth.mainTokenExpired": "Jeton süresi doldu — tekrar giriş yapın", + "codexAuth.emailCollision": "Bu hesap ana girişinizle eşleşiyor.", + + "codexAuth.resetCreditsTitle": "Kredileri Sıfırla", + "codexAuth.resetCreditsAvailable": "{count} sıfırlama krediniz var.", + "codexAuth.resetCreditsDesc": "Her kredi oran limitlerinizi anında sıfırlar.", + "codexAuth.noResetCredits": "Sıfırlama krediniz yok.", + "codexAuth.earnCreditsHint": "Krediler aylık ve tavsiye programı ile kazanılır.", + "codexAuth.creditsExpireNote": "Kredilerin süresi 30 gün içinde dolmaktadır.", + "codexAuth.useOneCredit": "1 Kredi Kullan", + "codexAuth.confirmResetTitle": "Sıfırlama Kredisi Kullanılsın mı?", + "codexAuth.confirmResetDesc": "Bu işlem oran limitlerinizi anında sıfırlayacaktır. {count} krediniz kaldı.", + "codexAuth.irreversible": "Bu işlem geri alınamaz.", + "codexAuth.useCredit": "Kredi Kullan", + "codexAuth.redeeming": "Sıfırlanıyor...", + "codexAuth.resetSuccess": "Oran limitleri sıfırlandı! Kalan kredi: {remaining}.", + "codexAuth.resetSuccessGeneric": "Oran limitleri sıfırlandı!", + "codexAuth.resetAlreadyRedeemed": "Bu kredi zaten kullanıldı.", + "codexAuth.resetNothingToReset": "Şu anda sıfırlanması gereken oran limiti yok.", + "codexAuth.resetNoCredit": "Kullanılabilir sıfırlama kredisi yok.", + "codexAuth.resetError": "Sıfırlama kredisi kullanılamadı.", + "codexAuth.fifoNote": "En eski kredi ilk önce kullanılır.", + "codexAuth.confirmWhichCredit": "{date} tarihli kredi kullanılacak.", + "codexAuth.creditNext": "Sonraki kullanılacak", + "codexAuth.creditLabel": "Kredi #{n}", + "codexAuth.creditNextBadge": "SONRAKİ", + "codexAuth.creditGranted": "Verildiği tarih {date}", + "codexAuth.creditExpires": "Son kullanma {date} ({days}gün kaldı)", + + // api access page + "api.title": "API Erişimi", + "api.subtitle": "Dış uygulamalardan opencodex proxy'sine erişmek için üretilen API anahtarlarını kullanın.", + "api.baseUrl": "Taban URL", + "api.responsesEndpoint": "Responses API", + "api.chatCompletionsEndpoint": "Chat Completions API", + "api.messagesEndpoint": "Messages API", + "api.modelsEndpoint": "Models API", + "api.endpointNote": "OpenAI uyumlu istemcilerle taban URL'yi kullanın.", + "api.endpointsTitle": "Uç noktalar", + "api.authTitle": "Kimlik Doğrulama", + "api.authLoopback": "Loopback bağlantıları (127.0.0.1) kimlik doğrulamasını atlar.", + "api.authBaseUrlNote": "İstemcileri taban URL ile yapılandırın.", + "api.newKeyTitle": "Yeni anahtar oluşturuldu", + "api.newKeyNote": "Bu anahtarı şimdi kopyalayın — tekrar gösterilmeyecektir.", + "api.copy": "Kopyala", + "api.copied": "Kopyalandı", + "api.dismiss": "Kapat", + "api.generateTitle": "Anahtar oluştur", + "api.keyNamePlaceholder": "Anahtar adı (isteğe bağlı)", + "api.generate": "Oluştur", + "api.generating": "Oluşturuluyor…", + "api.activeKeys": "Aktif anahtarlar ({count})", + "api.activeKeysLoading": "Aktif anahtarlar", + "api.noKeys": "Henüz API anahtarı yok. Yukarıdan bir tane oluşturun.", + "api.workspace.sections": "API bölümleri", + "api.section.keys": "Anahtarlar", + "api.section.connect": "Bağlan", + "api.section.endpoints": "Uç noktalar", + "api.section.models": "Modeller", + "api.section.examples": "Örnekler", + "api.workspace.details": "API anahtar detayları", + "api.workspace.keyDetails": "Anahtar detayları", + "api.workspace.keyPrefix": "Anahtar ön eki", + "api.workspace.deleteKey": "Anahtarı sil", + "api.workspace.deleteConfirm": "Bu anahtarı silmek istediğinize emin misiniz?", + "api.workspace.usageExamples": "Kullanım örnekleri", + "api.copyUrlHint": "URL kopyalamak için tıklayın", + "api.urlCopied": "URL kopyalandı", + "api.copyExampleHint": "Örnek kopyalamak için tıklayın", + "api.exampleCopied": "Örnek kopyalandı", + "api.colName": "İsim", + "api.colKey": "Anahtar", + "api.colCreated": "Oluşturuldu", + "api.confirm": "Onayla", + "api.deleteAria": "API anahtarını sil", + "api.modelsTitle": "Harici model kataloğu", + "api.modelsCount": "{count} çağrılabilir", + "api.modelsLoading": "Modeller yükleniyor…", + "api.modelsSearch": "Modellerde ara", + "api.modelsSubtitle": "Uç noktalarınızla bu tam model ID'lerini kullanın.", + "api.modelsEmpty": "Henüz harici olarak çağrılabilir model yok.", + "api.modelsNoMatch": "“{query}” ile eşleşen model yok.", + "api.modelsLoadFailed": "Harici model kataloğu yüklenemedi.", + "api.colModel": "Model", + "api.colSource": "Kaynak", + "api.colProtocols": "Protokoller", + "api.sourceNative": "ChatGPT havuzu", + "api.sourceCombo": "Kombo rotası", + "api.sourceCustom": "Özel", + "api.protocolResponses": "Responses", + "api.protocolChatCompletions": "Chat Completions", + "api.protocolMessages": "Messages", + "api.copyModelId": "ID Kopyala", + "api.modelCopied": "Kopyalandı", + "api.testModel": "Test Et", + "api.testingModel": "Test ediliyor…", + "api.testSucceeded": "Tamam", + "api.testFailed": "Başarısız", + "api.usageChatTitle": "Chat Completions örneği", + "api.usageResponsesTitle": "Responses örneği", + "api.usageMessagesTitle": "Messages örneği", + "api.usageSampleInput": "Merhaba dünya!", + "api.clientConfig.title": "İstemci konfigürasyonu", + "api.clientConfig.rowsLabel": "Bir istemci bağlayın", + "api.clientConfig.details": "Detaylar", + "api.clientConfig.detailsAria": "{client} konfigürasyon detayları", + "api.clientConfig.copyAria": "{client} konfigürasyon JSON kopyala", + "api.clientConfig.downloadAria": "{client} konfigürasyonu indir", + "api.clientConfig.rowMeta": "{destination} · {count} model", + "api.clientConfig.rowError": "{client} konfigürasyonu oluşturulamadı.", + "api.clientConfig.copiedAnnounceClient": "{client} konfigürasyon JSON panoya kopyalandı.", + "api.clientConfig.clientOpencode": "OpenCode", + "api.clientConfig.clientPi": "Pi", + "api.clientConfig.clientHermes": "Hermes", + "api.clientConfig.clientOpenclaw": "OpenClaw", + "api.clientConfig.clientKimi": "Kimi Code", + "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.copy": "JSON Kopyala", + "api.clientConfig.download": "İndir", + "api.clientConfig.loading": "İstemci konfigürasyonu oluşturuluyor…", + "api.clientConfig.jsonLabel": "{client} konfigürasyon JSON", + "api.clientConfig.destination": "Hedef dosya", + "api.clientConfig.envHint": "Başlatmadan önce anahtarı ayarlayın", + "api.clientConfig.mergeWarning": "Bunu hedef dosyaya birleştirin.", + "api.clientConfig.modelCount": "{count} model dışa aktarıldı", + "api.clientConfig.missingLimits": "{total} modelden {count} tanesi bağlam sınırı olmadan gönderildi.", + "api.clientConfig.noKeyYet": "{env} arkasında henüz anahtar yok.", + "api.clientConfig.loadFailed": "Model listesi okunamadı.", + "api.clientConfig.copiedAnnounce": "İstemci konfigürasyonu JSON panoya kopyalandı.", + "api.clientConfig.copyFailed": "Kopyalanamadı.", + "api.clientConfig.downloadedAnnounce": "{filename} indirildi.", + "api.clientConfig.whereDisclosure": "Bu dosya nereye gidiyor", + "api.clientConfig.whereBody": "Yukarıdaki hedef genel yoldur.", + "api.keysLoadFailed": "API anahtarları yüklenemedi.", + "api.createFailed": "API anahtarı oluşturulamadı.", + "api.deleteFailed": "API anahtarı silinemedi.", + "api.auth.endpoint": "Uç nokta", + "api.auth.required": "Gerekli", + "api.auth.accepted": "Kabul edildi", + "api.auth.rejected": "Kabul edilmedi", + "api.auth.testProtocol": "{protocol} Test Et", + "api.auth.testNeedsFreshKey": "Test için bir anahtar oluşturun.", + "api.key.name": "Anahtar adı", + "api.key.rename": "Yeniden adlandır", + "api.key.saveName": "İsmi kaydet", + "api.key.renaming": "Kaydediliyor…", + "api.key.renameFailed": "Yeniden adlandırılamadı.", + "api.key.deleting": "Siliniyor…", + "api.key.copyFailed": "Kopyalanamadı.", + "api.attribution.title": "Atfedilen kullanım", + "api.attribution.requests7d": "Son 7 gün istekleri", + "api.attribution.totalRequests": "Toplam atfedilen istekler", + "api.attribution.totalRequestsAvailable": "Mevcut geçmişteki istekler", + "api.attribution.sinceAvailable": "Mevcut atıf başlangıcı", + "api.attribution.lastUsed": "Son kullanım", + "api.attribution.since": "Atıf başlangıcı", + "api.attribution.neverUsed": "Henüz kullanılmadı", + "api.attribution.unavailable": "Kullanım mevcut değil", + "api.attribution.unavailableDetail": "Henüz kullanım atfedilmedi.", + "api.attribution.ambiguous": "İki anahtar aynı ID'yi paylaşıyor.", + "api.attribution.railAmbiguous": "mükerrer ID", + + // Claude Code inbound + "claude.subtitle": "Claude Code içinde GPT, Gemini ve diğer modelleri kullanın.", + "claude.pageTitle": "Claude Code", + "claude.workspace.settings": "Ayarlar", + "claude.enabledLabel": "Claude bağlantısı", + "claude.enabledHint": "Kapalı olduğunda Claude Code bu proxy'yi kullanamaz.", + "claude.authMode": "Kimlik Doğrulama Modu", + "claude.authModeHint": "Abonelik Claude hesabı gerektirir, Proxy modunda gerekmez", + "claude.authModeSubscription": "Abonelik (Claude hesabı)", + "claude.authModeProxy": "Proxy (hesap gerekmez)", + "claude.authModeAuto": "Otomatik (Claude doğrulamasını algıla)", + "claude.effectiveMode.label": "Sonraki başlatmada geçerli", + "claude.effectiveMode.manual": "Manuel: {mode}", + "claude.effectiveMode.autoPresent": "Otomatik: abonelik — {source} üzerinden Claude kimliği bulundu", + "claude.effectiveMode.autoAbsent": "Otomatik: proxy modu — Claude kimliği bulunamadı", + "claude.effectiveMode.autoUnknown": "Otomatik: abonelik — doğrulanamadı", + "claude.effectiveMode.admissionKey": "Bu proxy'nin API anahtarı hâlâ gönderiliyor.", + "claude.authSource.claude-json-oauth": "Claude hesabı", + "claude.authSource.claude-credentials-file": "kimlik bilgileri dosyası", + "claude.authSource.macos-keychain": "macOS Keychain", + "claude.authSource.exported-env": "ortam değişkeni", + "claude.authSource.unknown": "algılanan bir kimlik bilgisi", + "claude.systemEnv": "Otomatik bağlan", + "claude.systemEnvDesc": "Açık olduğunda terminalde claude çalıştırmak proxy üzerinden geçer.", + "claude.systemEnvUnsupported": "Otomatik bağlan yalnızca macOS üzerinde kullanılabilir.", + "claude.systemEnvWarn": "⚠ Terminal uygulamasını tamamen kapatıp yeniden açmalısınız.", + "claude.fastMode": "Hızlı Mod (OpenAI)", + "claude.fastModeDesc": "OpenAI modelleri için service_tier ayarını kontrol eder.", + "claude.fastAuto": "Otomatik", + "claude.fastOn": "AÇIK", + "claude.fastOff": "KAPALI", + "claude.autoContext": "Otomatik büyük bağlam kullan", + "claude.autoContextDesc": "1M işaretlemesinin nereye kadar gideceğini kontrol eder.", + "claude.autoContextInert": "Pasif durumdadır.", + "claude.autoCompactWindow": "Otomatik özetleme noktası", + "claude.autoCompactDefault": "350k (varsayılan)", + "claude.autoCompactWindowDesc": "Sohbet bu noktaya ulaştığında eski mesajlar özetlenir.", + "claude.autoCompactWindowWarn": "Bunu değiştirmek GPT modellerini bozabilir.", + "claude.injectAgents": "Alt ajanları otomatik kaydet", + "claude.injectAgentsDesc": "Alt ajanlar sekmesindeki modelleri Claude Code ajanları olarak kaydeder.", + "claude.webSearchSidecar": "Web arama yan araç geçersiz kılması", + "claude.webSearchSidecarHint": "Claude Code istekleri için ana web arama sidecar'ını geçersiz kılın.", + "claude.visionSidecar": "Görsel yan araç geçersiz kılması", + "claude.visionSidecarHint": "Claude Code istekleri için ana görsel sidecar'ını geçersiz kılın.", + "claude.useMainSetting": "Ana ayarı kullan", + "claude.sidecarModelPlaceholder": "Ana ayar modeli", + "claude.quickstart": "Başlarken", + "claude.quickstartHint": "{cmd} Claude Code'u proxy üzerinden açar.", + "claude.manualEnv": "Manuel kurulum (gelişmiş)", + "claude.smallFastModel": "Arka plan yardımcı modeli", + "claude.smallFastModelHint": "Claude Code'un sohbet özetleri için kullandığı model.", + "claude.smallFastModelAccurateHint": "Claude Code'un arka plan işleri için kullandığı model.", + "claude.smallFastModelUnsetOption": "Claude Code seçsin (yerel model)", + "claude.smallFastModelNativeWarning": "Ayarlanmadığında yerel model kullanılabilir.", + "claude.slotUnset": "Claude varsayılanını kullan", + "claude.modelMap": "Model yakalama", + "claude.modelMapHint": "Belirli bir model isteklerini yakalar ve seçtiğiniz modele yönlendirir.", + "claude.mapFrom": "Orijinal model (örn. claude-sonnet-4-5)", + "claude.mapTo": "Değiştirilecek model (örn. gemini/gemini-3-pro)", + "claude.addMapping": "Kural ekle", + "claude.removeMapping": "Kuralı kaldır", + "claude.aliases": "Kullanılabilir modeller", + "claude.aliasesHint": "Claude Code'un /model menüsünde görünen modeller.", + "claude.aliasProviderOther": "Diğer", + "claude.loading": "Yükleniyor…", + "claude.loadFail": "Claude ayarları yüklenemedi", + "claude.saved": "Kaydedildi.", + "claude.saveFailed": "Kaydetme başarısız", + "claude.networkError": "Ağ hatası — proxy çalışıyor mu?", + "claude.toggleAria": "Claude bağlantısını değiştir", + "claude.none": "Yok", + + // Combos workspace + "cws.loading": "Kombolar yükleniyor…", + "cws.loadFailed": "Kombolar yüklenemedi.", + "cws.saveFailed": "Kombo kaydedilemedi.", + "cws.removeFailed": "Kombo kaldırılamadı.", + "cws.saved": "Kombo kaydedildi.", + "cws.created": "{model} oluşturuldu.", + "cws.removed": "combo/{id} kaldırıldı.", + "cws.renamed": "{from}, {to} olarak yeniden adlandırıldı.", + "cws.add": "Kombo ekle", + "cws.addTitle": "Kombo ekle", + "cws.addSubtitle": "Sağlayıcılar arasında sanal bir model oluşturun.", + "cws.create": "Kombo oluştur", + "cws.railAria": "Kombo listesi", + "cws.searchPlaceholder": "Kombolarda veya hedeflerde ara…", + "cws.noSearchResults": "Aramanızla eşleşen kombo yok.", + "cws.group.failover": "Yedekli (Failover)", + "cws.group.roundRobin": "Round-robin", + "cws.targetCount": "{count} hedef", + "cws.targetCountOne": "1 hedef", + "cws.overviewTitle": "Kombolar", + "cws.overviewBlurb": "Sağlayıcı/model hedefleri arasında devreden sanal modeller.", + "cws.count.total": "Toplam", + "cws.count.failover": "Failover", + "cws.count.roundRobin": "Round-robin", + "cws.howTitle": "Nasıl çalışır", + "cws.howBody": "Codex'ten kombonun genel model adını isteyin.", + "cws.attentionTitle": "Dikkat gerekiyor", + "cws.attention.empty": "Yapılandırılmış hedef yok", + "cws.attention.few": "Yalnızca bir hedef — geçiş yapılacak yer yok", + "cws.attention.catalogOmitted": "Model kataloğunda eksik", + "cws.emptyTitle": "İlk kombonuzu oluşturun", + "cws.empty.createDesc": "Sanal bir model adlandırın ve iki veya daha fazla arka ucu bağlayın.", + "cws.backToAll": "Tüm kombolara dön", + "cws.allCombos": "Tüm kombolar", + "cws.copyModel": "ID kopyala", + "cws.copied": "Kopyalandı", + "cws.tabsLabel": "Kombo detay bölümleri", + "cws.tab.config": "Konfigürasyon", + "cws.tab.about": "Hakkında", + "cws.strategy": "Strateji", + "cws.strategy.failover": "Yedekli (Failover)", + "cws.strategy.roundRobin": "Round-robin", + "cws.strategy.failoverHint": "Hedefleri sırayla deneyin. İlk hedef hata verirse diğerine geçin.", + "cws.strategy.roundRobinHint": "Trafiği ağırlığa göre dengeli şekilde dağıtın.", + "cws.field.id": "Kombo ID", + "cws.field.idHint": "İstemciler {model} isteyecek", + "cws.field.idInternalHint": "Dahili kombo ID.", + "cws.field.idHintEdit": "Yeniden adlandırmak komboyu yeni bir ID'ye taşır.", + "cws.field.alias": "Genel model adı", + "cws.field.aliasPlaceholder": "deepseek-v4-flash veya üretici/model", + "cws.field.aliasHint": "İsteğe bağlı genel takma ad.", + "cws.field.stickyLimit": "Döndürmeden önceki sabit başarılar", + "cws.field.stickyLimitHint": "Seçilen hedefi bu kadar başarılı istek boyunca tutun.", + "cws.field.defaultEffort": "Varsayılan akıl yürütme", + "cws.field.defaultEffortNone": "Yok (hedef varsayılanı)", + "cws.field.defaultEffortHint": "İstemci çabayı belirttiğinde kullanılır.", + "cws.field.defaultEffortUnsupported": "Bu çaba hedeflerin ortak merdiveninde yok.", + "cws.field.defaultEffortUnsupportedOption": "kesişimde değil", + "cws.targets": "Hedefler", + "cws.targets.failoverHint": "Sıralama önemlidir — birincil olan ilktir.", + "cws.targets.roundRobinHint": "Ağırlıklar seçimi kontrol eder.", + "cws.target.provider": "Sağlayıcı", + "cws.target.model": "Model", + "cws.target.weight": "Ağırlık", + "cws.target.pickProvider": "Sağlayıcı seçin…", + "cws.target.pickProviderFirst": "Önce bir sağlayıcı seçin…", + "cws.target.pickModel": "Model seçin…", + "cws.target.noModels": "Bu sağlayıcı için model yok", + "cws.target.modelPlaceholder": "model ID", + "cws.target.add": "Hedef ekle", + "cws.target.drag": "Yeniden sıralamak için sürükleyin", + "cws.target.moveUp": "Yukarı taşı", + "cws.target.moveDown": "Aşağı taşı", + "cws.aboutTitle": "Çalışma zamanı", + "cws.aboutBody": "Başarısız hedefler kısa süre soğumaya alınır.", + "cws.removeConfirmTitle": "{model} kaldırılsın mı?", + "cws.removeConfirmDesc": "Bu işlem sanal modeli konfigürasyondan kaldırır.", + "cws.unsavedTitle": "Kaydedilmemiş değişiklikler", + "cws.unsavedDesc": "Düzenlemelerden vazgeçip devam edilsin mi?", + "cws.keepEditing": "Düzenlemeye devam et", + "cws.err.missingId": "Kombo ID gereklidir.", + "cws.err.invalidId": "ID harf veya sayı ile başlamalıdır.", + "cws.err.duplicateId": "Bu ID ile bir kombo zaten var.", + "cws.err.invalidAlias": "Takma ad geçerli karakterler içermelidir.", + "cws.err.aliasReservedNamespace": "Takma ad ayrılmış namespace kullanamaz.", + "cws.err.aliasNativeFamily": "Yerel OpenAI isimlerine izin verilmez.", + "cws.err.duplicateAlias": "Başka bir kombo bu takma adı zaten kullanıyor.", + "cws.err.noTargets": "En az bir hedef ekleyin.", + "cws.err.incompleteTarget": "Her hedefin bir sağlayıcısı ve modeli olmalıdır.", + "cws.target.disabled": "{name} (devre dışı)", + "cws.err.reservedNamespace": "Önce fiziksel sağlayıcı yeniden adlandırılmalıdır.", + "cws.err.providerCollision": "Kombo ID'si bir sağlayıcı adı ile çakışıyor.", + "cws.err.unknownProvider": "Her hedef yapılandırılmış bir sağlayıcı kullanmalıdır.", + "cws.err.duplicateTarget": "Aynı hedef yalnızca bir kez görünebilir.", + "cws.err.invalidStickyLimit": "Limit 1 ile 100 arasında bir tam sayı olmalıdır.", + "cws.err.invalidWeight": "Ağırlık 1 ile 10000 arasında olmalıdır.", + "cws.err.noEnabledTarget": "En az bir hedef etkin bir sağlayıcı kullanmalıdır.", + "claude.tabsLabel": "Claude istemcisi", + "claude.tabCode": "Code", + "claude.tabDesktop": "Desktop", + "claudeDesktop.title": "Claude Desktop", + "claudeDesktop.subtitle": "Claude model ailelerini {port} portundaki bir modele yönlendirin.", + "claudeDesktop.importJson": "JSON İçe Aktar", + "claudeDesktop.exportJson": "JSON Dışa Aktar", + "claudeDesktop.loading": "Claude Desktop profili yükleniyor…", + "claudeDesktop.loadFail": "Claude Desktop profili yüklenemedi.", + "claudeDesktop.retry": "Tekrar Dene", + "claudeDesktop.saveFailed": "Claude Desktop profili kaydedilemedi.", + "claudeDesktop.applyFailed": "Profil kaydedildi ancak uygulanamadı.", + "claudeDesktop.updateFailed": "Güncelleme başarısız oldu.", + "claudeDesktop.savedApplied": "Profil kaydedildi ve uygulandı.", + "claudeDesktop.appliedMarkerUnsaved": "Profil uygulandı ancak işaretçi kaydedilmedi.", + "claudeDesktop.savedAppliedAnnounce": "Profil kaydedildi ve uygulandı.", + "claudeDesktop.saved": "Profil kaydedildi.", + "claudeDesktop.savedAnnounce": "Profil kaydedildi.", + "claudeDesktop.exported": "Profil JSON olarak dışa aktarıldı.", + "claudeDesktop.importExpected": "Sürüm 1 profili bekleniyordu.", + "claudeDesktop.importReady": "JSON içe aktarıldı.", + "claudeDesktop.importedAnnounce": "Profil JSON içe aktarıldı.", + "claudeDesktop.importInvalid": "Seçilen dosya geçerli bir profil değil.", + "claudeDesktop.importFailed": "İçe aktarma başarısız oldu. {error}", + "claudeDesktop.moved": "{route}, {family} ailesine taşındı.", + "claudeDesktop.unsaved": "Kaydedilmemiş değişiklikler", + "claudeDesktop.upToDate": "Profil güncel", + "claudeDesktop.saving": "Kaydediliyor…", + "claudeDesktop.applying": "Uygulanıyor…", + "claudeDesktop.saveApply": "Kaydet & uygula", + "claudeDesktop.emptyTitle": "Kullanılabilir model yok", + "claudeDesktop.emptyHint": "Bir sağlayıcı ekleyin veya etkinleştirin.", + "claudeDesktop.assignmentsLabel": "Claude model ailesi atamaları", + "claudeDesktop.family.opus": "Opus", + "claudeDesktop.family.fable": "Fable", + "claudeDesktop.family.sonnet": "Sonnet", + "claudeDesktop.family.haiku": "Haiku", + "claudeDesktop.modelCountOne": "{count} model", + "claudeDesktop.modelCountMany": "{count} model", + "claudeDesktop.chooseDefault": "Bir varsayılan seçin", + "claudeDesktop.temporaryDefault": "Geçici varsayılan", + "claudeDesktop.laneEmpty": "Buraya bir model sürükleyin.", + "claudeDesktop.laneNoMatch": "Bu ailede aramanızla eşleşen model yok.", + "nav.grok": "Grok", + "grok.title": "Grok Build", + "grok.subtitle": "opencodex'in Grok konfigürasyonunuza kaydettiği modeller.", + "grok.loading": "Grok durumu yükleniyor…", + "grok.loadFail": "Grok konfigürasyonu okunamadı.", + "grok.notConfiguredTitle": "Grok Build henüz bağlanmadı", + "grok.notConfiguredHint": "Proxy'yi Grok yüklüyken başlatın.", + "grok.endpoint": "Uç nokta", + "grok.colModel": "Model", + "grok.colAlias": "Grok takma adı", + "grok.colContext": "Bağlam", + "grok.groupNative": "Yerel modeller", + "grok.groupRouted": "Yönlendirilen modeller", + "grok.enabledCount": "{total} modelden {on} tanesi kayıtlı", + "grok.saved": "Seçim kaydedildi.", + "grok.savedApplied": "Seçim kaydedildi ve Grok konfigürasyonuna yazıldı.", + "grok.saveFailed": "Grok seçimi kaydedilemedi.", + "grok.applyFailed": "Seçim kaydedildi ancak Grok güncellenemedi.", + "grok.applySkipped": "Seçim kaydedildi.", + "grok.saveApply": "Kaydet & uygula", + "grok.saving": "Kaydediliyor…", + "grok.applying": "Uygulanıyor…", + "grok.unsaved": "Kaydedilmemiş değişiklikler", + "grok.upToDate": "Seçim güncel", + "grok.toggleModel": "{id} modelini Grok ile kaydet", + "claudeDesktop.available": "Kullanılabilir", + "claudeDesktop.defaultBadge": "Varsayılan", + "claudeDesktop.supports1m": "1M", + "claudeDesktop.unavailable": "Kullanılamıyor", + "claudeDesktop.contextM": "{n}M bağlam", + "claudeDesktop.contextK": "{n}k bağlam", + "claudeDesktop.contextUnknown": "bağlam bilinmiyor", + "claudeDesktop.alias": "Takma Ad", + "claudeDesktop.useAsDefault": "{family} varsayılanı olarak kullan", + "claudeDesktop.moveTo": "Şuraya taşı", + "claudeDesktop.move": "Taşı", + "claudeDesktop.status.applied": "Desktop'a Uygulandı", + "claudeDesktop.status.stale": "Konfigürasyon eski — tekrar uygulayın", + "claudeDesktop.status.notApplied": "Uygulanmadı", + "claudeDesktop.status.notActiveProfile": "Desktop başka bir profil kullanıyor", + "claudeDesktop.status.disabled": "Claude Desktop entegrasyonu kapalı.", + "claudeDesktop.enableApply": "Etkinleştir ve uygula", + "claudeDesktop.health.lastRequest": "Son istek", + "claudeDesktop.health.stats": "{count} istek / {errors} hata", + "claudeDesktop.effort.supported": "çaba", + "claudeDesktop.effort.displayOnly": "çaba (yalnızca ekran)", +}; From 0df9f3a98bccdfae3464860c6d25fac585d48ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:46:11 +0300 Subject: [PATCH 03/16] test(gui): add regression unit test for Turkish i18n locale --- gui/src/i18n/shared.ts | 2 +- gui/src/i18n/tr.ts | 1 + gui/tests/i18n-tr-locale.test.ts | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 gui/tests/i18n-tr-locale.test.ts diff --git a/gui/src/i18n/shared.ts b/gui/src/i18n/shared.ts index 3586b8b3ba..05c14b2f15 100644 --- a/gui/src/i18n/shared.ts +++ b/gui/src/i18n/shared.ts @@ -31,7 +31,7 @@ export function detectInitial(): Locale { const stored = localStorage.getItem(LANG_KEY); if (stored === "en" || stored === "de" || stored === "ko" || stored === "zh" || stored === "ru" || stored === "ja" || stored === "tr") return stored; } catch { /* ignore */ } - const nav = typeof navigator !== "undefined" ? navigator.language.toLowerCase() : "en"; + const nav = typeof navigator !== "undefined" && navigator?.language ? navigator.language.toLowerCase() : "en"; if (nav.startsWith("de")) return "de"; if (nav.startsWith("ko")) return "ko"; if (nav.startsWith("zh")) return "zh"; diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index b364ff1ed9..0be2ed7ee6 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -463,6 +463,7 @@ export const tr: Record = { "models.contextCapped": "350k sınırı", "models.contextCapLabel": "Bağlam sınırı", "models.v2Label": "Alt Ajan", + "models.shadowCallOriginal": "⚠ {models} →", "models.v2DocsLink": "v1 / v2 nedir?", "models.v2Mode_v1": "v1", "models.v2Mode_default": "taban", diff --git a/gui/tests/i18n-tr-locale.test.ts b/gui/tests/i18n-tr-locale.test.ts new file mode 100644 index 0000000000..dc8108046c --- /dev/null +++ b/gui/tests/i18n-tr-locale.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, test } from "bun:test"; +import { DICTS, LOCALES, detectInitial } from "../src/i18n/shared"; +import { en } from "../src/i18n/en"; +import { tr } from "../src/i18n/tr"; + +describe("Turkish (tr) i18n locale", () => { + test("registers tr in LOCALES array with correct metadata", () => { + const trLocale = LOCALES.find(l => l.code === "tr"); + expect(trLocale).toBeDefined(); + expect(trLocale?.name).toBe("Türkçe"); + expect(trLocale?.htmlLang).toBe("tr"); + }); + + test("includes tr in DICTS map", () => { + expect(DICTS.tr).toBe(tr); + }); + + test("tr dictionary covers all TKeys from source-of-truth en catalog", () => { + const enKeys = Object.keys(en); + const missingKeys = enKeys.filter(k => !(k in tr)); + expect(missingKeys).toEqual([]); + }); + + test("detectInitial handles locale fallback cleanly", () => { + const initial = detectInitial(); + expect(initial).toBeDefined(); + expect(typeof initial).toBe("string"); + }); +}); From 7f38b582568f3b400b67c3829fbdd286d6869386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:50:10 +0300 Subject: [PATCH 04/16] docs(tr): resolve CodeRabbit review findings in README.tr.md --- readme/README.tr.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/readme/README.tr.md b/readme/README.tr.md index d7b06ce66b..c31721100a 100644 --- a/readme/README.tr.md +++ b/readme/README.tr.md @@ -1,3 +1,5 @@ +# opencodex +

make codex open!

OpenAI Codex, Claude Code, Claude Desktop & Grok Build için Evrensel Sağlayıcı Proxy'si
İki komut ile yönlendirdiğiniz her LLM'i çalıştırın.

@@ -120,11 +122,11 @@ ocx v2 <...> # çoklu ajan v1/v2 yüzey kontrolleri ocx update [--tag preview] # opencodex'i günceller ``` -Sabitlenmemiş başlatmalar, tercih edilen bağlantı noktası meşgulse başka bir boş bağlantı noktası seçebilir; açık bir `--port` asla atlamaz. Tam referans: [CLI dokümantasyonu](https://opencodex.me/reference/cli/). +Bağlantı noktası açıkça belirtilmeden başlatılırsa, tercih edilen bağlantı noktası meşgul olduğunda başka bir boş bağlantı noktası seçilebilir; `--port` açıkça belirtilirse başka bir bağlantı noktasına geçilmez. Tam referans: [CLI dokümantasyonu](https://opencodex.me/reference/cli/). ### Otomatik başlatma: servis mi shim mi? -Çökme durumunda yeniden başlayan ve her zaman açık bir proxy için **servisi** (`ocx service`) kullanın. Arka plan arka plan işlemi (daemon) olmadan hafif, isteğe bağlı başlatma için **shim**'i (`ocx codex-shim install`) kullanın. `ocx service uninstall` / `ocx codex-shim uninstall` ile kaldırın. +Çökme durumunda yeniden başlayan ve her zaman açık bir proxy için **servisi** (`ocx service`) kullanın. Arka planda sürekli çalışan bir daemon olmadan hafif, isteğe bağlı başlatma için **shim**'i (`ocx codex-shim install`) kullanın. `ocx service uninstall` / `ocx codex-shim uninstall` ile kaldırın. ### Kaldırma From 4badb9e2d6dbda4572bef530738a5ce1ae7f4425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:52:54 +0300 Subject: [PATCH 05/16] test(gui): strengthen detectInitial test assertion for Turkish locale --- gui/tests/i18n-tr-locale.test.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/gui/tests/i18n-tr-locale.test.ts b/gui/tests/i18n-tr-locale.test.ts index dc8108046c..3d8dbc48bb 100644 --- a/gui/tests/i18n-tr-locale.test.ts +++ b/gui/tests/i18n-tr-locale.test.ts @@ -21,9 +21,30 @@ describe("Turkish (tr) i18n locale", () => { expect(missingKeys).toEqual([]); }); - test("detectInitial handles locale fallback cleanly", () => { - const initial = detectInitial(); - expect(initial).toBeDefined(); - expect(typeof initial).toBe("string"); + test("detectInitial detects tr from navigator language", () => { + const origNav = globalThis.navigator; + try { + // @ts-expect-error test isolation mock + globalThis.navigator = { language: "tr-TR" }; + expect(detectInitial()).toBe("tr"); + } finally { + globalThis.navigator = origNav; + } + }); + + test("detectInitial detects tr from stored preference", () => { + const origStorage = globalThis.localStorage; + try { + const store = new Map([["ocx-lang", "tr"]]); + // @ts-expect-error test isolation mock + globalThis.localStorage = { + getItem: (k: string) => store.get(k) ?? null, + setItem: (k: string, v: string) => store.set(k, v), + removeItem: (k: string) => store.delete(k), + }; + expect(detectInitial()).toBe("tr"); + } finally { + globalThis.localStorage = origStorage; + } }); }); From b27400a8d257bfc4d8a052b7332cfe7bcff5c9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:05:49 +0300 Subject: [PATCH 06/16] fix(gui): address Codex review feedback for Turkish locale, formatters, and placeholder parity --- gui/src/components/QuotaBars.tsx | 2 + gui/src/formatUptime.ts | 1 + gui/src/i18n/tr.ts | 64 ++++++++++++++++---------------- gui/tests/i18n-tr-locale.test.ts | 23 ++++++++++++ 4 files changed, 59 insertions(+), 31 deletions(-) diff --git a/gui/src/components/QuotaBars.tsx b/gui/src/components/QuotaBars.tsx index da782d0d02..8389428b3d 100644 --- a/gui/src/components/QuotaBars.tsx +++ b/gui/src/components/QuotaBars.tsx @@ -124,6 +124,8 @@ function bcp47(locale: Locale): string { return "ru-RU"; case "ja": return "ja-JP"; + case "tr": + return "tr-TR"; default: { const _exhaustive: never = locale; return _exhaustive; diff --git a/gui/src/formatUptime.ts b/gui/src/formatUptime.ts index 42ea2689b5..7909ceec83 100644 --- a/gui/src/formatUptime.ts +++ b/gui/src/formatUptime.ts @@ -7,6 +7,7 @@ const UPTIME_UNITS: Record = { "dash.shadowCallWarning": "⚠ Etkinleştirildiğinde, {models} için olan TÜM istekler seçilen modelle değiştirilecektir.", "dash.shadowCallOriginal": "Orijinal", "dash.shadowCallModel": "Yedek model", - "dash.shadowCallTooltip": "Codex App arka planda başlık ve commit mesajı oluşturmak için yardımcı çağrılar yapar. opencodex bu istekleri seçtiğiniz modele yönlendirir.", + "dash.shadowCallTooltip": "Codex App arka planda başlık ve commit mesajı oluşturmak için yardımcı çağrılar yapar (orijinal {models}). Etkinleştirerek bunları seçtiğiniz modele yönlendirebilirsiniz.", "models.shadowCallIntercept": "Gölge Çağrı Yakalama", "models.shadowCallInterceptHint": "Codex App'in başlıklar ve commit mesajları için yaptığı arka plan çağrılarını ({models}) yakalar ve seçtiğiniz modele yönlendirir.", "dash.sidecarBackend": "Arka uç", @@ -365,12 +365,13 @@ export const tr: Record = { "prov.loginRequestFail": "{provider} giriş isteği başarısız oldu", "prov.loginCancelled": "{provider} girişi iptal edildi", "prov.loginTimeout": "{provider} girişi zaman aşımına uğradı.", - "prov.loginOk": "{provider} hesabına giriş yapıldı.", + "prov.loginOk": "{provider} hesabına giriş yapıldı. Proxy'yi başlatın: {cmd}", + "prov.added": "\"{name}\" eklendi. Proxy'yi başlatın: {cmd}", "oauthTos.highTitle": "{provider}: abonelik OAuth riski", "oauthTos.elevatedTitle": "{provider}: gayri resmi OAuth köprüsü", - "oauthTos.anthropicBody": "Claude abonelik OAuth jetonlarını OpenCodex gibi bir proxy üzerinden kullanmak hesabın kısıtlanmasına yol açabilir.", - "oauthTos.highBody": "OpenCodex {provider} bağlantısını üçüncü taraf bir OAuth yolu üzerinden sağlar.", - "oauthTos.elevatedBody": "OpenCodex {provider} bağlantısını resmi olmayan bir yoldan kurar.", + "oauthTos.anthropicBody": "Claude abonelik OAuth jetonlarının OpenCodex gibi üçüncü taraf bir proxy üzerinden doğrudan yeniden kullanılması desteklenen bir Anthropic entegrasyonu değildir ve erişim kısıtlamalarına yol açabilir. Claude aboneliklerini kullanan desteklenen Agent SDK entegrasyonları ayrıdır.", + "oauthTos.highBody": "OpenCodex, {provider} bağlantısını üçüncü taraf bir OAuth yolu üzerinden kurar. Desteklenmeyen kullanım, erişim kısıtlamalarına veya hesabın askıya alınmasına yol açabilir.", + "oauthTos.elevatedBody": "OpenCodex, {provider} bağlantısını resmi olmayan bir OAuth yolu üzerinden kurar. Mümkün olduğunda resmi istemciyi kullanın; sıra dışı veya otomatik trafik kötüye kullanım olarak değerlendirilebilir ve erişim kısıtlanabilir veya askıya alınabilir.", "oauthTos.saferPath": "Daha güvenli seçenek: OpenCodex'te bir API anahtarı yapılandırın.", "oauthTos.acknowledge": "Riski anlıyorum ve OAuth ile devam etmek istiyorum.", "oauthTos.continue": "OAuth ile devam et", @@ -387,7 +388,7 @@ export const tr: Record = { "prov.defaultDisabled": "Varsayılan yapmadan önce bu sağlayıcıyı etkinleştirin.", "prov.updateFail": "Sağlayıcı güncellenemedi.", "prov.networkError": "Ağ hatası. Proxy'nin çalıştığını kontrol edin.", - "prov.added": "\"{name}\" eklendi.", + "prov.added": "\"{name}\" eklendi. Proxy'yi başlatın: {cmd}", "prov.removeConfirm": "\"{name}\" sağlayıcısı kaldırılsın mı?", "prov.hasApiKey": "API anahtarı yapılandırıldı", "prov.hasHeaders": "özel başlıklar yapılandırıldı", @@ -552,9 +553,9 @@ export const tr: Record = { "models.selectedCount": "{n} seçildi", // subagents - "sub.subtitle": "Codex'in alt ajanlarında öne çıkacak modelleri seçin.", + "sub.subtitle": "Codex'in {cmd} komutu, geçersiz kılma olarak yalnızca ilk 5 modeli (önceliğe göre) sunar. Buradan 5 taneye kadar seçin (yerel gpt veya yönlendirilen) ve opencodex bunların katalog önceliğini tam olarak bunların liderlik edeceği şekilde ayarlar. Diğer herhangi bir model tam adıyla çağrılabilir kalır; bu yalnızca neyin gösterileceğini kontrol eder.", "sub.featured": "Öne Çıkarılanlar", - "sub.orderHint": "Burada gösterilen sıra Codex model seçicisinin en üstünü belirler.", + "sub.orderHint": "Burada gösterilen sıralama, Codex model seçicisinin üst kısmındaki 1-5 pozisyonlarını ve {cmd} için varsayılan model adaylarını belirler.", "sub.noneSelected": "Hiçbiri seçilmedi — aşağıdaki listeden seçin.", "sub.models": "Modeller", "sub.search": "Modellerde ara…", @@ -563,7 +564,7 @@ export const tr: Record = { "sub.delegation.model": "İlk çağrılacak model", "sub.delegation.modelHint": "Codex'in iş devrederken ilk ulaştığı model.", "sub.noModels": "Model yok — önce bir sağlayıcı ekleyin.", - "sub.saved": "{n} model kaydedildi.", + "sub.saved": "{n} model kaydedildi. Bunları spawn_agent geçersiz kılmaları olarak görmek için yeni bir Codex oturumu başlatın (veya {cmd} çalıştırın).", "sub.saveFailed": "Kaydetme başarısız", "sub.networkError": "Ağ hatası — proxy çalışıyor mu?", "sub.loadFail": "Modeller yüklenemedi", @@ -601,7 +602,7 @@ export const tr: Record = { "logs.filter.conversation.apply": "Günlükleri filtrele", "logs.conversation.totals": "{requests} istek · {tokens} jeton · {cost}", "logs.conversation.scope": "Toplamlar yalnızca yüklü günlükleri kapsar.", - "logs.conversation.excluded": "({unpriced} fiyatlandırılmamış hariç)", + "logs.conversation.excluded": "({unpriced} fiyatlandırılmamış, {unmetered} ölçülmemiş hariç)", "logs.detail.conversation": "Sohbet", "logs.badge.claude": "Claude", "logs.badge.grok": "Grok", @@ -805,7 +806,7 @@ export const tr: Record = { "storage.cleanup.preset": "{percent}", "storage.cleanup.preview": "Önizleme", "storage.cleanup.confirmTitle": "Arşiv temizliğini onayla", - "storage.cleanup.confirmBody": "{count} arşivlenmiş dosya işlenecek (~{size}).", + "storage.cleanup.confirmBody": "Toplam ~{size} tutan {count} arşivlenmiş oturum dosyası silinsin mi (%{percent} eşiği)?", "storage.cleanup.moreFiles": "…ve {n} tane daha", "storage.cleanup.permanent": "Kalıcı olarak sil (karantinayı atla)", "storage.cleanup.permanentWarn": "Kalıcı silme geri alınamaz.", @@ -824,7 +825,7 @@ export const tr: Record = { "storage.cleanup.err.invalid_digest": "Önizleme özeti eksik veya geçersiz.", "storage.cleanup.err.invalid_mode": "Temizleme modu karantina veya kalıcı olmalıdır.", "storage.cleanup.err.fs_failed": "Dosya sistemi temizliği başarısız oldu.", - "storage.cleanup.err.fs_failed_trash": "Dosya sistemi temizliği başarısız oldu.", + "storage.cleanup.err.fs_failed_trash": "{trashDir} için çöp kutusu işlemi başarısız oldu.", "storage.cleanup.err.db_reconcile_failed": "Codex durum veritabanı güncellenemedi.", "storage.cleanup.err.cleanup_failed": "Temizleme başarısız oldu.", @@ -1115,7 +1116,7 @@ export const tr: Record = { "pws.healthSummary.staleCredentials": "{provider} {account}: eksik kimlik bilgileri.", "pws.removeConfirm": "Kaldır", "pws.removeConfirmBody": "\"{name}\" sağlayıcısı kaldırılsın mı?", - "pws.removeDefaultConfirmBody": "Varsayılan sağlayıcı \"{name}\" kaldırılsın mı?", + "pws.removeDefaultConfirmBody": "Varsayılan sağlayıcı \"{name}\" kaldırılsın mı? \"{defaultProvider}\" varsayılan sağlayıcı olacaktır. Bu işlem geri alınamaz.", "pws.removeConfirmTitle": "Sağlayıcıyı kaldır", "pws.saveSettings": "Kaydet", "pws.saving": "Kaydediliyor…", @@ -1138,8 +1139,8 @@ export const tr: Record = { "pws.capacity.currentAccount": "Mevcut geçerli hesap", "pws.capacity.nextRecovery": "Sonraki kapasite yenilenmesi", "pws.capacity.recoveryShare": "+%{percent} havuz kapasitesi", - "pws.capacity.incomplete": "Eksik kapsam: {excluded} hesap hariç tutuldu", - "pws.capacity.partial": "Kısmi pencere kapsamı", + "pws.capacity.incomplete": "Kısmi pencere kapsamı ({unknown} bilinmeyen, {excluded} hariç tutuldu)", + "pws.capacity.partial": "Kısmi ({total} hesap üzerinden {count} tanesi kota metriği bildiriyor)", "pws.capacity.windowPartial": "Kısmi", "pws.capacity.windowPartialA11y": "{window}: eksik hesap kapsamı", "pws.dashboard.recentlyUsed": "SON KULLANILANLAR", @@ -1248,8 +1249,9 @@ export const tr: Record = { "integrations.dialog.grok.breakage": "Devre dışı bırakmak opencodex model takma adlarını kaldırır.", "integrations.dialog.grok.undo": "Tekrar açmak yeni bir blok yazar.", "integrations.dialog.grok.confirm": "Devre Dışı Bırak", + "integrations.dialog.grok.changes": "Yapılandırma dosyası: {path}", "integrations.dialog.desktop.title": "Claude Desktop entegrasyonu devre dışı bırakılsın mı?", - "integrations.dialog.desktop.changes": "Desktop orijinal profiline dönecek.", + "integrations.dialog.desktop.changes": "Yapılandırma dosyası: {path}", "integrations.dialog.desktop.breakage": "Claude Desktop standart modele geri dönecektir.", "integrations.dialog.desktop.undo": "Tekrar açmak profili yeniden oluşturur.", "integrations.dialog.desktop.restart": "Claude Desktop için yeniden başlatma gereklidir.", @@ -1257,12 +1259,12 @@ export const tr: Record = { "integrations.native.msg.nonLoopbackRemoved": "Grok Build otomatik olarak kaldırıldı.", "integrations.native.msg.nonLoopbackRemovedNoop": "Kaldırılacak blok yoktu.", "integrations.native.msg.nonLoopbackSuperseded": "Başka bir işlem yeni bir blok yazdı.", - "integrations.native.error.orphanedMarker": "Bitiş işareti eksik.", + "integrations.native.error.orphanedMarker": "{path} konumunda yetim opencodex işareti", "integrations.native.error.homeMismatch": "Dizin eşleşmedi.", "integrations.native.error.notInstalled": "Grok Build yüklü değil.", "integrations.native.error.configBusy": "Konfigürasyon meşgul.", - "integrations.native.error.desktopUnsafeMetadata": "Meta veri okunamadı.", - "integrations.native.error.desktopCleanupIncomplete": "Eski kimlik dosyaları kaldı.", + "integrations.native.error.desktopUnsafeMetadata": "{path} konumunda güvensiz meta veri formatı", + "integrations.native.error.desktopCleanupIncomplete": "{paths} için temizleme tamamlanamadı", "integrations.native.msg.desktopDisabled": "Claude Desktop entegrasyonu devre dışı bırakıldı.", "integrations.native.msg.desktopEnabled": "Claude Desktop entegrasyonu etkinleştirildi.", "integrations.state.absent": "Uygulanmadı", @@ -1297,14 +1299,14 @@ export const tr: Record = { "integrations.restore.confirm": "Geri Yükle", "integrations.restore.confirmDrift": "Yedekle ve geri yükle", "integrations.restore.pending": "Geri yükleniyor…", - "integrations.restore.manual": "Otomatik geri yükleme başarısız oldu.", + "integrations.restore.manual": "{reason}: {path} dosyasını el ile geri yükleyin", "integrations.error.load": "Entegrasyon durumu yüklenemedi.", "integrations.error.stale": "Son yenileme başarısız oldu.", "integrations.error.busy": "İşlem devam ediyor.", "integrations.error.conflict": "Konfigürasyon yazıldıktan sonra değişti.", "integrations.error.unsafe": "Konfigürasyon güvenle değiştirilemiyor.", "integrations.error.generic": "Entegrasyon değişikliği başarısız oldu.", - "integrations.error.nonLoopback": "Yalnızca localhost üzerinden ulaşabilir.", + "integrations.error.nonLoopback": "İistemci {client} geri döngü (loopback) dışı", "integrations.status.installed": "Yüklü", "integrations.status.notInstalled": "Yüklü değil", "integrations.status.appliedAt": "Uygulandı", @@ -1316,8 +1318,8 @@ export const tr: Record = { "integrations.bulk.partial": "Bazı istemciler devre dışı bırakılamadı: {clients}", "integrations.bulk.success": "Uygulanan istemci entegrasyonları devre dışı bırakıldı.", "integrations.retention.degraded": "Yedek temizliği geride kaldı.", - "integrations.error.residual": "Dosya ara durumda olabilir.", - "integrations.error.recover": "Yedek konumda.", + "integrations.error.residual": "{path} konumunda {message}", + "integrations.error.recover": "{path} kurtarılırken {message}", "integrations.kind.apply": "Uygulandı", "integrations.kind.disable": "Devre Dışı Bırakıldı", "integrations.kind.refresh": "Güncellendi", @@ -1390,7 +1392,7 @@ export const tr: Record = { "codexAuth.autoSwitchQuotaDesc": "Kota: %{threshold} veya üzeri kullanımda sonraki istek daha az kullanılan bir hesaba geçebilir.", "codexAuth.autoSwitchQuotaOffDesc": "Kullanıma dayalı proaktif geçiş kapalı.", "codexAuth.autoSwitchRoundRobinDesc": "Round-robin bu eşiği kullanmaz.", - "codexAuth.autoSwitchFillFirstDesc": "İlk doldurma modu.", + "codexAuth.autoSwitchFillFirstDesc": "Kullanım %{threshold} eşiğini aşana kadar hesabı doldurun, ardından sonraki kullanılabilir hesaba geçin.", "codexAuth.autoSwitchFillFirstOffDesc": "İlk doldurma modunda kullanım boşaltma noktası yok.", "codexAuth.failureRecoveryNote": "Hata kurtarma ayrıdır.", "codexAuth.autoSwitchThreshold": "Kullanım eşiği", @@ -1409,7 +1411,7 @@ export const tr: Record = { "codexAuth.requestUserInputLoadFailed": "Özellik okunamadı.", "anthropicPool.title": "Claude hesap havuzu (deneysel)", - "anthropicPool.enabledDesc": "429 durumunda hesabı soğutur ve yedek hesaba geçer.", + "anthropicPool.enabledDesc": "Kullanım %{threshold} seviyesine ulaştığında sonraki anahtara geçin.", "anthropicPool.disabledDesc": "Yalnızca aktif Claude hesabını kullanır.", "anthropicPool.experimentalWarning": "Deneysel özelliktir.", "anthropicPool.needTwoAccounts": "Havuzu etkinleştirmeden önce en az iki Claude OAuth hesabı ekleyin.", @@ -1451,7 +1453,7 @@ export const tr: Record = { "accountPool.priorityOption": "{name} ({value})", "accountPool.priorityCustom": "Özel", "accountPool.priorityUpdated": "{email} için seçim sırası güncellendi", - "accountPool.priorityUpdateFailed": "Seçim sırası kaydedilemedi.", + "accountPool.priorityUpdateFailed": "{email} için öncelik güncellenemedi", "codexAuth.switched": "Sonraki istek için {email} seçildi", "codexAuth.loadFailed": "Codex hesap ayarları yüklenemedi.", @@ -1516,7 +1518,7 @@ export const tr: Record = { // api access page "api.title": "API Erişimi", - "api.subtitle": "Dış uygulamalardan opencodex proxy'sine erişmek için üretilen API anahtarlarını kullanın.", + "api.subtitle": "Harici uygulamalardan opencodex proxy'sine erişmek için üretilen API anahtarlarını kullanın. Anahtarlar {authHeader} başlığı üzerinden kimlik doğrulaması yapar; her bir uç noktanın neleri kabul ettiğini görmek için aşağıdaki tabloya bakın.", "api.baseUrl": "Taban URL", "api.responsesEndpoint": "Responses API", "api.chatCompletionsEndpoint": "Chat Completions API", @@ -1615,7 +1617,7 @@ export const tr: Record = { "api.clientConfig.loadFailed": "Model listesi okunamadı.", "api.clientConfig.copiedAnnounce": "İstemci konfigürasyonu JSON panoya kopyalandı.", "api.clientConfig.copyFailed": "Kopyalanamadı.", - "api.clientConfig.downloadedAnnounce": "{filename} indirildi.", + "api.clientConfig.downloadedAnnounce": "{filename} indirildi. Henüz hiçbir şey değişmedi — kendiniz {destination} konumuna birleştirin.", "api.clientConfig.whereDisclosure": "Bu dosya nereye gidiyor", "api.clientConfig.whereBody": "Yukarıdaki hedef genel yoldur.", "api.keysLoadFailed": "API anahtarları yüklenemedi.", @@ -1671,7 +1673,7 @@ export const tr: Record = { "claude.authSource.unknown": "algılanan bir kimlik bilgisi", "claude.systemEnv": "Otomatik bağlan", "claude.systemEnvDesc": "Açık olduğunda terminalde claude çalıştırmak proxy üzerinden geçer.", - "claude.systemEnvUnsupported": "Otomatik bağlan yalnızca macOS üzerinde kullanılabilir.", + "claude.systemEnvUnsupported": "{cmd} için sistem çevre değişkenleri desteklenmiyor.", "claude.systemEnvWarn": "⚠ Terminal uygulamasını tamamen kapatıp yeniden açmalısınız.", "claude.fastMode": "Hızlı Mod (OpenAI)", "claude.fastModeDesc": "OpenAI modelleri için service_tier ayarını kontrol eder.", @@ -1767,7 +1769,7 @@ export const tr: Record = { "cws.field.id": "Kombo ID", "cws.field.idHint": "İstemciler {model} isteyecek", "cws.field.idInternalHint": "Dahili kombo ID.", - "cws.field.idHintEdit": "Yeniden adlandırmak komboyu yeni bir ID'ye taşır.", + "cws.field.idHintEdit": "Yeniden adlandırmak komboyu yeni bir ID'ye taşır. {model} isteyen istemciler yeni ID'yi kullanır.", "cws.field.alias": "Genel model adı", "cws.field.aliasPlaceholder": "deepseek-v4-flash veya üretici/model", "cws.field.aliasHint": "İsteğe bağlı genel takma ad.", @@ -1775,7 +1777,7 @@ export const tr: Record = { "cws.field.stickyLimitHint": "Seçilen hedefi bu kadar başarılı istek boyunca tutun.", "cws.field.defaultEffort": "Varsayılan akıl yürütme", "cws.field.defaultEffortNone": "Yok (hedef varsayılanı)", - "cws.field.defaultEffortHint": "İstemci çabayı belirttiğinde kullanılır.", + "cws.field.defaultEffortHint": "Yalnızca istemci akıl yürütme çabasını belirtmediğinde (atladığında) kullanılır. Seçenekler, seçilen hedeflerin duyurulan çabalarının kesişimidir; katalog çaba meta verisi olmayan hedefler hiçbir seçenek sunmaz.", "cws.field.defaultEffortUnsupported": "Bu çaba hedeflerin ortak merdiveninde yok.", "cws.field.defaultEffortUnsupportedOption": "kesişimde değil", "cws.targets": "Hedefler", diff --git a/gui/tests/i18n-tr-locale.test.ts b/gui/tests/i18n-tr-locale.test.ts index 3d8dbc48bb..807a1093c4 100644 --- a/gui/tests/i18n-tr-locale.test.ts +++ b/gui/tests/i18n-tr-locale.test.ts @@ -21,6 +21,29 @@ describe("Turkish (tr) i18n locale", () => { expect(missingKeys).toEqual([]); }); + test("tr dictionary preserves all interpolation placeholders from en catalog", () => { + const enKeys = Object.keys(en) as Array; + const placeholderRe = /\{([a-zA-Z0-9_]+)\}/g; + const mismatched: string[] = []; + + for (const key of enKeys) { + const enVal = en[key]; + const trVal = tr[key]; + if (typeof enVal !== "string" || typeof trVal !== "string") continue; + + const enPlaceholders = new Set([...enVal.matchAll(placeholderRe)].map(m => m[1])); + const trPlaceholders = new Set([...trVal.matchAll(placeholderRe)].map(m => m[1])); + + for (const ph of enPlaceholders) { + if (!trPlaceholders.has(ph)) { + mismatched.push(`${key}: missing {${ph}}`); + } + } + } + + expect(mismatched).toEqual([]); + }); + test("detectInitial detects tr from navigator language", () => { const origNav = globalThis.navigator; try { From ea277d17d7564b1b3ccea97f1d4ddd4079398607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:17:14 +0300 Subject: [PATCH 07/16] fix(gui): resolve Codex review points for status codes, duplicate keys, and risk warnings --- gui/src/i18n/tr.ts | 12 ++++----- gui/src/status-codes.ts | 42 ++++++++++++++++---------------- gui/tests/i18n-tr-locale.test.ts | 18 ++++++++++++++ 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 3ba4d01228..28e370ad37 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -388,7 +388,6 @@ export const tr: Record = { "prov.defaultDisabled": "Varsayılan yapmadan önce bu sağlayıcıyı etkinleştirin.", "prov.updateFail": "Sağlayıcı güncellenemedi.", "prov.networkError": "Ağ hatası. Proxy'nin çalıştığını kontrol edin.", - "prov.added": "\"{name}\" eklendi. Proxy'yi başlatın: {cmd}", "prov.removeConfirm": "\"{name}\" sağlayıcısı kaldırılsın mı?", "prov.hasApiKey": "API anahtarı yapılandırıldı", "prov.hasHeaders": "özel başlıklar yapılandırıldı", @@ -1140,7 +1139,7 @@ export const tr: Record = { "pws.capacity.nextRecovery": "Sonraki kapasite yenilenmesi", "pws.capacity.recoveryShare": "+%{percent} havuz kapasitesi", "pws.capacity.incomplete": "Kısmi pencere kapsamı ({unknown} bilinmeyen, {excluded} hariç tutuldu)", - "pws.capacity.partial": "Kısmi ({total} hesap üzerinden {count} tanesi kota metriği bildiriyor)", + "pws.capacity.partial": "Kısmi ({count} hesap kota metriği bildiriyor)", "pws.capacity.windowPartial": "Kısmi", "pws.capacity.windowPartialA11y": "{window}: eksik hesap kapsamı", "pws.dashboard.recentlyUsed": "SON KULLANILANLAR", @@ -1245,11 +1244,10 @@ export const tr: Record = { "integrations.detail.grokModels": "{count} model bağlandı", "integrations.detail.grokAbsent": "Konfigürasyonda opencodex bloğu yok", "integrations.dialog.grok.title": "Grok Build entegrasyonu devre dışı bırakılsın mı?", - "integrations.dialog.grok.changes": "Yalnızca opencodex tarafından işaretlenen blok kaldırılacak.", + "integrations.dialog.grok.changes": "Yalnızca opencodex tarafından yönetilen blok kaldırılacak. Yapılandırma dosyası: {path}", "integrations.dialog.grok.breakage": "Devre dışı bırakmak opencodex model takma adlarını kaldırır.", "integrations.dialog.grok.undo": "Tekrar açmak yeni bir blok yazar.", "integrations.dialog.grok.confirm": "Devre Dışı Bırak", - "integrations.dialog.grok.changes": "Yapılandırma dosyası: {path}", "integrations.dialog.desktop.title": "Claude Desktop entegrasyonu devre dışı bırakılsın mı?", "integrations.dialog.desktop.changes": "Yapılandırma dosyası: {path}", "integrations.dialog.desktop.breakage": "Claude Desktop standart modele geri dönecektir.", @@ -1306,7 +1304,7 @@ export const tr: Record = { "integrations.error.conflict": "Konfigürasyon yazıldıktan sonra değişti.", "integrations.error.unsafe": "Konfigürasyon güvenle değiştirilemiyor.", "integrations.error.generic": "Entegrasyon değişikliği başarısız oldu.", - "integrations.error.nonLoopback": "İistemci {client} geri döngü (loopback) dışı", + "integrations.error.nonLoopback": "{client} istemcisi geri döngü (loopback) dışında bir adreste. Yerel entegrasyonlar yalnızca loopback (127.0.0.1 veya ::1) üzerinden düzenlenebilir. Bir API anahtarı ayarlayın veya yerel erişim kullanın.", "integrations.status.installed": "Yüklü", "integrations.status.notInstalled": "Yüklü değil", "integrations.status.appliedAt": "Uygulandı", @@ -1413,7 +1411,7 @@ export const tr: Record = { "anthropicPool.title": "Claude hesap havuzu (deneysel)", "anthropicPool.enabledDesc": "Kullanım %{threshold} seviyesine ulaştığında sonraki anahtara geçin.", "anthropicPool.disabledDesc": "Yalnızca aktif Claude hesabını kullanır.", - "anthropicPool.experimentalWarning": "Deneysel özelliktir.", + "anthropicPool.experimentalWarning": "Deneysel: Claude OAuth hesaplarını döndürmek desteklenmeyen bir kullanım yoludur ve Anthropic hesap kısıtlamalarına veya hesabın askıya alınmasına yol açabilir. Aynı kuruluşu paylaşan hesaplar oran limitlerini paylaşır ve döndürmeden ek kapasite kazanmaz. Riskleri anlamıyorsanız kapalı tutun.", "anthropicPool.needTwoAccounts": "Havuzu etkinleştirmeden önce en az iki Claude OAuth hesabı ekleyin.", "anthropicPool.threshold": "Yeni oturum kullanım eşiği", "anthropicPool.thresholdAria": "Yeni oturum kullanım eşiği, yüzde", @@ -1527,7 +1525,7 @@ export const tr: Record = { "api.endpointNote": "OpenAI uyumlu istemcilerle taban URL'yi kullanın.", "api.endpointsTitle": "Uç noktalar", "api.authTitle": "Kimlik Doğrulama", - "api.authLoopback": "Loopback bağlantıları (127.0.0.1) kimlik doğrulamasını atlar.", + "api.authLoopback": "Geri döngü (loopback) bağlantıları (127.0.0.1 / ::1) kimlik doğrulamasını atlar. Harici/ağ istemcileri x-opencodex-api-key veya Authorization başlığında bir ocx_ API anahtarı ya da OPENCODEX_API_AUTH_TOKEN göndermelidir.", "api.authBaseUrlNote": "İstemcileri taban URL ile yapılandırın.", "api.newKeyTitle": "Yeni anahtar oluşturuldu", "api.newKeyNote": "Bu anahtarı şimdi kopyalayın — tekrar gösterilmeyecektir.", diff --git a/gui/src/status-codes.ts b/gui/src/status-codes.ts index 6be2fc757b..dab9e2b0b6 100644 --- a/gui/src/status-codes.ts +++ b/gui/src/status-codes.ts @@ -1,6 +1,6 @@ export interface StatusCodeInfo { label: string; description: string } -type Locale = "en" | "de" | "ko" | "zh" | "ru" | "ja"; +type Locale = "en" | "de" | "ko" | "zh" | "ru" | "ja" | "tr"; type LocalizedInfo = Record; const STATUS_CODES: Record = { @@ -10,8 +10,8 @@ const STATUS_CODES: Record = { zh: { label: "错误请求", description: "代理无法理解该请求。重试前请检查模型、消息结构、标头和 JSON 正文。" }, de: { label: "Ungültige Anfrage", description: "Der Proxy konnte die Anfrage nicht verstehen. Prüfe Modell, Nachrichtenformat, Header und JSON-Body vor einem erneuten Versuch." }, ru: { label: "Некорректный запрос", description: "Прокси не смог интерпретировать запрос. Перед повторной попыткой проверьте модель, формат сообщений, заголовки и тело JSON." }, - ja: { label: "不正なリクエスト", description: "プロキシがリクエストを解釈できませんでした。再試行前にモデル、メッセージ形式、ヘッダー、JSON 本文を確認してください。" }, + tr: { label: "Hatalı istek", description: "Proxy isteği anlayamadı. Yeniden denemeden önce modeli, mesaj yapısını, başlıkları ve JSON gövdesini kontrol edin." }, }, 401: { en: { label: "Unauthorized", description: "Credentials are missing, expired, or invalid. Re-login or refresh the account/provider credentials used by opencodex." }, @@ -19,8 +19,8 @@ const STATUS_CODES: Record = { zh: { label: "未授权", description: "凭据缺失、已过期或无效。请重新登录,或刷新 opencodex 使用的账号/提供商凭据。" }, de: { label: "Nicht autorisiert", description: "Anmeldedaten fehlen, sind abgelaufen oder ungültig. Melde dich erneut an oder aktualisiere die von opencodex genutzten Konto-/Anbieter-Zugangsdaten." }, ru: { label: "Не авторизован", description: "Учётные данные отсутствуют, истекли или недействительны. Войдите заново или обновите учётные данные аккаунта или провайдера, которые использует opencodex." }, - ja: { label: "認証が必要", description: "認証情報が不在・期限切れ・無効です。opencodex が使用するアカウントまたはプロバイダー認証情報を再ログインまたは更新してください。" }, + tr: { label: "Yetkisiz erişim", description: "Kimlik bilgileri eksik, süresi dolmuş veya geçersiz. opencodex tarafından kullanılan hesap veya sağlayıcı kimlik bilgilerini yeniden doğrulayın." }, }, 402: { en: { label: "Payment required", description: "The upstream provider rejected the request because billing, credits, or plan access is not available. Add credits, update billing, or switch provider." }, @@ -28,8 +28,8 @@ const STATUS_CODES: Record = { zh: { label: "需要付款", description: "上游提供商因账单、额度或套餐权限不可用而拒绝了请求。请充值、更新账单信息或切换提供商。" }, de: { label: "Zahlung erforderlich", description: "Der Upstream-Anbieter hat die Anfrage abgelehnt, weil Abrechnung, Guthaben oder Planzugriff nicht verfügbar ist. Guthaben aufladen, Abrechnung aktualisieren oder Anbieter wechseln." }, ru: { label: "Требуется оплата", description: "Вышестоящий провайдер отклонил запрос из-за проблем с оплатой, кредитами или доступом по тарифному плану. Пополните баланс, обновите платёжные данные или переключитесь на другого провайдера." }, - ja: { label: "支払いが必要", description: "課金、クレジット、プランアクセスが利用できないため上流プロバイダーがリクエストを拒否しました。クレジット追加、支払い情報更新、プロバイダー切替が必要です。" }, + tr: { label: "Ödeme gerekli", description: "Yukarı akış sağlayıcısı faturalandırma, kredi veya plan erişimi bulunmadığından isteği reddetti. Kredi ekleyin, ödeme bilgilerini güncelleyin veya sağlayıcı değiştirin." }, }, 403: { en: { label: "Forbidden", description: "The account is authenticated but not allowed to use this model or operation. Often a plan/subscription gate (e.g. Ollama Cloud Pro), org policy, or model permission — not necessarily a bad API key." }, @@ -37,8 +37,8 @@ const STATUS_CODES: Record = { zh: { label: "禁止访问", description: "账号已认证,但无权使用此模型或操作。常见原因是套餐/订阅限制(例如 Ollama Cloud Pro)、组织策略或模型权限——不一定是 API 密钥无效。" }, de: { label: "Verboten", description: "Das Konto ist authentifiziert, darf dieses Modell oder diese Operation aber nicht nutzen. Oft Plan-/Abo-Sperre (z. B. Ollama Cloud Pro), Organisationsrichtlinie oder Modellrecht — nicht zwingend ein ungültiger API-Key." }, ru: { label: "Доступ запрещён", description: "Аккаунт аутентифицирован, но не имеет права использовать эту модель или операцию. Часто причина — ограничение тарифа или подписки (например, Ollama Cloud Pro), политика организации или права доступа к модели, а не обязательно неверный API-ключ." }, - ja: { label: "アクセス禁止", description: "アカウントは認証済みですがこのモデルや操作の使用が許可されていません。多くはプラン/サブスクリプション制限(例: Ollama Cloud Pro)、組織ポリシー、モデル権限であり、API キーが不正とは限りません。" }, + tr: { label: "Erişim yasaklandı", description: "Hesabın kimliği doğrulandı ancak bu modeli veya işlemi kullanma izni yok. Genellikle plan/abonelik sınırı (örn. Ollama Cloud Pro), organizasyon politikası veya model izni kaynaklıdır." }, }, 404: { en: { label: "Not found", description: "The requested route, model, account, or upstream resource was not found. Verify the model name and opencodex provider configuration." }, @@ -46,8 +46,8 @@ const STATUS_CODES: Record = { zh: { label: "未找到", description: "找不到请求的路由、模型、账号或上游资源。请确认模型名称和 opencodex 提供商配置。" }, de: { label: "Nicht gefunden", description: "Die angeforderte Route, das Modell, das Konto oder die Upstream-Ressource wurde nicht gefunden. Prüfe Modellname und opencodex-Anbieterkonfiguration." }, ru: { label: "Не найдено", description: "Запрошенный маршрут, модель, аккаунт или вышестоящий ресурс не найден. Проверьте имя модели и конфигурацию провайдера в opencodex." }, - ja: { label: "見つかりません", description: "要求されたルート、モデル、アカウント、上流リソースが見つかりませんでした。モデル名と opencodex プロバイダー設定を確認してください。" }, + tr: { label: "Bulunamadı", description: "İstenen rota, model, hesap veya yukarı akış kaynağı bulunamadı. Model adını ve opencodex sağlayıcı yapılandırmasını doğrulayın." }, }, 408: { en: { label: "Request timeout", description: "The request took too long before the proxy or upstream provider could complete it. Retry with a smaller request or a different provider." }, @@ -55,8 +55,8 @@ const STATUS_CODES: Record = { zh: { label: "请求超时", description: "代理或上游提供商未能在限定时间内完成请求。请缩小请求后重试,或切换提供商。" }, de: { label: "Anfrage-Timeout", description: "Die Anfrage dauerte zu lange, bevor Proxy oder Upstream-Anbieter sie abschließen konnten. Mit kleinerer Anfrage oder anderem Anbieter erneut versuchen." }, ru: { label: "Тайм-аут запроса", description: "Обработка запроса заняла слишком много времени, и прокси или вышестоящий провайдер не успел её завершить. Повторите попытку с меньшим запросом или через другого провайдера." }, - ja: { label: "リクエストタイムアウト", description: "プロキシまたは上流プロバイダーがリクエストを完了する前に時間切れになりました。より小さいリクエストで再試行するか、別のプロバイダーに切り替えてください。" }, + tr: { label: "İstek zaman aşımı", description: "Proxy veya yukarı akış sağlayıcısı isteği tamamlayamadan zaman aşımına uğradı. Daha küçük bir istek veya farklı bir sağlayıcı ile tekrar deneyin." }, }, 409: { en: { label: "Conflict", description: "The request conflicts with the current account, session, or provider state. Refresh the session or retry after the active operation finishes." }, @@ -64,8 +64,8 @@ const STATUS_CODES: Record = { zh: { label: "状态冲突", description: "请求与当前账号、会话或提供商状态冲突。请刷新会话,或等待当前操作完成后重试。" }, de: { label: "Konflikt", description: "Die Anfrage kollidiert mit dem aktuellen Konto-, Sitzungs- oder Anbieterstatus. Sitzung aktualisieren oder nach Abschluss der laufenden Operation erneut versuchen." }, ru: { label: "Конфликт", description: "Запрос конфликтует с текущим состоянием аккаунта, сессии или провайдера. Обновите сессию или повторите попытку после завершения текущей операции." }, - ja: { label: "状態の衝突", description: "リクエストが現在のアカウント、セッション、プロバイダー状態と衝突しています。セッションを更新するか、進行中の操作が終わった後に再試行してください。" }, + tr: { label: "Durum çakışması", description: "İstek mevcut hesap, oturum veya sağlayıcı durumuyla çakışıyor. Oturumu yenileyin veya aktif işlem bittikten sonra tekrar deneyin." }, }, 413: { en: { label: "Request too large", description: "The prompt, attachments, or generated payload exceeds a proxy or upstream limit. Reduce tokens, file size, or conversation history." }, @@ -73,8 +73,8 @@ const STATUS_CODES: Record = { zh: { label: "请求过大", description: "提示、附件或生成的负载超过了代理或上游限制。请减少 token、文件大小或对话历史。" }, de: { label: "Anfrage zu groß", description: "Prompt, Anhänge oder generierte Nutzlast überschreiten ein Proxy- oder Upstream-Limit. Tokens, Dateigröße oder Verlauf reduzieren." }, ru: { label: "Слишком большой запрос", description: "Промпт, вложения или сформированная полезная нагрузка превышают лимит прокси или вышестоящего провайдера. Сократите количество токенов, размер файлов или историю диалога." }, - ja: { label: "リクエストが大きすぎます", description: "プロンプト、添付ファイル、生成ペイロードがプロキシまたは上流の制限を超えました。トークン、ファイルサイズ、会話履歴を減らしてください。" }, + tr: { label: "İstek çok büyük", description: "İstemi, ekler veya oluşturulan veri proxy ya da yukarı akış sınırını aşıyor. Jeton sayısını, dosya boyutunu veya sohbet geçmişini azaltın." }, }, 422: { en: { label: "Invalid content", description: "The provider accepted the request format but rejected its contents. Check model options, tool definitions, message roles, and unsupported fields." }, @@ -82,8 +82,8 @@ const STATUS_CODES: Record = { zh: { label: "内容无效", description: "提供商接受了请求格式,但拒绝了其中的内容。请检查模型选项、工具定义、消息角色和不支持的字段。" }, de: { label: "Ungültiger Inhalt", description: "Der Anbieter akzeptierte das Anfrageformat, lehnte den Inhalt aber ab. Prüfe Modelloptionen, Tool-Definitionen, Nachrichtenrollen und nicht unterstützte Felder." }, ru: { label: "Недопустимое содержимое", description: "Провайдер принял формат запроса, но отклонил его содержимое. Проверьте параметры модели, определения инструментов, роли сообщений и неподдерживаемые поля." }, - ja: { label: "内容の検証失敗", description: "プロバイダーはリクエスト形式を受け付けましたが内容を拒否しました。モデルオプション、ツール定義、メッセージロール、未サポートのフィールドを確認してください。" }, + tr: { label: "Geçersiz içerik", description: "Sağlayıcı istek formatını kabul etti ancak içeriğini reddetti. Model seçeneklerini, araç tanımlarını, mesaj rollerini ve desteklenmeyen alanları kontrol edin." }, }, 424: { en: { label: "Provider dependency failed", description: "A required upstream dependency failed while opencodex was routing the request. Retry later or switch to another configured provider." }, @@ -91,8 +91,8 @@ const STATUS_CODES: Record = { zh: { label: "提供商依赖失败", description: "opencodex 路由请求时,必需的上游依赖失败。请稍后重试,或切换到另一个已配置的提供商。" }, de: { label: "Anbieter-Abhängigkeit fehlgeschlagen", description: "Eine erforderliche Upstream-Abhängigkeit ist fehlgeschlagen, während opencodex die Anfrage geroutet hat. Später erneut versuchen oder zu einem anderen Anbieter wechseln." }, ru: { label: "Сбой зависимости провайдера", description: "Необходимая вышестоящая зависимость дала сбой, пока opencodex маршрутизировал запрос. Повторите попытку позже или переключитесь на другого настроенного провайдера." }, - ja: { label: "プロバイダー依存の失敗", description: "opencodex がリクエストをルーティング中に必要な上流依存が失敗しました。後で再試行するか、別の設定済みプロバイダーに切り替えてください。" }, + tr: { label: "Sağlayıcı bağımlılığı başarısız", description: "opencodex isteği yönlendirirken gerekli bir yukarı akış bağımlılığı başarısız oldu. Daha sonra tekrar deneyin veya başka bir sağlayıcıya geçin." }, }, 429: { en: { label: "Rate limited", description: "The upstream provider rate or quota limit has been reached. Wait for the quota window to reset or switch account/provider." }, @@ -100,8 +100,8 @@ const STATUS_CODES: Record = { zh: { label: "限流", description: "已达到上游提供商的速率或额度限制。请等待额度窗口重置,或切换账号/提供商。" }, de: { label: "Ratenlimit erreicht", description: "Das Raten- oder Kontingentlimit des Upstream-Anbieters ist erreicht. Auf Reset des Kontingentfensters warten oder Konto/Anbieter wechseln." }, ru: { label: "Превышен лимит запросов", description: "Достигнут лимит скорости или квота вышестоящего провайдера. Дождитесь сброса окна квоты или переключитесь на другой аккаунт или провайдера." }, - ja: { label: "レート制限", description: "上流プロバイダーのレートまたはクォータ制限に達しました。クォータウィンドウがリセットされるまで待つか、アカウント/プロバイダーを切り替えてください。" }, + tr: { label: "Oran sınırı aşıldı", description: "Yukarı akış sağlayıcısının hız veya kota sınırına ulaşıldı. Kota penceresinin sıfırlanmasını bekleyin ya da hesap/sağlayıcı değiştirin." }, }, 499: { en: { label: "Client closed request", description: "The client disconnected or canceled the request before opencodex finished routing it. Retry if the cancellation was accidental." }, @@ -109,8 +109,8 @@ const STATUS_CODES: Record = { zh: { label: "客户端已取消", description: "opencodex 完成路由前,客户端已断开连接或取消请求。如果不是有意取消,请重试。" }, de: { label: "Client hat Anfrage geschlossen", description: "Der Client hat die Verbindung getrennt oder die Anfrage abgebrochen, bevor opencodex das Routing abgeschlossen hat. Bei versehentlichem Abbruch erneut versuchen." }, ru: { label: "Запрос закрыт клиентом", description: "Клиент отключился или отменил запрос до того, как opencodex завершил его маршрутизацию. Если отмена была случайной, повторите попытку." }, - ja: { label: "クライアントがリクエストをクローズ", description: "opencodex がルーティングを終える前にクライアントが切断またはキャンセルしました。意図しないキャンセルなら再試行してください。" }, + tr: { label: "İstemci isteği kapattı", description: "opencodex yönlendirmeyi bitirmeden önce istemci bağlantıyı kesti veya isteği iptal etti. İptal kazara yapıldıysa tekrar deneyin." }, }, 500: { en: { label: "Proxy error", description: "opencodex hit an internal error while handling the request. Retry once, then check proxy logs if it repeats." }, @@ -118,8 +118,8 @@ const STATUS_CODES: Record = { zh: { label: "代理错误", description: "opencodex 处理请求时发生内部错误。请先重试一次;如果重复出现,请检查代理日志。" }, de: { label: "Proxy-Fehler", description: "opencodex ist bei der Anfragebearbeitung auf einen internen Fehler gestoßen. Einmal erneut versuchen, bei Wiederholung Proxy-Logs prüfen." }, ru: { label: "Ошибка прокси", description: "В opencodex произошла внутренняя ошибка при обработке запроса. Повторите попытку один раз; если ошибка повторяется, проверьте логи прокси." }, - ja: { label: "プロキシエラー", description: "opencodex がリクエスト処理中に内部エラーに遭遇しました。1 回再試行し、繰り返す場合はプロキシログを確認してください。" }, + tr: { label: "Proxy hatası", description: "opencodex isteği işlerken dahili bir hatayla karşılaştı. Bir kez tekrar deneyin, tekrarlarsa proxy günlüklerini kontrol edin." }, }, 502: { en: { label: "Bad upstream response", description: "The upstream provider returned an invalid or failed response through the proxy. Retry or route the request to another provider." }, @@ -127,8 +127,8 @@ const STATUS_CODES: Record = { zh: { label: "上游响应错误", description: "上游提供商通过代理返回了无效或失败的响应。请重试,或将请求路由到其他提供商。" }, de: { label: "Ungültige Upstream-Antwort", description: "Der Upstream-Anbieter lieferte über den Proxy eine ungültige oder fehlgeschlagene Antwort. Erneut versuchen oder zu einem anderen Anbieter routen." }, ru: { label: "Некорректный ответ провайдера", description: "Вышестоящий провайдер вернул через прокси недействительный или ошибочный ответ. Повторите попытку или направьте запрос другому провайдеру." }, - ja: { label: "上流レスポンス不良", description: "上流プロバイダーがプロキシ経由で無効または失敗したレスポンスを返しました。再試行するか、リクエストを別のプロバイダーにルーティングしてください。" }, + tr: { label: "Kötü yukarı akış yanıtı", description: "Yukarı akış sağlayıcısı proxy üzerinden geçersiz veya başarısız bir yanıt döndürdü. Tekrar deneyin veya isteği başka bir sağlayıcıya yönlendirin." }, }, 503: { en: { label: "Provider unavailable", description: "The proxy or upstream provider is temporarily unavailable or overloaded. Wait briefly, then retry or switch provider." }, @@ -136,8 +136,8 @@ const STATUS_CODES: Record = { zh: { label: "提供商不可用", description: "代理或上游提供商暂时不可用或过载。请稍后重试,或切换提供商。" }, de: { label: "Anbieter nicht verfügbar", description: "Proxy oder Upstream-Anbieter ist vorübergehend nicht verfügbar oder überlastet. Kurz warten, dann erneut versuchen oder Anbieter wechseln." }, ru: { label: "Провайдер недоступен", description: "Прокси или вышестоящий провайдер временно недоступен или перегружен. Немного подождите, затем повторите попытку или смените провайдера." }, - ja: { label: "プロバイダー利用不可", description: "プロキシまたは上流プロバイダーが一時的に利用不可または過負荷です。少し待ってから再試行するか、プロバイダーを切り替えてください。" }, + tr: { label: "Sağlayıcı kullanılamıyor", description: "Proxy veya yukarı akış sağlayıcısı geçici olarak kullanılamıyor veya aşırı yüklü. Kısa bir süre bekleyip tekrar deneyin ya da sağlayıcı değiştirin." }, }, 504: { en: { label: "Upstream timeout", description: "The upstream provider did not respond before the proxy timeout. Retry with a smaller request or choose a faster provider." }, @@ -145,8 +145,8 @@ const STATUS_CODES: Record = { zh: { label: "上游超时", description: "上游提供商未在代理超时前响应。请缩小请求后重试,或选择响应更快的提供商。" }, de: { label: "Upstream-Timeout", description: "Der Upstream-Anbieter antwortete nicht vor dem Proxy-Timeout. Mit kleinerer Anfrage erneut versuchen oder schnelleren Anbieter wählen." }, ru: { label: "Тайм-аут вышестоящего провайдера", description: "Вышестоящий провайдер не ответил до истечения тайм-аута прокси. Повторите попытку с меньшим запросом или выберите более быстрого провайдера." }, - ja: { label: "上流タイムアウト", description: "上流プロバイダーがプロキシタイムアウト前に応答しませんでした。より小さいリクエストで再試行するか、より速いプロバイダーを選んでください。" }, + tr: { label: "Yukarı akış zaman aşımı", description: "Yukarı akış sağlayıcısı proxy zaman aşımı süresinden önce yanıt vermedi. Daha küçük bir istekle tekrar deneyin veya daha hızlı bir sağlayıcı seçin." }, }, 529: { en: { label: "Provider overloaded", description: "The upstream provider is overloaded or capacity-limited. Wait and retry, or switch to another account/provider." }, @@ -154,8 +154,8 @@ const STATUS_CODES: Record = { zh: { label: "提供商过载", description: "上游提供商过载或容量受限。请等待后重试,或切换到其他账号/提供商。" }, de: { label: "Anbieter überlastet", description: "Der Upstream-Anbieter ist überlastet oder kapazitätsbegrenzt. Warten und erneut versuchen oder anderes Konto/Anbieter nutzen." }, ru: { label: "Провайдер перегружен", description: "Вышестоящий провайдер перегружен или ограничен по мощности. Подождите и повторите попытку либо переключитесь на другой аккаунт или провайдера." }, - ja: { label: "プロバイダー過負荷", description: "上流プロバイダーが過負荷または容量制限されています。待ってから再試行するか、別のアカウント/プロバイダーに切り替えてください。" }, + tr: { label: "Sağlayıcı aşırı yüklü", description: "Yukarı akış sağlayıcısı aşırı yüklü veya kapasitesi sınırlı. Bekleyip tekrar deneyin veya başka bir hesap/sağlayıcıya geçin." }, }, }; @@ -166,8 +166,8 @@ const GENERIC_STATUS: { client: LocalizedInfo; server: LocalizedInfo } = { zh: { label: "请求错误", description: "代理或上游提供商拒绝了该请求。请检查请求结构、凭据、模型名称和提供商配置。" }, de: { label: "Anfragefehler", description: "Der Proxy oder Upstream-Anbieter hat die Anfrage abgelehnt. Prüfe Anfrageformat, Anmeldedaten, Modellname und Anbieterkonfiguration." }, ru: { label: "Ошибка запроса", description: "Прокси или вышестоящий провайдер отклонил запрос. Проверьте структуру запроса, учётные данные, имя модели и конфигурацию провайдера." }, - ja: { label: "リクエストエラー", description: "プロキシまたは上流プロバイダーがリクエストを拒否しました。リクエスト形式、認証情報、モデル名、プロバイダー設定を確認してください。" }, + tr: { label: "İstek hatası", description: "Proxy veya yukarı akış sağlayıcısı isteği reddetti. İstek yapısını, kimlik bilgilerini, model adını ve sağlayıcı yapılandırmasını kontrol edin." }, }, server: { en: { label: "Server or upstream error", description: "opencodex or an upstream provider failed while processing the request. Retry later or route the request to another provider." }, @@ -175,13 +175,13 @@ const GENERIC_STATUS: { client: LocalizedInfo; server: LocalizedInfo } = { zh: { label: "服务器或上游错误", description: "opencodex 或上游提供商处理请求时失败。请稍后重试,或将请求路由到其他提供商。" }, de: { label: "Server- oder Upstream-Fehler", description: "opencodex oder ein Upstream-Anbieter ist bei der Anfragebearbeitung fehlgeschlagen. Später erneut versuchen oder zu einem anderen Anbieter routen." }, ru: { label: "Ошибка сервера или провайдера", description: "opencodex или вышестоящий провайдер завершил обработку запроса с ошибкой. Повторите попытку позже или направьте запрос другому провайдеру." }, - ja: { label: "サーバーまたは上流エラー", description: "opencodex または上流プロバイダーがリクエスト処理中に失敗しました。後で再試行するか、リクエストを別のプロバイダーにルーティングしてください。" }, + tr: { label: "Sunucu veya yukarı akış hatası", description: "opencodex veya bir yukarı akış sağlayıcısı isteği işlerken başarısız oldu. Daha sonra tekrar deneyin veya isteği başka bir sağlayıcıya yönlendirin." }, }, }; function normalizeLocale(locale: string): Locale { - return locale === "de" || locale === "ko" || locale === "zh" || locale === "ru" || locale === "ja" ? locale : "en"; + return locale === "de" || locale === "ko" || locale === "zh" || locale === "ru" || locale === "ja" || locale === "tr" ? locale : "en"; } export function statusCodeInfo(code: number, locale: string): StatusCodeInfo | null { diff --git a/gui/tests/i18n-tr-locale.test.ts b/gui/tests/i18n-tr-locale.test.ts index 807a1093c4..9bfd5896d1 100644 --- a/gui/tests/i18n-tr-locale.test.ts +++ b/gui/tests/i18n-tr-locale.test.ts @@ -44,6 +44,24 @@ describe("Turkish (tr) i18n locale", () => { expect(mismatched).toEqual([]); }); + test("tr.ts has no duplicate key declarations", async () => { + const text = await Bun.file(new URL("../src/i18n/tr.ts", import.meta.url)).text(); + const keyRe = /^\s*"([^"]+)":/gm; + const seen = new Set(); + const duplicates: string[] = []; + + for (const match of text.matchAll(keyRe)) { + const key = match[1]; + if (seen.has(key)) { + duplicates.push(key); + } else { + seen.add(key); + } + } + + expect(duplicates).toEqual([]); + }); + test("detectInitial detects tr from navigator language", () => { const origNav = globalThis.navigator; try { From 92ffd95d5e49a492c9eb9d668f78afec12eca5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:23:19 +0300 Subject: [PATCH 08/16] fix(gui): address CodeRabbit review suggestions for uptime formatting and test isolation --- gui/src/formatUptime.ts | 2 +- gui/tests/i18n-tr-locale.test.ts | 33 +++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/gui/src/formatUptime.ts b/gui/src/formatUptime.ts index 7909ceec83..c15d292687 100644 --- a/gui/src/formatUptime.ts +++ b/gui/src/formatUptime.ts @@ -7,7 +7,7 @@ const UPTIME_UNITS: Record { test("registers tr in LOCALES array with correct metadata", () => { @@ -31,13 +32,14 @@ describe("Turkish (tr) i18n locale", () => { const trVal = tr[key]; if (typeof enVal !== "string" || typeof trVal !== "string") continue; - const enPlaceholders = new Set([...enVal.matchAll(placeholderRe)].map(m => m[1])); - const trPlaceholders = new Set([...trVal.matchAll(placeholderRe)].map(m => m[1])); + const enPlaceholders = [...enVal.matchAll(placeholderRe)].map(m => m[1]).sort(); + const trPlaceholders = [...trVal.matchAll(placeholderRe)].map(m => m[1]).sort(); - for (const ph of enPlaceholders) { - if (!trPlaceholders.has(ph)) { - mismatched.push(`${key}: missing {${ph}}`); - } + if ( + enPlaceholders.length !== trPlaceholders.length || + enPlaceholders.some((ph, index) => ph !== trPlaceholders[index]) + ) { + mismatched.push(`${key}: placeholder parity mismatch (en: [${enPlaceholders}], tr: [${trPlaceholders}])`); } } @@ -62,14 +64,31 @@ describe("Turkish (tr) i18n locale", () => { expect(duplicates).toEqual([]); }); - test("detectInitial detects tr from navigator language", () => { + test("formatUptime correctly formats seconds, minutes, hours, and days in Turkish", () => { + expect(formatUptime(45, "tr")).toBe("45 sn"); + expect(formatUptime(300, "tr")).toBe("5 dk"); + expect(formatUptime(3600, "tr")).toBe("1 saat"); + expect(formatUptime(3660, "tr")).toBe("1 saat 1 dk"); + expect(formatUptime(86400, "tr")).toBe("1 gün"); + expect(formatUptime(90000, "tr")).toBe("1 gün 1 saat"); + }); + + test("detectInitial detects tr from navigator language cleanly isolated from localStorage", () => { const origNav = globalThis.navigator; + const origStorage = globalThis.localStorage; try { // @ts-expect-error test isolation mock globalThis.navigator = { language: "tr-TR" }; + // @ts-expect-error test isolation mock + globalThis.localStorage = { + getItem: () => null, + setItem: () => {}, + removeItem: () => {}, + }; expect(detectInitial()).toBe("tr"); } finally { globalThis.navigator = origNav; + globalThis.localStorage = origStorage; } }); From d705d48323534be7bed8ee57cb482b9110f92fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:37:42 +0300 Subject: [PATCH 09/16] fix(gui): move uptime labels to shared i18n locale catalog --- gui/src/formatUptime.ts | 12 +++++++++--- gui/src/i18n/de.ts | 4 ++++ gui/src/i18n/en.ts | 4 ++++ gui/src/i18n/ja.ts | 4 ++++ gui/src/i18n/ko.ts | 4 ++++ gui/src/i18n/ru.ts | 4 ++++ gui/src/i18n/tr.ts | 4 ++++ gui/src/i18n/zh.ts | 4 ++++ 8 files changed, 37 insertions(+), 3 deletions(-) diff --git a/gui/src/formatUptime.ts b/gui/src/formatUptime.ts index c15d292687..732a6bb6bf 100644 --- a/gui/src/formatUptime.ts +++ b/gui/src/formatUptime.ts @@ -1,6 +1,6 @@ -import type { Locale } from "./i18n/shared"; +import { DICTS, type Locale } from "./i18n/shared"; -const UPTIME_UNITS: Record = { +const FALLBACK_UNITS: Record = { en: { day: "d", hour: "h", minute: "m", second: "s" }, de: { day: "T", hour: "Std", minute: "Min", second: "Sek" }, ko: { day: "일", hour: "시간", minute: "분", second: "초" }, @@ -12,7 +12,13 @@ const UPTIME_UNITS: Record = { "nav.dashboard": "Übersicht", + "uptime.day": "T", + "uptime.hour": "Std", + "uptime.minute": "Min", + "uptime.second": "Sek", "nav.startup": "Startsicherheit", "nav.providers": "Anbieter", "nav.models": "Modelle", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 7d1c7964ec..4bc58d8772 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -8,6 +8,10 @@ export const en = { // sidebar / nav / common "nav.dashboard": "Dashboard", + "uptime.day": "d", + "uptime.hour": "h", + "uptime.minute": "m", + "uptime.second": "s", "nav.startup": "Startup", "nav.providers": "Providers", "nav.models": "Models", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index b569245dbb..3ac13a8fba 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -6,6 +6,10 @@ import type { TKey } from "./en"; export const ja: Record = { // sidebar / nav / common "nav.dashboard": "ダッシュボード", + "uptime.day": "日", + "uptime.hour": "時間", + "uptime.minute": "分", + "uptime.second": "秒", "nav.startup": "起動安全性", "nav.providers": "プロバイダー", "nav.models": "モデル", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 92918b62e3..df49b087ed 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -6,6 +6,10 @@ import type { TKey } from "./en"; export const ko: Record = { // sidebar / nav / common "nav.dashboard": "대시보드", + "uptime.day": "일", + "uptime.hour": "시간", + "uptime.minute": "분", + "uptime.second": "초", "nav.startup": "시작 안전성", "nav.providers": "프로바이더", "nav.models": "모델", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index b78d79adda..55ed6b4407 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -6,6 +6,10 @@ import type { TKey } from "./en"; export const ru: Record = { // sidebar / nav / common "nav.dashboard": "Дашборд", + "uptime.day": "д", + "uptime.hour": "ч", + "uptime.minute": "мин", + "uptime.second": "с", "nav.startup": "Безопасность запуска", "nav.providers": "Провайдеры", "nav.models": "Модели", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 28e370ad37..f17af80f3a 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -7,6 +7,10 @@ import type { TKey } from "./en"; export const tr: Record = { // sidebar / nav / common "nav.dashboard": "Gösterge Paneli", + "uptime.day": " gün", + "uptime.hour": " saat", + "uptime.minute": " dk", + "uptime.second": " sn", "nav.startup": "Başlatma Güvenliği", "nav.providers": "Sağlayıcılar", "nav.models": "Modeller", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index dff991dc61..74e4fc522f 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -6,6 +6,10 @@ import type { TKey } from "./en"; export const zh: Record = { // sidebar / nav / common "nav.dashboard": "仪表盘", + "uptime.day": "天", + "uptime.hour": "小时", + "uptime.minute": "分钟", + "uptime.second": "秒", "nav.startup": "启动安全", "nav.providers": "提供方", "nav.models": "模型", From 105715be8ac747358ac050fc57e6d0433b7ba071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:39:08 +0300 Subject: [PATCH 10/16] ci: trigger fresh CI run for PR #1235 From 1d0a8647e4dfd74a14e7322b937ea6c0cbf0c5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:40:50 +0300 Subject: [PATCH 11/16] fix(gui): address Codex review points for translation precision and root test isolation --- gui/src/formatUptime.ts | 12 +++--------- gui/src/i18n/tr.ts | 16 ++++++++-------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/gui/src/formatUptime.ts b/gui/src/formatUptime.ts index 732a6bb6bf..c15d292687 100644 --- a/gui/src/formatUptime.ts +++ b/gui/src/formatUptime.ts @@ -1,6 +1,6 @@ -import { DICTS, type Locale } from "./i18n/shared"; +import type { Locale } from "./i18n/shared"; -const FALLBACK_UNITS: Record = { +const UPTIME_UNITS: Record = { en: { day: "d", hour: "h", minute: "m", second: "s" }, de: { day: "T", hour: "Std", minute: "Min", second: "Sek" }, ko: { day: "일", hour: "시간", minute: "분", second: "초" }, @@ -12,13 +12,7 @@ const FALLBACK_UNITS: Record = { "dash.stopFailed": "Proxy durdurulamadı (HTTP {status}).", "dash.stopping": "Durduruluyor…", "dash.codexAutoStart": "opencodex'i Codex ile başlat", - "dash.codexAutoStartHint": "Yüklü bir shim'in ocx ensure çalıştırmasına izin verir.", + "dash.codexAutoStartHint": "Yüklü bir shim'in ocx ensure çalıştırmasına izin verir. Arka plan servisi veya yeniden başlatma koruması kurmaz; sistem durumu için Başlatma Güvenliği'ne bakın.", "dash.searchModel": "Arama yan araç modeli", "dash.searchModelHint": "OpenAI dışı yönlendirilen modellerde web_search için kullanılan model. ChatGPT girişi gerektirir.", "dash.searchReasoning": "Arama akıl yürütme çabası", @@ -475,10 +475,10 @@ export const tr: Record = { "models.v2ModeDesc_v1": "Tüm modeller → v1 yüzeyi", "models.v2ModeDesc_default": "Yukarı akış varsayılanları", "models.v2ModeDesc_v2": "Tüm modeller → v2 yüzeyi", - "models.v2Help": "Tüm modeller için çoklu ajan yüzeyini kontrol eder.", + "models.v2Help": "v1 alt ajanları birincil modelle sınırlandırır; base standart ajan sınırlarını devralır; v2 tam çoklu ajan orkestrasyonunu etkinleştirir.", "dash.multiAgent": "Alt Ajan", "models.v2Conflict": "[agents] max_threads ayarlanmış — config.toml dosyasından kaldırın", - "models.v2Applied": "Alt ajan modu güncellendi", + "models.v2Applied": "Çoklu ajan modu güncellendi. Yeni oturumlar bu modu kullanacaktır; model seçiciyi yenilemek için Codex uygulamasını yeniden başlatın.", "models.v2ThreadsLabel": "Maksimum iş parçacığı", "models.v2ThreadsDefault": "varsayılan (4)", "models.v2ThreadsApplied": "İş parçacığı limiti güncellendi", @@ -827,7 +827,7 @@ export const tr: Record = { "storage.cleanup.err.referenced_history": "Seçilen arşivler hâlâ geçmiş tarafından referans gösteriliyor.", "storage.cleanup.err.invalid_digest": "Önizleme özeti eksik veya geçersiz.", "storage.cleanup.err.invalid_mode": "Temizleme modu karantina veya kalıcı olmalıdır.", - "storage.cleanup.err.fs_failed": "Dosya sistemi temizliği başarısız oldu.", + "storage.cleanup.err.fs_failed": "Dosya sistemi temizliği başarısız oldu. Bazı dosyalar başarısızlıktan önce taşınmış olabilir; CODEX_HOME/.trash veya hedef dizini inceleyin.", "storage.cleanup.err.fs_failed_trash": "{trashDir} için çöp kutusu işlemi başarısız oldu.", "storage.cleanup.err.db_reconcile_failed": "Codex durum veritabanı güncellenemedi.", "storage.cleanup.err.cleanup_failed": "Temizleme başarısız oldu.", @@ -1126,7 +1126,7 @@ export const tr: Record = { "pws.settingsSaved": "Ayarlar kaydedildi.", "pws.accountModeSaved": "Hesap modu kaydedildi.", "pws.accountModeFailed": "Hesap modu değiştirilemedi.", - "pws.accountModeConfirm": "OpenAI hesap modunu değiştirmek istediğinize emin misiniz?", + "pws.accountModeConfirm": "OpenAI hesap modu değiştirilsin mi? Aktif oturumlar hedef hesap havuzuna yönlendirilecek ve kota yeni mod altında birikecektir.", "pws.settingsUnsavedBar": "Kaydedilmemiş değişiklikleriniz var.", "pws.unsavedLeaveBody": "Kaydedilmemiş değişiklikleriniz var. Ayrılmadan önce kaydetmek ister misiniz?", "pws.unsavedLeaveTitle": "Kaydedilmemiş değişiklikler", @@ -1192,7 +1192,7 @@ export const tr: Record = { "modal.forwardHintPrefix": "Anahtar gerekmez — proxy kimlik bilgilerinizi iletir", "modal.forwardCredentials": "codex girişi", "modal.forwardHintSuffix": "kimlik bilgilerini bu sağlayıcıya iletir.", - "modal.localHint": "API anahtarı saklanmaz.", + "modal.localHint": "Yerel mod, Cursor'ın statik kamuya açık kataloğunu kaydeder. Canlı taşıma, dosya ve kabuk (shell) araçları denetlenene kadar kapalı kalır.", "modal.getApiKey": "{label} API anahtarınızı alın", "modal.apiKey": "API anahtarı", "modal.apiKeyTransport": "API anahtar başlığı", @@ -1553,7 +1553,7 @@ export const tr: Record = { "api.workspace.keyDetails": "Anahtar detayları", "api.workspace.keyPrefix": "Anahtar ön eki", "api.workspace.deleteKey": "Anahtarı sil", - "api.workspace.deleteConfirm": "Bu anahtarı silmek istediğinize emin misiniz?", + "api.workspace.deleteConfirm": "Bu API anahtarı silinsin mi? Bunu kullanan istemciler erişimi anında kaybedecektir. Bu işlem geri alınamaz.", "api.workspace.usageExamples": "Kullanım örnekleri", "api.copyUrlHint": "URL kopyalamak için tıklayın", "api.urlCopied": "URL kopyalandı", @@ -1637,7 +1637,7 @@ export const tr: Record = { "api.key.renaming": "Kaydediliyor…", "api.key.renameFailed": "Yeniden adlandırılamadı.", "api.key.deleting": "Siliniyor…", - "api.key.copyFailed": "Kopyalanamadı.", + "api.key.copyFailed": "Otomatik kopyalanamadı. Kapatmadan önce anahtarı manuel olarak seçip kopyalayın — tekrar gösterilmeyecektir.", "api.attribution.title": "Atfedilen kullanım", "api.attribution.requests7d": "Son 7 gün istekleri", "api.attribution.totalRequests": "Toplam atfedilen istekler", From 1110cd60ed42de4883b589e4f5fe71a975dc60f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:29:00 +0300 Subject: [PATCH 12/16] fix(gui): sync upstream additions for prov.loginSameAccount and vision-reasoning-labels --- gui/src/i18n/tr.ts | 1 + gui/src/i18n/vision-reasoning-labels.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 7cd7a3728d..a061839fc6 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -369,6 +369,7 @@ export const tr: Record = { "prov.loginRequestFail": "{provider} giriş isteği başarısız oldu", "prov.loginCancelled": "{provider} girişi iptal edildi", "prov.loginTimeout": "{provider} girişi zaman aşımına uğradı.", + "prov.loginSameAccount": "Hâlâ aynı {provider} hesabı — tarayıcıda hesap değiştirin, ardından tekrar Hesap Ekle'yi deneyin.", "prov.loginOk": "{provider} hesabına giriş yapıldı. Proxy'yi başlatın: {cmd}", "prov.added": "\"{name}\" eklendi. Proxy'yi başlatın: {cmd}", "oauthTos.highTitle": "{provider}: abonelik OAuth riski", diff --git a/gui/src/i18n/vision-reasoning-labels.ts b/gui/src/i18n/vision-reasoning-labels.ts index 2a9d5f43cf..735d0e7838 100644 --- a/gui/src/i18n/vision-reasoning-labels.ts +++ b/gui/src/i18n/vision-reasoning-labels.ts @@ -9,6 +9,7 @@ const VISION_REASONING_LABELS = { zh: { low: "低", medium: "中", high: "高", xhigh: "极高", max: "最大" }, ru: { low: "Низкий", medium: "Средний", high: "Высокий", xhigh: "Очень высокий", max: "Максимальный" }, ja: { low: "低", medium: "中", high: "高", xhigh: "非常に高い", max: "最大" }, + tr: { low: "Düşük", medium: "Orta", high: "Yüksek", xhigh: "Çok yüksek", max: "Maksimum" }, } satisfies Record>; /** Localized display label for the wire-level vision reasoning value. */ From 025b219c47e38a15e02132c7db5311026065410a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Sun, 9 Aug 2026 13:56:07 +0300 Subject: [PATCH 13/16] fix(gui): address maintainer Wibias review points for test-safe descriptor mocking, semantic translation pass, and dev merge --- gui/src/i18n/tr.ts | 103 ++++++++++++++++++------------- gui/tests/i18n-tr-locale.test.ts | 49 +++++++++------ 2 files changed, 90 insertions(+), 62 deletions(-) diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index a061839fc6..1182e6a2d5 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -370,8 +370,8 @@ export const tr: Record = { "prov.loginCancelled": "{provider} girişi iptal edildi", "prov.loginTimeout": "{provider} girişi zaman aşımına uğradı.", "prov.loginSameAccount": "Hâlâ aynı {provider} hesabı — tarayıcıda hesap değiştirin, ardından tekrar Hesap Ekle'yi deneyin.", - "prov.loginOk": "{provider} hesabına giriş yapıldı. Proxy'yi başlatın: {cmd}", - "prov.added": "\"{name}\" eklendi. Proxy'yi başlatın: {cmd}", + "prov.loginOk": "{provider} hesabına giriş yapıldı. Modellerini listelemek için {cmd} çalıştırın (veya canlı olarak uygulanır).", + "prov.added": "\"{name}\" eklendi. Modellerini listelemek için {cmd} çalıştırın (veya canlı olarak uygulanır).", "oauthTos.highTitle": "{provider}: abonelik OAuth riski", "oauthTos.elevatedTitle": "{provider}: gayri resmi OAuth köprüsü", "oauthTos.anthropicBody": "Claude abonelik OAuth jetonlarının OpenCodex gibi üçüncü taraf bir proxy üzerinden doğrudan yeniden kullanılması desteklenen bir Anthropic entegrasyonu değildir ve erişim kısıtlamalarına yol açabilir. Claude aboneliklerini kullanan desteklenen Agent SDK entegrasyonları ayrıdır.", @@ -1220,6 +1220,7 @@ export const tr: Record = { "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", "integrations.tab.pi": "Pi", + "integrations.tab.omp": "OMP", "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", @@ -1249,25 +1250,25 @@ export const tr: Record = { "integrations.detail.grokModels": "{count} model bağlandı", "integrations.detail.grokAbsent": "Konfigürasyonda opencodex bloğu yok", "integrations.dialog.grok.title": "Grok Build entegrasyonu devre dışı bırakılsın mı?", - "integrations.dialog.grok.changes": "Yalnızca opencodex tarafından yönetilen blok kaldırılacak. Yapılandırma dosyası: {path}", - "integrations.dialog.grok.breakage": "Devre dışı bırakmak opencodex model takma adlarını kaldırır.", - "integrations.dialog.grok.undo": "Tekrar açmak yeni bir blok yazar.", + "integrations.dialog.grok.changes": "Yalnızca {path} dosyasında opencodex tarafından işaretlenen blok kaldırılacaktır. Blok dışında yazılan içerik değişmeden kalır.", + "integrations.dialog.grok.breakage": "Devre dışı bırakmak, opencodex model takma adlarını Grok Build'den kaldırır. xAI hesabınızla kullanılan modeller kullanılabilir kalır.", + "integrations.dialog.grok.undo": "opencodex bir geri döngü (loopback) adresinde çalışıyorsa, bunu tekrar açmak şu anda mevcut olan modellerden yeni bir blok yazar.", "integrations.dialog.grok.confirm": "Devre Dışı Bırak", "integrations.dialog.desktop.title": "Claude Desktop entegrasyonu devre dışı bırakılsın mı?", - "integrations.dialog.desktop.changes": "Yapılandırma dosyası: {path}", - "integrations.dialog.desktop.breakage": "Claude Desktop standart modele geri dönecektir.", - "integrations.dialog.desktop.undo": "Tekrar açmak profili yeniden oluşturur.", - "integrations.dialog.desktop.restart": "Claude Desktop için yeniden başlatma gereklidir.", + "integrations.dialog.desktop.changes": "Eğer {path} opencodex tarafından yönetilen bir ağ geçidi profili içeriyorsa, Desktop önce kimlik bilgisiz yeni bir standart profil seçer, ardından eski profili ve yedeği kaldırır.", + "integrations.dialog.desktop.breakage": "Claude Desktop, opencodex üzerinden yönlendirilen modeller yerine standart Claude'a geri dönecektir.", + "integrations.dialog.desktop.undo": "Bunu tekrar açmak, kaydedilmiş model atamalarınızdan opencodex profilini yeniden oluşturur.", + "integrations.dialog.desktop.restart": "Claude Desktop bu yapılandırmayı yalnızca açılışta okur. Değişikliğin yürürlüğe girmesi için uygulamayı tamamen kapatıp yeniden açın.", "integrations.dialog.desktop.confirm": "Devre Dışı Bırak", - "integrations.native.msg.nonLoopbackRemoved": "Grok Build otomatik olarak kaldırıldı.", - "integrations.native.msg.nonLoopbackRemovedNoop": "Kaldırılacak blok yoktu.", - "integrations.native.msg.nonLoopbackSuperseded": "Başka bir işlem yeni bir blok yazdı.", - "integrations.native.error.orphanedMarker": "{path} konumunda yetim opencodex işareti", - "integrations.native.error.homeMismatch": "Dizin eşleşmedi.", - "integrations.native.error.notInstalled": "Grok Build yüklü değil.", - "integrations.native.error.configBusy": "Konfigürasyon meşgul.", - "integrations.native.error.desktopUnsafeMetadata": "{path} konumunda güvensiz meta veri formatı", - "integrations.native.error.desktopCleanupIncomplete": "{paths} için temizleme tamamlanamadı", + "integrations.native.msg.nonLoopbackRemoved": "Grok Build yalnızca opencodex bir geri döngü (loopback) adresinde çalışırken otomatik olarak kaydolabilir. Loopback'i işaret eden önceki blok kaldırıldı.", + "integrations.native.msg.nonLoopbackRemovedNoop": "Grok Build yalnızca opencodex bir geri döngü (loopback) adresinde çalışırken otomatik olarak kaydolabilir. Kaldırılacak önceki bir blok yoktu.", + "integrations.native.msg.nonLoopbackSuperseded": "Grok Build yalnızca opencodex bir geri döngü adresi üzerinde çalışırken otomatik kaydedilebilir. Bu sırada başka bir işlem yeni bir blok yazdı, bu nedenle şu an dosyadaki blok bu istek tarafından oluşturulmadı.", + "integrations.native.error.orphanedMarker": "{path} bir opencodex başlangıç işaretine sahip ancak bitiş işareti yok. opencodex bloğunun nerede bittiğini belirleyemediği için dosya değiştirilmeden bırakıldı.", + "integrations.native.error.homeMismatch": "Yüklü servis dizini mevcut ev dizini ile uyuşmuyor, bu nedenle dosya değiştirilmeden bırakıldı.", + "integrations.native.error.notInstalled": "Grok Build yüklü değil, bu nedenle değiştirilecek bir şey yok.", + "integrations.native.error.configBusy": "Yapılandırma başka bir yerde kaydediliyor ve değiştirilemedi. Kısa süre sonra tekrar deneyin.", + "integrations.native.error.desktopUnsafeMetadata": "{path} konumundaki Claude Desktop meta verileri güvenle okunamadı, bu nedenle kütüphanesi değiştirilmedi.", + "integrations.native.error.desktopCleanupIncomplete": "Claude Desktop standart moda yönlendirildi, ancak eski opencodex kimlik bilgisi dosyaları şu konumda kalmaya devam ediyor: {paths}.", "integrations.native.msg.desktopDisabled": "Claude Desktop entegrasyonu devre dışı bırakıldı.", "integrations.native.msg.desktopEnabled": "Claude Desktop entegrasyonu etkinleştirildi.", "integrations.state.absent": "Uygulanmadı", @@ -1333,6 +1334,7 @@ export const tr: Record = { "integrations.semantics.openclaw": "Anında uygulanır.", "integrations.semantics.kimi": "Uygulamak için yeniden başlatın.", "integrations.semantics.gajae": "Yeni oturuma uygulanır.", + "integrations.semantics.omp": "Kataloğu yüklemek için OMP'yi yeniden başlatın.", "codexAuth.mainAccount": "Ana Hesap", "codexAuth.codexApp": "Codex Uygulaması", "codexAuth.appLogin": "Uygulama girişi", @@ -1342,6 +1344,15 @@ export const tr: Record = { "codexAuth.accountModePoolDesc": "Ana giriş ve eklenen hesaplar burada döner.", "codexAuth.accountModeDirect": "Doğrudan mod", "codexAuth.accountModeDirectDesc": "İstekler yalnızca ana girişi kullanır.", + "codexAuth.accountPickerTitle": "Model seçiciden belirli bir Codex hesabını hedefleyin", + "codexAuth.accountPickerOffDesc": "Etkinleştirildiğinde, sıradan GPT seçici satırlarının yerini her hesap seçicisi için bir giriş alır, böylece çıkış yapmadan bir konuşma için tam hesabı seçebilirsiniz. Kapatılması hiçbir hesabı kaldırmaz.", + "codexAuth.accountPickerOnDesc": "Her seçici, kayıtlı bir hesap için genel bir etikettir. Bunu seçmek, konuşmayı eşlenen hesaba kilitler: asla dönmez veya yedek hesaba geçmez ve aktif Havuz hesabını değiştirmez.", + "codexAuth.accountPickerCompatibility": "Yerleşik Codex Uygulama girişinin kendi seçicisi vardır; oluşturulan haritalar normalde bunu main olarak adlandırır ve gerektiğinde main-2 gibi çakışmasız bir son ek kullanır. Eklenen hesaplar kararlı ve gizlilik açısından güvenli etiketler alırken, özel seçici etiketleri değişmeden kalır. Mevcut konuşmalar ve kaydedilmiş model seçimleri yönlendirilmeye devam eder. Bunu kapatmak oluşturulan girişleri gizler ancak seçicileri ve tam rotaları korur. Yalın GPT model kimlikleri Havuz veya Doğrudan davranışlarını sürdürür.", + "codexAuth.accountPickerUpdated": "Hesap hedefleme güncellendi.", + "codexAuth.accountPickerUpdateFailed": "Hesap hedefleme güncellenemedi. Son doğrulanan ayar gösteriliyor.", + "codexAuth.accountPickerLoadFailed": "Hesap hedefleme ayarı yüklenemedi.", + "codexAuth.accountPickerRefreshFailed": "Bu ayar yenilenemedi. Son doğrulanan değer hâlâ gösteriliyor.", + "codexAuth.catalogRefreshPending": "Değişiklik kaydedildi ancak Codex model kataloğunun yenilenmesi bekleniyor. Yeniden denemek için ocx sync çalıştırın.", "codexAuth.openaiMissing": "Yerleşik OpenAI sağlayıcısı yapılandırılmamış.", "codexAuth.openaiDisabled": "Yerleşik OpenAI sağlayıcısı devre dışı.", "codexAuth.openaiUnavailableDesc": "OpenAI hesaplarınız kullanılabilir.", @@ -1613,7 +1624,7 @@ export const tr: Record = { "api.clientConfig.jsonLabel": "{client} konfigürasyon JSON", "api.clientConfig.destination": "Hedef dosya", "api.clientConfig.envHint": "Başlatmadan önce anahtarı ayarlayın", - "api.clientConfig.mergeWarning": "Bunu hedef dosyaya birleştirin.", + "api.clientConfig.mergeWarning": "Bunu hedef dosyaya birleştirin. Dosyanın üzerine yazıp değiştirmek, diğer sağlayıcılarınızı ve MCP ayarlarınızı silecektir.", "api.clientConfig.modelCount": "{count} model dışa aktarıldı", "api.clientConfig.missingLimits": "{total} modelden {count} tanesi bağlam sınırı olmadan gönderildi.", "api.clientConfig.noKeyYet": "{env} arkasında henüz anahtar yok.", @@ -1622,7 +1633,8 @@ export const tr: Record = { "api.clientConfig.copyFailed": "Kopyalanamadı.", "api.clientConfig.downloadedAnnounce": "{filename} indirildi. Henüz hiçbir şey değişmedi — kendiniz {destination} konumuna birleştirin.", "api.clientConfig.whereDisclosure": "Bu dosya nereye gidiyor", - "api.clientConfig.whereBody": "Yukarıdaki hedef genel yoldur.", + "api.clientConfig.whereBody": "Yukarıdaki hedef genel (global) yoldur. Çalışma dizinindeki projeye özel bir konfigürasyon dosyası buna öncelik eder ve istemci anahtarı bu dosyadan değil, konfigürasyonda belirtilen çevre değişkeninden okur.", + "api.clientConfig.clientOmp": "OMP", "api.keysLoadFailed": "API anahtarları yüklenemedi.", "api.createFailed": "API anahtarı oluşturulamadı.", "api.deleteFailed": "API anahtarı silinemedi.", @@ -1684,14 +1696,14 @@ export const tr: Record = { "claude.fastOn": "AÇIK", "claude.fastOff": "KAPALI", "claude.autoContext": "Otomatik büyük bağlam kullan", - "claude.autoContextDesc": "1M işaretlemesinin nereye kadar gideceğini kontrol eder.", - "claude.autoContextInert": "Pasif durumdadır.", + "claude.autoContextDesc": "1M işaretinin ne kadar ileri gideceğini kontrol eder. AÇIK: 200k jeton üzerindeki her model (GPT modelleri vb.) 1M aralığı kazanır. KAPALI: yalnızca gerçek 1M modelleri alır.", + "claude.autoContextInert": "Pasif durumdadır çünkü konfigürasyon dosyasında eski bir bağlam boyutu değeri (maxContextTokens) var. Yeniden etkinleştirmek için oradan kaldırın.", "claude.autoCompactWindow": "Otomatik özetleme noktası", "claude.autoCompactDefault": "350k (varsayılan)", - "claude.autoCompactWindowDesc": "Sohbet bu noktaya ulaştığında eski mesajlar özetlenir.", - "claude.autoCompactWindowWarn": "Bunu değiştirmek GPT modellerini bozabilir.", + "claude.autoCompactWindowDesc": "Sohbet bu noktaya ulaştığında eski mesajlar özetlenir. Her modelin kendi sınırını asla aşmaz, bu nedenle 200k modeller etkilenmez.", + "claude.autoCompactWindowWarn": "Bunu değiştirmek GPT modellerini bozabilir — bir modelin gerçek sınırından daha yüksek ayarlanırsa, sohbetler özet devreye girmeden önce hata verecektir.", "claude.injectAgents": "Alt ajanları otomatik kaydet", - "claude.injectAgentsDesc": "Alt ajanlar sekmesindeki modelleri Claude Code ajanları olarak kaydeder.", + "claude.injectAgentsDesc": "Alt Ajanlar sekmesinde seçilen modelleri (ve mevcut varsayılan modeli) çağrılabilir Claude Code ajanları (ocx-*) olarak kaydeder. Sonraki oturumdan itibaren uygulanır.", "claude.webSearchSidecar": "Web arama yan araç geçersiz kılması", "claude.webSearchSidecarHint": "Claude Code istekleri için ana web arama sidecar'ını geçersiz kılın.", "claude.visionSidecar": "Görsel yan araç geçersiz kılması", @@ -1699,13 +1711,13 @@ export const tr: Record = { "claude.useMainSetting": "Ana ayarı kullan", "claude.sidecarModelPlaceholder": "Ana ayar modeli", "claude.quickstart": "Başlarken", - "claude.quickstartHint": "{cmd} Claude Code'u proxy üzerinden açar.", + "claude.quickstartHint": "{cmd} Claude Code'u proxy üzerinden açar. claude.ai girişiniz aktif kalır.", "claude.manualEnv": "Manuel kurulum (gelişmiş)", "claude.smallFastModel": "Arka plan yardımcı modeli", - "claude.smallFastModelHint": "Claude Code'un sohbet özetleri için kullandığı model.", - "claude.smallFastModelAccurateHint": "Claude Code'un arka plan işleri için kullandığı model.", - "claude.smallFastModelUnsetOption": "Claude Code seçsin (yerel model)", - "claude.smallFastModelNativeWarning": "Ayarlanmadığında yerel model kullanılabilir.", + "claude.smallFastModelHint": "Claude Code'un sohbet özetleri ve konu tespiti gibi arka plan işleri için kullandığı model. haiku alt ajan takma adı da bunu kullanır. Boş = Claude varsayılanı (Haiku).", + "claude.smallFastModelAccurateHint": "Claude Code'un sohbet özetleri ve konu tespiti gibi arka plan işleri için kullandığı model. haiku alt ajan takma adı da bunu kullanır.", + "claude.smallFastModelUnsetOption": "Bırakın Claude Code seçsin (yerel model)", + "claude.smallFastModelNativeWarning": "Ayarlanmadığında, OpenCodex yardımcı model geçersiz kılmalarını boş bırakır. Claude Code kendi yerel Sonnet modelini kullanabilir ve bu durum yerel sağlayıcınızdan ücret alınmasına yol açabilir.", "claude.slotUnset": "Claude varsayılanını kullan", "claude.modelMap": "Model yakalama", "claude.modelMapHint": "Belirli bir model isteklerini yakalar ve seçtiğiniz modele yönlendirir.", @@ -1750,11 +1762,11 @@ export const tr: Record = { "cws.count.failover": "Failover", "cws.count.roundRobin": "Round-robin", "cws.howTitle": "Nasıl çalışır", - "cws.howBody": "Codex'ten kombonun genel model adını isteyin.", + "cws.howBody": "Codex'ten kombonun kamuya açık model adını isteyin. Bir ad olmadan varsayılan combo/ şeklindedir. OpenCodex bir hedef seçer ve yalnızca yeniden denenebilir yukarı akış hatalarında atlar. Hiçbir hedef kullanılabilir kalmazsa, istek küresel varsayılan sağlayıcıyı kullanmak yerine kapalı olarak başarısız olur (fail closed).", "cws.attentionTitle": "Dikkat gerekiyor", "cws.attention.empty": "Yapılandırılmış hedef yok", "cws.attention.few": "Yalnızca bir hedef — geçiş yapılacak yer yok", - "cws.attention.catalogOmitted": "Model kataloğunda eksik", + "cws.attention.catalogOmitted": "Model kataloğunda eksik — üye yetenekleri eksik veya uyumsuz (eksik bağlam penceresi / meta veri veya boş modalite kesişimi). Takma ada göre yönlendirme hâlâ çalışır", "cws.emptyTitle": "İlk kombonuzu oluşturun", "cws.empty.createDesc": "Sanal bir model adlandırın ve iki veya daha fazla arka ucu bağlayın.", "cws.backToAll": "Tüm kombolara dön", @@ -1767,25 +1779,29 @@ export const tr: Record = { "cws.strategy": "Strateji", "cws.strategy.failover": "Yedekli (Failover)", "cws.strategy.roundRobin": "Round-robin", - "cws.strategy.failoverHint": "Hedefleri sırayla deneyin. İlk hedef hata verirse diğerine geçin.", - "cws.strategy.roundRobinHint": "Trafiği ağırlığa göre dengeli şekilde dağıtın.", + "cws.strategy.failoverHint": "Hedefleri sırayla deneyin. İlk hedef yeniden denenebilir bir hatayla (oran sınırı, kesinti, abonelik engeli) başarısız olursa sonraki hedefe atlayın.", + "cws.strategy.roundRobinHint": "Trafiği ağırlığa göre kararlı bir şekilde dengeleyin. Seçilen her hedefi bir dizi başarılı istek boyunca tutun, ardından ilerleyin.", "cws.field.id": "Kombo ID", "cws.field.idHint": "İstemciler {model} isteyecek", - "cws.field.idInternalHint": "Dahili kombo ID.", - "cws.field.idHintEdit": "Yeniden adlandırmak komboyu yeni bir ID'ye taşır. {model} isteyen istemciler yeni ID'yi kullanır.", + "cws.field.idInternalHint": "Dahili kombo ID. Oluşturduktan sonra değiştirebilirsiniz.", + "cws.field.idHintEdit": "Yeniden adlandırmak komboyu yeni bir ID'ye taşır. İstemciler {model} ister.", "cws.field.alias": "Genel model adı", "cws.field.aliasPlaceholder": "deepseek-v4-flash veya üretici/model", - "cws.field.aliasHint": "İsteğe bağlı genel takma ad.", + "cws.field.aliasHint": "İsteğe bağlı. Ön eki olmayan yalın bir ad, üretici/model gibi özel bir ön ek kullanın veya combo/ kullanmak için boş bırakın.", + "cws.field.nativeAlias": "Yerel OpenAI takma adı", + "cws.field.nativeAliasHint": "Bu kombonun desteklenen nitelemesiz yerel bir OpenAI model kimliğine sahip olmasına izin verin. Hesap ve sağlayıcı nitelikli OpenAI rotaları ayrı kalır.", + "cws.field.displayName": "Görünen ad", + "cws.field.displayNameHint": "Bu kombo için seçici etiketi. Yerel OpenAI takma adı etkinleştirildiğinde gereklidir.", "cws.field.stickyLimit": "Döndürmeden önceki sabit başarılar", - "cws.field.stickyLimitHint": "Seçilen hedefi bu kadar başarılı istek boyunca tutun.", + "cws.field.stickyLimitHint": "Ağırlıklı seçici ilerlemeden önce seçilen hedefi bu kadar başarılı istek boyunca tutun.", "cws.field.defaultEffort": "Varsayılan akıl yürütme", "cws.field.defaultEffortNone": "Yok (hedef varsayılanı)", "cws.field.defaultEffortHint": "Yalnızca istemci akıl yürütme çabasını belirtmediğinde (atladığında) kullanılır. Seçenekler, seçilen hedeflerin duyurulan çabalarının kesişimidir; katalog çaba meta verisi olmayan hedefler hiçbir seçenek sunmaz.", - "cws.field.defaultEffortUnsupported": "Bu çaba hedeflerin ortak merdiveninde yok.", + "cws.field.defaultEffortUnsupported": "Bu çaba hedeflerin ortak merdiveninde yok — istek anında yok sayılacak veya uydurulacaktır.", "cws.field.defaultEffortUnsupportedOption": "kesişimde değil", "cws.targets": "Hedefler", "cws.targets.failoverHint": "Sıralama önemlidir — birincil olan ilktir.", - "cws.targets.roundRobinHint": "Ağırlıklar seçimi kontrol eder.", + "cws.targets.roundRobinHint": "Ağırlıklar kararlı bağıntılı seçimi kontrol eder; sıralama rotasyon halkasındaki eşitlikleri bozar.", "cws.target.provider": "Sağlayıcı", "cws.target.model": "Model", "cws.target.weight": "Ağırlık", @@ -1799,9 +1815,9 @@ export const tr: Record = { "cws.target.moveUp": "Yukarı taşı", "cws.target.moveDown": "Aşağı taşı", "cws.aboutTitle": "Çalışma zamanı", - "cws.aboutBody": "Başarısız hedefler kısa süre soğumaya alınır.", + "cws.aboutBody": "Başarısız hedefler Retry-After süresine uyarak kısa süreliğine soğumaya alınır. Geçersiz istek veya bağlam (context) hataları diğer hedefe atlamaz. Her hedef çabayı kendi yeteneklerine uyarlar; tükenen kombolar kapalı olarak başarısız olur (fail closed). Günlükler ve Kullanım bölümü sıralı fiziksel denemeleri ve deneme başına kullanımı saklar.", "cws.removeConfirmTitle": "{model} kaldırılsın mı?", - "cws.removeConfirmDesc": "Bu işlem sanal modeli konfigürasyondan kaldırır.", + "cws.removeConfirmDesc": "Bu işlem sanal modeli konfigürasyondan ve Codex kataloğundan kaldırır. Hiçbir sağlayıcıyı silmez.", "cws.unsavedTitle": "Kaydedilmemiş değişiklikler", "cws.unsavedDesc": "Düzenlemelerden vazgeçip devam edilsin mi?", "cws.keepEditing": "Düzenlemeye devam et", @@ -1811,6 +1827,9 @@ export const tr: Record = { "cws.err.invalidAlias": "Takma ad geçerli karakterler içermelidir.", "cws.err.aliasReservedNamespace": "Takma ad ayrılmış namespace kullanamaz.", "cws.err.aliasNativeFamily": "Yerel OpenAI isimlerine izin verilmez.", + "cws.err.unsupportedNativeAlias": "Yerel takma ad, şu anda desteklenen yalın bir OpenAI model kimliği olmalıdır.", + "cws.err.missingNativeAliasDisplayName": "Yerel takma adlar için bir görünen ad gereklidir.", + "cws.err.invalidDisplayName": "Görünen ad en fazla 128 karakter olmalı ve kontrol karakteri içermemelidir.", "cws.err.duplicateAlias": "Başka bir kombo bu takma adı zaten kullanıyor.", "cws.err.noTargets": "En az bir hedef ekleyin.", "cws.err.incompleteTarget": "Her hedefin bir sağlayıcısı ve modeli olmalıdır.", diff --git a/gui/tests/i18n-tr-locale.test.ts b/gui/tests/i18n-tr-locale.test.ts index 93e053e201..15048d43b8 100644 --- a/gui/tests/i18n-tr-locale.test.ts +++ b/gui/tests/i18n-tr-locale.test.ts @@ -74,37 +74,46 @@ describe("Turkish (tr) i18n locale", () => { }); test("detectInitial detects tr from navigator language cleanly isolated from localStorage", () => { - const origNav = globalThis.navigator; - const origStorage = globalThis.localStorage; + const origNav = Object.getOwnPropertyDescriptor(globalThis, "navigator"); + const origStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage"); try { - // @ts-expect-error test isolation mock - globalThis.navigator = { language: "tr-TR" }; - // @ts-expect-error test isolation mock - globalThis.localStorage = { - getItem: () => null, - setItem: () => {}, - removeItem: () => {}, - }; + Object.defineProperty(globalThis, "navigator", { + value: { language: "tr-TR" }, + configurable: true, + writable: true, + }); + Object.defineProperty(globalThis, "localStorage", { + value: { + getItem: () => null, + setItem: () => {}, + removeItem: () => {}, + }, + configurable: true, + writable: true, + }); expect(detectInitial()).toBe("tr"); } finally { - globalThis.navigator = origNav; - globalThis.localStorage = origStorage; + if (origNav) Object.defineProperty(globalThis, "navigator", origNav); + if (origStorage) Object.defineProperty(globalThis, "localStorage", origStorage); } }); test("detectInitial detects tr from stored preference", () => { - const origStorage = globalThis.localStorage; + const origStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage"); try { const store = new Map([["ocx-lang", "tr"]]); - // @ts-expect-error test isolation mock - globalThis.localStorage = { - getItem: (k: string) => store.get(k) ?? null, - setItem: (k: string, v: string) => store.set(k, v), - removeItem: (k: string) => store.delete(k), - }; + Object.defineProperty(globalThis, "localStorage", { + value: { + getItem: (k: string) => store.get(k) ?? null, + setItem: (k: string, v: string) => store.set(k, v), + removeItem: (k: string) => store.delete(k), + }, + configurable: true, + writable: true, + }); expect(detectInitial()).toBe("tr"); } finally { - globalThis.localStorage = origStorage; + if (origStorage) Object.defineProperty(globalThis, "localStorage", origStorage); } }); }); From 06fbc75c87351306818ad7167bf66375e81f17d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Karata=C5=9F?= <81361836+ardakrt@users.noreply.github.com> Date: Sun, 9 Aug 2026 21:25:33 +0300 Subject: [PATCH 14/16] fix(gui,docs): refine V2 effort-cap, account-switch, round-robin, Claude fallback semantics and Opus grammar in README.tr.md --- gui/src/i18n/tr.ts | 10 +++++----- readme/README.tr.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 1182e6a2d5..42fa385e96 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -285,7 +285,7 @@ export const tr: Record = { "dash.injectionEffortNone": "Model varsayılanı", "dash.effortCapLabel": "V2 ultra çaba limiti", "dash.subagentEffortCapLabel": "V2 alt ajan çaba limiti", - "dash.effortCapHelp": "V2 ultra modu için akıl yürütme çabasını sınırlar.", + "dash.effortCapHelp": "V2 ultra modu turları için akıl yürütme çabasını sınırlar. Ayarlandığında, gelen maksimum çaba istekleri (ultra modundan) seçilen seviye ile sınırlandırılır. Alt ajan limiti yalnızca türetilen çocuk ajanları etkiler. Limitler çabayı yalnızca düşürür, asla yükseltmez. Bir model sınırlandırılan seviyeyi desteklemiyorsa, desteklenen en yakın alt seviyeye iner.", "dash.effortCapNone": "Limit yok", "dash.maintenance": "Bakım", "dash.maintenanceHint": "Codex'in model kataloğunu yenileyin veya daha yeni bir opencodex sürümü yükleyin.", @@ -1396,12 +1396,12 @@ export const tr: Record = { "codexAuth.prepareForPool": "Havuz İçin Hazırla", "codexAuth.poolPreparedToast": "{email} Havuz modu için hazırlandı", "codexAuth.switchTitle": "Aktif hesap değiştirilsin mi?", - "codexAuth.switchDesc": "Anında yürürlüğe girer.", + "codexAuth.switchDesc": "Anında yürürlüğe girer. Mevcut hesaba bağlı iş parçacıkları ve işlenmekte olan istekler yakalanan hesaplarını korur; yeni veya bağımsız istekler seçilen hesabın sıra kademesini kullanır ve aynı seçim sırasındaki hesaplar sırayla görev almaya devam eder.", "codexAuth.cacheWarning": "Hesap değiştirildiğinde önbellek sıfırlanır.", "codexAuth.setAsNext": "Sonraki istekte bu hesabı kullan", "codexAuth.cancel": "İptal", "codexAuth.switchBack": "Ana hesaba geri dönülsün mü?", - "codexAuth.switchBackDesc": "Anında yürürlüğe girer.", + "codexAuth.switchBackDesc": "Anında yürürlüğe girer. Mevcut hesaba bağlı iş parçacıkları ve işlenmekte olan istekler yakalanan hesaplarını korur; yeni veya bağımsız istekler Uygulama giriş hesabınızın sıra kademesini kullanır ve aynı seçim sırasındaki hesaplar sırayla görev almaya devam eder.", "codexAuth.autoSwitch": "Kullanıma dayalı proaktif geçiş", "codexAuth.autoSwitchQuotaDesc": "Kota: %{threshold} veya üzeri kullanımda sonraki istek daha az kullanılan bir hesaba geçebilir.", "codexAuth.autoSwitchQuotaOffDesc": "Kullanıma dayalı proaktif geçiş kapalı.", @@ -1444,7 +1444,7 @@ export const tr: Record = { "accountPool.strategyRoundRobin": "Round-robin", "accountPool.strategyFillFirst": "İlk doldurma", "accountPool.strategyHintQuota": "Kota kullanımı eşik aşıldığında hesabı değiştirebilir.", - "accountPool.strategyHintRoundRobin": "Round-robin görevi olmayan hesapları döndürür.", + "accountPool.strategyHintRoundRobin": "Round-robin yalnızca canlı bir hesap bağı olmayan yeni/bağımsız görevleri döndürür; mevcut görevler bağlı kalabilir ve kullanım eşiği normal rotasyonu değiştirmez.", "accountPool.strategyHintFillFirst": "İlk doldurma eşiği boşaltma noktası olarak kullanır.", "accountPool.unboundDefinition": "Bağlı olmayan yeni görev.", "accountPool.stickyLimit": "Döndürmeden önceki sabit atamalar", @@ -1688,7 +1688,7 @@ export const tr: Record = { "claude.authSource.unknown": "algılanan bir kimlik bilgisi", "claude.systemEnv": "Otomatik bağlan", "claude.systemEnvDesc": "Açık olduğunda terminalde claude çalıştırmak proxy üzerinden geçer.", - "claude.systemEnvUnsupported": "{cmd} için sistem çevre değişkenleri desteklenmiyor.", + "claude.systemEnvUnsupported": "Otomatik bağlanma yalnızca macOS üzerinde kullanılabilirdir. Bu sistemde Claude'u {cmd} ile başlatın.", "claude.systemEnvWarn": "⚠ Terminal uygulamasını tamamen kapatıp yeniden açmalısınız.", "claude.fastMode": "Hızlı Mod (OpenAI)", "claude.fastModeDesc": "OpenAI modelleri için service_tier ayarını kontrol eder.", diff --git a/readme/README.tr.md b/readme/README.tr.md index c31721100a..a803f91708 100644 --- a/readme/README.tr.md +++ b/readme/README.tr.md @@ -30,7 +30,7 @@ ocx start # localhost:10100 üzerinde proxy + panel Claude Desktop demosu
- Claude Desktop, herhangi bir modeli çalıştırıyor.
Opus yanıtlar, ardından görevi GPT-5.6 Sol alt ajanına devreder.
+ Claude Desktop, herhangi bir modeli çalıştırıyor.
Opus yanıt verir, ardından görevi GPT-5.6 Sol alt ajanına devreder.
Grok Build demosu
From d895c705f7f204e324c1d2b4302363ec5104cb3a Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Sun, 9 Aug 2026 20:59:08 +0200 Subject: [PATCH 15/16] fix(gui): route locale labels through i18n catalogs --- gui/src/App.tsx | 4 ++-- gui/src/formatUptime.ts | 25 +++++++++---------------- gui/src/i18n/catalogs.ts | 29 +++++++++++++++++++++++++++++ gui/src/i18n/shared.ts | 29 ++++++++++------------------- gui/tests/i18n-tr-locale.test.ts | 8 ++++---- 5 files changed, 54 insertions(+), 41 deletions(-) create mode 100644 gui/src/i18n/catalogs.ts diff --git a/gui/src/App.tsx b/gui/src/App.tsx index 853116d094..411484feba 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -13,7 +13,7 @@ import Startup from "./pages/Startup"; import ErrorBoundary from "./components/ErrorBoundary"; import { SidebarGithubRow } from "./components/sidebar-github-row"; import { IconGrid, IconServer, IconBoxes, IconBot, IconList, IconActivity, IconHardDrive, IconKey, IconMenu, IconSun, IconMoon, IconMonitor, IconGlobe, IconPower, IconX } from "./icons"; -import { useI18n, useT, LOCALES, type Locale, type TKey } from "./i18n/shared"; +import { useI18n, useT, LOCALES, LOCALE_NAMES, type Locale, type TKey } from "./i18n/shared"; import { Select } from "./ui"; import { installApiAuthFetch } from "./api"; import { type Page } from "./app-routing"; @@ -252,7 +252,7 @@ export default function App() { ({ value: l.code, label: LOCALE_NAMES[l.code] }))} + options={LOCALES.map(l => ({ value: l.code, label: localeDisplayName(l.code) }))} onChange={v => setLocale(v as Locale)} label={t("lang.label")} placement="right" diff --git a/gui/src/i18n/catalogs.ts b/gui/src/i18n/catalogs.ts index 7bf8d14301..6d756ef8d0 100644 --- a/gui/src/i18n/catalogs.ts +++ b/gui/src/i18n/catalogs.ts @@ -12,15 +12,9 @@ export type Locale = "en" | "de" | "ko" | "zh" | "ru" | "ja" | "tr"; export const DICTS: Record> = { en, de, ko, zh, ru, ja, tr }; /** Native language names shown by the language picker, kept inside i18n rather than UI metadata. */ -export const LOCALE_NAMES: Record = { - en: "English", - de: "Deutsch", - ko: "한국어", - zh: "中文", - ru: "Русский", - ja: "日本語", - tr: "Türkçe", -}; +export function localeDisplayName(locale: Locale): string { + return DICTS[locale]["lang.nativeName"]; +} export function catalogValue(locale: Locale, key: TKey): string { return DICTS[locale][key]; diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 15aaaf622c..ad073d31a4 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -88,6 +88,7 @@ export const de: Record = { "theme.dark": "Dunkel", "theme.system": "System", "lang.label": "Sprache", + "lang.nativeName": "Deutsch", "provider.name.commandCodeAuth": "Command Code - Auth", "provider.name.commandCodeApi": "Command Code - API", "provider.name.volcengine": "Volcengine Ark", @@ -1697,8 +1698,8 @@ export const de: Record = { "modal.apiKeyPlaceholder": "sk-… (oder $ENV_VAR)", "modal.defaultModelPlaceholder": "z. B. gpt-5.5", "modal.baseUrlPlaceholder": "https://...", - "modal.baseUrlPlaceholderError": "Base-URL enthält einen ungelösten {Platzhalter}. Ersetze ihn durch deinen tatsächlichen Wert.", - "modal.baseUrlPlaceholderHint": "Ersetze den {Platzhalter} in der Base-URL durch deine tatsächliche Account-ID, bevor du hinzufügst.", + "modal.baseUrlPlaceholderError": "Base-URL enthält einen ungelösten {placeholder}. Ersetze ihn durch deinen tatsächlichen Wert.", + "modal.baseUrlPlaceholderHint": "Ersetze den {placeholder} in der Base-URL durch deine tatsächliche Account-ID, bevor du hinzufügst.", "modal.adding": "Wird hinzugefügt…", "modal.useOauthLogin": "← OAuth-Login verwenden", "codexAuth.addIdPlaceholder": "codex-work, codex-alt, team…", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 363242c87f..1b8c25bf3b 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -48,6 +48,7 @@ export const en = { "theme.dark": "Dark", "theme.system": "System", "lang.label": "Language", + "lang.nativeName": "English", "provider.name.commandCodeAuth": "Command Code - Auth", "provider.name.commandCodeApi": "Command Code - API", "provider.name.volcengine": "Volcengine Ark", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index 1b25c89b05..6a163d1618 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -93,6 +93,7 @@ export const ja: Record = { "theme.dark": "ダーク", "theme.system": "システム", "lang.label": "言語", + "lang.nativeName": "日本語", "provider.name.commandCodeAuth": "Command Code - Auth", "provider.name.commandCodeApi": "Command Code - API", "provider.name.volcengine": "Volcengine Ark", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 0c96c410cc..30097ac458 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -88,6 +88,7 @@ export const ko: Record = { "theme.dark": "다크", "theme.system": "시스템", "lang.label": "언어", + "lang.nativeName": "한국어", "provider.name.commandCodeAuth": "Command Code - Auth", "provider.name.commandCodeApi": "Command Code - API", "provider.name.volcengine": "Volcengine Ark", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index 01dddd805d..d462a81adb 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -93,6 +93,7 @@ export const ru: Record = { "theme.dark": "Тёмная", "theme.system": "Системная", "lang.label": "Язык", + "lang.nativeName": "Русский", "provider.name.commandCodeAuth": "Command Code - Auth", "provider.name.commandCodeApi": "Command Code - API", "provider.name.volcengine": "Volcengine Ark", diff --git a/gui/src/i18n/shared.ts b/gui/src/i18n/shared.ts index 3d4464a053..3ae7d94516 100644 --- a/gui/src/i18n/shared.ts +++ b/gui/src/i18n/shared.ts @@ -1,7 +1,7 @@ import { createContext, useContext } from "react"; -import { DICTS, LOCALE_NAMES, type Locale, type TKey } from "./catalogs"; +import { DICTS, localeDisplayName, type Locale, type TKey } from "./catalogs"; -export { DICTS, LOCALE_NAMES, type Locale, type TKey }; +export { DICTS, localeDisplayName, type Locale, type TKey }; export const LOCALES: { code: Locale; htmlLang: string }[] = [ { code: "en", htmlLang: "en" }, diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 42fa385e96..32e97e2e41 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -47,6 +47,7 @@ export const tr: Record = { "theme.dark": "Koyu", "theme.system": "Sistem", "lang.label": "Dil", + "lang.nativeName": "Türkçe", "provider.name.commandCodeAuth": "Command Code - Auth", "provider.name.commandCodeApi": "Command Code - API", "provider.name.volcengine": "Volcengine Ark", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index dfec1a146b..c62f4477cc 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -88,6 +88,7 @@ export const zh: Record = { "theme.dark": "深色", "theme.system": "跟随系统", "lang.label": "语言", + "lang.nativeName": "中文", "provider.name.commandCodeAuth": "Command Code - Auth", "provider.name.commandCodeApi": "Command Code - API", "provider.name.volcengine": "火山方舟", diff --git a/gui/tests/i18n-locales.test.ts b/gui/tests/i18n-locales.test.ts new file mode 100644 index 0000000000..20d0906ef7 --- /dev/null +++ b/gui/tests/i18n-locales.test.ts @@ -0,0 +1,200 @@ +import { describe, expect, test } from "bun:test"; +import { + DICTS, + LOCALES, + localeDisplayName, + detectInitial, +} from "../src/i18n/shared"; +import { en } from "../src/i18n/en"; +import { formatUptime } from "../src/formatUptime"; + +describe("i18n locale contracts", () => { + test("LOCALES and DICTS contain exactly the same locales", () => { + const localeCodes = LOCALES.map(locale => locale.code).sort(); + const dictCodes = Object.keys(DICTS).sort(); + + expect(dictCodes).toEqual(localeCodes); + expect(new Set(localeCodes).size).toBe(localeCodes.length); + + for (const locale of LOCALES) { + expect(locale.htmlLang.length).toBeGreaterThan(0); + expect("name" in locale).toBe(false); + } + }); + + test("every locale has a catalog-backed display name", () => { + for (const { code } of LOCALES) { + const displayName = DICTS[code]["lang.nativeName"]; + + expect(displayName.trim().length).toBeGreaterThan(0); + expect(localeDisplayName(code)).toBe(displayName); + } + }); + + test("every locale catalog has exactly the English key set", () => { + const expectedKeys = Object.keys(en).sort(); + + for (const { code } of LOCALES) { + const actualKeys = Object.keys(DICTS[code]).sort(); + expect(actualKeys).toEqual(expectedKeys); + } + }); + + test("every locale preserves interpolation placeholders exactly", () => { + const placeholderRe = /\{([a-zA-Z0-9_]+)\}/g; + const mismatches: string[] = []; + + for (const { code } of LOCALES) { + if (code === "en") continue; + + for (const key of Object.keys(en) as Array) { + const enValue = en[key]; + const localeValue = DICTS[code][key]; + + const enPlaceholders = [...enValue.matchAll(placeholderRe)] + .map(match => match[1]) + .sort(); + + const localePlaceholders = [...localeValue.matchAll(placeholderRe)] + .map(match => match[1]) + .sort(); + + if ( + enPlaceholders.length !== localePlaceholders.length || + enPlaceholders.some( + (placeholder, index) => placeholder !== localePlaceholders[index], + ) + ) { + mismatches.push( + `${code}.${key}: en=[${enPlaceholders.join(",")}] locale=[${localePlaceholders.join(",")}]`, + ); + } + } + } + + expect(mismatches).toEqual([]); + }); + + test("locale source files contain no duplicate dictionary keys", async () => { + const duplicates: string[] = []; + + for (const { code } of LOCALES) { + const text = await Bun.file( + new URL(`../src/i18n/${code}.ts`, import.meta.url), + ).text(); + + const keyRe = /^\s*"([^"]+)":/gm; + const seen = new Set(); + + for (const match of text.matchAll(keyRe)) { + const key = match[1]; + + if (seen.has(key)) { + duplicates.push(`${code}.${key}`); + } else { + seen.add(key); + } + } + } + + expect(duplicates).toEqual([]); + }); + + test("formatUptime uses catalog units for every locale", () => { + for (const { code } of LOCALES) { + const day = DICTS[code]["uptime.day"]; + const hour = DICTS[code]["uptime.hour"]; + const minute = DICTS[code]["uptime.minute"]; + const second = DICTS[code]["uptime.second"]; + + expect(formatUptime(45, code)).toBe(`45${second}`); + expect(formatUptime(300, code)).toBe(`5${minute}`); + expect(formatUptime(3600, code)).toBe(`1${hour}`); + expect(formatUptime(3660, code)).toBe(`1${hour} 1${minute}`); + expect(formatUptime(86400, code)).toBe(`1${day}`); + expect(formatUptime(90000, code)).toBe(`1${day} 1${hour}`); + } + }); + + test("detectInitial recognizes every supported navigator locale", () => { + const originalNavigator = Object.getOwnPropertyDescriptor( + globalThis, + "navigator", + ); + const originalStorage = Object.getOwnPropertyDescriptor( + globalThis, + "localStorage", + ); + + try { + Object.defineProperty(globalThis, "localStorage", { + value: { + getItem: () => null, + setItem: () => {}, + removeItem: () => {}, + }, + configurable: true, + writable: true, + }); + + for (const { code, htmlLang } of LOCALES) { + Object.defineProperty(globalThis, "navigator", { + value: { language: htmlLang }, + configurable: true, + writable: true, + }); + + expect(detectInitial()).toBe(code); + } + } finally { + if (originalNavigator) { + Object.defineProperty(globalThis, "navigator", originalNavigator); + } else { + Reflect.deleteProperty(globalThis, "navigator"); + } + + if (originalStorage) { + Object.defineProperty(globalThis, "localStorage", originalStorage); + } else { + Reflect.deleteProperty(globalThis, "localStorage"); + } + } + }); + + test("detectInitial recognizes every supported stored locale", () => { + const originalStorage = Object.getOwnPropertyDescriptor( + globalThis, + "localStorage", + ); + + let storedLocale: string | null = null; + + try { + Object.defineProperty(globalThis, "localStorage", { + value: { + getItem: (key: string) => + key === "ocx-lang" ? storedLocale : null, + setItem: (_key: string, value: string) => { + storedLocale = value; + }, + removeItem: () => { + storedLocale = null; + }, + }, + configurable: true, + writable: true, + }); + + for (const { code } of LOCALES) { + storedLocale = code; + expect(detectInitial()).toBe(code); + } + } finally { + if (originalStorage) { + Object.defineProperty(globalThis, "localStorage", originalStorage); + } else { + Reflect.deleteProperty(globalThis, "localStorage"); + } + } + }); +}); \ No newline at end of file diff --git a/gui/tests/i18n-tr-locale.test.ts b/gui/tests/i18n-tr-locale.test.ts deleted file mode 100644 index f29a425825..0000000000 --- a/gui/tests/i18n-tr-locale.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { DICTS, LOCALES, LOCALE_NAMES, detectInitial } from "../src/i18n/shared"; -import { en } from "../src/i18n/en"; -import { tr } from "../src/i18n/tr"; -import { formatUptime } from "../src/formatUptime"; - -describe("Turkish (tr) i18n locale", () => { - test("registers tr in LOCALES array with correct metadata", () => { - const trLocale = LOCALES.find(l => l.code === "tr"); - expect(trLocale).toEqual({ code: "tr", htmlLang: "tr" }); - expect("name" in (trLocale ?? {})).toBe(false); - expect(LOCALE_NAMES.tr).toBe("Türkçe"); - }); - - test("includes tr in DICTS map", () => { - expect(DICTS.tr).toBe(tr); - }); - - test("tr dictionary covers all TKeys from source-of-truth en catalog", () => { - const enKeys = Object.keys(en); - const missingKeys = enKeys.filter(k => !(k in tr)); - expect(missingKeys).toEqual([]); - }); - - test("tr dictionary preserves all interpolation placeholders from en catalog", () => { - const enKeys = Object.keys(en) as Array; - const placeholderRe = /\{([a-zA-Z0-9_]+)\}/g; - const mismatched: string[] = []; - - for (const key of enKeys) { - const enVal = en[key]; - const trVal = tr[key]; - if (typeof enVal !== "string" || typeof trVal !== "string") continue; - - const enPlaceholders = [...enVal.matchAll(placeholderRe)].map(m => m[1]).sort(); - const trPlaceholders = [...trVal.matchAll(placeholderRe)].map(m => m[1]).sort(); - - if ( - enPlaceholders.length !== trPlaceholders.length || - enPlaceholders.some((ph, index) => ph !== trPlaceholders[index]) - ) { - mismatched.push(`${key}: placeholder parity mismatch (en: [${enPlaceholders}], tr: [${trPlaceholders}])`); - } - } - - expect(mismatched).toEqual([]); - }); - - test("tr.ts has no duplicate key declarations", async () => { - const text = await Bun.file(new URL("../src/i18n/tr.ts", import.meta.url)).text(); - const keyRe = /^\s*"([^"]+)":/gm; - const seen = new Set(); - const duplicates: string[] = []; - - for (const match of text.matchAll(keyRe)) { - const key = match[1]; - if (seen.has(key)) { - duplicates.push(key); - } else { - seen.add(key); - } - } - - expect(duplicates).toEqual([]); - }); - - test("formatUptime correctly formats seconds, minutes, hours, and days in Turkish", () => { - expect(formatUptime(45, "tr")).toBe("45 sn"); - expect(formatUptime(300, "tr")).toBe("5 dk"); - expect(formatUptime(3600, "tr")).toBe("1 saat"); - expect(formatUptime(3660, "tr")).toBe("1 saat 1 dk"); - expect(formatUptime(86400, "tr")).toBe("1 gün"); - expect(formatUptime(90000, "tr")).toBe("1 gün 1 saat"); - }); - - test("detectInitial detects tr from navigator language cleanly isolated from localStorage", () => { - const origNav = Object.getOwnPropertyDescriptor(globalThis, "navigator"); - const origStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage"); - try { - Object.defineProperty(globalThis, "navigator", { - value: { language: "tr-TR" }, - configurable: true, - writable: true, - }); - Object.defineProperty(globalThis, "localStorage", { - value: { - getItem: () => null, - setItem: () => {}, - removeItem: () => {}, - }, - configurable: true, - writable: true, - }); - expect(detectInitial()).toBe("tr"); - } finally { - if (origNav) Object.defineProperty(globalThis, "navigator", origNav); - if (origStorage) Object.defineProperty(globalThis, "localStorage", origStorage); - } - }); - - test("detectInitial detects tr from stored preference", () => { - const origStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage"); - try { - const store = new Map([["ocx-lang", "tr"]]); - Object.defineProperty(globalThis, "localStorage", { - value: { - getItem: (k: string) => store.get(k) ?? null, - setItem: (k: string, v: string) => store.set(k, v), - removeItem: (k: string) => store.delete(k), - }, - configurable: true, - writable: true, - }); - expect(detectInitial()).toBe("tr"); - } finally { - if (origStorage) Object.defineProperty(globalThis, "localStorage", origStorage); - } - }); -});