Compare commits

...

19 Commits

Author SHA1 Message Date
David Hill
5d419a0211 tweak(ui): expand question dock toggle area 2026-02-27 21:30:49 +00:00
David Hill
8b168981aa tweak(ui): active state on type your own answer 2026-02-27 18:50:50 +00:00
David Hill
724dd665ec tweak(ui): collapse questions 2026-02-27 18:47:53 +00:00
Adam
a94f564ff0 fix(app): scroll issues 2026-02-27 09:47:56 -06:00
Adam
6ef3af73df chore(app): i18n sync (#15362) 2026-02-27 09:45:00 -06:00
Adam
e5ae6c51b0 chore: update translator model 2026-02-27 09:27:12 -06:00
Adam
9d76ef6c66 chore: update docs locale sync workflow 2026-02-27 09:18:17 -06:00
Kit Langton
e49e781cb8 feat(app): add Warp to the open menu (#15368) 2026-02-27 09:44:01 -05:00
Kit Langton
78cea89e0e chore(script): source team members from TEAM_MEMBERS (#15369) 2026-02-27 09:43:51 -05:00
James Long
3dc10a1c16 Change keybindings to navigate between child sessions (#14814) 2026-02-27 09:41:23 -05:00
Adam
157920b2fb chore: update test 2026-02-27 06:54:15 -06:00
Brendan Allan
967313234a desktop: add latest.json finalizer script (#15335) 2026-02-27 20:18:21 +08:00
Adam
dfa0281117 fix(app): auto-accept permissions 2026-02-27 06:17:40 -06:00
Adam
4a94096994 fix(app): update provider sprite 2026-02-27 06:01:00 -06:00
opencode-agent[bot]
3407ded9d0 chore: generate 2026-02-27 11:41:55 +00:00
vaur94
a325c9af8f feat(app): add Turkish (tr) locale for app and ui packages (#15278)
Co-authored-by: Ugur <ugur@example.com>
2026-02-27 05:41:06 -06:00
Pirro Zani
dc8c011510 docs(readme): add Greek translation and update language navigation (#15281) 2026-02-27 05:39:56 -06:00
Filip
1f108bc401 feat(app): recent projects section in command pallette (#15270) 2026-02-27 05:33:22 -06:00
Frank
6b3118883c wip: zen 2026-02-27 02:56:43 -05:00
115 changed files with 2870 additions and 401 deletions

View File

@@ -47,12 +47,14 @@ jobs:
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Install OpenCode
if: steps.changes.outputs.has_changes == 'true'
run: curl -fsSL https://opencode.ai/install | bash
- name: Sync locale docs with OpenCode
if: steps.changes.outputs.has_changes == 'true'
uses: sst/opencode/github@latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
OPENCODE_CONFIG_CONTENT: |
{
"permission": {
@@ -96,11 +98,8 @@ jobs:
}
}
}
with:
model: opencode/gpt-5.3-codex
agent: docs
use_github_token: true
prompt: |
run: |
opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF'
Update localized docs to match the latest English docs changes.
Changed English doc files:
@@ -118,6 +117,7 @@ jobs:
7. Keep locale docs structure aligned with their corresponding English pages.
8. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
9. If no locale updates are needed, make no changes.
EOF
- name: Commit and push locale docs updates
if: steps.changes.outputs.has_changes == 'true'

View File

@@ -1,7 +1,7 @@
---
description: Translate content for a specified locale while preserving technical terms
mode: subagent
model: opencode/gemini-3.1-pro
model: opencode/gemini-3-pro
---
You are a professional translator and localization specialist.

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -34,7 +34,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -34,7 +34,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

140
README.gr.md Normal file
View File

@@ -0,0 +1,140 @@
<p align="center">
<a href="https://opencode.ai">
<picture>
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
</picture>
</a>
</p>
<p align="center">Ο πράκτορας τεχνητής νοημοσύνης ανοικτού κώδικα για προγραμματισμό.</p>
<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p>
<p align="center">
<a href="README.md">English</a> |
<a href="README.zh.md">简体中文</a> |
<a href="README.zht.md">繁體中文</a> |
<a href="README.ko.md">한국어</a> |
<a href="README.de.md">Deutsch</a> |
<a href="README.es.md">Español</a> |
<a href="README.fr.md">Français</a> |
<a href="README.it.md">Italiano</a> |
<a href="README.da.md">Dansk</a> |
<a href="README.ja.md">日本語</a> |
<a href="README.pl.md">Polski</a> |
<a href="README.ru.md">Русский</a> |
<a href="README.bs.md">Bosanski</a> |
<a href="README.ar.md">العربية</a> |
<a href="README.no.md">Norsk</a> |
<a href="README.br.md">Português (Brasil)</a> |
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
---
### Εγκατάσταση
```bash
# YOLO
curl -fsSL https://opencode.ai/install | bash
# Διαχειριστές πακέτων
npm i -g opencode-ai@latest # ή bun/pnpm/yarn
scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS και Linux (προτείνεται, πάντα ενημερωμένο)
brew install opencode # macOS και Linux (επίσημος τύπος brew, λιγότερο συχνές ενημερώσεις)
sudo pacman -S opencode # Arch Linux (Σταθερό)
paru -S opencode-bin # Arch Linux (Τελευταία έκδοση από AUR)
mise use -g opencode # Οποιοδήποτε λειτουργικό σύστημα
nix run nixpkgs#opencode # ή github:anomalyco/opencode με βάση την πιο πρόσφατη αλλαγή από το dev branch
```
> [!TIP]
> Αφαίρεσε παλαιότερες εκδόσεις από τη 0.1.x πριν από την εγκατάσταση.
### Εφαρμογή Desktop (BETA)
Το OpenCode είναι επίσης διαθέσιμο ως εφαρμογή. Κατέβασε το απευθείας από τη [σελίδα εκδόσεων](https://github.com/anomalyco/opencode/releases) ή το [opencode.ai/download](https://opencode.ai/download).
| Πλατφόρμα | Λήψη |
| --------------------- | ------------------------------------- |
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
| Windows | `opencode-desktop-windows-x64.exe` |
| Linux | `.deb`, `.rpm`, ή AppImage |
```bash
# macOS (Homebrew)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### Κατάλογος Εγκατάστασης
Το script εγκατάστασης τηρεί την ακόλουθη σειρά προτεραιότητας για τη διαδρομή εγκατάστασης:
1. `$OPENCODE_INSTALL_DIR` - Προσαρμοσμένος κατάλογος εγκατάστασης
2. `$XDG_BIN_DIR` - Διαδρομή συμβατή με τις προδιαγραφές XDG Base Directory
3. `$HOME/bin` - Τυπικός κατάλογος εκτελέσιμων αρχείων χρήστη (εάν υπάρχει ή μπορεί να δημιουργηθεί)
4. `$HOME/.opencode/bin` - Προεπιλεγμένη εφεδρική διαδρομή
```bash
# Παραδείγματα
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```
### Πράκτορες
Το OpenCode περιλαμβάνει δύο ενσωματωμένους πράκτορες μεταξύ των οποίων μπορείτε να εναλλάσσεστε με το πλήκτρο `Tab`.
- **build** - Προεπιλεγμένος πράκτορας με πλήρη πρόσβαση για εργασία πάνω σε κώδικα
- **plan** - Πράκτορας μόνο ανάγνωσης για ανάλυση και εξερεύνηση κώδικα
- Αρνείται την επεξεργασία αρχείων από προεπιλογή
- Ζητά άδεια πριν εκτελέσει εντολές bash
- Ιδανικός για εξερεύνηση άγνωστων αρχείων πηγαίου κώδικα ή σχεδιασμό αλλαγών
Περιλαμβάνεται επίσης ένας **general** υποπράκτορας για σύνθετες αναζητήσεις και πολυβηματικές διεργασίες.
Χρησιμοποιείται εσωτερικά και μπορεί να κληθεί χρησιμοποιώντας `@general` στα μηνύματα.
Μάθετε περισσότερα για τους [πράκτορες](https://opencode.ai/docs/agents).
### Οδηγός Χρήσης
Για περισσότερες πληροφορίες σχετικά με τη ρύθμιση του OpenCode, [**πλοηγήσου στον οδηγό χρήσης μας**](https://opencode.ai/docs).
### Συνεισφορά
Εάν ενδιαφέρεσαι να συνεισφέρεις στο OpenCode, διαβάστε τα [οδηγό χρήσης συνεισφοράς](./CONTRIBUTING.md) πριν υποβάλεις ένα pull request.
### Δημιουργία πάνω στο OpenCode
Εάν εργάζεσαι σε ένα έργο σχετικό με το OpenCode και χρησιμοποιείτε το "opencode" ως μέρος του ονόματός του, για παράδειγμα "opencode-dashboard" ή "opencode-mobile", πρόσθεσε μια σημείωση στο README σας για να διευκρινίσεις ότι δεν είναι κατασκευασμένο από την ομάδα του OpenCode και δεν έχει καμία σχέση με εμάς.
### Συχνές Ερωτήσεις
#### Πώς διαφέρει αυτό από το Claude Code;
Είναι πολύ παρόμοιο με το Claude Code ως προς τις δυνατότητες. Ακολουθούν οι βασικές διαφορές:
- 100% ανοιχτού κώδικα
- Δεν είναι συνδεδεμένο με κανέναν πάροχο. Αν και συνιστούμε τα μοντέλα που παρέχουμε μέσω του [OpenCode Zen](https://opencode.ai/zen), το OpenCode μπορεί να χρησιμοποιηθεί με Claude, OpenAI, Google, ή ακόμα και τοπικά μοντέλα. Καθώς τα μοντέλα εξελίσσονται, τα κενά μεταξύ τους θα κλείσουν και οι τιμές θα μειωθούν, οπότε είναι σημαντικό να είσαι ανεξάρτητος από τον πάροχο.
- Out-of-the-box υποστήριξη LSP
- Εστίαση στο TUI. Το OpenCode είναι κατασκευασμένο από χρήστες που χρησιμοποιούν neovim και τους δημιουργούς του [terminal.shop](https://terminal.shop)· θα εξαντλήσουμε τα όρια του τι είναι δυνατό στο terminal.
- Αρχιτεκτονική client/server. Αυτό, για παράδειγμα, μπορεί να επιτρέψει στο OpenCode να τρέχει στον υπολογιστή σου ενώ το χειρίζεσαι εξ αποστάσεως από μια εφαρμογή κινητού, που σημαίνει ότι το TUI frontend είναι μόνο ένας από τους πιθανούς clients.
---
**Γίνε μέλος της κοινότητάς μας** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -34,7 +34,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -34,7 +34,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -33,7 +33,8 @@
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a>
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

View File

@@ -50,6 +50,15 @@ async function clearPermissionDock(page: any, label: RegExp) {
}
}
async function setAutoAccept(page: any, enabled: boolean) {
const button = page.locator('[data-action="prompt-permissions"]').first()
await expect(button).toBeVisible()
const pressed = (await button.getAttribute("aria-pressed")) === "true"
if (pressed === enabled) return
await button.click()
await expect(button).toHaveAttribute("aria-pressed", enabled ? "true" : "false")
}
async function withMockPermission<T>(
page: any,
request: {
@@ -168,6 +177,7 @@ test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSess
test("blocked permission flow supports allow once", async ({ page, sdk, gotoSession }) => {
await withDockSession(sdk, "e2e composer dock permission once", async (session) => {
await gotoSession(session.id)
await setAutoAccept(page, false)
await withMockPermission(
page,
{
@@ -195,6 +205,7 @@ test("blocked permission flow supports allow once", async ({ page, sdk, gotoSess
test("blocked permission flow supports reject", async ({ page, sdk, gotoSession }) => {
await withDockSession(sdk, "e2e composer dock permission reject", async (session) => {
await gotoSession(session.id)
await setAutoAccept(page, false)
await withMockPermission(
page,
{
@@ -221,6 +232,7 @@ test("blocked permission flow supports reject", async ({ page, sdk, gotoSession
test("blocked permission flow supports allow always", async ({ page, sdk, gotoSession }) => {
await withDockSession(sdk, "e2e composer dock permission always", async (session) => {
await gotoSession(session.id)
await setAutoAccept(page, false)
await withMockPermission(
page,
{
@@ -300,6 +312,7 @@ test("child session permission request blocks parent dock and supports allow onc
}) => {
await withDockSession(sdk, "e2e composer dock child permission parent", async (session) => {
await gotoSession(session.id)
await setAutoAccept(page, false)
const child = await sdk.session
.create({

View File

@@ -2,6 +2,7 @@ import { createSignal } from "solid-js"
import { Dialog } from "@opencode-ai/ui/dialog"
import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
export type Highlight = {
@@ -16,6 +17,7 @@ export type Highlight = {
export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
const dialog = useDialog()
const language = useLanguage()
const settings = useSettings()
const [index, setIndex] = createSignal(0)
@@ -83,16 +85,16 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
<div class="flex flex-col items-start gap-3">
{isLast() ? (
<Button variant="primary" size="large" onClick={handleClose}>
Get started
{language.t("dialog.releaseNotes.action.getStarted")}
</Button>
) : (
<Button variant="secondary" size="large" onClick={handleNext}>
Next
{language.t("dialog.releaseNotes.action.next")}
</Button>
)}
<Button variant="ghost" size="small" onClick={handleDisable}>
Don't show these in the future
{language.t("dialog.releaseNotes.action.hideFuture")}
</Button>
</div>
@@ -128,7 +130,7 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
{feature()!.media!.type === "image" ? (
<img
src={feature()!.media!.src}
alt={feature()!.media!.alt ?? feature()?.title ?? "Release preview"}
alt={feature()!.media!.alt ?? feature()?.title ?? language.t("dialog.releaseNotes.media.alt")}
class="w-full h-full object-cover"
/>
) : (

View File

@@ -8,6 +8,7 @@ import fuzzysort from "fuzzysort"
import { createMemo, createResource, createSignal } from "solid-js"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { useLayout } from "@/context/layout"
import { useLanguage } from "@/context/language"
interface DialogSelectDirectoryProps {
@@ -19,6 +20,7 @@ interface DialogSelectDirectoryProps {
type Row = {
absolute: string
search: string
group: "recent" | "folders"
}
function cleanInput(value: string) {
@@ -101,7 +103,7 @@ function displayPath(path: string, input: string, home: string) {
return tildeOf(full, home) || full
}
function toRow(absolute: string, home: string): Row {
function toRow(absolute: string, home: string, group: Row["group"]): Row {
const full = trimTrailing(absolute)
const tilde = tildeOf(full, home)
const withSlash = (value: string) => {
@@ -113,7 +115,16 @@ function toRow(absolute: string, home: string): Row {
const search = Array.from(
new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)),
).join("\n")
return { absolute: full, search }
return { absolute: full, search, group }
}
function uniqueRows(rows: Row[]) {
const seen = new Set<string>()
return rows.filter((row) => {
if (seen.has(row.absolute)) return false
seen.add(row.absolute)
return true
})
}
function useDirectorySearch(args: {
@@ -237,6 +248,7 @@ function useDirectorySearch(args: {
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
const sync = useGlobalSync()
const sdk = useGlobalSDK()
const layout = useLayout()
const dialog = useDialog()
const language = useLanguage()
@@ -266,9 +278,42 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
start,
})
const recentProjects = createMemo(() => {
const projects = layout.projects.list()
const byProject = new Map<string, number>()
for (const project of projects) {
let at = 0
const dirs = [project.worktree, ...(project.sandboxes ?? [])]
for (const directory of dirs) {
const sessions = sync.child(directory, { bootstrap: false })[0].session
for (const session of sessions) {
if (session.time.archived) continue
const updated = session.time.updated ?? session.time.created
if (updated > at) at = updated
}
}
byProject.set(project.worktree, at)
}
return projects
.map((project, index) => ({ project, at: byProject.get(project.worktree) ?? 0, index }))
.sort((a, b) => b.at - a.at || a.index - b.index)
.slice(0, 5)
.map(({ project }) => {
const row = toRow(project.worktree, home(), "recent")
const name = project.name || getFilename(project.worktree)
return {
...row,
search: `${row.search}\n${name}`,
}
})
})
const items = async (value: string) => {
const results = await directories(value)
return results.map((absolute) => toRow(absolute, home()))
const directoryRows = results.map((absolute) => toRow(absolute, home(), "folders"))
return uniqueRows([...recentProjects(), ...directoryRows])
}
function resolve(absolute: string) {
@@ -285,6 +330,14 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
items={items}
key={(x) => x.absolute}
filterKeys={["search"]}
groupBy={(item) => item.group}
sortGroupsBy={(a, b) => {
if (a.category === b.category) return 0
return a.category === "recent" ? -1 : 1
}}
groupHeader={(group) =>
group.category === "recent" ? language.t("home.recentProjects") : language.t("command.project.open")
}
ref={(r) => (list = r)}
onFilter={(value) => setFilter(cleanInput(value))}
onKeyEvent={(e, item) => {

View File

@@ -449,7 +449,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
</div>
<Show when={item.updated}>
<span class="text-12-regular text-text-weak whitespace-nowrap ml-2">
{getRelativeTime(new Date(item.updated!).toISOString())}
{getRelativeTime(new Date(item.updated!).toISOString(), language.t)}
</span>
</Show>
</div>

View File

@@ -1310,43 +1310,45 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</div>
</div>
<Show when={store.mode === "normal" && permission.permissionsEnabled() && params.id}>
<div class="pointer-events-none absolute bottom-2 left-2">
<div class="pointer-events-auto">
<TooltipKeybind
placement="top"
gutter={8}
title={language.t(
accepting() ? "command.permissions.autoaccept.disable" : "command.permissions.autoaccept.enable",
)}
keybind={command.keybind("permissions.autoaccept")}
<div class="pointer-events-none absolute bottom-2 left-2">
<div class="pointer-events-auto">
<TooltipKeybind
placement="top"
gutter={8}
title={language.t(
accepting() ? "command.permissions.autoaccept.disable" : "command.permissions.autoaccept.enable",
)}
keybind={command.keybind("permissions.autoaccept")}
>
<Button
data-action="prompt-permissions"
variant="ghost"
disabled={!params.id}
onClick={() => {
if (!params.id) return
permission.toggleAutoAccept(params.id, sdk.directory)
}}
classList={{
"size-6 flex items-center justify-center": true,
"text-text-base": !accepting(),
"hover:bg-surface-success-base": accepting(),
}}
aria-label={
accepting()
? language.t("command.permissions.autoaccept.disable")
: language.t("command.permissions.autoaccept.enable")
}
aria-pressed={accepting()}
>
<Button
data-action="prompt-permissions"
variant="ghost"
onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)}
classList={{
"_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true,
"text-text-base": !accepting(),
"hover:bg-surface-success-base": accepting(),
}}
aria-label={
accepting()
? language.t("command.permissions.autoaccept.disable")
: language.t("command.permissions.autoaccept.enable")
}
aria-pressed={accepting()}
>
<Icon
name="chevron-double-right"
size="small"
classList={{ "text-icon-success-base": accepting() }}
/>
</Button>
</TooltipKeybind>
</div>
<Icon
name="chevron-double-right"
size="small"
classList={{ "text-icon-success-base": accepting() }}
/>
</Button>
</TooltipKeybind>
</div>
</Show>
</div>
</div>
</DockShellForm>
<Show when={store.mode === "normal" || store.mode === "shell"}>

View File

@@ -35,6 +35,7 @@ const OPEN_APPS = [
"terminal",
"iterm2",
"ghostty",
"warp",
"xcode",
"android-studio",
"powershell",
@@ -63,6 +64,7 @@ const MAC_APPS = [
{ id: "terminal", label: "Terminal", icon: "terminal", openWith: "Terminal" },
{ id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" },
{ id: "ghostty", label: "Ghostty", icon: "ghostty", openWith: "Ghostty" },
{ id: "warp", label: "Warp", icon: "warp", openWith: "Warp" },
{ id: "xcode", label: "Xcode", icon: "xcode", openWith: "Xcode" },
{
id: "android-studio",
@@ -428,7 +430,7 @@ export function SessionHeader() {
<Spinner class="size-3.5 text-icon-base" />
</Show>
</div>
<span class="text-12-regular text-text-strong">Open</span>
<span class="text-12-regular text-text-strong">{language.t("common.open")}</span>
</Button>
<div class="self-stretch w-px bg-border-weak-base" />
<DropdownMenu

View File

@@ -162,7 +162,7 @@ export const SettingsProviders: Component = () => {
when={canDisconnect(item)}
fallback={
<span class="text-14-regular text-text-base opacity-0 group-hover:opacity-100 transition-opacity duration-200 pr-3 cursor-default">
Connected from your environment variables
{language.t("settings.providers.connected.environmentDescription")}
</span>
}
>
@@ -229,10 +229,12 @@ export const SettingsProviders: Component = () => {
<div class="flex flex-col min-w-0">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
<ProviderIcon id={icon("synthetic")} class="size-5 shrink-0 icon-strong-base" />
<span class="text-14-medium text-text-strong">Custom provider</span>
<span class="text-14-medium text-text-strong">{language.t("provider.custom.title")}</span>
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
</div>
<span class="text-12-regular text-text-weak pl-8">Add an OpenAI-compatible provider by base URL.</span>
<span class="text-12-regular text-text-weak pl-8">
{language.t("settings.providers.custom.description")}
</span>
</div>
<Button
size="large"

View File

@@ -204,7 +204,10 @@ function createGlobalSync() {
showToast({
variant: "error",
title: language.t("toast.session.listFailed.title", { project }),
description: formatServerError(err),
description: formatServerError(err, {
unknown: language.t("error.chain.unknown"),
invalidConfiguration: language.t("error.server.invalidConfiguration"),
}),
})
})
@@ -234,6 +237,8 @@ function createGlobalSync() {
setStore: child[1],
vcsCache: cache,
loadSessions,
unknownError: language.t("error.chain.unknown"),
invalidConfigurationError: language.t("error.server.invalidConfiguration"),
})
})()
@@ -308,6 +313,9 @@ function createGlobalSync() {
url: globalSDK.url,
}),
requestFailedTitle: language.t("common.requestFailed"),
unknownError: language.t("error.chain.unknown"),
invalidConfigurationError: language.t("error.server.invalidConfiguration"),
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
setGlobalStore,
})
}

View File

@@ -36,6 +36,9 @@ export async function bootstrapGlobal(input: {
connectErrorTitle: string
connectErrorDescription: string
requestFailedTitle: string
unknownError: string
invalidConfigurationError: string
formatMoreCount: (count: number) => string
setGlobalStore: SetStoreFunction<GlobalStore>
}) {
const health = await input.globalSDK.global
@@ -88,8 +91,11 @@ export async function bootstrapGlobal(input: {
const results = await Promise.allSettled(tasks)
const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason)
if (errors.length) {
const message = errors[0] instanceof Error ? errors[0].message : String(errors[0])
const more = errors.length > 1 ? ` (+${errors.length - 1} more)` : ""
const message = formatServerError(errors[0], {
unknown: input.unknownError,
invalidConfiguration: input.invalidConfigurationError,
})
const more = errors.length > 1 ? input.formatMoreCount(errors.length - 1) : ""
showToast({
variant: "error",
title: input.requestFailedTitle,
@@ -116,6 +122,8 @@ export async function bootstrapDirectory(input: {
setStore: SetStoreFunction<State>
vcsCache: VcsCache
loadSessions: (directory: string) => Promise<void> | void
unknownError: string
invalidConfigurationError: string
}) {
if (input.store.status !== "complete") input.setStore("status", "loading")
@@ -137,7 +145,10 @@ export async function bootstrapDirectory(input: {
showToast({
variant: "error",
title: `Failed to reload ${project}`,
description: formatServerError(err),
description: formatServerError(err, {
unknown: input.unknownError,
invalidConfiguration: input.invalidConfigurationError,
}),
})
input.setStore("status", "partial")
return

View File

@@ -19,6 +19,7 @@ import { dict as no } from "@/i18n/no"
import { dict as br } from "@/i18n/br"
import { dict as th } from "@/i18n/th"
import { dict as bs } from "@/i18n/bs"
import { dict as tr } from "@/i18n/tr"
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
@@ -35,6 +36,7 @@ import { dict as uiNo } from "@opencode-ai/ui/i18n/no"
import { dict as uiBr } from "@opencode-ai/ui/i18n/br"
import { dict as uiTh } from "@opencode-ai/ui/i18n/th"
import { dict as uiBs } from "@opencode-ai/ui/i18n/bs"
import { dict as uiTr } from "@opencode-ai/ui/i18n/tr"
export type Locale =
| "en"
@@ -53,6 +55,7 @@ export type Locale =
| "br"
| "th"
| "bs"
| "tr"
type RawDictionary = typeof en & typeof uiEn
type Dictionary = i18n.Flatten<RawDictionary>
@@ -78,6 +81,7 @@ const LOCALES: readonly Locale[] = [
"no",
"br",
"th",
"tr",
]
const LABEL_KEY: Record<Locale, keyof Dictionary> = {
@@ -97,6 +101,7 @@ const LABEL_KEY: Record<Locale, keyof Dictionary> = {
br: "language.br",
th: "language.th",
bs: "language.bs",
tr: "language.tr",
}
const base = i18n.flatten({ ...en, ...uiEn })
@@ -117,6 +122,7 @@ const DICT: Record<Locale, Dictionary> = {
br: { ...base, ...i18n.flatten({ ...br, ...uiBr }) },
th: { ...base, ...i18n.flatten({ ...th, ...uiTh }) },
bs: { ...base, ...i18n.flatten({ ...bs, ...uiBs }) },
tr: { ...base, ...i18n.flatten({ ...tr, ...uiTr }) },
}
const localeMatchers: Array<{ locale: Locale; match: (language: string) => boolean }> = [
@@ -138,6 +144,7 @@ const localeMatchers: Array<{ locale: Locale; match: (language: string) => boole
{ locale: "br", match: (language) => language.startsWith("pt") },
{ locale: "th", match: (language) => language.startsWith("th") },
{ locale: "bs", match: (language) => language.startsWith("bs") },
{ locale: "tr", match: (language) => language.startsWith("tr") },
]
type ParityKey = "command.session.previous.unseen" | "command.session.next.unseen"
@@ -157,6 +164,7 @@ const PARITY_CHECK: Record<Exclude<Locale, "en">, Record<ParityKey, string>> = {
br,
th,
bs,
tr,
}
void PARITY_CHECK

View File

@@ -31,12 +31,33 @@ describe("autoRespondsPermission", () => {
expect(autoRespondsPermission({ root: true }, sessions, permission("child"), "/tmp/project")).toBe(true)
})
test("ignores auto-accept from unrelated sessions", () => {
test("defaults to auto-accept when no lineage override exists", () => {
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" }), session({ id: "other" })]
const autoAccept = {
other: true,
}
expect(autoRespondsPermission(autoAccept, sessions, permission("child"), "/tmp/project")).toBe(false)
expect(autoRespondsPermission(autoAccept, sessions, permission("child"), "/tmp/project")).toBe(true)
})
test("inherits a parent session's false override", () => {
const directory = "/tmp/project"
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
const autoAccept = {
[`${base64Encode(directory)}/root`]: false,
}
expect(autoRespondsPermission(autoAccept, sessions, permission("child"), directory)).toBe(false)
})
test("prefers a child override over parent override", () => {
const directory = "/tmp/project"
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
const autoAccept = {
[`${base64Encode(directory)}/root`]: false,
[`${base64Encode(directory)}/child`]: true,
}
expect(autoRespondsPermission(autoAccept, sessions, permission("child"), directory)).toBe(true)
})
})

View File

@@ -5,6 +5,11 @@ export function acceptKey(sessionID: string, directory?: string) {
return `${base64Encode(directory)}/${sessionID}`
}
function accepted(autoAccept: Record<string, boolean>, sessionID: string, directory?: string) {
const key = acceptKey(sessionID, directory)
return autoAccept[key] ?? autoAccept[sessionID]
}
function sessionLineage(session: { id: string; parentID?: string }[], sessionID: string) {
const parent = session.reduce((acc, item) => {
if (item.parentID) acc.set(item.id, item.parentID)
@@ -29,8 +34,8 @@ export function autoRespondsPermission(
permission: { sessionID: string },
directory?: string,
) {
return sessionLineage(session, permission.sessionID).some((id) => {
const key = acceptKey(id, directory)
return autoAccept[key] ?? autoAccept[id] ?? false
})
const value = sessionLineage(session, permission.sessionID)
.map((id) => accepted(autoAccept, id, directory))
.find((item): item is boolean => item !== undefined)
return value ?? true
}

View File

@@ -115,8 +115,8 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
}
function isAutoAccepting(sessionID: string, directory?: string) {
const key = acceptKey(sessionID, directory)
return store.autoAccept[key] ?? store.autoAccept[sessionID] ?? false
const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
}
function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
@@ -168,10 +168,11 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
function disable(sessionID: string, directory?: string) {
bumpEnableVersion(sessionID, directory)
const key = directory ? acceptKey(sessionID, directory) : undefined
const key = directory ? acceptKey(sessionID, directory) : sessionID
setStore(
produce((draft) => {
if (key) delete draft.autoAccept[key]
draft.autoAccept[key] = false
if (!directory) return
delete draft.autoAccept[sessionID]
}),
)

View File

@@ -734,4 +734,18 @@ export const dict = {
"workspace.reset.archived.one": "ستتم أرشفة جلسة واحدة.",
"workspace.reset.archived.many": "ستتم أرشفة {{count}} جلسات.",
"workspace.reset.note": "سيؤدي هذا إلى إعادة تعيين مساحة العمل لتتطابق مع الفرع الافتراضي.",
"common.open": "فتح",
"dialog.releaseNotes.action.getStarted": "البدء",
"dialog.releaseNotes.action.next": "التالي",
"dialog.releaseNotes.action.hideFuture": "عدم إظهار هذا في المستقبل",
"dialog.releaseNotes.media.alt": "معاينة الإصدار",
"toast.project.reloadFailed.title": "فشل في إعادة تحميل {{project}}",
"error.server.invalidConfiguration": "تكوين غير صالح",
"common.moreCountSuffix": " (+{{count}} إضافي)",
"common.time.justNow": "الآن",
"common.time.minutesAgo.short": "قبل {{count}} د",
"common.time.hoursAgo.short": "قبل {{count}} س",
"common.time.daysAgo.short": "قبل {{count}} ي",
"settings.providers.connected.environmentDescription": "متصل من متغيرات البيئة الخاصة بك",
"settings.providers.custom.description": "أضف مزود متوافق مع OpenAI بواسطة عنوان URL الأساسي.",
}

View File

@@ -742,4 +742,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sessão será arquivada.",
"workspace.reset.archived.many": "{{count}} sessões serão arquivadas.",
"workspace.reset.note": "Isso redefinirá o espaço de trabalho para corresponder ao branch padrão.",
"common.open": "Abrir",
"dialog.releaseNotes.action.getStarted": "Começar",
"dialog.releaseNotes.action.next": "Próximo",
"dialog.releaseNotes.action.hideFuture": "Não mostrar isso no futuro",
"dialog.releaseNotes.media.alt": "Prévia do lançamento",
"toast.project.reloadFailed.title": "Falha ao recarregar {{project}}",
"error.server.invalidConfiguration": "Configuração inválida",
"common.moreCountSuffix": " (+{{count}} mais)",
"common.time.justNow": "Agora mesmo",
"common.time.minutesAgo.short": "{{count}}m atrás",
"common.time.hoursAgo.short": "{{count}}h atrás",
"common.time.daysAgo.short": "{{count}}d atrás",
"settings.providers.connected.environmentDescription": "Conectado a partir de suas variáveis de ambiente",
"settings.providers.custom.description": "Adicionar um provedor compatível com a OpenAI através do URL base.",
}

View File

@@ -819,4 +819,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sesija će biti arhivirana.",
"workspace.reset.archived.many": "Biće arhivirano {{count}} sesija.",
"workspace.reset.note": "Ovo će resetovati radni prostor da odgovara podrazumijevanoj grani.",
"common.open": "Otvori",
"dialog.releaseNotes.action.getStarted": "Započni",
"dialog.releaseNotes.action.next": "Sljedeće",
"dialog.releaseNotes.action.hideFuture": "Ne prikazuj ovo u budućnosti",
"dialog.releaseNotes.media.alt": "Pregled izdanja",
"toast.project.reloadFailed.title": "Nije uspjelo ponovno učitavanje {{project}}",
"error.server.invalidConfiguration": "Nevažeća konfiguracija",
"common.moreCountSuffix": " (+{{count}} više)",
"common.time.justNow": "Upravo sada",
"common.time.minutesAgo.short": "prije {{count}} min",
"common.time.hoursAgo.short": "prije {{count}} h",
"common.time.daysAgo.short": "prije {{count}} d",
"settings.providers.connected.environmentDescription": "Povezano sa vašim varijablama okruženja",
"settings.providers.custom.description": "Dodajte provajdera kompatibilnog s OpenAI putem osnovnog URL-a.",
}

View File

@@ -813,4 +813,18 @@ export const dict = {
"workspace.reset.archived.one": "1 session vil blive arkiveret.",
"workspace.reset.archived.many": "{{count}} sessioner vil blive arkiveret.",
"workspace.reset.note": "Dette vil nulstille arbejdsområdet til at matche hovedgrenen.",
"common.open": "Åbn",
"dialog.releaseNotes.action.getStarted": "Kom i gang",
"dialog.releaseNotes.action.next": "Næste",
"dialog.releaseNotes.action.hideFuture": "Vis ikke disse i fremtiden",
"dialog.releaseNotes.media.alt": "Forhåndsvisning af udgivelse",
"toast.project.reloadFailed.title": "Kunne ikke genindlæse {{project}}",
"error.server.invalidConfiguration": "Ugyldig konfiguration",
"common.moreCountSuffix": " (+{{count}} mere)",
"common.time.justNow": "Lige nu",
"common.time.minutesAgo.short": "{{count}}m siden",
"common.time.hoursAgo.short": "{{count}}t siden",
"common.time.daysAgo.short": "{{count}}d siden",
"settings.providers.connected.environmentDescription": "Tilsluttet fra dine miljøvariabler",
"settings.providers.custom.description": "Tilføj en OpenAI-kompatibel udbyder via basis-URL.",
}

View File

@@ -751,4 +751,18 @@ export const dict = {
"workspace.reset.archived.one": "1 Sitzung wird archiviert.",
"workspace.reset.archived.many": "{{count}} Sitzungen werden archiviert.",
"workspace.reset.note": "Dadurch wird der Arbeitsbereich auf den Standard-Branch zurückgesetzt.",
"common.open": "Öffnen",
"dialog.releaseNotes.action.getStarted": "Loslegen",
"dialog.releaseNotes.action.next": "Weiter",
"dialog.releaseNotes.action.hideFuture": "In Zukunft nicht mehr anzeigen",
"dialog.releaseNotes.media.alt": "Vorschau auf die Version",
"toast.project.reloadFailed.title": "Fehler beim Neuladen von {{project}}",
"error.server.invalidConfiguration": "Ungültige Konfiguration",
"common.moreCountSuffix": " (+{{count}} weitere)",
"common.time.justNow": "Gerade eben",
"common.time.minutesAgo.short": "vor {{count}} Min",
"common.time.hoursAgo.short": "vor {{count}} Std",
"common.time.daysAgo.short": "vor {{count}} Tg",
"settings.providers.connected.environmentDescription": "Verbunden aus Ihren Umgebungsvariablen",
"settings.providers.custom.description": "Fügen Sie einen OpenAI-kompatiblen Anbieter per Basis-URL hinzu.",
} satisfies Partial<Record<Keys, string>>

View File

@@ -218,6 +218,7 @@ export const dict = {
"common.loading": "Loading",
"common.loading.ellipsis": "...",
"common.cancel": "Cancel",
"common.open": "Open",
"common.connect": "Connect",
"common.disconnect": "Disconnect",
"common.submit": "Submit",
@@ -347,6 +348,11 @@ export const dict = {
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "e.g. bun install",
"dialog.releaseNotes.action.getStarted": "Get started",
"dialog.releaseNotes.action.next": "Next",
"dialog.releaseNotes.action.hideFuture": "Don't show these in the future",
"dialog.releaseNotes.media.alt": "Release preview",
"context.breakdown.title": "Context Breakdown",
"context.breakdown.note": 'Approximate breakdown of input tokens. "Other" includes tool definitions and overhead.',
"context.breakdown.system": "System",
@@ -397,6 +403,7 @@ export const dict = {
"language.br": "Português (Brasil)",
"language.bs": "Bosanski",
"language.th": "ไทย",
"language.tr": "Türkçe",
"toast.language.title": "Language",
"toast.language.description": "Switched to {{language}}",
@@ -435,6 +442,7 @@ export const dict = {
"toast.session.unshare.failed.description": "An error occurred while unsharing the session",
"toast.session.listFailed.title": "Failed to load sessions for {{project}}",
"toast.project.reloadFailed.title": "Failed to reload {{project}}",
"toast.update.title": "Update available",
"toast.update.description": "A new version of OpenCode ({{version}}) is now available to install.",
@@ -459,6 +467,7 @@ export const dict = {
"directory.error.invalidUrl": "Invalid directory in URL.",
"error.chain.unknown": "Unknown error",
"error.server.invalidConfiguration": "Invalid configuration",
"error.chain.causedBy": "Caused by:",
"error.chain.apiError": "API error",
"error.chain.status": "Status: {{status}}",
@@ -569,6 +578,7 @@ export const dict = {
"common.closeTab": "Close tab",
"common.dismiss": "Dismiss",
"common.moreCountSuffix": " (+{{count}} more)",
"common.requestFailed": "Request failed",
"common.moreOptions": "More options",
"common.learnMore": "Learn more",
@@ -581,6 +591,11 @@ export const dict = {
"common.loadMore": "Load more",
"common.key.esc": "ESC",
"common.time.justNow": "Just now",
"common.time.minutesAgo.short": "{{count}}m ago",
"common.time.hoursAgo.short": "{{count}}h ago",
"common.time.daysAgo.short": "{{count}}d ago",
"sidebar.menu.toggle": "Toggle menu",
"sidebar.nav.projectsAndSessions": "Projects and sessions",
"sidebar.settings": "Settings",
@@ -741,7 +756,9 @@ export const dict = {
"settings.providers.description": "Provider settings will be configurable here.",
"settings.providers.section.connected": "Connected providers",
"settings.providers.connected.empty": "No connected providers",
"settings.providers.connected.environmentDescription": "Connected from your environment variables",
"settings.providers.section.popular": "Popular providers",
"settings.providers.custom.description": "Add an OpenAI-compatible provider by base URL.",
"settings.providers.tag.environment": "Environment",
"settings.providers.tag.config": "Config",
"settings.providers.tag.custom": "Custom",

View File

@@ -825,4 +825,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sesión será archivada.",
"workspace.reset.archived.many": "{{count}} sesiones serán archivadas.",
"workspace.reset.note": "Esto restablecerá el espacio de trabajo para coincidir con la rama predeterminada.",
"common.open": "Abrir",
"dialog.releaseNotes.action.getStarted": "Comenzar",
"dialog.releaseNotes.action.next": "Siguiente",
"dialog.releaseNotes.action.hideFuture": "No mostrar esto en el futuro",
"dialog.releaseNotes.media.alt": "Vista previa de la versión",
"toast.project.reloadFailed.title": "Error al recargar {{project}}",
"error.server.invalidConfiguration": "Configuración inválida",
"common.moreCountSuffix": " (+{{count}} más)",
"common.time.justNow": "Justo ahora",
"common.time.minutesAgo.short": "hace {{count}} min",
"common.time.hoursAgo.short": "hace {{count}} h",
"common.time.daysAgo.short": "hace {{count}} d",
"settings.providers.connected.environmentDescription": "Conectado desde tus variables de entorno",
"settings.providers.custom.description": "Añade un proveedor compatible con OpenAI por su URL base.",
}

View File

@@ -749,4 +749,18 @@ export const dict = {
"workspace.reset.archived.one": "1 session sera archivée.",
"workspace.reset.archived.many": "{{count}} sessions seront archivées.",
"workspace.reset.note": "Cela réinitialisera l'espace de travail pour correspondre à la branche par défaut.",
"common.open": "Ouvrir",
"dialog.releaseNotes.action.getStarted": "Commencer",
"dialog.releaseNotes.action.next": "Suivant",
"dialog.releaseNotes.action.hideFuture": "Ne plus afficher à l'avenir",
"dialog.releaseNotes.media.alt": "Aperçu de la version",
"toast.project.reloadFailed.title": "Échec du rechargement de {{project}}",
"error.server.invalidConfiguration": "Configuration invalide",
"common.moreCountSuffix": " (+{{count}} de plus)",
"common.time.justNow": "À l'instant",
"common.time.minutesAgo.short": "il y a {{count}}m",
"common.time.hoursAgo.short": "il y a {{count}}h",
"common.time.daysAgo.short": "il y a {{count}}j",
"settings.providers.connected.environmentDescription": "Connecté à partir de vos variables d'environnement",
"settings.providers.custom.description": "Ajouter un fournisseur compatible avec OpenAI via l'URL de base.",
}

View File

@@ -738,4 +738,18 @@ export const dict = {
"workspace.reset.archived.one": "1つのセッションがアーカイブされます。",
"workspace.reset.archived.many": "{{count}}個のセッションがアーカイブされます。",
"workspace.reset.note": "これにより、ワークスペースはデフォルトブランチと一致するようにリセットされます。",
"common.open": "開く",
"dialog.releaseNotes.action.getStarted": "始める",
"dialog.releaseNotes.action.next": "次へ",
"dialog.releaseNotes.action.hideFuture": "今後表示しない",
"dialog.releaseNotes.media.alt": "リリースのプレビュー",
"toast.project.reloadFailed.title": "{{project}} の再読み込みに失敗しました",
"error.server.invalidConfiguration": "無効な設定",
"common.moreCountSuffix": " (他 {{count}} 件)",
"common.time.justNow": "たった今",
"common.time.minutesAgo.short": "{{count}} 分前",
"common.time.hoursAgo.short": "{{count}} 時間前",
"common.time.daysAgo.short": "{{count}} 日前",
"settings.providers.connected.environmentDescription": "環境変数から接続されました",
"settings.providers.custom.description": "ベース URL を指定して OpenAI 互換のプロバイダーを追加します。",
}

View File

@@ -738,4 +738,18 @@ export const dict = {
"workspace.reset.archived.one": "1개의 세션이 보관됩니다.",
"workspace.reset.archived.many": "{{count}}개의 세션이 보관됩니다.",
"workspace.reset.note": "이 작업은 작업 공간을 기본 브랜치와 일치하도록 재설정합니다.",
"common.open": "열기",
"dialog.releaseNotes.action.getStarted": "시작하기",
"dialog.releaseNotes.action.next": "다음",
"dialog.releaseNotes.action.hideFuture": "다시 보지 않기",
"dialog.releaseNotes.media.alt": "릴리스 미리보기",
"toast.project.reloadFailed.title": "{{project}} 다시 불러오기 실패",
"error.server.invalidConfiguration": "잘못된 구성",
"common.moreCountSuffix": " (외 {{count}}개)",
"common.time.justNow": "방금 전",
"common.time.minutesAgo.short": "{{count}}분 전",
"common.time.hoursAgo.short": "{{count}}시간 전",
"common.time.daysAgo.short": "{{count}}일 전",
"settings.providers.connected.environmentDescription": "환경 변수에서 연결됨",
"settings.providers.custom.description": "기본 URL로 OpenAI 호환 공급자를 추가합니다.",
}

View File

@@ -821,4 +821,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sesjon vil bli arkivert.",
"workspace.reset.archived.many": "{{count}} sesjoner vil bli arkivert.",
"workspace.reset.note": "Dette vil tilbakestille arbeidsområdet til å samsvare med standardgrenen.",
"common.open": "Åpne",
"dialog.releaseNotes.action.getStarted": "Kom i gang",
"dialog.releaseNotes.action.next": "Neste",
"dialog.releaseNotes.action.hideFuture": "Ikke vis disse igjen",
"dialog.releaseNotes.media.alt": "Forhåndsvisning av utgivelse",
"toast.project.reloadFailed.title": "Kunne ikke laste inn {{project}} på nytt",
"error.server.invalidConfiguration": "Ugyldig konfigurasjon",
"common.moreCountSuffix": " (+{{count}} mer)",
"common.time.justNow": "Akkurat nå",
"common.time.minutesAgo.short": "{{count}} m siden",
"common.time.hoursAgo.short": "{{count}} t siden",
"common.time.daysAgo.short": "{{count}} d siden",
"settings.providers.connected.environmentDescription": "Koblet til fra miljøvariablene dine",
"settings.providers.custom.description": "Legg til en OpenAI-kompatibel leverandør via basis-URL.",
} satisfies Partial<Record<Keys, string>>

View File

@@ -15,8 +15,9 @@ import { dict as ru } from "./ru"
import { dict as th } from "./th"
import { dict as zh } from "./zh"
import { dict as zht } from "./zht"
import { dict as tr } from "./tr"
const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, th, zh, zht]
const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, th, tr, zh, zht]
const keys = ["command.session.previous.unseen", "command.session.next.unseen"] as const
describe("i18n parity", () => {

View File

@@ -740,4 +740,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sesja zostanie zarchiwizowana.",
"workspace.reset.archived.many": "{{count}} sesji zostanie zarchiwizowanych.",
"workspace.reset.note": "To zresetuje przestrzeń roboczą, aby odpowiadała domyślnej gałęzi.",
"common.open": "Otwórz",
"dialog.releaseNotes.action.getStarted": "Rozpocznij",
"dialog.releaseNotes.action.next": "Dalej",
"dialog.releaseNotes.action.hideFuture": "Nie pokazuj tego w przyszłości",
"dialog.releaseNotes.media.alt": "Podgląd wydania",
"toast.project.reloadFailed.title": "Nie udało się ponownie wczytać {{project}}",
"error.server.invalidConfiguration": "Nieprawidłowa konfiguracja",
"common.moreCountSuffix": " (jeszcze {{count}})",
"common.time.justNow": "Przed chwilą",
"common.time.minutesAgo.short": "{{count}} min temu",
"common.time.hoursAgo.short": "{{count}} godz. temu",
"common.time.daysAgo.short": "{{count}} dni temu",
"settings.providers.connected.environmentDescription": "Połączono ze zmiennymi środowiskowymi",
"settings.providers.custom.description": "Dodaj dostawcę zgodnego z OpenAI poprzez podstawowy URL.",
}

View File

@@ -821,4 +821,18 @@ export const dict = {
"workspace.reset.archived.one": "1 сессия будет архивирована.",
"workspace.reset.archived.many": "{{count}} сессий будет архивировано.",
"workspace.reset.note": "Рабочее пространство будет сброшено в соответствие с веткой по умолчанию.",
"common.open": "Открыть",
"dialog.releaseNotes.action.getStarted": "Начать",
"dialog.releaseNotes.action.next": "Далее",
"dialog.releaseNotes.action.hideFuture": "Больше не показывать",
"dialog.releaseNotes.media.alt": "Превью релиза",
"toast.project.reloadFailed.title": "Не удалось перезагрузить {{project}}",
"error.server.invalidConfiguration": "Недопустимая конфигурация",
"common.moreCountSuffix": " (ещё {{count}})",
"common.time.justNow": "Только что",
"common.time.minutesAgo.short": "{{count}} мин назад",
"common.time.hoursAgo.short": "{{count}} ч назад",
"common.time.daysAgo.short": "{{count}} д назад",
"settings.providers.connected.environmentDescription": "Подключено из ваших переменных окружения",
"settings.providers.custom.description": "Добавить провайдера, совместимого с OpenAI, по базовому URL.",
}

View File

@@ -811,4 +811,18 @@ export const dict = {
"workspace.reset.archived.one": "1 เซสชันจะถูกจัดเก็บ",
"workspace.reset.archived.many": "{{count}} เซสชันจะถูกจัดเก็บ",
"workspace.reset.note": "สิ่งนี้จะรีเซ็ตพื้นที่ทำงานให้ตรงกับสาขาเริ่มต้น",
"common.open": "เปิด",
"dialog.releaseNotes.action.getStarted": "เริ่มต้น",
"dialog.releaseNotes.action.next": "ถัดไป",
"dialog.releaseNotes.action.hideFuture": "ไม่ต้องแสดงสิ่งนี้อีกในอนาคต",
"dialog.releaseNotes.media.alt": "ตัวอย่างรุ่น",
"toast.project.reloadFailed.title": "ไม่สามารถโหลด {{project}} ใหม่ได้",
"error.server.invalidConfiguration": "การกำหนดค่าไม่ถูกต้อง",
"common.moreCountSuffix": " (เพิ่มอีก {{count}})",
"common.time.justNow": "เมื่อสักครู่นี้",
"common.time.minutesAgo.short": "{{count}} นาทีที่แล้ว",
"common.time.hoursAgo.short": "{{count}} ชม. ที่แล้ว",
"common.time.daysAgo.short": "{{count}} วันที่แล้ว",
"settings.providers.connected.environmentDescription": "เชื่อมต่อจากตัวแปรสภาพแวดล้อมของคุณ",
"settings.providers.custom.description": "เพิ่มผู้ให้บริการที่รองรับ OpenAI ด้วย URL หลัก",
}

849
packages/app/src/i18n/tr.ts Normal file
View File

@@ -0,0 +1,849 @@
import { dict as en } from "./en"
type Keys = keyof typeof en
export const dict = {
"command.category.suggested": "Önerilen",
"command.category.view": "Görünüm",
"command.category.project": "Proje",
"command.category.provider": "Sağlayıcı",
"command.category.server": "Sunucu",
"command.category.session": "Oturum",
"command.category.theme": "Tema",
"command.category.language": "Dil",
"command.category.file": "Dosya",
"command.category.context": "Bağlam",
"command.category.terminal": "Terminal",
"command.category.model": "Model",
"command.category.mcp": "MCP",
"command.category.agent": "Ajan",
"command.category.permissions": "İzinler",
"command.category.workspace": "Çalışma Alanı",
"command.category.settings": "Ayarlar",
"theme.scheme.system": "Sistem",
"theme.scheme.light": "Açık",
"theme.scheme.dark": "Koyu",
"command.sidebar.toggle": "Kenar çubuğunu aç/kapat",
"command.project.open": "Proje aç",
"command.provider.connect": "Sağlayıcı bağla",
"command.server.switch": "Sunucu değiştir",
"command.settings.open": "Ayarları aç",
"command.session.previous": "Önceki oturum",
"command.session.next": "Sonraki oturum",
"command.session.previous.unseen": "Önceki okunmamış oturum",
"command.session.next.unseen": "Sonraki okunmamış oturum",
"command.session.archive": "Oturumu arşivle",
"command.palette": "Komut paleti",
"command.theme.cycle": "Tema değiştir",
"command.theme.set": "Tema kullan: {{theme}}",
"command.theme.scheme.cycle": "Renk şemasını değiştir",
"command.theme.scheme.set": "Renk şeması kullan: {{scheme}}",
"command.language.cycle": "Dil değiştir",
"command.language.set": "Dil kullan: {{language}}",
"command.session.new": "Yeni oturum",
"command.file.open": "Dosya aç",
"command.tab.close": "Sekmeyi kapat",
"command.context.addSelection": "Seçimi bağlama ekle",
"command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle",
"command.input.focus": "Girişi odakla",
"command.terminal.toggle": "Terminali aç/kapat",
"command.fileTree.toggle": "Dosya ağacını aç/kapat",
"command.review.toggle": "İncelemeyi aç/kapat",
"command.terminal.new": "Yeni terminal",
"command.terminal.new.description": "Yeni bir terminal sekmesi oluştur",
"command.steps.toggle": "Adımları aç/kapat",
"command.steps.toggle.description": "Mevcut mesaj için adımları göster veya gizle",
"command.message.previous": "Önceki mesaj",
"command.message.previous.description": "Önceki kullanıcı mesajına git",
"command.message.next": "Sonraki mesaj",
"command.message.next.description": "Sonraki kullanıcı mesajına git",
"command.model.choose": "Model seç",
"command.model.choose.description": "Farklı bir model seç",
"command.mcp.toggle": "MCP'leri aç/kapat",
"command.mcp.toggle.description": "MCP'leri aç/kapat",
"command.agent.cycle": "Ajan değiştir",
"command.agent.cycle.description": "Sonraki ajana geç",
"command.agent.cycle.reverse": "Ajanı geri değiştir",
"command.agent.cycle.reverse.description": "Önceki ajana geç",
"command.model.variant.cycle": "Düşünme eforu değiştir",
"command.model.variant.cycle.description": "Sonraki efor seviyesine geç",
"command.prompt.mode.shell": "Kabuk",
"command.prompt.mode.normal": "Komut",
"command.permissions.autoaccept.enable": "Düzenlemeleri otomatik kabul et",
"command.permissions.autoaccept.disable": "Otomatik kabulü durdur",
"command.workspace.toggle": "Çalışma alanlarını aç/kapat",
"command.workspace.toggle.description": "Kenar çubuğunda birden fazla çalışma alanını göster veya gizle",
"command.session.undo": "Geri al",
"command.session.undo.description": "Son mesajı geri al",
"command.session.redo": "Yinele",
"command.session.redo.description": "Son geri alınan mesajı yinele",
"command.session.compact": "Oturumu sıkıştır",
"command.session.compact.description": "Bağlam boyutunu azaltmak için oturumu özetle",
"command.session.fork": "Mesajdan dallandır",
"command.session.fork.description": "Önceki bir mesajdan yeni oturum oluştur",
"command.session.share": "Oturumu paylaş",
"command.session.share.description": "Bu oturumu paylaş ve URL'yi panoya kopyala",
"command.session.unshare": "Paylaşımı kaldır",
"command.session.unshare.description": "Bu oturumun paylaşımını durdur",
"palette.search.placeholder": "Dosya, komut ve oturum ara",
"palette.empty": "Sonuç bulunamadı",
"palette.group.commands": "Komutlar",
"palette.group.files": "Dosyalar",
"dialog.provider.search.placeholder": "Sağlayıcı ara",
"dialog.provider.empty": "Sağlayıcı bulunamadı",
"dialog.provider.group.popular": "Popüler",
"dialog.provider.group.other": "Diğer",
"dialog.provider.tag.recommended": "Önerilen",
"dialog.provider.opencode.note": "Claude, GPT, Gemini ve daha fazlasını içeren seçilmiş modeller",
"dialog.provider.opencode.tagline": "Güvenilir optimize edilmiş modeller",
"dialog.provider.opencodeGo.tagline": "Herkes için düşük maliyetli abonelik",
"dialog.provider.anthropic.note": "Pro ve Max dahil Claude modellerine doğrudan erişim",
"dialog.provider.copilot.note": "GitHub Copilot üzerinden kodlama yardımı için yapay zekâ modelleri",
"dialog.provider.openai.note": "Hızlı ve yetenekli genel yapay zekâ görevleri için GPT modelleri",
"dialog.provider.google.note": "Hızlı ve yapılandırılmış yanıtlar için Gemini modelleri",
"dialog.provider.openrouter.note": "Tek bir sağlayıcıdan tüm desteklenen modellere eriş",
"dialog.provider.vercel.note": "Akıllı yönlendirme ile yapay zekâ modellerine birleşik erişim",
"dialog.model.select.title": "Model seç",
"dialog.model.search.placeholder": "Model ara",
"dialog.model.empty": "Model sonucu yok",
"dialog.model.manage": "Modelleri yönet",
"dialog.model.manage.description": "Model seçicide hangi modellerin görüneceğini özelleştirin.",
"dialog.model.manage.provider.toggle": "Tüm {{provider}} modellerini aç/kapat",
"dialog.model.unpaid.freeModels.title": "OpenCode tarafından sunulan ücretsiz modeller",
"dialog.model.unpaid.addMore.title": "Popüler sağlayıcılardan daha fazla model ekleyin",
"dialog.provider.viewAll": "Daha fazla sağlayıcı göster",
"provider.connect.title": "{{provider}} bağla",
"provider.connect.title.anthropicProMax": "Claude Pro/Max ile giriş yap",
"provider.connect.selectMethod": "{{provider}} için giriş yöntemini seçin.",
"provider.connect.method.apiKey": "API anahtarı",
"provider.connect.status.inProgress": "Yetkilendirme devam ediyor...",
"provider.connect.status.waiting": "Yetkilendirme bekleniyor...",
"provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}",
"provider.connect.apiKey.description":
"{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için {{provider}} API anahtarınızı girin.",
"provider.connect.apiKey.label": "{{provider}} API anahtarı",
"provider.connect.apiKey.placeholder": "API anahtarı",
"provider.connect.apiKey.required": "API anahtarı gerekli",
"provider.connect.opencodeZen.line1":
"OpenCode Zen, kodlama ajanları için seçilmiş güvenilir optimize edilmiş modellere erişim sağlar.",
"provider.connect.opencodeZen.line2":
"Tek bir API anahtarıyla Claude, GPT, Gemini, GLM ve daha fazlası gibi modellere erişebilirsiniz.",
"provider.connect.opencodeZen.visit.prefix": "",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.",
"provider.connect.oauth.code.visit.prefix":
"Hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için ",
"provider.connect.oauth.code.visit.link": "bu bağlantıya",
"provider.connect.oauth.code.visit.suffix": " tıklayarak yetkilendirme kodunuzu alın.",
"provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu",
"provider.connect.oauth.code.placeholder": "Yetkilendirme kodu",
"provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli",
"provider.connect.oauth.code.invalid": "Geçersiz yetkilendirme kodu",
"provider.connect.oauth.auto.visit.prefix": "",
"provider.connect.oauth.auto.visit.link": "Bu bağlantıya",
"provider.connect.oauth.auto.visit.suffix":
" tıklayarak aşağıdaki kodu girin ve hesabınızı bağlayarak OpenCode'da {{provider}} modellerini kullanın.",
"provider.connect.oauth.auto.confirmationCode": "Onay kodu",
"provider.connect.toast.connected.title": "{{provider}} bağlandı",
"provider.connect.toast.connected.description": "{{provider}} modelleri artık kullanımda.",
"provider.custom.title": "Özel sağlayıcı",
"provider.custom.description.prefix": "OpenAI uyumlu bir sağlayıcı yapılandırın. ",
"provider.custom.description.link": "Sağlayıcı yapılandırma dökümanları",
"provider.custom.description.suffix": " sayfasına bakın.",
"provider.custom.field.providerID.label": "Sağlayıcı kimlik",
"provider.custom.field.providerID.placeholder": "saglayicim",
"provider.custom.field.providerID.description": "Küçük harfler, rakamlar, tire veya alt çizgi",
"provider.custom.field.name.label": "Görünen ad",
"provider.custom.field.name.placeholder": "Yapay Zekâ Sağlayıcım",
"provider.custom.field.baseURL.label": "Temel URL",
"provider.custom.field.baseURL.placeholder": "https://api.saglayicim.com/v1",
"provider.custom.field.apiKey.label": "API anahtarı",
"provider.custom.field.apiKey.placeholder": "API anahtarı",
"provider.custom.field.apiKey.description":
"İsteğe bağlı. Kimlik doğrulamayı başlıklar ile yönetiyorsanız boş bırakın.",
"provider.custom.models.label": "Modeller",
"provider.custom.models.id.label": "Kimlik",
"provider.custom.models.id.placeholder": "model-kimlik",
"provider.custom.models.name.label": "Ad",
"provider.custom.models.name.placeholder": "Görünen Ad",
"provider.custom.models.remove": "Modeli kaldır",
"provider.custom.models.add": "Model ekle",
"provider.custom.headers.label": "Başlıklar (isteğe bağlı)",
"provider.custom.headers.key.label": "Başlık",
"provider.custom.headers.key.placeholder": "Başlık-Adı",
"provider.custom.headers.value.label": "Değer",
"provider.custom.headers.value.placeholder": "değer",
"provider.custom.headers.remove": "Başlığı kaldır",
"provider.custom.headers.add": "Başlık ekle",
"provider.custom.error.providerID.required": "Sağlayıcı kimlik gerekli",
"provider.custom.error.providerID.format": "Küçük harf, rakam, tire veya alt çizgi kullanın",
"provider.custom.error.providerID.exists": "Bu sağlayıcı kimlik zaten mevcut",
"provider.custom.error.name.required": "Görünen ad gerekli",
"provider.custom.error.baseURL.required": "Temel URL gerekli",
"provider.custom.error.baseURL.format": "http:// veya https:// ile başlamalı",
"provider.custom.error.required": "Gerekli",
"provider.custom.error.duplicate": "Tekrar",
"provider.disconnect.toast.disconnected.title": "{{provider}} bağlantısı kesildi",
"provider.disconnect.toast.disconnected.description": "{{provider}} modelleri artık kullanılabilir değil.",
"model.tag.free": "Ücretsiz",
"model.tag.latest": "En yeni",
"model.provider.anthropic": "Anthropic",
"model.provider.openai": "OpenAI",
"model.provider.google": "Google",
"model.provider.xai": "xAI",
"model.provider.meta": "Meta",
"model.input.text": "metin",
"model.input.image": "görsel",
"model.input.audio": "ses",
"model.input.video": "video",
"model.input.pdf": "pdf",
"model.tooltip.allows": "Kabul eder: {{inputs}}",
"model.tooltip.reasoning.allowed": "Akıl yürütme destekler",
"model.tooltip.reasoning.none": "Akıl yürütme yok",
"model.tooltip.context": "Bağlam limiti {{limit}}",
"common.search.placeholder": "Ara",
"common.goBack": "Geri git",
"common.goForward": "İleri git",
"common.loading": "Yükleniyor",
"common.loading.ellipsis": "...",
"common.cancel": "İptal",
"common.connect": "Bağlan",
"common.disconnect": "Bağlantı Kes",
"common.submit": "Gönder",
"common.save": "Kaydet",
"common.saving": "Kaydediliyor...",
"common.default": "Varsayılan",
"common.attachment": "ek",
"prompt.placeholder.shell": "Kabuk komutu girin...",
"prompt.placeholder.normal": 'Bir şeyler sorun... "{{example}}"',
"prompt.placeholder.simple": "Bir şeyler sorun...",
"prompt.placeholder.summarizeComments": "Yorumları özetle…",
"prompt.placeholder.summarizeComment": "Yorumu özetle…",
"prompt.mode.shell": "Kabuk",
"prompt.mode.normal": "Komut",
"prompt.mode.shell.exit": ıkmak için esc",
"prompt.example.1": "Kod tabanındaki bir TODO'yu düzelt",
"prompt.example.2": "Bu projenin teknoloji yığını nedir?",
"prompt.example.3": "Bozuk testleri düzelt",
"prompt.example.4": "Kimlik doğrulamanın nasıl çalıştığınııkla",
"prompt.example.5": "Güvenlik açıkları bul ve düzelt",
"prompt.example.6": "Kullanıcı servisi için birim testleri ekle",
"prompt.example.7": "Bu fonksiyonu daha okunabilir hale getir",
"prompt.example.8": "Bu hata ne anlama geliyor?",
"prompt.example.9": "Bu sorunu ayıklamama yardım et",
"prompt.example.10": "API dokümantasyonu oluştur",
"prompt.example.11": "Veritabanı sorgularını optimize et",
"prompt.example.12": "Girdi doğrulama ekle",
"prompt.example.13": "İçin yeni bir bileşen oluştur...",
"prompt.example.14": "Bu projeyi nasıl dağıtabilirim?",
"prompt.example.15": "Kodumu en iyi uygulamalar için incele",
"prompt.example.16": "Bu fonksiyona hata yönetimi ekle",
"prompt.example.17": "Bu regex kalıbınııkla",
"prompt.example.18": "Bunu TypeScript'e dönüştür",
"prompt.example.19": "Kod tabanı boyunca loglama ekle",
"prompt.example.20": "Hangi bağımlılıklar güncellenmemiş?",
"prompt.example.21": "Bir göç betiği yazmama yardım et",
"prompt.example.22": "Bu uç nokta için önbellekleme uygula",
"prompt.example.23": "Bu listeye sayfalama ekle",
"prompt.example.24": "İçin bir CLI komutu oluştur...",
"prompt.example.25": "Ortam değişkenleri burada nasıl çalışıyor?",
"prompt.popover.emptyResults": "Eşleşen sonuç yok",
"prompt.popover.emptyCommands": "Eşleşen komut yok",
"prompt.dropzone.label": "Görsel veya PDF'leri buraya bırakın",
"prompt.dropzone.file.label": "@bahsetmek için dosyayı bırakın",
"prompt.slash.badge.custom": "özel",
"prompt.slash.badge.skill": "beceri",
"prompt.slash.badge.mcp": "mcp",
"prompt.context.active": "aktif",
"prompt.context.includeActiveFile": "Aktif dosyayı dahil et",
"prompt.context.removeActiveFile": "Aktif dosyayı bağlamdan çıkar",
"prompt.context.removeFile": "Dosyayı bağlamdan çıkar",
"prompt.action.attachFile": "Dosya ekle",
"prompt.attachment.remove": "Eki kaldır",
"prompt.action.send": "Gönder",
"prompt.action.stop": "Durdur",
"prompt.toast.pasteUnsupported.title": "Desteklenmeyen yapıştırma",
"prompt.toast.pasteUnsupported.description": "Buraya sadece görsel veya PDF yapıştırılabilir.",
"prompt.toast.modelAgentRequired.title": "Bir ajan ve model seçin",
"prompt.toast.modelAgentRequired.description": "Komut göndermeden önce bir ajan ve model seçin.",
"prompt.toast.worktreeCreateFailed.title": "Çalışma ağacı oluşturulamadı",
"prompt.toast.sessionCreateFailed.title": "Oturum oluşturulamadı",
"prompt.toast.shellSendFailed.title": "Kabuk komutu gönderilemedi",
"prompt.toast.commandSendFailed.title": "Komut gönderilemedi",
"prompt.toast.promptSendFailed.title": "Komut gönderilemedi",
"prompt.toast.promptSendFailed.description": "Oturum alınamadı",
"dialog.mcp.title": "MCP'ler",
"dialog.mcp.description": "{{total}} içerisinden {{enabled}} etkin",
"dialog.mcp.empty": "Yapılandırılmış MCP yok",
"dialog.lsp.empty": "LSP'ler dosya türlerinden otomatik algılanır",
"dialog.plugins.empty": "Eklentiler opencode.json içinde yapılandırılır",
"mcp.status.connected": "bağlı",
"mcp.status.failed": "başarısız",
"mcp.status.needs_auth": "kimlik doğrulama gerekli",
"mcp.status.disabled": "devre dışı",
"dialog.fork.empty": "Dallandırılacak mesaj yok",
"dialog.directory.search.placeholder": "Klasör ara",
"dialog.directory.empty": "Klasör bulunamadı",
"dialog.server.title": "Sunucular",
"dialog.server.description": "Bu uygulamanın hangi OpenCode sunucusuna bağlanacağını değiştirin.",
"dialog.server.search.placeholder": "Sunucu ara",
"dialog.server.empty": "Henüz sunucu yok",
"dialog.server.add.title": "Sunucu ekle",
"dialog.server.add.url": "Sunucu URL'si",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Sunucuya bağlanılamadı",
"dialog.server.add.checking": "Kontrol ediliyor...",
"dialog.server.add.button": "Sunucu ekle",
"dialog.server.default.title": "Varsayılan sunucu",
"dialog.server.default.description":
"Uygulama başlatıldığında yerel sunucu başlatmak yerine bu sunucuya bağlan. Yeniden başlatma gerektirir.",
"dialog.server.default.none": "Sunucu seçilmedi",
"dialog.server.default.set": "Mevcut sunucuyu varsayılan olarak ayarla",
"dialog.server.default.clear": "Temizle",
"dialog.server.action.remove": "Sunucuyu kaldır",
"dialog.server.menu.edit": "Düzenle",
"dialog.server.menu.default": "Varsayılan olarak ayarla",
"dialog.server.menu.defaultRemove": "Varsayılanı kaldır",
"dialog.server.menu.delete": "Sil",
"dialog.server.current": "Mevcut Sunucu",
"dialog.server.status.default": "Varsayılan",
"dialog.project.edit.title": "Projeyi düzenle",
"dialog.project.edit.name": "Ad",
"dialog.project.edit.icon": "Simge",
"dialog.project.edit.icon.alt": "Proje simgesi",
"dialog.project.edit.icon.hint": "Tıkla veya bir görsel sürükle",
"dialog.project.edit.icon.recommended": "Önerilen: 128x128px",
"dialog.project.edit.color": "Renk",
"dialog.project.edit.color.select": "{{color}} rengini seç",
"dialog.project.edit.worktree.startup": "Çalışma alanı başlatma betiği",
"dialog.project.edit.worktree.startup.description": "Yeni bir çalışma alanı (worktree) oluşturduktan sonra çalışır.",
"dialog.project.edit.worktree.startup.placeholder": "örneğin bun install",
"context.breakdown.title": "Bağlam Dökümü",
"context.breakdown.note": 'Girdi tokenlerinin yaklaşık dökümü. "Diğer" araç tanımları ve ek yükleri içerir.',
"context.breakdown.system": "Sistem",
"context.breakdown.user": "Kullanıcı",
"context.breakdown.assistant": "Asistan",
"context.breakdown.tool": "Araç Çağrıları",
"context.breakdown.other": "Diğer",
"context.systemPrompt.title": "Sistem Komutu",
"context.rawMessages.title": "Ham mesajlar",
"context.stats.session": "Oturum",
"context.stats.messages": "Mesajlar",
"context.stats.provider": "Sağlayıcı",
"context.stats.model": "Model",
"context.stats.limit": "Bağlam Limiti",
"context.stats.totalTokens": "Toplam Token",
"context.stats.usage": "Kullanım",
"context.stats.inputTokens": "Girdi Tokenleri",
"context.stats.outputTokens": ıktı Tokenleri",
"context.stats.reasoningTokens": "Akıl Yürütme Tokenleri",
"context.stats.cacheTokens": "Önbellek Tokenleri (okuma/yazma)",
"context.stats.userMessages": "Kullanıcı Mesajları",
"context.stats.assistantMessages": "Asistan Mesajları",
"context.stats.totalCost": "Toplam Maliyet",
"context.stats.sessionCreated": "Oturum Oluşturulma",
"context.stats.lastActivity": "Son Etkinlik",
"context.usage.tokens": "Tokenler",
"context.usage.usage": "Kullanım",
"context.usage.cost": "Maliyet",
"context.usage.clickToView": "Bağlamı görüntüle",
"context.usage.view": "Bağlam kullanımını görüntüle",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.bs": "Bosanski",
"language.th": "ไทย",
"language.tr": "Türkçe",
"toast.language.title": "Dil",
"toast.language.description": "{{language}} diline geçildi",
"toast.theme.title": "Tema değiştirildi",
"toast.scheme.title": "Renk şeması",
"toast.workspace.enabled.title": "Çalışma alanları etkinleştirildi",
"toast.workspace.enabled.description": "Kenar çubuğunda birden fazla çalışma ağacı gösterilecek",
"toast.workspace.disabled.title": "Çalışma alanları devre dışı bırakıldı",
"toast.workspace.disabled.description": "Kenar çubuğunda yalnızca ana çalışma ağacı gösterilecek",
"toast.permissions.autoaccept.on.title": "Düzenlemeler otomatik kabul ediliyor",
"toast.permissions.autoaccept.on.description": "Düzenleme ve yazma izinleri otomatik olarak onaylanacak",
"toast.permissions.autoaccept.off.title": "Otomatik kabul durduruldu",
"toast.permissions.autoaccept.off.description": "Düzenleme ve yazma izinleri onay gerektirecek",
"toast.model.none.title": "Model seçilmedi",
"toast.model.none.description": "Bu oturumu özetlemek için bir sağlayıcı bağlayın",
"toast.file.loadFailed.title": "Dosya yüklenemedi",
"toast.file.listFailed.title": "Dosyalar listelenemedi",
"toast.context.noLineSelection.title": "Satır seçimi yok",
"toast.context.noLineSelection.description": "Önce bir dosya sekmesinde satır aralığı seçin.",
"toast.session.share.copyFailed.title": "URL panoya kopyalanamadı",
"toast.session.share.success.title": "Oturum paylaşıldı",
"toast.session.share.success.description": "Paylaşım URL'si panoya kopyalandı!",
"toast.session.share.failed.title": "Oturum paylaşılamadı",
"toast.session.share.failed.description": "Oturum paylaşılırken bir hata oluştu",
"toast.session.unshare.success.title": "Oturum paylaşımı kaldırıldı",
"toast.session.unshare.success.description": "Oturum paylaşımı başarıyla kaldırıldı!",
"toast.session.unshare.failed.title": "Oturum paylaşımı kaldırılamadı",
"toast.session.unshare.failed.description": "Oturum paylaşımı kaldırılırken bir hata oluştu",
"toast.session.listFailed.title": "{{project}} için oturumlar yüklenemedi",
"toast.update.title": "Güncelleme mevcut",
"toast.update.description": "OpenCode'un yeni bir sürümü ({{version}}) yüklemeye hazır.",
"toast.update.action.installRestart": "Yükle ve yeniden başlat",
"toast.update.action.notYet": "Şimdi değil",
"error.page.title": "Bir şeyler yanlış gitti",
"error.page.description": "Uygulama yüklenirken bir hata oluştu.",
"error.page.details.label": "Hata Detayları",
"error.page.action.restart": "Yeniden Başlat",
"error.page.action.checking": "Kontrol ediliyor...",
"error.page.action.checkUpdates": "Güncellemeleri kontrol et",
"error.page.action.updateTo": "{{version}} sürümüne güncelle",
"error.page.report.prefix": "Lütfen bu hatayı OpenCode ekibine bildirin",
"error.page.report.discord": "Discord üzerinden",
"error.page.version": "Sürüm: {{version}}",
"error.dev.rootNotFound":
"Kök eleman bulunamadı. index.html dosyanıza eklemeyi unuttunuz mu? Ya da id özelliği yanlış mı yazıldı?",
"error.globalSync.connectFailed": "Sunucuya bağlanılamadı. `{{url}}` adresinde çalışan bir sunucu var mı?",
"directory.error.invalidUrl": "URL'de geçersiz dizin.",
"error.chain.unknown": "Bilinmeyen hata",
"error.chain.causedBy": "Nedeni:",
"error.chain.apiError": "API hatası",
"error.chain.status": "Durum: {{status}}",
"error.chain.retryable": "Yeniden denenebilir: {{retryable}}",
"error.chain.responseBody": "Yanıt gövdesi:\n{{body}}",
"error.chain.didYouMean": "Bunu mu demek istediniz: {{suggestions}}",
"error.chain.modelNotFound": "Model bulunamadı: {{provider}}/{{model}}",
"error.chain.checkConfig": "Yapılandırma dosyanızı (opencode.json) sağlayıcı/model adlarını kontrol edin",
"error.chain.mcpFailed":
'MCP sunucusu "{{name}}" başarısız oldu. Not: OpenCode henüz MCP kimlik doğrulamasını desteklemiyor.',
"error.chain.providerAuthFailed": "Sağlayıcı kimlik doğrulaması başarısız ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'"{{provider}}" sağlayıcısı başlatılamadı. Kimlik bilgilerini ve yapılandırmayı kontrol edin.',
"error.chain.configJsonInvalid": "{{path}} adresindeki yapılandırma dosyası geçerli JSON(C) değil",
"error.chain.configJsonInvalidWithMessage":
"{{path}} adresindeki yapılandırma dosyası geçerli JSON(C) değil: {{message}}",
"error.chain.configDirectoryTypo":
'"{{dir}}" dizini {{path}} içinde geçerli değil. Dizini "{{suggestion}}" olarak yeniden adlandırın veya kaldırın. Bu yaygın bir yazım hatasıdır.',
"error.chain.configFrontmatterError": "{{path}} içindeki ön bilgi ayrıştırılamadı:\n{{message}}",
"error.chain.configInvalid": "{{path}} adresindeki yapılandırma dosyası geçersiz",
"error.chain.configInvalidWithMessage": "{{path}} adresindeki yapılandırma dosyası geçersiz: {{message}}",
"notification.permission.title": "İzin gerekli",
"notification.permission.description": "{{projectName}} içindeki {{sessionTitle}} izin gerektiriyor",
"notification.question.title": "Soru",
"notification.question.description": "{{projectName}} içindeki {{sessionTitle}} bir soru soruyor",
"notification.action.goToSession": "Oturuma git",
"notification.session.responseReady.title": "Yanıt hazır",
"notification.session.error.title": "Oturum hatası",
"notification.session.error.fallbackDescription": "Bir hata oluştu",
"home.recentProjects": "Son projeler",
"home.empty.title": "Son proje yok",
"home.empty.description": "Yerel bir proje açarak başlayın",
"session.tab.session": "Oturum",
"session.tab.review": "İnceleme",
"session.tab.context": "Bağlam",
"session.panel.reviewAndFiles": "İnceleme ve dosyalar",
"session.review.filesChanged": "{{count}} Dosya Değişti",
"session.review.change.one": "Değişiklik",
"session.review.change.other": "Değişiklik",
"session.review.loadingChanges": "Değişiklikler yükleniyor...",
"session.review.empty": "Bu oturumda henüz değişiklik yok",
"session.review.noVcs": "Git VCS algılanamadı, oturum değişiklikleri tespit edilemeyecek",
"session.review.noChanges": "Değişiklik yok",
"session.files.selectToOpen": "Açmak için bir dosya seçin",
"session.files.all": "Tüm dosyalar",
"session.files.binaryContent": "İkili dosya (içerik görüntülenemiyor)",
"session.messages.renderEarlier": "Önceki mesajları göster",
"session.messages.loadingEarlier": "Önceki mesajlar yükleniyor...",
"session.messages.loadEarlier": "Önceki mesajları yükle",
"session.messages.loading": "Mesajlar yükleniyor...",
"session.messages.jumpToLatest": "En sona atla",
"session.context.addToContext": "{{selection}} bağlama ekle",
"session.todo.title": "Görevler",
"session.todo.collapse": "Daralt",
"session.todo.expand": "Genişlet",
"session.new.worktree.main": "Ana dal",
"session.new.worktree.mainWithBranch": "Ana dal ({{branch}})",
"session.new.worktree.create": "Yeni çalışma ağacı oluştur",
"session.new.lastModified": "Son değişiklik",
"session.header.search.placeholder": "{{project}} ara",
"session.header.searchFiles": "Dosya ara",
"session.header.openIn": "Aç",
"session.header.open.action": "{{app}} ile aç",
"session.header.open.ariaLabel": "{{app}} ile aç",
"session.header.open.menu": "Açma seçenekleri",
"session.header.open.copyPath": "Yolu kopyala",
"status.popover.trigger": "Durum",
"status.popover.ariaLabel": "Sunucu yapılandırmaları",
"status.popover.tab.servers": "Sunucular",
"status.popover.tab.mcp": "MCP",
"status.popover.tab.lsp": "LSP",
"status.popover.tab.plugins": "Eklentiler",
"status.popover.action.manageServers": "Sunucuları yönet",
"session.share.popover.title": "Web'de yayınla",
"session.share.popover.description.shared": "Bu oturum web'de herkese açıktır. Bağlantıya sahip herkes erişebilir.",
"session.share.popover.description.unshared":
"Oturumu web'de herkese açık olarak paylaşın. Bağlantıya sahip herkes erişebilecek.",
"session.share.action.share": "Paylaş",
"session.share.action.publish": "Yayınla",
"session.share.action.publishing": "Yayınlanıyor...",
"session.share.action.unpublish": "Yayından kaldır",
"session.share.action.unpublishing": "Yayından kaldırılıyor...",
"session.share.action.view": "Görüntüle",
"session.share.copy.copied": "Kopyalandı",
"session.share.copy.copyLink": "Bağlantı kopyala",
"lsp.tooltip.none": "LSP sunucusu yok",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Komut yükleniyor...",
"terminal.loading": "Terminal yükleniyor...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Terminali kapat",
"terminal.connectionLost.title": "Bağlantı Kesildi",
"terminal.connectionLost.description":
"Terminal bağlantısı kesildi. Bu durum sunucu yeniden başladığında oluşabilir.",
"common.closeTab": "Sekmeyi kapat",
"common.dismiss": "Kapat",
"common.requestFailed": "İstek başarısız",
"common.moreOptions": "Daha fazla seçenek",
"common.learnMore": "Daha fazla bilgi",
"common.rename": "Yeniden adlandır",
"common.reset": "Sıfırla",
"common.archive": "Arşivle",
"common.delete": "Sil",
"common.close": "Kapat",
"common.edit": "Düzenle",
"common.loadMore": "Daha fazla yükle",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Menüyü aç/kapat",
"sidebar.nav.projectsAndSessions": "Projeler ve oturumlar",
"sidebar.settings": "Ayarlar",
"sidebar.help": "Yardım",
"sidebar.workspaces.enable": "Çalışma alanlarını etkinleştir",
"sidebar.workspaces.disable": "Çalışma alanlarını devre dışı bırak",
"sidebar.gettingStarted.title": "Başlarken",
"sidebar.gettingStarted.line1": "OpenCode ücretsiz modeller içerir, böylece hemen başlayabilirsiniz.",
"sidebar.gettingStarted.line2": "Claude, GPT, Gemini vb. modelleri kullanmak için herhangi bir sağlayıcı bağlayın.",
"sidebar.project.recentSessions": "Son oturumlar",
"sidebar.project.viewAllSessions": "Tüm oturumları görüntüle",
"sidebar.project.clearNotifications": "Bildirimleri temizle",
"app.name.desktop": "OpenCode Masaüstü",
"settings.section.desktop": "Masaüstü",
"settings.section.server": "Sunucu",
"settings.tab.general": "Genel",
"settings.tab.shortcuts": "Kısayollar",
"settings.desktop.section.wsl": "WSL",
"settings.desktop.wsl.title": "WSL entegrasyonu",
"settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.",
"settings.general.section.appearance": "Görünüm",
"settings.general.section.notifications": "Sistem bildirimleri",
"settings.general.section.updates": "Güncellemeler",
"settings.general.section.sounds": "Ses efektleri",
"settings.general.section.feed": "Akış",
"settings.general.section.display": "Ekran",
"settings.general.row.language.title": "Dil",
"settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin",
"settings.general.row.appearance.title": "Görünüm",
"settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin",
"settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
"settings.general.row.font.title": "Yazı Tipi",
"settings.general.row.font.description": "Kod bloklarında kullanılan monospace yazı tipini özelleştirin",
"settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster",
"settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle",
"settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet",
"settings.general.row.shellToolPartsExpanded.description":
"Zaman çizelgesinde kabuk araç bileşenlerini varsayılan olarak genişletilmiş göster",
"settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet",
"settings.general.row.editToolPartsExpanded.description":
"Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster",
"settings.general.row.wayland.title": "Yerel Wayland kullan",
"settings.general.row.wayland.description":
"Wayland'da X11 geri dönüşünü devre dışı bırak. Yeniden başlatma gerektirir.",
"settings.general.row.wayland.tooltip":
"Karışık yenileme hızlı monitörlere sahip Linux'ta yerel Wayland daha kararlı olabilir.",
"settings.general.row.releaseNotes.title": "Sürüm notları",
"settings.general.row.releaseNotes.description": "Güncellemelerden sonra Yenilikler bildirimlerini göster",
"settings.updates.row.startup.title": "Başlangıçta güncellemeleri kontrol et",
"settings.updates.row.startup.description": "OpenCode başladığında otomatik güncelleme kontrolü yap",
"settings.updates.row.check.title": "Güncellemeleri kontrol et",
"settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle",
"settings.updates.action.checkNow": "Şimdi kontrol et",
"settings.updates.action.checking": "Kontrol ediliyor...",
"settings.updates.toast.latest.title": "Güncelsiniz",
"settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.",
"font.option.ibmPlexMono": "IBM Plex Mono",
"font.option.cascadiaCode": "Cascadia Code",
"font.option.firaCode": "Fira Code",
"font.option.hack": "Hack",
"font.option.inconsolata": "Inconsolata",
"font.option.intelOneMono": "Intel One Mono",
"font.option.iosevka": "Iosevka",
"font.option.jetbrainsMono": "JetBrains Mono",
"font.option.mesloLgs": "Meslo LGS",
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
"sound.option.none": "Yok",
"sound.option.alert01": "Uyarı 01",
"sound.option.alert02": "Uyarı 02",
"sound.option.alert03": "Uyarı 03",
"sound.option.alert04": "Uyarı 04",
"sound.option.alert05": "Uyarı 05",
"sound.option.alert06": "Uyarı 06",
"sound.option.alert07": "Uyarı 07",
"sound.option.alert08": "Uyarı 08",
"sound.option.alert09": "Uyarı 09",
"sound.option.alert10": "Uyarı 10",
"sound.option.bipbop01": "Bip-bop 01",
"sound.option.bipbop02": "Bip-bop 02",
"sound.option.bipbop03": "Bip-bop 03",
"sound.option.bipbop04": "Bip-bop 04",
"sound.option.bipbop05": "Bip-bop 05",
"sound.option.bipbop06": "Bip-bop 06",
"sound.option.bipbop07": "Bip-bop 07",
"sound.option.bipbop08": "Bip-bop 08",
"sound.option.bipbop09": "Bip-bop 09",
"sound.option.bipbop10": "Bip-bop 10",
"sound.option.staplebops01": "Staplebops 01",
"sound.option.staplebops02": "Staplebops 02",
"sound.option.staplebops03": "Staplebops 03",
"sound.option.staplebops04": "Staplebops 04",
"sound.option.staplebops05": "Staplebops 05",
"sound.option.staplebops06": "Staplebops 06",
"sound.option.staplebops07": "Staplebops 07",
"sound.option.nope01": "Hayır 01",
"sound.option.nope02": "Hayır 02",
"sound.option.nope03": "Hayır 03",
"sound.option.nope04": "Hayır 04",
"sound.option.nope05": "Hayır 05",
"sound.option.nope06": "Hayır 06",
"sound.option.nope07": "Hayır 07",
"sound.option.nope08": "Hayır 08",
"sound.option.nope09": "Hayır 09",
"sound.option.nope10": "Hayır 10",
"sound.option.nope11": "Hayır 11",
"sound.option.nope12": "Hayır 12",
"sound.option.yup01": "Evet 01",
"sound.option.yup02": "Evet 02",
"sound.option.yup03": "Evet 03",
"sound.option.yup04": "Evet 04",
"sound.option.yup05": "Evet 05",
"sound.option.yup06": "Evet 06",
"settings.general.notifications.agent.title": "Ajan",
"settings.general.notifications.agent.description":
"Ajan tamamlandığında veya dikkat gerektirdiğinde sistem bildirimi göster",
"settings.general.notifications.permissions.title": "İzinler",
"settings.general.notifications.permissions.description": "İzin gerektiğinde sistem bildirimi göster",
"settings.general.notifications.errors.title": "Hatalar",
"settings.general.notifications.errors.description": "Hata oluştuğunda sistem bildirimi göster",
"settings.general.sounds.agent.title": "Ajan",
"settings.general.sounds.agent.description": "Ajan tamamlandığında veya dikkat gerektirdiğinde ses çal",
"settings.general.sounds.permissions.title": "İzinler",
"settings.general.sounds.permissions.description": "İzin gerektiğinde ses çal",
"settings.general.sounds.errors.title": "Hatalar",
"settings.general.sounds.errors.description": "Hata oluştuğunda ses çal",
"settings.shortcuts.title": "Klavye kısayolları",
"settings.shortcuts.reset.button": "Varsayılanlara sıfırla",
"settings.shortcuts.reset.toast.title": "Kısayollar sıfırlandı",
"settings.shortcuts.reset.toast.description": "Klavye kısayolları varsayılanlara sıfırlandı.",
"settings.shortcuts.conflict.title": "Kısayol zaten kullanılıyor",
"settings.shortcuts.conflict.description": "{{keybind}} zaten {{titles}} için atanmış.",
"settings.shortcuts.unassigned": "Atanmamış",
"settings.shortcuts.pressKeys": "Tuşlara basın",
"settings.shortcuts.search.placeholder": "Kısayol ara",
"settings.shortcuts.search.empty": "Kısayol bulunamadı",
"settings.shortcuts.group.general": "Genel",
"settings.shortcuts.group.session": "Oturum",
"settings.shortcuts.group.navigation": "Gezinme",
"settings.shortcuts.group.modelAndAgent": "Model ve ajan",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Komut",
"settings.providers.title": "Sağlayıcılar",
"settings.providers.description": "Sağlayıcı ayarları burada yapılandırılabilecek.",
"settings.providers.section.connected": "Bağlı sağlayıcılar",
"settings.providers.connected.empty": "Bağlı sağlayıcı yok",
"settings.providers.section.popular": "Popüler sağlayıcılar",
"settings.providers.tag.environment": "Ortam",
"settings.providers.tag.config": "Yapılandırma",
"settings.providers.tag.custom": "Özel",
"settings.providers.tag.other": "Diğer",
"settings.models.title": "Modeller",
"settings.models.description": "Model ayarları burada yapılandırılabilecek.",
"settings.agents.title": "Ajanlar",
"settings.agents.description": "Ajan ayarları burada yapılandırılabilecek.",
"settings.commands.title": "Komutlar",
"settings.commands.description": "Komut ayarları burada yapılandırılabilecek.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP ayarları burada yapılandırılabilecek.",
"settings.permissions.title": "İzinler",
"settings.permissions.description": "Sunucunun varsayılan olarak hangi araçları kullanabileceğini kontrol edin.",
"settings.permissions.section.tools": "Araçlar",
"settings.permissions.toast.updateFailed.title": "İzinler güncellenemedi",
"settings.permissions.action.allow": "İzin Ver",
"settings.permissions.action.ask": "Sor",
"settings.permissions.action.deny": "Reddet",
"settings.permissions.tool.read.title": "Oku",
"settings.permissions.tool.read.description": "Bir dosyayı okuma (dosya yoluyla eşleşir)",
"settings.permissions.tool.edit.title": "Düzenle",
"settings.permissions.tool.edit.description": "Düzenleme, yazma, yama ve çoklu düzenleme dahil dosyaları değiştir",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Glob kalıpları kullanarak dosyaları eşle",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Düzenli ifadeler kullanarak dosya içerikleri ara",
"settings.permissions.tool.list.title": "Listele",
"settings.permissions.tool.list.description": "Bir dizindeki dosyaları listele",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Kabuk komutları çalıştır",
"settings.permissions.tool.task.title": "Görev",
"settings.permissions.tool.task.description": "Alt ajanlar başlat",
"settings.permissions.tool.skill.title": "Beceri",
"settings.permissions.tool.skill.description": "Ada göre bir beceri yükle",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Dil sunucusu sorguları çalıştır",
"settings.permissions.tool.todoread.title": "Görev Oku",
"settings.permissions.tool.todoread.description": "Görev listesini oku",
"settings.permissions.tool.todowrite.title": "Görev Yaz",
"settings.permissions.tool.todowrite.description": "Görev listesini güncelle",
"settings.permissions.tool.webfetch.title": "Web Getir",
"settings.permissions.tool.webfetch.description": "Bir URL'den içerik getir",
"settings.permissions.tool.websearch.title": "Web Ara",
"settings.permissions.tool.websearch.description": "Web'de ara",
"settings.permissions.tool.codesearch.title": "Kod Ara",
"settings.permissions.tool.codesearch.description": "Web'de kod ara",
"settings.permissions.tool.external_directory.title": "Harici Dizin",
"settings.permissions.tool.external_directory.description": "Proje dizini dışındaki dosyalara eriş",
"settings.permissions.tool.doom_loop.title": "Sonsuz Döngü",
"settings.permissions.tool.doom_loop.description": "Aynı girdiyle tekrarlanan araç çağrılarını algıla",
"session.delete.failed.title": "Oturum silinemedi",
"session.delete.title": "Oturumu sil",
"session.delete.confirm": '"{{name}}" oturumu silinsin mi?',
"session.delete.button": "Oturumu sil",
"workspace.new": "Yeni çalışma alanı",
"workspace.type.local": "yerel",
"workspace.type.sandbox": "sandbox",
"workspace.create.failed.title": "Çalışma alanı oluşturulamadı",
"workspace.delete.failed.title": "Çalışma alanı silinemedi",
"workspace.resetting.title": "Çalışma alanı sıfırlanıyor",
"workspace.resetting.description": "Bu bir dakika sürebilir.",
"workspace.reset.failed.title": "Çalışma alanı sıfırlanamadı",
"workspace.reset.success.title": "Çalışma alanı sıfırlandı",
"workspace.reset.success.description": "Çalışma alanı artık varsayılan dalla eşleşiyor.",
"workspace.error.stillPreparing": "Çalışma alanı hâlâ hazırlanıyor",
"workspace.status.checking": "Birleşmemiş değişiklikler kontrol ediliyor...",
"workspace.status.error": "Git durumu doğrulanamadı.",
"workspace.status.clean": "Birleşmemiş değişiklik algılanmadı.",
"workspace.status.dirty": "Bu çalışma alanında birleşmemiş değişiklikler algılandı.",
"workspace.delete.title": "Çalışma alanını sil",
"workspace.delete.confirm": '"{{name}}" çalışma alanı silinsin mi?',
"workspace.delete.button": "Çalışma alanını sil",
"workspace.reset.title": "Çalışma alanını sıfırla",
"workspace.reset.confirm": '"{{name}}" çalışma alanı sıfırlansın mı?',
"workspace.reset.button": "Çalışma alanını sıfırla",
"workspace.reset.archived.none": "Arşivlenecek aktif oturum yok.",
"workspace.reset.archived.one": "1 oturum arşivlenecek.",
"workspace.reset.archived.many": "{{count}} oturum arşivlenecek.",
"workspace.reset.note": "Bu işlem çalışma alanını varsayılan dalla eşleşecek şekilde sıfırlayacak.",
"common.open": "Aç",
"dialog.releaseNotes.action.getStarted": "Başla",
"dialog.releaseNotes.action.next": "İleri",
"dialog.releaseNotes.action.hideFuture": "Bunu gelecekte bir daha gösterme",
"dialog.releaseNotes.media.alt": "Sürüm önizlemesi",
"toast.project.reloadFailed.title": "{{project}} yeniden yüklenemedi",
"error.server.invalidConfiguration": "Geçersiz yapılandırma",
"common.moreCountSuffix": " (+{{count}} daha)",
"common.time.justNow": "Şimdi",
"common.time.minutesAgo.short": "{{count}}dk önce",
"common.time.hoursAgo.short": "{{count}}sa önce",
"common.time.daysAgo.short": "{{count}}g önce",
"settings.providers.connected.environmentDescription": "Ortam değişkenlerinizden bağlandı",
"settings.providers.custom.description": "Temel URL üzerinden OpenAI uyumlu bir sağlayıcı ekleyin.",
} satisfies Partial<Record<Keys, string>>

View File

@@ -809,4 +809,18 @@ export const dict = {
"workspace.reset.archived.one": "将归档 1 个会话。",
"workspace.reset.archived.many": "将归档 {{count}} 个会话。",
"workspace.reset.note": "这将把工作区重置为与默认分支一致。",
"common.open": "打开",
"dialog.releaseNotes.action.getStarted": "开始",
"dialog.releaseNotes.action.next": "下一步",
"dialog.releaseNotes.action.hideFuture": "不再显示",
"dialog.releaseNotes.media.alt": "发布预览",
"toast.project.reloadFailed.title": "无法重新加载 {{project}}",
"error.server.invalidConfiguration": "配置无效",
"common.moreCountSuffix": " (还有 {{count}} 个)",
"common.time.justNow": "刚刚",
"common.time.minutesAgo.short": "{{count}}分钟前",
"common.time.hoursAgo.short": "{{count}}小时前",
"common.time.daysAgo.short": "{{count}}天前",
"settings.providers.connected.environmentDescription": "已通过环境变量连接",
"settings.providers.custom.description": "通过基础 URL 添加与 OpenAI 兼容的提供商。",
} satisfies Partial<Record<Keys, string>>

View File

@@ -804,4 +804,18 @@ export const dict = {
"workspace.reset.archived.one": "將封存 1 個工作階段。",
"workspace.reset.archived.many": "將封存 {{count}} 個工作階段。",
"workspace.reset.note": "這將把工作區重設為與預設分支一致。",
"common.open": "打開",
"dialog.releaseNotes.action.getStarted": "開始",
"dialog.releaseNotes.action.next": "下一步",
"dialog.releaseNotes.action.hideFuture": "不再顯示",
"dialog.releaseNotes.media.alt": "發佈預覽",
"toast.project.reloadFailed.title": "無法重新載入 {{project}}",
"error.server.invalidConfiguration": "無效的設定",
"common.moreCountSuffix": " (還有 {{count}} 個)",
"common.time.justNow": "剛剛",
"common.time.minutesAgo.short": "{{count}}分鐘前",
"common.time.hoursAgo.short": "{{count}}小時前",
"common.time.daysAgo.short": "{{count}}天前",
"settings.providers.connected.environmentDescription": "已從環境變數連線",
"settings.providers.custom.description": "透過基本 URL 新增與 OpenAI 相容的提供者。",
} satisfies Partial<Record<Keys, string>>

View File

@@ -3,6 +3,7 @@ import { createStore } from "solid-js/store"
import { Button } from "@opencode-ai/ui/button"
import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { showToast } from "@opencode-ai/ui/toast"
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
import { useLanguage } from "@/context/language"
@@ -22,6 +23,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
customOn: [] as boolean[],
editing: false,
sending: false,
collapsed: false,
})
let root: HTMLDivElement | undefined
@@ -31,6 +33,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
const input = createMemo(() => store.custom[store.tab] ?? "")
const on = createMemo(() => store.customOn[store.tab] === true)
const multi = createMemo(() => question()?.multiple === true)
const picked = createMemo(() => store.answers[store.tab]?.length ?? 0)
const summary = createMemo(() => {
const n = Math.min(store.tab + 1, total())
@@ -39,6 +42,8 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
const last = createMemo(() => store.tab >= total() - 1)
const fold = () => setStore("collapsed", (value) => !value)
const customUpdate = (value: string, selected: boolean = on()) => {
const prev = input().trim()
const next = value.trim()
@@ -228,38 +233,62 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
setStore("editing", false)
}
const jump = (tab: number) => {
const click = (target: EventTarget | null) => {
if (store.sending) return
setStore("tab", tab)
setStore("editing", false)
if (!(target instanceof Node)) {
fold()
return
}
const list = root?.querySelector('[data-slot="question-options"]')
if (list instanceof HTMLElement && list.contains(target)) return
fold()
}
return (
<DockPrompt
kind="question"
ref={(el) => (root = el)}
bodyProps={{
onClick: (event) => click(event.target),
}}
header={
<>
<div
data-action="session-question-toggle"
class="flex flex-1 min-w-0 items-center gap-2 cursor-default select-none"
role="button"
tabIndex={0}
style={{ margin: "0 -10px", padding: "0 0 0 10px" }}
onClick={(event) => {
event.stopPropagation()
fold()
}}
onKeyDown={(event) => {
if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault()
fold()
}}
>
<div data-slot="question-header-title">{summary()}</div>
<div data-slot="question-progress">
<For each={questions()}>
{(_, i) => (
<button
type="button"
data-slot="question-progress-segment"
data-active={i() === store.tab}
data-answered={
(store.answers[i()]?.length ?? 0) > 0 ||
(store.customOn[i()] === true && (store.custom[i()] ?? "").trim().length > 0)
}
disabled={store.sending}
onClick={() => jump(i())}
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
/>
)}
</For>
<div class="ml-auto">
<IconButton
data-action="session-question-toggle-button"
icon="chevron-down"
size="normal"
variant="ghost"
classList={{ "rotate-180": store.collapsed }}
onMouseDown={(event) => {
event.preventDefault()
event.stopPropagation()
}}
onClick={(event) => {
event.stopPropagation()
fold()
}}
aria-label={store.collapsed ? language.t("session.todo.expand") : language.t("session.todo.collapse")}
/>
</div>
</>
</div>
}
footer={
<>
@@ -279,56 +308,122 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
</>
}
>
<div data-slot="question-text">{question()?.question}</div>
<Show when={multi()} fallback={<div data-slot="question-hint">{language.t("ui.question.singleHint")}</div>}>
<div data-slot="question-hint">{language.t("ui.question.multiHint")}</div>
<div
data-slot="question-text"
class="cursor-default"
classList={{
"mb-6": store.collapsed && picked() === 0,
}}
onClick={(event) => {
event.stopPropagation()
fold()
}}
onKeyDown={(event) => {
if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault()
event.stopPropagation()
fold()
}}
>
{question()?.question}
</div>
<Show when={store.collapsed && picked() > 0}>
<div data-slot="question-hint" class="cursor-default mb-6">
{picked()} answer{picked() === 1 ? "" : "s"} selected
</div>
</Show>
<div data-slot="question-options">
<For each={options()}>
{(opt, i) => {
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
return (
<div data-slot="question-answers" hidden={store.collapsed} aria-hidden={store.collapsed}>
<Show when={multi()} fallback={<div data-slot="question-hint">{language.t("ui.question.singleHint")}</div>}>
<div data-slot="question-hint">{language.t("ui.question.multiHint")}</div>
</Show>
<div data-slot="question-options">
<For each={options()}>
{(opt, i) => {
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
return (
<button
data-slot="question-option"
data-picked={picked()}
role={multi() ? "checkbox" : "radio"}
aria-checked={picked()}
disabled={store.sending}
onClick={() => selectOption(i())}
>
<span data-slot="question-option-check" aria-hidden="true">
<span
data-slot="question-option-box"
data-type={multi() ? "checkbox" : "radio"}
data-picked={picked()}
>
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
<Icon name="check-small" size="small" />
</Show>
</span>
</span>
<span data-slot="question-option-main">
<span data-slot="option-label">{opt.label}</span>
<Show when={opt.description}>
<span data-slot="option-description">{opt.description}</span>
</Show>
</span>
</button>
)
}}
</For>
<Show
when={store.editing}
fallback={
<button
data-slot="question-option"
data-picked={picked()}
data-custom="true"
data-picked={on()}
role={multi() ? "checkbox" : "radio"}
aria-checked={picked()}
aria-checked={on()}
disabled={store.sending}
onClick={() => selectOption(i())}
onClick={customOpen}
>
<span data-slot="question-option-check" aria-hidden="true">
<span
data-slot="question-option-box"
data-type={multi() ? "checkbox" : "radio"}
data-picked={picked()}
>
<span
data-slot="question-option-check"
aria-hidden="true"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
customToggle()
}}
>
<span data-slot="question-option-box" data-type={multi() ? "checkbox" : "radio"} data-picked={on()}>
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
<Icon name="check-small" size="small" />
</Show>
</span>
</span>
<span data-slot="question-option-main">
<span data-slot="option-label">{opt.label}</span>
<Show when={opt.description}>
<span data-slot="option-description">{opt.description}</span>
</Show>
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
<span data-slot="option-description">{input() || language.t("ui.question.custom.placeholder")}</span>
</span>
</button>
)
}}
</For>
<Show
when={store.editing}
fallback={
<button
}
>
<form
data-slot="question-option"
data-custom="true"
data-picked={on()}
role={multi() ? "checkbox" : "radio"}
aria-checked={on()}
disabled={store.sending}
onClick={customOpen}
onMouseDown={(e) => {
if (store.sending) {
e.preventDefault()
return
}
if (e.target instanceof HTMLTextAreaElement) return
const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
if (input instanceof HTMLTextAreaElement) input.focus()
}}
onSubmit={(e) => {
e.preventDefault()
commitCustom()
}}
>
<span
data-slot="question-option-check"
@@ -347,80 +442,39 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
</span>
<span data-slot="question-option-main">
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
<span data-slot="option-description">{input() || language.t("ui.question.custom.placeholder")}</span>
</span>
</button>
}
>
<form
data-slot="question-option"
data-custom="true"
data-picked={on()}
role={multi() ? "checkbox" : "radio"}
aria-checked={on()}
onMouseDown={(e) => {
if (store.sending) {
e.preventDefault()
return
}
if (e.target instanceof HTMLTextAreaElement) return
const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
if (input instanceof HTMLTextAreaElement) input.focus()
}}
onSubmit={(e) => {
e.preventDefault()
commitCustom()
}}
>
<span
data-slot="question-option-check"
aria-hidden="true"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
customToggle()
}}
>
<span data-slot="question-option-box" data-type={multi() ? "checkbox" : "radio"} data-picked={on()}>
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
<Icon name="check-small" size="small" />
</Show>
</span>
</span>
<span data-slot="question-option-main">
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
<textarea
ref={(el) =>
setTimeout(() => {
el.focus()
el.style.height = "0px"
el.style.height = `${el.scrollHeight}px`
}, 0)
}
data-slot="question-custom-input"
placeholder={language.t("ui.question.custom.placeholder")}
value={input()}
rows={1}
disabled={store.sending}
onKeyDown={(e) => {
if (e.key === "Escape") {
e.preventDefault()
setStore("editing", false)
return
<textarea
ref={(el) =>
setTimeout(() => {
el.focus()
el.style.height = "0px"
el.style.height = `${el.scrollHeight}px`
}, 0)
}
if (e.key !== "Enter" || e.shiftKey) return
e.preventDefault()
commitCustom()
}}
onInput={(e) => {
customUpdate(e.currentTarget.value)
e.currentTarget.style.height = "0px"
e.currentTarget.style.height = `${e.currentTarget.scrollHeight}px`
}}
/>
</span>
</form>
</Show>
data-slot="question-custom-input"
placeholder={language.t("ui.question.custom.placeholder")}
value={input()}
rows={1}
disabled={store.sending}
onKeyDown={(e) => {
if (e.key === "Escape") {
e.preventDefault()
setStore("editing", false)
return
}
if (e.key !== "Enter" || e.shiftKey) return
e.preventDefault()
commitCustom()
}}
onInput={(e) => {
customUpdate(e.currentTarget.value)
e.currentTarget.style.height = "0px"
e.currentTarget.style.height = `${e.currentTarget.scrollHeight}px`
}}
/>
</span>
</form>
</Show>
</div>
</div>
</DockPrompt>
)

View File

@@ -49,6 +49,7 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
let scroll: HTMLDivElement | undefined
let restoreFrame: number | undefined
let userInteracted = false
let restored: { x: number; y: number } | undefined
const sdk = useSDK()
const layout = useLayout()
@@ -65,6 +66,11 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
const handleInteraction = () => {
userInteracted = true
if (restoreFrame !== undefined) {
cancelAnimationFrame(restoreFrame)
restoreFrame = undefined
}
}
const doRestore = () => {
@@ -82,8 +88,11 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
const targetY = Math.min(s.y, maxY)
const targetX = Math.min(s.x, maxX)
if (el.scrollTop === targetY && el.scrollLeft === targetX) return
if (el.scrollTop !== targetY) el.scrollTop = targetY
if (el.scrollLeft !== targetX) el.scrollLeft = targetX
restored = { x: el.scrollLeft, y: el.scrollTop }
}
const queueRestore = () => {
@@ -92,9 +101,16 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
}
const handleScroll = (event: Event & { currentTarget: HTMLDivElement }) => {
if (!layout.ready() || !userInteracted) return
const el = event.currentTarget
const prev = restored
if (prev && el.scrollTop === prev.y && el.scrollLeft === prev.x) {
restored = undefined
return
}
restored = undefined
handleInteraction()
if (!layout.ready()) return
if (el.clientHeight === 0 || el.clientWidth === 0) return
props.view().setScroll("review", {
@@ -133,10 +149,11 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
onCleanup(() => {
if (restoreFrame !== undefined) cancelAnimationFrame(restoreFrame)
if (scroll) {
scroll.removeEventListener("wheel", handleInteraction)
scroll.removeEventListener("pointerdown", handleInteraction)
scroll.removeEventListener("touchstart", handleInteraction)
scroll.removeEventListener("keydown", handleInteraction)
scroll.removeEventListener("wheel", handleInteraction, { capture: true })
scroll.removeEventListener("mousewheel", handleInteraction, { capture: true })
scroll.removeEventListener("pointerdown", handleInteraction, { capture: true })
scroll.removeEventListener("touchstart", handleInteraction, { capture: true })
scroll.removeEventListener("keydown", handleInteraction, { capture: true })
}
})
@@ -147,6 +164,7 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
scrollRef={(el) => {
scroll = el
el.addEventListener("wheel", handleInteraction, { passive: true, capture: true })
el.addEventListener("mousewheel", handleInteraction, { passive: true, capture: true })
el.addEventListener("pointerdown", handleInteraction, { passive: true, capture: true })
el.addEventListener("touchstart", handleInteraction, { passive: true, capture: true })
el.addEventListener("keydown", handleInteraction, { passive: true, capture: true })

View File

@@ -7,11 +7,28 @@ export type ConfigInvalidError = {
}
}
export function formatServerError(error: unknown) {
if (isConfigInvalidErrorLike(error)) return parseReabaleConfigInvalidError(error)
type Label = {
unknown: string
invalidConfiguration: string
}
const fallback: Label = {
unknown: "Unknown error",
invalidConfiguration: "Invalid configuration",
}
function resolveLabel(labels: Partial<Label> | undefined): Label {
return {
unknown: labels?.unknown ?? fallback.unknown,
invalidConfiguration: labels?.invalidConfiguration ?? fallback.invalidConfiguration,
}
}
export function formatServerError(error: unknown, labels?: Partial<Label>) {
if (isConfigInvalidErrorLike(error)) return parseReabaleConfigInvalidError(error, labels)
if (error instanceof Error && error.message) return error.message
if (typeof error === "string" && error) return error
return "Unknown error"
return resolveLabel(labels).unknown
}
function isConfigInvalidErrorLike(error: unknown): error is ConfigInvalidError {
@@ -20,8 +37,8 @@ function isConfigInvalidErrorLike(error: unknown): error is ConfigInvalidError {
return o.name === "ConfigInvalidError" && typeof o.data === "object" && o.data !== null
}
export function parseReabaleConfigInvalidError(errorInput: ConfigInvalidError) {
const head = "Invalid configuration"
export function parseReabaleConfigInvalidError(errorInput: ConfigInvalidError, labels?: Partial<Label>) {
const head = resolveLabel(labels).invalidConfiguration
const file = errorInput.data.path && errorInput.data.path !== "config" ? errorInput.data.path : ""
const detail = errorInput.data.message?.trim() ?? ""
const issues = (errorInput.data.issues ?? []).map((issue) => {

View File

@@ -1,4 +1,12 @@
export function getRelativeTime(dateString: string): string {
type TimeKey =
| "common.time.justNow"
| "common.time.minutesAgo.short"
| "common.time.hoursAgo.short"
| "common.time.daysAgo.short"
type Translate = (key: TimeKey, params?: Record<string, string | number>) => string
export function getRelativeTime(dateString: string, t: Translate): string {
const date = new Date(dateString)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
@@ -7,8 +15,8 @@ export function getRelativeTime(dateString: string): string {
const diffHours = Math.floor(diffMinutes / 60)
const diffDays = Math.floor(diffHours / 24)
if (diffSeconds < 60) return "Just now"
if (diffMinutes < 60) return `${diffMinutes}m ago`
if (diffHours < 24) return `${diffHours}h ago`
return `${diffDays}d ago`
if (diffSeconds < 60) return t("common.time.justNow")
if (diffMinutes < 60) return t("common.time.minutesAgo.short", { count: diffMinutes })
if (diffHours < 24) return t("common.time.hoursAgo.short", { count: diffHours })
return t("common.time.daysAgo.short", { count: diffDays })
}

View File

@@ -632,7 +632,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
`Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
`Subscription quota exceeded. You can continue using free models.`,
result.resetInSec,
)
}
@@ -647,7 +647,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
`Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
`Subscription quota exceeded. You can continue using free models.`,
result.resetInSec,
)
}
@@ -662,7 +662,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
`Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
`Subscription quota exceeded. You can continue using free models.`,
result.resetInSec,
)
}

View File

@@ -0,0 +1,157 @@
#!/usr/bin/env bun
import { Buffer } from "node:buffer"
import { $ } from "bun"
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
"dry-run": { type: "boolean", default: false },
},
})
const dryRun = values["dry-run"]
import { parseArgs } from "node:util"
const repo = process.env.GH_REPO
if (!repo) throw new Error("GH_REPO is required")
const releaseId = process.env.OPENCODE_RELEASE
if (!releaseId) throw new Error("OPENCODE_RELEASE is required")
const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN
if (!token) throw new Error("GH_TOKEN or GITHUB_TOKEN is required")
const apiHeaders = {
Authorization: `token ${token}`,
Accept: "application/vnd.github+json",
}
const releaseRes = await fetch(`https://api.github.com/repos/${repo}/releases/${releaseId}`, {
headers: apiHeaders,
})
if (!releaseRes.ok) {
throw new Error(`Failed to fetch release: ${releaseRes.status} ${releaseRes.statusText}`)
}
type Asset = {
name: string
url: string
browser_download_url: string
}
type Release = {
tag_name?: string
assets?: Asset[]
}
const release = (await releaseRes.json()) as Release
const assets = release.assets ?? []
const assetByName = new Map(assets.map((asset) => [asset.name, asset]))
const latestAsset = assetByName.get("latest.json")
if (!latestAsset) throw new Error("latest.json asset not found")
const latestRes = await fetch(latestAsset.url, {
headers: {
Authorization: `token ${token}`,
Accept: "application/octet-stream",
},
})
if (!latestRes.ok) {
throw new Error(`Failed to fetch latest.json: ${latestRes.status} ${latestRes.statusText}`)
}
const latestText = new TextDecoder().decode(await latestRes.arrayBuffer())
const latest = JSON.parse(latestText)
const base = { ...latest }
delete base.platforms
const fetchSignature = async (asset: Asset) => {
const res = await fetch(asset.url, {
headers: {
Authorization: `token ${token}`,
Accept: "application/octet-stream",
},
})
if (!res.ok) {
throw new Error(`Failed to fetch signature: ${res.status} ${res.statusText}`)
}
return Buffer.from(await res.arrayBuffer()).toString()
}
const entries: Record<string, { url: string; signature: string }> = {}
const add = (key: string, asset: Asset, signature: string) => {
if (entries[key]) return
entries[key] = {
url: asset.browser_download_url,
signature,
}
}
const targets = [
{ key: "linux-x86_64-deb", asset: "opencode-desktop-linux-amd64.deb" },
{ key: "linux-x86_64-rpm", asset: "opencode-desktop-linux-x86_64.rpm" },
{ key: "linux-aarch64-deb", asset: "opencode-desktop-linux-arm64.deb" },
{ key: "linux-aarch64-rpm", asset: "opencode-desktop-linux-aarch64.rpm" },
{ key: "windows-x86_64-nsis", asset: "opencode-desktop-windows-x64.exe" },
{ key: "darwin-x86_64-app", asset: "opencode-desktop-darwin-x64.app.tar.gz" },
{
key: "darwin-aarch64-app",
asset: "opencode-desktop-darwin-aarch64.app.tar.gz",
},
]
for (const target of targets) {
const asset = assetByName.get(target.asset)
if (!asset) continue
const sig = assetByName.get(`${target.asset}.sig`)
if (!sig) continue
const signature = await fetchSignature(sig)
add(target.key, asset, signature)
}
const alias = (key: string, source: string) => {
if (entries[key]) return
const entry = entries[source]
if (!entry) return
entries[key] = entry
}
alias("linux-x86_64", "linux-x86_64-deb")
alias("linux-aarch64", "linux-aarch64-deb")
alias("windows-x86_64", "windows-x86_64-nsis")
alias("darwin-x86_64", "darwin-x86_64-app")
alias("darwin-aarch64", "darwin-aarch64-app")
const platforms = Object.fromEntries(
Object.keys(entries)
.sort()
.map((key) => [key, entries[key]]),
)
const output = {
...base,
platforms,
}
const dir = process.env.RUNNER_TEMP ?? "/tmp"
const file = `${dir}/latest.json`
await Bun.write(file, JSON.stringify(output, null, 2))
const tag = release.tag_name
if (!tag) throw new Error("Release tag not found")
if (dryRun) {
console.log(`dry-run: wrote latest.json for ${tag} to ${file}`)
process.exit(0)
}
await $`gh release upload ${tag} ${file} --clobber --repo ${repo}`
console.log(`finalized latest.json for ${tag}`)

View File

@@ -3,6 +3,32 @@ import { message } from "@tauri-apps/plugin-dialog"
import { initI18n, t } from "./i18n"
import { commands } from "./bindings"
function installError(error: unknown) {
const text = String(error)
if (text.includes("CLI installation is only supported on macOS & Linux")) {
return t("desktop.cli.error.unsupportedPlatform")
}
if (text.includes("Sidecar binary not found")) {
return t("desktop.cli.error.sidecarMissing")
}
if (text.includes("Failed to write install script")) {
return t("desktop.cli.error.scriptWriteFailed")
}
if (text.includes("Failed to set script permissions")) {
return t("desktop.cli.error.scriptPermissionFailed")
}
if (text.includes("Failed to run install script")) {
return t("desktop.cli.error.scriptRunFailed")
}
if (text.includes("Install script failed")) {
return t("desktop.cli.error.scriptFailed")
}
if (text.includes("Could not determine install path")) {
return t("desktop.cli.error.installPathUnknown")
}
return text || t("desktop.cli.error.unknown")
}
export async function installCli(): Promise<void> {
await initI18n()
@@ -10,6 +36,8 @@ export async function installCli(): Promise<void> {
const path = await commands.installCli()
await message(t("desktop.cli.installed.message", { path }), { title: t("desktop.cli.installed.title") })
} catch (e) {
await message(t("desktop.cli.failed.message", { error: String(e) }), { title: t("desktop.cli.failed.title") })
await message(t("desktop.cli.failed.message", { error: installError(e) }), {
title: t("desktop.cli.failed.title"),
})
}
}

View File

@@ -23,4 +23,37 @@ export const dict = {
"desktop.cli.installed.message": "تم تثبيت CLI في {{path}}\n\nأعد تشغيل الطرفية لاستخدام الأمر 'opencode'.",
"desktop.cli.failed.title": "فشل التثبيت",
"desktop.cli.failed.message": "فشل تثبيت CLI: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "ملف",
"desktop.menu.edit": "تعديل",
"desktop.menu.view": "عرض",
"desktop.menu.help": "مساعدة",
"desktop.menu.file.newSession": "جلسة جديدة",
"desktop.menu.file.openProject": "فتح مشروع...",
"desktop.menu.view.toggleSidebar": "تبديل الشريط الجانبي",
"desktop.menu.view.toggleTerminal": "تبديل الطرفية",
"desktop.menu.view.toggleFileTree": "تبديل شجرة الملفات",
"desktop.menu.view.back": "رجوع",
"desktop.menu.view.forward": "تقدم",
"desktop.menu.view.previousSession": "الجلسة السابقة",
"desktop.menu.view.nextSession": "الجلسة التالية",
"desktop.menu.help.documentation": "وثائق OpenCode",
"desktop.menu.help.supportForum": "منتدى الدعم",
"desktop.menu.help.shareFeedback": "مشاركة التعليقات",
"desktop.menu.help.reportBug": "الإبلاغ عن خطأ",
"desktop.cli.error.unsupportedPlatform": "تثبيت CLI مدعوم فقط على macOS و Linux.",
"desktop.cli.error.sidecarMissing": "ملف OpenCode CLI الثنائي مفقود. حاول إعادة تثبيت تطبيق سطح المكتب.",
"desktop.cli.error.scriptWriteFailed": "فشل تحضير برنامج تثبيت CLI.",
"desktop.cli.error.scriptPermissionFailed": "فشل جعل برنامج تثبيت CLI قابلاً للتنفيذ.",
"desktop.cli.error.scriptRunFailed": "فشل تشغيل برنامج تثبيت CLI.",
"desktop.cli.error.scriptFailed": "فشل برنامج تثبيت CLI.",
"desktop.cli.error.installPathUnknown": "تعذر تحديد مكان تثبيت CLI.",
"desktop.cli.error.unknown": "خطأ تثبيت غير معروف",
"desktop.loading.status.initial": "لحظة من فضلك...",
"desktop.loading.status.done": "تم الانتهاء",
"desktop.loading.status.migrating": "جارٍ ترحيل قاعدة البيانات الخاصة بك",
"desktop.loading.status.waiting": "قد يستغرق هذا بضع دقائق",
"desktop.loading.progressAria": "تقدم ترحيل قاعدة البيانات",
"desktop.server.local": "خادم محلي",
}

View File

@@ -24,4 +24,38 @@ export const dict = {
"desktop.cli.installed.message": "CLI instalada em {{path}}\n\nReinicie seu terminal para usar o comando 'opencode'.",
"desktop.cli.failed.title": "Falha na instalação",
"desktop.cli.failed.message": "Falha ao instalar a CLI: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "Arquivo",
"desktop.menu.edit": "Editar",
"desktop.menu.view": "Visualizar",
"desktop.menu.help": "Ajuda",
"desktop.menu.file.newSession": "Nova Sessão",
"desktop.menu.file.openProject": "Abrir Projeto...",
"desktop.menu.view.toggleSidebar": "Alternar Barra Lateral",
"desktop.menu.view.toggleTerminal": "Alternar Terminal",
"desktop.menu.view.toggleFileTree": "Alternar Árvore de Arquivos",
"desktop.menu.view.back": "Voltar",
"desktop.menu.view.forward": "Avançar",
"desktop.menu.view.previousSession": "Sessão Anterior",
"desktop.menu.view.nextSession": "Próxima Sessão",
"desktop.menu.help.documentation": "Documentação do OpenCode",
"desktop.menu.help.supportForum": "Fórum de Suporte",
"desktop.menu.help.shareFeedback": "Compartilhar Feedback",
"desktop.menu.help.reportBug": "Relatar um Bug",
"desktop.cli.error.unsupportedPlatform": "A instalação da CLI é suportada apenas no macOS e Linux.",
"desktop.cli.error.sidecarMissing":
"O binário da CLI do OpenCode está ausente. Tente reinstalar o aplicativo de desktop.",
"desktop.cli.error.scriptWriteFailed": "Falha ao preparar o script de instalação da CLI.",
"desktop.cli.error.scriptPermissionFailed": "Falha ao tornar o script de instalação da CLI executável.",
"desktop.cli.error.scriptRunFailed": "Falha ao executar o script de instalação da CLI.",
"desktop.cli.error.scriptFailed": "O instalador da CLI falhou.",
"desktop.cli.error.installPathUnknown": "Não foi possível determinar onde a CLI foi instalada.",
"desktop.cli.error.unknown": "Erro de instalação desconhecido",
"desktop.loading.status.initial": "Só um momento...",
"desktop.loading.status.done": "Tudo pronto",
"desktop.loading.status.migrating": "Migrando seu banco de dados",
"desktop.loading.status.waiting": "Isso pode levar alguns minutos",
"desktop.loading.progressAria": "Progresso da migração do banco de dados",
"desktop.server.local": "Servidor Local",
}

View File

@@ -25,4 +25,38 @@ export const dict = {
"CLI je instaliran u {{path}}\n\nRestartuj terminal da bi koristio komandu 'opencode'.",
"desktop.cli.failed.title": "Instalacija nije uspjela",
"desktop.cli.failed.message": "Neuspjela instalacija CLI-a: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "Datoteka",
"desktop.menu.edit": "Uredi",
"desktop.menu.view": "Prikaz",
"desktop.menu.help": "Pomoć",
"desktop.menu.file.newSession": "Nova sesija",
"desktop.menu.file.openProject": "Otvori projekat...",
"desktop.menu.view.toggleSidebar": "Prebaci bočnu traku",
"desktop.menu.view.toggleTerminal": "Prebaci terminal",
"desktop.menu.view.toggleFileTree": "Prebaci stablo datoteka",
"desktop.menu.view.back": "Nazad",
"desktop.menu.view.forward": "Naprijed",
"desktop.menu.view.previousSession": "Prethodna sesija",
"desktop.menu.view.nextSession": "Sljedeća sesija",
"desktop.menu.help.documentation": "OpenCode Dokumentacija",
"desktop.menu.help.supportForum": "Forum za podršku",
"desktop.menu.help.shareFeedback": "Podijeli povratne informacije",
"desktop.menu.help.reportBug": "Prijavi grešku",
"desktop.cli.error.unsupportedPlatform": "Instalacija CLI-a je podržana samo na macOS-u i Linux-u.",
"desktop.cli.error.sidecarMissing":
"Nedostaje binarna datoteka OpenCode CLI-a. Pokušaj ponovo instalirati desktop aplikaciju.",
"desktop.cli.error.scriptWriteFailed": "Nije uspjela priprema skripte za instalaciju CLI-a.",
"desktop.cli.error.scriptPermissionFailed": "Nije uspjelo postavljanje izvršnih dozvola za instalaciju CLI-a.",
"desktop.cli.error.scriptRunFailed": "Nije uspjelo pokretanje skripte za instalaciju CLI-a.",
"desktop.cli.error.scriptFailed": "Instalacija CLI-a nije uspjela.",
"desktop.cli.error.installPathUnknown": "Nije bilo moguće utvrditi gdje je instaliran CLI.",
"desktop.cli.error.unknown": "Nepoznata greška pri instalaciji",
"desktop.loading.status.initial": "Samo trenutak...",
"desktop.loading.status.done": "Sve je gotovo",
"desktop.loading.status.migrating": "Migracija baze podataka u toku",
"desktop.loading.status.waiting": "Ovo može potrajati nekoliko minuta",
"desktop.loading.progressAria": "Napredak migracije baze podataka",
"desktop.server.local": "Lokalni server",
}

View File

@@ -25,4 +25,37 @@ export const dict = {
"CLI installeret i {{path}}\n\nGenstart din terminal for at bruge 'opencode'-kommandoen.",
"desktop.cli.failed.title": "Installation mislykkedes",
"desktop.cli.failed.message": "Kunne ikke installere CLI: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "Filer",
"desktop.menu.edit": "Rediger",
"desktop.menu.view": "Vis",
"desktop.menu.help": "Hjælp",
"desktop.menu.file.newSession": "Ny session",
"desktop.menu.file.openProject": "Åbn projekt...",
"desktop.menu.view.toggleSidebar": "Slå sidepanel til/fra",
"desktop.menu.view.toggleTerminal": "Slå terminal til/fra",
"desktop.menu.view.toggleFileTree": "Slå filoversigt til/fra",
"desktop.menu.view.back": "Tilbage",
"desktop.menu.view.forward": "Fremad",
"desktop.menu.view.previousSession": "Forrige session",
"desktop.menu.view.nextSession": "Næste session",
"desktop.menu.help.documentation": "OpenCode Dokumentation",
"desktop.menu.help.supportForum": "Supportforum",
"desktop.menu.help.shareFeedback": "Del feedback",
"desktop.menu.help.reportBug": "Rapporter en fejl",
"desktop.cli.error.unsupportedPlatform": "CLI-installation understøttes kun på macOS og Linux.",
"desktop.cli.error.sidecarMissing": "OpenCode CLI-binærfil mangler. Prøv at geninstallere desktop-appen.",
"desktop.cli.error.scriptWriteFailed": "Kunne ikke forberede CLI-installationsscriptet.",
"desktop.cli.error.scriptPermissionFailed": "Kunne ikke gøre CLI-installationsscriptet eksekverbart.",
"desktop.cli.error.scriptRunFailed": "Kunne ikke køre CLI-installationsscriptet.",
"desktop.cli.error.scriptFailed": "CLI-installationsprogrammet mislykkedes.",
"desktop.cli.error.installPathUnknown": "Kunne ikke fastslå, hvor CLI'en blev installeret.",
"desktop.cli.error.unknown": "Ukendt installationsfejl",
"desktop.loading.status.initial": "Lige et øjeblik...",
"desktop.loading.status.done": "Helt færdig",
"desktop.loading.status.migrating": "Migrerer din database",
"desktop.loading.status.waiting": "Dette kan tage et par minutter",
"desktop.loading.progressAria": "Status for databasemigrering",
"desktop.server.local": "Lokal server",
}

View File

@@ -25,4 +25,38 @@ export const dict = {
"CLI wurde in {{path}} installiert\n\nStarten Sie Ihr Terminal neu, um den Befehl 'opencode' zu verwenden.",
"desktop.cli.failed.title": "Installation fehlgeschlagen",
"desktop.cli.failed.message": "CLI konnte nicht installiert werden: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "Datei",
"desktop.menu.edit": "Bearbeiten",
"desktop.menu.view": "Ansicht",
"desktop.menu.help": "Hilfe",
"desktop.menu.file.newSession": "Neue Sitzung",
"desktop.menu.file.openProject": "Projekt öffnen...",
"desktop.menu.view.toggleSidebar": "Seitenleiste umschalten",
"desktop.menu.view.toggleTerminal": "Terminal umschalten",
"desktop.menu.view.toggleFileTree": "Dateibaum umschalten",
"desktop.menu.view.back": "Zurück",
"desktop.menu.view.forward": "Vorwärts",
"desktop.menu.view.previousSession": "Vorherige Sitzung",
"desktop.menu.view.nextSession": "Nächste Sitzung",
"desktop.menu.help.documentation": "OpenCode-Dokumentation",
"desktop.menu.help.supportForum": "Support-Forum",
"desktop.menu.help.shareFeedback": "Feedback teilen",
"desktop.menu.help.reportBug": "Einen Fehler melden",
"desktop.cli.error.unsupportedPlatform": "Die CLI-Installation wird nur unter macOS und Linux unterstützt.",
"desktop.cli.error.sidecarMissing":
"Das OpenCode CLI-Binary fehlt. Versuchen Sie, die Desktop-App neu zu installieren.",
"desktop.cli.error.scriptWriteFailed": "Das CLI-Installationsskript konnte nicht vorbereitet werden.",
"desktop.cli.error.scriptPermissionFailed": "Das CLI-Installationsskript konnte nicht ausführbar gemacht werden.",
"desktop.cli.error.scriptRunFailed": "Das CLI-Installationsskript konnte nicht ausgeführt werden.",
"desktop.cli.error.scriptFailed": "Das CLI-Installationsprogramm ist fehlgeschlagen.",
"desktop.cli.error.installPathUnknown": "Es konnte nicht ermittelt werden, wo die CLI installiert wurde.",
"desktop.cli.error.unknown": "Unbekannter Installationsfehler",
"desktop.loading.status.initial": "Einen Moment bitte...",
"desktop.loading.status.done": "Alles erledigt",
"desktop.loading.status.migrating": "Ihre Datenbank wird migriert",
"desktop.loading.status.waiting": "Dies kann einige Minuten dauern",
"desktop.loading.progressAria": "Fortschritt der Datenbankmigration",
"desktop.server.local": "Lokaler Server",
}

View File

@@ -3,6 +3,24 @@ export const dict = {
"desktop.menu.installCli": "Install CLI...",
"desktop.menu.reloadWebview": "Reload Webview",
"desktop.menu.restart": "Restart",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "File",
"desktop.menu.edit": "Edit",
"desktop.menu.view": "View",
"desktop.menu.help": "Help",
"desktop.menu.file.newSession": "New Session",
"desktop.menu.file.openProject": "Open Project...",
"desktop.menu.view.toggleSidebar": "Toggle Sidebar",
"desktop.menu.view.toggleTerminal": "Toggle Terminal",
"desktop.menu.view.toggleFileTree": "Toggle File Tree",
"desktop.menu.view.back": "Back",
"desktop.menu.view.forward": "Forward",
"desktop.menu.view.previousSession": "Previous Session",
"desktop.menu.view.nextSession": "Next Session",
"desktop.menu.help.documentation": "OpenCode Documentation",
"desktop.menu.help.supportForum": "Support Forum",
"desktop.menu.help.shareFeedback": "Share Feedback",
"desktop.menu.help.reportBug": "Report a Bug",
"desktop.dialog.chooseFolder": "Choose a folder",
"desktop.dialog.chooseFile": "Choose a file",
@@ -24,4 +42,20 @@ export const dict = {
"desktop.cli.installed.message": "CLI installed to {{path}}\n\nRestart your terminal to use the 'opencode' command.",
"desktop.cli.failed.title": "Installation Failed",
"desktop.cli.failed.message": "Failed to install CLI: {{error}}",
"desktop.cli.error.unsupportedPlatform": "CLI installation is only supported on macOS and Linux.",
"desktop.cli.error.sidecarMissing": "OpenCode CLI binary is missing. Try reinstalling the desktop app.",
"desktop.cli.error.scriptWriteFailed": "Failed to prepare CLI installer script.",
"desktop.cli.error.scriptPermissionFailed": "Failed to make CLI installer executable.",
"desktop.cli.error.scriptRunFailed": "Failed to run CLI installer script.",
"desktop.cli.error.scriptFailed": "CLI installer failed.",
"desktop.cli.error.installPathUnknown": "Could not determine where the CLI was installed.",
"desktop.cli.error.unknown": "Unknown installation error",
"desktop.loading.status.initial": "Just a moment...",
"desktop.loading.status.done": "All done",
"desktop.loading.status.migrating": "Migrating your database",
"desktop.loading.status.waiting": "This may take a couple of minutes",
"desktop.loading.progressAria": "Database migration progress",
"desktop.server.local": "Local Server",
}

View File

@@ -24,4 +24,38 @@ export const dict = {
"desktop.cli.installed.message": "CLI instalada en {{path}}\n\nReinicia tu terminal para usar el comando 'opencode'.",
"desktop.cli.failed.title": "Instalación fallida",
"desktop.cli.failed.message": "No se pudo instalar la CLI: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "Archivo",
"desktop.menu.edit": "Editar",
"desktop.menu.view": "Ver",
"desktop.menu.help": "Ayuda",
"desktop.menu.file.newSession": "Nueva sesión",
"desktop.menu.file.openProject": "Abrir proyecto...",
"desktop.menu.view.toggleSidebar": "Alternar barra lateral",
"desktop.menu.view.toggleTerminal": "Alternar terminal",
"desktop.menu.view.toggleFileTree": "Alternar árbol de archivos",
"desktop.menu.view.back": "Atrás",
"desktop.menu.view.forward": "Adelante",
"desktop.menu.view.previousSession": "Sesión anterior",
"desktop.menu.view.nextSession": "Siguiente sesión",
"desktop.menu.help.documentation": "Documentación de OpenCode",
"desktop.menu.help.supportForum": "Foro de soporte",
"desktop.menu.help.shareFeedback": "Compartir comentarios",
"desktop.menu.help.reportBug": "Informar de un error",
"desktop.cli.error.unsupportedPlatform": "La instalación de la CLI solo es compatible con macOS y Linux.",
"desktop.cli.error.sidecarMissing":
"Falta el binario de la CLI de OpenCode. Intenta reinstalar la aplicación de escritorio.",
"desktop.cli.error.scriptWriteFailed": "No se pudo preparar el script del instalador de la CLI.",
"desktop.cli.error.scriptPermissionFailed": "No se pudo hacer ejecutable el script del instalador de la CLI.",
"desktop.cli.error.scriptRunFailed": "No se pudo ejecutar el script del instalador de la CLI.",
"desktop.cli.error.scriptFailed": "El instalador de la CLI falló.",
"desktop.cli.error.installPathUnknown": "No se pudo determinar dónde se instaló la CLI.",
"desktop.cli.error.unknown": "Error de instalación desconocido",
"desktop.loading.status.initial": "Un momento...",
"desktop.loading.status.done": "Todo listo",
"desktop.loading.status.migrating": "Migrando tu base de datos",
"desktop.loading.status.waiting": "Esto puede tardar unos minutos",
"desktop.loading.progressAria": "Progreso de migración de la base de datos",
"desktop.server.local": "Servidor local",
}

View File

@@ -25,4 +25,38 @@ export const dict = {
"CLI installée dans {{path}}\n\nRedémarrez votre terminal pour utiliser la commande 'opencode'.",
"desktop.cli.failed.title": "Échec de l'installation",
"desktop.cli.failed.message": "Impossible d'installer la CLI : {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "Fichier",
"desktop.menu.edit": "Édition",
"desktop.menu.view": "Affichage",
"desktop.menu.help": "Aide",
"desktop.menu.file.newSession": "Nouvelle session",
"desktop.menu.file.openProject": "Ouvrir un projet...",
"desktop.menu.view.toggleSidebar": "Basculer la barre latérale",
"desktop.menu.view.toggleTerminal": "Basculer le terminal",
"desktop.menu.view.toggleFileTree": "Basculer l'arborescence des fichiers",
"desktop.menu.view.back": "Retour",
"desktop.menu.view.forward": "Suivant",
"desktop.menu.view.previousSession": "Session précédente",
"desktop.menu.view.nextSession": "Session suivante",
"desktop.menu.help.documentation": "Documentation d'OpenCode",
"desktop.menu.help.supportForum": "Forum d'assistance",
"desktop.menu.help.shareFeedback": "Partager des commentaires",
"desktop.menu.help.reportBug": "Signaler un bug",
"desktop.cli.error.unsupportedPlatform": "L'installation de la CLI n'est prise en charge que sur macOS et Linux.",
"desktop.cli.error.sidecarMissing":
"Le binaire de la CLI OpenCode est manquant. Essayez de réinstaller l'application de bureau.",
"desktop.cli.error.scriptWriteFailed": "Impossible de préparer le script d'installation de la CLI.",
"desktop.cli.error.scriptPermissionFailed": "Impossible de rendre le script d'installation de la CLI exécutable.",
"desktop.cli.error.scriptRunFailed": "Impossible d'exécuter le script d'installation de la CLI.",
"desktop.cli.error.scriptFailed": "L'installateur de la CLI a échoué.",
"desktop.cli.error.installPathUnknown": "Impossible de déterminer où la CLI a été installée.",
"desktop.cli.error.unknown": "Erreur d'installation inconnue",
"desktop.loading.status.initial": "Un instant...",
"desktop.loading.status.done": "Terminé",
"desktop.loading.status.migrating": "Migration de votre base de données",
"desktop.loading.status.waiting": "Cela peut prendre quelques minutes",
"desktop.loading.progressAria": "Progression de la migration de la base de données",
"desktop.server.local": "Serveur local",
}

View File

@@ -25,4 +25,38 @@ export const dict = {
"CLI を {{path}} にインストールしました\n\nターミナルを再起動して 'opencode' コマンドを使用してください。",
"desktop.cli.failed.title": "インストールに失敗しました",
"desktop.cli.failed.message": "CLI のインストールに失敗しました: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "ファイル",
"desktop.menu.edit": "編集",
"desktop.menu.view": "表示",
"desktop.menu.help": "ヘルプ",
"desktop.menu.file.newSession": "新しいセッション",
"desktop.menu.file.openProject": "プロジェクトを開く...",
"desktop.menu.view.toggleSidebar": "サイドバーの切り替え",
"desktop.menu.view.toggleTerminal": "ターミナルの切り替え",
"desktop.menu.view.toggleFileTree": "ファイルツリーの切り替え",
"desktop.menu.view.back": "戻る",
"desktop.menu.view.forward": "進む",
"desktop.menu.view.previousSession": "前のセッション",
"desktop.menu.view.nextSession": "次のセッション",
"desktop.menu.help.documentation": "OpenCode ドキュメント",
"desktop.menu.help.supportForum": "サポートフォーラム",
"desktop.menu.help.shareFeedback": "フィードバックを共有",
"desktop.menu.help.reportBug": "バグを報告",
"desktop.cli.error.unsupportedPlatform": "CLI のインストールは macOS と Linux のみでサポートされています。",
"desktop.cli.error.sidecarMissing":
"OpenCode CLI のバイナリが見つかりません。デスクトップアプリを再インストールしてみてください。",
"desktop.cli.error.scriptWriteFailed": "CLI インストーラースクリプトの準備に失敗しました。",
"desktop.cli.error.scriptPermissionFailed": "CLI インストーラースクリプトに実行権限を付与できませんでした。",
"desktop.cli.error.scriptRunFailed": "CLI インストーラースクリプトの実行に失敗しました。",
"desktop.cli.error.scriptFailed": "CLI インストーラーが失敗しました。",
"desktop.cli.error.installPathUnknown": "CLI がどこにインストールされたか特定できませんでした。",
"desktop.cli.error.unknown": "不明なインストールエラー",
"desktop.loading.status.initial": "少々お待ちください...",
"desktop.loading.status.done": "完了しました",
"desktop.loading.status.migrating": "データベースを移行しています",
"desktop.loading.status.waiting": "これには数分かかる場合があります",
"desktop.loading.progressAria": "データベース移行の進行状況",
"desktop.server.local": "ローカルサーバー",
}

View File

@@ -24,4 +24,37 @@ export const dict = {
"CLI가 {{path}}에 설치되었습니다\n\n터미널을 다시 시작하여 'opencode' 명령을 사용하세요.",
"desktop.cli.failed.title": "설치 실패",
"desktop.cli.failed.message": "CLI 설치 실패: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "파일",
"desktop.menu.edit": "편집",
"desktop.menu.view": "보기",
"desktop.menu.help": "도움말",
"desktop.menu.file.newSession": "새 세션",
"desktop.menu.file.openProject": "프로젝트 열기...",
"desktop.menu.view.toggleSidebar": "사이드바 전환",
"desktop.menu.view.toggleTerminal": "터미널 전환",
"desktop.menu.view.toggleFileTree": "파일 트리 전환",
"desktop.menu.view.back": "뒤로",
"desktop.menu.view.forward": "앞으로",
"desktop.menu.view.previousSession": "이전 세션",
"desktop.menu.view.nextSession": "다음 세션",
"desktop.menu.help.documentation": "OpenCode 문서",
"desktop.menu.help.supportForum": "지원 포럼",
"desktop.menu.help.shareFeedback": "피드백 공유",
"desktop.menu.help.reportBug": "버그 신고",
"desktop.cli.error.unsupportedPlatform": "CLI 설치는 macOS 및 Linux에서만 지원됩니다.",
"desktop.cli.error.sidecarMissing": "OpenCode CLI 바이너리가 누락되었습니다. 데스크톱 앱을 다시 설치해 보세요.",
"desktop.cli.error.scriptWriteFailed": "CLI 설치 스크립트를 준비하지 못했습니다.",
"desktop.cli.error.scriptPermissionFailed": "CLI 설치 스크립트를 실행 가능하게 만들지 못했습니다.",
"desktop.cli.error.scriptRunFailed": "CLI 설치 스크립트를 실행하지 못했습니다.",
"desktop.cli.error.scriptFailed": "CLI 설치 프로그램이 실패했습니다.",
"desktop.cli.error.installPathUnknown": "CLI가 어디에 설치되었는지 확인할 수 없습니다.",
"desktop.cli.error.unknown": "알 수 없는 설치 오류",
"desktop.loading.status.initial": "잠시만 기다려 주세요...",
"desktop.loading.status.done": "모두 완료되었습니다",
"desktop.loading.status.migrating": "데이터베이스 마이그레이션 중",
"desktop.loading.status.waiting": "이 작업은 몇 분 정도 걸릴 수 있습니다",
"desktop.loading.progressAria": "데이터베이스 마이그레이션 진행률",
"desktop.server.local": "로컬 서버",
}

View File

@@ -25,4 +25,37 @@ export const dict = {
"CLI installert til {{path}}\n\nStart terminalen på nytt for å bruke 'opencode'-kommandoen.",
"desktop.cli.failed.title": "Installasjon mislyktes",
"desktop.cli.failed.message": "Kunne ikke installere CLI: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "Fil",
"desktop.menu.edit": "Rediger",
"desktop.menu.view": "Vis",
"desktop.menu.help": "Hjelp",
"desktop.menu.file.newSession": "Ny sesjon",
"desktop.menu.file.openProject": "Åpne prosjekt...",
"desktop.menu.view.toggleSidebar": "Vis/skjul sidefelt",
"desktop.menu.view.toggleTerminal": "Vis/skjul terminal",
"desktop.menu.view.toggleFileTree": "Vis/skjul filtre",
"desktop.menu.view.back": "Tilbake",
"desktop.menu.view.forward": "Frem",
"desktop.menu.view.previousSession": "Forrige sesjon",
"desktop.menu.view.nextSession": "Neste sesjon",
"desktop.menu.help.documentation": "OpenCode Dokumentasjon",
"desktop.menu.help.supportForum": "Støtteforum",
"desktop.menu.help.shareFeedback": "Del tilbakemelding",
"desktop.menu.help.reportBug": "Rapporter en feil",
"desktop.cli.error.unsupportedPlatform": "CLI-installasjon støttes kun på macOS og Linux.",
"desktop.cli.error.sidecarMissing": "OpenCode CLI-binærfil mangler. Prøv å installere skrivebordsappen på nytt.",
"desktop.cli.error.scriptWriteFailed": "Kunne ikke klargjøre CLI-installasjonsskriptet.",
"desktop.cli.error.scriptPermissionFailed": "Kunne ikke gjøre CLI-installasjonsskriptet kjørbart.",
"desktop.cli.error.scriptRunFailed": "Kunne ikke kjøre CLI-installasjonsskriptet.",
"desktop.cli.error.scriptFailed": "CLI-installasjonsprogrammet mislyktes.",
"desktop.cli.error.installPathUnknown": "Kunne ikke avgjøre hvor CLI ble installert.",
"desktop.cli.error.unknown": "Ukjent installasjonsfeil",
"desktop.loading.status.initial": "Et øyeblikk...",
"desktop.loading.status.done": "Alt ferdig",
"desktop.loading.status.migrating": "Migrerer databasen din",
"desktop.loading.status.waiting": "Dette kan ta et par minutter",
"desktop.loading.progressAria": "Fremdrift for databasemigrering",
"desktop.server.local": "Lokal server",
}

View File

@@ -25,4 +25,38 @@ export const dict = {
"CLI zainstalowane w {{path}}\n\nUruchom ponownie terminal, aby użyć polecenia 'opencode'.",
"desktop.cli.failed.title": "Instalacja nie powiodła się",
"desktop.cli.failed.message": "Nie udało się zainstalować CLI: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "Plik",
"desktop.menu.edit": "Edycja",
"desktop.menu.view": "Widok",
"desktop.menu.help": "Pomoc",
"desktop.menu.file.newSession": "Nowa sesja",
"desktop.menu.file.openProject": "Otwórz projekt...",
"desktop.menu.view.toggleSidebar": "Przełącz pasek boczny",
"desktop.menu.view.toggleTerminal": "Przełącz terminal",
"desktop.menu.view.toggleFileTree": "Przełącz drzewo plików",
"desktop.menu.view.back": "Wstecz",
"desktop.menu.view.forward": "Dalej",
"desktop.menu.view.previousSession": "Poprzednia sesja",
"desktop.menu.view.nextSession": "Następna sesja",
"desktop.menu.help.documentation": "Dokumentacja OpenCode",
"desktop.menu.help.supportForum": "Forum wsparcia",
"desktop.menu.help.shareFeedback": "Prześlij opinię",
"desktop.menu.help.reportBug": "Zgłoś błąd",
"desktop.cli.error.unsupportedPlatform": "Instalacja CLI jest obsługiwana tylko na macOS i Linux.",
"desktop.cli.error.sidecarMissing":
"Brakuje pliku binarnego OpenCode CLI. Spróbuj ponownie zainstalować aplikację na komputer.",
"desktop.cli.error.scriptWriteFailed": "Nie udało się przygotować skryptu instalatora CLI.",
"desktop.cli.error.scriptPermissionFailed": "Nie udało się nadać uprawnień do wykonania skryptu instalatora CLI.",
"desktop.cli.error.scriptRunFailed": "Nie udało się uruchomić skryptu instalatora CLI.",
"desktop.cli.error.scriptFailed": "Instalator CLI nie powiódł się.",
"desktop.cli.error.installPathUnknown": "Nie udało się ustalić, gdzie zostało zainstalowane CLI.",
"desktop.cli.error.unknown": "Nieznany błąd instalacji",
"desktop.loading.status.initial": "Chwileczkę...",
"desktop.loading.status.done": "Gotowe",
"desktop.loading.status.migrating": "Migrowanie bazy danych",
"desktop.loading.status.waiting": "Może to potrwać kilka minut",
"desktop.loading.progressAria": "Postęp migracji bazy danych",
"desktop.server.local": "Serwer lokalny",
}

View File

@@ -24,4 +24,38 @@ export const dict = {
"CLI установлен в {{path}}\n\nПерезапустите терминал, чтобы использовать команду 'opencode'.",
"desktop.cli.failed.title": "Ошибка установки",
"desktop.cli.failed.message": "Не удалось установить CLI: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "Файл",
"desktop.menu.edit": "Правка",
"desktop.menu.view": "Вид",
"desktop.menu.help": "Справка",
"desktop.menu.file.newSession": "Новая сессия",
"desktop.menu.file.openProject": "Открыть проект...",
"desktop.menu.view.toggleSidebar": "Переключить боковую панель",
"desktop.menu.view.toggleTerminal": "Переключить терминал",
"desktop.menu.view.toggleFileTree": "Переключить дерево файлов",
"desktop.menu.view.back": "Назад",
"desktop.menu.view.forward": "Вперед",
"desktop.menu.view.previousSession": "Предыдущая сессия",
"desktop.menu.view.nextSession": "Следующая сессия",
"desktop.menu.help.documentation": "Документация OpenCode",
"desktop.menu.help.supportForum": "Форум поддержки",
"desktop.menu.help.shareFeedback": "Поделиться отзывом",
"desktop.menu.help.reportBug": "Сообщить об ошибке",
"desktop.cli.error.unsupportedPlatform": "Установка CLI поддерживается только в macOS и Linux.",
"desktop.cli.error.sidecarMissing":
"Отсутствует бинарный файл OpenCode CLI. Попробуйте переустановить настольное приложение.",
"desktop.cli.error.scriptWriteFailed": "Не удалось подготовить скрипт установщика CLI.",
"desktop.cli.error.scriptPermissionFailed": "Не удалось сделать скрипт установщика CLI исполняемым.",
"desktop.cli.error.scriptRunFailed": "Не удалось запустить скрипт установщика CLI.",
"desktop.cli.error.scriptFailed": "Ошибка установщика CLI.",
"desktop.cli.error.installPathUnknown": "Не удалось определить, куда был установлен CLI.",
"desktop.cli.error.unknown": "Неизвестная ошибка установки",
"desktop.loading.status.initial": "Минуточку...",
"desktop.loading.status.done": "Всё готово",
"desktop.loading.status.migrating": "Миграция вашей базы данных",
"desktop.loading.status.waiting": "Это может занять пару минут",
"desktop.loading.progressAria": "Прогресс миграции базы данных",
"desktop.server.local": "Локальный сервер",
}

View File

@@ -23,4 +23,37 @@ export const dict = {
"desktop.cli.installed.message": "CLI 已安装到 {{path}}\n\n重启终端以使用 'opencode' 命令。",
"desktop.cli.failed.title": "安装失败",
"desktop.cli.failed.message": "无法安装 CLI: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "文件",
"desktop.menu.edit": "编辑",
"desktop.menu.view": "查看",
"desktop.menu.help": "帮助",
"desktop.menu.file.newSession": "新会话",
"desktop.menu.file.openProject": "打开项目...",
"desktop.menu.view.toggleSidebar": "切换侧边栏",
"desktop.menu.view.toggleTerminal": "切换终端",
"desktop.menu.view.toggleFileTree": "切换文件树",
"desktop.menu.view.back": "后退",
"desktop.menu.view.forward": "前进",
"desktop.menu.view.previousSession": "上一个会话",
"desktop.menu.view.nextSession": "下一个会话",
"desktop.menu.help.documentation": "OpenCode 文档",
"desktop.menu.help.supportForum": "支持论坛",
"desktop.menu.help.shareFeedback": "分享反馈",
"desktop.menu.help.reportBug": "报告错误",
"desktop.cli.error.unsupportedPlatform": "CLI 安装仅在 macOS 和 Linux 上受支持。",
"desktop.cli.error.sidecarMissing": "OpenCode CLI 二进制文件缺失。请尝试重新安装桌面应用程序。",
"desktop.cli.error.scriptWriteFailed": "无法准备 CLI 安装脚本。",
"desktop.cli.error.scriptPermissionFailed": "无法使 CLI 安装脚本可执行。",
"desktop.cli.error.scriptRunFailed": "无法运行 CLI 安装脚本。",
"desktop.cli.error.scriptFailed": "CLI 安装程序失败。",
"desktop.cli.error.installPathUnknown": "无法确定 CLI 的安装位置。",
"desktop.cli.error.unknown": "未知的安装错误",
"desktop.loading.status.initial": "稍等片刻...",
"desktop.loading.status.done": "全部完成",
"desktop.loading.status.migrating": "正在迁移您的数据库",
"desktop.loading.status.waiting": "这可能需要几分钟",
"desktop.loading.progressAria": "数据库迁移进度",
"desktop.server.local": "本地服务器",
}

View File

@@ -23,4 +23,37 @@ export const dict = {
"desktop.cli.installed.message": "CLI 已安裝到 {{path}}\n\n重新啟動終端機以使用 'opencode' 命令。",
"desktop.cli.failed.title": "安裝失敗",
"desktop.cli.failed.message": "無法安裝 CLI: {{error}}",
"desktop.menu.app": "OpenCode",
"desktop.menu.file": "檔案",
"desktop.menu.edit": "編輯",
"desktop.menu.view": "檢視",
"desktop.menu.help": "說明",
"desktop.menu.file.newSession": "新工作階段",
"desktop.menu.file.openProject": "開啟專案...",
"desktop.menu.view.toggleSidebar": "切換側邊欄",
"desktop.menu.view.toggleTerminal": "切換終端機",
"desktop.menu.view.toggleFileTree": "切換檔案樹",
"desktop.menu.view.back": "上一步",
"desktop.menu.view.forward": "下一步",
"desktop.menu.view.previousSession": "上一個工作階段",
"desktop.menu.view.nextSession": "下一個工作階段",
"desktop.menu.help.documentation": "OpenCode 文件",
"desktop.menu.help.supportForum": "支援論壇",
"desktop.menu.help.shareFeedback": "分享意見回饋",
"desktop.menu.help.reportBug": "回報錯誤",
"desktop.cli.error.unsupportedPlatform": "CLI 安裝僅支援 macOS 與 Linux。",
"desktop.cli.error.sidecarMissing": "OpenCode CLI 執行檔遺失。請嘗試重新安裝桌面應用程式。",
"desktop.cli.error.scriptWriteFailed": "無法準備 CLI 安裝指令碼。",
"desktop.cli.error.scriptPermissionFailed": "無法將 CLI 安裝指令碼設為可執行。",
"desktop.cli.error.scriptRunFailed": "無法執行 CLI 安裝指令碼。",
"desktop.cli.error.scriptFailed": "CLI 安裝程式失敗。",
"desktop.cli.error.installPathUnknown": "無法確定 CLI 的安裝位置。",
"desktop.cli.error.unknown": "未知的安裝錯誤",
"desktop.loading.status.initial": "稍等片刻...",
"desktop.loading.status.done": "全部完成",
"desktop.loading.status.migrating": "正在移轉您的資料庫",
"desktop.loading.status.waiting": "這可能需要幾分鐘",
"desktop.loading.progressAria": "資料庫移轉進度",
"desktop.server.local": "本機伺服器",
}

View File

@@ -445,7 +445,7 @@ render(() => {
}
const server: ServerConnection.Any = data.is_sidecar
? {
displayName: "Local Server",
displayName: t("desktop.server.local"),
type: "sidecar",
variant: "base",
http,

View File

@@ -8,11 +8,18 @@ import "./styles.css"
import { createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js"
import { commands, events, InitStep } from "./bindings"
import { Channel } from "@tauri-apps/api/core"
import { initI18n, t } from "./i18n"
const root = document.getElementById("root")!
const lines = ["Just a moment...", "Migrating your database", "This may take a couple of minutes"]
const lines = [
t("desktop.loading.status.initial"),
t("desktop.loading.status.migrating"),
t("desktop.loading.status.waiting"),
]
const delays = [3000, 9000]
void initI18n()
render(() => {
const [step, setStep] = createSignal<InitStep | null>(null)
const [line, setLine] = createSignal(0)
@@ -54,9 +61,9 @@ render(() => {
})
const status = createMemo(() => {
if (phase() === "done") return "All done"
if (phase() === "done") return t("desktop.loading.status.done")
if (phase() === "sqlite_waiting") return lines[line()]
return "Just a moment..."
return t("desktop.loading.status.initial")
})
return (
@@ -72,7 +79,7 @@ render(() => {
<Progress
value={value()}
class="w-20 [&_[data-slot='progress-track']]:h-1 [&_[data-slot='progress-track']]:border-0 [&_[data-slot='progress-track']]:rounded-none [&_[data-slot='progress-track']]:bg-surface-weak [&_[data-slot='progress-fill']]:rounded-none [&_[data-slot='progress-fill']]:bg-icon-warning-base"
aria-label="Database migration progress"
aria-label={t("desktop.loading.progressAria")}
getValueLabel={({ value }) => `${Math.round(value)}%`}
/>
</div>

View File

@@ -16,7 +16,7 @@ export async function createMenu(trigger: (id: string) => void) {
const menu = await Menu.new({
items: [
await Submenu.new({
text: "OpenCode",
text: t("desktop.menu.app"),
items: [
await PredefinedMenuItem.new({
item: { About: null },
@@ -62,15 +62,15 @@ export async function createMenu(trigger: (id: string) => void) {
].filter(Boolean),
}),
await Submenu.new({
text: "File",
text: t("desktop.menu.file"),
items: [
await MenuItem.new({
text: "New Session",
text: t("desktop.menu.file.newSession"),
accelerator: "Shift+Cmd+S",
action: () => trigger("session.new"),
}),
await MenuItem.new({
text: "Open Project...",
text: t("desktop.menu.file.openProject"),
accelerator: "Cmd+O",
action: () => trigger("project.open"),
}),
@@ -83,7 +83,7 @@ export async function createMenu(trigger: (id: string) => void) {
],
}),
await Submenu.new({
text: "Edit",
text: t("desktop.menu.edit"),
items: [
await PredefinedMenuItem.new({
item: "Undo",
@@ -109,44 +109,44 @@ export async function createMenu(trigger: (id: string) => void) {
],
}),
await Submenu.new({
text: "View",
text: t("desktop.menu.view"),
items: [
await MenuItem.new({
action: () => trigger("sidebar.toggle"),
text: "Toggle Sidebar",
text: t("desktop.menu.view.toggleSidebar"),
accelerator: "Cmd+B",
}),
await MenuItem.new({
action: () => trigger("terminal.toggle"),
text: "Toggle Terminal",
text: t("desktop.menu.view.toggleTerminal"),
accelerator: "Ctrl+`",
}),
await MenuItem.new({
action: () => trigger("fileTree.toggle"),
text: "Toggle File Tree",
text: t("desktop.menu.view.toggleFileTree"),
}),
await PredefinedMenuItem.new({
item: "Separator",
}),
await MenuItem.new({
action: () => trigger("common.goBack"),
text: "Back",
text: t("desktop.menu.view.back"),
}),
await MenuItem.new({
action: () => trigger("common.goForward"),
text: "Forward",
text: t("desktop.menu.view.forward"),
}),
await PredefinedMenuItem.new({
item: "Separator",
}),
await MenuItem.new({
action: () => trigger("session.previous"),
text: "Previous Session",
text: t("desktop.menu.view.previousSession"),
accelerator: "Option+ArrowUp",
}),
await MenuItem.new({
action: () => trigger("session.next"),
text: "Next Session",
text: t("desktop.menu.view.nextSession"),
accelerator: "Option+ArrowDown",
}),
await PredefinedMenuItem.new({
@@ -155,16 +155,16 @@ export async function createMenu(trigger: (id: string) => void) {
],
}),
await Submenu.new({
text: "Help",
text: t("desktop.menu.help"),
items: [
// missing native macos search
await MenuItem.new({
action: () => openUrl("https://opencode.ai/docs"),
text: "OpenCode Documentation",
text: t("desktop.menu.help.documentation"),
}),
await MenuItem.new({
action: () => openUrl("https://discord.com/invite/opencode"),
text: "Support Forum",
text: t("desktop.menu.help.supportForum"),
}),
await PredefinedMenuItem.new({
item: "Separator",
@@ -177,11 +177,11 @@ export async function createMenu(trigger: (id: string) => void) {
}),
await MenuItem.new({
action: () => openUrl("https://github.com/anomalyco/opencode/issues/new?template=feature_request.yml"),
text: "Share Feedback",
text: t("desktop.menu.help.shareFeedback"),
}),
await MenuItem.new({
action: () => openUrl("https://github.com/anomalyco/opencode/issues/new?template=bug_report.yml"),
text: "Report a Bug",
text: t("desktop.menu.help.reportBug"),
}),
],
}),

View File

@@ -56,8 +56,9 @@ function detectLocale() {
function UiI18nBridge(props: ParentProps) {
const locale = createMemo(() => detectLocale())
const zh = uiZh as Partial<Record<string, string>>
const t = (key: keyof typeof uiEn, params?: UiI18nParams) => {
const value = locale() === "zh" ? (uiZh[key] ?? uiEn[key]) : uiEn[key]
const value = locale() === "zh" ? (zh[key] ?? uiEn[key]) : uiEn[key]
const text = value ?? String(key)
return resolveTemplate(text, params)
}

View File

@@ -48,6 +48,7 @@ import type { SkillTool } from "@/tool/skill"
import { useKeyboard, useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
import { useSDK } from "@tui/context/sdk"
import { useCommandDialog } from "@tui/component/dialog-command"
import type { DialogContext } from "@tui/ui/dialog"
import { useKeybind } from "@tui/context/keybind"
import { Header } from "./header"
import { parsePatch } from "diff"
@@ -226,6 +227,8 @@ export function Session() {
let scroll: ScrollBoxRenderable
let prompt: PromptRef
const keybind = useKeybind()
const dialog = useDialog()
const renderer = useRenderer()
// Allow exit when in child session (prompt is hidden)
const exit = useExit()
@@ -312,19 +315,40 @@ export function Session() {
const local = useLocal()
function moveChild(direction: number) {
function moveFirstChild() {
if (children().length === 1) return
let next = children().findIndex((x) => x.id === session()?.id) + direction
if (next >= children().length) next = 0
if (next < 0) next = children().length - 1
if (children()[next]) {
const next = children().find((x) => !!x.parentID)
if (next) {
navigate({
type: "session",
sessionID: children()[next].id,
sessionID: next.id,
})
}
}
function moveChild(direction: number) {
if (children().length === 1) return
const sessions = children().filter((x) => !!x.parentID)
let next = sessions.findIndex((x) => x.id === session()?.id) + direction
if (next >= sessions.length) next = 0
if (next < 0) next = sessions.length - 1
if (sessions[next]) {
navigate({
type: "session",
sessionID: sessions[next].id,
})
}
}
function childSessionHandler(func: (dialog: DialogContext) => void) {
return (dialog: DialogContext) => {
if (!session()?.parentID || dialog.stack.length > 0) return
func(dialog)
}
}
const command = useCommandDialog()
command.register(() => [
{
@@ -884,24 +908,13 @@ export function Session() {
},
},
{
title: "Next child session",
value: "session.child.next",
keybind: "session_child_cycle",
title: "Go to child session",
value: "session.child.first",
keybind: "session_child_first",
category: "Session",
hidden: true,
onSelect: (dialog) => {
moveChild(1)
dialog.clear()
},
},
{
title: "Previous child session",
value: "session.child.previous",
keybind: "session_child_cycle_reverse",
category: "Session",
hidden: true,
onSelect: (dialog) => {
moveChild(-1)
moveFirstChild()
dialog.clear()
},
},
@@ -911,7 +924,7 @@ export function Session() {
keybind: "session_parent",
category: "Session",
hidden: true,
onSelect: (dialog) => {
onSelect: childSessionHandler((dialog) => {
const parentID = session()?.parentID
if (parentID) {
navigate({
@@ -920,7 +933,29 @@ export function Session() {
})
}
dialog.clear()
},
}),
},
{
title: "Next child session",
value: "session.child.next",
keybind: "session_child_cycle",
category: "Session",
hidden: true,
onSelect: childSessionHandler((dialog) => {
moveChild(1)
dialog.clear()
}),
},
{
title: "Previous child session",
value: "session.child.previous",
keybind: "session_child_cycle_reverse",
category: "Session",
hidden: true,
onSelect: childSessionHandler((dialog) => {
moveChild(-1)
dialog.clear()
}),
},
])
@@ -971,9 +1006,6 @@ export function Session() {
}
})
const dialog = useDialog()
const renderer = useRenderer()
// snap to bottom when session changes
createEffect(on(() => route.sessionID, toBottom))
@@ -1933,7 +1965,7 @@ function Task(props: ToolProps<typeof TaskTool>) {
</box>
<Show when={props.metadata.sessionId}>
<text fg={theme.text}>
{keybind.print("session_child_cycle")}
{keybind.print("session_child_first")}
<span style={{ fg: theme.textMuted }}> view subagents</span>
</text>
</Show>

View File

@@ -896,9 +896,10 @@ export namespace Config {
.describe("Delete word backward in input"),
history_previous: z.string().optional().default("up").describe("Previous history item"),
history_next: z.string().optional().default("down").describe("Next history item"),
session_child_cycle: z.string().optional().default("<leader>right").describe("Next child session"),
session_child_cycle_reverse: z.string().optional().default("<leader>left").describe("Previous child session"),
session_parent: z.string().optional().default("<leader>up").describe("Go to parent session"),
session_child_first: z.string().optional().default("<leader>down").describe("Go to first child session"),
session_child_cycle: z.string().optional().default("right").describe("Go to next child session"),
session_child_cycle_reverse: z.string().optional().default("left").describe("Go to previous child session"),
session_parent: z.string().optional().default("up").describe("Go to parent session"),
terminal_suspend: z.string().optional().default("ctrl+z").describe("Suspend terminal"),
terminal_title_toggle: z.string().optional().default("none").describe("Toggle terminal title"),
tips_toggle: z.string().optional().default("<leader>h").describe("Toggle tips on home screen"),

View File

@@ -46,23 +46,14 @@ const VERSION = await (async () => {
return `${major}.${minor}.${patch + 1}`
})()
const bot = ["actions-user", "opencode", "opencode-agent[bot]"]
const teamPath = path.resolve(import.meta.dir, "../../../.github/TEAM_MEMBERS")
const team = [
"actions-user",
"opencode",
"rekram1-node",
"thdxr",
"kommander",
"jayair",
"fwang",
"MrMushrooooom",
"adamdotdevin",
"iamdavidhill",
"Brendonovich",
"nexxeln",
"Hona",
"jlongster",
"opencode-agent[bot]",
"R44VC0RP",
...(await Bun.file(teamPath)
.text()
.then((x) => x.split(/\r?\n/).map((x) => x.trim()))
.then((x) => x.filter((x) => x && !x.startsWith("#")))),
...bot,
]
export const Script = {

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 KiB

View File

@@ -0,0 +1,3 @@
<svg fill="inherit" viewBox="0 0 100 25">
<path d="M79.01 5.863c-4.066 0-6.511 2.92-6.511 6.535 0 3.635 2.445 6.555 6.511 6.555 4.046 0 6.512-2.92 6.512-6.555s-2.466-6.535-6.512-6.535Zm0 10.968c-2.633 0-4.172-1.933-4.172-4.433s1.539-4.455 4.172-4.455c2.635 0 4.151 1.933 4.151 4.434 0 2.521-1.516 4.454-4.15 4.454Zm14.393 2.096c3.393 0 5.542-1.808 5.837-4.539h-2.36c-.316 1.555-1.517 2.437-3.477 2.437-2.423 0-3.878-1.68-3.878-4.433 0-2.774 1.476-4.434 3.878-4.434 1.96 0 3.14.862 3.477 2.5h2.36c-.295-2.773-2.444-4.622-5.837-4.622-3.856 0-6.217 2.669-6.217 6.535 0 3.887 2.36 6.556 6.217 6.556Zm-29.543-.311h2.36v-6.01c0-2.752 1.348-4.244 3.772-4.244h2.276V6.177h-2.255c-2.128 0-3.288.735-3.898 2.605l-.443-.063.527-2.542h-2.36v12.439h.02Zm-24.445-7.332c.106-2.101 1.517-3.53 3.793-3.53 2.276 0 3.646 1.345 3.646 3.53h-7.439Zm9.778.4c0-3.426-2.381-5.821-5.943-5.821-3.73 0-6.174 2.563-6.174 6.535 0 4.013 2.423 6.555 6.28 6.555 2.929 0 5.247-1.597 5.669-3.887h-2.36c-.507 1.156-1.666 1.828-3.31 1.828-2.38 0-3.877-1.408-3.94-3.803h9.694c.042-.588.084-.861.084-1.408Zm5.69 6.932h1.939l5.5-12.44h-2.529L56 15.99l-.316.021-3.793-9.833h-2.508l5.5 12.439ZM32.23 12.35c0-.882-.359-1.701-.99-2.437a8.594 8.594 0 0 1-1.497 1.093c.337.42.527.861.527 1.345 0 2.731-5.837 4.811-14.14 4.811-8.281.021-14.118-2.059-14.118-4.811 0-.463.168-.925.505-1.345a8.13 8.13 0 0 1-1.475-1.093c-.632.736-.99 1.555-.99 2.438 0 4.034 7.207 6.534 16.1 6.534 8.87.021 16.078-2.5 16.078-6.535Zm-3.351 1.534c-.906-.462-1.96-.861-3.16-1.197-1.37.378-2.909.672-4.553.861 2.318.294 4.341.778 5.9 1.408.76-.336 1.37-.693 1.813-1.072Zm-17.849-.357a31.902 31.902 0 0 1-4.467-.84c-1.18.336-2.255.735-3.16 1.197.42.379 1.01.715 1.748 1.05 1.539-.63 3.52-1.113 5.88-1.407Zm21.2-6.808c0-4.013-7.207-6.534-16.079-6.534C7.26.185.051 2.706.051 6.719c0 4.035 7.208 6.535 16.1 6.535 8.872.021 16.079-2.5 16.079-6.535Zm-1.94 0c0 2.732-5.836 4.812-14.139 4.812-8.302.021-14.14-2.06-14.14-4.812 0-2.731 5.838-4.811 14.14-4.811 7.86 0 14.14 2.08 14.14 4.811Zm-3.223 2.564c.758-.336 1.37-.694 1.812-1.072-2.95-1.513-7.544-2.353-12.728-2.353s-9.799.84-12.728 2.353c.422.378 1.012.715 1.75 1.05 2.507-1.05 6.363-1.68 10.978-1.68 4.404 0 8.324.651 10.916 1.702ZM1.042 15.628c-.632.736-.99 1.534-.99 2.438 0 4.034 7.207 6.534 16.1 6.534 8.892 0 16.099-2.521 16.099-6.534 0-.883-.359-1.702-.99-2.438-.422.4-.907.757-1.497 1.093.337.42.527.861.527 1.345 0 2.731-5.837 4.811-14.14 4.811-8.302 0-14.14-2.08-14.14-4.811 0-.463.17-.925.506-1.345a10.73 10.73 0 0 1-1.475-1.093Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.4004 21H5V3H19.4004V6.59961H8.59961V17.4004H15.7998V13.7998H12.2002V10.2002H19.4004V21Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 227 B

View File

@@ -1,4 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.2" d="M19.2002 17.4H8.40017V13.8H15.6002V10.2H19.2002V17.4ZM8.40017 13.8H4.80017V10.2H8.40017V13.8Z" fill="currentColor"/>
<path d="M8.40005 17.4H19.2001V21H4.80005V13.8H8.40005V17.4ZM15.6001 10.2V13.8H8.40005V10.2H15.6001ZM19.2001 10.2H15.6001V6.6H4.80005V3H19.2001V10.2Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 416 B

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -13,6 +13,7 @@ import powershell from "../assets/icons/app/powershell.svg"
import terminal from "../assets/icons/app/terminal.png"
import textmate from "../assets/icons/app/textmate.png"
import vscode from "../assets/icons/app/vscode.svg"
import warp from "../assets/icons/app/warp.png"
import xcode from "../assets/icons/app/xcode.png"
import zed from "../assets/icons/app/zed.svg"
import zedDark from "../assets/icons/app/zed-dark.svg"
@@ -27,6 +28,7 @@ const icons = {
terminal,
iterm2,
ghostty,
warp,
xcode,
"android-studio": androidStudio,
antigravity,

View File

@@ -9,6 +9,7 @@ export const iconNames = [
"terminal",
"iterm2",
"ghostty",
"warp",
"xcode",
"android-studio",
"antigravity",

View File

@@ -1,4 +1,4 @@
import type { JSX } from "solid-js"
import type { ComponentProps, JSX } from "solid-js"
import { DockShell, DockTray } from "./dock-surface"
export function DockPrompt(props: {
@@ -7,12 +7,13 @@ export function DockPrompt(props: {
children: JSX.Element
footer: JSX.Element
ref?: (el: HTMLDivElement) => void
bodyProps?: Omit<ComponentProps<"div">, "children">
}) {
const slot = (name: string) => `${props.kind}-${name}`
return (
<div data-component="dock-prompt" data-kind={props.kind} ref={props.ref}>
<DockShell data-slot={slot("body")}>
<DockShell {...(props.bodyProps ?? {})} data-slot={slot("body")}>
<div data-slot={slot("header")}>{props.header}</div>
<div data-slot={slot("content")}>{props.children}</div>
</DockShell>

View File

@@ -808,49 +808,6 @@
min-width: 0;
}
[data-slot="question-progress"] {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
[data-slot="question-progress-segment"] {
width: 16px;
height: 16px;
padding: 0;
border: 0;
background: transparent;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
touch-action: manipulation;
&::after {
content: "";
width: 16px;
height: 2px;
border-radius: 999px;
background-color: var(--icon-weak-base);
transition: background-color 0.2s ease;
}
&[data-active="true"]::after {
background-color: var(--icon-strong-base);
}
&[data-answered="true"]::after {
background-color: var(--icon-interactive-base);
}
&:disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
[data-slot="question-content"] {
display: flex;
flex-direction: column;
@@ -1020,6 +977,14 @@
white-space: normal;
overflow-wrap: anywhere;
}
&[data-picked="true"] {
[data-slot="question-custom-input"]:focus-visible {
outline: none;
outline-offset: 0;
border-radius: 0;
}
}
}
[data-slot="question-custom"] {

View File

@@ -463,14 +463,22 @@ function contextToolTrigger(part: ToolPart, i18n: ReturnType<typeof useI18n>) {
}
}
function contextToolSummary(parts: ToolPart[]) {
function contextToolSummary(parts: ToolPart[], i18n: ReturnType<typeof useI18n>) {
const read = parts.filter((part) => part.tool === "read").length
const search = parts.filter((part) => part.tool === "glob" || part.tool === "grep").length
const list = parts.filter((part) => part.tool === "list").length
return [
read ? `${read} ${read === 1 ? "read" : "reads"}` : undefined,
search ? `${search} ${search === 1 ? "search" : "searches"}` : undefined,
list ? `${list} ${list === 1 ? "list" : "lists"}` : undefined,
read
? i18n.t(read === 1 ? "ui.messagePart.context.read.one" : "ui.messagePart.context.read.other", { count: read })
: undefined,
search
? i18n.t(search === 1 ? "ui.messagePart.context.search.one" : "ui.messagePart.context.search.other", {
count: search,
})
: undefined,
list
? i18n.t(list === 1 ? "ui.messagePart.context.list.one" : "ui.messagePart.context.list.other", { count: list })
: undefined,
].filter((value): value is string => !!value)
}
@@ -595,7 +603,7 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) {
() =>
!!props.busy || props.parts.some((part) => part.state.status === "pending" || part.state.status === "running"),
)
const summary = createMemo(() => contextToolSummary(props.parts))
const summary = createMemo(() => contextToolSummary(props.parts, i18n))
const details = createMemo(() => summary().join(", "))
return (
@@ -979,7 +987,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
return (
<div style="width: 100%; display: flex; justify-content: flex-end;">
<span class="text-13-regular text-text-weak cursor-default">
{i18n.t("ui.tool.questions")} dismissed
{i18n.t("ui.messagePart.questions.dismissed")}
</span>
</div>
)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 275 KiB

View File

@@ -33,6 +33,7 @@ export const iconNames = [
"ovhcloud",
"openrouter",
"opencode",
"opencode-go",
"openai",
"ollama-cloud",
"nvidia",
@@ -75,6 +76,7 @@ export const iconNames = [
"firmware",
"fireworks-ai",
"fastrouter",
"evroc",
"deepseek",
"deepinfra",
"cortecs",

View File

@@ -1,4 +1,5 @@
import { createSignal, onCleanup, onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js"
import { useI18n } from "../context/i18n"
export interface ScrollViewProps extends ComponentProps<"div"> {
viewportRef?: (el: HTMLDivElement) => void
@@ -6,6 +7,7 @@ export interface ScrollViewProps extends ComponentProps<"div"> {
}
export function ScrollView(props: ScrollViewProps) {
const i18n = useI18n()
const merged = mergeProps({ orientation: "vertical" }, props)
const [local, events, rest] = splitProps(
merged,
@@ -188,7 +190,7 @@ export function ScrollView(props: ScrollViewProps) {
onClick={events.onClick as any}
tabIndex={0}
role="region"
aria-label="scrollable content"
aria-label={i18n.t("ui.scrollView.ariaLabel")}
onKeyDown={(e) => {
onKeyDown(e)
if (typeof events.onKeyDown === "function") events.onKeyDown(e as any)

View File

@@ -16,18 +16,8 @@ import { useFileComponent } from "../context/file"
import { useI18n } from "../context/i18n"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { checksum } from "@opencode-ai/util/encode"
import {
createEffect,
createMemo,
createSignal,
For,
Match,
onCleanup,
Show,
Switch,
untrack,
type JSX,
} from "solid-js"
import { createEffect, createMemo, createSignal, For, Match, Show, Switch, untrack, type JSX } from "solid-js"
import { onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { type FileContent, type FileDiff } from "@opencode-ai/sdk/v2"
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
@@ -191,6 +181,15 @@ export const SessionReview = (props: SessionReviewProps) => {
highlightedFile = undefined
}
const openFileLabel = () => i18n.t("ui.sessionReview.openFile")
const selectionLabel = (range: SelectedLineRange) => {
const start = Math.min(range.start, range.end)
const end = Math.max(range.start, range.end)
if (start === end) return i18n.t("ui.sessionReview.selection.line", { line: start })
return i18n.t("ui.sessionReview.selection.lines", { start, end })
}
const focusSearch = () => {
if (!hasDiffs()) return
setSearchOpen(true)
@@ -475,7 +474,8 @@ export const SessionReview = (props: SessionReviewProps) => {
const wrapper = anchors.get(focus.file)
const anchor = wrapper?.querySelector(`[data-comment-id="${focus.id}"]`)
const ready = anchor instanceof HTMLElement
const ready =
anchor instanceof HTMLElement && anchor.style.pointerEvents !== "none" && anchor.style.opacity !== "0"
const target = ready ? anchor : wrapper
if (!target) {
@@ -751,11 +751,11 @@ export const SessionReview = (props: SessionReviewProps) => {
</Show>
<span data-slot="session-review-filename">{getFilename(file)}</span>
<Show when={props.onViewFile}>
<Tooltip value="Open file" placement="top" gutter={4}>
<Tooltip value={openFileLabel()} placement="top" gutter={4}>
<button
data-slot="session-review-view-button"
type="button"
aria-label="Open file"
aria-label={openFileLabel()}
onClick={(e) => {
e.stopPropagation()
props.onViewFile?.(file)

View File

@@ -3,6 +3,9 @@ export const dict = {
"ui.sessionReview.title.lastTurn": "تغييرات آخر دور",
"ui.sessionReview.diffStyle.unified": "موحد",
"ui.sessionReview.diffStyle.split": "منقسم",
"ui.sessionReview.openFile": "فتح ملف",
"ui.sessionReview.selection.line": "سطر {{line}}",
"ui.sessionReview.selection.lines": "الأسطر {{start}}-{{end}}",
"ui.sessionReview.expandAll": "توسيع الكل",
"ui.sessionReview.collapseAll": "طي الكل",
"ui.sessionReview.change.added": "مضاف",
@@ -53,6 +56,13 @@ export const dict = {
"ui.sessionTurn.status.gatheringThoughts": "جمع الأفكار",
"ui.sessionTurn.status.consideringNextSteps": "النظر في الخطوات التالية",
"ui.messagePart.questions.dismissed": "تم رفض الأسئلة",
"ui.messagePart.context.read.one": "{{count}} قراءة",
"ui.messagePart.context.read.other": "{{count}} قراءات",
"ui.messagePart.context.search.one": "{{count}} بحث",
"ui.messagePart.context.search.other": "{{count}} عمليات بحث",
"ui.messagePart.context.list.one": "{{count}} قائمة",
"ui.messagePart.context.list.other": "{{count}} قوائم",
"ui.messagePart.diagnostic.error": "خطأ",
"ui.messagePart.title.edit": "تحرير",
"ui.messagePart.title.write": "كتابة",
@@ -72,6 +82,7 @@ export const dict = {
"ui.textField.copied": "تم النسخ",
"ui.imagePreview.alt": "معاينة الصورة",
"ui.scrollView.ariaLabel": "محتوى قابل للتمرير",
"ui.tool.read": "قراءة",
"ui.tool.loaded": "تم التحميل",

View File

@@ -3,6 +3,9 @@ export const dict = {
"ui.sessionReview.title.lastTurn": "Alterações do último turno",
"ui.sessionReview.diffStyle.unified": "Unificado",
"ui.sessionReview.diffStyle.split": "Dividido",
"ui.sessionReview.openFile": "Abrir arquivo",
"ui.sessionReview.selection.line": "linha {{line}}",
"ui.sessionReview.selection.lines": "linhas {{start}}-{{end}}",
"ui.sessionReview.expandAll": "Expandir tudo",
"ui.sessionReview.collapseAll": "Recolher tudo",
"ui.sessionReview.change.added": "Adicionado",
@@ -53,6 +56,13 @@ export const dict = {
"ui.sessionTurn.status.gatheringThoughts": "Organizando pensamentos",
"ui.sessionTurn.status.consideringNextSteps": "Considerando próximos passos",
"ui.messagePart.questions.dismissed": "Perguntas descartadas",
"ui.messagePart.context.read.one": "{{count}} leitura",
"ui.messagePart.context.read.other": "{{count}} leituras",
"ui.messagePart.context.search.one": "{{count}} pesquisa",
"ui.messagePart.context.search.other": "{{count}} pesquisas",
"ui.messagePart.context.list.one": "{{count}} lista",
"ui.messagePart.context.list.other": "{{count}} listas",
"ui.messagePart.diagnostic.error": "Erro",
"ui.messagePart.title.edit": "Editar",
"ui.messagePart.title.write": "Escrever",
@@ -72,6 +82,7 @@ export const dict = {
"ui.textField.copied": "Copiado",
"ui.imagePreview.alt": "Visualização de imagem",
"ui.scrollView.ariaLabel": "conteúdo rolável",
"ui.tool.read": "Ler",
"ui.tool.loaded": "Carregado",

View File

@@ -7,6 +7,9 @@ export const dict = {
"ui.sessionReview.title.lastTurn": "Promjene u posljednjem potezu",
"ui.sessionReview.diffStyle.unified": "Ujedinjeno",
"ui.sessionReview.diffStyle.split": "Podijeljeno",
"ui.sessionReview.openFile": "Otvori fajl",
"ui.sessionReview.selection.line": "linija {{line}}",
"ui.sessionReview.selection.lines": "linije {{start}}-{{end}}",
"ui.sessionReview.expandAll": "Proširi sve",
"ui.sessionReview.collapseAll": "Sažmi sve",
"ui.sessionReview.change.added": "Dodano",
@@ -57,6 +60,13 @@ export const dict = {
"ui.sessionTurn.status.gatheringThoughts": "Sređivanje misli",
"ui.sessionTurn.status.consideringNextSteps": "Razmatranje sljedećih koraka",
"ui.messagePart.questions.dismissed": "Pitanja odbačena",
"ui.messagePart.context.read.one": "{{count}} čitanje",
"ui.messagePart.context.read.other": "{{count}} čitanja",
"ui.messagePart.context.search.one": "{{count}} pretraga",
"ui.messagePart.context.search.other": "{{count}} pretrage",
"ui.messagePart.context.list.one": "{{count}} lista",
"ui.messagePart.context.list.other": "{{count}} liste",
"ui.messagePart.diagnostic.error": "Greška",
"ui.messagePart.title.edit": "Uredi",
"ui.messagePart.title.write": "Napiši",
@@ -76,6 +86,7 @@ export const dict = {
"ui.textField.copied": "Kopirano",
"ui.imagePreview.alt": "Pregled slike",
"ui.scrollView.ariaLabel": "sadržaj za pomjeranje",
"ui.tool.read": "Čitanje",
"ui.tool.loaded": "Učitano",

Some files were not shown because too many files have changed in this diff Show More