Compare commits

...

124 Commits

Author SHA1 Message Date
Hona e80e60d3f6 feat(i18n): translate current V2 copy 2026-08-23 17:15:52 +00:00
Hona 8f4520b570 fix(i18n): localize current timeline notices 2026-08-23 17:11:49 +00:00
Hona 545afd34a5 feat(i18n): complete Turkish locale coverage 2026-08-23 17:04:05 +00:00
Hona 458b89d38c feat(i18n): complete Asian locale coverage 2026-08-23 17:04:05 +00:00
Hona 896a197d00 fix(i18n): localize retained navigation labels 2026-08-23 17:04:05 +00:00
Hona d0278b79e7 style(i18n): format translated dictionaries 2026-08-23 17:04:05 +00:00
Hona 7f4b8911c5 feat(i18n): translate new V2 app copy 2026-08-23 17:04:05 +00:00
Hona f619fae8b8 refactor(i18n): keep locale logic in language layer 2026-08-23 17:04:05 +00:00
Hona a5c3fab578 fix(i18n): localize dynamic app notices 2026-08-23 17:04:05 +00:00
Brendan Allan 89451c3e32 fix(app): navigate parent with escape (#44441) 2026-08-23 23:45:29 +08:00
Brendan Allan ad7d515d50 fix(session-ui): avoid replaying thinking summary animation (#44440) 2026-08-23 23:43:17 +08:00
Brendan Allan 8e71c8425f fix(app): hide empty terminal cursor (#44435) 2026-08-23 15:34:40 +00:00
Brendan Allan da4ab56801 fix(session-ui): hide empty file diffs (#44425) 2026-08-23 23:15:47 +08:00
Brendan Allan dd780ca882 fix(app): hand off optimistic attachments (#44411) 2026-08-23 22:59:51 +08:00
Brendan Allan fb9c9a2cbd fix(desktop): restore session completion alerts (#44412) 2026-08-23 22:35:06 +08:00
Brendan Allan 4c786f0ff8 fix(app): preserve subagent tab lineage (#44404) 2026-08-23 22:18:39 +08:00
Brendan Allan 1def4aa35a fix(app): avoid replaying streamed markdown fade (#44396) 2026-08-23 21:10:43 +08:00
opencode-agent[bot] de11122c96 feat(app): search sessions by ID (#44387)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-08-23 20:17:57 +08:00
opencode-agent[bot] 62e3b6ad78 chore: update nix node_modules hashes 2026-08-23 11:49:19 +00:00
Brendan Allan 2c99b549db fix(app): redesign model selection dialog (#44383) 2026-08-23 19:34:14 +08:00
Luke Parker d7c3a95368 feat(app): refine expanded shell output (#44368) 2026-08-23 11:31:40 +00:00
opencode-agent[bot] 3b5b5aee0c chore: update nix node_modules hashes 2026-08-23 11:16:11 +00:00
Brendan Allan 8cbfac3184 fix(desktop): smooth startup hydration (#44377) 2026-08-23 19:07:10 +08:00
opencode-agent[bot] e765349865 feat(session-ui): render streaming markdown with Solid (#44354)
Co-authored-by: Brendonovich <Brendonovich@users.noreply.github.com>
2026-08-23 19:00:46 +08:00
opencode-agent[bot] 750f3384ff fix(app): defer service worker takeover (#44374)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-08-23 18:52:45 +08:00
Simon Klee 1cf61593b5 ci: fix node build (#44349) 2026-08-23 11:26:26 +02:00
opencode-agent[bot] 708e4d8867 fix(app): refine mobile error page (#44353) 2026-08-23 09:23:10 +00:00
Luke Parker 0a5349c51c fix(session-ui): restore compaction summaries (#44311) 2026-08-23 18:57:06 +10:00
opencode-agent[bot] 3fd93b659b chore: update nix node_modules hashes 2026-08-23 08:28:38 +00:00
Simon Klee 17a445de3e tui: update OpenTUI to 0.5.7 (#44345) 2026-08-23 10:15:17 +02:00
Brendan Allan 68e3888bc3 fix(app): preserve session header while loading (#44346) 2026-08-23 16:09:13 +08:00
Brendan Allan 27a53969d6 fix(app): stabilize initial timeline rendering (#44333) 2026-08-23 16:06:50 +08:00
Brendan Allan 1e3d3fcaca fix(app): complete leading paginated turns (#44322) 2026-08-23 15:27:33 +08:00
opencode-agent[bot] 7420903859 chore: update nix node_modules hashes 2026-08-23 07:25:41 +00:00
Simon Klee 853d3534e5 cli: share app archive across Node builds (#44331) 2026-08-23 09:11:54 +02:00
Brendan Allan 6cd3da1d4c fix(app): preserve scroll across history prepends (#44317) 2026-08-23 15:00:29 +08:00
Brendan Allan 9692bcbc21 fix(session-ui): align status tool typography (#44330) 2026-08-23 14:55:53 +08:00
Brendan Allan 79d5359d11 fix(app): align background hint spacing (#44329) 2026-08-23 14:50:32 +08:00
Brendan Allan b9ca77c5ff fix(app): suspend composer until restored (#44320) 2026-08-23 14:46:28 +08:00
Brendan Allan 7a72e51710 fix(app): reuse global model state (#44319) 2026-08-23 14:26:53 +08:00
Brendan Allan 0fda7d1b33 fix(session-ui): reduce tool error spacing (#44318) 2026-08-23 14:25:48 +08:00
Brendan Allan 6ba707d305 fix(app): synchronize composer model controls (#44316) 2026-08-23 14:25:27 +08:00
Brendan Allan d144c81d17 fix(app): preserve session shell while loading (#44315) 2026-08-23 14:18:38 +08:00
Brendan Allan b8fb894ec7 fix(server): compress responses with correct content types (#44321) 2026-08-23 06:18:20 +00:00
Brendan Allan b7167aaab0 feat(app): add project icon setting (#44323) 2026-08-23 06:18:15 +00:00
Luke Parker ad59ccc2e2 fix(ui): standardize safe text metrics (#44106) 2026-08-23 13:47:17 +10:00
Luke Parker a82318019f fix(app): skip service worker in development (#44302) 2026-08-23 13:35:05 +10:00
Luke Parker e9d58abd99 test(app): stabilize file expansion anchor (#44293) 2026-08-23 13:05:31 +10:00
Brendan Allan 1ce7e90d3e fix(app): register service worker after load (#44294) 2026-08-23 10:50:56 +08:00
Aiden Cline 92658e4389 fix(ai): preserve raw provider error payload on responses streams (#44271) 2026-08-22 20:59:59 -05:00
Dax 358a53cb1f fix(core): skip models.dev refresh event when the catalog is unchanged (#44282) 2026-08-22 21:03:02 -04:00
Dax Raad 54e2eef182 ci: use bun 1.4.0 instead of canary for publish builds 2026-08-22 20:44:26 -04:00
Dax 282b644cde fix(core): expire locations from session activity (#44275) 2026-08-22 20:21:37 -04:00
opencode-agent[bot] 4fee1bcf3c fix(tui): preserve rollback-compatible tab state (#44277) 2026-08-23 00:16:03 +00:00
opencode-agent[bot] da850f18da chore: update nix node_modules hashes 2026-08-23 00:05:17 +00:00
Dax 9a716609c6 fix(www): redirect root to docs (#44276) 2026-08-22 20:01:18 -04:00
Dax 917051a881 feat(www): rebuild site with Astro (#44274) 2026-08-22 19:49:47 -04:00
opencode-agent[bot] 55f490b898 fix(tui): flatten autocomplete descriptions (#44261) 2026-08-23 05:15:35 +05:30
Aiden Cline b190b778d4 fix(tui): avoid premature environment sync (#44270) 2026-08-22 18:39:04 -05:00
opencode-agent[bot] 95235df4c8 refactor(ai): accept nullable response phases (#44235)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
2026-08-22 17:30:31 -05:00
opencode-agent[bot] c3f21fb5fb fix(ai): normalize Responses file inputs (#44234)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-08-22 16:45:18 -05:00
Dax Raad f282a22d98 feat(www): bold doc headings and header nav links 2026-08-22 16:48:52 -04:00
Dax Raad a859d48b08 fix(www): use OpenCode favicon instead of blume placeholder 2026-08-22 16:48:51 -04:00
Dax Raad 1f60ad6e2f feat(www): use OpenTUI Mono font for docs 2026-08-22 16:37:04 -04:00
Brendan Allan bdec7a4f87 fix(app): lighten background move hint (#44217) 2026-08-23 01:25:02 +08:00
opencode-agent[bot] d35f81ef92 chore: update nix node_modules hashes 2026-08-22 17:03:24 +00:00
Brendan Allan d0d0ada4a9 feat(app): cache mobile web startup assets (#44205) 2026-08-22 16:46:20 +00:00
opencode-agent[bot] beb6216343 fix(ui): constrain focus trap to top dialog (#44188)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-08-22 23:37:37 +08:00
Brendan Allan d9c9c50809 fix(app): pin background hint to timeline spacer (#44187) 2026-08-22 23:31:30 +08:00
opencode-agent[bot] a7106ae023 fix(app): clear terminal line with command delete (#44183)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-08-22 14:56:14 +00:00
Brendan Allan 2ae3b82c24 feat(app): add bottom terminal panel (#44175) 2026-08-22 22:52:26 +08:00
opencode-agent[bot] 784042202e chore: update nix node_modules hashes 2026-08-22 14:25:38 +00:00
opencode-agent[bot] 320da4256d fix(app): preserve home during server switch (#44178)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-08-22 22:22:55 +08:00
Brendan Allan 2bbaeb0d4b fix(app): simplify timeline anchoring (#44147) 2026-08-22 14:09:00 +00:00
Brendan Allan ada4f1bee6 feat(app): merge repeated file edits (#44107) 2026-08-22 21:49:09 +08:00
opencode-agent[bot] 75efc9d833 fix(tui): seed moved worktree location (#44143)
Co-authored-by: jlongster <17031+jlongster@users.noreply.github.com>
2026-08-22 09:07:27 -04:00
Brendan Allan 93e1f383dd fix(app): allow selecting diff text (#44140) 2026-08-22 20:34:47 +08:00
opencode-agent[bot] e4924ebf64 feat(app): add copy project ID command (#44126)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-08-22 19:57:47 +08:00
opencode-agent[bot] 992446e85d feat(app): add copy session ID command (#44125)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-08-22 19:52:04 +08:00
Brendan Allan b94ba27f00 feat(app): fade the background move hint (#44109) 2026-08-22 19:50:27 +08:00
Brendan Allan 8a2966fdbe fix(app): avoid duplicate retry errors (#44124) 2026-08-22 19:49:22 +08:00
Brendan Allan 4aca1be945 fix(app): prevent duplicate web mounts (#44119) 2026-08-22 19:38:25 +08:00
Luke Parker 100f23ca99 feat(app): show writing command state (#44095) 2026-08-22 20:21:33 +10:00
Brendan Allan 8f54597ef2 fix(app): collapse failed patch cards (#44105) 2026-08-22 18:05:23 +08:00
OpeOginni 85f32fa0da fix(app): load workspace sessions by directory (#44027)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-08-22 07:50:08 +00:00
Luke Parker 8062b5455a feat(app): align moved location notice (#44085) 2026-08-22 17:30:42 +10:00
Luke Parker 4566395d42 feat(app): show subagent delegation state (#44078) 2026-08-22 17:03:32 +10:00
opencode-agent[bot] 3c70f6df28 fix(app): enforce single titlebar action owner (#44075)
Co-authored-by: Hona <10430890+Hona@users.noreply.github.com>
2026-08-22 17:03:24 +10:00
Brendan Allan 97536add75 fix(app): preserve bottom scroll anchoring (#44077) 2026-08-22 14:53:01 +08:00
Brendan Allan 6258c1943a feat(app): group adjacent patch calls (#43901) 2026-08-22 06:51:53 +00:00
Luke Parker 0d2c865c93 feat(app): move background work to session summary (#44068) 2026-08-22 16:31:53 +10:00
Luke Parker 7032a096bf fix(desktop): configure app before instance lock (#44067) 2026-08-22 16:09:13 +10:00
Luke Parker 80ef4f454f feat(desktop): align loaded resource calls with figma (#44060) 2026-08-22 15:17:11 +10:00
Luke Parker aa2dd5040f fix(desktop): omit shared rail from framed disclosures (#44061) 2026-08-22 15:17:09 +10:00
Luke Parker 9b17449e88 fix(desktop): match instructions notice typography to figma (#44059) 2026-08-22 15:06:13 +10:00
Luke Parker 346689fb43 fix(storybook): use production styles and remove synthetic tool stories (#44058) 2026-08-22 15:05:51 +10:00
Luke Parker 87d2488c78 feat(desktop): apply left border rail styling to all tool disclosures (#44052) 2026-08-22 14:23:18 +10:00
Aiden Cline a1d0f43531 fix(ai): preserve unique Gemini function call IDs (#44051)
Signed-off-by: Major Hayden <major@mhtx.net>
Co-authored-by: Major Hayden <major@mhtx.net>
2026-08-21 23:12:04 -05:00
Luke Parker 030b2d9543 feat(desktop): align instructions updated notice presentation with figma (#44050) 2026-08-22 13:49:24 +10:00
Luke Parker e7177a8764 fix(desktop): align web search results presentation with figma (#44039) 2026-08-22 13:22:22 +10:00
opencode-agent[bot] a84b1c15ce fix(app): wait for session route id (#44048)
Co-authored-by: Hona <10430890+Hona@users.noreply.github.com>
2026-08-22 13:22:10 +10:00
opencode-agent[bot] d3eee25ee2 fix(session-ui): scope context row keys (#44043)
Co-authored-by: Hona <10430890+Hona@users.noreply.github.com>
2026-08-22 02:57:53 +00:00
Luke Parker b4fabf5984 fix(desktop): align webfetch link styling with figma (#44037) 2026-08-22 02:29:03 +00:00
Kit Langton e33d688428 fix(codegen): stabilize generated contract names (#44000) 2026-08-22 00:16:27 +00:00
Kit Langton f69f78ec6b refactor(core): inline portable shell scanner (#44026) 2026-08-22 00:06:03 +00:00
opencode-agent[bot] e2758b9945 chore: update nix node_modules hashes 2026-08-21 23:53:08 +00:00
Kit Langton 7c6ecaaca8 fix(core): inherit fork instruction entries (#44004) 2026-08-21 19:46:49 -04:00
Kit Langton c29a7c152d feat(session): add viewed state (#42811) 2026-08-21 19:34:35 -04:00
Kit Langton fa4c5b26dc fix(core): stabilize forked message IDs (#44011) 2026-08-21 19:28:51 -04:00
Kit Langton fb703ede73 fix(core): transfer only settled history (#44008) 2026-08-21 19:28:04 -04:00
Kit Langton 2937f0e635 fix(core): omit running shells from forks (#44001) 2026-08-21 19:27:13 -04:00
Kit Langton 3694149135 fix(core): resolve device verification URL (#44021) 2026-08-21 23:26:40 +00:00
Kit Langton c4eeefe0f1 refactor: reuse undefined effect (#44010) 2026-08-21 19:17:29 -04:00
Kit Langton 667c274c7f fix(tui): contain MCP sidebar errors (#44003) 2026-08-21 23:00:06 +00:00
Kit Langton 2c8e2a2b28 fix(core): retry recreated locations (#44005) 2026-08-21 22:50:38 +00:00
opencode-agent[bot] 9e9eac81e3 chore: update nix node_modules hashes 2026-08-21 21:55:24 +00:00
Kit Langton 59f0e3a378 fix(core): omit active assistants from forks (#43994) 2026-08-21 21:52:36 +00:00
Dax Raad 656c4cc7fa fix(sdk): declare protocol test dependency 2026-08-21 17:38:51 -04:00
Dax Raad f28c10aa4d feat(sdk): prepare v2 packages for publishing 2026-08-21 17:28:34 -04:00
opencode-agent[bot] 97ccafce3b fix(core): honor wildcard gitignore rules in FFF (#43988)
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-08-21 16:14:29 -05:00
Kit Langton 1864bc4161 fix(tui): preserve model label during location boot (#43974) 2026-08-21 17:06:43 -04:00
Aiden Cline 87ef814190 fix(ai): preserve thought signatures on visible text parts (#43984) 2026-08-21 15:28:43 -05:00
Kit Langton e945ddf80e chore: enforce effect simplifications (#43979) 2026-08-21 19:48:27 +00:00
Kit Langton fa1b4ef7ec fix(tui): scope prompt history by session (#43977) 2026-08-21 19:46:10 +00:00
625 changed files with 37345 additions and 24237 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@opencode-ai/core": patch
---
Fix OpenCode Console device authorization URLs when the server returns an origin-rooted verification path.
-1
View File
@@ -27,7 +27,6 @@ jobs:
working-directory: packages/www
run: bun run build
env:
BLUME_ENV: ${{ github.ref_name == 'v2' && 'production' || 'dev' }}
CLOUDFLARE_ENV: ${{ github.ref_name == 'v2' && 'production' || 'dev' }}
- name: Deploy
+37 -6
View File
@@ -91,7 +91,7 @@ jobs:
- uses: ./.github/actions/setup-bun
with:
bun-version: canary # Bun 1.4 until its stable release is published
bun-version: 1.4.0
- name: Setup git committer
id: committer
@@ -113,7 +113,7 @@ jobs:
id: build
run: ./packages/cli/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
env:
BUN_COMPILE_RELEASE: canary
BUN_COMPILE_RELEASE: bun-v1.4.0
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
GH_REPO: ${{ needs.version.outputs.repo }}
@@ -195,9 +195,33 @@ jobs:
path: packages/cli/dist/cli-*
if-no-files-found: error
build-node-cli:
build-node-app-archive:
needs: version
if: github.repository == 'anomalyco/opencode' && false # Temporarily disabled
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 30
if: github.repository == 'anomalyco/opencode'
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ./.github/actions/setup-bun
- name: Build app archive
run: bun packages/cli/script/build-node.ts --app-archive-only --app-archive=.cache/app-archive.bin --skip-install
env:
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: opencode-node-app-archive
path: packages/cli/.cache/app-archive.bin
if-no-files-found: error
build-node-cli:
needs:
- version
- build-node-app-archive
if: github.repository == 'anomalyco/opencode'
strategy:
fail-fast: false
matrix:
@@ -210,6 +234,7 @@ jobs:
host: macos-26
- target: windows-arm64
host: blacksmith-4vcpu-windows-2025
bun_install_flags: --cpu=*
- target: windows-x64
host: blacksmith-4vcpu-windows-2025
runs-on: ${{ matrix.settings.host }}
@@ -221,14 +246,19 @@ jobs:
- uses: ./.github/actions/setup-bun
with:
install-flags: --os=* --cpu=*
install-flags: ${{ matrix.settings.bun_install_flags }}
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "26.4.0"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: opencode-node-app-archive
path: packages/cli/.cache
- name: Build
run: bun packages/cli/script/build-node.ts --target=${{ matrix.settings.target }} --skip-install --outdir=dist/node
run: bun packages/cli/script/build-node.ts --target=${{ matrix.settings.target }} --skip-install --outdir=dist/node --app-archive=.cache/app-archive.bin
env:
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
@@ -543,6 +573,7 @@ jobs:
- version
- build-cli
- sign-cli-macos
- build-node-app-archive
- build-node-cli
- sign-cli-windows
- build-electron
+8
View File
@@ -50,6 +50,14 @@ jobs:
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Test Effect simplification rules
if: runner.os == 'Linux'
run: bun run test:effect-simplification-rules
- name: Check Effect simplifications
if: runner.os == 'Linux'
run: bun run lint:effect-simplifications
- name: Configure git identity
run: |
git config --global user.email "bot@opencode.ai"
File diff suppressed because it is too large Load Diff
+1 -17
View File
@@ -1,19 +1,3 @@
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [
[
"./plugins/tui-smoke.tsx",
{
"enabled": false,
"label": "workspace",
"keybinds": {
"smoke_modal": "ctrl+alt+m",
"smoke_screen": "ctrl+alt+o",
"smoke_screen_home": "escape,ctrl+shift+h",
"smoke_screen_modal": "ctrl+alt+m",
"smoke_dialog_close": "escape,q"
}
}
]
]
"$schema": "https://opencode.ai/tui.json"
}
+1 -1
View File
@@ -1,5 +1,5 @@
- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit generated client files directly.
- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk` composes Client, Core, and Server.
- Current implementation changes belong in `packages/core`, `packages/cli`, `packages/server`, `packages/protocol`, `packages/schema`, and related generated client surfaces when required.
- The default branch in this repo is `v2`.
- Base all new branches and worktrees on `v2`, or `origin/v2` when the local `v2` ref is unavailable. Do not base them on `dev`.
+913 -2414
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-8pRvkbUX2aZhFTFtFuUM6mPqZZhfC4mFd1+BXVMzEJk=",
"aarch64-linux": "sha256-df25TWdjjLKeLZJEfrDpgVaV8ZAZhHWPoV2IPIQ4U2w=",
"aarch64-darwin": "sha256-VjbOx7Zi9eTiPxqpKN3+EQWweBfJHf7y36sGSN1peg0=",
"x86_64-darwin": "sha256-q7nW4AR2OnepnDcPDtYECgcsXI+JRHOCWhPsAX8t7q0="
"x86_64-linux": "sha256-P8AZ2THuXiET4hiSYTwGGdTgbm2l3XGZGJxQwg6lusE=",
"aarch64-linux": "sha256-73LxDeDPQspY4Bn/gbGU43j/k7gBZ5ywMMpUjDl3elg=",
"aarch64-darwin": "sha256-sc9YyTAFlbWxlFb9vwFYLs6IYd3SRoju9mQufzeQdd8=",
"x86_64-darwin": "sha256-nnk637DELjzGB3t9aLDYbHz5h3Dd3nvWcWGdaQk8yPM="
}
}
+10 -9
View File
@@ -18,13 +18,16 @@
"bench:devex": "bun run --cwd packages/app test:bench:devex",
"lint": "oxlint",
"lint:effect-patterns": "ast-grep scan -c script/ast-grep/sgconfig.yml packages/util/src packages/core/src packages/server/src packages/protocol/src packages/cli/src",
"lint:effect-simplifications": "ast-grep scan -c script/ast-grep/effect-simplifications/sgconfig.yml --off=unused-suppression packages",
"test:lint-rules": "ast-grep test -c script/ast-grep/sgconfig.yml",
"test:effect-simplification-rules": "ast-grep test -c script/ast-grep/effect-simplifications/sgconfig.yml",
"typecheck": "bun turbo typecheck --concurrency=3",
"typecheck:profile": "bun script/profile-typecheck.ts",
"typecheck:profile:packages": "bun script/profile-typecheck-packages.ts",
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
"postinstall": "bun run --cwd packages/core fix-node-pty",
"prepare": "husky",
"reserve-packages": "bun script/reserve-package-names.ts",
"random": "echo 'Random script'",
"sso": "aws sso login --sso-session=opencode --no-browser",
"test": "echo 'do not run tests from root' && exit 1"
@@ -33,8 +36,7 @@
"packages": [
"packages/*",
"packages/console/*",
"packages/stats/*",
"packages/slack"
"packages/stats/*"
],
"catalog": {
"@effect/opentelemetry": "4.0.0-rc.110",
@@ -47,10 +49,10 @@
"@octokit/rest": "22.0.0",
"@hono/standard-validator": "0.2.0",
"@hono/zod-validator": "0.4.2",
"@opentui/core": "0.5.6",
"@opentui/keymap": "0.5.6",
"@opentui/solid": "0.5.6",
"@tanstack/solid-virtual": "3.13.32",
"@opentui/core": "0.5.7",
"@opentui/keymap": "0.5.7",
"@opentui/solid": "0.5.7",
"@tanstack/solid-virtual": "3.13.37",
"@shikijs/stream": "4.2.0",
"@standard-schema/spec": "1.1.0",
"ulid": "3.0.1",
@@ -127,7 +129,6 @@
"@aws-sdk/client-s3": "3.933.0",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "1.18.5",
"heap-snapshot-toolkit": "1.1.3",
"typescript": "catalog:"
},
@@ -173,7 +174,7 @@
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch",
"@ff-labs/fff-bun@0.10.1": "patches/@ff-labs%2Ffff-bun@0.10.1.patch"
"@tanstack/virtual-core@3.17.8": "patches/@tanstack%2Fvirtual-core@3.17.8.patch",
"@ff-labs/fff-bun@0.10.5": "patches/@ff-labs%2Ffff-bun@0.10.5.patch"
}
}
+1 -1
View File
@@ -370,7 +370,7 @@ const responseError = Effect.fn("RecordingEnv.responseError")(function* (
response: HttpClientResponse.HttpClientResponse,
) {
if (response.status >= 200 && response.status < 300) return undefined
const body = yield* response.text.pipe(Effect.catch(() => Effect.succeed("")))
const body = yield* response.text.pipe(Effect.orElseSucceed(() => ""))
return `${response.status}${body ? `: ${body.slice(0, 180)}` : ""}`
})
+32 -18
View File
@@ -212,11 +212,11 @@ type GeminiEvent = Schema.Schema.Type<typeof GeminiEvent>
interface ParserState {
readonly finishReason?: string
readonly hasToolCalls: boolean
readonly nextToolCallId: number
readonly promptFeedback?: GeminiPromptFeedback
readonly usage?: Usage
readonly lifecycle: Lifecycle.State
readonly reasoningSignature?: string
readonly textSignature?: string
}
// =============================================================================
@@ -328,7 +328,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request: LLMR
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
return yield* ProviderShared.unsupportedContent("Gemini", "assistant", ["text", "reasoning", "tool-call"])
if (part.type === "text") {
parts.push({ text: part.text })
parts.push({ text: part.text, thoughtSignature: thoughtSignature(part.providerMetadata) })
continue
}
if (part.type === "reasoning") {
@@ -540,14 +540,16 @@ const finish = (state: ParserState): ReadonlyArray<LLMEvent> => {
if (finishReason === undefined && state.usage === undefined) return []
const events: LLMEvent[] = []
const lifecycle = state.reasoningSignature
? Lifecycle.reasoningEnd(
state.lifecycle,
events,
"reasoning-0",
googleMetadata({ thoughtSignature: state.reasoningSignature }),
)
: state.lifecycle
let lifecycle = state.lifecycle
if (state.reasoningSignature !== undefined)
lifecycle = Lifecycle.reasoningEnd(
lifecycle,
events,
"reasoning-0",
googleMetadata({ thoughtSignature: state.reasoningSignature }),
)
if (state.textSignature !== undefined)
lifecycle = Lifecycle.textEnd(lifecycle, events, "text-0", googleMetadata({ thoughtSignature: state.textSignature }))
Lifecycle.finish(lifecycle, events, {
reason: {
normalized:
@@ -577,12 +579,15 @@ const step = (state: ParserState, event: GeminiEvent) => {
const events: LLMEvent[] = []
let hasToolCalls = nextState.hasToolCalls
let lifecycle = nextState.lifecycle
let nextToolCallId = nextState.nextToolCallId
let reasoningSignature = nextState.reasoningSignature
let textSignature = nextState.textSignature
for (const part of candidate.content.parts) {
if ("thoughtSignature" in part && part.thoughtSignature && "thought" in part && part.thought)
reasoningSignature = part.thoughtSignature
const signature = "thoughtSignature" in part && part.thoughtSignature ? part.thoughtSignature : undefined
// Gemini attaches replay signatures to thought parts, visible text, or function calls;
// each block kind must retain the signature attached to its own parts.
if (signature !== undefined && "thought" in part && part.thought) reasoningSignature = signature
else if (signature !== undefined && "text" in part) textSignature = signature
if ("text" in part && part.text.length > 0) {
if (part.thought) {
lifecycle = Lifecycle.reasoningDelta(
@@ -590,7 +595,7 @@ const step = (state: ParserState, event: GeminiEvent) => {
events,
"reasoning-0",
part.text,
part.thoughtSignature ? googleMetadata({ thoughtSignature: part.thoughtSignature }) : undefined,
signature ? googleMetadata({ thoughtSignature: signature }) : undefined,
)
continue
}
@@ -600,13 +605,22 @@ const step = (state: ParserState, event: GeminiEvent) => {
"reasoning-0",
reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined,
)
lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", part.text)
lifecycle = Lifecycle.textDelta(
lifecycle,
events,
"text-0",
part.text,
textSignature ? googleMetadata({ thoughtSignature: textSignature }) : undefined,
)
textSignature = undefined
continue
}
if ("functionCall" in part) {
const input = part.functionCall.args === undefined ? {} : part.functionCall.args
const id = `tool_${nextToolCallId++}`
// Gemini 2.0+ and Vertex supply a unique function call ID on the part; when omitted (e.g. Gemini 1.5),
// generate a globally unique ID rather than a per-request counter to prevent cross-request collisions in downstream registries.
const id = part.functionCall.id ?? `tool_${crypto.randomUUID().replaceAll("-", "")}`
const metadata = {
...(part.functionCall.id === undefined ? {} : { functionCallId: part.functionCall.id }),
...(part.thoughtSignature === undefined ? {} : { thoughtSignature: part.thoughtSignature }),
@@ -635,8 +649,8 @@ const step = (state: ParserState, event: GeminiEvent) => {
...nextState,
hasToolCalls,
lifecycle,
nextToolCallId,
reasoningSignature,
textSignature,
finishReason: candidate.finishReason ?? nextState.finishReason,
},
events,
@@ -658,7 +672,7 @@ export const protocol = Protocol.make({
},
stream: {
event: Protocol.jsonEvent(GeminiEvent),
initial: () => ({ hasToolCalls: false, nextToolCallId: 0, lifecycle: Lifecycle.initial() }),
initial: () => ({ hasToolCalls: false, lifecycle: Lifecycle.initial() }),
step,
onHalt: finish,
},
+1
View File
@@ -8,3 +8,4 @@ export * as OpenAICompatibleResponses from "./openai-compatible-responses.js"
export * as OpenAIResponses from "./openai-responses.js"
export * as OpenResponses from "./open-responses.js"
export * as OpenResponsesChannel from "./open-responses-channel.js"
export * as XAIResponses from "./xai-responses.js"
@@ -10,6 +10,7 @@ import {
} from "../route/transport/index.js"
import * as ProviderShared from "./shared.js"
import { OpenResponses } from "./open-responses.js"
import { OpenResponsesContinuation } from "./open-responses-continuation.js"
const WebSocketResponseCreate = Schema.StructWithRest(Schema.Struct({ type: Schema.tag("response.create") }), [
Schema.Record(Schema.String, Schema.Unknown),
@@ -25,11 +26,6 @@ export interface Options {
readonly enabled?: (url: string) => boolean
readonly url?: (url: string) => string
readonly headers?: (headers: Headers.Headers) => Headers.Headers
readonly driver?: (input: {
readonly request: Readonly<Record<string, unknown>>
readonly message: string
readonly base: WebSocketChannelDriver
}) => WebSocketChannelDriver
}
export interface Prepared {
@@ -158,7 +154,13 @@ export const transport = <Body>(options: Options): Transport<Body, Prepared, str
url: yield* WebSocketTransport.toWebSocketUrl(options.url?.(parts.url) ?? parts.url),
headers,
rotateAfterMs: options.rotateAfterMs,
driver: options.driver?.({ request: create.request, message: create.message, base }) ?? base,
driver: OpenResponsesContinuation.driver({
id: options.id,
name: options.name,
request: create.request,
message: create.message,
base,
}),
}
})
: undefined
@@ -4,7 +4,7 @@ import { Effect, Option, Schema } from "effect"
import * as ProviderShared from "./shared.js"
import { OpenResponses } from "./open-responses.js"
const PROTOCOL = "openai-responses.websocket.v1"
const PROTOCOL = "open-responses.websocket.v1"
const VERSION = 1
const decodeEvent = Schema.decodeUnknownEffect(OpenResponses.protocol.stream.event)
@@ -161,4 +161,4 @@ export const driver = (input: DriverInput): WebSocketChannelDriver => {
}
}
export const OpenAIResponsesChannel = { driver } as const
export const OpenResponsesContinuation = { driver } as const
+74 -58
View File
@@ -42,8 +42,12 @@ const OpenResponsesInputImage = Schema.Struct({
const OpenResponsesInputFile = Schema.Struct({
type: Schema.tag("input_file"),
filename: Schema.String,
file_data: Schema.String,
mime_type: Schema.optional(Schema.String),
file_data: Schema.optional(Schema.String),
file_url: Schema.optional(Schema.String),
})
const OpenResponsesInputVideo = Schema.Struct({
type: Schema.tag("input_video"),
video_url: Schema.String,
})
const MediaInput = Schema.Union([OpenResponsesInputImage, OpenResponsesInputFile])
export type MediaInput = Schema.Schema.Type<typeof MediaInput>
@@ -54,9 +58,14 @@ const OpenResponsesOutputText = Schema.Struct({
text: Schema.String,
})
export const MessagePhase = Schema.Literals(["commentary", "final_answer"])
export const MessagePhase = Schema.NullOr(Schema.Literals(["commentary", "final_answer"]))
type MessagePhase = Schema.Schema.Type<typeof MessagePhase>
const messagePhase = (value: unknown): MessagePhase | undefined => {
if (value === null || value === "commentary" || value === "final_answer") return value
return undefined
}
const OpenResponsesReasoningSummaryText = Schema.Struct({
type: Schema.tag("summary_text"),
text: Schema.String,
@@ -81,6 +90,7 @@ const OpenResponsesFunctionCallOutputContent = Schema.Union([
OpenResponsesInputText,
OpenResponsesInputImage,
OpenResponsesInputFile,
OpenResponsesInputVideo,
])
const OpenResponsesFunctionCallOutput = Schema.Union([
@@ -237,6 +247,7 @@ const OpenResponsesErrorPayload = Schema.Struct({
message: optionalNull(Schema.String),
param: optionalNull(Schema.String),
})
type OpenResponsesErrorPayload = Schema.Schema.Type<typeof OpenResponsesErrorPayload>
const WebSocketErrorHeader = Schema.Union([Schema.String, Schema.Number, Schema.Boolean])
export const WebSocketErrorEvent = Schema.StructWithRest(
@@ -301,20 +312,6 @@ export const Event = Schema.StructWithRest(
)
export type Event = Schema.Schema.Type<typeof Event>
const RefusalEvent = Schema.Union([
Schema.Struct({
type: Schema.tag("response.refusal.delta"),
item_id: Schema.String,
delta: Schema.String,
}),
Schema.Struct({
type: Schema.tag("response.refusal.done"),
item_id: Schema.String,
refusal: Schema.String,
}),
])
const isRefusalEvent = Schema.is(RefusalEvent)
export interface Extension {
readonly id: string
readonly name: string
@@ -323,7 +320,6 @@ export interface Extension {
readonly media: ProviderShared.NormalizedMedia
readonly request: LLMRequest
}) => MediaInput | undefined
readonly messagePhase?: (value: unknown) => MessagePhase | null | undefined
}
const BASE: Extension = { id: ADAPTER, name: NAME }
@@ -336,7 +332,6 @@ export interface ParserState {
readonly hasFunctionCall: boolean
readonly lifecycle: Lifecycle.State
readonly messageItems: ReadonlySet<string>
readonly messagePhase: (value: unknown) => MessagePhase | null | undefined
readonly messagePhases: Readonly<Record<string, MessagePhase | null>>
readonly reasoningItems: Readonly<Record<string, ReasoningStreamItem>>
readonly store: boolean | undefined
@@ -414,26 +409,29 @@ const lowerReasoning = (part: ReasoningPart, providerMetadataKey: string): OpenR
}
}
const hostedToolItemID = (part: ToolResultPart, providerMetadataKey: string) => {
return itemID(part.providerMetadata, providerMetadataKey)
}
const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (
part: MediaPart,
request: LLMRequest,
extension: Extension,
target: "message" | "tool-result",
) {
const media = ProviderShared.normalizeMedia(part)
const extended = extension.lowerMedia?.({ part, media, request })
if (extended) return extended
const url =
typeof part.data === "string" && (part.data.startsWith("https://") || part.data.startsWith("http://"))
? part.data
: undefined
if (!media.mime.startsWith("image/")) {
if (target === "tool-result" && media.mime.startsWith("video/"))
return { type: "input_video" as const, video_url: url ?? media.dataUrl }
return {
type: "input_file" as const,
filename: part.filename ?? (media.mime === "application/pdf" ? "document.pdf" : "file"),
file_data: media.dataUrl,
...(url ? { file_url: url } : { file_data: media.base64 }),
}
}
return { type: "input_image" as const, image_url: media.dataUrl }
return { type: "input_image" as const, image_url: url ?? media.dataUrl }
})
const lowerUserContent = Effect.fnUntraced(function* (
@@ -442,10 +440,17 @@ const lowerUserContent = Effect.fnUntraced(function* (
extension: Extension,
) {
if (part.type === "text") return { type: "input_text" as const, text: part.text }
if (part.type === "media") return yield* lowerMedia(part, request, extension)
if (part.type === "media") return yield* lowerMessageMedia(part, request, extension)
return yield* ProviderShared.unsupportedContent(extension.name, "user", ["text", "media"])
})
const lowerMessageMedia = Effect.fnUntraced(function* (part: MediaPart, request: LLMRequest, extension: Extension) {
const lowered = yield* lowerMedia(part, request, extension, "message")
if (lowered.type === "input_video")
return yield* ProviderShared.invalidRequest(`${extension.name} user messages do not support input_video`)
return lowered
})
// Tool results may carry structured text, images, and files. Keep media as provider-native
// content instead of JSON-stringifying base64 into a prompt string.
const lowerToolResultContentItem = Effect.fnUntraced(function* (
@@ -458,6 +463,20 @@ const lowerToolResultContentItem = Effect.fnUntraced(function* (
{ type: "media", mediaType: item.mime, data: item.uri, filename: item.name },
request,
extension,
"tool-result",
)
})
const lowerHostedToolResultContentItem = Effect.fnUntraced(function* (
item: Content,
request: LLMRequest,
extension: Extension,
) {
if (item.type === "text") return { type: "input_text" as const, text: item.text }
return yield* lowerMessageMedia(
{ type: "media", mediaType: item.mime, data: item.uri, filename: item.name },
request,
extension,
)
})
@@ -510,7 +529,7 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
>((groups, part) => {
const metadata = part.providerMetadata?.[providerMetadataKey]
const id = itemID(part.providerMetadata, providerMetadataKey)
const phase = ProviderShared.isRecord(metadata) ? messagePhase(metadata.phase, extension) : undefined
const phase = ProviderShared.isRecord(metadata) ? messagePhase(metadata.phase) : undefined
const group = groups.at(-1)
if (group && group.id === id && group.phase === phase) group.parts.push(part)
else groups.push({ id, phase, parts: [part] })
@@ -560,17 +579,19 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
}
if (part.type === "tool-result" && part.providerExecuted === true) {
flushText()
const itemID = hostedToolItemID(part, providerMetadataKey)
if (store !== false && itemID && !hostedToolReferences.has(itemID))
input.push({ type: "item_reference", id: itemID })
const id = itemID(part.providerMetadata, providerMetadataKey)
if (store !== false && id && !hostedToolReferences.has(id))
input.push({ type: "item_reference", id })
if (store === false && part.result.type === "content") {
const content: ReadonlyArray<Content> = part.result.value
input.push({
role: "user",
content: yield* Effect.forEach(content, (item) => lowerToolResultContentItem(item, request, extension)),
content: yield* Effect.forEach(content, (item) =>
lowerHostedToolResultContentItem(item, request, extension),
),
})
}
if (itemID) hostedToolReferences.add(itemID)
if (id) hostedToolReferences.add(id)
continue
}
return yield* ProviderShared.unsupportedContent(extension.name, "assistant", [
@@ -782,18 +803,17 @@ const reasoningMetadata = (state: ParserState, item: StreamItem & { id: string }
// best-effort, not guaranteed.
const onOutputItemAdded = (state: ParserState, event: Event): StepResult => {
const item = event.item
if (item?.type === "message" && item.id)
if (item?.type === "message" && item.id) {
const phase = messagePhase(item.phase)
return [
{
...state,
messageItems: new Set([...state.messageItems, item.id]),
messagePhases: (() => {
const phase = state.messagePhase(item.phase)
return phase === undefined ? state.messagePhases : { ...state.messagePhases, [item.id]: phase }
})(),
messagePhases: phase === undefined ? state.messagePhases : { ...state.messagePhases, [item.id]: phase },
},
NO_EVENTS,
]
}
if (item && isReasoningItem(item)) {
const events: LLMEvent[] = []
return [
@@ -951,7 +971,7 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
if (!item) return [state, NO_EVENTS] satisfies StepResult
if (item.type === "message" && item.id) {
const itemPhase = state.messagePhase(item.phase)
const itemPhase = messagePhase(item.phase)
const phase = itemPhase === undefined ? state.messagePhases[item.id] : itemPhase
const events: LLMEvent[] = []
const messageItems = new Set(state.messageItems)
@@ -1059,22 +1079,26 @@ const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (
return [{ ...state, lifecycle, hasFunctionCall, tools: pending.tools }, events] satisfies StepResult
})
// Build a single human-readable message from whatever the provider supplied.
// Build the prettiest summary available from whatever the provider supplied.
// When both code and message are present, prefix the code so consumers see
// the failure mode (e.g. `rate_limit_exceeded: Slow down`) instead of just
// the bare message — production rate limits and context-length failures used
// to be indistinguishable from generic stream drops.
const providerErrorMessage = (event: Event, fallback: string): string => {
const nested = event.error ?? event.response?.error ?? undefined
// to be indistinguishable from generic stream drops. Returns undefined when
// the payload carries no usable summary.
const providerErrorMessage = (event: Event, nested: OpenResponsesErrorPayload | undefined): string | undefined => {
const message = event.message || nested?.message || undefined
const code = event.code || nested?.code || undefined
if (message && code) return `${code}: ${message}`
return message || code || fallback
return message || code
}
export const providerFailure = (id: string, event: Event, fallback: string) => {
const code = event.code || event.error?.code || event.response?.error?.code || undefined
const message = providerErrorMessage(event, fallback)
const nested = event.error ?? event.response?.error ?? undefined
const code = event.code || nested?.code || undefined
// Keep the full raw payload on the error even when the message is a summary.
const body = JSON.stringify(nested ?? event) ?? ""
const summary = providerErrorMessage(event, nested)
const message = summary ?? (body === "{}" ? fallback : body)
const status =
typeof event.status === "number"
? event.status
@@ -1084,7 +1108,8 @@ export const providerFailure = (id: string, event: Event, fallback: string) => {
return new AIError({
module: id,
method: "stream",
reason: classifyProviderFailure({ message, code, status }),
body,
reason: classifyProviderFailure({ message, code, status, rawBody: body }),
})
}
@@ -1100,21 +1125,18 @@ export const step = (state: ParserState, event: Event) => {
)
}
if (event.type === "response.refusal.delta" || event.type === "response.refusal.done") {
if (!isRefusalEvent(event)) return ProviderShared.eventError(state.id, `${event.type} is malformed`)
const value = event.type === "response.refusal.delta" ? event.delta : event.refusal
if (!event.item_id || typeof value !== "string") return ProviderShared.eventError(state.id, `${event.type} is malformed`)
return Effect.succeed(
event.type === "response.refusal.delta"
? onOutputTextDelta(state, event, event.item_id)
: onOutputTextDone(state, { ...event, text: event.refusal }, event.item_id),
: onOutputTextDone(state, { ...event, text: value }, event.item_id),
)
}
if (event.type === "response.reasoning.delta" || event.type === "response.reasoning_summary_text.delta") {
if (!event.item_id) return ProviderShared.eventError(state.id, `${event.type} is missing item_id`)
return Effect.succeed(onReasoningDelta(state, event, event.item_id))
}
if (event.type === "response.reasoning.done" || event.type === "response.reasoning_summary_text.done") {
if (!event.item_id) return ProviderShared.eventError(state.id, `${event.type} is missing item_id`)
return Effect.succeed(onReasoningDone(state, event))
}
if (event.type === "response.reasoning_summary_part.added")
return event.item_id
? Effect.succeed(onReasoningSummaryPartAdded(state, event))
@@ -1159,17 +1181,11 @@ export const initial = (request: LLMRequest, extension: Extension = BASE): Parse
tools: ToolStream.empty<string>(),
lifecycle: Lifecycle.initial(),
messageItems: new Set<string>(),
messagePhase: (value) => messagePhase(value, extension),
messagePhases: {},
reasoningItems: {},
store: OpenResponsesOptions.resolve(request).store,
})
const messagePhase = (value: unknown, extension: Extension): MessagePhase | null | undefined => {
if (value === "commentary" || value === "final_answer") return value
return extension.messagePhase?.(value)
}
export const protocol = Protocol.make({
id: ADAPTER,
body: {
+1 -1
View File
@@ -110,7 +110,7 @@ export const model = (input: ModelInput) => {
const multipartImages = yield* Effect.forEach(sourceImages, (image) => {
if (image.type === "bytes") return Effect.succeed({ data: image.data, mediaType: image.mediaType })
if (image.type === "url") return ImageInputs.decodeDataUrl(image.url, ADAPTER)
return Effect.succeed(undefined)
return Effect.undefined
})
const multipartMask =
mask === undefined
+23 -99
View File
@@ -5,14 +5,13 @@ import { Auth } from "../route/auth.js"
import { Endpoint } from "../route/endpoint.js"
import { Protocol } from "../route/protocol.js"
import { HttpTransport } from "../route/transport/index.js"
import { LLMEvent, LLMRequest, type JsonSchema, type ToolDefinition } from "../schema/index.js"
import { LLMRequest, type JsonSchema, type ToolDefinition } from "../schema/index.js"
import { OpenResponses } from "./open-responses.js"
import { optionalArray, ProviderShared } from "./shared.js"
import { Lifecycle } from "./utils/lifecycle.js"
import { OpenAIImage } from "./utils/openai-image.js"
import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js"
import { ToolSchemaProjection } from "./utils/tool-schema.js"
import { OpenResponsesChannel, type Options } from "./open-responses-channel.js"
import { OpenAIResponsesChannel } from "./openai-responses-channel.js"
import { OpenResponsesChannel } from "./open-responses-channel.js"
const ADAPTER = "openai-responses"
const NAME = "OpenAI Responses"
@@ -40,20 +39,8 @@ const OpenAIResponsesToolChoice = Schema.Union([
Schema.Struct({ type: Schema.tag("image_generation") }),
])
const OpenAIResponsesInputItem = Schema.Union([
Schema.Struct({
type: Schema.tag("message"),
id: Schema.optionalKey(Schema.String),
role: Schema.tag("assistant"),
content: Schema.Array(Schema.Struct({ type: Schema.tag("output_text"), text: Schema.String })),
phase: Schema.optionalKey(Schema.NullOr(OpenResponses.MessagePhase)),
}),
OpenResponses.InputItem,
])
const OpenAIResponsesCoreFields = {
...OpenResponses.coreFields,
input: Schema.Array(OpenAIResponsesInputItem),
tools: optionalArray(OpenAIResponsesTools),
tool_choice: Schema.optional(OpenAIResponsesToolChoice),
}
@@ -67,16 +54,6 @@ export type OpenAIResponsesBody = Schema.Schema.Type<typeof OpenAIResponsesBody>
const extension = {
id: ADAPTER,
name: NAME,
messagePhase: (value: unknown) => (value === null ? null : undefined),
lowerMedia: ({ part, media, request }) => {
if (request.model.provider !== "xai" || media.mime !== "application/pdf") return undefined
return {
type: "input_file",
filename: part.filename ?? "document.pdf",
file_data: media.base64,
mime_type: media.mime,
}
},
} satisfies OpenResponses.Extension
const nativeImageToolInput = (tool: ToolDefinition) => {
@@ -128,46 +105,7 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request:
} satisfies OpenAIResponsesBody
})
type HostedToolData = OpenResponses.StreamItem & {
readonly id: string
readonly status?: string
readonly action?: unknown
readonly queries?: unknown
readonly results?: unknown
readonly code?: string
readonly container_id?: string
readonly outputs?: unknown
readonly server_label?: string
readonly output?: unknown
readonly result?: string
readonly output_format?: "png" | "jpeg" | "webp"
readonly error?: unknown
}
const HOSTED_TOOLS = {
web_search_call: { name: "web_search", input: (item) => item.action ?? {} },
web_search_preview_call: { name: "web_search_preview", input: (item) => item.action ?? {} },
file_search_call: { name: "file_search", input: (item) => ({ queries: item.queries ?? [] }) },
code_interpreter_call: {
name: "code_interpreter",
input: (item) => ({ code: item.code, container_id: item.container_id }),
},
computer_use_call: { name: "computer_use", input: (item) => item.action ?? {} },
image_generation_call: { name: "image_generation", input: () => ({}) },
mcp_call: {
name: "mcp",
input: (item) => ({ server_label: item.server_label, name: item.name, arguments: item.arguments }),
},
local_shell_call: { name: "local_shell", input: (item) => item.action ?? {} },
} as const satisfies Record<string, { readonly name: string; readonly input: (item: HostedToolData) => unknown }>
type HostedToolType = keyof typeof HOSTED_TOOLS
type HostedToolItem = HostedToolData & { readonly type: HostedToolType }
const isHostedToolItem = (item: OpenResponses.StreamItem): item is HostedToolItem =>
item.type in HOSTED_TOOLS && typeof item.id === "string" && item.id.length > 0
const hostedToolResult = Effect.fn("OpenAIResponses.hostedToolResult")(function* (item: HostedToolItem) {
const hostedToolResult = Effect.fn("OpenAIResponses.hostedToolResult")(function* (item: ResponsesHostedTools.Item) {
const isError = item.error !== undefined && item.error !== null
if (item.type === "image_generation_call" && item.result) {
yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(
@@ -188,32 +126,22 @@ const hostedToolResult = Effect.fn("OpenAIResponses.hostedToolResult")(function*
return isError ? { type: "error" as const, value: item.error } : { type: "json" as const, value: item }
})
const onHostedToolDone = Effect.fn("OpenAIResponses.onHostedToolDone")(function* (
state: OpenResponses.ParserState,
item: HostedToolItem,
) {
const tool = HOSTED_TOOLS[item.type]
const providerMetadata = OpenResponses.providerMetadata(state, { itemId: item.id })
const events: LLMEvent[] = []
const lifecycle = Lifecycle.stepStart(state.lifecycle, events)
events.push(
LLMEvent.toolCall({
id: item.id,
name: tool.name,
input: tool.input(item),
providerExecuted: true,
providerMetadata,
}),
LLMEvent.toolResult({
id: item.id,
name: tool.name,
result: yield* hostedToolResult(item),
providerExecuted: true,
providerMetadata,
}),
)
return [{ ...state, lifecycle }, events] satisfies OpenResponses.StepResult
})
const HOSTED_TOOLS = {
web_search_call: { name: "web_search", input: (item) => item.action ?? {} },
web_search_preview_call: { name: "web_search_preview", input: (item) => item.action ?? {} },
file_search_call: { name: "file_search", input: (item) => ({ queries: item.queries ?? [] }) },
code_interpreter_call: {
name: "code_interpreter",
input: (item) => ({ code: item.code, container_id: item.container_id }),
},
computer_use_call: { name: "computer_use", input: (item) => item.action ?? {} },
image_generation_call: { name: "image_generation", input: () => ({}), result: hostedToolResult },
mcp_call: {
name: "mcp",
input: (item) => ({ server_label: item.server_label, name: item.name, arguments: item.arguments }),
},
local_shell_call: { name: "local_shell", input: (item) => item.action ?? {} },
} as const satisfies ResponsesHostedTools.Definitions
const step = (state: OpenResponses.ParserState, event: OpenResponses.Event) => {
if (event.type === "response.reasoning_text.delta" || event.type === "response.reasoning_summary.delta")
@@ -224,8 +152,8 @@ const step = (state: OpenResponses.ParserState, event: OpenResponses.Event) => {
return event.item_id
? Effect.succeed(OpenResponses.onReasoningDone(state, event))
: ProviderShared.eventError(ADAPTER, `${event.type} is missing item_id`)
if (event.type === "response.output_item.done" && event.item && isHostedToolItem(event.item))
return onHostedToolDone(state, event.item)
if (event.type === "response.output_item.done" && event.item && ResponsesHostedTools.isItem(event.item, HOSTED_TOOLS))
return ResponsesHostedTools.onDone(state, event.item, HOSTED_TOOLS)
return OpenResponses.step(state, event)
}
@@ -247,11 +175,7 @@ const endpoint = Endpoint.path<OpenAIResponsesBody>(PATH, { baseURL: DEFAULT_BAS
const auth = Auth.none
export const httpTransport = HttpTransport.sseJson.with<OpenAIResponsesBody>()
export const channelTransport = (options: Omit<Options, "driver">) =>
OpenResponsesChannel.transport<OpenAIResponsesBody>({
...options,
driver: (input) => OpenAIResponsesChannel.driver({ id: options.id, name: options.name, ...input }),
})
export const channelTransport = OpenResponsesChannel.transport<OpenAIResponsesBody>
export const transport = channelTransport({
id: ADAPTER,
name: NAME,
@@ -16,7 +16,7 @@ export const decodeDataUrl = (
url: string,
module: string,
): Effect.Effect<{ readonly mediaType: string; readonly data: Uint8Array } | undefined, AIError> => {
if (!url.startsWith("data:")) return Effect.succeed(undefined)
if (!url.startsWith("data:")) return Effect.undefined
const match = /^data:([^;,]+);base64,(.*)$/s.exec(url)
if (!match) return Effect.fail(invalid(module, "Image data URLs must contain a MIME type and base64 data"))
return Effect.fromResult(Encoding.decodeBase64(match[2])).pipe(
+8 -2
View File
@@ -21,9 +21,15 @@ export const textStart = (state: State, events: LLMEvent[], id: string, provider
return { ...stepped, text: new Set([...stepped.text, id]) }
}
export const textDelta = (state: State, events: LLMEvent[], id: string, text: string): State => {
export const textDelta = (
state: State,
events: LLMEvent[],
id: string,
text: string,
providerMetadata?: ProviderMetadata,
): State => {
const started = textStart(state, events, id)
events.push(LLMEvent.textDelta({ id, text }))
events.push(LLMEvent.textDelta({ id, text, providerMetadata }))
return started
}
@@ -0,0 +1,70 @@
import { Effect } from "effect"
import { LLMEvent, type AIError, type ToolResultPart } from "../../schema/index.js"
import { OpenResponses } from "../open-responses.js"
import { Lifecycle } from "./lifecycle.js"
export type Item = OpenResponses.StreamItem & {
readonly id: string
readonly status?: string
readonly action?: unknown
readonly queries?: unknown
readonly results?: unknown
readonly code?: string
readonly container_id?: string
readonly outputs?: unknown
readonly server_label?: string
readonly output?: unknown
readonly result?: string
readonly output_format?: "png" | "jpeg" | "webp"
readonly error?: unknown
}
export interface Definition {
readonly name: string
readonly input: (item: Item) => unknown
readonly result?: (item: Item) => Effect.Effect<ToolResultPart["result"], AIError>
}
export type Definitions = Readonly<Record<string, Definition>>
export const isItem = <Tools extends Definitions>(item: OpenResponses.StreamItem, tools: Tools): item is Item =>
item.type in tools && typeof item.id === "string" && item.id.length > 0
export const onDone: (
state: OpenResponses.ParserState,
item: Item,
tools: Definitions,
) => Effect.Effect<OpenResponses.StepResult, AIError> = Effect.fn("ResponsesHostedTools.onDone")(function* (
state,
item,
tools,
) {
const tool = tools[item.type]
if (!tool) return [state, []] satisfies OpenResponses.StepResult
const providerMetadata = OpenResponses.providerMetadata(state, { itemId: item.id })
const events: LLMEvent[] = []
const lifecycle = Lifecycle.stepStart(state.lifecycle, events)
events.push(
LLMEvent.toolCall({
id: item.id,
name: tool.name,
input: tool.input(item),
providerExecuted: true,
providerMetadata,
}),
LLMEvent.toolResult({
id: item.id,
name: tool.name,
result: tool.result
? yield* tool.result(item)
: item.error !== undefined && item.error !== null
? { type: "error", value: item.error }
: { type: "json", value: item },
providerExecuted: true,
providerMetadata,
}),
)
return [{ ...state, lifecycle }, events] satisfies OpenResponses.StepResult
})
export * as ResponsesHostedTools from "./responses-hosted-tools.js"
@@ -0,0 +1,55 @@
import { Effect } from "effect"
import { Protocol } from "../route/protocol.js"
import { OpenResponses } from "./open-responses.js"
import { ProviderShared } from "./shared.js"
import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js"
const ADAPTER = "xai-responses"
const NAME = "xAI Responses"
const extension = {
id: ADAPTER,
name: NAME,
} satisfies OpenResponses.Extension
const HOSTED_TOOLS = {
web_search_call: { name: "web_search", input: (item) => item.action ?? {} },
x_search_call: { name: "x_search", input: (item) => item.action ?? {} },
file_search_call: { name: "file_search", input: (item) => ({ queries: item.queries ?? [] }) },
code_interpreter_call: {
name: "code_interpreter",
input: (item) => ({ code: item.code, container_id: item.container_id }),
},
image_generation_call: { name: "image_generation", input: () => ({}) },
mcp_call: {
name: "mcp",
input: (item) => ({ server_label: item.server_label, name: item.name, arguments: item.arguments }),
},
} as const satisfies ResponsesHostedTools.Definitions
const step = (state: OpenResponses.ParserState, event: OpenResponses.Event) => {
if (event.type === "response.reasoning_text.delta" || event.type === "response.reasoning_summary.delta")
return event.item_id
? Effect.succeed(OpenResponses.onReasoningDelta(state, event, event.item_id))
: ProviderShared.eventError(ADAPTER, `${event.type} is missing item_id`)
if (event.type === "response.reasoning_text.done" || event.type === "response.reasoning_summary.done")
return event.item_id
? Effect.succeed(OpenResponses.onReasoningDone(state, event))
: ProviderShared.eventError(ADAPTER, `${event.type} is missing item_id`)
if (event.type === "response.output_item.done" && event.item && ResponsesHostedTools.isItem(event.item, HOSTED_TOOLS))
return ResponsesHostedTools.onDone(state, event.item, HOSTED_TOOLS)
return OpenResponses.step(state, event)
}
export const protocol = Protocol.make({
id: ADAPTER,
body: OpenResponses.protocol.body,
stream: {
event: OpenResponses.protocol.stream.event,
initial: (request) => OpenResponses.initial(request, extension),
step,
terminal: OpenResponses.terminal,
},
})
export * as XAIResponses from "./xai-responses.js"
+7 -2
View File
@@ -80,6 +80,9 @@ export interface ProviderFailure {
readonly message: string
readonly status?: number | undefined
readonly code?: string | undefined
// Raw wire payload, scanned for failure signals (codes, overflow phrases)
// that the summary message does not carry. Not shown to users.
readonly rawBody?: string | undefined
readonly retryAfterMs?: number | undefined
readonly rateLimit?: HttpRateLimitDetails | undefined
readonly http?: HttpContext | undefined
@@ -89,11 +92,13 @@ export interface ProviderFailure {
// Keep HTTP failures and provider-reported stream failures on one typed path so
// session retry policy never needs provider-specific string matching.
export function classifyProviderFailure(input: ProviderFailure): AIError["reason"] {
const body = input.http?.body ?? ""
const body = input.http?.body ?? input.rawBody ?? ""
const codes = [input.code, ...providerCodes(body), ...providerCodes(input.message)]
.filter((code): code is string => code !== undefined)
.map((code) => code.toLowerCase())
const text = body || input.message
// Scan the raw payload too so signals missing from the summary message
// (e.g. overflow phrases nested in a JSON error body) still classify.
const text = [input.message, body].filter((value) => value.length > 0).join("\n")
const common = { message: input.message, providerMetadata: input.providerMetadata, http: input.http }
const clientScoped = input.status === undefined || (input.status >= 400 && input.status < 500)
+4 -3
View File
@@ -5,7 +5,8 @@ import { HttpOptions, ProviderID, type ModelID } from "../schema/index.js"
import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js"
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat.js"
import * as OpenAIChat from "../protocols/openai-chat.js"
import * as OpenAIResponses from "../protocols/openai-responses.js"
import { OpenResponsesChannel } from "../protocols/open-responses-channel.js"
import { XAIResponses } from "../protocols/xai-responses.js"
import { XAIImages } from "../protocols/xai-images.js"
import type { OpenAIOptionsInput } from "./openai-options.js"
import type { ProviderPackage } from "../provider-package.js"
@@ -34,9 +35,9 @@ const responsesRoute = Route.make({
id: "openai-responses",
provider: id,
providerMetadataKey: "xai",
protocol: OpenAIResponses.protocol,
protocol: XAIResponses.protocol,
endpoint: Endpoint.path("/responses", { baseURL: OpenAICompatibleProfiles.profiles.xai.baseURL }),
transport: OpenAIResponses.channelTransport({
transport: OpenResponsesChannel.transport({
id: "openai-responses",
name: "xAI Responses",
rotateAfterMs: RESPONSES_WEBSOCKET_ROTATE_AFTER_MS,
+3
View File
@@ -153,6 +153,9 @@ export class AIError extends Schema.TaggedError<AIError>()("AI.Error", {
module: Schema.String,
method: Schema.String,
reason: AIErrorReason,
// Raw provider payload as a string, so classified failures never lose the
// original error detail even when the pretty message is a summary.
body: Schema.optional(Schema.String),
}) {
override readonly cause = this.reason
@@ -1,7 +1,14 @@
{
"version": 1,
"metadata": {
"tags": ["prefix:pdf", "pdf", "provider:openai", "protocol:openai-responses", "tool", "tool-result"],
"tags": [
"prefix:pdf",
"pdf",
"provider:openai",
"protocol:openai-responses",
"tool",
"tool-result"
],
"name": "pdf/openai-tool-result",
"recordedAt": "2026-07-22T18:15:36.438Z"
},
@@ -14,7 +21,7 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gpt-4o-mini\",\"input\":[{\"role\":\"system\",\"content\":\"Read the PDF returned by the tool and follow the user's response format exactly.\"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Return only the verification code from the PDF.\"}]},{\"type\":\"function_call\",\"call_id\":\"call_pdf_1\",\"name\":\"read_pdf\",\"arguments\":\"{}\"},{\"type\":\"function_call_output\",\"call_id\":\"call_pdf_1\",\"output\":[{\"type\":\"input_text\",\"text\":\"PDF read successfully\"},{\"type\":\"input_file\",\"filename\":\"verification.pdf\",\"file_data\":\"data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3NSA+PgpzdHJlYW0KQlQKL0YxIDE4IFRmCjcyIDcyMCBUZAooUERGIGNhc3NldHRlIHZlcmlmaWNhdGlvbiBjb2RlOiBPUkNISUQtNzM5MSkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQxIDAwMDAwIG4gCjAwMDAwMDAzNjUgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0MzUKJSVFT0YK\"}]}],\"tools\":[{\"type\":\"function\",\"name\":\"read_pdf\",\"description\":\"Read the attached PDF.\",\"parameters\":{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"max_output_tokens\":40,\"temperature\":0,\"stream\":true}"
"body": "{\"model\":\"gpt-4o-mini\",\"input\":[{\"role\":\"system\",\"content\":\"Read the PDF returned by the tool and follow the user's response format exactly.\"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Return only the verification code from the PDF.\"}]},{\"type\":\"function_call\",\"call_id\":\"call_pdf_1\",\"name\":\"read_pdf\",\"arguments\":\"{}\"},{\"type\":\"function_call_output\",\"call_id\":\"call_pdf_1\",\"output\":[{\"type\":\"input_text\",\"text\":\"PDF read successfully\"},{\"type\":\"input_file\",\"filename\":\"verification.pdf\",\"file_data\":\"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3NSA+PgpzdHJlYW0KQlQKL0YxIDE4IFRmCjcyIDcyMCBUZAooUERGIGNhc3NldHRlIHZlcmlmaWNhdGlvbiBjb2RlOiBPUkNISUQtNzM5MSkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQxIDAwMDAwIG4gCjAwMDAwMDAzNjUgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0MzUKJSVFT0YK\"}]}],\"tools\":[{\"type\":\"function\",\"name\":\"read_pdf\",\"description\":\"Read the attached PDF.\",\"parameters\":{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"max_output_tokens\":40,\"temperature\":0,\"stream\":true}"
},
"response": {
"status": 200,
@@ -1,7 +1,13 @@
{
"version": 1,
"metadata": {
"tags": ["prefix:pdf", "pdf", "provider:openai", "protocol:openai-responses", "user-input"],
"tags": [
"prefix:pdf",
"pdf",
"provider:openai",
"protocol:openai-responses",
"user-input"
],
"name": "pdf/openai-user-input",
"recordedAt": "2026-07-22T18:15:34.867Z"
},
@@ -14,7 +20,7 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gpt-4o-mini\",\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_file\",\"filename\":\"verification.pdf\",\"file_data\":\"data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3NSA+PgpzdHJlYW0KQlQKL0YxIDE4IFRmCjcyIDcyMCBUZAooUERGIGNhc3NldHRlIHZlcmlmaWNhdGlvbiBjb2RlOiBPUkNISUQtNzM5MSkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQxIDAwMDAwIG4gCjAwMDAwMDAzNjUgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0MzUKJSVFT0YK\"},{\"type\":\"input_text\",\"text\":\"Return only the verification code from the PDF.\"}]}],\"store\":false,\"max_output_tokens\":40,\"temperature\":0,\"stream\":true}"
"body": "{\"model\":\"gpt-4o-mini\",\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_file\",\"filename\":\"verification.pdf\",\"file_data\":\"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3NSA+PgpzdHJlYW0KQlQKL0YxIDE4IFRmCjcyIDcyMCBUZAooUERGIGNhc3NldHRlIHZlcmlmaWNhdGlvbiBjb2RlOiBPUkNISUQtNzM5MSkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQxIDAwMDAwIG4gCjAwMDAwMDAzNjUgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0MzUKJSVFT0YK\"},{\"type\":\"input_text\",\"text\":\"Return only the verification code from the PDF.\"}]}],\"store\":false,\"max_output_tokens\":40,\"temperature\":0,\"stream\":true}"
},
"response": {
"status": 200,
@@ -1,9 +1,17 @@
{
"version": 1,
"metadata": {
"tags": ["prefix:pdf", "pdf", "provider:xai", "protocol:openai-responses", "tool", "tool-result"],
"tags": [
"prefix:pdf",
"pdf",
"provider:xai",
"protocol:xai-responses",
"tool",
"tool-result"
],
"name": "pdf/xai-tool-result",
"recordedAt": "2026-07-22T18:15:43.608Z"
"recordedAt": "2026-07-22T18:15:43.608Z",
"protocol": "xai-responses"
},
"interactions": [
{
@@ -14,7 +22,7 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"grok-4.5\",\"input\":[{\"role\":\"system\",\"content\":\"Read the PDF returned by the tool and follow the user's response format exactly.\"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Return only the verification code from the PDF.\"}]},{\"type\":\"function_call\",\"call_id\":\"call_pdf_1\",\"name\":\"read_pdf\",\"arguments\":\"{}\"},{\"type\":\"function_call_output\",\"call_id\":\"call_pdf_1\",\"output\":[{\"type\":\"input_text\",\"text\":\"PDF read successfully\"},{\"type\":\"input_file\",\"filename\":\"verification.pdf\",\"file_data\":\"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3NSA+PgpzdHJlYW0KQlQKL0YxIDE4IFRmCjcyIDcyMCBUZAooUERGIGNhc3NldHRlIHZlcmlmaWNhdGlvbiBjb2RlOiBPUkNISUQtNzM5MSkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQxIDAwMDAwIG4gCjAwMDAwMDAzNjUgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0MzUKJSVFT0YK\",\"mime_type\":\"application/pdf\"}]}],\"tools\":[{\"type\":\"function\",\"name\":\"read_pdf\",\"description\":\"Read the attached PDF.\",\"parameters\":{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"max_output_tokens\":40,\"temperature\":0,\"stream\":true}"
"body": "{\"model\":\"grok-4.5\",\"input\":[{\"role\":\"system\",\"content\":\"Read the PDF returned by the tool and follow the user's response format exactly.\"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Return only the verification code from the PDF.\"}]},{\"type\":\"function_call\",\"call_id\":\"call_pdf_1\",\"name\":\"read_pdf\",\"arguments\":\"{}\"},{\"type\":\"function_call_output\",\"call_id\":\"call_pdf_1\",\"output\":[{\"type\":\"input_text\",\"text\":\"PDF read successfully\"},{\"type\":\"input_file\",\"filename\":\"verification.pdf\",\"file_data\":\"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3NSA+PgpzdHJlYW0KQlQKL0YxIDE4IFRmCjcyIDcyMCBUZAooUERGIGNhc3NldHRlIHZlcmlmaWNhdGlvbiBjb2RlOiBPUkNISUQtNzM5MSkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQxIDAwMDAwIG4gCjAwMDAwMDAzNjUgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0MzUKJSVFT0YK\"}]}],\"tools\":[{\"type\":\"function\",\"name\":\"read_pdf\",\"description\":\"Read the attached PDF.\",\"parameters\":{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"max_output_tokens\":40,\"temperature\":0,\"stream\":true}"
},
"response": {
"status": 200,
@@ -1,9 +1,16 @@
{
"version": 1,
"metadata": {
"tags": ["prefix:pdf", "pdf", "provider:xai", "protocol:openai-responses", "user-input"],
"tags": [
"prefix:pdf",
"pdf",
"provider:xai",
"protocol:xai-responses",
"user-input"
],
"name": "pdf/xai-user-input",
"recordedAt": "2026-07-22T18:15:42.429Z"
"recordedAt": "2026-07-22T18:15:42.429Z",
"protocol": "xai-responses"
},
"interactions": [
{
@@ -14,7 +21,7 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"grok-4.5\",\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_file\",\"filename\":\"verification.pdf\",\"file_data\":\"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3NSA+PgpzdHJlYW0KQlQKL0YxIDE4IFRmCjcyIDcyMCBUZAooUERGIGNhc3NldHRlIHZlcmlmaWNhdGlvbiBjb2RlOiBPUkNISUQtNzM5MSkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQxIDAwMDAwIG4gCjAwMDAwMDAzNjUgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0MzUKJSVFT0YK\",\"mime_type\":\"application/pdf\"},{\"type\":\"input_text\",\"text\":\"Return only the verification code from the PDF.\"}]}],\"store\":false,\"max_output_tokens\":40,\"temperature\":0,\"stream\":true}"
"body": "{\"model\":\"grok-4.5\",\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_file\",\"filename\":\"verification.pdf\",\"file_data\":\"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3NSA+PgpzdHJlYW0KQlQKL0YxIDE4IFRmCjcyIDcyMCBUZAooUERGIGNhc3NldHRlIHZlcmlmaWNhdGlvbiBjb2RlOiBPUkNISUQtNzM5MSkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQxIDAwMDAwIG4gCjAwMDAwMDAzNjUgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0MzUKJSVFT0YK\"},{\"type\":\"input_text\",\"text\":\"Return only the verification code from the PDF.\"}]}],\"store\":false,\"max_output_tokens\":40,\"temperature\":0,\"stream\":true}"
},
"response": {
"status": 200,
+17
View File
@@ -106,3 +106,20 @@ describe("provider error classification", () => {
expect(classifyProviderFailure({ message: "not-json" })._tag).toBe("UnknownProvider")
})
})
describe("provider error rawBody classification", () => {
test("classifies overflow signals buried in the raw payload when the summary is vague", () => {
const reason = classifyProviderFailure({
message: "Request failed",
rawBody: '{"error":{"message":"This model\'s maximum context length is 40960 tokens"}}',
})
expect(reason._tag).toBe("InvalidRequest")
expect(reason).toMatchObject({ classification: "context-overflow" })
})
test("extracts nested codes from the raw payload", () => {
expect(
classifyProviderFailure({ message: "Request failed", rawBody: '{"error":{"code":"insufficient_quota"}}' })._tag,
).toBe("QuotaExceeded")
})
})
+107 -26
View File
@@ -848,7 +848,7 @@ describe("Gemini route", () => {
providerMetadata: { google: { thoughtSignature: "thought_sig" } },
})
expect(toolCall).toMatchObject({
id: "tool_0",
id: "provider_call",
providerMetadata: { google: { functionCallId: "provider_call", thoughtSignature: "tool_sig" } },
})
expect(response.events.findIndex((event) => event.type === "reasoning-end")).toBeLessThan(
@@ -862,14 +862,14 @@ describe("Gemini route", () => {
Message.assistant([
{ type: "reasoning", text: "thinking", providerMetadata: reasoningEnd?.providerMetadata },
ToolCallPart.make({
id: "tool_0",
id: "provider_call",
name: "lookup",
input: { query: "weather" },
providerMetadata: toolCall?.providerMetadata,
}),
]),
Message.tool({
id: "tool_0",
id: "provider_call",
name: "lookup",
result: "done",
resultType: "text",
@@ -905,6 +905,61 @@ describe("Gemini route", () => {
}),
)
it.effect("preserves thoughtSignature on visible text parts", () =>
Effect.gen(function* () {
const body = sseEvents({
candidates: [
{
content: { role: "model", parts: [{ text: "All done.", thoughtSignature: "text_sig" }] },
finishReason: "STOP",
},
],
})
const response = yield* LLMClient.generate(request).pipe(Effect.provide(fixedResponse(body)))
const delta = response.events.find((event) => event.type === "text-delta")
expect(delta).toMatchObject({
id: "text-0",
text: "All done.",
providerMetadata: { google: { thoughtSignature: "text_sig" } },
})
const prepared = yield* compileRequest(
LLM.request({
model,
messages: [Message.assistant([{ type: "text", text: "All done.", providerMetadata: delta?.providerMetadata }])],
}),
)
expect(prepared.body.contents).toEqual([
{ role: "model", parts: [{ text: "All done.", thoughtSignature: "text_sig" }] },
])
}),
)
it.effect("flushes a trailing empty signed text part at block close", () =>
Effect.gen(function* () {
const body = sseEvents({
candidates: [
{
content: {
role: "model",
parts: [{ text: "Working." }, { text: "", thoughtSignature: "tail_sig" }],
},
finishReason: "STOP",
},
],
})
const response = yield* LLMClient.generate(request).pipe(Effect.provide(fixedResponse(body)))
const delta = response.events.find((event) => event.type === "text-delta")
const end = response.events.find((event) => event.type === "text-end")
expect(delta).toMatchObject({ id: "text-0", text: "Working.", providerMetadata: undefined })
expect(end).toMatchObject({
id: "text-0",
providerMetadata: { google: { thoughtSignature: "tail_sig" } },
})
}),
)
it.effect("replays unsigned Gemini 3 tool calls with the validator bypass sentinel", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
@@ -1046,21 +1101,17 @@ describe("Gemini route", () => {
providerMetadata: { google: { promptTokenCount: 5, candidatesTokenCount: 1 } },
})
expect(response.toolCalls).toEqual([
{
type: "tool-call",
id: "tool_0",
name: "lookup",
input: { query: "weather" },
providerExecuted: undefined,
providerMetadata: undefined,
},
])
expect(response.toolCalls[0].id).toMatch(/^tool_[0-9a-zA-Z]+$/)
expect(response.toolCalls[0]).toMatchObject({
type: "tool-call",
name: "lookup",
input: { query: "weather" },
})
expect(response.events).toEqual([
{ type: "step-start", index: 0 },
{
type: "tool-call",
id: "tool_0",
id: response.toolCalls[0].id,
name: "lookup",
input: { query: "weather" },
providerExecuted: undefined,
@@ -1103,7 +1154,8 @@ describe("Gemini route", () => {
),
)
expect(response.toolCalls).toEqual([{ type: "tool-call", id: "tool_0", name: "ping", input: {} }])
expect(response.toolCalls[0].id).toMatch(/^tool_[0-9a-zA-Z]+$/)
expect(response.toolCalls).toMatchObject([{ type: "tool-call", name: "ping", input: {} }])
}),
)
@@ -1143,7 +1195,7 @@ describe("Gemini route", () => {
content: {
role: "model",
parts: [
{ functionCall: { id: "tool_0", name: "lookup", args: { query: "weather" } } },
{ functionCall: { id: "call_0", name: "lookup", args: { query: "weather" } } },
{ functionCall: { name: "lookup", args: { query: "news" } } },
],
},
@@ -1157,16 +1209,20 @@ describe("Gemini route", () => {
}),
).pipe(Effect.provide(fixedResponse(body)))
expect(response.toolCalls).toEqual([
{
type: "tool-call",
id: "tool_0",
name: "lookup",
input: { query: "weather" },
providerMetadata: { google: { functionCallId: "tool_0" } },
},
{ type: "tool-call", id: "tool_1", name: "lookup", input: { query: "news" } },
])
expect(response.toolCalls[0]).toMatchObject({
type: "tool-call",
id: "call_0",
name: "lookup",
input: { query: "weather" },
providerMetadata: { google: { functionCallId: "call_0" } },
})
expect(response.toolCalls[1]).toMatchObject({
type: "tool-call",
name: "lookup",
input: { query: "news" },
})
expect(response.toolCalls[1].id).toMatch(/^tool_[0-9a-zA-Z]+$/)
expect(response.toolCalls[0].id).not.toBe(response.toolCalls[1].id)
expect(response.events.at(-1)).toMatchObject({
type: "finish",
reason: { normalized: "tool-calls", raw: "STOP" },
@@ -1174,6 +1230,31 @@ describe("Gemini route", () => {
}),
)
it.effect("assigns distinct unique fallback ids across separate requests", () =>
Effect.gen(function* () {
const body = sseEvents({
candidates: [
{
content: {
role: "model",
parts: [{ functionCall: { name: "lookup", args: { query: "weather" } } }],
},
finishReason: "STOP",
},
],
})
const req = LLMRequest.update(request, {
tools: [ToolDefinition.make({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } })],
})
const first = yield* LLMClient.generate(req).pipe(Effect.provide(fixedResponse(body)))
const second = yield* LLMClient.generate(req).pipe(Effect.provide(fixedResponse(body)))
expect(first.toolCalls[0].id).toMatch(/^tool_[0-9a-zA-Z]+$/)
expect(second.toolCalls[0].id).toMatch(/^tool_[0-9a-zA-Z]+$/)
expect(first.toolCalls[0].id).not.toBe(second.toolCalls[0].id)
}),
)
it.effect("maps length and content-filter finish reasons", () =>
Effect.gen(function* () {
const length = yield* LLMClient.generate(request).pipe(
@@ -93,7 +93,7 @@ describe("Open Responses-compatible route", () => {
}),
)
it.effect("omits OpenAI-only nullable phases from the Open Responses baseline", () =>
it.effect("preserves nullable phases in the forgiving Open Responses baseline", () =>
Effect.gen(function* () {
const model = configure({
apiKey: "test-key",
@@ -113,7 +113,9 @@ describe("Open Responses-compatible route", () => {
)
expect(prepared.body).toMatchObject({
input: [{ type: "message", role: "assistant", content: [{ type: "output_text", text: "Unclassified." }] }],
input: [
{ type: "message", role: "assistant", content: [{ type: "output_text", text: "Unclassified." }], phase: null },
],
})
}),
)
@@ -28,7 +28,7 @@ import * as Azure from "../../src/providers/azure.js"
import * as OpenAI from "../../src/providers/openai.js"
import * as XAI from "../../src/providers/xai.js"
import * as OpenAIResponses from "../../src/protocols/openai-responses.js"
import { OpenAIResponsesChannel } from "../../src/protocols/openai-responses-channel.js"
import { OpenResponsesContinuation } from "../../src/protocols/open-responses-continuation.js"
import * as ProviderShared from "../../src/protocols/shared.js"
import { continuationRequest, nativeOpenAIResponsesContinuation } from "../continuation-scenarios.js"
import { it } from "../lib/effect.js"
@@ -68,7 +68,7 @@ const baseChannelDriver = (message: string): WebSocketChannelDriver => ({
const continuationDriver = (request: Readonly<Record<string, unknown>>) => {
const message = ProviderShared.encodeJson(request)
return OpenAIResponsesChannel.driver({
return OpenResponsesContinuation.driver({
id: "openai-responses",
name: "OpenAI Responses",
request,
@@ -1273,7 +1273,7 @@ describe("OpenAI Responses route", () => {
{
type: "input_file",
filename: "report.pdf",
file_data: "data:application/pdf;base64,JVBERi0xLjQ=",
file_data: "JVBERi0xLjQ=",
},
])
}),
@@ -1300,12 +1300,12 @@ describe("OpenAI Responses route", () => {
)
expect(expectToolOutput(prepared.body).output).toEqual([
{ type: "input_file", filename: "report.pdf", file_data: dataUrl },
{ type: "input_file", filename: "report.pdf", file_data: base64 },
])
}),
)
it.effect("uses xAI inline file encoding for PDF tool results", () =>
it.effect("uses standard inline file encoding for xAI PDF tool results", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
@@ -1334,7 +1334,6 @@ describe("OpenAI Responses route", () => {
type: "input_file",
filename: "report.pdf",
file_data: "JVBERi0xLjQ=",
mime_type: "application/pdf",
},
])
}),
@@ -1359,7 +1358,61 @@ describe("OpenAI Responses route", () => {
)
expect(expectToolOutput(prepared.body).output).toEqual([
{ type: "input_file", filename: "file", file_data: "data:audio/mpeg;base64,AAECAw==" },
{ type: "input_file", filename: "file", file_data: "AAECAw==" },
])
}),
)
it.effect("lowers remote tool-result media URLs without base64 wrapping", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model,
messages: [
Message.assistant([ToolCallPart.make({ id: "call_1", name: "fetch", input: {} })]),
Message.tool({
id: "call_1",
name: "fetch",
resultType: "content",
result: [
{ type: "file", uri: "https://example.com/image.png", mime: "image/png" },
{ type: "file", uri: "https://example.com/report.pdf", mime: "application/pdf", name: "report.pdf" },
],
}),
],
}),
)
expect(expectToolOutput(prepared.body).output).toEqual([
{ type: "input_image", image_url: "https://example.com/image.png" },
{ type: "input_file", filename: "report.pdf", file_url: "https://example.com/report.pdf" },
])
}),
)
it.effect("lowers tool-result videos as input_video", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model,
messages: [
Message.assistant([ToolCallPart.make({ id: "call_1", name: "record", input: {} })]),
Message.tool({
id: "call_1",
name: "record",
resultType: "content",
result: [
{ type: "file", uri: "data:video/mp4;base64,AAECAw==", mime: "video/mp4" },
{ type: "file", uri: "https://example.com/demo.mp4", mime: "video/mp4" },
],
}),
],
}),
)
expect(expectToolOutput(prepared.body).output).toEqual([
{ type: "input_video", video_url: "data:video/mp4;base64,AAECAw==" },
{ type: "input_video", video_url: "https://example.com/demo.mp4" },
])
}),
)
@@ -2712,7 +2765,7 @@ describe("OpenAI Responses route", () => {
{
type: "input_file",
filename: "report.pdf",
file_data: "data:application/pdf;base64,JVBERi0xLjQ=",
file_data: "JVBERi0xLjQ=",
},
],
},
@@ -2720,7 +2773,7 @@ describe("OpenAI Responses route", () => {
}),
)
it.effect("uses xAI inline file encoding for user PDFs", () =>
it.effect("uses standard inline file encoding for xAI user PDFs", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
@@ -2744,7 +2797,6 @@ describe("OpenAI Responses route", () => {
type: "input_file",
filename: "report.pdf",
file_data: "JVBERi0xLjQ=",
mime_type: "application/pdf",
},
],
},
@@ -2769,7 +2821,7 @@ describe("OpenAI Responses route", () => {
{
type: "input_file",
filename: "file",
file_data: "data:application/x-tar;base64,AAECAw==",
file_data: "AAECAw==",
},
],
},
@@ -2777,6 +2829,37 @@ describe("OpenAI Responses route", () => {
}),
)
it.effect("lowers remote user media URLs without base64 wrapping", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model,
messages: [
Message.user([
{ type: "media", mediaType: "image/png", data: "https://example.com/image.png" },
{
type: "media",
mediaType: "application/pdf",
data: "https://example.com/report.pdf",
filename: "report.pdf",
},
]),
],
}),
)
expect(prepared.body.input).toEqual([
{
role: "user",
content: [
{ type: "input_image", image_url: "https://example.com/image.png" },
{ type: "input_file", filename: "report.pdf", file_url: "https://example.com/report.pdf" },
],
},
])
}),
)
it.effect("fails with a typed rate limit for provider error frames", () =>
Effect.gen(function* () {
const error = yield* LLMClient.generate(request).pipe(
@@ -2933,36 +3016,42 @@ describe("OpenAI Responses route", () => {
}),
)
it.effect("falls back to a stable default when error is null", () =>
it.effect("falls back to the raw payload when error is null", () =>
Effect.gen(function* () {
const error = yield* LLMClient.generate(request).pipe(
Effect.provide(fixedResponse(sseEvents({ type: "error", error: null }))),
Effect.flip,
)
expect(error.reason).toMatchObject({ _tag: "UnknownProvider", message: "OpenAI Responses stream error" })
expect(error.reason).toMatchObject({ _tag: "UnknownProvider" })
expect(error.reason.message).toContain('"error":null')
expect(error.body).toBe(error.reason.message)
}),
)
it.effect("falls back to a stable default when both error and response are absent", () =>
it.effect("falls back to the raw payload when both error and response are absent", () =>
Effect.gen(function* () {
const error = yield* LLMClient.generate(request).pipe(
Effect.provide(fixedResponse(sseEvents({ type: "error" }))),
Effect.flip,
)
expect(error.reason).toMatchObject({ _tag: "UnknownProvider", message: "OpenAI Responses stream error" })
expect(error.reason).toMatchObject({ _tag: "UnknownProvider" })
expect(error.reason.message).toContain('"type":"error"')
expect(error.body).toBe(error.reason.message)
}),
)
it.effect("falls back to a stable default when response.failed has no error payload", () =>
it.effect("keeps the raw response payload when response.failed has no error payload", () =>
Effect.gen(function* () {
const error = yield* LLMClient.generate(request).pipe(
Effect.provide(fixedResponse(sseEvents({ type: "response.failed", response: { id: "resp_failed_3" } }))),
Effect.flip,
)
expect(error.reason).toMatchObject({ _tag: "UnknownProvider", message: "OpenAI Responses response failed" })
expect(error.reason).toMatchObject({ _tag: "UnknownProvider" })
expect(error.reason.message).toContain('"resp_failed_3"')
expect(error.body).toBe(error.reason.message)
}),
)
@@ -64,7 +64,7 @@ const targets: ReadonlyArray<{
id: "xai",
name: "xAI Grok 4.5",
provider: "xai",
protocol: "openai-responses",
protocol: "xai-responses",
requires: "XAI_API_KEY",
filename: "verification.pdf",
maxTokens: 40,
@@ -0,0 +1,77 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { LLM, LLMEvent } from "../../src/index.js"
import { XAI } from "../../src/providers.js"
import { OpenResponses } from "../../src/protocols/open-responses.js"
import { OpenAIResponses } from "../../src/protocols/openai-responses.js"
import { XAIResponses } from "../../src/protocols/xai-responses.js"
import { LLMClient } from "../../src/route.js"
import { compileRequest } from "../../src/route/client.js"
import { it } from "../lib/effect.js"
import { fixedResponse } from "../lib/http.js"
import { sseEvents } from "../lib/sse.js"
const model = XAI.configure({ apiKey: "test", baseURL: "https://api.x.ai/v1" }).responses("grok-4.6")
describe("xAI Responses route", () => {
it.effect("extends the Open Responses baseline directly", () =>
Effect.gen(function* () {
expect(XAIResponses.protocol.body).toBe(OpenResponses.protocol.body)
expect(XAIResponses.protocol.body).not.toBe(OpenAIResponses.protocol.body)
const prepared = yield* compileRequest(LLM.request({ model, prompt: "Hello" }))
expect(prepared.protocol).toBe("xai-responses")
}),
)
it.effect("parses xAI reasoning text events", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Think" })).pipe(
Effect.provide(
fixedResponse(
sseEvents(
{ type: "response.reasoning_text.delta", item_id: "reasoning_1", delta: "Considering." },
{ type: "response.reasoning_text.done", item_id: "reasoning_1" },
{
type: "response.output_item.done",
item: { type: "reasoning", id: "reasoning_1", encrypted_content: "opaque" },
},
{ type: "response.completed", response: { id: "response_1" } },
),
),
),
)
expect(response.message.content.find((part) => part.type === "reasoning")).toMatchObject({
type: "reasoning",
text: "Considering.",
providerMetadata: { xai: { itemId: "reasoning_1", reasoningEncryptedContent: "opaque" } },
})
}),
)
it.effect("parses xAI hosted tool items", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Search X" })).pipe(
Effect.provide(
fixedResponse(
sseEvents(
{
type: "response.output_item.done",
item: { type: "x_search_call", id: "x_search_1", status: "completed", action: { query: "news" } },
},
{ type: "response.completed", response: { id: "response_1" } },
),
),
),
)
expect(response.events.find(LLMEvent.is.toolCall)).toMatchObject({
id: "x_search_1",
name: "x_search",
input: { query: "news" },
providerExecuted: true,
})
}),
)
})
+7
View File
@@ -19,6 +19,12 @@
- Always prefer `createStore` over multiple `createSignal` calls
## Typography
- Use `--line-height-compact` (`16px`) for `13px` compact UI text and `--line-height-base` (`20px`) for body text.
- Do not use `leading-none`, `line-height: 1`, or a `13px` line height for normal text. Inter descenders clip inside truncation and overflow containers.
- Keep control and row heights explicit. Fix font metrics directly rather than using transforms, negative margins, or clip-padding compensation.
## Localization
- NEVER hardcode user-visible English strings in production code. ALWAYS use an i18n key for visible copy, placeholders, accessible labels, tooltips, menus, dialogs, toasts, empty states, and displayed errors.
@@ -27,6 +33,7 @@
- When migrating existing copy to i18n, preserve the English text byte-for-byte unless the task explicitly requests a copy change.
- NEVER change existing English text or English keys to facilitate translation. English is intentional, designer-written source copy; adapt locale-specific translations and i18n mechanics around it.
- Keep locale complexity behind the shared typed i18n APIs. Feature and component code should use `language.t(...)` for ordinary copy and `language.plural(baseKey, count, params)` for count-sensitive copy. It must not inspect the locale, call `Intl.PluralRules`, construct or select plural-category keys such as `.one` or `.other`, or branch on locale-specific grammar.
- Do not compose translated count phrases or sentence fragments in feature components. Add a focused language-context operation that owns plural selection and composition when one UI concept needs coordinated keys.
- Prefer complete translated phrases. Do not concatenate grammatical fragments or make call sites assemble sentences. Keep placeholders to irreducible dynamic values such as names, paths, and counts.
- If a translation cannot be expressed by the current API, deepen the shared language/UI i18n module so one typed call owns locale selection, plural resolution, fallback, and interpolation. Do not leak that machinery into product code.
- Do not translate from model knowledge alone. Verify terminology and grammar with Unicode CLDR locale/plural data, Microsoft Localization Style Guides and terminology, Apple localization/style guidance and localized platform UI, Mozilla localization style guides, Mozilla Pontoon, and the Firefox localization corpus at `github.com/mozilla-l10n/firefox-l10n`.
@@ -259,6 +259,34 @@ export function event(
return makeEvent(type, data)
}
export function compactionStarted(data: Extract<OpenCodeEvent, { type: "session.compaction.started" }>["data"]) {
return makeEvent("session.compaction.started", data)
}
export function compactionDelta(data: Extract<OpenCodeEvent, { type: "session.compaction.delta" }>["data"]) {
return makeEvent("session.compaction.delta", data)
}
export function compactionEnded(data: Extract<OpenCodeEvent, { type: "session.compaction.ended" }>["data"]) {
return makeEvent("session.compaction.ended", data)
}
export function compactionFailed(data: Extract<OpenCodeEvent, { type: "session.compaction.failed" }>["data"]) {
return makeEvent("session.compaction.failed", data)
}
export function toolInputStarted(data: Extract<OpenCodeEvent, { type: "session.tool.input.started" }>["data"]) {
return makeEvent("session.tool.input.started", data)
}
export function toolInputEnded(data: Extract<OpenCodeEvent, { type: "session.tool.input.ended" }>["data"]) {
return makeEvent("session.tool.input.ended", data)
}
export function toolCalled(data: Extract<OpenCodeEvent, { type: "session.tool.called" }>["data"]) {
return makeEvent("session.tool.called", data)
}
export function validateTimelineEvent(input: unknown): OpenCodeEvent {
if (!input || typeof input !== "object") throw new Error("Timeline event must be an object")
if (!("type" in input) || typeof input.type !== "string") throw new Error("Timeline event requires a type")
@@ -1,4 +1,5 @@
import { expect, test } from "@playwright/test"
import { createTwoFilesPatch } from "diff"
import {
defineVisualRegions,
reportVisualStability,
@@ -13,10 +14,63 @@ import {
setupTimeline,
shell,
textPart,
toolPart,
userMessage,
type TimelineMessage,
} from "./fixture"
test("follows an expanded patch that arrives as the user reaches the bottom", async ({ page }) => {
const toolID = "prt_bottom_follow_patch"
const input = { patchText: "Update src/edit.ts" }
const timeline = await setupTimeline(page, {
messages: [
...history(20),
userMessage(),
assistantMessage([textPart("prt_bottom_follow_text", "Working")], { completed: false }),
],
settings: { editToolPartsExpanded: true },
reducedMotion: true,
})
const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") })
await scroller.evaluate((element) => {
element.scrollTop = Math.max(0, element.scrollHeight - element.clientHeight - 300)
element.dispatchEvent(new WheelEvent("wheel", { bubbles: true, cancelable: true, deltaY: 300 }))
element.scrollTop = element.scrollHeight
})
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop))
.toBeLessThanOrEqual(1)
await timeline.send(partUpdated(toolPart(toolID, "patch", "running", input)))
await timeline.send(
partUpdated(
toolPart(toolID, "patch", "completed", input, {
metadata: {
files: [
{
file: "src/edit.ts",
status: "modified",
patch: createTwoFilesPatch(
"a/src/edit.ts",
"b/src/edit.ts",
Array.from({ length: 40 }, (_, index) => `export const value${index} = ${index}\n`).join(""),
Array.from({ length: 40 }, (_, index) => `export const value${index} = ${index + 1}\n`).join(""),
),
additions: 40,
deletions: 40,
},
],
},
}),
),
)
await timeline.waitForPart(toolID)
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop))
.toBeLessThanOrEqual(1)
})
test("does not reverse visible rows when the user wheels during shell remeasurement", async ({ page }, testInfo) => {
const shellID = "prt_wheel_01_shell"
const followingID = "prt_wheel_02_following"
@@ -53,6 +53,37 @@ benchmark.describe("performance: first navigation paint", () => {
expect(result.summary.unknownSamples).toBe(0)
})
benchmark("opens a session from the new session page without a blank frame", async ({ page, report }) => {
await mockStressTimeline(page)
await installTimelineSettings(page)
await installStressSessionTabs(page, { draftID })
await page.goto("/")
const draftHref = stressDraftHref(draftID)
const draftTab = page.locator(`[data-slot="titlebar-tabs"] a[href="${draftHref}"]`)
await expect(draftTab).toHaveCount(1)
await draftTab.click()
await expect(page.locator('[data-component="new-session"]')).toBeVisible()
const href = stressSessionHref(fixture.targetID)
const sessionTab = page.locator(`[data-slot="titlebar-tabs"] a[href="${href}"]`)
await expect(sessionTab).toHaveCount(1)
const result = await measureFirstNavigation(page, {
href,
destinationPath: href,
sourceSelector: '[data-component="new-session"]',
destinationSelector: messageSelector(fixture.expected.targetMessageIDs.at(-1)!),
contentSelector,
navigate: async () => {
await sessionTab.click()
await expectSessionTitle(page, fixture.expected.targetTitle)
},
})
report(result)
expect(result.summary.blankSamples).toBe(0)
expect(result.summary.unknownSamples).toBe(0)
})
benchmark("opens a child session without a blank frame", async ({ page, report }) => {
await setup(page)
const href = stressSessionHref(fixture.childID)
@@ -45,21 +45,17 @@ test("opens the comment editor for a line number range", async ({ page }) => {
await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on lines 1-3")
})
test("shows a comment button when a line number is hovered", async ({ page }) => {
test("shows a comment button when a diff line is hovered", async ({ page }) => {
const review = page.locator('[data-component="session-review"]')
const lineNumber = review.locator('[data-column-number="1"]').last()
await expectAppVisible(lineNumber)
const line = review.getByText("export const first = 1", { exact: true })
await expectAppVisible(line)
const comment = review.getByRole("button", { name: "Comment", exact: true })
await expect(async () => {
await lineNumber.hover()
await expect(lineNumber).toHaveAttribute("data-hovered", "")
await expect(comment).toHaveCount(1)
await expect(comment).toHaveCSS("pointer-events", "auto")
await comment.focus()
await expect(comment).toBeFocused()
}).toPass({ timeout: 10_000 })
await comment.press("Enter")
const comment = review.getByRole("button", { name: "Comment", exact: true, includeHidden: true })
await expect(comment).toHaveCount(1)
await line.dispatchEvent("pointermove", { pointerType: "mouse", bubbles: true, composed: true })
await expect(comment).toBeVisible()
await expect(comment).toHaveCSS("pointer-events", "auto")
await comment.dispatchEvent("click")
await expect(review.getByRole("textbox")).toBeVisible()
await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 1")
})
@@ -18,7 +18,7 @@ const branchDiffs = [
),
]
test("keeps the review tree and terminal sized when both panels are open", async ({ page }) => {
test("uses side placement by default and supports the terminal across the bottom", async ({ page }) => {
test.setTimeout(120_000)
await page.setViewportSize({ width: 1400, height: 900 })
await mockOpenCodeServer(page, {
@@ -27,7 +27,7 @@ test("keeps the review tree and terminal sized when both panels are open", async
id: projectID,
worktree: directory,
vcs: "git",
name: "review-terminal-stacked",
name: "review-terminal-bottom",
time: { created: 1700000000000, updated: 1700000000000 },
sandboxes: [],
},
@@ -45,7 +45,7 @@ test("keeps the review tree and terminal sized when both panels are open", async
sessions: [
{
id: sessionID,
slug: "review-terminal-stacked",
slug: "review-terminal-bottom",
projectID,
directory,
title,
@@ -138,7 +138,22 @@ test("keeps the review tree and terminal sized when both panels are open", async
await page.keyboard.press("Control+Backquote")
await expect(page.locator("#terminal-panel")).toBeVisible()
await expectTree(page, 2_773, "action.yml")
await expectStackGeometry(page)
await expectSideGeometry(page)
await page.evaluate(() => {
const settings = JSON.parse(localStorage.getItem("settings.v3") ?? "{}")
localStorage.setItem(
"settings.v3",
JSON.stringify({ ...settings, general: { ...settings.general, terminalPlacement: "bottom" } }),
)
})
await page.reload()
await expectSessionReady(page, { server, sessionID, title })
await expect(page.locator("#review-panel")).toBeVisible()
await page.keyboard.press("Control+Backquote")
await expect(page.locator("#terminal-panel")).toBeVisible()
await expectTree(page, 2_773, "action.yml")
await expectBottomGeometry(page)
})
async function expectTree(page: Page, total: number, file: string) {
@@ -163,23 +178,51 @@ async function expectMountedTree(page: Page, total: number) {
expect(state.rows).toBeLessThanOrEqual(60)
}
async function expectStackGeometry(page: Page) {
async function expectSideGeometry(page: Page) {
const geometry = await page.evaluate(() => {
const review = document.querySelector<HTMLElement>("#review-panel")!.getBoundingClientRect()
const terminal = document.querySelector<HTMLElement>("#terminal-panel")!.getBoundingClientRect()
return {
reviewLeft: review.left,
reviewRight: review.right,
terminalLeft: terminal.left,
terminalRight: terminal.right,
terminalTop: terminal.top,
reviewTop: review.top,
}
})
expect(Math.abs(geometry.terminalLeft - geometry.reviewLeft)).toBeLessThanOrEqual(1)
expect(Math.abs(geometry.terminalRight - geometry.reviewRight)).toBeLessThanOrEqual(1)
expect(geometry.terminalTop).toBeGreaterThan(geometry.reviewTop)
}
async function expectBottomGeometry(page: Page) {
const geometry = await page.evaluate(() => {
const review = document.querySelector<HTMLElement>("#review-panel")!
const terminal = document.querySelector<HTMLElement>("#terminal-panel")!
const terminalRect = terminal.getBoundingClientRect()
const reviewParent = review.parentElement!.getBoundingClientRect()
const terminalParent = terminal.parentElement!.getBoundingClientRect()
const sidebar = review.querySelector<HTMLElement>('[data-slot="session-review-v2-sidebar"]')!
return {
review: review.getBoundingClientRect().height,
reviewBottom: review.getBoundingClientRect().bottom,
reviewParent: reviewParent.height,
terminal: terminal.getBoundingClientRect().height,
terminal: terminalRect.height,
terminalLeft: terminalRect.left,
terminalRight: terminalRect.right,
terminalTop: terminalRect.top,
terminalParent: terminalParent.height,
sidebar: sidebar.getBoundingClientRect().width,
viewport: window.innerWidth,
}
})
expect(Math.abs(geometry.review - geometry.reviewParent)).toBeLessThanOrEqual(1)
expect(Math.abs(geometry.terminal - geometry.terminalParent)).toBeLessThanOrEqual(1)
expect(geometry.terminalTop - geometry.reviewBottom).toBeGreaterThanOrEqual(7)
expect(geometry.terminalTop - geometry.reviewBottom).toBeLessThanOrEqual(9)
expect(geometry.terminalLeft).toBeLessThanOrEqual(9)
expect(geometry.terminalRight).toBeGreaterThanOrEqual(geometry.viewport - 9)
expect(geometry.sidebar).toBeGreaterThanOrEqual(240)
}
@@ -0,0 +1,48 @@
import { expect, test, type Route } from "@playwright/test"
const server = "http://127.0.0.1:4097"
test("nested server dialog keeps focus inside the top layer", async ({ page }) => {
await page.addInitScript((server) => {
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [server] }))
}, server)
await page.route("**/*", async (route) => {
const url = new URL(route.request().url())
if (url.origin !== server) return route.fallback()
if (url.pathname === "/api/event") {
return route.fulfill({
status: 200,
contentType: "text/event-stream",
body: 'data: {"id":"evt_connected","type":"server.connected","data":{}}\n\n',
})
}
if (url.pathname === "/api/global/health" || url.pathname === "/api/health") {
return json(route, { healthy: true, version: "2.0.0" })
}
return json(route, {})
})
await page.goto("/")
await page.keyboard.press("Control+,")
const settings = page.locator(".settings-dialog")
await expect(settings).toBeVisible()
await settings.getByRole("tab", { name: "Servers" }).click()
await settings.getByRole("button", { name: "Add server" }).click()
const editor = page.getByRole("dialog", { name: "Add server" })
await expect(editor.getByPlaceholder("http://localhost:4096")).toBeFocused()
const username = editor.getByPlaceholder("username")
const password = editor.getByPlaceholder("password")
await username.click()
await expect(username).toBeFocused()
await username.fill("kit")
await expect(username).toHaveValue("kit")
await page.keyboard.press("Tab")
await expect(password).toBeFocused()
await password.fill("secret")
await expect(password).toHaveValue("secret")
})
function json(route: Route, body: unknown, status = 200) {
return route.fulfill({ status, contentType: "application/json", body: JSON.stringify(body) })
}
@@ -99,7 +99,7 @@ test.describe("regression: session timeline local row state", () => {
await wrapper.evaluate((element) => {
;(element as HTMLElement).dataset.regressionMarker = "before-stream"
})
await wrapper.locator('[data-slot="collapsible-trigger"]').first().click()
await wrapper.locator('[data-scope="apply-patch"] button').click()
await expectExpanded(wrapper, false)
events.push(...textEvents())
@@ -139,7 +139,7 @@ test.describe("regression: session timeline local row state", () => {
expect(siblingProbe).toEqual({
fileMarker: "before",
frameMarker: "before",
rowKey: `assistant-part:${userMessageID}:part:${assistantMessageID}:${editPartID}`,
rowKey: `assistant-part:part:${assistantMessageID}:${editPartID}`,
rowMarker: "before",
shadowRoots: 0,
toolMarker: "before",
@@ -179,8 +179,8 @@ test.describe("regression: session timeline local row state", () => {
await expectSessionTitle(page, title)
const wrapper = page.locator(`[data-timeline-part-id="${editPartID}"]`).first()
const trigger = wrapper.locator('[data-slot="collapsible-trigger"]').first()
const diff = wrapper.locator('[data-component="edit-content"]').first()
const trigger = wrapper.locator('[data-component="sticky-accordion-header"]')
const diff = wrapper.locator('[data-component="apply-patch-file-diff"]').first()
await expectAppVisible(diff)
await expect.poll(() => wrapper.evaluate((element) => element.getBoundingClientRect().height)).toBeGreaterThan(500)
const samples = await wrapper.evaluate(async (element) => {
@@ -190,8 +190,8 @@ test.describe("regression: session timeline local row state", () => {
for (const offset of [0, 120, 240, 360, 480]) {
root.scrollBy(0, offset - (result.at(-1)?.offset ?? 0))
await new Promise(requestAnimationFrame)
const trigger = element.querySelector<HTMLElement>('[data-slot="collapsible-trigger"]')!
const diff = element.querySelector<HTMLElement>('[data-component="edit-content"]')!
const trigger = element.querySelector<HTMLElement>('[data-component="sticky-accordion-header"]')!
const diff = element.querySelector<HTMLElement>('[data-component="apply-patch-file-diff"]')!
result.push({
offset,
trigger: trigger.getBoundingClientRect().y,
@@ -202,7 +202,7 @@ test.describe("regression: session timeline local row state", () => {
return result
})
expect(samples[0]!.trigger).toBeLessThan(samples[0]!.diff)
expect(samples[0]!.trigger).toBeGreaterThanOrEqual(samples[0]!.diff)
expect(samples.every((sample) => Math.abs(sample.trigger - samples[0]!.trigger) <= 1)).toBe(true)
expect(samples.every((sample) => sample.trigger < sample.bottom)).toBe(true)
})
@@ -234,7 +234,9 @@ async function readToolState(page: Page) {
.evaluate(
(element, textPartID) => ({
expanded: (() => {
const trigger = element.querySelector('[data-slot="collapsible-trigger"]')
const trigger =
element.querySelector('[data-scope="apply-patch"] button') ??
element.querySelector('[data-slot="collapsible-trigger"]')
const aria = trigger?.getAttribute("aria-expanded")
if (aria === "true") return true
if (aria === "false") return false
@@ -409,7 +411,9 @@ function eventValue<Type extends OpenCodeEvent["type"]>(
}
function readExpanded(element: Element) {
const trigger = element.querySelector('[data-slot="collapsible-trigger"]')
const trigger =
element.querySelector('[data-scope="apply-patch"] button') ??
element.querySelector('[data-slot="collapsible-trigger"]')
const aria = trigger?.getAttribute("aria-expanded")
if (aria === "true") return true
if (aria === "false") return false
@@ -1,5 +1,12 @@
import { expect, test } from "@playwright/test"
import { assistantMessage, setupTimeline, toolPart, userMessage } from "../performance/timeline-stability/fixture"
import { createTwoFilesPatch } from "diff"
import {
assistantMessage,
setupTimeline,
toolPart,
userMessage,
userText,
} from "../performance/timeline-stability/fixture"
test("renders completed write content", async ({ page }) => {
const id = "prt_file_projection_write"
@@ -47,5 +54,144 @@ test("renders a completed single-file patch", async ({ page }) => {
settings: { editToolPartsExpanded: true },
})
await expect(page.locator(`[data-timeline-part-id="${id}"] [data-component="apply-patch-file-diff"]`)).toBeVisible()
const wrapper = page.locator(`[data-timeline-part-id="${id}"]`)
const file = wrapper.locator('[data-scope="apply-patch"]')
const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") })
await expect(file.getByRole("button")).toHaveAttribute("aria-expanded", "false")
await expect(wrapper.locator('[data-component="apply-patch-file-diff"]')).toHaveCount(0)
await file.getByRole("button").click()
await expect(wrapper.locator('[data-component="apply-patch-file-diff"]')).toBeVisible()
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop))
.toBeLessThanOrEqual(1)
await file.getByRole("button").click()
await expect(wrapper.locator('[data-component="apply-patch-file-diff"]')).toHaveCount(0)
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop))
.toBeLessThanOrEqual(1)
await file.getByRole("button").click()
await expect(wrapper.locator('[data-component="apply-patch-file-diff"]')).toBeVisible()
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop))
.toBeLessThanOrEqual(1)
})
test("keeps an expanded file diff header at the same viewport position", async ({ page }) => {
const id = "prt_file_projection_anchored_patch"
const before = Array.from({ length: 80 }, (_, index) => `export const value${index} = ${index}\n`).join("")
const after = before.replaceAll(" = ", " = compute(").replaceAll("\n", ")\n")
await setupTimeline(page, {
messages: [
userMessage([userText("Preceding context ".repeat(120))]),
assistantMessage([
toolPart(
id,
"patch",
"completed",
{ patchText: "Update src/anchored.ts" },
{
metadata: {
files: [
{
file: "src/anchored.ts",
status: "modified",
patch: createTwoFilesPatch("a/src/anchored.ts", "b/src/anchored.ts", before, after),
additions: 80,
deletions: 80,
},
],
},
},
),
]),
],
viewport: { width: 1200, height: 600 },
})
const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") })
const wrapper = page.locator(`[data-timeline-part-id="${id}"]`)
const row = page.locator("[data-timeline-key]", { has: wrapper })
const trigger = wrapper.getByRole("button")
await expect
.poll(() =>
row.evaluate((element) => {
const measured = element.querySelector<HTMLElement>("[data-index]")
return measured
? Math.abs(element.getBoundingClientRect().height - measured.getBoundingClientRect().height)
: Number.POSITIVE_INFINITY
}),
)
.toBeLessThanOrEqual(1)
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight))
.toBeGreaterThan(1)
await scroller.evaluate((element) => {
element.scrollTop = element.scrollHeight - element.clientHeight - 0.25
})
await expect(trigger).toBeInViewport()
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop))
.toBeLessThanOrEqual(0.5)
const bottomScrollTop = await scroller.evaluate((element) => element.scrollTop)
await scroller.hover()
await page.mouse.wheel(0, -20)
await expect
.poll(() => scroller.evaluate((element, bottom) => bottom - element.scrollTop, bottomScrollTop))
.toBeGreaterThan(0)
const y = await trigger.evaluate((element) => element.getBoundingClientRect().y)
const collapsedHeight = await row.evaluate((element) => element.getBoundingClientRect().height)
await trigger.click()
await expect(wrapper.locator('[data-component="apply-patch-file-diff"]')).toBeVisible()
await expect
.poll(() =>
row.evaluate((element, collapsed) => {
const measured = element.querySelector<HTMLElement>("[data-index]")
const allocatedHeight = element.getBoundingClientRect().height
return {
grew: allocatedHeight > collapsed + 1,
measured: measured ? Math.abs(allocatedHeight - measured.getBoundingClientRect().height) <= 1 : false,
}
}, collapsedHeight),
)
.toEqual({ grew: true, measured: true })
await expect
.poll(() => trigger.evaluate((element, initialY) => Math.abs(element.getBoundingClientRect().y - initialY), y))
.toBeLessThanOrEqual(5)
const scrollTop = await scroller.evaluate((element) => element.scrollTop)
await scroller.hover()
await page.mouse.wheel(0, 200)
await expect
.poll(() => scroller.evaluate((element, initial) => element.scrollTop - initial, scrollTop))
.toBeGreaterThan(50)
const scrolled = await scroller.evaluate((element, initial) => element.scrollTop - initial, scrollTop)
expect(scrolled).toBeLessThan(400)
const expandedY = await trigger.evaluate((element) => element.getBoundingClientRect().y)
await trigger.click()
await expect(wrapper.locator('[data-component="apply-patch-file-diff"]')).toHaveCount(0)
await expect
.poll(() =>
scroller.evaluate((element) => Math.abs(element.scrollHeight - element.clientHeight - element.scrollTop)),
)
.toBeLessThanOrEqual(1)
await expect.poll(() => trigger.evaluate((element) => element.getBoundingClientRect().y)).toBeGreaterThan(expandedY)
await trigger.click()
await expect(wrapper.locator('[data-component="apply-patch-file-diff"]')).toBeVisible()
await expect
.poll(() =>
row.evaluate((element) => {
const measured = element.querySelector<HTMLElement>("[data-index]")
return measured
? Math.abs(element.getBoundingClientRect().height - measured.getBoundingClientRect().height)
: Number.POSITIVE_INFINITY
}),
)
.toBeLessThanOrEqual(1)
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop))
.toBeLessThanOrEqual(1)
})
@@ -2,7 +2,7 @@ import { expect, test } from "@playwright/test"
import { assistantMessage, setupTimeline, toolPart, userMessage } from "../performance/timeline-stability/fixture"
import { createTwoFilesPatch } from "diff"
test("preserves nested patch file state through outer collapse and reopen", async ({ page }) => {
test("keeps patch file disclosures independent", async ({ page }) => {
const patchID = "prt_nested_patch"
const files = [patchFile("src/a.ts", "modified"), patchFile("src/b.ts", "added"), patchFile("src/old.ts", "deleted")]
await setupTimeline(page, {
@@ -21,15 +21,17 @@ test("preserves nested patch file state through outer collapse and reopen", asyn
settings: { editToolPartsExpanded: true },
})
const wrapper = page.locator(`[data-timeline-part-id="${patchID}"]`)
const outer = wrapper.locator('[data-slot="collapsible-trigger"]').first()
const modified = wrapper.locator('[data-scope="apply-patch"] [data-type="update"]')
const deleted = wrapper.locator('[data-scope="apply-patch"] [data-type="delete"]')
await expect(wrapper.locator('[data-scope="apply-patch"] [aria-expanded="false"]')).toHaveCount(3)
await deleted.getByRole("button").click()
await expect(deleted.getByRole("button")).toHaveAttribute("aria-expanded", "true")
await outer.click()
await expect(outer).toHaveAttribute("aria-expanded", "false")
await outer.click()
await expect(outer).toHaveAttribute("aria-expanded", "true")
await expect(deleted.getByRole("button")).toHaveAttribute("aria-expanded", "true")
await expect(modified.getByRole("button")).toHaveAttribute("aria-expanded", "false")
await modified.getByRole("button").click()
await expect(modified.getByRole("button")).toHaveAttribute("aria-expanded", "true")
await deleted.getByRole("button").click()
await expect(deleted.getByRole("button")).toHaveAttribute("aria-expanded", "false")
await expect(modified.getByRole("button")).toHaveAttribute("aria-expanded", "true")
})
function patchFile(file: string, status: "added" | "modified" | "deleted") {
@@ -17,7 +17,7 @@ import { mockOpenCodeServer } from "../utils/mock-server"
import { installSseTransport } from "../utils/sse-transport"
import { expectSessionTitle } from "../utils/waits"
const messagePageSize = 200
const messagePageSize = 20
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
const messages = Array.from({ length: messagePageSize / 2 + 1 }, (_, index) => {
const id = `msg_${String(index + 1001).padStart(4, "0")}_history_root_user`
@@ -1,5 +1,6 @@
import { expect, test } from "@playwright/test"
import {
assistantID,
assistantMessage,
completedAssistantInfo,
messageUpdated,
@@ -8,9 +9,13 @@ import {
renderedPartID,
setupTimeline,
shell,
sessionID,
status,
stepStarted,
textPart,
toolCalled,
toolInputEnded,
toolInputStarted,
userMessage,
} from "../performance/timeline-stability/fixture"
@@ -34,7 +39,56 @@ for (const expanded of [false, true]) {
})
}
test("shows and expands a running shell command without shimmering it", async ({ page }) => {
test("transitions a streaming shell from writing through command execution", async ({ page }) => {
const id = "prt_shell_streaming_input"
const command = "printf ready"
const timeline = await setupTimeline(page, {
messages: [userMessage(), assistantMessage([], { completed: false })],
})
await timeline.send(toolInputStarted({ sessionID, assistantMessageID: assistantID, id, name: "shell" }))
const tool = page.locator(`[data-timeline-part-id="${id}"]`)
const title = tool.locator('[data-slot="basic-tool-tool-title"]')
const titleShimmer = title.locator('[data-component="text-shimmer"]')
const subtitle = tool.locator('[data-slot="basic-tool-tool-subtitle"]')
await expect(titleShimmer).toHaveAttribute("aria-label", "Shell")
await expect(titleShimmer).toHaveAttribute("data-active", "true")
await expect(subtitle).toHaveText("Writing command...")
await expect(subtitle.locator('[data-component="text-shimmer"]')).toHaveCount(0)
await expect(tool.locator('[data-component="shell-submessage"]')).toHaveCount(0)
await expect(tool.locator('[data-slot="collapsible-trigger"]')).toHaveCSS("height", "28px")
await expect(tool.locator('[data-component="tool-trigger"]')).toHaveCSS("gap", "6px")
await expect(title).toHaveCSS("font-size", "13px")
await expect(title).toHaveCSS("font-family", "Inter, sans-serif")
await expect(title).toHaveCSS("font-weight", "530")
await expect(title).toHaveCSS("line-height", "16px")
await expect(title).toHaveCSS("color", "rgb(22, 22, 22)")
await expect(subtitle).toHaveCSS("font-size", "13px")
await expect(subtitle).toHaveCSS("font-family", "Inter, sans-serif")
await expect(subtitle).toHaveCSS("font-weight", "440")
await expect(subtitle).toHaveCSS("line-height", "16px")
await expect(subtitle).toHaveCSS("color", "rgb(92, 92, 92)")
const input = JSON.stringify({ command })
await timeline.send(toolInputEnded({ sessionID, assistantMessageID: assistantID, id, text: input }))
await expect(titleShimmer).toHaveAttribute("data-active", "true")
await expect(subtitle).toHaveText(command)
await expect(tool).not.toContainText("Writing command...")
await timeline.send(
toolCalled({
sessionID,
assistantMessageID: assistantID,
id,
input: { command },
executed: true,
}),
)
await expect(titleShimmer).toHaveAttribute("data-active", "true")
await expect(subtitle).toHaveText(command)
})
test("shimmers and expands a running shell command", async ({ page }) => {
const id = "prt_shell_running_command"
const command = "sleep 10 && echo done"
await setupTimeline(page, {
@@ -44,8 +98,10 @@ test("shows and expands a running shell command without shimmering it", async ({
const tool = page.locator(`[data-timeline-part-id="${id}"]`)
await expect(tool.locator('[data-component="text-shimmer"]')).toHaveAttribute("data-active", "true")
await expect(tool).not.toContainText("Writing command...")
await expect(tool.locator('[data-component="shell-submessage"]')).toHaveText(command)
await expect(tool.locator('[data-component="shell-submessage"] [data-component="text-shimmer"]')).toHaveCount(0)
await expect(tool.locator('[data-slot="collapsible-trigger"]')).toHaveCSS("height", "28px")
await tool.locator('[data-slot="collapsible-trigger"]').click()
await expect(tool.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "true")
await expect(tool.locator('[data-slot="bash-pre"]')).toContainText("still running")
@@ -1,6 +1,15 @@
import { expect, test } from "@playwright/test"
import type { SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client/promise"
import { event, session, sessionID, setupTimeline } from "../performance/timeline-stability/fixture"
import {
compactionDelta,
compactionEnded,
compactionFailed,
compactionStarted,
event,
session,
sessionID,
setupTimeline,
} from "../performance/timeline-stability/fixture"
const user = { id: "msg_user", type: "user", text: "Run it", time: { created: 1 } } satisfies SessionMessageInfo
@@ -73,6 +82,160 @@ test("renders current protocol notices in CLI order", async ({ page }) => {
expect(ownerWarnings).toEqual([])
})
test("renders a compaction summary while it streams and after completion", async ({ page }) => {
const timeline = await setupTimeline(page, { sessionMessages: [user, assistant(true)] })
await timeline.send(
compactionStarted({
sessionID,
reason: "manual",
recent: "",
}),
)
const compaction = page.locator('[data-component="session-compaction-message"]')
await expect(compaction.getByText("Session compacted", { exact: true })).toBeVisible()
await timeline.send(
compactionDelta({
sessionID,
text: "## Checkpoint\n\nStreamed implementation details.",
}),
)
await expect(compaction.getByRole("heading", { name: "Checkpoint" })).toBeVisible()
await expect(compaction).toContainText("Streamed implementation details.")
await timeline.send(
compactionEnded({
sessionID,
reason: "manual",
text: "## Checkpoint\n\nFinal implementation details.",
recent: "",
}),
)
await expect(compaction).toContainText("Final implementation details.")
await expect(compaction).not.toContainText("Streamed implementation details.")
})
test("updates running compactions to failed and cancelled boundaries", async ({ page }) => {
const timeline = await setupTimeline(page, { sessionMessages: [user, assistant(true)] })
await timeline.send(compactionStarted({ sessionID, reason: "auto", recent: "" }))
await timeline.send(compactionDelta({ sessionID, text: "Partial summary that should be discarded." }))
await timeline.send(
compactionFailed({
sessionID,
reason: "auto",
error: {
type: "compaction.failed",
message: 'Error: {"error":{"type":"ProviderError","message":"The provider rejected the summary."}}',
},
}),
)
const compactions = page.locator('[data-component="session-compaction-message"]')
const failed = compactions.filter({ hasText: "The provider rejected the summary." })
await expect(failed.getByText("Session compacted", { exact: true })).toBeVisible()
await expect(failed.getByText("ProviderError: The provider rejected the summary.", { exact: true })).toBeVisible()
await expect(failed).not.toContainText("Partial summary that should be discarded.")
await timeline.send(compactionStarted({ sessionID, reason: "manual", recent: "" }))
await timeline.send(
compactionFailed({
sessionID,
reason: "manual",
error: { type: "aborted", message: "Cancellation detail should stay hidden." },
}),
)
await expect(compactions).toHaveCount(2)
const cancelled = compactions.filter({ hasNotText: "The provider rejected the summary." })
await expect(cancelled.getByText("Session compacted", { exact: true })).toBeVisible()
await expect(cancelled).not.toContainText("Cancellation detail should stay hidden.")
})
test("shows a delegating row while subagent input streams", async ({ page }) => {
await setupTimeline(page, {
sessionMessages: [
user,
{
...assistant(false),
content: [
{
type: "tool",
id: "call_subagent",
name: "subagent",
state: { status: "streaming", input: "" },
time: { created: 2 },
},
],
},
],
})
const delegating = page.locator('[data-component="task-tool-delegating"]')
await expect(delegating).toBeVisible()
const shimmer = delegating.locator('[data-component="text-shimmer"]')
await expect(shimmer).toHaveAttribute("aria-label", "Delegating agent...")
await expect(shimmer).toHaveCSS("line-height", "16px")
const icon = delegating.locator('[data-slot="icon-svg"]')
await expect(icon.locator('use[href="#opencode-v2-icon-subagent"]')).toBeVisible()
await expect(icon).toHaveCSS("color", "rgb(174, 174, 174)")
await expect(page.locator('[data-component="task-tool-card"]')).toHaveCount(0)
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
})
test("renders the moved location notice in its compact timeline style", async ({ page }) => {
const directory = `/Users/usrnk1/Developer/opencode/${"nested-directory/".repeat(24)}session`
await page.setViewportSize({ width: 480, height: 720 })
await setupTimeline(page, {
sessionMessages: [
user,
{
id: "msg_location",
type: "location-switched",
location: { directory },
time: { created: 2 },
},
],
})
const notice = page.locator('[data-slot="session-timeline-notice"][data-type="location-switched"]')
const label = notice.locator('[data-slot="session-timeline-notice-label"]')
const value = notice.locator('[data-slot="session-timeline-notice-value"]')
const tooltipTrigger = notice.locator('[data-component="tooltip-v2-trigger"]')
await expect(label).toHaveText("Moved to")
await expect(value).toHaveText(directory)
await expect(notice).not.toContainText("·")
await expect(notice.locator("svg")).toHaveCount(0)
await expect(notice).toHaveCSS("height", "28px")
await expect(notice).toHaveCSS("gap", "8px")
await expect(notice).toHaveCSS("padding-top", "4px")
await expect(notice).toHaveCSS("padding-bottom", "4px")
await expect(label).toHaveCSS("font-size", "13px")
await expect(label).toHaveCSS("font-weight", "530")
await expect(label).toHaveCSS("line-height", "16px")
await expect(label).toHaveCSS("color", "rgb(128, 128, 128)")
await expect(value).toHaveCSS("font-size", "13px")
await expect(value).toHaveCSS("font-weight", "440")
await expect(value).toHaveCSS("line-height", "16px")
await expect(value).toHaveCSS("color", "rgb(128, 128, 128)")
await expect(value).toHaveCSS("text-overflow", "ellipsis")
await expect(value).toHaveCSS("white-space", "nowrap")
await expect(value).toHaveAttribute("dir", "ltr")
await expect.poll(() => value.evaluate((element) => element.scrollWidth > element.clientWidth)).toBe(true)
const tooltip = page.getByText("Session working directory changed", { exact: true })
await label.hover()
await expect(tooltip).toBeVisible()
await page.mouse.move(0, 0)
await expect(tooltip).toBeHidden()
await tooltipTrigger.focus()
await expect(tooltipTrigger).toBeFocused()
await expect(tooltip).toBeVisible()
})
test("moves blocking work to the background with Ctrl+B", async ({ page }) => {
await setupTimeline(page, { sessionMessages: [user, assistant(false, true)] })
const card = page.locator('[data-component="task-tool-card"]')
@@ -81,7 +244,24 @@ test("moves blocking work to the background with Ctrl+B", async ({ page }) => {
await expect(card).not.toContainText("(background)")
await expect(page.getByText("Called `subagent`", { exact: false })).toHaveCount(0)
await expect(page.locator('[data-component="background-tool-control"]')).toHaveCount(0)
await expect(page.locator('[data-action="session-background-toggle"]')).toContainText("Move 1 subagent to background")
const hint = page.locator('[data-component="session-background-hint"]')
const hintPrefix = hint.locator('[data-slot="session-background-hint-prefix"]')
await expect(hint).toBeVisible()
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
await expect
.poll(async () => {
const [cardBox, hintBox, prefixBox] = await Promise.all([
card.boundingBox(),
hint.boundingBox(),
hintPrefix.boundingBox(),
])
if (!cardBox || !hintBox || !prefixBox) return undefined
return {
aligned: Math.abs(cardBox.x - prefixBox.x) < 2,
ordered: cardBox.y < hintBox.y,
}
})
.toEqual({ aligned: true, ordered: true })
const request = page.waitForRequest(
(request) =>
@@ -104,10 +284,10 @@ test("navigates from a running subagent card and hides background controls in th
sessionStatus: { [sessionID]: { type: "busy" }, [childID]: { type: "busy" } },
})
await expect(page.locator('[data-action="session-background-toggle"]')).toContainText("Move 1 subagent to background")
await expect(page.getByText(/move running work to the background/i)).toBeVisible()
await page.locator('[data-component="task-tool-card"]').click()
await expect(page).toHaveURL(new RegExp(`/session/${childID}$`))
await expect(page.locator('[data-component="session-background-dock"]')).toHaveCount(0)
await expect(page.getByText(/move running work to the background/i)).toHaveCount(0)
})
test("shows a badge for active background work", async ({ page }) => {
@@ -118,7 +298,14 @@ test("shows a badge for active background work", async ({ page }) => {
sessionStatus: { [childID]: { type: "busy" } },
})
await expect(page.locator('[data-component="session-background-dock"]')).toContainText("1 subagent in background")
await page.getByRole("button", { name: "Session details" }).click()
const summary = page.getByRole("button", { name: "1 item running in background" })
await expect(summary).toContainText("1")
await expect(summary).toContainText("Running work in background")
await summary.click()
await expect(
page.locator('[data-component="session-background-list"]').getByText("Agent", { exact: true }),
).toBeVisible()
})
test("separates blocking and already-backgrounded work into two rows", async ({ page }) => {
@@ -193,10 +380,15 @@ test("separates blocking and already-backgrounded work into two rows", async ({
},
})
const dock = page.locator('[data-component="session-background-dock"]')
const backgroundCard = page.locator('[data-timeline-part-id="call_backgrounded"]')
await expect(dock).toContainText("Move 1 subagent to background")
await expect(dock.getByText("Running 1 shell and 1 subagent in background", { exact: true })).toBeVisible()
await expect(page.getByText(/move running work to the background/i)).toBeVisible()
await page.getByRole("button", { name: "Session details" }).click()
const summary = page.getByRole("button", { name: "2 items running in background" })
await expect(summary).toContainText("2")
await summary.click()
const list = page.locator('[data-component="session-background-list"]')
await expect(list).toContainText("Background task")
await expect(list).toContainText("sleep 120")
await expect(backgroundCard).toContainText("Background task (background)")
await expect(backgroundCard.locator('[data-component="session-progress-indicator-v2"]')).toBeVisible()
await expect(
@@ -1,6 +1,7 @@
import { expect, test } from "@playwright/test"
import {
assistantMessage,
partUpdated,
setupTimeline,
status,
toolPart,
@@ -69,9 +70,126 @@ test.describe("session timeline projection", () => {
]) {
await expect(page.locator(`[data-timeline-part-id="${id}"]`).first(), id).toBeVisible()
}
const patch = page.locator('[data-timeline-part-id="prt_patch"]')
await expect(patch.getByText("1 file", { exact: true })).toBeVisible()
await expect(patch.getByRole("button", { name: "Patch 1 file", exact: true })).toHaveCount(0)
await expect(patch.getByRole("button")).toHaveCount(1)
await expect(patch.locator('[data-scope="apply-patch"] button[aria-expanded="false"]')).toHaveCount(1)
await expect(patch.locator('[data-slot="message-part-title-filename"]')).toHaveCount(0)
await expect(patch.locator('[data-slot="message-part-actions"]')).toHaveCount(0)
const edit = page.locator('[data-timeline-part-id="prt_edit"]')
await expect(edit.locator('[data-component="apply-patch-tool"]')).toBeVisible()
await expect(edit.locator('[data-slot="basic-tool-tool-title"]')).toContainText("Edit")
await expect(page.locator('[data-timeline-part-id="prt_todo"]')).toHaveCount(0)
})
test("combines adjacent patch calls and repeated files into one group", async ({ page }) => {
const first = "prt_patch_first"
const second = "prt_patch_second"
const timeline = await setupTimeline(page, {
messages: [
userMessage(),
assistantMessage([
toolPart(
first,
"patch",
"completed",
{ patchText: "Update src/first.ts" },
{
metadata: { files: [patchFile("src/first.ts", "modified")] },
},
),
]),
],
})
const initial = page.locator(`[data-timeline-part-id="${first}"]`)
const initialFile = initial.locator('[data-scope="apply-patch"] [data-type="update"]')
await expect(initialFile).toBeVisible()
await initialFile.getByRole("button").click()
await expect(initialFile.getByRole("button")).toHaveAttribute("aria-expanded", "true")
await initial.evaluate((element) => {
const row = element.closest<HTMLElement>("[data-timeline-key]")
if (row) row.dataset.patchRow = "stable"
})
await timeline.send(
partUpdated(toolPart(second, "patch", "running", { patchText: "Update more files" }, { metadata: {} })),
)
const group = page.locator(`[data-timeline-part-ids="${first},${second}"]`)
await expect(group.locator("xpath=ancestor::*[@data-timeline-key]")).toHaveAttribute("data-patch-row", "stable")
await expect(group.locator('[data-slot="apply-patch-filename"]')).toHaveText(["first.ts"])
await expect(group.locator('[data-scope="apply-patch"] [data-type="update"] button')).toHaveAttribute(
"aria-expanded",
"true",
)
await timeline.send(
partUpdated(
toolPart(
second,
"patch",
"completed",
{ patchText: "Update more files" },
{
metadata: {
files: [patchFile("src/first.ts", "modified"), patchFile("src/second.ts", "added")],
},
},
),
),
)
await expect(group.locator('[data-slot="apply-patch-filename"]')).toHaveText(["first.ts", "second.ts"])
await expect(group.locator('[data-scope="apply-patch"] [data-type="update"] button')).toHaveAttribute(
"aria-expanded",
"true",
)
await expect(group.locator('[data-scope="apply-patch"] [data-type="add"] button')).toHaveAttribute(
"aria-expanded",
"false",
)
await expect(page.locator(`[data-timeline-part-id="${first}"], [data-timeline-part-id="${second}"]`)).toHaveCount(0)
})
test("combines adjacent edit calls and repeated files into one group", async ({ page }) => {
const first = "prt_edit_first"
const second = "prt_edit_second"
await setupTimeline(page, {
messages: [
userMessage(),
assistantMessage([
toolPart(
first,
"edit",
"completed",
{ path: "src/first.ts", oldString: "one", newString: "two" },
{
metadata: { files: [patchFile("src/first.ts", "modified")] },
},
),
toolPart(
second,
"edit",
"completed",
{ path: "src/first.ts", oldString: "two", newString: "three" },
{
metadata: { files: [patchFile("src/first.ts", "modified")] },
},
),
]),
],
settings: { editToolPartsExpanded: true },
})
const group = page.locator(`[data-timeline-part-ids="${first},${second}"]`)
await expect(group.locator('[data-slot="basic-tool-tool-title"]')).toContainText("Edit")
await expect(group.getByText("1 file", { exact: true })).toBeVisible()
await expect(group.locator('[data-slot="apply-patch-filename"]')).toHaveText(["first.ts"])
await expect(group.locator('[data-scope="apply-patch"] button')).toHaveAttribute("aria-expanded", "true")
})
test("projects gaps, dividers, assistant parts, and errors together", async ({ page }) => {
const firstUser = userMessage(
[
@@ -196,11 +314,7 @@ function patchPart(id: string) {
{ patchText: "Update the projected files" },
{
metadata: {
files: [
patchFile("src/a.ts", "modified"),
patchFile("src/b.ts", "added"),
patchFile("src/old.ts", "deleted"),
],
files: [patchFile("src/a.ts", "modified")],
},
},
)
@@ -98,9 +98,9 @@ test("labels completed searches with result counts", async ({ page }) => {
const group = page.locator(`[data-timeline-part-ids="${glob},${grep}"]`)
await group.locator('[data-slot="collapsible-trigger"]').click()
const rows = group.locator('[data-component="tool-trigger"]')
await expect(rows.nth(0)).toContainText("(1 match)")
await expect(rows.nth(1)).toContainText("(12 matches)")
const rows = group.locator('[data-component="context-tool-group-list"] [data-component="tool-trigger"]')
await expect(rows.filter({ hasText: "Glob" })).toContainText("(1 match)")
await expect(rows.filter({ hasText: "Grep" })).toContainText("(12 matches)")
})
test("labels read tools from their path input", async ({ page }) => {
@@ -111,7 +111,11 @@ test("labels read tools from their path input", async ({ page }) => {
const group = page.locator(`[data-timeline-part-ids="${id}"]`)
await group.locator('[data-slot="collapsible-trigger"]').click()
await expect(group.locator('[data-slot="basic-tool-tool-subtitle"]')).toHaveText("a.ts")
await expect(
group
.locator('[data-component="context-tool-group-list"] [data-component="tool-trigger"]')
.filter({ hasText: "Read" }),
).toContainText("a.ts")
})
test("labels skill tools from IDs and result metadata", async ({ page }) => {
@@ -121,25 +125,23 @@ test("labels skill tools from IDs and result metadata", async ({ page }) => {
messages: [
userMessage(),
assistantMessage([
toolPart(pending, "skill", "running", { id: "sample-skill" }),
toolPart(pending, "skill", "running", { id: "frontend-design" }),
toolPart(completed, "skill", "completed", { id: "opencode" }, { metadata: { name: "OpenCode" } }),
]),
],
})
await expect(page.locator(`[data-timeline-part-id="${pending}"] [data-component="text-shimmer"]`)).toHaveAttribute(
"aria-label",
"sample-skill",
)
await expect(page.locator(`[data-timeline-part-id="${completed}"] [data-component="text-shimmer"]`)).toHaveAttribute(
"aria-label",
"OpenCode",
)
for (const id of [pending, completed]) {
for (const [id, name] of [
[pending, "frontend-design"],
[completed, "OpenCode"],
] as const) {
const skill = page.locator(`[data-timeline-part-id="${id}"]`)
await expect(skill.locator('[data-slot="skill-tool-label"]')).toHaveText("Skill")
await expect(skill.locator('[data-slot="skill-tool-separator"]')).toHaveText("·")
await expect(skill.locator('use[href="#opencode-v2-icon-post-skill"]')).toBeVisible()
const loaded = skill.locator('[data-component="tool-loaded-item"]')
await expect(loaded).toHaveAttribute("aria-label", `Loaded ${name} skill`)
await expect(loaded).toHaveCSS("line-height", "16px")
await expect(loaded.locator('[data-slot="tool-loaded-label"]')).toHaveText("Loaded")
await expect(loaded.locator('[data-slot="tool-loaded-kind"]')).toHaveText("skill")
await expect(loaded.locator('[data-component="text-shimmer"]')).toHaveAttribute("aria-label", name)
}
})
@@ -26,6 +26,42 @@ test("navigates to a subagent child session missing from the session list", asyn
await expect(titlebarRight.getByRole("button", { name: "Toggle review" })).toHaveCount(1)
})
test("returns to the parent session with Escape", async ({ page }) => {
await setup(page)
await openChildFromParent(page)
await expectSessionTitle(page, taskDescription)
await page.keyboard.press("Escape")
await Promise.all([expect(page).toHaveURL(sessionHref(parentID)), expectSessionTitle(page, parentTitle)])
})
test("shows parent lineage while the child timeline loads", async ({ page }) => {
await setup(page)
const requested = Promise.withResolvers<void>()
const release = Promise.withResolvers<void>()
await page.route(
(url) =>
url.pathname === `/api/session/${childID}/message` &&
url.port === (process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"),
async (route) => {
requested.resolve()
await release.promise
await route.fallback()
},
)
await page.goto(sessionHref(parentID))
await expectSessionTitle(page, parentTitle)
await page.locator(`a[href="${sessionHref(childID)}"]`).click()
await Promise.all([requested.promise, expect(page).toHaveURL(sessionHref(childID))])
await Promise.all([
expect(page.locator('[data-slot="session-title-parent"]')).toHaveText(parentTitle),
expect(page.locator('[data-slot="session-title-child"]')).toHaveText(childTitle),
]).finally(() => release.resolve())
await expectSessionTitle(page, taskDescription)
})
test("keeps the parent visible while the child session resolves", async ({ page }) => {
await setup(page)
const requested = Promise.withResolvers<void>()
@@ -50,6 +86,44 @@ test("keeps the parent visible while the child session resolves", async ({ page
await expectSessionTitle(page, taskDescription)
})
test("keeps the parent tab selected while a loaded child session resolves", async ({ page }) => {
await setup(page)
await openChildFromParent(page)
await expectSessionTitle(page, taskDescription)
await page.goBack()
await Promise.all([expect(page).toHaveURL(sessionHref(parentID)), expectSessionTitle(page, parentTitle)])
const requested = Promise.withResolvers<void>()
const release = Promise.withResolvers<void>()
await page.route(
(url) => url.pathname === `/api/session/${childID}` && url.port === (process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"),
async (route) => {
requested.resolve()
await release.promise
await route.fallback()
},
)
const parentTab = page.locator("[data-titlebar-tab-slot]", {
has: page.locator('[data-slot="tab-title"]', { hasText: parentTitle }),
})
await page.locator(`a[href="${sessionHref(childID)}"]`).click()
await Promise.all([requested.promise, expect(page).toHaveURL(sessionHref(childID))])
await Promise.all([
expect(parentTab).toHaveAttribute("data-active", "true"),
expect(page.locator('[data-slot="session-title-parent"]')).toHaveText(parentTitle),
]).finally(() => release.resolve())
await expectSessionTitle(page, taskDescription)
const home = page.getByRole("button", { name: "Home" })
await home.click()
await expect(page).toHaveURL("/")
const childTab = page.locator(`[data-slot="titlebar-tabs"] a[href="${sessionHref(childID)}"]`)
await expect(childTab).toHaveCount(1)
await childTab.click()
await Promise.all([expect(page).toHaveURL(sessionHref(childID)), expectSessionTitle(page, taskDescription)])
})
test("shows the not found fallback when the viewed session is deleted", async ({ page }) => {
const events: OpenCodeEvent[] = []
await setup(page, () => events.splice(0, 1))
@@ -9,10 +9,14 @@ const sessionID = "ses_terminal_composer_focus"
const ptyID = "pty_terminal_composer_focus"
const newPtyID = "pty_terminal_composer_focus_new"
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
const ptyInput: string[] = []
let sendPtyOutput: ((data: string) => void) | undefined
test.use({ viewport: { width: 1440, height: 900 } })
test.beforeEach(async ({ page }) => {
ptyInput.length = 0
sendPtyOutput = undefined
await mockOpenCodeServer(page, {
directory,
project: {
@@ -70,7 +74,50 @@ test.beforeEach(async ({ page }) => {
body: JSON.stringify({ location: ptyLocation(), data: { ticket: "e2e-ticket", expires_in: 60 } }),
}),
)
await page.routeWebSocket(new RegExp(`/api/pty/${ptyID}/connect`), () => undefined)
await page.routeWebSocket(new RegExp(`/api/pty/${ptyID}/connect`), (ws) => {
ws.onMessage((message) => ptyInput.push(message.toString()))
sendPtyOutput = (data) => ws.send(data)
})
})
test("clears the terminal line with Command+Delete", async ({ page }) => {
await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`)
await expectSessionTitle(page, "Terminal composer focus")
const terminal = page.locator('[data-component="terminal"]')
await page.keyboard.press("Control+Backquote")
await expect(terminal.locator("textarea")).toHaveCount(1)
await page.keyboard.press("Meta+Backspace")
await expect.poll(() => ptyInput.join("")).toBe("\x15")
})
test("hides the native contenteditable caret", async ({ page }) => {
await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`)
await expectSessionTitle(page, "Terminal composer focus")
await page.keyboard.press("Control+Backquote")
const terminal = page.locator('[data-component="terminal"]')
await expect(terminal).toHaveAttribute("contenteditable", "true")
await expect(terminal).toHaveCSS("caret-color", "rgba(0, 0, 0, 0)")
})
test("reveals the terminal after its first server output renders", async ({ page }) => {
await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`)
await expectSessionTitle(page, "Terminal composer focus")
await page.keyboard.press("Control+Backquote")
const terminal = page.locator('[data-component="terminal"]')
await expect(terminal).toHaveAttribute("contenteditable", "true")
await expect(terminal).toHaveCSS("opacity", "0")
await expect.poll(() => sendPtyOutput).toBeDefined()
sendPtyOutput?.("\x1b[?25h")
await expect(terminal).toHaveCSS("opacity", "0")
sendPtyOutput?.("ready")
await expect(terminal).toHaveCSS("opacity", "1")
})
test("routes typing to the composer unless the open terminal is focused", async ({ page }) => {
@@ -358,7 +358,8 @@ test.describe("smoke: session timeline", () => {
const shellTrigger = shell.locator('[data-slot="collapsible-trigger"]')
const shellSubtitle = shell.locator('[data-slot="basic-tool-tool-subtitle"]')
await expect(shellSubtitle).toHaveCount(0)
await expect(shell.locator('[data-slot="bash-pre"]')).toContainText("$ bun typecheck")
await expect(shell.locator('[data-slot="bash-command"]')).toHaveText("bun typecheck")
await expect(shell.locator('[data-slot="bash-result"]')).not.toContainText("bun typecheck")
await shellTrigger.click()
await expect(shellTrigger).toHaveAttribute("aria-expanded", "false")
await expect(shellSubtitle).toHaveText("bun typecheck")
+2
View File
@@ -48,9 +48,11 @@
"typescript": "catalog:",
"vite": "8.2.1",
"vite-plugin-icons-spritesheet": "3.0.1",
"vite-plugin-pwa": "1.3.0",
"vite-plugin-solid": "2.11.14"
},
"dependencies": {
"@ibm/plex": "6.4.1",
"@corvu/drawer": "catalog:",
"@dnd-kit/abstract": "0.5.0",
"@dnd-kit/dom": "0.5.0",
+11
View File
@@ -15,3 +15,14 @@
/*.css
Content-Type: text/css
/site.webmanifest
Content-Type: application/manifest+json
/sw.js
Content-Type: application/javascript
Cache-Control: no-cache
/registerSW.js
Content-Type: application/javascript
Cache-Control: no-cache
+7 -1
View File
@@ -1,4 +1,5 @@
import type { Data } from "@opencode-ai/client/solid"
import type { SessionMessageUser } from "@opencode-ai/client/promise"
import type { Accessor } from "solid-js"
import type { ModelSelection } from "@/providers/models/selection"
import type { ServerSDK } from "@/runtime/server/client"
@@ -41,6 +42,10 @@ export type ComposerSelection = {
export type ComposerSession = {
id: string
directory: string
handoff?: {
set: (message: SessionMessageUser) => void
clear: (messageID: string) => void
}
api: {
command: (input: Parameters<ServerSDK["api"]["session"]["command"]>[0]) => Promise<unknown>
shell: (input: Parameters<ServerSDK["api"]["session"]["shell"]>[0]) => Promise<unknown>
@@ -51,6 +56,7 @@ export type ComposerSession = {
location: { command: Pick<Data["location"]["command"], "list"> }
session: {
prompt: (input: Parameters<Data["session"]["prompt"]>[0]) => Promise<unknown>
setStatus: Data["session"]["setStatus"]
}
}
current: Accessor<{ agent?: string; model?: { id: string; providerID: string; variant?: string } } | undefined>
@@ -77,7 +83,7 @@ export type NewSessionComposerAdapter = ComposerAdapterBase & {
start: (
selection: ComposerSelection,
submission: ReturnType<typeof createComposerSubmission>,
) => Promise<ComposerSession | undefined>
) => Promise<{ session: ComposerSession; cleanupReady: Promise<void> } | undefined>
}
export type ComposerAdapter = ActiveComposerAdapter | NewSessionComposerAdapter
+1 -1
View File
@@ -29,7 +29,7 @@ export function Composer(props: {
accentSubmit={props.accentSubmit}
borderUnderlay={props.borderUnderlay}
class={props.class}
variantControlVisible={!props.model.model.loading}
modelControlsVisible={!props.model.model.loading}
attachKeybind={command.keybindParts("file.attach")}
attachShortcut={command.keybind("file.attach")}
modelControl={
+14 -12
View File
@@ -42,7 +42,7 @@ export type ComposerEditorProps = {
borderUnderlay?: boolean
class?: string
modelControl?: JSX.Element
variantControlVisible?: boolean
modelControlsVisible?: boolean
attachKeybind?: string[]
attachShortcut?: string
}
@@ -233,17 +233,19 @@ export function ComposerEditor(props: ComposerEditorProps) {
/>
)}
</Show>
{props.modelControl}
<Show when={(props.variantControlVisible ?? true) && view.variant} keyed>
{(control) => (
<Show when={control.options().length > 1}>
<ComposerEditorConfiguredSelect
title={i18n.t("ui.promptInput.chooseVariant")}
keybind={["Shift", "Mod", "D"]}
control={control}
/>
</Show>
)}
<Show when={props.modelControlsVisible ?? true}>
{props.modelControl}
<Show when={view.variant} keyed>
{(control) => (
<Show when={control.options().length > 1}>
<ComposerEditorConfiguredSelect
title={i18n.t("ui.promptInput.chooseVariant")}
keybind={["Shift", "Mod", "D"]}
control={control}
/>
</Show>
)}
</Show>
</Show>
</div>
<ComposerEditorSubmitButton
+68 -6
View File
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test"
import type { ModelSelection } from "@/providers/models/selection"
import type { SessionMessageUser } from "@opencode-ai/client/promise"
import { Skill } from "@opencode-ai/schema/skill"
import type { ActiveComposerAdapter, ComposerControls, ComposerSession, NewSessionComposerAdapter } from "./adapter"
import { createMemoryComposerState } from "./state"
@@ -69,6 +70,8 @@ function submitInput(
function session(input: {
calls: string[]
prompt: (value: Parameters<ComposerSession["data"]["session"]["prompt"]>[0]) => Promise<void>
handoff?: ComposerSession["handoff"]
statuses?: ("idle" | "running")[]
current?: ComposerSession["current"]
admitted?: (messageID: string) => boolean
shell?: () => Promise<unknown>
@@ -77,6 +80,7 @@ function session(input: {
return {
id: "session-1",
directory: "C:/repo",
handoff: input.handoff,
current: input.current ?? (() => undefined),
admitted: input.admitted ?? (() => false),
api: {
@@ -92,6 +96,7 @@ function session(input: {
data: {
location: { command: { list: () => [] } },
session: {
setStatus: (_sessionID, status) => input.statuses?.push(status),
prompt: async (value) => {
input.calls.push("prompt")
await input.prompt(value)
@@ -140,10 +145,12 @@ describe("Composer submission", () => {
test("starts and promotes a New Session once before admitting its first prompt", async () => {
const draft = createMemoryComposerState({ prompt: "first prompt" }).capture()
const promoted = createMemoryComposerState().capture()
const promoted = createMemoryComposerState({ prompt: "restored draft" }).capture()
const calls: string[] = []
const statuses: ("idle" | "running")[] = []
const admitted = Promise.withResolvers<Parameters<ComposerSession["data"]["session"]["prompt"]>[0]>()
const target = session({ calls, prompt: async (value) => admitted.resolve(value) })
const cleanupReady = Promise.withResolvers<void>()
const target = session({ calls, statuses, prompt: async (value) => admitted.resolve(value) })
const adapter: NewSessionComposerAdapter = {
kind: "new-session",
state: draft,
@@ -156,20 +163,72 @@ describe("Composer submission", () => {
async start(_selection, submission) {
calls.push("start")
submission.retarget(promoted)
return target
return { session: target, cleanupReady: cleanupReady.promise }
},
}
await submitInput(adapter).submit(new Event("submit"))
const submitted = submitInput(adapter).submit(new Event("submit"))
const request = await admitted.promise
expect(calls).toEqual(["start", "submitted", "switch-agent", "switch-model", "prompt"])
expect(calls).toEqual(["start", "switch-agent", "switch-model", "prompt"])
expect(statuses).toEqual(["running"])
expect(promoted.current()).toMatchObject([{ type: "text", content: "restored draft" }])
cleanupReady.resolve()
await submitted
expect(calls).toEqual(["start", "switch-agent", "switch-model", "prompt", "submitted"])
expect(request.delivery).toBe("steer")
expect(request.text).toBe("first prompt")
expect(draft.current()).toEqual([{ type: "text", content: "", start: 0, end: 0 }])
expect(promoted.current()).toEqual([{ type: "text", content: "", start: 0, end: 0 }])
})
test("hands off image-only first prompts before admission", async () => {
const draft = createMemoryComposerState().capture()
draft.set([
{ type: "text", content: "", start: 0, end: 0 },
{
type: "image",
id: "attachment",
filename: "image.png",
mime: "image/png",
blob: { id: "attachment", url: "data:image/png;base64,YQ==" },
},
])
const handedOff = Promise.withResolvers<SessionMessageUser>()
const target = session({
calls: [],
handoff: { set: handedOff.resolve, clear() {} },
prompt: async () => undefined,
})
const adapter: NewSessionComposerAdapter = {
kind: "new-session",
state: draft,
ready: () => true,
controls,
working: () => false,
submitted() {},
async start() {
return { session: target, cleanupReady: Promise.resolve() }
},
}
await submitInput(adapter).submit(new Event("submit"))
expect(await handedOff.promise).toMatchObject({
type: "user",
text: "",
files: [
{
data: "",
mime: "image/png",
source: { type: "uri", uri: "data:image/png;base64,YQ==" },
name: "image.png",
},
],
})
})
test("does not restore a prompt already acknowledged by the durable inbox", async () => {
const state = createMemoryComposerState({ prompt: "admitted prompt" }).capture()
const checked = Promise.withResolvers<void>()
@@ -233,7 +292,7 @@ describe("Composer submission", () => {
submitted() {},
async start(_selection, submission) {
submission.retarget(promoted)
return target
return { session: target, cleanupReady: Promise.resolve() }
},
}
@@ -250,10 +309,12 @@ describe("Composer submission", () => {
test("reuses the message ID when an unacknowledged admission is retried", async () => {
const state = createMemoryComposerState({ prompt: "retry me" }).capture()
const attempts: string[] = []
const statuses: ("idle" | "running")[] = []
const first = Promise.withResolvers<void>()
const second = Promise.withResolvers<void>()
const target = session({
calls: [],
statuses,
prompt: async (value) => {
attempts.push(value.id ?? "")
throw new Error("network unavailable")
@@ -283,6 +344,7 @@ describe("Composer submission", () => {
expect(attempts).toHaveLength(4)
expect(new Set(attempts).size).toBe(1)
expect(statuses).toEqual(["running", "idle", "running", "idle"])
expect(state.current()).toMatchObject([{ type: "text", content: "retry me" }])
})
+58 -12
View File
@@ -1,4 +1,5 @@
import { SessionMessage } from "@opencode-ai/schema/session-message"
import type { SessionMessageUser } from "@opencode-ai/client/promise"
import { Event } from "@opencode-ai/schema/event"
import type { Accessor } from "solid-js"
import type { PromptHistoryComment } from "./history/entry"
@@ -65,15 +66,43 @@ export function createComposerSubmit(input: ComposerSubmitInput) {
const comments = input.comments.capture()
try {
const session =
const started =
input.adapter.kind === "active-session"
? input.adapter.session()
? { session: input.adapter.session(), cleanupReady: Promise.resolve() }
: await input.adapter.start(value.selection, submission)
if (!session) return
if (!started) return
const session = started.session
input.addToHistory(value.prompt, value.mode)
input.resetHistory()
const restore = () => restoreSubmission(input, submission, value, comments)
const command = value.mode === "normal" ? findCommand(session, value.text) : undefined
if (value.mode === "normal" && !command) {
if (value.images.length > 0) session.handoff?.set(handoffMessage(value))
const optimisticBusy = !input.adapter.working()
if (optimisticBusy) session.data.session.setStatus(session.id, "running")
const sending = sendPrompt(session, value).then(
() => ({ ok: true as const }),
(error) => ({ ok: false as const, error }),
)
await started.cleanupReady
input.adapter.submitted()
submission.context
.filter((item) => !!item.comment?.trim())
.forEach((item) => submission.target().context.remove(item.key))
input.comments.clear()
clearSubmission(input, submission)
void sending.then((result) => {
if (!result.ok)
failSubmission(input, session, "prompt", result.error, restore, value.id, () => {
if (optimisticBusy) session.data.session.setStatus(session.id, "idle")
})
})
return
}
await started.cleanupReady
input.adapter.submitted()
if (value.mode === "shell") {
@@ -82,7 +111,6 @@ export function createComposerSubmit(input: ComposerSubmitInput) {
return
}
const command = findCommand(session, value.text)
if (command) {
clearSubmission(input, submission)
void sendCommand(session, value, command).catch((error) =>
@@ -91,14 +119,6 @@ export function createComposerSubmit(input: ComposerSubmitInput) {
return
}
submission.context
.filter((item) => !!item.comment?.trim())
.forEach((item) => submission.target().context.remove(item.key))
input.comments.clear()
clearSubmission(input, submission)
void sendPrompt(session, value).catch((error) =>
failSubmission(input, session, "prompt", error, restore, value.id),
)
} finally {
submitting.delete(input.adapter.state)
}
@@ -110,6 +130,29 @@ export function createComposerSubmit(input: ComposerSubmitInput) {
}
}
function handoffMessage(value: ComposerSubmission): SessionMessageUser {
return {
id: value.id,
type: "user",
text: value.text,
files: value.images.map((image) => ({
data: "",
mime: image.mime,
source: { type: "uri", uri: image.blob.url },
name: image.sourcePath ?? image.filename,
})),
metadata: {
displayText: value.text,
agent: value.selection.agent,
model: {
...value.selection.model,
...(value.selection.variant ? { variant: value.selection.variant } : {}),
},
},
time: { created: Date.now() },
}
}
function readSubmission(
input: ComposerSubmitInput,
prompt: Prompt,
@@ -313,8 +356,11 @@ function failSubmission(
error: unknown,
restore: () => boolean,
messageID?: string,
rollback?: () => void,
) {
if (messageID && session.admitted(messageID)) return
if (messageID) session.handoff?.clear(messageID)
rollback?.()
restore()
input.notify.failed(kind, error)
}
+1
View File
@@ -1,6 +1,7 @@
export { AppBaseProviders, AppInterface, preloadRoute } from "./app"
export { ACCEPTED_FILE_EXTENSIONS } from "./runtime/platform/file-picker"
export { useCommand } from "./shell/commands/command"
export { currentRoute, type LayoutRoute, useCurrentRoute } from "./shell/state/layout"
export { loadLocaleDict, normalizeLocale, type Locale, useLanguage } from "./runtime/i18n/language"
export { type FatalRendererErrorLog, type Platform, PlatformProvider } from "./runtime/platform/platform"
export { ServerConnection, useServers } from "./runtime/server/registry"
+7 -1
View File
@@ -42,6 +42,10 @@ const clearAuthToken = () => {
const web = createWebPlatform(pkg.version)
if (import.meta.env.PROD && "serviceWorker" in navigator) {
window.addEventListener("load", () => void navigator.serviceWorker.register("/sw.js"), { once: true })
}
if (import.meta.env.VITE_SENTRY_DSN) {
init({
dsn: import.meta.env.VITE_SENTRY_DSN,
@@ -61,7 +65,9 @@ if (import.meta.env.VITE_SENTRY_DSN) {
})
}
if (root instanceof HTMLElement) {
if (root instanceof HTMLElement && root.dataset.opencodeMounted === undefined) {
// Lazy chunks can import the entry chunk back under a distinct URL, so claim the root before async startup.
root.dataset.opencodeMounted = ""
void loadInitialLocale().then((locale) => {
const auth = authFromToken(new URLSearchParams(location.search).get("auth_token"))
clearAuthToken()
+3 -2
View File
@@ -3,7 +3,7 @@ import { type HomeProjectSelection, useLayout } from "@/shell/state/layout"
import { ServerConnection } from "@/runtime/server/registry"
import { useTabs } from "@/shell/tabs/tabs"
import { toggleHomeProjectSelection } from "@/shell/layout/helpers"
import { createEffect, createMemo } from "solid-js"
import { createEffect, createMemo, startTransition } from "solid-js"
export function createHomeController() {
const layout = useLayout()
@@ -50,7 +50,8 @@ export function createHomeController() {
selection: {
value: selection,
set: setSelection,
focusServer: (conn: ServerConnection.Any) => setSelection({ server: ServerConnection.key(conn) }),
focusServer: (conn: ServerConnection.Any) =>
void startTransition(() => setSelection({ server: ServerConnection.key(conn) })),
},
server: {
list: global.servers.list,
@@ -30,6 +30,7 @@ export function HomeCommandPalette(props: {
opened: server.projects.list,
stored: () => server.sync.data.project,
load: (search, signal) => server.sdk.api.session.list({ parentID: null, search, limit: 50 }, { signal }),
get: (sessionID, signal) => server.sdk.api.session.get({ sessionID }, { signal }),
untitled: () => language.t("command.session.new"),
category: () => language.t("command.category.session"),
})
@@ -137,6 +137,17 @@ export function createHomeSessionsController(home: HomeController) {
showProjectName: () => !home.project.selected(),
server: () => home.selection.value().server,
canCreate: () => !!home.project.newSession(),
lookup: async (sessionID: string) => {
const ctx = home.server.focusedContext()
if (!ctx) return
const result = await ctx.sdk.api.session.get({ sessionID })
if (result.time.archived) return
return buildHomeSessionRecords({
sessions: () => [result],
projectDirectories,
projects: home.project.list,
})[0]
},
create: home.project.openNewSession,
open: (session: SessionInfo, options?: OpenSessionOptions) => {
const directoryKey = pathKey(session.location.directory)
+35 -5
View File
@@ -8,13 +8,21 @@ import { createMemo, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import type { HomeController } from "../model"
import { homeSessionSearchKey, type HomeSessionRecord, type HomeSessionsController } from "./controller"
import { looksLikeSessionID } from "@/session/search"
type HomeSessionSearchSource = Pick<HomeSessionsController, "data" | "session">
export function createHomeSessionSearchController(home: HomeController, sessions: HomeSessionSearchSource) {
const command = useCommand()
const language = useLanguage()
const [state, setState] = createStore({ value: "", focused: false, highlighted: "" })
const [state, setState] = createStore({
value: "",
focused: false,
highlighted: "",
exact: undefined as HomeSessionRecord | undefined,
lookingUp: false,
})
let lookup = 0
let root: HTMLDivElement | undefined
let input: HTMLInputElement | undefined
let list: HTMLDivElement | undefined
@@ -22,9 +30,11 @@ export function createHomeSessionSearchController(home: HomeController, sessions
const results = createMemo(() => {
const value = query().toLowerCase()
if (!value) return []
return sessions.data
const records = sessions.data
.searchRecords()
.filter((record) => `${sessionLabel(record.session)} ${record.projectName}`.toLowerCase().includes(value))
if (!state.exact || records.some((record) => record.session.id === state.exact?.session.id)) return records
return [state.exact, ...records]
})
const active = createMemo(() => {
const records = results()
@@ -50,6 +60,7 @@ export function createHomeSessionSearchController(home: HomeController, sessions
close()
}),
)
onCleanup(() => lookup++)
command.register("home.search", () => [
{
@@ -67,7 +78,26 @@ export function createHomeSessionSearchController(home: HomeController, sessions
}
function close() {
setState({ value: "", focused: false })
lookup++
setState({ value: "", focused: false, exact: undefined, lookingUp: false })
}
function update(value: string) {
const current = ++lookup
const sessionID = value.trim()
setState({ value, highlighted: "", exact: undefined, lookingUp: false })
if (!looksLikeSessionID(sessionID)) return
setState("lookingUp", true)
void sessions.session.lookup(sessionID).then(
(record) => {
if (current !== lookup) return
setState({ exact: record, lookingUp: false })
},
() => {
if (current !== lookup) return
setState("lookingUp", false)
},
)
}
function select(record: HomeSessionRecord, options?: { background?: boolean }) {
@@ -81,11 +111,11 @@ export function createHomeSessionSearchController(home: HomeController, sessions
placeholder,
open,
focus,
input: (value: string) => setState({ value, highlighted: "" }),
input: update,
close,
},
result: {
loading: sessions.data.loading,
loading: () => sessions.data.loading() || state.lookingUp,
list: results,
active,
noResultsLabel: () => language.t("home.sessions.search.noResults", { query: query() }),
+1 -1
View File
@@ -509,7 +509,7 @@ function HomeSessionsEmpty(props: { onNewSession?: () => void; language: ReturnT
<div class="flex min-h-full flex-col items-center gap-4 px-6 pt-[52px] text-center">
<div
class={`
shrink-0 text-[13px] leading-[13px] tracking-[-0.04px]
shrink-0 text-[13px] leading-text-compact tracking-[-0.04px]
text-v2-text-text-base [font-weight:530]
`}
>
+8
View File
@@ -17,6 +17,14 @@
font-style: normal;
}
@font-face {
font-family: "IBM Plex Mono";
src: url("@ibm/plex/IBM-Plex-Mono/fonts/complete/woff2/IBMPlexMono-Text.woff2") format("woff2");
font-weight: 440;
font-style: normal;
font-display: swap;
}
@media (display-mode: standalone) {
/* WebKit excludes safe-area insets from dvh in installed apps. */
#root {
@@ -1,5 +1,6 @@
import { base64Encode } from "@opencode-ai/util/encode"
import { getDirectory } from "@opencode-ai/util/path"
import type { SessionMessageUser } from "@opencode-ai/client/promise"
import { startTransition } from "solid-js"
import type { NewSessionComposerAdapter } from "@/composer/adapter"
import { useComposerState } from "@/composer/persistence"
@@ -9,11 +10,13 @@ import { useLanguage } from "@/runtime/i18n/language"
import { useLocal } from "@/providers/models/selection"
import { usePermission } from "@/session/requests/permission"
import { useData, useServer } from "@/runtime/server/current"
import { useServerSDK } from "@/runtime/server/client"
import { type ServerSDK, useServerSDK } from "@/runtime/server/client"
import { useTabs } from "@/shell/tabs/tabs"
import { useWorkspaceLocation } from "@/workspaces/location"
import { useSessionKey } from "@/session/session-layout"
import { showToast } from "@/shell/notifications/toast"
import { SessionRouteKey, SessionStateKey } from "@/runtime/server/scope"
import { clearSessionMessageHandoff, setSessionMessageHandoff } from "@/session/handoff"
export function createNewSessionComposerAdapter(props: {
draftID: string
@@ -53,26 +56,35 @@ export function createNewSessionComposerAdapter(props: {
})
if (!sessionDirectory) return
const created = await serverSDK.api.session
.create({
agent: selection.agent,
model: {
id: selection.model.modelID,
providerID: selection.model.providerID,
variant: selection.variant,
},
location: { directory: sessionDirectory },
})
.catch((error) => {
const created = data.session.create({
agent: selection.agent,
model: {
id: selection.model.modelID,
providerID: selection.model.providerID,
variant: selection.variant,
},
location: { directory: sessionDirectory },
})
const creation = created.request.then(
() => ({ ok: true as const }),
(error) => {
showToast({
title: language.t("prompt.toast.sessionCreateFailed.title"),
description: errorMessage(language, error),
})
})
if (!created) return
data.session.remember(created)
await startTransition(() => {
return { ok: false as const, error }
},
)
const afterCreation = async <T,>(run: () => Promise<T>) => {
const result = await creation
if (!result.ok) throw result.error
return run()
}
const sessionKey = SessionStateKey.from(
serverSDK.scope,
SessionRouteKey.fromRoute(base64Encode(sessionDirectory), created.id),
)
const cleanupReady = startTransition(() => {
tabs.updateDraft(props.draftID, { worktree: undefined })
if (permission.isAutoAcceptingDirectory(projectDirectory)) {
permission.enableAutoAccept(created.id, sessionDirectory)
@@ -92,13 +104,32 @@ export function createNewSessionComposerAdapter(props: {
})
return {
id: created.id,
directory: sessionDirectory,
api: serverSDK.api.session,
data,
current: () => data.session.get(created.id) ?? created,
admitted: (messageID) =>
data.session.input.has(created.id, messageID) || !!data.session.message.get(created.id, messageID),
cleanupReady,
session: {
id: created.id,
directory: sessionDirectory,
handoff: createMessageHandoff(sessionKey, created.id, serverSDK.event),
api: {
command: (input) => afterCreation(() => serverSDK.api.session.command(input)),
shell: (input) => afterCreation(() => serverSDK.api.session.shell(input)),
switchAgent: (input) => afterCreation(() => serverSDK.api.session.switchAgent(input)),
switchModel: (input) => afterCreation(() => serverSDK.api.session.switchModel(input)),
},
data: {
location: data.location,
session: {
setStatus: data.session.setStatus,
prompt: (input) =>
data.session.prompt({
...input,
gate: Promise.all([input.gate, afterCreation(async () => undefined)]),
}),
},
},
current: () => data.session.get(created.id),
admitted: (messageID) =>
data.session.input.has(created.id, messageID) || !!data.session.message.get(created.id, messageID),
},
}
},
}
@@ -111,6 +142,27 @@ export function createNewSessionComposerAdapter(props: {
}
}
function createMessageHandoff(key: string, sessionID: string, event: ServerSDK["event"]) {
let unsubscribe: VoidFunction | undefined
return {
set(message: SessionMessageUser) {
unsubscribe?.()
setSessionMessageHandoff(key, message)
unsubscribe = event.on("session.inbox.enqueued", (item) => {
if (item.data.sessionID !== sessionID || item.data.inboxID !== message.id) return
unsubscribe?.()
unsubscribe = undefined
clearSessionMessageHandoff(key, message.id)
})
},
clear(messageID: string) {
unsubscribe?.()
unsubscribe = undefined
clearSessionMessageHandoff(key, messageID)
},
}
}
async function resolveSessionDirectory(input: {
projectDirectory: string
worktree: string
+1 -3
View File
@@ -1,6 +1,5 @@
import { createPromptProjectController } from "@/new-session/project/selector"
import { useSettingsDialog } from "@/settings/command"
import { useTitlebarRightMount } from "@/shell/titlebar/titlebar"
import { useSettings } from "@/settings/model"
import { useTabs, type DraftTab } from "@/shell/tabs/tabs"
import { useSearchParams } from "@solidjs/router"
@@ -15,7 +14,6 @@ import { useNewSessionCommands } from "./commands"
/** The draft-only Session page. Submitting promotes the draft into a real Session. */
export default function NewSessionPage(props: { draftId: string }) {
const settings = useSettings()
const rightMount = useTitlebarRightMount()
const [search, setSearch] = useSearchParams<{ draftId?: string; prompt?: string }>()
const tabs = useTabs()
const openWorkspaces = useSettingsDialog("workspaces")
@@ -69,7 +67,7 @@ export default function NewSessionPage(props: { draftId: string }) {
return (
<div class="relative size-full overflow-hidden flex flex-col">
{suspendUntilPromptReady()}
<NewSessionStatus mount={rightMount()} visible={settings.visibility.status()} />
<NewSessionStatus visible={settings.visibility.status()} />
<div class="flex-1 min-h-0 flex flex-col gap-2 p-2">
<NewSessionView composer={model} project={project} workspace={workspace} />
</div>
+10 -15
View File
@@ -4,7 +4,6 @@ import { Icon } from "@opencode-ai/ui/icon"
import { Wordmark } from "@opencode-ai/ui/wordmark"
import { Show, createMemo, createSignal } from "solid-js"
import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web"
import createPresence from "solid-presence"
import { Composer } from "@/composer/composer"
import type { ComposerModel } from "@/composer/model"
@@ -15,6 +14,7 @@ import {
type PromptProjectController,
} from "@/new-session/project/selector"
import { StatusPopover } from "@/shell/status/status-popover"
import { TitlebarRight } from "@/shell/titlebar/right-slot"
import { useLanguage } from "@/runtime/i18n/language"
import { useWorkspaceLocation } from "@/workspaces/location"
import { useProviders } from "@/providers/catalog/providers"
@@ -87,21 +87,16 @@ export function NewSessionView(props: {
)
}
export function NewSessionStatus(props: { mount: HTMLElement | null; visible: boolean }) {
export function NewSessionStatus(props: { visible: boolean }) {
const language = useLanguage()
return (
<Show when={props.mount} keyed>
{(mount) => (
<Portal mount={mount}>
<Show when={props.visible}>
<Tooltip appearance="standard" placement="bottom" value={language.t("status.popover.trigger")}>
<StatusPopover />
</Tooltip>
</Show>
</Portal>
)}
</Show>
<TitlebarRight>
<Show when={props.visible}>
<Tooltip appearance="standard" placement="bottom" value={language.t("status.popover.trigger")}>
<StatusPopover />
</Tooltip>
</Show>
</TitlebarRight>
)
}
@@ -144,7 +139,7 @@ function ProviderTip() {
>
<button
type="button"
class="flex h-6 min-w-0 items-center rounded-[4px] pl-1.5 text-[13px] leading-none tracking-[-0.04px] text-v2-text-text-faint transition-[background-color,color] duration-150 ease-in-out hover:bg-v2-overlay-simple-overlay-hover hover:text-v2-text-text-muted focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:text-v2-text-text-muted focus-visible:outline-none"
class="flex h-6 min-w-0 items-center rounded-[4px] pl-1.5 text-[13px] leading-text-compact tracking-[-0.04px] text-v2-text-text-faint transition-[background-color,color] duration-150 ease-in-out hover:bg-v2-overlay-simple-overlay-hover hover:text-v2-text-text-muted focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:text-v2-text-text-muted focus-visible:outline-none"
onClick={openProviders}
>
<span class="truncate">{language.t("home.providerTip")}</span>
@@ -190,7 +190,7 @@ function ProviderPicker(props: { directory?: string; onSelect: (provider: string
{(group) => (
<Show when={group.items().length > 0}>
<section class="flex flex-col">
<div class="px-3 pb-2 text-[13px] font-[440] leading-none tracking-[-0.04px] text-v2-text-text-muted">
<div class="px-3 pb-2 text-[13px] font-[440] leading-text-compact tracking-[-0.04px] text-v2-text-text-muted">
{group.title}
</div>
<For each={group.items()}>
@@ -198,7 +198,7 @@ function ProviderPicker(props: { directory?: string; onSelect: (provider: string
<button
type="button"
data-provider-id={provider.id}
class="flex min-h-9 w-full items-center gap-2 rounded-md px-3 py-2.5 text-left text-[13px] leading-none tracking-[-0.04px] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
class="flex min-h-9 w-full items-center gap-2 rounded-md px-3 py-2.5 text-left text-[13px] leading-text-compact tracking-[-0.04px] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
classList={{ "bg-v2-overlay-simple-overlay-hover": store.active === provider.id }}
onMouseEnter={() => setStore("active", provider.id)}
disabled={store.connecting !== undefined}
+54 -28
View File
@@ -7,6 +7,7 @@ import { Switch } from "@opencode-ai/ui/switch"
import { TextInput } from "@opencode-ai/ui/text-input"
import { useFilteredList } from "@opencode-ai/ui/hooks"
import { For, Show, type Component } from "solid-js"
import { createStore } from "solid-js/store"
import { useLocal } from "@/providers/models/selection"
import { popularProviders } from "@/providers/catalog/providers"
import { useLanguage } from "@/runtime/i18n/language"
@@ -23,6 +24,7 @@ export const DialogManageModels: Component = () => {
const local = useLocal()
const language = useLanguage()
const dialog = useDialog()
const [store, setStore] = createStore({ collapsed: {} as Record<string, boolean> })
const directory = () => decode64(local.slug())
const handleConnectProvider = () => {
@@ -120,16 +122,38 @@ export const DialogManageModels: Component = () => {
}
>
<For each={list.grouped.latest}>
{(group) => (
<div class="settings-section" data-component="settings-models-provider">
<div class="settings-models-group-header justify-between">
<div class="flex min-w-0 items-center gap-2">
<ProviderIcon id={group.category} width={16} height={16} class="ml-4 shrink-0" />
<h3 class="settings-section-title">{group.items[0].provider.name}</h3>
</div>
<div>
{(group) => {
const searching = () => list.filter().length > 0
const expanded = () => searching() || !store.collapsed[group.category]
return (
<div
class="settings-section"
data-component="settings-models-provider"
data-expanded={expanded() ? "" : undefined}
>
<div class="settings-models-group-header justify-between">
<button
type="button"
class="settings-models-group-trigger"
aria-expanded={expanded()}
disabled={searching()}
onClick={() => setStore("collapsed", group.category, expanded())}
>
<span class="settings-models-group-chevron">
<Icon
name="chevron-down"
size="small"
classList={{ "-rotate-90 rtl:rotate-90": !expanded() }}
/>
</span>
<span class="settings-models-group-label">
<ProviderIcon id={group.category} width={16} height={16} class="shrink-0" />
<span class="settings-section-title">{group.items[0].provider.name}</span>
</span>
</button>
<Switch
class="mr-6"
class="me-6"
checked={providerVisible(group.category)}
onChange={(checked) => setProviderVisibility(group.category, checked)}
hideLabel
@@ -137,26 +161,28 @@ export const DialogManageModels: Component = () => {
{group.items[0].provider.name}
</Switch>
</div>
<Show when={expanded()}>
<SettingsList>
<For each={group.items}>
{(item) => (
<SettingsRow title={item.name} description="">
<div>
<Switch
checked={local.model.visible({ modelID: item.id, providerID: item.provider.id })}
onChange={(checked) => setModelVisibility(item, checked)}
hideLabel
>
{item.name}
</Switch>
</div>
</SettingsRow>
)}
</For>
</SettingsList>
</Show>
</div>
<SettingsList>
<For each={group.items}>
{(item) => (
<SettingsRow title={item.name} description="">
<div>
<Switch
checked={local.model.visible({ modelID: item.id, providerID: item.provider.id })}
onChange={(checked) => setModelVisibility(item, checked)}
hideLabel
>
{item.name}
</Switch>
</div>
</SettingsRow>
)}
</For>
</SettingsList>
</div>
)}
)
}}
</For>
</Show>
</Show>
+7 -36
View File
@@ -1,44 +1,24 @@
import { type Accessor, createMemo, createResource } from "solid-js"
import { createStore } from "solid-js/store"
import { type Accessor, createMemo } from "solid-js"
import { DateTime } from "luxon"
import { filter, firstBy, flat, groupBy, mapValues, pipe, uniqueBy, values } from "remeda"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useProviders } from "@/providers/catalog/providers"
import { Persist, persisted } from "@/runtime/persistence/storage"
import { useGlobal } from "@/runtime/server/runtime"
export type ModelKey = { providerID: string; modelID: string }
type Visibility = "show" | "hide"
type User = ModelKey & { visibility: Visibility; favorite?: boolean }
type Store = {
user: User[]
recent: ModelKey[]
variant?: Record<string, string | undefined>
}
const RECENT_LIMIT = 5
function modelKey(model: ModelKey) {
return `${model.providerID}:${model.modelID}`
}
const createModelsPersistedState = () => {
const [store, setStore, _, ready] = persisted(
Persist.global("model"),
createStore<Store>({
user: [],
recent: [],
variant: {},
}),
)
return [store, setStore, ready] as const
}
const createModelsController = (directory: Accessor<string | undefined>) => {
const providers = useProviders(() => directory())
const [store, setStore, ready] = createModelsPersistedState()
const models = useGlobal().models
const store = models.store
const setStore = models.set
const available = createMemo(() =>
providers.connected().flatMap((p) =>
@@ -148,23 +128,14 @@ const createModelsController = (directory: Accessor<string | undefined>) => {
setStore("variant", key, value)
}
const [recentModels] = createResource(
async () => {
const recent = store.recent
await ready.promise
return recent
},
(p) => p,
{ initialValue: [] },
)
return {
ready,
ready: models.ready,
list,
find,
visible,
setVisibility,
recent: {
list: () => recentModels()!,
list: models.recent,
push,
},
variant: {
@@ -10,9 +10,10 @@ import { Dialog, DialogBody, DialogHeader, DialogTitle } from "@opencode-ai/ui/d
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { List } from "@opencode-ai/ui/list"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Menu } from "@opencode-ai/ui/menu"
import { TextInput } from "@opencode-ai/ui/text-input"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { ModelTooltip } from "./tooltip"
import { useLanguage } from "@/runtime/i18n/language"
import { decode64 } from "@/runtime/persistence/base64"
@@ -20,6 +21,8 @@ import { handleDocumentSearchKeydown } from "@/shell/commands/search-keydown"
import { createMenuDismissController } from "@/shell/commands/menu-dismiss"
import { createEventListener } from "@solid-primitives/event-listener"
import { matchesModelSearch } from "./search"
import { SettingsList } from "@/settings/list"
import "@/settings/settings.css"
const isFree = (provider: string, cost: { input: number } | undefined) =>
provider === "opencode" && (!cost || cost.input === 0)
@@ -44,70 +47,182 @@ const sortModelGroups = (a: { category: string; items: ModelItem[] }, b: { categ
const ModelList: Component<{
provider?: string
class?: string
onSelect: () => void
action?: JSX.Element
model?: ModelState
}> = (props) => {
const model = props.model ?? useLocal().model
const language = useLanguage()
const controller = createModelSelectorController({
model: props.model,
provider: () => props.provider,
onSelect: props.onSelect,
})
const [store, setStore] = createStore({
search: "",
active: "",
collapsed: {} as Record<string, boolean>,
})
const models = createMemo(() => controller.models(store.search))
const groups = createMemo(() => controller.groups(models()))
const expanded = (provider: string) => store.search.length > 0 || !store.collapsed[provider]
const visibleModels = () => models().filter((item) => expanded(item.provider.id))
let scrollRef: HTMLDivElement | undefined
const models = createMemo(() =>
model
.list()
.filter((m) => model.visible({ modelID: m.id, providerID: m.provider.id }))
.filter((m) => (props.provider ? m.provider.id === props.provider : true)),
)
const setSearch = (value: string) => {
const first = controller.models(value).find((item) => value.length > 0 || !store.collapsed[item.provider.id])
setStore({ search: value, active: first ? modelKey(first) : "" })
}
const moveActive = (delta: number) => {
const keys = visibleModels().map(modelKey)
if (keys.length === 0) return
const index = keys.indexOf(store.active)
const start = index === -1 ? (delta > 0 ? -1 : 0) : index
setStore("active", keys[(start + delta + keys.length) % keys.length])
queueMicrotask(() => {
scrollRef
?.querySelector<HTMLElement>(`[data-option-key="${CSS.escape(store.active)}"]`)
?.scrollIntoView({ block: "nearest" })
})
}
const selectActive = () => {
const item = visibleModels().find((item) => modelKey(item) === store.active)
if (item) controller.select(item)
}
return (
<List
class={`flex-1 px-3 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0 ${props.class ?? ""}`}
search={{ placeholder: language.t("dialog.model.search.placeholder"), autofocus: true, action: props.action }}
emptyMessage={language.t("dialog.model.empty")}
key={(x) => `${x.provider.id}:${x.id}`}
items={models}
current={model.current()}
filterKeys={["provider.name", "name", "id"]}
sortBy={(a, b) => a.name.localeCompare(b.name)}
groupBy={(x) => x.provider.name}
sortGroupsBy={(a, b) => {
const aProvider = a.items[0].provider.id
const bProvider = b.items[0].provider.id
if (popularProviders.includes(aProvider) && !popularProviders.includes(bProvider)) return -1
if (!popularProviders.includes(aProvider) && popularProviders.includes(bProvider)) return 1
return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider)
}}
itemWrapper={(item, node) => (
<Tooltip
appearance="standard"
class="w-full"
placement="right-start"
gutter={12}
openDelay={0}
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
>
{node}
</Tooltip>
)}
onSelect={(x) => {
model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
recent: true,
})
props.onSelect()
}}
>
{(i) => (
<div class="w-full flex items-center gap-x-2 text-13-regular">
<span class="truncate">{i.name}</span>
<Show when={isFree(i.provider.id, i.cost)}>
<Badge appearance="standard">{language.t("model.tag.free")}</Badge>
</Show>
<Show when={i.latest}>
<Badge appearance="standard">{language.t("model.tag.latest")}</Badge>
<div class="flex min-h-0 flex-1 flex-col">
<div class="shrink-0 px-4 pt-px pb-3">
<div class="relative">
<TextInput
type="search"
appearance="base"
class="!w-full self-stretch"
placeholder={language.t("dialog.model.search.placeholder")}
value={store.search}
autofocus
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
onInput={(event) => setSearch(event.currentTarget.value)}
onKeyDown={(event) => {
if (event.altKey || event.metaKey) return
if (event.key === "ArrowDown") {
event.preventDefault()
moveActive(1)
return
}
if (event.key === "ArrowUp") {
event.preventDefault()
moveActive(-1)
return
}
if (event.key === "Enter" && !event.isComposing) {
event.preventDefault()
selectActive()
}
}}
aria-label={language.t("dialog.model.search.placeholder")}
/>
<Show when={store.search}>
<IconButton
type="button"
variant="ghost-muted"
size="small"
class="settings-tab-search-clear"
icon={<Icon name="close" size="large" class="text-v2-icon-icon-muted" />}
onClick={() => setSearch("")}
aria-label={language.t("common.clear")}
/>
</Show>
</div>
)}
</List>
</div>
<div class="relative min-h-0 flex-1">
<div ref={(element) => (scrollRef = element)} class="settings-panel settings-models h-full px-4 pt-4 pb-4">
<Show
when={models().length > 0}
fallback={<div class="settings-models-status">{language.t("dialog.model.empty")}</div>}
>
<For each={groups()}>
{(group) => {
const searching = () => store.search.length > 0
const open = () => expanded(group.category)
return (
<section class="settings-section" data-expanded={open() ? "" : undefined}>
<h3 class="settings-models-group-header">
<button
type="button"
class="settings-models-group-trigger"
aria-expanded={open()}
disabled={searching()}
onClick={() => setStore("collapsed", group.category, open())}
>
<span class="settings-models-group-chevron">
<Icon name="chevron-down" size="small" classList={{ "-rotate-90 rtl:rotate-90": !open() }} />
</span>
<span class="settings-models-group-label">
<ProviderIcon id={group.category} width={16} height={16} class="shrink-0" />
<span class="settings-section-title">{group.items[0].provider.name}</span>
</span>
</button>
</h3>
<Show when={open()}>
<SettingsList>
<For each={group.items}>
{(item) => (
<button
type="button"
data-component="settings-row"
data-option-key={modelKey(item)}
class="-mx-4 w-[calc(100%+32px)] px-4 text-start first:rounded-t-lg last:rounded-b-lg hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none"
classList={{ "bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
onMouseEnter={() => setStore("active", modelKey(item))}
onMouseLeave={() => setStore("active", "")}
onClick={() => controller.select(item)}
>
<div data-slot="settings-row-copy">
<div data-slot="settings-row-title" class="flex items-center gap-2">
<Tooltip
placement="right-start"
gutter={12}
openDelay={0}
value={
<ModelTooltip
model={item}
latest={item.latest}
free={isFree(item.provider.id, item.cost)}
v2
/>
}
>
<span class="min-w-0 truncate">{item.name}</span>
</Tooltip>
<Show when={isFree(item.provider.id, item.cost)}>
<Badge class="shrink-0">{language.t("model.tag.free")}</Badge>
</Show>
<Show when={item.latest}>
<Badge class="shrink-0">{language.t("model.tag.latest")}</Badge>
</Show>
</div>
</div>
<div data-slot="settings-row-control" class="size-4">
<Show when={controller.current() === modelKey(item)}>
<Icon name="check" size="small" class="shrink-0 text-v2-icon-icon-base" />
</Show>
</div>
</button>
)}
</For>
</SettingsList>
</Show>
</section>
)
}}
</For>
</Show>
</div>
</div>
</div>
)
}
@@ -431,18 +546,25 @@ export const DialogSelectModel: Component<{ provider?: string; model?: ModelStat
}
return (
<Dialog>
<DialogHeader hideClose>
<Dialog size="large" variant="settings">
<DialogHeader hideClose closeLabel={language.t("common.close")}>
<DialogTitle>{language.t("dialog.model.select.title")}</DialogTitle>
<Button class="h-7 -my-1 text-14-medium" icon="plus-small" tabIndex={-1} onClick={provider}>
<Button icon="plus" onClick={provider}>
{language.t("command.provider.connect")}
</Button>
</DialogHeader>
<DialogBody>
<DialogBody class="flex min-h-0 flex-1 flex-col">
<ModelList provider={props.provider} model={props.model} onSelect={() => dialog.close()} />
<Button variant="ghost" class="ml-3 mt-5 mb-6 text-text-base self-start" onClick={manage}>
{language.t("dialog.model.manage")}
</Button>
<div class="shrink-0 border-t border-v2-border-border-muted px-4 py-3">
<button
type="button"
class="flex h-9 w-full items-center gap-2 rounded-md px-3 text-left text-[13px] font-[530] leading-text-compact tracking-[-0.04px] text-v2-text-text-base hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none"
onClick={manage}
>
<Icon name="outline-sliders" size="small" />
<span class="min-w-0 flex-1 truncate">{language.t("dialog.model.manage")}</span>
</button>
</div>
</DialogBody>
</Dialog>
)
+107
View File
@@ -1074,4 +1074,111 @@ export const dict = {
"workspace.reset.archived.many": "{{count}} ክፍለ-ጊዜዎች በማህደር ይቀመጣሉ።",
"workspace.reset.note": "ይህ workspaceን ከነባሪው ቅርንጫፍ ጋር እንዲመሳሰል ዳግም ያስጀምረዋል።",
"dialog.usageExceeded.dontShowAgain": "እንደገና አታሳይ",
"toast.migration.failed.title": "የውሂብ ፍልሰት አልተሳካም",
"command.session.background": "ወደ ዳራ አንቀሳቅስ",
"session.timeline.notice.finished": "{{actor}} ጨርሷል",
"session.timeline.notice.failed": "{{actor}} አልተሳካም",
"session.timeline.notice.cancelled": "{{actor}} ተሰርዟል",
"settings.tab.preferences": "ምርጫዎች",
"settings.tab.notifications": "ማሳወቂያዎች",
"settings.tab.projects": "ፕሮጀክቶች",
"settings.tab.extensions": "ቅጥያዎች",
"settings.preferences.description": "ምርጫዎችን፣ ገጽታን እና ነባሪ ባህሪን ያብጁ",
"settings.appearance.description": "ገጽታን እና ቅርጸ ቁምፊዎችን ያብጁ",
"settings.notifications.description": "ማሳወቂያዎች መቼ እንደሚደርሱዎት እና ድምፆች መቼ እንደሚሰሙ ይምረጡ",
"settings.shortcuts.description": "ለተለመዱ ድርጊቶች አቋራጮችን ያብጁ",
"settings.servers.description": "የአገልጋይ ግንኙነቶችን ያስተዳድሩ",
"settings.projects.title": "ፕሮጀክቶች",
"settings.projects.description": "በዚህ አገልጋይ ላይ የፕሮጀክት ቅንብሮችን ያስተዳድሩ",
"settings.projects.empty": "ምንም ፕሮጀክት አልተገኘም",
"settings.projects.server.all": "ሁሉም አገልጋዮች",
"settings.mcps.description": "የModel Context Protocol (MCP) አገልጋዮችን እና መሣሪያዎችን ያስተዳድሩ",
"settings.extensions.description": "በዚህ አገልጋይ ላይ የሚገኙ ቅጥያዎችን ያስተዳድሩ",
"settings.extensions.tab.mcps": "MCPs",
"settings.extensions.tab.skills": "ችሎታዎች",
"settings.extensions.availableAll": "ለሁሉም ፕሮጀክቶች ይገኛል",
"settings.extensions.manageConfig": "በopencode.json ውስጥ ያስተዳድሩ",
"settings.extensions.addSkills": "ችሎታዎችን እንዴት ማከል እንደሚቻል",
"dialog.server.authenticate.title": "ማንነትን ያረጋግጡ",
"project.settings.general.description": "የፕሮጀክት ስምን እና ገጽታን ያስተዳድሩ",
"project.settings.scripts": "ስክሪፕቶች",
"project.settings.scripts.description": "ለዚህ ፕሮጀክት ስክሪፕቶችን ያዋቅሩ",
"project.settings.extensions.description": "ለዚህ ፕሮጀክት የሚገኙ ቅጥያዎችን ይመልከቱ",
"project.settings.extensions.tab.lsps": "LSPs",
"project.settings.extensions.added": "ወደዚህ ፕሮጀክት ታክሏል",
"project.settings.extensions.shared": "ከሁሉም ፕሮጀክቶች ጋር የተጋራ",
"project.settings.extensions.lsp.detected": "የተገኙ የቋንቋ አገልጋዮች",
"project.settings.extensions.lsp.description": "ከፋይል ዓይነቶች በራስ-ሰር የተገኘ",
"project.settings.extensions.setupRequired": "ማዋቀር ያስፈልጋል",
"settings.general.section.general": "አጠቃላይ",
"common.viewAll": "ሁሉንም ይመልከቱ",
"session.new.workspace.local.tooltip": "የአሁኑን checkout ይጠቀሙ",
"session.new.workspace.new.tooltip": "የተለየ checkout ይፍጠሩ",
"session.new.workspace.fromBranch": "ከ{{branch}}",
"session.new.workspace.trigger.tooltip": "ክፍለ ጊዜው የት እንደሚሄድ ይምረጡ",
"session.new.workspace.search.placeholder": "Workspace-ዎችን ይፈልጉ",
"settings.tab.workspaces": "Workspace-ዎች",
"settings.workspaces.filter.all": "ሁሉም ፕሮጀክቶች",
"settings.workspaces.empty": "ምንም workspace የለም",
"settings.workspaces.count.one": "{{count}} workspace",
"settings.workspaces.count.other": "{{count}} workspace-ዎች",
"settings.workspaces.sessions.one": "በ{{project}} ውስጥ {{count}} ክፍለ ጊዜ",
"settings.workspaces.sessions.other": "በ{{project}} ውስጥ {{count}} ክፍለ ጊዜዎች",
"settings.workspaces.lastActiveSession": "የመጨረሻው ንቁ ክፍለ ጊዜ",
"settings.workspaces.deleteAll": "ሁሉንም workspace-ዎች ሰርዝ",
"settings.workspaces.deleteAll.confirm": "ሁሉንም {{count}} workspace-ዎች ይሰረዙ?",
"settings.workspaces.delete.warning": "የworkspace ማውጫው እና ቅርንጫፉ፣ ከዚህ በታች የሚታዩ ያልተዋሃዱ ለውጦችን ጨምሮ፣ እስከመጨረሻው ይወገዳሉ።",
"settings.workspaces.deleteAll.warning":
"በ{{project}} ውስጥ ያሉት {{count}} የተመረጡ workspace-ዎች እያንዳንዳቸው ንጹህ፣ እንቅስቃሴ-አልባ እና የተገናኘ ክፍለ ጊዜ ከሌላቸው ብቻ እስከመጨረሻው ይወገዳሉ።",
"settings.workspaces.delete.blocked.active": "ንቁው workspace ሊሰረዝ አይችልም።",
"settings.workspaces.delete.blocked.linked": "የተገናኙ ክፍለ ጊዜዎች ይቀራሉ፣ ነገር ግን የሥራ ማውጫቸው እስከመጨረሻው ይወገዳል።",
"settings.workspaces.default.title": "ነባሪ አካባቢ",
"settings.workspaces.default.description": "አዲስ ክፍለ ጊዜዎች የት እንደሚጀምሩ ይምረጡ",
"settings.workspaces.default.lastUsed": "በእያንዳንዱ ፕሮጀክት ለመጨረሻ ጊዜ ያገለገለ",
"settings.workspaces.default.local": "አካባቢያዊ ማውጫ",
"settings.workspaces.default.new": "አዲስ workspace",
"workspace.move.title": "ወደ workspace አንቀሳቅስ",
"workspace.move.menu.title": "ክፍለ ጊዜን አንቀሳቅስ ወደ",
"workspace.move.failed": "ክፍለ ጊዜን ማንቀሳቀስ አልተሳካም",
"workspace.lifecycle.creating": "Workspace በመፍጠር ላይ",
"workspace.lifecycle.created": "Workspace ተፈጥሯል",
"workspace.lifecycle.starting": "ክፍለ ጊዜ በመጀመር ላይ",
"workspace.onboarding.title": "ክፍለ ጊዜዎችን በworkspace-ዎች ለዩ",
"workspace.onboarding.description": "እያንዳንዱ የራሱ checkout ስላለው በአካባቢያዊ ማከማቻዎ ላይ ምንም ነገር ጣልቃ አይገባም",
"workspace.lifecycle.moving": "ወደ workspace በማንቀሳቀስ ላይ",
"workspace.lifecycle.set": "Workspace ተዘጋጅቷል",
"session.summary.title": "የክፍለ ጊዜ ዝርዝሮች",
"session.summary.noBranch": "ቅርንጫፍ የለም",
"session.summary.basedOn": "በ{{branch}} ላይ የተመሰረተ",
"provider.disconnect.toast.noCredentials.description": "ለ{{provider}} ሊወገድ የሚችል የማረጋገጫ መረጃ አልተገኘም።",
"provider.disconnect.toast.failed.description": "{{provider}}ን ማቋረጥ አልተሳካም።",
"desktop.menu.installCli": "CLIን ጫን...",
"desktop.cli.installed.title": "CLI ተጭኗል",
"desktop.cli.installed.message": "CLI በ{{path}} ላይ ተጭኗል\n\nየ'opencode2' ትዕዛዝን ለመጠቀም ተርሚናልዎን ዳግም ያስጀምሩ።",
"desktop.cli.failed.title": "መጫን አልተሳካም",
"desktop.cli.failed.message": "CLIን መጫን አልተሳካም፦ {{error}}",
"command.project.copyID": "የፕሮጀክት IDን ቅዳ",
"command.session.copyID": "የክፍለ ጊዜ IDን ቅዳ",
"server.row.incompatible":
"ይህ አገልጋይ OpenCode {{version}}ን እያስኬደ ነው፣ ይህም ከዚህ መተግበሪያ ጋር ተኳሃኝ አይደለም። ለመቀጠል ወደ OpenCode V2 ያሻሽሉት።",
"toast.session.copyID.failed.title": "የክፍለ ጊዜ IDን መቅዳት አልተሳካም",
"toast.session.copyID.failed.description": "የክፍለ ጊዜ IDን ሲቀዳ ስህተት ተፈጥሯል",
"toast.project.copyID.failed.title": "የፕሮጀክት IDን መቅዳት አልተሳካም",
"toast.project.copyID.failed.description": "የፕሮጀክት IDን ሲቀዳ ስህተት ተፈጥሯል",
"error.page.title.status": "የአገልጋይ ጥያቄ አልተሳካም",
"error.page.description.status": "አገልጋዩ የHTTP {{status}} ምላሽ መልሷል።",
"error.page.action.reload": "ዳግም ጫን",
"session.error.incompatible": "ይህ የአገልጋይ ስሪት አይደገፍም",
"session.error.incompatible.description":
"{{server}} OpenCode {{version}}ን እያስኬደ ነው፣ ይህም ከዚህ መተግበሪያ ጋር ተኳሃኝ አይደለም። ለመቀጠል አገልጋዩን ወደ OpenCode V2 ያሻሽሉት።",
"session.background.moveInline": "እየሄደ ያለውን ሥራ ወደ ዳራ ለማንቀሳቀስ {{keybind}}ን ይጫኑ",
"session.background.running": "ሥራ በዳራ እየሄደ ነው",
"session.background.runningCount.one": "{{count}} ንጥል በዳራ እየሄደ ነው",
"session.background.runningCount.other": "{{count}} ንጥሎች በዳራ እየሄዱ ነው",
"settings.general.row.terminalPlacement.title": "የተርሚናል አቀማመጥ",
"settings.general.row.terminalPlacement.description": "ተርሚናሉ በክፍለ ጊዜዎች ውስጥ የት እንደሚከፈት ይምረጡ",
"settings.general.row.terminalPlacement.side": "ጎን",
"settings.general.row.terminalPlacement.bottom": "ታች",
"settings.general.row.showProjectIcon.title": "የፕሮጀክት አዶ",
"settings.general.row.showProjectIcon.description": "የፕሮጀክት አዶውን በክፍለ ጊዜው ራስጌ ውስጥ አሳይ",
}
+122
View File
@@ -1104,4 +1104,126 @@ export const dict = {
"error.childStore.persistedProjectIconCreateFailed": "فشل إنشاء أيقونة المشروع الدائمة",
"error.childStore.storeCreateFailed": "فشل إنشاء المخزن",
"terminal.connectionLost.abnormalClose": "تم إغلاق WebSocket بشكل غير طبيعي: {{code}}",
"toast.migration.failed.title": "فشل ترحيل البيانات",
"command.session.background": "نقل إلى الخلفية",
"session.timeline.notice.finished": "انتهى \u2068{{actor}}\u2069",
"session.timeline.notice.failed": "فشل \u2068{{actor}}\u2069",
"session.timeline.notice.cancelled": "أُلغي \u2068{{actor}}\u2069",
"settings.tab.preferences": "التفضيلات",
"settings.tab.notifications": "الإشعارات",
"settings.tab.projects": "المشاريع",
"settings.tab.extensions": "الملحقات",
"settings.preferences.description": "تخصيص التفضيلات والسمة والسلوك الافتراضي",
"settings.appearance.description": "تخصيص السمة والخطوط",
"settings.notifications.description": "اختر متى تتلقى الإشعارات وتسمع الأصوات",
"settings.shortcuts.description": "تخصيص اختصارات الإجراءات الشائعة",
"settings.servers.description": "إدارة اتصالات الخوادم",
"settings.projects.title": "المشاريع",
"settings.projects.description": "إدارة إعدادات المشاريع على هذا الخادم",
"settings.projects.empty": "لم يتم العثور على مشاريع",
"settings.projects.server.all": "كل الخوادم",
"settings.mcps.description": "إدارة خوادم وأدوات بروتوكول سياق النموذج (MCP)",
"settings.extensions.description": "إدارة الملحقات المتاحة على هذا الخادم",
"settings.extensions.tab.mcps": "خوادم MCP",
"settings.extensions.tab.skills": "المهارات",
"settings.extensions.availableAll": "متاحة لكل المشاريع",
"settings.extensions.manageConfig": "إدارة في opencode.json",
"settings.extensions.addSkills": "كيفية إضافة المهارات",
"dialog.server.authenticate.title": "المصادقة",
"project.settings.general.description": "إدارة اسم المشروع ومظهره",
"project.settings.scripts": "البرامج النصية",
"project.settings.scripts.description": "تكوين البرامج النصية لهذا المشروع",
"project.settings.extensions.description": "عرض الملحقات المتاحة لهذا المشروع",
"project.settings.extensions.tab.lsps": "خوادم LSP",
"project.settings.extensions.added": "مضافة إلى هذا المشروع",
"project.settings.extensions.shared": "مشتركة مع كل المشاريع",
"project.settings.extensions.lsp.detected": "خوادم اللغة المكتشفة",
"project.settings.extensions.lsp.description": "تُكتشف تلقائيًا من أنواع الملفات",
"project.settings.extensions.setupRequired": "الإعداد مطلوب",
"settings.general.section.general": "عام",
"common.viewAll": "عرض الكل",
"session.new.workspace.local.tooltip": "استخدام نسخة العمل الحالية",
"session.new.workspace.new.tooltip": "إنشاء نسخة عمل معزولة",
"session.new.workspace.fromBranch": "من \u2068{{branch}}\u2069",
"session.new.workspace.trigger.tooltip": "حدد مكان تشغيل الجلسة",
"session.new.workspace.search.placeholder": "البحث في مساحات العمل",
"settings.tab.workspaces": "مساحات العمل",
"settings.workspaces.filter.all": "كل المشاريع",
"settings.workspaces.empty": "لا توجد مساحات عمل",
"settings.workspaces.count.zero": "مساحات العمل: {{count}}",
"settings.workspaces.count.one": "{{count}} مساحة عمل",
"settings.workspaces.count.two": "عدد مساحات العمل: {{count}}",
"settings.workspaces.count.few": "{{count}} مساحات عمل",
"settings.workspaces.count.many": "{{count}} مساحة عمل",
"settings.workspaces.count.other": "مساحات العمل: {{count}}",
"settings.workspaces.sessions.zero": "الجلسات في \u2068{{project}}\u2069: {{count}}",
"settings.workspaces.sessions.one": "{{count}} جلسة في \u2068{{project}}\u2069",
"settings.workspaces.sessions.two": "عدد الجلسات في \u2068{{project}}\u2069: {{count}}",
"settings.workspaces.sessions.few": "{{count}} جلسات في \u2068{{project}}\u2069",
"settings.workspaces.sessions.many": "{{count}} جلسة في \u2068{{project}}\u2069",
"settings.workspaces.sessions.other": "الجلسات في \u2068{{project}}\u2069: {{count}}",
"settings.workspaces.lastActiveSession": "آخر جلسة نشطة",
"settings.workspaces.deleteAll": "حذف كل مساحات العمل",
"settings.workspaces.deleteAll.confirm": "حذف مساحات العمل كلها وعددها {{count}}؟",
"settings.workspaces.delete.warning":
"سيُحذف دليل مساحة العمل وفرعها نهائيًا، بما في ذلك أي تغييرات غير مدمجة موضحة أدناه.",
"settings.workspaces.deleteAll.warning":
"ستُحذف مساحات العمل المحددة وعددها {{count}} في \u2068{{project}}\u2069 نهائيًا فقط إذا كانت كلها نظيفة وغير نشطة ولا ترتبط بأي جلسات.",
"settings.workspaces.delete.blocked.active": "لا يمكن حذف مساحة العمل النشطة.",
"settings.workspaces.delete.blocked.linked": "ستبقى الجلسات المرتبطة، لكن دليل عملها سيُحذف نهائيًا.",
"settings.workspaces.default.title": "البيئة الافتراضية",
"settings.workspaces.default.description": "اختر مكان بدء الجلسات الجديدة",
"settings.workspaces.default.lastUsed": "آخر استخدام لكل مشروع",
"settings.workspaces.default.local": "الدليل المحلي",
"settings.workspaces.default.new": "مساحة عمل جديدة",
"workspace.move.title": "نقل إلى مساحة عمل",
"workspace.move.menu.title": "نقل الجلسة إلى",
"workspace.move.failed": "فشل نقل الجلسة",
"workspace.lifecycle.creating": "جارٍ إنشاء مساحة العمل",
"workspace.lifecycle.created": "تم إنشاء مساحة العمل",
"workspace.lifecycle.starting": "جارٍ بدء الجلسة",
"workspace.onboarding.title": "عزل الجلسات باستخدام مساحات العمل",
"workspace.onboarding.description": "تحصل كل جلسة على نسخة عمل خاصة بها، فلا يتداخل شيء مع مستودعك المحلي",
"workspace.lifecycle.moving": "جارٍ النقل إلى مساحة العمل",
"workspace.lifecycle.set": "تم تعيين مساحة العمل",
"session.summary.title": "تفاصيل الجلسة",
"session.summary.noBranch": "لا يوجد فرع",
"session.summary.basedOn": "استنادًا إلى \u2068{{branch}}\u2069",
"provider.disconnect.toast.noCredentials.description":
"لم يتم العثور على بيانات اعتماد قابلة للإزالة لـ \u2068{{provider}}\u2069.",
"provider.disconnect.toast.failed.description": "فشل قطع الاتصال بـ \u2068{{provider}}\u2069.",
"command.project.copyID": "نسخ معرّف المشروع",
"command.session.copyID": "نسخ معرّف الجلسة",
"server.row.incompatible":
"يعمل هذا الخادم بإصدار OpenCode \u2068{{version}}\u2069، وهو غير متوافق مع هذا التطبيق. رقِّه إلى OpenCode V2 للمتابعة.",
"toast.session.copyID.failed.title": "فشل نسخ معرّف الجلسة",
"toast.session.copyID.failed.description": "حدث خطأ أثناء نسخ معرّف الجلسة",
"toast.project.copyID.failed.title": "فشل نسخ معرّف المشروع",
"toast.project.copyID.failed.description": "حدث خطأ أثناء نسخ معرّف المشروع",
"error.page.title.status": "فشل طلب الخادم",
"error.page.description.status": "أعاد الخادم استجابة HTTP بالحالة \u2068{{status}}\u2069.",
"error.page.action.reload": "إعادة التحميل",
"session.error.incompatible": "إصدار هذا الخادم غير مدعوم",
"session.error.incompatible.description":
"يعمل \u2068{{server}}\u2069 بإصدار OpenCode \u2068{{version}}\u2069، وهو غير متوافق مع هذا التطبيق. رقِّ الخادم إلى OpenCode V2 للمتابعة.",
"session.background.moveInline": "اضغط على \u2068{{keybind}}\u2069 لنقل العمل الجاري إلى الخلفية",
"session.background.running": "العمل جارٍ في الخلفية",
"session.background.runningCount.zero": "العناصر قيد التشغيل في الخلفية: {{count}}",
"session.background.runningCount.one": "{{count}} عنصر قيد التشغيل في الخلفية",
"session.background.runningCount.two": "عدد العناصر قيد التشغيل في الخلفية: {{count}}",
"session.background.runningCount.few": "{{count}} عناصر قيد التشغيل في الخلفية",
"session.background.runningCount.many": "{{count}} عنصرًا قيد التشغيل في الخلفية",
"session.background.runningCount.other": "العناصر قيد التشغيل في الخلفية: {{count}}",
"settings.general.row.terminalPlacement.title": "موضع المحطة الطرفية",
"settings.general.row.terminalPlacement.description": "اختر مكان فتح المحطة الطرفية في الجلسات",
"settings.general.row.terminalPlacement.side": "الجانب",
"settings.general.row.terminalPlacement.bottom": "الأسفل",
"settings.general.row.showProjectIcon.title": "أيقونة المشروع",
"settings.general.row.showProjectIcon.description": "إظهار أيقونة المشروع في رأس الجلسة",
"desktop.menu.installCli": "تثبيت CLI...",
"desktop.cli.installed.title": "تم تثبيت CLI",
"desktop.cli.installed.message":
"تم تثبيت CLI في \u2068{{path}}\u2069\n\nأعد تشغيل المحطة الطرفية لاستخدام الأمر 'opencode2'.",
"desktop.cli.failed.title": "فشل التثبيت",
"desktop.cli.failed.message": "فشل تثبيت CLI: \u2068{{error}}\u2069",
}
+111
View File
@@ -1107,4 +1107,115 @@ export const dict = {
"workspace.reset.archived.one": "1 sessiya arxivlənəcək.",
"workspace.reset.archived.many": "{{count}} sessiya arxivlənəcək.",
"workspace.reset.note": "Bu iş sahəsini standart branch ilə uyğunlaşdırmaq üçün sıfırlayacaq.",
"toast.migration.failed.title": "Məlumat miqrasiyası uğursuz oldu",
"command.session.background": "Arxa plana keçir",
"session.timeline.notice.finished": "{{actor}} tamamlandı",
"session.timeline.notice.failed": "{{actor}} uğursuz oldu",
"session.timeline.notice.cancelled": "{{actor}} ləğv edildi",
"settings.tab.preferences": "Tərcihlər",
"settings.tab.notifications": "Bildirişlər",
"settings.tab.projects": "Layihələr",
"settings.tab.extensions": "Genişləndirmələr",
"settings.preferences.description": "Tərcihləri, mövzunu və standart davranışı fərdiləşdirin",
"settings.appearance.description": "Mövzunu və şriftləri fərdiləşdirin",
"settings.notifications.description": "Bildirişlərin nə vaxt alınacağını və səslərin nə vaxt səslənəcəyini seçin",
"settings.shortcuts.description": "Ümumi əməliyyatlar üçün qısayolları fərdiləşdirin",
"settings.servers.description": "Server bağlantılarını idarə edin",
"settings.projects.title": "Layihələr",
"settings.projects.description": "Bu serverdə layihə tənzimləmələrini idarə edin",
"settings.projects.empty": "Layihə tapılmadı",
"settings.projects.server.all": "Bütün serverlər",
"settings.mcps.description": "Model Context Protocol (MCP) serverlərini və alətlərini idarə edin",
"settings.extensions.description": "Bu serverdə mövcud olan genişləndirmələri idarə edin",
"settings.extensions.tab.mcps": "MCP-lər",
"settings.extensions.tab.skills": "Bacarıqlar",
"settings.extensions.availableAll": "Bütün layihələr üçün mövcuddur",
"settings.extensions.manageConfig": "opencode.json-da idarə edin",
"settings.extensions.addSkills": "Bacarıqları necə əlavə etməli",
"dialog.server.authenticate.title": "Autentifikasiya",
"project.settings.general.description": "Layihənin adını və görünüşünü idarə edin",
"project.settings.scripts": "Skriptlər",
"project.settings.scripts.description": "Bu layihə üçün skriptləri konfiqurasiya edin",
"project.settings.extensions.description": "Bu layihə üçün mövcud genişləndirmələrə baxın",
"project.settings.extensions.tab.lsps": "LSP-lər",
"project.settings.extensions.added": "Bu layihəyə əlavə edilib",
"project.settings.extensions.shared": "Bütün layihələrlə paylaşılıb",
"project.settings.extensions.lsp.detected": "Aşkarlanmış dil serverləri",
"project.settings.extensions.lsp.description": "Fayl növlərindən avtomatik aşkarlanır",
"project.settings.extensions.setupRequired": "Quraşdırma tələb olunur",
"settings.general.section.general": "Ümumi",
"common.viewAll": "Hamısına bax",
"session.new.workspace.local.tooltip": "Cari checkout-dan istifadə et",
"session.new.workspace.new.tooltip": "Təcrid olunmuş checkout yarat",
"session.new.workspace.fromBranch": "{{branch}} branch-indən",
"session.new.workspace.trigger.tooltip": "Sessiyanın harada işləyəcəyini seçin",
"session.new.workspace.search.placeholder": "İş sahələrini axtar",
"settings.tab.workspaces": "İş sahələri",
"settings.workspaces.filter.all": "Bütün layihələr",
"settings.workspaces.empty": "İş sahəsi yoxdur",
"settings.workspaces.count.one": "{{count}} iş sahəsi",
"settings.workspaces.count.other": "{{count}} iş sahəsi",
"settings.workspaces.sessions.one": "{{project}} layihəsində {{count}} sessiya",
"settings.workspaces.sessions.other": "{{project}} layihəsində {{count}} sessiya",
"settings.workspaces.lastActiveSession": "Son aktiv sessiya",
"settings.workspaces.deleteAll": "Bütün iş sahələrini sil",
"settings.workspaces.deleteAll.confirm": "Bütün {{count}} iş sahəsi silinsin?",
"settings.workspaces.delete.warning":
"İş sahəsi qovluğu və branch, aşağıda göstərilən birləşdirilməmiş dəyişikliklər də daxil olmaqla, həmişəlik silinəcək.",
"settings.workspaces.deleteAll.warning":
"{{project}} layihəsində seçilmiş {{count}} iş sahəsi yalnız hər biri təmiz, qeyri-aktiv və əlaqəli sessiyasız olduqda həmişəlik silinəcək.",
"settings.workspaces.delete.blocked.active": "Aktiv iş sahəsi silinə bilməz.",
"settings.workspaces.delete.blocked.linked":
"Əlaqəli sessiyalar qalacaq, lakin onların iş qovluğu həmişəlik silinəcək.",
"settings.workspaces.default.title": "Standart mühit",
"settings.workspaces.default.description": "Yeni sessiyaların harada başlayacağını seçin",
"settings.workspaces.default.lastUsed": "Hər layihə üzrə son istifadə edilən",
"settings.workspaces.default.local": "Yerli qovluq",
"settings.workspaces.default.new": "Yeni iş sahəsi",
"workspace.move.title": "İş sahəsinə köçür",
"workspace.move.menu.title": "Sessiyanı buraya köçür",
"workspace.move.failed": "Sessiyanı köçürmək mümkün olmadı",
"workspace.lifecycle.creating": "İş sahəsi yaradılır",
"workspace.lifecycle.created": "İş sahəsi yaradıldı",
"workspace.lifecycle.starting": "Sessiya başladılır",
"workspace.onboarding.title": "Sessiyaları iş sahələri ilə təcrid edin",
"workspace.onboarding.description":
"Hər birinin öz checkout-u var, buna görə heç nə yerli repozitoriyanıza müdaxilə etmir",
"workspace.lifecycle.moving": "İş sahəsinə köçürülür",
"workspace.lifecycle.set": "İş sahəsi təyin edildi",
"session.summary.title": "Sessiya detalları",
"session.summary.noBranch": "Branch yoxdur",
"session.summary.basedOn": "{{branch}} əsasında",
"provider.disconnect.toast.noCredentials.description": "{{provider}} üçün silinə bilən giriş məlumatı tapılmadı.",
"provider.disconnect.toast.failed.description": "{{provider}} bağlantısını kəsmək mümkün olmadı.",
"desktop.menu.installCli": "CLI quraşdır...",
"desktop.cli.installed.title": "CLI quraşdırıldı",
"desktop.cli.installed.message":
"CLI {{path}} ünvanına quraşdırıldı\n\n'opencode2' əmrindən istifadə etmək üçün terminalı yenidən başladın.",
"desktop.cli.failed.title": "Quraşdırma uğursuz oldu",
"desktop.cli.failed.message": "CLI quraşdırmaq mümkün olmadı: {{error}}",
"command.project.copyID": "Layihə ID-sini kopyala",
"command.session.copyID": "Sessiya ID-sini kopyala",
"server.row.incompatible":
"Bu server bu tətbiqlə uyğun olmayan OpenCode {{version}} versiyasını işlədir. Davam etmək üçün onu OpenCode V2-yə yeniləyin.",
"toast.session.copyID.failed.title": "Sessiya ID-sini kopyalamaq mümkün olmadı",
"toast.session.copyID.failed.description": "Sessiya ID-si kopyalanarkən xəta baş verdi",
"toast.project.copyID.failed.title": "Layihə ID-sini kopyalamaq mümkün olmadı",
"toast.project.copyID.failed.description": "Layihə ID-si kopyalanarkən xəta baş verdi",
"error.page.title.status": "Server sorğusu uğursuz oldu",
"error.page.description.status": "Server HTTP {{status}} cavabı qaytardı.",
"error.page.action.reload": "Yenidən yüklə",
"session.error.incompatible": "Bu server versiyası dəstəklənmir",
"session.error.incompatible.description":
"{{server}} bu tətbiqlə uyğun olmayan OpenCode {{version}} versiyasını işlədir. Davam etmək üçün serveri OpenCode V2-yə yeniləyin.",
"session.background.moveInline": "İşləyən işi arxa plana keçirmək üçün {{keybind}} basın",
"session.background.running": "İş arxa planda davam edir",
"session.background.runningCount.one": "{{count}} element arxa planda işləyir",
"session.background.runningCount.other": "{{count}} element arxa planda işləyir",
"settings.general.row.terminalPlacement.title": "Terminalın yerləşməsi",
"settings.general.row.terminalPlacement.description": "Sessiyalarda terminalın harada açılacağını seçin",
"settings.general.row.terminalPlacement.side": "Yan tərəf",
"settings.general.row.terminalPlacement.bottom": "Aşağı",
"settings.general.row.showProjectIcon.title": "Layihə nişanı",
"settings.general.row.showProjectIcon.description": "Sessiya başlığında layihə nişanını göstərin",
}
+112
View File
@@ -1109,4 +1109,116 @@ export const dict = {
"workspace.reset.archived.many": "{{count}} сесии ще бъдат архивирани.",
"workspace.reset.note": "Това ще нулира работното пространство, за да съответства на клона по подразбиране.",
"dialog.usageExceeded.dontShowAgain": "Не показвай отново",
"provider.disconnect.toast.noCredentials.description":
"Няма намерени подвижни идентификационни данни за {{provider}}.",
"provider.disconnect.toast.failed.description": "Неуспешно прекъсване на връзката {{provider}}.",
"toast.migration.failed.title": "Мигрирането на данни не бе успешно",
"command.session.background": "Преместване на заден план",
"session.timeline.notice.finished": "{{actor}} приключи",
"session.timeline.notice.failed": "{{actor}} неуспешно",
"session.timeline.notice.cancelled": "{{actor}} отменен",
"settings.tab.preferences": "Предпочитания",
"settings.tab.notifications": "Известия",
"settings.tab.projects": "Проекти",
"settings.tab.extensions": "Разширения",
"settings.preferences.description": "Персонализирайте предпочитанията и темата и поведението по подразбиране",
"settings.appearance.description": "Персонализирайте темата и шрифтовете",
"settings.notifications.description": "Изберете кога да получавате известия и да чувате звуци",
"settings.shortcuts.description": "Персонализирайте преки пътища за обичайни действия",
"settings.servers.description": "Управлявайте връзките със сървъра",
"settings.projects.title": "Проекти",
"settings.projects.description": "Управление на настройките на проекта на този сървър",
"settings.projects.empty": "Няма намерени проекти",
"settings.projects.server.all": "Всички сървъри",
"settings.mcps.description": "Управление на сървъри и инструменти на протокола за контекст на модела (MCP).",
"settings.extensions.description": "Управление на разширенията, налични на този сървър",
"settings.extensions.tab.mcps": "MCPs",
"settings.extensions.tab.skills": "Умения",
"settings.extensions.availableAll": "Предлага се за всички проекти",
"settings.extensions.manageConfig": "Управление в opencode.json",
"settings.extensions.addSkills": "Как да добавите умения",
"dialog.server.authenticate.title": "Удостоверяване",
"project.settings.general.description": "Управлявайте името и външния вид на проекта",
"project.settings.scripts": "Скриптове",
"project.settings.scripts.description": "Конфигуриране на скриптове за този проект",
"project.settings.extensions.description": "Вижте разширенията, налични за този проект",
"project.settings.extensions.tab.lsps": "LSPs",
"project.settings.extensions.added": "Добавен към този проект",
"project.settings.extensions.shared": "Споделено с всички проекти",
"project.settings.extensions.lsp.detected": "Открити езикови сървъри",
"project.settings.extensions.lsp.description": "Автоматично разпознаване от типове файлове",
"project.settings.extensions.setupRequired": "Изисква се настройка",
"settings.general.section.general": "Общи",
"common.viewAll": "Вижте всички",
"session.new.workspace.local.tooltip": "Използвайте текущото работно копие",
"session.new.workspace.new.tooltip": "Създайте изолирано работно копие",
"session.new.workspace.fromBranch": "от {{branch}}",
"session.new.workspace.trigger.tooltip": "Изберете къде да стартирате сесията",
"session.new.workspace.search.placeholder": "Търсете работни пространства",
"settings.tab.workspaces": "Работни пространства",
"settings.workspaces.filter.all": "Всички проекти",
"settings.workspaces.empty": "Няма работни пространства",
"settings.workspaces.count.one": "{{count}} работно пространство",
"settings.workspaces.count.other": "{{count}} работни пространства",
"settings.workspaces.sessions.one": "{{count}} сесия в {{project}}",
"settings.workspaces.sessions.other": "{{count}} сесии в {{project}}",
"settings.workspaces.lastActiveSession": "Последна активна сесия",
"settings.workspaces.deleteAll": "Изтрийте всички работни области",
"settings.workspaces.deleteAll.confirm": "Да се ​​изтрият ли всички {{count}} работни пространства?",
"settings.workspaces.delete.warning":
"Директорията и клонът на работното пространство ще бъдат премахнати за постоянно, включително всички необединени промени, показани по-долу.",
"settings.workspaces.deleteAll.warning":
"Избраните {{count}} работни пространства в {{project}} ще бъдат премахнати за постоянно само ако всяко е чисто, неактивно и няма свързани сесии.",
"settings.workspaces.delete.blocked.active": "Активното работно пространство не може да бъде изтрито.",
"settings.workspaces.delete.blocked.linked":
"Свързаните сесии ще останат, но тяхната работна директория ще бъде премахната за постоянно.",
"settings.workspaces.default.title": "Среда по подразбиране",
"settings.workspaces.default.description": "Изберете къде започват новите сесии",
"settings.workspaces.default.lastUsed": "Последно използвано за проект",
"settings.workspaces.default.local": "Локален указател",
"settings.workspaces.default.new": "Ново работно пространство",
"workspace.move.title": "Преместете се в работното пространство",
"workspace.move.menu.title": "Преместване на сесията към",
"workspace.move.failed": "Преместването на сесията не бе успешно",
"workspace.lifecycle.creating": "Създаване на работно пространство",
"workspace.lifecycle.created": "Работното пространство е създадено",
"workspace.lifecycle.starting": "Начална сесия",
"workspace.onboarding.title": "Изолирайте сесиите с работни пространства",
"workspace.onboarding.description":
"Всяко получава собствено работно копие, така че нищо да не пречи на локалното ви хранилище",
"workspace.lifecycle.moving": "Преминаване към работното пространство",
"workspace.lifecycle.set": "Работното пространство е зададено",
"session.summary.title": "Подробности за сесията",
"session.summary.noBranch": "Няма клон",
"session.summary.basedOn": "Въз основа на {{branch}}",
"desktop.menu.installCli": "Инсталиране на CLI...",
"desktop.cli.installed.title": "CLI е инсталиран",
"desktop.cli.installed.message":
"CLI е инсталиран в {{path}}\n\nРестартирайте терминала, за да използвате командата 'opencode2'.",
"desktop.cli.failed.title": "Неуспешна инсталация",
"desktop.cli.failed.message": "CLI не можа да бъде инсталиран: {{error}}",
"command.project.copyID": "Копиране на идентификатора на проекта",
"command.session.copyID": "Копиране на идентификатора на сесията",
"server.row.incompatible":
"Този сървър работи с OpenCode {{version}}, който не е съвместим с приложението. Надстройте го до OpenCode V2, за да продължите.",
"toast.session.copyID.failed.title": "Идентификаторът на сесията не можа да бъде копиран",
"toast.session.copyID.failed.description": "Възникна грешка при копирането на идентификатора на сесията",
"toast.project.copyID.failed.title": "Идентификаторът на проекта не можа да бъде копиран",
"toast.project.copyID.failed.description": "Възникна грешка при копирането на идентификатора на проекта",
"error.page.title.status": "Заявката към сървъра е неуспешна",
"error.page.description.status": "Сървърът върна HTTP отговор {{status}}.",
"error.page.action.reload": "Презареждане",
"session.error.incompatible": "Тази версия на сървъра не се поддържа",
"session.error.incompatible.description":
"{{server}} работи с OpenCode {{version}}, който не е съвместим с приложението. Надстройте сървъра до OpenCode V2, за да продължите.",
"session.background.moveInline": "Натиснете {{keybind}}, за да преместите текущата работа във фонов режим",
"session.background.running": "Работата се изпълнява във фонов режим",
"session.background.runningCount.one": "{{count}} задача се изпълнява във фонов режим",
"session.background.runningCount.other": "{{count}} задачи се изпълняват във фонов режим",
"settings.general.row.terminalPlacement.title": "Разположение на терминала",
"settings.general.row.terminalPlacement.description": "Изберете къде да се отваря терминалът в сесиите",
"settings.general.row.terminalPlacement.side": "Отстрани",
"settings.general.row.terminalPlacement.bottom": "Отдолу",
"settings.general.row.showProjectIcon.title": "Икона на проекта",
"settings.general.row.showProjectIcon.description": "Показване на иконата на проекта в заглавката на сесията",
}
+112
View File
@@ -1092,4 +1092,116 @@ export const dict: Record<string, string> = {
"workspace.reset.archived.many": "{{count}} সেশন আর্কাইভ করা হবে।",
"workspace.reset.note": "এটি ডিফল্ট শাখার সাথে মেলে ওয়ার্কস্পেস রিসেট করবে।",
"dialog.usageExceeded.dontShowAgain": "আবার দেখাবেন না",
"toast.migration.failed.title": "ডেটা মাইগ্রেশন ব্যর্থ হয়েছে",
"command.session.background": "ব্যাকগ্রাউন্ডে সরান",
"session.timeline.notice.finished": "{{actor}} শেষ করেছে",
"session.timeline.notice.failed": "{{actor}} ব্যর্থ হয়েছে",
"session.timeline.notice.cancelled": "{{actor}} বাতিল হয়েছে",
"settings.tab.preferences": "পছন্দসমূহ",
"settings.tab.notifications": "বিজ্ঞপ্তি",
"settings.tab.projects": "প্রকল্প",
"settings.tab.extensions": "এক্সটেনশন",
"settings.preferences.description": "পছন্দ, থিম এবং ডিফল্ট আচরণ কাস্টমাইজ করুন",
"settings.appearance.description": "থিম এবং ফন্ট কাস্টমাইজ করুন",
"settings.notifications.description": "কখন বিজ্ঞপ্তি পাবেন ও শব্দ শুনবেন তা বেছে নিন",
"settings.shortcuts.description": "সাধারণ কাজের শর্টকাট কাস্টমাইজ করুন",
"settings.servers.description": "সার্ভার সংযোগ পরিচালনা করুন",
"settings.projects.title": "প্রকল্প",
"settings.projects.description": "এই সার্ভারের প্রকল্প সেটিংস পরিচালনা করুন",
"settings.projects.empty": "কোনো প্রকল্প পাওয়া যায়নি",
"settings.projects.server.all": "সব সার্ভার",
"settings.mcps.description": "Model Context Protocol (MCP) সার্ভার ও টুল পরিচালনা করুন",
"settings.extensions.description": "এই সার্ভারে উপলভ্য এক্সটেনশন পরিচালনা করুন",
"settings.extensions.tab.mcps": "MCPs",
"settings.extensions.tab.skills": "স্কিল",
"settings.extensions.availableAll": "সব প্রকল্পে উপলভ্য",
"settings.extensions.manageConfig": "opencode.json-এ পরিচালনা করুন",
"settings.extensions.addSkills": "স্কিল যোগ করার নিয়ম",
"dialog.server.authenticate.title": "প্রমাণীকরণ",
"project.settings.general.description": "প্রকল্পের নাম ও চেহারা পরিচালনা করুন",
"project.settings.scripts": "স্ক্রিপ্ট",
"project.settings.scripts.description": "এই প্রকল্পের স্ক্রিপ্ট কনফিগার করুন",
"project.settings.extensions.description": "এই প্রকল্পে উপলভ্য এক্সটেনশন দেখুন",
"project.settings.extensions.tab.lsps": "LSPs",
"project.settings.extensions.added": "এই প্রকল্পে যোগ করা হয়েছে",
"project.settings.extensions.shared": "সব প্রকল্পের সঙ্গে শেয়ার করা",
"project.settings.extensions.lsp.detected": "শনাক্ত করা ল্যাঙ্গুয়েজ সার্ভার",
"project.settings.extensions.lsp.description": "ফাইলের ধরন থেকে স্বয়ংক্রিয়ভাবে শনাক্ত করা",
"project.settings.extensions.setupRequired": "সেটআপ প্রয়োজন",
"settings.general.section.general": "সাধারণ",
"common.viewAll": "সব দেখুন",
"session.new.workspace.local.tooltip": "বর্তমান চেকআউট ব্যবহার করুন",
"session.new.workspace.new.tooltip": "আলাদা চেকআউট তৈরি করুন",
"session.new.workspace.fromBranch": "{{branch}} থেকে",
"session.new.workspace.trigger.tooltip": "সেশন কোথায় চলবে তা নির্বাচন করুন",
"session.new.workspace.search.placeholder": "ওয়ার্কস্পেস খুঁজুন",
"settings.tab.workspaces": "ওয়ার্কস্পেস",
"settings.workspaces.filter.all": "সব প্রকল্প",
"settings.workspaces.empty": "কোনো ওয়ার্কস্পেস নেই",
"settings.workspaces.count.one": "{{count}}টি ওয়ার্কস্পেস",
"settings.workspaces.count.other": "{{count}}টি ওয়ার্কস্পেস",
"settings.workspaces.sessions.one": "{{project}}-এ {{count}}টি সেশন",
"settings.workspaces.sessions.other": "{{project}}-এ {{count}}টি সেশন",
"settings.workspaces.lastActiveSession": "সর্বশেষ সক্রিয় সেশন",
"settings.workspaces.deleteAll": "সব ওয়ার্কস্পেস মুছুন",
"settings.workspaces.deleteAll.confirm": "সব {{count}}টি ওয়ার্কস্পেস মুছবেন?",
"settings.workspaces.delete.warning":
"নিচে দেখানো একীভূত না হওয়া পরিবর্তনসহ ওয়ার্কস্পেস ডিরেক্টরি ও ব্রাঞ্চ স্থায়ীভাবে মুছে যাবে।",
"settings.workspaces.deleteAll.warning":
"{{project}}-এর নির্বাচিত {{count}}টি ওয়ার্কস্পেসের প্রতিটি পরিষ্কার, নিষ্ক্রিয় এবং কোনো সেশনের সঙ্গে যুক্ত না থাকলেই স্থায়ীভাবে মুছে যাবে।",
"settings.workspaces.delete.blocked.active": "সক্রিয় ওয়ার্কস্পেস মোছা যাবে না।",
"settings.workspaces.delete.blocked.linked":
"যুক্ত সেশনগুলো থাকবে, তবে তাদের কার্যকরী ডিরেক্টরি স্থায়ীভাবে মুছে যাবে।",
"settings.workspaces.default.title": "ডিফল্ট পরিবেশ",
"settings.workspaces.default.description": "নতুন সেশন কোথায় শুরু হবে তা বেছে নিন",
"settings.workspaces.default.lastUsed": "প্রতি প্রকল্পে সর্বশেষ ব্যবহৃত",
"settings.workspaces.default.local": "স্থানীয় ডিরেক্টরি",
"settings.workspaces.default.new": "নতুন ওয়ার্কস্পেস",
"workspace.move.title": "ওয়ার্কস্পেসে সরান",
"workspace.move.menu.title": "সেশন এখানে সরান",
"workspace.move.failed": "সেশন সরানো ব্যর্থ হয়েছে",
"workspace.lifecycle.creating": "ওয়ার্কস্পেস তৈরি হচ্ছে",
"workspace.lifecycle.created": "ওয়ার্কস্পেস তৈরি হয়েছে",
"workspace.lifecycle.starting": "সেশন শুরু হচ্ছে",
"workspace.onboarding.title": "ওয়ার্কস্পেস দিয়ে সেশন আলাদা রাখুন",
"workspace.onboarding.description":
"প্রতিটি নিজস্ব চেকআউট পায়, তাই আপনার স্থানীয় রিপোজিটরিতে কোনো হস্তক্ষেপ হয় না",
"workspace.lifecycle.moving": "ওয়ার্কস্পেসে সরানো হচ্ছে",
"workspace.lifecycle.set": "ওয়ার্কস্পেস সেট করা হয়েছে",
"session.summary.title": "সেশনের বিবরণ",
"session.summary.noBranch": "কোনো ব্রাঞ্চ নেই",
"session.summary.basedOn": "{{branch}}-এর ভিত্তিতে",
"provider.disconnect.toast.noCredentials.description":
"{{provider}}-এর জন্য অপসারণযোগ্য কোনো শংসাপত্র পাওয়া যায়নি।",
"provider.disconnect.toast.failed.description": "{{provider}}-এর সংযোগ বিচ্ছিন্ন করা যায়নি।",
"desktop.menu.installCli": "CLI ইনস্টল করুন...",
"desktop.cli.installed.title": "CLI ইনস্টল হয়েছে",
"desktop.cli.installed.message":
"CLI {{path}}-এ ইনস্টল হয়েছে\n\n'opencode2' কমান্ড ব্যবহার করতে টার্মিনাল পুনরায় চালু করুন।",
"desktop.cli.failed.title": "ইনস্টলেশন ব্যর্থ হয়েছে",
"desktop.cli.failed.message": "CLI ইনস্টল করা যায়নি: {{error}}",
"command.project.copyID": "প্রকল্পের ID কপি করুন",
"command.session.copyID": "সেশনের ID কপি করুন",
"server.row.incompatible":
"এই সার্ভারে OpenCode {{version}} চলছে, যা এই অ্যাপের সঙ্গে সামঞ্জস্যপূর্ণ নয়। চালিয়ে যেতে এটিকে OpenCode V2-এ আপগ্রেড করুন।",
"toast.session.copyID.failed.title": "সেশন ID কপি করা যায়নি",
"toast.session.copyID.failed.description": "সেশন ID কপি করার সময় একটি ত্রুটি ঘটেছে",
"toast.project.copyID.failed.title": "প্রকল্প ID কপি করা যায়নি",
"toast.project.copyID.failed.description": "প্রকল্প ID কপি করার সময় একটি ত্রুটি ঘটেছে",
"error.page.title.status": "সার্ভারের অনুরোধ ব্যর্থ হয়েছে",
"error.page.description.status": "সার্ভারটি HTTP {{status}} প্রতিক্রিয়া দিয়েছে।",
"error.page.action.reload": "পুনরায় লোড করুন",
"session.error.incompatible": "এই সার্ভার সংস্করণটি সমর্থিত নয়",
"session.error.incompatible.description":
"{{server}}-এ OpenCode {{version}} চলছে, যা এই অ্যাপের সঙ্গে সামঞ্জস্যপূর্ণ নয়। চালিয়ে যেতে সার্ভারটি OpenCode V2-এ আপগ্রেড করুন।",
"session.background.moveInline": "চলমান কাজটি পটভূমিতে সরাতে {{keybind}} চাপুন",
"session.background.running": "পটভূমিতে কাজ চলছে",
"session.background.runningCount.one": "পটভূমিতে {{count}}টি আইটেম চলছে",
"session.background.runningCount.other": "পটভূমিতে {{count}}টি আইটেম চলছে",
"settings.general.row.terminalPlacement.title": "টার্মিনালের অবস্থান",
"settings.general.row.terminalPlacement.description": "সেশনে টার্মিনাল কোথায় খুলবে তা বেছে নিন",
"settings.general.row.terminalPlacement.side": "পাশে",
"settings.general.row.terminalPlacement.bottom": "নিচে",
"settings.general.row.showProjectIcon.title": "প্রকল্পের আইকন",
"settings.general.row.showProjectIcon.description": "সেশন হেডারে প্রকল্পের আইকন দেখান",
}
+114
View File
@@ -1117,4 +1117,118 @@ export const dict = {
"error.childStore.persistedProjectIconCreateFailed": "Falha ao criar ícone de projeto persistente",
"error.childStore.storeCreateFailed": "Falha ao criar armazenamento",
"terminal.connectionLost.abnormalClose": "WebSocket fechado anormalmente: {{code}}",
"provider.disconnect.toast.noCredentials.description": "Nenhuma credencial removível encontrada para {{provider}}.",
"provider.disconnect.toast.failed.description": "Falha ao desconectar {{provider}}.",
"toast.migration.failed.title": "Falha na migração de dados",
"command.session.background": "Mover para segundo plano",
"session.timeline.notice.finished": "{{actor}} finalizado",
"session.timeline.notice.failed": "{{actor}} falhou",
"session.timeline.notice.cancelled": "{{actor}} cancelado",
"settings.tab.preferences": "Preferências",
"settings.tab.notifications": "Notificações",
"settings.tab.projects": "Projetos",
"settings.tab.extensions": "Extensões",
"settings.preferences.description": "Personalize preferências, tema e comportamento padrão",
"settings.appearance.description": "Personalize tema e fontes",
"settings.notifications.description": "Escolha quando receber notificações e ouvir sons",
"settings.shortcuts.description": "Personalize atalhos para ações comuns",
"settings.servers.description": "Gerenciar conexões de servidor",
"settings.projects.title": "Projetos",
"settings.projects.description": "Gerencie as configurações do projeto neste servidor",
"settings.projects.empty": "Nenhum projeto encontrado",
"settings.projects.server.all": "Todos os servidores",
"settings.mcps.description": "Gerenciar servidores e ferramentas do Model Context Protocol (MCP)",
"settings.extensions.description": "Gerenciar extensões disponíveis neste servidor",
"settings.extensions.tab.mcps": "MCP",
"settings.extensions.tab.skills": "Habilidades",
"settings.extensions.availableAll": "Disponível para todos os projetos",
"settings.extensions.manageConfig": "Gerenciar em opencode.json",
"settings.extensions.addSkills": "Como adicionar habilidades",
"dialog.server.authenticate.title": "Autenticar",
"project.settings.general.description": "Gerenciar o nome e a aparência do projeto",
"project.settings.scripts": "Roteiros",
"project.settings.scripts.description": "Configurar scripts para este projeto",
"project.settings.extensions.description": "Ver extensões disponíveis para este projeto",
"project.settings.extensions.tab.lsps": "LSPs",
"project.settings.extensions.added": "Adicionado a este projeto",
"project.settings.extensions.shared": "Compartilhado com todos os projetos",
"project.settings.extensions.lsp.detected": "Servidores de linguagem detectados",
"project.settings.extensions.lsp.description": "Detectado automaticamente em tipos de arquivo",
"project.settings.extensions.setupRequired": "Configuração necessária",
"settings.general.section.general": "Em geral",
"common.viewAll": "Ver tudo",
"session.new.workspace.local.tooltip": "Usar checkout atual",
"session.new.workspace.new.tooltip": "Criar checkout isolado",
"session.new.workspace.fromBranch": "de {{branch}}",
"session.new.workspace.trigger.tooltip": "Selecione onde executar a sessão",
"session.new.workspace.search.placeholder": "Pesquisar espaços de trabalho",
"settings.tab.workspaces": "Espaços de trabalho",
"settings.workspaces.filter.all": "Todos os projetos",
"settings.workspaces.empty": "Sem espaços de trabalho",
"settings.workspaces.count.one": "Espaço de trabalho {{count}}",
"settings.workspaces.count.other": "Espaços de trabalho {{count}}",
"settings.workspaces.sessions.one": "Sessão {{count}} em {{project}}",
"settings.workspaces.sessions.other": "Sessões {{count}} em {{project}}",
"settings.workspaces.lastActiveSession": "Última sessão ativa",
"settings.workspaces.deleteAll": "Excluir todos os espaços de trabalho",
"settings.workspaces.deleteAll.confirm": "Excluir todos os espaços de trabalho {{count}}?",
"settings.workspaces.delete.warning":
"O diretório e a ramificação do espaço de trabalho serão removidos permanentemente, incluindo quaisquer alterações não mescladas mostradas abaixo.",
"settings.workspaces.deleteAll.warning":
"Os espaços de trabalho selecionados {{count}} em {{project}} serão removidos permanentemente somente se cada um estiver limpo, inativo e não tiver sessões vinculadas.",
"settings.workspaces.delete.blocked.active": "O espaço de trabalho ativo não pode ser excluído.",
"settings.workspaces.delete.blocked.linked":
"As sessões vinculadas permanecerão, mas seu diretório de trabalho será removido permanentemente.",
"settings.workspaces.default.title": "Ambiente padrão",
"settings.workspaces.default.description": "Escolha onde as novas sessões começam",
"settings.workspaces.default.lastUsed": "Última utilização por projeto",
"settings.workspaces.default.local": "Diretório local",
"settings.workspaces.default.new": "Novo espaço de trabalho",
"workspace.move.title": "Mover para o espaço de trabalho",
"workspace.move.menu.title": "Mover sessão para",
"workspace.move.failed": "Falha ao mover sessão",
"workspace.lifecycle.creating": "Criando espaço de trabalho",
"workspace.lifecycle.created": "Espaço de trabalho criado",
"workspace.lifecycle.starting": "Iniciando sessão",
"workspace.onboarding.title": "Isolar sessões com espaços de trabalho",
"workspace.onboarding.description":
"Cada um recebe seu próprio checkout, então nada interfere no seu repositório local",
"workspace.lifecycle.moving": "Movendo para o espaço de trabalho",
"workspace.lifecycle.set": "Espaço de trabalho definido",
"session.summary.title": "Detalhes da sessão",
"session.summary.noBranch": "Sem branch",
"session.summary.basedOn": "Baseado em {{branch}}",
"settings.workspaces.count.many": "{{count}} espaços de trabalho",
"settings.workspaces.sessions.many": "{{count}} sessões em {{project}}",
"desktop.menu.installCli": "Instalar CLI...",
"desktop.cli.installed.title": "CLI instalada",
"desktop.cli.installed.message": "CLI instalada em {{path}}\n\nReinicie o terminal para usar o comando 'opencode2'.",
"desktop.cli.failed.title": "Falha na instalação",
"desktop.cli.failed.message": "Falha ao instalar a CLI: {{error}}",
"command.project.copyID": "Copiar ID do projeto",
"command.session.copyID": "Copiar ID da sessão",
"server.row.incompatible":
"Este servidor está executando o OpenCode {{version}}, que não é compatível com este aplicativo. Atualize-o para o OpenCode V2 para continuar.",
"toast.session.copyID.failed.title": "Falha ao copiar o ID da sessão",
"toast.session.copyID.failed.description": "Ocorreu um erro ao copiar o ID da sessão",
"toast.project.copyID.failed.title": "Falha ao copiar o ID do projeto",
"toast.project.copyID.failed.description": "Ocorreu um erro ao copiar o ID do projeto",
"error.page.title.status": "Falha na solicitação ao servidor",
"error.page.description.status": "O servidor retornou uma resposta HTTP {{status}}.",
"error.page.action.reload": "Recarregar",
"session.error.incompatible": "Esta versão do servidor não é compatível",
"session.error.incompatible.description":
"{{server}} está executando o OpenCode {{version}}, que não é compatível com este aplicativo. Atualize o servidor para o OpenCode V2 para continuar.",
"session.background.moveInline": "Pressione {{keybind}} para mover o trabalho em execução para segundo plano",
"session.background.running": "Trabalho em execução em segundo plano",
"session.background.runningCount.one": "{{count}} item em execução em segundo plano",
"session.background.runningCount.other": "{{count}} itens em execução em segundo plano",
"session.background.runningCount.many": "{{count}} de itens em execução em segundo plano",
"settings.general.row.terminalPlacement.title": "Posição do terminal",
"settings.general.row.terminalPlacement.description": "Escolha onde o terminal será aberto nas sessões",
"settings.general.row.terminalPlacement.side": "Lateral",
"settings.general.row.terminalPlacement.bottom": "Inferior",
"settings.general.row.showProjectIcon.title": "Ícone do projeto",
"settings.general.row.showProjectIcon.description": "Mostrar o ícone do projeto no cabeçalho da sessão",
}
+113 -1
View File
@@ -631,7 +631,6 @@ export const dict = {
"toast.context.noLineSelection.title": "Nema odabranih linija",
"toast.context.noLineSelection.description": "Prvo odaberi raspon linija u kartici datoteke.",
"toast.session.unshare.success.title": "Dijeljenje sesije ukinuto",
"toast.session.unshare.success.description": "Dijeljenje sesije je uspješno ukinuto!",
"toast.session.unshare.failed.title": "Neuspjelo ukidanje dijeljenja",
@@ -1195,4 +1194,117 @@ export const dict = {
"error.childStore.persistedProjectIconCreateFailed": "Nije uspjelo kreiranje trajne ikone projekta",
"error.childStore.storeCreateFailed": "Nije uspjelo kreiranje skladišta",
"terminal.connectionLost.abnormalClose": "WebSocket zatvoren nenormalno: {{code}}",
"provider.disconnect.toast.noCredentials.description": "Nisu pronađeni uklonjivi akreditivi za {{provider}}.",
"provider.disconnect.toast.failed.description": "Prekid veze {{provider}} nije uspio.",
"toast.migration.failed.title": "Migracija podataka nije uspjela",
"command.session.background": "Premjesti u pozadinu",
"session.timeline.notice.finished": "{{actor}} završeno",
"session.timeline.notice.failed": "{{actor}} nije uspio",
"session.timeline.notice.cancelled": "{{actor}} otkazan",
"settings.tab.preferences": "Postavke",
"settings.tab.notifications": "Obavještenja",
"settings.tab.projects": "Projekti",
"settings.tab.extensions": "Ekstenzije",
"settings.preferences.description": "Prilagodite postavke i temu i zadano ponašanje",
"settings.appearance.description": "Prilagodite temu i fontove",
"settings.notifications.description": "Odaberite kada ćete primati obavještenja i čuti zvukove",
"settings.shortcuts.description": "Prilagodite prečice za uobičajene radnje",
"settings.servers.description": "Upravljajte serverskim vezama",
"settings.projects.title": "Projekti",
"settings.projects.description": "Upravljajte postavkama projekta na ovom serveru",
"settings.projects.empty": "Nije pronađen nijedan projekat",
"settings.projects.server.all": "Svi serveri",
"settings.mcps.description": "Upravljajte serverima i alatima Model Context Protocol (MCP).",
"settings.extensions.description": "Upravljajte ekstenzijama dostupnim na ovom serveru",
"settings.extensions.tab.mcps": "MCP-ovi",
"settings.extensions.tab.skills": "Vještine",
"settings.extensions.availableAll": "Dostupan za sve projekte",
"settings.extensions.manageConfig": "Upravljajte u opencode.json",
"settings.extensions.addSkills": "Kako dodati vještine",
"dialog.server.authenticate.title": "Autentificiraj",
"project.settings.general.description": "Upravljajte imenom i izgledom projekta",
"project.settings.scripts": "Skripte",
"project.settings.scripts.description": "Konfigurirajte skripte za ovaj projekat",
"project.settings.extensions.description": "Pogledajte proširenja dostupna za ovaj projekat",
"project.settings.extensions.tab.lsps": "LSP-ovi",
"project.settings.extensions.added": "Dodato ovom projektu",
"project.settings.extensions.shared": "Dijeljeno sa svim projektima",
"project.settings.extensions.lsp.detected": "Otkriveni jezički serveri",
"project.settings.extensions.lsp.description": "Automatski otkriveno prema tipovima datoteka",
"project.settings.extensions.setupRequired": "Potrebna je konfiguracija",
"settings.general.section.general": "Opšte",
"common.viewAll": "Pogledaj sve",
"session.new.workspace.local.tooltip": "Koristi trenutnu radnu kopiju",
"session.new.workspace.new.tooltip": "Kreiraj izolovanu radnu kopiju",
"session.new.workspace.fromBranch": "od {{branch}}",
"session.new.workspace.trigger.tooltip": "Odaberite gdje želite pokrenuti sesiju",
"session.new.workspace.search.placeholder": "Pretražite radne prostore",
"settings.tab.workspaces": "Radni prostori",
"settings.workspaces.filter.all": "Svi projekti",
"settings.workspaces.empty": "Nema radnih prostora",
"settings.workspaces.count.one": "{{count}} radni prostor",
"settings.workspaces.count.other": "{{count}} radni prostori",
"settings.workspaces.sessions.one": "{{count}} sesija u {{project}}",
"settings.workspaces.sessions.other": "{{count}} sesije u {{project}}",
"settings.workspaces.lastActiveSession": "Zadnja aktivna sesija",
"settings.workspaces.deleteAll": "Izbrišite sve radne prostore",
"settings.workspaces.deleteAll.confirm": "Izbrisati sve {{count}} radne prostore?",
"settings.workspaces.delete.warning":
"Direktorij i grana radnog prostora će biti trajno uklonjeni, uključujući sve nespojene promjene prikazane u nastavku.",
"settings.workspaces.deleteAll.warning":
"{{count}} odabrani radni prostori u {{project}} će biti trajno uklonjeni samo ako je svaki čist, neaktivan i nema povezanih sesija.",
"settings.workspaces.delete.blocked.active": "Aktivni radni prostor se ne može izbrisati.",
"settings.workspaces.delete.blocked.linked":
"Povezane sesije će ostati, ali će njihov radni direktorij biti trajno uklonjen.",
"settings.workspaces.default.title": "Zadano okruženje",
"settings.workspaces.default.description": "Odaberite gdje počinju nove sesije",
"settings.workspaces.default.lastUsed": "Posljednje korišteno po projektu",
"settings.workspaces.default.local": "Lokalni imenik",
"settings.workspaces.default.new": "Novi radni prostor",
"workspace.move.title": "Premjestite se u radni prostor",
"workspace.move.menu.title": "Premjesti sesiju na",
"workspace.move.failed": "Premještanje sesije nije uspjelo",
"workspace.lifecycle.creating": "Kreiranje radnog prostora",
"workspace.lifecycle.created": "Radni prostor kreiran",
"workspace.lifecycle.starting": "Početak sesije",
"workspace.onboarding.title": "Izolirajte sesije s radnim prostorima",
"workspace.onboarding.description": "Svaka dobija vlastitu radnu kopiju, tako da ništa ne ometa lokalni repozitorij",
"workspace.lifecycle.moving": "Prelazak u radni prostor",
"workspace.lifecycle.set": "Radni prostor postavljen",
"session.summary.title": "Detalji sesije",
"session.summary.noBranch": "Nema grane",
"session.summary.basedOn": "Zasnovano na {{branch}}",
"settings.workspaces.count.few": "{{count}} radna prostora",
"settings.workspaces.sessions.few": "{{count}} sesije u {{project}}",
"desktop.menu.installCli": "Instaliraj CLI...",
"desktop.cli.installed.title": "CLI je instaliran",
"desktop.cli.installed.message":
"CLI je instaliran u {{path}}\n\nPonovo pokrenite terminal da biste koristili naredbu 'opencode2'.",
"desktop.cli.failed.title": "Instalacija nije uspjela",
"desktop.cli.failed.message": "Instalacija CLI-ja nije uspjela: {{error}}",
"command.project.copyID": "Kopiraj ID projekta",
"command.session.copyID": "Kopiraj ID sesije",
"server.row.incompatible":
"Ovaj server koristi OpenCode {{version}}, koji nije kompatibilan s ovom aplikacijom. Nadogradite ga na OpenCode V2 da biste nastavili.",
"toast.session.copyID.failed.title": "Kopiranje ID-a sesije nije uspjelo",
"toast.session.copyID.failed.description": "Došlo je do greške pri kopiranju ID-a sesije",
"toast.project.copyID.failed.title": "Kopiranje ID-a projekta nije uspjelo",
"toast.project.copyID.failed.description": "Došlo je do greške pri kopiranju ID-a projekta",
"error.page.title.status": "Zahtjev serveru nije uspio",
"error.page.description.status": "Server je vratio HTTP odgovor {{status}}.",
"error.page.action.reload": "Ponovo učitaj",
"session.error.incompatible": "Ova verzija servera nije podržana",
"session.error.incompatible.description":
"{{server}} koristi OpenCode {{version}}, koji nije kompatibilan s ovom aplikacijom. Nadogradite server na OpenCode V2 da biste nastavili.",
"session.background.moveInline": "Pritisnite {{keybind}} da premjestite aktivni rad u pozadinu",
"session.background.running": "Rad se izvršava u pozadini",
"session.background.runningCount.one": "{{count}} stavka se izvršava u pozadini",
"session.background.runningCount.few": "{{count}} stavke se izvršavaju u pozadini",
"session.background.runningCount.other": "{{count}} stavki se izvršava u pozadini",
"settings.general.row.terminalPlacement.title": "Položaj terminala",
"settings.general.row.terminalPlacement.description": "Odaberite gdje se terminal otvara u sesijama",
"settings.general.row.terminalPlacement.side": "Sa strane",
"settings.general.row.terminalPlacement.bottom": "Na dnu",
"settings.general.row.showProjectIcon.title": "Ikona projekta",
"settings.general.row.showProjectIcon.description": "Prikaži ikonu projekta u zaglavlju sesije",
}
+114
View File
@@ -1083,6 +1083,50 @@ export const dict = {
"session.delete.confirm": 'Voleu suprimir la sessió "{{name}}"?',
"session.delete.button": "Suprimeix la sessió",
"workspace.new": "Nou espai de treball",
"common.viewAll": "Mostra-ho tot",
"session.new.workspace.local.tooltip": "Utilitza el checkout actual",
"session.new.workspace.new.tooltip": "Crea un checkout aïllat",
"session.new.workspace.fromBranch": "des de {{branch}}",
"session.new.workspace.trigger.tooltip": "Selecciona on s'executarà la sessió",
"session.new.workspace.search.placeholder": "Cerca espais de treball",
"settings.tab.workspaces": "Espais de treball",
"settings.workspaces.filter.all": "Tots els projectes",
"settings.workspaces.empty": "No hi ha espais de treball",
"settings.workspaces.count.one": "{{count}} espai de treball",
"settings.workspaces.count.many": "{{count}} d'espais de treball",
"settings.workspaces.count.other": "{{count}} espais de treball",
"settings.workspaces.sessions.one": "{{count}} sessió a {{project}}",
"settings.workspaces.sessions.many": "{{count}} de sessions a {{project}}",
"settings.workspaces.sessions.other": "{{count}} sessions a {{project}}",
"settings.workspaces.lastActiveSession": "Última sessió activa",
"settings.workspaces.deleteAll": "Suprimeix tots els espais de treball",
"settings.workspaces.deleteAll.confirm": "Voleu suprimir els {{count}} espais de treball?",
"settings.workspaces.delete.warning":
"El directori i la branca de l'espai de treball se suprimiran permanentment, inclosos els canvis no fusionats que es mostren a continuació.",
"settings.workspaces.deleteAll.warning":
"Els {{count}} espais de treball seleccionats de {{project}} se suprimiran permanentment només si tots estan nets, inactius i no tenen sessions enllaçades.",
"settings.workspaces.delete.blocked.active": "L'espai de treball actiu no es pot suprimir.",
"settings.workspaces.delete.blocked.linked":
"Les sessions enllaçades es conservaran, però el seu directori de treball se suprimirà permanentment.",
"settings.workspaces.default.title": "Entorn predeterminat",
"settings.workspaces.default.description": "Trieu on s'inicien les sessions noves",
"settings.workspaces.default.lastUsed": "Últim utilitzat per projecte",
"settings.workspaces.default.local": "Directori local",
"settings.workspaces.default.new": "Espai de treball nou",
"workspace.move.title": "Mou a un espai de treball",
"workspace.move.menu.title": "Mou la sessió a",
"workspace.move.failed": "No s'ha pogut moure la sessió",
"workspace.lifecycle.creating": "S'està creant l'espai de treball",
"workspace.lifecycle.created": "S'ha creat l'espai de treball",
"workspace.lifecycle.starting": "S'està iniciant la sessió",
"workspace.onboarding.title": "Aïlla les sessions amb espais de treball",
"workspace.onboarding.description":
"Cadascuna té el seu propi checkout, de manera que res no interfereix amb el repositori local",
"workspace.lifecycle.moving": "S'està movent a l'espai de treball",
"workspace.lifecycle.set": "Espai de treball establert",
"session.summary.title": "Detalls de la sessió",
"session.summary.noBranch": "Sense branca",
"session.summary.basedOn": "Basat en {{branch}}",
"workspace.type.local": "locals",
"workspace.type.sandbox": "caixa de sorra",
"workspace.create.failed.title": "No s'ha pogut crear l'espai de treball",
@@ -1108,4 +1152,74 @@ export const dict = {
"workspace.reset.archived.many": "{{count}} sessions s'arxivaran.",
"workspace.reset.note": "Això restablirà l'espai de treball perquè coincideixi amb la branca predeterminada.",
"dialog.usageExceeded.dontShowAgain": "No ho tornis a mostrar",
"provider.disconnect.toast.noCredentials.description": "No s'han trobat credencials eliminables per a {{provider}}.",
"provider.disconnect.toast.failed.description": "No s'ha pogut desconnectar {{provider}}.",
"toast.migration.failed.title": "La migració de dades ha fallat",
"command.session.background": "Mou al segon pla",
"session.timeline.notice.finished": "{{actor}} ha acabat",
"session.timeline.notice.failed": "{{actor}} ha fallat",
"session.timeline.notice.cancelled": "{{actor}} s'ha cancel·lat",
"settings.tab.preferences": "Preferències",
"settings.tab.notifications": "Notificacions",
"settings.tab.projects": "Projectes",
"settings.tab.extensions": "Extensions",
"settings.preferences.description": "Personalitzeu les preferències, el tema i el comportament predeterminat",
"settings.appearance.description": "Personalitzeu el tema i els tipus de lletra",
"settings.notifications.description": "Trieu quan voleu rebre notificacions i sentir sons",
"settings.shortcuts.description": "Personalitzeu les dreceres per a les accions habituals",
"settings.servers.description": "Gestioneu les connexions als servidors",
"settings.projects.title": "Projectes",
"settings.projects.description": "Gestioneu la configuració dels projectes d'aquest servidor",
"settings.projects.empty": "No s'ha trobat cap projecte",
"settings.projects.server.all": "Tots els servidors",
"settings.mcps.description": "Gestioneu els servidors i les eines del Model Context Protocol (MCP)",
"settings.extensions.description": "Gestioneu les extensions disponibles en aquest servidor",
"settings.extensions.tab.mcps": "MCP",
"settings.extensions.tab.skills": "Habilitats",
"settings.extensions.availableAll": "Disponible per a tots els projectes",
"settings.extensions.manageConfig": "Gestiona-ho a opencode.json",
"settings.extensions.addSkills": "Com afegir habilitats",
"dialog.server.authenticate.title": "Autentica",
"project.settings.general.description": "Gestioneu el nom i l'aparença del projecte",
"project.settings.scripts": "Scripts",
"project.settings.scripts.description": "Configureu els scripts d'aquest projecte",
"project.settings.extensions.description": "Consulteu les extensions disponibles per a aquest projecte",
"project.settings.extensions.tab.lsps": "LSP",
"project.settings.extensions.added": "Afegit a aquest projecte",
"project.settings.extensions.shared": "Compartit amb tots els projectes",
"project.settings.extensions.lsp.detected": "Servidors de llenguatge detectats",
"project.settings.extensions.lsp.description": "Detectats automàticament segons els tipus de fitxer",
"project.settings.extensions.setupRequired": "Cal configurar-ho",
"settings.general.section.general": "General",
"desktop.menu.installCli": "Instal·la la CLI...",
"desktop.cli.installed.title": "CLI instal·lada",
"desktop.cli.installed.message":
"La CLI s'ha instal·lat a {{path}}\n\nReinicieu el terminal per utilitzar l'ordre 'opencode2'.",
"desktop.cli.failed.title": "La instal·lació ha fallat",
"desktop.cli.failed.message": "No s'ha pogut instal·lar la CLI: {{error}}",
"command.project.copyID": "Copia l'ID del projecte",
"command.session.copyID": "Copia l'ID de la sessió",
"server.row.incompatible":
"Aquest servidor executa OpenCode {{version}}, que no és compatible amb aquesta aplicació. Actualitzeu-lo a OpenCode V2 per continuar.",
"toast.session.copyID.failed.title": "No s'ha pogut copiar l'ID de la sessió",
"toast.session.copyID.failed.description": "S'ha produït un error en copiar l'ID de la sessió",
"toast.project.copyID.failed.title": "No s'ha pogut copiar l'ID del projecte",
"toast.project.copyID.failed.description": "S'ha produït un error en copiar l'ID del projecte",
"error.page.title.status": "La sol·licitud al servidor ha fallat",
"error.page.description.status": "El servidor ha retornat una resposta HTTP {{status}}.",
"error.page.action.reload": "Torna a carregar",
"session.error.incompatible": "Aquesta versió del servidor no és compatible",
"session.error.incompatible.description":
"{{server}} executa OpenCode {{version}}, que no és compatible amb aquesta aplicació. Actualitzeu el servidor a OpenCode V2 per continuar.",
"session.background.moveInline": "Premeu {{keybind}} per moure la feina en curs al segon pla",
"session.background.running": "Hi ha feina en curs en segon pla",
"session.background.runningCount.one": "{{count}} element en curs en segon pla",
"session.background.runningCount.many": "{{count}} d'elements en curs en segon pla",
"session.background.runningCount.other": "{{count}} elements en curs en segon pla",
"settings.general.row.terminalPlacement.title": "Ubicació del terminal",
"settings.general.row.terminalPlacement.description": "Trieu on s'obre el terminal a les sessions",
"settings.general.row.terminalPlacement.side": "Al costat",
"settings.general.row.terminalPlacement.bottom": "A baix",
"settings.general.row.showProjectIcon.title": "Icona del projecte",
"settings.general.row.showProjectIcon.description": "Mostra la icona del projecte a la capçalera de la sessió",
}
+117
View File
@@ -1100,4 +1100,121 @@ export const dict = {
"workspace.reset.archived.many": "{{count}} relací bude archivováno.",
"workspace.reset.note": "Tím se pracovní prostor resetuje tak, aby odpovídal výchozí větvi.",
"dialog.usageExceeded.dontShowAgain": "Znovu nezobrazovat",
"provider.disconnect.toast.noCredentials.description":
"Pro {{provider}} nebyly nalezeny žádné vyměnitelné přihlašovací údaje.",
"provider.disconnect.toast.failed.description": "Odpojení {{provider}} se nezdařilo.",
"toast.migration.failed.title": "Migrace dat se nezdařila",
"command.session.background": "Přesuňte se na pozadí",
"session.timeline.notice.finished": "{{actor}} dokončeno",
"session.timeline.notice.failed": "{{actor}} se nezdařilo",
"session.timeline.notice.cancelled": "{{actor}} zrušeno",
"settings.tab.preferences": "Předvolby",
"settings.tab.notifications": "Oznámení",
"settings.tab.projects": "Projekty",
"settings.tab.extensions": "Rozšíření",
"settings.preferences.description": "Přizpůsobte předvolby a motiv a výchozí chování",
"settings.appearance.description": "Přizpůsobte si motiv a písma",
"settings.notifications.description": "Vyberte, kdy chcete dostávat oznámení a slyšet zvuky",
"settings.shortcuts.description": "Přizpůsobte si zkratky pro běžné akce",
"settings.servers.description": "Správa připojení k serveru",
"settings.projects.title": "Projekty",
"settings.projects.description": "Spravujte nastavení projektu na tomto serveru",
"settings.projects.empty": "Nebyly nalezeny žádné projekty",
"settings.projects.server.all": "Všechny servery",
"settings.mcps.description": "Správa serverů a nástrojů Model Context Protocol (MCP).",
"settings.extensions.description": "Spravujte rozšíření dostupná na tomto serveru",
"settings.extensions.tab.mcps": "MCP",
"settings.extensions.tab.skills": "Dovednosti",
"settings.extensions.availableAll": "Dostupné pro všechny projekty",
"settings.extensions.manageConfig": "Spravujte v opencode.json",
"settings.extensions.addSkills": "Jak přidat dovednosti",
"dialog.server.authenticate.title": "Ověřit",
"project.settings.general.description": "Spravujte název a vzhled projektu",
"project.settings.scripts": "Skripty",
"project.settings.scripts.description": "Nakonfigurujte skripty pro tento projekt",
"project.settings.extensions.description": "Zobrazit rozšíření dostupná pro tento projekt",
"project.settings.extensions.tab.lsps": "LSP",
"project.settings.extensions.added": "Přidáno do tohoto projektu",
"project.settings.extensions.shared": "Sdíleno se všemi projekty",
"project.settings.extensions.lsp.detected": "Zjištěné jazykové servery",
"project.settings.extensions.lsp.description": "Automaticky zjištěno z typů souborů",
"project.settings.extensions.setupRequired": "Je vyžadováno nastavení",
"settings.general.section.general": "Obecné",
"common.viewAll": "Zobrazit vše",
"session.new.workspace.local.tooltip": "Použít aktuální pracovní kopii",
"session.new.workspace.new.tooltip": "Vytvořit izolovanou pracovní kopii",
"session.new.workspace.fromBranch": "od {{branch}}",
"session.new.workspace.trigger.tooltip": "Vyberte, kde chcete relaci spustit",
"session.new.workspace.search.placeholder": "Prohledejte pracovní prostory",
"settings.tab.workspaces": "Pracovní prostory",
"settings.workspaces.filter.all": "Všechny projekty",
"settings.workspaces.empty": "Žádné pracovní prostory",
"settings.workspaces.count.one": "Pracovní plocha {{count}}",
"settings.workspaces.count.other": "Pracovní prostory {{count}}",
"settings.workspaces.sessions.one": "Relace {{count}} v {{project}}",
"settings.workspaces.sessions.other": "Relace {{count}} v {{project}}",
"settings.workspaces.lastActiveSession": "Poslední aktivní relace",
"settings.workspaces.deleteAll": "Smazat všechny pracovní prostory",
"settings.workspaces.deleteAll.confirm": "Smazat všechny pracovní prostory {{count}}?",
"settings.workspaces.delete.warning":
"Adresář a větev pracovního prostoru budou trvale odstraněny, včetně všech nesloučených změn zobrazených níže.",
"settings.workspaces.deleteAll.warning":
"Vybrané pracovní prostory {{count}} v {{project}} budou trvale odstraněny, pouze pokud jsou čisté, neaktivní a nemají žádné propojené relace.",
"settings.workspaces.delete.blocked.active": "Aktivní pracovní prostor nelze smazat.",
"settings.workspaces.delete.blocked.linked":
"Propojené relace zůstanou, ale jejich pracovní adresář bude trvale odstraněn.",
"settings.workspaces.default.title": "Výchozí prostředí",
"settings.workspaces.default.description": "Vyberte, kde začínají nové relace",
"settings.workspaces.default.lastUsed": "Naposledy použito na projekt",
"settings.workspaces.default.local": "Místní adresář",
"settings.workspaces.default.new": "Nový pracovní prostor",
"workspace.move.title": "Přesuňte se do pracovního prostoru",
"workspace.move.menu.title": "Přesunout relaci do",
"workspace.move.failed": "Přesun relace se nezdařil",
"workspace.lifecycle.creating": "Vytváření pracovního prostoru",
"workspace.lifecycle.created": "Pracovní prostor vytvořen",
"workspace.lifecycle.starting": "Zahájení relace",
"workspace.onboarding.title": "Izolujte relace s pracovními prostory",
"workspace.onboarding.description": "Každá získá vlastní pracovní kopii, takže nic nezasahuje do místního repozitáře",
"workspace.lifecycle.moving": "Přesun do pracovního prostoru",
"workspace.lifecycle.set": "Pracovní prostor nastaven",
"session.summary.title": "Podrobnosti relace",
"session.summary.noBranch": "Žádná větev",
"session.summary.basedOn": "Na základě {{branch}}",
"settings.workspaces.count.few": "{{count}} pracovní plochy",
"settings.workspaces.count.many": "{{count}} pracovních míst",
"settings.workspaces.sessions.few": "{{count}} sezení v {{project}}",
"settings.workspaces.sessions.many": "{{count}} relací v {{project}}",
"desktop.menu.installCli": "Nainstalovat CLI...",
"desktop.cli.installed.title": "CLI nainstalováno",
"desktop.cli.installed.message":
"CLI bylo nainstalováno do {{path}}\n\nChcete-li používat příkaz 'opencode2', restartujte terminál.",
"desktop.cli.failed.title": "Instalace se nezdařila",
"desktop.cli.failed.message": "CLI se nepodařilo nainstalovat: {{error}}",
"command.project.copyID": "Kopírovat ID projektu",
"command.session.copyID": "Kopírovat ID relace",
"server.row.incompatible":
"Tento server používá OpenCode {{version}}, který není kompatibilní s touto aplikací. Chcete-li pokračovat, upgradujte jej na OpenCode V2.",
"toast.session.copyID.failed.title": "ID relace se nepodařilo zkopírovat",
"toast.session.copyID.failed.description": "Při kopírování ID relace došlo k chybě",
"toast.project.copyID.failed.title": "ID projektu se nepodařilo zkopírovat",
"toast.project.copyID.failed.description": "Při kopírování ID projektu došlo k chybě",
"error.page.title.status": "Požadavek na server se nezdařil",
"error.page.description.status": "Server vrátil odpověď HTTP {{status}}.",
"error.page.action.reload": "Načíst znovu",
"session.error.incompatible": "Tato verze serveru není podporována",
"session.error.incompatible.description":
"{{server}} používá OpenCode {{version}}, který není kompatibilní s touto aplikací. Chcete-li pokračovat, upgradujte server na OpenCode V2.",
"session.background.moveInline": "Stisknutím {{keybind}} přesunete probíhající práci na pozadí",
"session.background.running": "Práce probíhá na pozadí",
"session.background.runningCount.one": "Na pozadí probíhá {{count}} položka",
"session.background.runningCount.few": "Na pozadí probíhají {{count}} položky",
"session.background.runningCount.many": "Na pozadí probíhá {{count}} položky",
"session.background.runningCount.other": "Na pozadí probíhá {{count}} položek",
"settings.general.row.terminalPlacement.title": "Umístění terminálu",
"settings.general.row.terminalPlacement.description": "Zvolte, kde se bude terminál v relacích otevírat",
"settings.general.row.terminalPlacement.side": "Na straně",
"settings.general.row.terminalPlacement.bottom": "Dole",
"settings.general.row.showProjectIcon.title": "Ikona projektu",
"settings.general.row.showProjectIcon.description": "Zobrazit ikonu projektu v záhlaví relace",
}
+112
View File
@@ -1186,4 +1186,116 @@ export const dict = {
"desktop.picker.error.sizeLimit": "De valgte vedhæftede filer overskrider grænsen på {{limit}} MB",
"command.logs.export": "Eksportér logfiler",
"provider.disconnect.toast.noCredentials.description":
"Der blev ikke fundet nogen flytbare legitimationsoplysninger for {{provider}}.",
"provider.disconnect.toast.failed.description": "Kunne ikke afbryde {{provider}}.",
"toast.migration.failed.title": "Datamigrering mislykkedes",
"command.session.background": "Flyt til baggrunden",
"session.timeline.notice.finished": "{{actor}} færdig",
"session.timeline.notice.failed": "{{actor}} mislykkedes",
"session.timeline.notice.cancelled": "{{actor}} annulleret",
"settings.tab.preferences": "Præferencer",
"settings.tab.notifications": "Meddelelser",
"settings.tab.projects": "Projekter",
"settings.tab.extensions": "Udvidelser",
"settings.preferences.description": "Tilpas præferencer og tema og standardadfærd",
"settings.appearance.description": "Tilpas tema og skrifttyper",
"settings.notifications.description": "Vælg, hvornår du vil modtage notifikationer og høre lyde",
"settings.shortcuts.description": "Tilpas genveje til almindelige handlinger",
"settings.servers.description": "Administrer serverforbindelser",
"settings.projects.title": "Projekter",
"settings.projects.description": "Administrer projektindstillinger på denne server",
"settings.projects.empty": "Ingen projekter fundet",
"settings.projects.server.all": "Alle servere",
"settings.mcps.description": "Administrer Model Context Protocol (MCP) servere og værktøjer",
"settings.extensions.description": "Administrer udvidelser, der er tilgængelige på denne server",
"settings.extensions.tab.mcps": "MCP'er",
"settings.extensions.tab.skills": "Færdigheder",
"settings.extensions.availableAll": "Tilgængelig til alle projekter",
"settings.extensions.manageConfig": "Administrer i opencode.json",
"settings.extensions.addSkills": "Sådan tilføjer du færdigheder",
"dialog.server.authenticate.title": "Godkend",
"project.settings.general.description": "Administrer projektnavn og udseende",
"project.settings.scripts": "Scripts",
"project.settings.scripts.description": "Konfigurer scripts til dette projekt",
"project.settings.extensions.description": "Se tilgængelige udvidelser til dette projekt",
"project.settings.extensions.tab.lsps": "LSP'er",
"project.settings.extensions.added": "Tilføjet til dette projekt",
"project.settings.extensions.shared": "Deles med alle projekter",
"project.settings.extensions.lsp.detected": "Detekterede sprogservere",
"project.settings.extensions.lsp.description": "Auto-detekteret fra filtyper",
"project.settings.extensions.setupRequired": "Opsætning påkrævet",
"settings.general.section.general": "Generel",
"common.viewAll": "Se alle",
"session.new.workspace.local.tooltip": "Brug det aktuelle checkout",
"session.new.workspace.new.tooltip": "Opret et isoleret checkout",
"session.new.workspace.fromBranch": "fra {{branch}}",
"session.new.workspace.trigger.tooltip": "Vælg, hvor sessionen skal køres",
"session.new.workspace.search.placeholder": "Søg i arbejdsområder",
"settings.tab.workspaces": "Arbejdsrum",
"settings.workspaces.filter.all": "Alle projekter",
"settings.workspaces.empty": "Ingen arbejdsområder",
"settings.workspaces.count.one": "{{count}} arbejdsområde",
"settings.workspaces.count.other": "{{count}} arbejdsområder",
"settings.workspaces.sessions.one": "{{count}} session i {{project}}",
"settings.workspaces.sessions.other": "{{count}}-sessioner i {{project}}",
"settings.workspaces.lastActiveSession": "Sidste aktive session",
"settings.workspaces.deleteAll": "Slet alle arbejdsområder",
"settings.workspaces.deleteAll.confirm": "Vil du slette alle {{count}}-arbejdsområder?",
"settings.workspaces.delete.warning":
"Arbejdsområdets bibliotek og filial vil blive permanent fjernet, inklusive eventuelle ikke-sammenflettede ændringer vist nedenfor.",
"settings.workspaces.deleteAll.warning":
"De {{count}} valgte arbejdsområder i {{project}} vil kun blive permanent fjernet, hvis hver af dem er rene, inaktive og ikke har nogen linkede sessioner.",
"settings.workspaces.delete.blocked.active": "Det aktive arbejdsområde kan ikke slettes.",
"settings.workspaces.delete.blocked.linked":
"Linkede sessioner forbliver, men deres arbejdsmappe vil blive permanent fjernet.",
"settings.workspaces.default.title": "Standard miljø",
"settings.workspaces.default.description": "Vælg, hvor nye sessioner starter",
"settings.workspaces.default.lastUsed": "Sidst brugt pr. projekt",
"settings.workspaces.default.local": "Lokal vejviser",
"settings.workspaces.default.new": "Nyt arbejdsrum",
"workspace.move.title": "Flyt til arbejdsområdet",
"workspace.move.menu.title": "Flyt session til",
"workspace.move.failed": "Sessionen kunne ikke flyttes",
"workspace.lifecycle.creating": "Oprettelse af arbejdsrum",
"workspace.lifecycle.created": "Arbejdsområde oprettet",
"workspace.lifecycle.starting": "Start session",
"workspace.onboarding.title": "Isoler sessioner med arbejdsområder",
"workspace.onboarding.description": "Hver får sit eget checkout, så intet forstyrrer dit lokale repository",
"workspace.lifecycle.moving": "Flytter til arbejdsområde",
"workspace.lifecycle.set": "Arbejdsområde angivet",
"session.summary.title": "Session detaljer",
"session.summary.noBranch": "Ingen gren",
"session.summary.basedOn": "Baseret på {{branch}}",
"desktop.menu.installCli": "Installér CLI...",
"desktop.cli.installed.title": "CLI installeret",
"desktop.cli.installed.message":
"CLI installeret i {{path}}\n\nGenstart terminalen for at bruge kommandoen 'opencode2'.",
"desktop.cli.failed.title": "Installationen mislykkedes",
"desktop.cli.failed.message": "CLI kunne ikke installeres: {{error}}",
"command.project.copyID": "Kopier projekt-id",
"command.session.copyID": "Kopier sessions-id",
"server.row.incompatible":
"Denne server kører OpenCode {{version}}, som ikke er kompatibel med denne app. Opgrader den til OpenCode V2 for at fortsætte.",
"toast.session.copyID.failed.title": "Sessions-id'et kunne ikke kopieres",
"toast.session.copyID.failed.description": "Der opstod en fejl under kopiering af sessions-id'et",
"toast.project.copyID.failed.title": "Projekt-id'et kunne ikke kopieres",
"toast.project.copyID.failed.description": "Der opstod en fejl under kopiering af projekt-id'et",
"error.page.title.status": "Serveranmodningen mislykkedes",
"error.page.description.status": "Serveren returnerede et HTTP {{status}}-svar.",
"error.page.action.reload": "Genindlæs",
"session.error.incompatible": "Denne serverversion understøttes ikke",
"session.error.incompatible.description":
"{{server}} kører OpenCode {{version}}, som ikke er kompatibel med denne app. Opgrader serveren til OpenCode V2 for at fortsætte.",
"session.background.moveInline": "Tryk på {{keybind}} for at flytte igangværende arbejde til baggrunden",
"session.background.running": "Arbejde kører i baggrunden",
"session.background.runningCount.one": "{{count}} element kører i baggrunden",
"session.background.runningCount.other": "{{count}} elementer kører i baggrunden",
"settings.general.row.terminalPlacement.title": "Terminalplacering",
"settings.general.row.terminalPlacement.description": "Vælg, hvor terminalen åbnes i sessioner",
"settings.general.row.terminalPlacement.side": "Side",
"settings.general.row.terminalPlacement.bottom": "Nederst",
"settings.general.row.showProjectIcon.title": "Projektikon",
"settings.general.row.showProjectIcon.description": "Vis projektikonet i sessionsoverskriften",
}
+113
View File
@@ -1123,4 +1123,117 @@ export const dict = {
"desktop.picker.error.notSelected": "Im Auswahldialog wurde keine Datei ausgewählt",
"desktop.picker.error.sizeLimit": "Die ausgewählten Anhänge überschreiten das Limit von {{limit}} MB",
"command.logs.export": "Protokolle exportieren",
"provider.disconnect.toast.noCredentials.description":
"Für {{provider}} wurden keine entfernbaren Anmeldeinformationen gefunden.",
"provider.disconnect.toast.failed.description": "{{provider}} konnte nicht getrennt werden.",
"toast.migration.failed.title": "Die Datenmigration ist fehlgeschlagen",
"command.session.background": "In den Hintergrund wechseln",
"session.timeline.notice.finished": "{{actor}} fertig",
"session.timeline.notice.failed": "{{actor}} ist fehlgeschlagen",
"session.timeline.notice.cancelled": "{{actor}} abgebrochen",
"settings.tab.preferences": "Präferenzen",
"settings.tab.notifications": "Benachrichtigungen",
"settings.tab.projects": "Projekte",
"settings.tab.extensions": "Erweiterungen",
"settings.preferences.description": "Passen Sie Einstellungen, Design und Standardverhalten an",
"settings.appearance.description": "Passen Sie Design und Schriftarten an",
"settings.notifications.description": "Wählen Sie aus, wann Sie Benachrichtigungen erhalten und Töne hören möchten",
"settings.shortcuts.description": "Passen Sie Verknüpfungen für häufige Aktionen an",
"settings.servers.description": "Serververbindungen verwalten",
"settings.projects.title": "Projekte",
"settings.projects.description": "Projekteinstellungen auf diesem Server verwalten",
"settings.projects.empty": "Keine Projekte gefunden",
"settings.projects.server.all": "Alle Server",
"settings.mcps.description": "Verwalten Sie Model Context Protocol (MCP)-Server und -Tools",
"settings.extensions.description": "Verwalten Sie die auf diesem Server verfügbaren Erweiterungen",
"settings.extensions.tab.mcps": "MCPs",
"settings.extensions.tab.skills": "Fähigkeiten",
"settings.extensions.availableAll": "Verfügbar für alle Projekte",
"settings.extensions.manageConfig": "Verwalten Sie in opencode.json",
"settings.extensions.addSkills": "So fügen Sie Fähigkeiten hinzu",
"dialog.server.authenticate.title": "Authentifizieren",
"project.settings.general.description": "Verwalten Sie den Namen und das Erscheinungsbild des Projekts",
"project.settings.scripts": "Skripte",
"project.settings.scripts.description": "Konfigurieren Sie Skripte für dieses Projekt",
"project.settings.extensions.description": "Für dieses Projekt verfügbare Erweiterungen anzeigen",
"project.settings.extensions.tab.lsps": "LSPs",
"project.settings.extensions.added": "Zu diesem Projekt hinzugefügt",
"project.settings.extensions.shared": "Mit allen Projekten geteilt",
"project.settings.extensions.lsp.detected": "Erkannte Sprachserver",
"project.settings.extensions.lsp.description": "Wird anhand der Dateitypen automatisch erkannt",
"project.settings.extensions.setupRequired": "Einrichtung erforderlich",
"settings.general.section.general": "Allgemein",
"common.viewAll": "Alle anzeigen",
"session.new.workspace.local.tooltip": "Aktuellen Checkout verwenden",
"session.new.workspace.new.tooltip": "Isolierten Checkout erstellen",
"session.new.workspace.fromBranch": "von {{branch}}",
"session.new.workspace.trigger.tooltip": "Wählen Sie aus, wo die Sitzung ausgeführt werden soll",
"session.new.workspace.search.placeholder": "Arbeitsbereiche durchsuchen",
"settings.tab.workspaces": "Arbeitsbereiche",
"settings.workspaces.filter.all": "Alle Projekte",
"settings.workspaces.empty": "Keine Arbeitsbereiche",
"settings.workspaces.count.one": "{{count}}-Arbeitsbereich",
"settings.workspaces.count.other": "{{count}} Arbeitsbereiche",
"settings.workspaces.sessions.one": "{{count}}-Sitzung in {{project}}",
"settings.workspaces.sessions.other": "{{count}}-Sitzungen in {{project}}",
"settings.workspaces.lastActiveSession": "Letzte aktive Sitzung",
"settings.workspaces.deleteAll": "Löschen Sie alle Arbeitsbereiche",
"settings.workspaces.deleteAll.confirm": "Alle {{count}}-Arbeitsbereiche löschen?",
"settings.workspaces.delete.warning":
"Das Arbeitsbereichsverzeichnis und der Zweig werden dauerhaft entfernt, einschließlich aller unten aufgeführten nicht zusammengeführten Änderungen.",
"settings.workspaces.deleteAll.warning":
"Die von {{count}} ausgewählten Arbeitsbereiche in {{project}} werden nur dann dauerhaft entfernt, wenn jeder sauber und inaktiv ist und keine verknüpften Sitzungen aufweist.",
"settings.workspaces.delete.blocked.active": "Der aktive Arbeitsbereich kann nicht gelöscht werden.",
"settings.workspaces.delete.blocked.linked":
"Verknüpfte Sitzungen bleiben bestehen, ihr Arbeitsverzeichnis wird jedoch dauerhaft entfernt.",
"settings.workspaces.default.title": "Standardumgebung",
"settings.workspaces.default.description": "Wählen Sie, wo neue Sitzungen beginnen",
"settings.workspaces.default.lastUsed": "Zuletzt pro Projekt verwendet",
"settings.workspaces.default.local": "Lokales Verzeichnis",
"settings.workspaces.default.new": "Neuer Arbeitsbereich",
"workspace.move.title": "Wechseln Sie zum Arbeitsbereich",
"workspace.move.menu.title": "Sitzung verschieben nach",
"workspace.move.failed": "Sitzung konnte nicht verschoben werden",
"workspace.lifecycle.creating": "Arbeitsbereich erstellen",
"workspace.lifecycle.created": "Arbeitsbereich erstellt",
"workspace.lifecycle.starting": "Beginn der Sitzung",
"workspace.onboarding.title": "Isolieren Sie Sitzungen mit Arbeitsbereichen",
"workspace.onboarding.description":
"Jeder erhält seinen eigenen Checkout, sodass Ihr lokales Repository nicht beeinträchtigt wird",
"workspace.lifecycle.moving": "In Arbeitsbereich verschieben",
"workspace.lifecycle.set": "Arbeitsbereich eingestellt",
"session.summary.title": "Sitzungsdetails",
"session.summary.noBranch": "Kein Branch",
"session.summary.basedOn": "Basierend auf {{branch}}",
"desktop.menu.installCli": "CLI installieren...",
"desktop.cli.installed.title": "CLI installiert",
"desktop.cli.installed.message":
"CLI wurde unter {{path}} installiert.\n\nStarten Sie Ihr Terminal neu, um den Befehl 'opencode2' zu verwenden.",
"desktop.cli.failed.title": "Installation fehlgeschlagen",
"desktop.cli.failed.message": "CLI konnte nicht installiert werden: {{error}}",
"command.project.copyID": "Projekt-ID kopieren",
"command.session.copyID": "Sitzungs-ID kopieren",
"server.row.incompatible":
"Auf diesem Server wird OpenCode {{version}} ausgeführt, das mit dieser App nicht kompatibel ist. Aktualisieren Sie ihn auf OpenCode V2, um fortzufahren.",
"toast.session.copyID.failed.title": "Sitzungs-ID konnte nicht kopiert werden",
"toast.session.copyID.failed.description": "Beim Kopieren der Sitzungs-ID ist ein Fehler aufgetreten",
"toast.project.copyID.failed.title": "Projekt-ID konnte nicht kopiert werden",
"toast.project.copyID.failed.description": "Beim Kopieren der Projekt-ID ist ein Fehler aufgetreten",
"error.page.title.status": "Serveranfrage fehlgeschlagen",
"error.page.description.status": "Der Server hat eine HTTP-Antwort mit dem Status {{status}} zurückgegeben.",
"error.page.action.reload": "Neu laden",
"session.error.incompatible": "Diese Serverversion wird nicht unterstützt",
"session.error.incompatible.description":
"Auf {{server}} wird OpenCode {{version}} ausgeführt, das mit dieser App nicht kompatibel ist. Aktualisieren Sie den Server auf OpenCode V2, um fortzufahren.",
"session.background.moveInline": "Drücken Sie {{keybind}}, um laufende Arbeit in den Hintergrund zu verschieben",
"session.background.running": "Arbeit wird im Hintergrund ausgeführt",
"session.background.runningCount.one": "{{count}} Element wird im Hintergrund ausgeführt",
"session.background.runningCount.other": "{{count}} Elemente werden im Hintergrund ausgeführt",
"settings.general.row.terminalPlacement.title": "Terminalposition",
"settings.general.row.terminalPlacement.description": "Wählen Sie aus, wo das Terminal in Sitzungen geöffnet wird",
"settings.general.row.terminalPlacement.side": "Seitlich",
"settings.general.row.terminalPlacement.bottom": "Unten",
"settings.general.row.showProjectIcon.title": "Projektsymbol",
"settings.general.row.showProjectIcon.description": "Projektsymbol in der Sitzungsüberschrift anzeigen",
} satisfies Partial<Record<Keys, string>>
+226 -102
View File
@@ -48,7 +48,8 @@ export const dict = {
"desktop.updater.dialog.checkFailed.title": "އަޕްޑޭޓް އެރަރ",
"desktop.updater.dialog.upToDate.message": "ޔޫ އާ އަޕް ޓު ޑޭޓް.",
"desktop.updater.dialog.upToDate.title": "ނޯ އަޕްޑޭޓްސް",
"desktop.updater.dialog.ready.message": "އަޕްޑޭޓް {{version}} ޑައުންލޯޑް ކޮށްފިއެވެ. މިހާރު ރީސްޓާޓް ކުރަންތަ؟",
"desktop.updater.dialog.ready.message":
"އަޕްޑޭޓް \u2068{{version}}\u2069 ޑައުންލޯޑް ކޮށްފިއެވެ. މިހާރު ރީސްޓާޓް ކުރަންތަ؟",
"desktop.updater.dialog.ready.title": "އަޕްޑޭޓް ރެޑީ",
"desktop.updater.dialog.restart": "އަލުން ފަށާށެވެ",
"desktop.updater.dialog.later": "ފަހުން",
@@ -61,8 +62,10 @@ export const dict = {
"desktop.recovery.unresponsive": "OpenCode އިން ޖަވާބެއް ނުދެއެވެ",
"desktop.recovery.unresponsive.detail":
"އެޕް އަލުން ލޯންޗްކޮށް، ލޮގްތައް ހުޅުވައި، ނުވަތަ މަޑުކޮށްގެން ތިބެވޭނެ އެވެ.",
"desktop.recovery.loadFailed.detail": "ވިންޑޯ: {{window}}\nURL: {{url}}\nއެރަރ: {{code}} {{description}}",
"desktop.recovery.terminated.detail": "ވިންޑޯ: {{window}}\nސަބަބު: {{reason}}\nކޯޑް: {{code}}",
"desktop.recovery.loadFailed.detail":
"ވިންޑޯ: \u2068{{window}}\u2069\nURL: \u2068{{url}}\u2069\nއެރަރ: \u2068{{code}}\u2069 \u2068{{description}}\u2069",
"desktop.recovery.terminated.detail":
"ވިންޑޯ: \u2068{{window}}\u2069\nސަބަބު: \u2068{{reason}}\u2069\nކޯޑް: \u2068{{code}}\u2069",
"desktop.recovery.unknown": "<ނޭނގޭ>",
"desktop.dialog.chooseFolder": "ފޯލްޑަރެއް ހޮވާށެވެ",
"desktop.dialog.chooseFile": "ފައިލެއް ހޮވާށެވެ",
@@ -75,25 +78,27 @@ export const dict = {
"desktop.wsl.error.listOnline": "އޮންލައިންކޮށް WSL ޑިސްޓްރޯސް ލިސްޓް ނުކުރެވިއްޖެއެވެ",
"desktop.wsl.error.executeDistro": "ޑިސްޓްރޯގައި ކޮމާންޑްތައް އެގްޒެކެޓް ނުކުރެވޭނެއެވެ",
"desktop.wsl.error.installWsl": "WSL އިންސްޓޯލް ކުރުން ނާކާމިޔާބުވެއްޖެ",
"desktop.wsl.error.installDistro": "ޑިސްޓްރޯ: {{distro}} އިންސްޓޯލް ކުރަން ނާކާމިޔާބު",
"desktop.wsl.error.installDistro": "ޑިސްޓްރޯ: \u2068{{distro}}\u2069 އިންސްޓޯލް ކުރަން ނާކާމިޔާބު",
"desktop.wsl.error.installOpencode": "OpenCode އިންސްޓޯލް ކުރުން ނާކާމިޔާބުވެއްޖެ",
"desktop.wsl.error.alreadyAdded": "{{distro}} މިހާރުވެސް އިތުރުކޮށްފައިވެއެވެ",
"desktop.wsl.error.alreadyAdded": "\u2068{{distro}}\u2069 މިހާރުވެސް އިތުރުކޮށްފައިވެއެވެ",
"desktop.wsl.error.opencodeMissing": "މި ޑިސްޓްރޯގައި opencode އިންސްޓޯލްކޮށްފައެއް ނުވެއެވެ",
"desktop.wsl.error.opencodeCannotRun": "opencode އިންސްޓޯލް ކޮށްފައި އޮތް ނަމަވެސް ހިންގޭ ގޮތެއް ނުވިއެވެ",
"desktop.wsl.error.opencodeNotInstalled": "OpenCode އަކީ {{distro}} ގައި އިންސްޓޯލް ކުރެވިފައިވާ އެއްޗެއް ނޫނެވެ",
"desktop.wsl.error.opencodeNotInstalled":
"OpenCode އަކީ \u2068{{distro}}\u2069 ގައި އިންސްޓޯލް ކުރެވިފައިވާ އެއްޗެއް ނޫނެވެ",
"desktop.wsl.error.updateVersion":
"OpenCode އަޕްޑޭޓް ނިމުނު ނަމަވެސް {{distro}} އަދިވެސް ރިޕޯޓް ކުރަނީ {{installed}}؛ ލަފާކުރެވޭ ގޮތުގައި {{expected}}",
"OpenCode އަޕްޑޭޓް ނިމުނު ނަމަވެސް \u2068{{distro}}\u2069 އަދިވެސް ރިޕޯޓް ކުރަނީ \u2068{{installed}}\u2069؛ ލަފާކުރެވޭ ގޮތުގައި \u2068{{expected}}\u2069",
"desktop.wsl.error.noVersion": "އެއްވެސް ވަރޝަނެއް ނެތެވެ",
"desktop.wsl.error.serverExited":
"ސްޓާޓްއަޕް ކުރުމަށްފަހު WSL ސަރވަރ އިން ނުކުމެއްޖެ (code={{code}} signal={{signal}})",
"ސްޓާޓްއަޕް ކުރުމަށްފަހު WSL ސަރވަރ އިން ނުކުމެއްޖެ (code=\u2068{{code}}\u2069 signal=\u2068{{signal}}\u2069)",
"desktop.wsl.error.serverExitedBeforeHealthy":
"WSL ސަރވަރ އިން ނުކުތީ ދުޅަހެޔޮ ސިއްހަތެއްގައި ހުރުމުގެ ކުރިން (code={{code}} signal={{signal}}){{output}}",
"WSL ސަރވަރ އިން ނުކުތީ ދުޅަހެޔޮ ސިއްހަތެއްގައި ހުރުމުގެ ކުރިން (code=\u2068{{code}}\u2069 signal=\u2068{{signal}}\u2069)\u2068{{output}}\u2069",
"desktop.wsl.error.healthTimeout":
"{{distro}} ހެލްތު ޗެކް ކުރުމަށް ސައިޑްކާރ ޓައިމް އައުޓް ކޮށްފައިވަނީ {{timeout}}ms އަށް ފަހުގައެވެ",
"desktop.wsl.error.commandTimeout": "{{command}} {{args}} ޓައިމް އައުޓް ކޮށްފައިވަނީ {{timeout}}ms އަށް ފަހުގައެވެ",
"\u2068{{distro}}\u2069 ހެލްތު ޗެކް ކުރުމަށް ސައިޑްކާރ ޓައިމް އައުޓް ކޮށްފައިވަނީ \u2068{{timeout}}\u2069ms އަށް ފަހުގައެވެ",
"desktop.wsl.error.commandTimeout":
"\u2068{{command}}\u2069 \u2068{{args}}\u2069 ޓައިމް އައުޓް ކޮށްފައިވަނީ \u2068{{timeout}}\u2069ms އަށް ފަހުގައެވެ",
"desktop.wsl.error.failedPort": "ބަނދަރު ހޯދުމަށް ނާކާމިޔާބުވިއެވެ",
"desktop.picker.error.notSelected": "ފައިލް ހޮވާފައިވަނީ ޕިކަރ އިން ނޫނެވެ",
"desktop.picker.error.sizeLimit": "ހޮވާލެވޭ އެޓޭޗްމަންޓްތައް {{limit}} MB ލިމިޓަށްވުރެ މަތިވެއެވެ",
"desktop.picker.error.sizeLimit": "ހޮވާލެވޭ އެޓޭޗްމަންޓްތައް \u2068{{limit}}\u2069 MB ލިމިޓަށްވުރެ މަތިވެއެވެ",
"command.category.suggested": "ލަފާދީފިއެވެ",
"command.category.view": "މަންޒަރު",
"command.category.project": "ޕްރޮޖެކްޓް",
@@ -119,7 +124,7 @@ export const dict = {
"command.project.open": "އޯޕަން ޕްރޮޖެކްޓް",
"command.project.previous": "ކުރީގެ މަޝްރޫޢު",
"command.project.next": "ދެން އޮތް މަޝްރޫޢެވެ",
"command.project.index": "ޕްރޮޖެކްޓް {{index}} އަށް ބަދަލުވާށެވެ",
"command.project.index": "ޕްރޮޖެކްޓް \u2068{{index}}\u2069 އަށް ބަދަލުވާށެވެ",
"command.provider.connect": "ޕްރޮވައިޑަރ ގުޅުވާށެވެ",
"command.server.switch": "ސަރވަރ ބަދަލުކުރުން",
"command.settings.open": "ސެޓިންގސް ހުޅުވާލާށެވެ",
@@ -130,11 +135,11 @@ export const dict = {
"command.session.archive": "އަރުޝީފް ސެޝަން",
"command.palette": "ކޮމާންޑް ޕެލެޓް",
"command.theme.cycle": "ސައިކަލް ތީމް",
"command.theme.set": "ތީމް ބޭނުންކުރުން: {{theme}}",
"command.theme.set": "ތީމް ބޭނުންކުރުން: \u2068{{theme}}\u2069",
"command.theme.scheme.cycle": "ސައިކަލް ކުލަ ސްކީމް",
"command.theme.scheme.set": "ކުލަ ސްކީމް ބޭނުންކުރުން: {{scheme}}",
"command.theme.scheme.set": "ކުލަ ސްކީމް ބޭނުންކުރުން: \u2068{{scheme}}\u2069",
"command.language.cycle": "ސައިކަލް ބަސް",
"command.language.set": "ބަސް ބޭނުންކުރުން: {{language}}",
"command.language.set": "ބަސް ބޭނުންކުރުން: \u2068{{language}}\u2069",
"command.session.new": "އާ ޖަލްސާއެއް",
"command.file.open": "ފައިލް ހުޅުވާލާށެވެ",
"command.tab.close": "ޓެބް ބަންދުކުރުން",
@@ -205,23 +210,23 @@ export const dict = {
"dialog.model.empty": "މޮޑެލް ނަތީޖާއެއް ނެތެވެ",
"dialog.model.manage": "މޮޑެލްތައް މެނޭޖްކުރުން",
"dialog.model.manage.description": "މޮޑެލް ސެލެކްޓަރގައި ފެންނަނީ ކޮން މޮޑެލްތަކެއްކަން ކަސްޓަމައިޒް ކުރުން.",
"dialog.model.manage.provider.toggle": "ހުރިހާ {{provider}} މޮޑެލްތަކެއް ޓޮގްލް ކުރާށެވެ",
"dialog.model.manage.provider.toggle": "ހުރިހާ \u2068{{provider}}\u2069 މޮޑެލްތަކެއް ޓޮގްލް ކުރާށެވެ",
"dialog.model.unpaid.freeModels.title": "ހިލޭ މޮޑެލްތައް ފޯރުކޮށްދެނީ OpenCode އިންނެވެ",
"dialog.model.unpaid.addMore.title": "މަޝްހޫރު ޕްރޮވައިޑަރުންގެ އިތުރު މޮޑެލްތައް އިތުރުކުރުން",
"dialog.model.unpaid.viewMoreProviders": "އިތުރު 70+ ޕްރޮވައިޑަރުން ބައްލަވާށެވެ",
"dialog.provider.viewAll": "އިތުރު ޕްރޮވައިޑަރުން ދައްކާށެވެ",
"provider.connect.title": "{{provider}} ގުޅުވާށެވެ",
"provider.connect.title": "\u2068{{provider}}\u2069 ގުޅުވާށެވެ",
"provider.connect.title.anthropicProMax": "Claude ޕްރޯ/މެކްސް އިން ލޮގިން ވާނެއެވެ",
"provider.connect.selectMethod": "{{provider}} އަށް ލޮގިން މެތޯޑް ހޮވާށެވެ.",
"provider.connect.selectMethod": "\u2068{{provider}}\u2069 އަށް ލޮގިން މެތޯޑް ހޮވާށެވެ.",
"provider.connect.method.apiKey": "API ކީ އެވެ",
"provider.connect.method.browser": "ބްރައުޒާ އެވެ",
"provider.connect.method.headless": "އިންޓަފޭސް ނެތި",
"provider.connect.status.inProgress": "ހުއްދަ ދިނުމުގެ މަސައްކަތް ކުރިއަށްދާ...",
"provider.connect.status.waiting": "ހުއްދައަށް އިންތިޒާރު ކުރަމުން...",
"provider.connect.status.failed": "ހުއްދަ ދިނުން ނާކާމިޔާބު: {{error}}",
"provider.connect.status.failed": "ހުއްދަ ދިނުން ނާކާމިޔާބު: \u2068{{error}}\u2069",
"provider.connect.apiKey.description":
"ތިބާގެ އެކައުންޓް ގުޅުވައި، OpenCode ގައި {{provider}} މޮޑެލްތައް ބޭނުންކުރުމަށް {{provider}} API ކީ ލިޔުއްވާ.",
"provider.connect.apiKey.label": "{{provider}} API ކީ އެވެ",
"ތިބާގެ އެކައުންޓް ގުޅުވައި، OpenCode ގައި \u2068{{provider}}\u2069 މޮޑެލްތައް ބޭނުންކުރުމަށް \u2068{{provider}}\u2069 API ކީ ލިޔުއްވާ.",
"provider.connect.apiKey.label": "\u2068{{provider}}\u2069 API ކީ އެވެ",
"provider.connect.apiKey.placeholder": "API ކީ އެވެ",
"provider.connect.apiKey.required": "API ކީ ބޭނުންވެއެވެ",
"provider.connect.opencodeZen.line1":
@@ -234,18 +239,18 @@ export const dict = {
"provider.connect.oauth.code.visit.prefix": "ޒިޔާރަތްކުރުން ",
"provider.connect.oauth.code.visit.link": "މި ލިންކެވެ",
"provider.connect.oauth.code.visit.suffix":
" އެކައުންޓް ގުޅުވައިދިނުމަށާއި OpenCode ގައި {{provider}} މޮޑެލްތައް ބޭނުންކުރުމަށް އޮތޯރައިޒޭޝަން ކޯޑް އެއްކުރުމަށްޓަކައެވެ.",
"provider.connect.oauth.code.label": "{{method}} ހުއްދަ ކޯޑް",
" އެކައުންޓް ގުޅުވައިދިނުމަށާއި OpenCode ގައި \u2068{{provider}}\u2069 މޮޑެލްތައް ބޭނުންކުރުމަށް އޮތޯރައިޒޭޝަން ކޯޑް އެއްކުރުމަށްޓަކައެވެ.",
"provider.connect.oauth.code.label": "\u2068{{method}}\u2069 ހުއްދަ ކޯޑް",
"provider.connect.oauth.code.placeholder": "ހުއްދަ ކޯޑް",
"provider.connect.oauth.code.required": "އޮތޯރިޒޭޝަން ކޯޑް ބޭނުންވެއެވެ",
"provider.connect.oauth.code.invalid": "ސައްހަ ނޫން އޮތޯރިޒޭޝަން ކޯޑެކެވެ",
"provider.connect.oauth.auto.visit.prefix": "ޒިޔާރަތްކުރުން ",
"provider.connect.oauth.auto.visit.link": "މި ލިންކެވެ",
"provider.connect.oauth.auto.visit.suffix":
" އަދި ތިރީގައިވާ ކޯޑް ޖައްސަވައިގެން އެކައުންޓް ގުޅުވައި، OpenCode ގައި {{provider}} މޮޑެލްތައް ބޭނުން ކުރައްވާށެވެ.",
" އަދި ތިރީގައިވާ ކޯޑް ޖައްސަވައިގެން އެކައުންޓް ގުޅުވައި، OpenCode ގައި \u2068{{provider}}\u2069 މޮޑެލްތައް ބޭނުން ކުރައްވާށެވެ.",
"provider.connect.oauth.auto.confirmationCode": "ކޮންފަރމަންސް ކޯޑް",
"provider.connect.toast.connected.title": "{{provider}} ގުޅިފައިވެއެވެ",
"provider.connect.toast.connected.description": "{{provider}} މޮޑެލްތައް މިހާރު ބޭނުންކުރެވޭނެ އެވެ.",
"provider.connect.toast.connected.title": "\u2068{{provider}}\u2069 ގުޅިފައިވެއެވެ",
"provider.connect.toast.connected.description": "\u2068{{provider}}\u2069 މޮޑެލްތައް މިހާރު ބޭނުންކުރެވޭނެ އެވެ.",
"provider.custom.title": "ކަސްޓަމް ޕްރޮވައިޑަރެވެ",
"provider.custom.unavailable": "މި ސަރވަރގައި ކަސްޓަމް ޕްރޮވައިޑަރުން ނުލިބެއެވެ",
"provider.custom.description.prefix": "OpenAI އާއި އެއްގޮތްވާ ޕްރޮވައިޑަރެއް ކޮންފިގްރޭޓްކުރުން. ބަލާށެވެ ",
@@ -285,8 +290,8 @@ export const dict = {
"provider.custom.error.baseURL.format": "ފަށަން ޖެހޭނީ http:// ނުވަތަ https:// އިންނެވެ.",
"provider.custom.error.required": "ބޭނުންވާ",
"provider.custom.error.duplicate": "ޑަބިލިކޭޓް ކުރުން",
"provider.disconnect.toast.disconnected.title": "{{provider}} ކަނެކްޓް ވެއްޖެއެވެ",
"provider.disconnect.toast.disconnected.description": "{{provider}} މޮޑެލްތައް މިހާރު ލިބެން ނެތެވެ.",
"provider.disconnect.toast.disconnected.title": "\u2068{{provider}}\u2069 ކަނެކްޓް ވެއްޖެއެވެ",
"provider.disconnect.toast.disconnected.description": "\u2068{{provider}}\u2069 މޮޑެލްތައް މިހާރު ލިބެން ނެތެވެ.",
"model.tag.free": "ހިލޭ",
"model.tag.latest": "އެންމެފަހުގެ",
"model.provider.anthropic": "Anthropic އެވެ",
@@ -299,7 +304,7 @@ export const dict = {
"model.input.audio": "އޯޑިއޯ އެވެ",
"model.input.video": "ވިޑިއޯ",
"model.input.pdf": "ޕީޑީއެފް",
"model.tooltip.allows": "ހުއްދަ ދެނީ: {{inputs}}",
"model.tooltip.allows": "ހުއްދަ ދެނީ: \u2068{{inputs}}\u2069",
"model.tooltip.context.label": "ކޮންޓެކްސްޓް",
"model.tooltip.inputs": "އިންޕުޓްސް",
"model.tooltip.model": "މޮޑެލް",
@@ -307,7 +312,7 @@ export const dict = {
"model.tooltip.reasoning": "ރިޒަނިންގ އެވެ",
"model.tooltip.reasoning.allowed": "ރިޒަނިންގ އަށް ހުއްދަދެއެވެ",
"model.tooltip.reasoning.none": "އެއްވެސް ވިސްނުމެއް ނެތެވެ",
"model.tooltip.context": "ކޮންޓެކްސްޓް ލިމިޓް {{limit}}",
"model.tooltip.context": "ކޮންޓެކްސްޓް ލިމިޓް \u2068{{limit}}\u2069",
"common.search.placeholder": "ހޯދުން",
"common.goBack": "ފަހަތަށް ދަތުރުކޮށްލާށެވެ",
"common.goForward": "ކުރިއަށް ދަތުރުކޮށްލާށެވެ",
@@ -324,8 +329,8 @@ export const dict = {
"common.saving": "ސޭވިންގ...",
"common.default": "ޑީފޯލްޓް",
"common.attachment": "އެކުލެވުން",
"prompt.placeholder.shell": "ޝެލް ކޮމާންޑް ލިޔުއްވާ... {{example}}",
"prompt.placeholder.normal": 'ކޮންމެ އެއްޗަކާ އަހާލަން... "{{example}}".',
"prompt.placeholder.shell": "ޝެލް ކޮމާންޑް ލިޔުއްވާ... \u2068{{example}}\u2069",
"prompt.placeholder.normal": 'ކޮންމެ އެއްޗަކާ އަހާލަން... "\u2068{{example}}\u2069".',
"prompt.placeholder.simple": "ކޮންމެ އެއްޗެއް އަހާލަން...",
"prompt.placeholder.summarizeComments": "ކޮމެންޓްތައް ޚުލާޞާކޮށްލައްވާ...",
"prompt.placeholder.summarizeComment": "ކޮމެންޓް ޚުލާޞާކޮށްލައްވާ...",
@@ -392,7 +397,7 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "ޕްރޮމްޕްޓް ފޮނުވަން ނާކާމިޔާބުވިއެވެ",
"prompt.toast.promptSendFailed.description": "ސެޝަން ހޯދުމަށް ނުކުޅެދިއްޖެއެވެ",
"dialog.mcp.title": "MCPs އެވެ",
"dialog.mcp.description": "{{enabled}} ގެ {{total}} އެނެބަލްކޮށްފައިވެއެވެ",
"dialog.mcp.description": "\u2068{{enabled}}\u2069 ގެ \u2068{{total}}\u2069 އެނެބަލްކޮށްފައިވެއެވެ",
"dialog.mcp.empty": "އެއްވެސް MCPs ކޮންފިގްރޭޓް ކުރެވިފައެއް ނުވެއެވެ",
"dialog.lsp.empty": "LSPs ފައިލް ވައްތަރުތަކުން އޮޓޯ ޑިޓެކްޓް ކުރެވިފައިވެއެވެ",
"dialog.plugins.empty": "opencode.json ގައި ކޮންފިގްރޭޓް ކުރެވިފައިވާ ޕްލަގިންސް",
@@ -409,7 +414,7 @@ export const dict = {
"dialog.directory.root": "މޫ",
"dialog.directory.parent": "ބެލެނިވެރިޔާއެވެ",
"dialog.directory.readError": "މި ފޯލްޑަރ ކިޔަން ނުކުޅެދިއްޖެއެވެ",
"app.server.unreachable": "{{server}} އަށް ވާސިލްވެވޭ ގޮތެއް ނުވިއެވެ",
"app.server.unreachable": "\u2068{{server}}\u2069 އަށް ވާސިލްވެވޭ ގޮތެއް ނުވިއެވެ",
"app.server.retrying": "އޮޓޮމެޓިކުން އަލުން މަސައްކަތް ކުރަނީ...",
"app.server.otherServers": "އެހެން ސަރވަރތަކެވެ",
"dialog.server.title": "ސަރވަރސް",
@@ -455,23 +460,23 @@ export const dict = {
"wsl.onboarding.ready": "WSL ތައްޔާރުވެއްޖެއެވެ.",
"wsl.onboarding.required": "WSL ކުރިއަށް ގެންދަން ޖެހެ އެވެ.",
"wsl.onboarding.checkingDistros": "ޗެކިންގ ޑިސްޓްރޯސް...",
"wsl.onboarding.installingDistro": "{{distro}} އިންސްޓޯލް ކުރަމުން...",
"wsl.onboarding.checkingDistro": "ޗެކް ކުރަމުންދަނީ {{distro}}...",
"wsl.onboarding.installingDistro": "\u2068{{distro}}\u2069 އިންސްޓޯލް ކުރަމުން...",
"wsl.onboarding.checkingDistro": "ޗެކް ކުރަމުންދަނީ \u2068{{distro}}\u2069...",
"wsl.onboarding.listingDistros": "ލިސްޓިން ޑިސްޓްރޯސް...",
"wsl.onboarding.distroReady": "{{distro}} ތައްޔާރުވެއްޖެއެވެ.",
"wsl.onboarding.distroNotInstalled": "{{distro}} އަދި އިންސްޓޯލްކޮށްފައެއް ނުވެއެވެ.",
"wsl.onboarding.openDistroOnce": "ސެޓަޕް ނިންމާލުމަށްޓަކައި {{distro}} އެއްފަހަރު ހުޅުވާލާށެވެ.",
"wsl.onboarding.finishingDistro": "{{distro}} އަށް ފިނިޝިންގ ސެޓަޕް.",
"wsl.onboarding.distroReady": "\u2068{{distro}}\u2069 ތައްޔާރުވެއްޖެއެވެ.",
"wsl.onboarding.distroNotInstalled": "\u2068{{distro}}\u2069 އަދި އިންސްޓޯލްކޮށްފައެއް ނުވެއެވެ.",
"wsl.onboarding.openDistroOnce": "ސެޓަޕް ނިންމާލުމަށްޓަކައި \u2068{{distro}}\u2069 އެއްފަހަރު ހުޅުވާލާށެވެ.",
"wsl.onboarding.finishingDistro": "\u2068{{distro}}\u2069 އަށް ފިނިޝިންގ ސެޓަޕް.",
"wsl.onboarding.pickDistro": "ޑިސްޓްރޯއެއް ހޮވާ ނުވަތަ ތިރީގައިވާ ޑިސްޓްރޯއެއް އިންސްޓޯލް ކުރާށެވެ.",
"wsl.onboarding.checkingOpencode": "ޗެކް ކުރަމުންދަނީ OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}} ގައި OpenCode ޗެކްކުރަނީ...",
"wsl.onboarding.checkingOpencodeIn": "\u2068{{distro}}\u2069 ގައި OpenCode ޗެކްކުރަނީ...",
"wsl.onboarding.updatingOpencode": "އަޕްޑޭޓް ކުރަމުންދަނީ OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}} ގައި OpenCode އަޕްޑޭޓް ކުރަމުންދާ...",
"wsl.onboarding.updateOpencodeIn": "{{distro}} ގައި OpenCode އަޕްޑޭޓް ކުރާށެވެ.",
"wsl.onboarding.updatingOpencodeIn": "\u2068{{distro}}\u2069 ގައި OpenCode އަޕްޑޭޓް ކުރަމުންދާ...",
"wsl.onboarding.updateOpencodeIn": "\u2068{{distro}}\u2069 ގައި OpenCode އަޕްޑޭޓް ކުރާށެވެ.",
"wsl.onboarding.updateOpencode": "OpenCode އަޕްޑޭޓް ކުރާށެވެ",
"wsl.onboarding.opencodeReadyIn": "OpenCode ތައްޔާރުކޮށްފައިވަނީ {{distro}} ގައެވެ.",
"wsl.onboarding.opencodeReadyIn": "OpenCode ތައްޔާރުކޮށްފައިވަނީ \u2068{{distro}}\u2069 ގައެވެ.",
"wsl.onboarding.opencodeReady": "OpenCode ތައްޔާރުވެއްޖެއެވެ.",
"wsl.onboarding.installOpencodeIn": "{{distro}} ގައި OpenCode އިންސްޓޯލް ކުރާށެވެ.",
"wsl.onboarding.installOpencodeIn": "\u2068{{distro}}\u2069 ގައި OpenCode އިންސްޓޯލް ކުރާށެވެ.",
"wsl.onboarding.installOpencode": "OpenCode އިންސްޓޯލް ކުރާށެވެ",
"wsl.onboarding.chooseDistroFirst": "ފުރަތަމަ ޑިސްޓްރޯއެއް ހޮވާށެވެ.",
"wsl.onboarding.loadFailed": "WSL ސްޓޭޓް ލޯޑް ނުކުރެވިއްޖެއެވެ.",
@@ -504,11 +509,11 @@ export const dict = {
"wsl.onboarding.wsl2Required": "WSL 2 ބޭނުންވެއެވެ.",
"wsl.onboarding.toolsRequired": "މި ޑިސްޓްރޯއަށް ބޭނުންވަނީ bash އަދި curl އެވެ.",
"wsl.onboarding.openTerminal": "އޯޕަން ޓާމިނަލް",
"wsl.onboarding.path": "މަގު: {{path}}",
"wsl.onboarding.path": "މަގު: \u2068{{path}}\u2069",
"wsl.onboarding.notFound": "ނުފެނެއެވެ",
"wsl.onboarding.version": "ވަރޝަން: {{version}}",
"wsl.onboarding.version": "ވަރޝަން: \u2068{{version}}\u2069",
"wsl.onboarding.unknown": "ނޭނގޭ އެއްޗެއް",
"wsl.onboarding.desktopVersion": "ޑެސްކްޓޮޕް {{version}} އެވެ",
"wsl.onboarding.desktopVersion": "ޑެސްކްޓޮޕް \u2068{{version}}\u2069 އެވެ",
"wsl.onboarding.versionMismatch": "އިންސްޓޯލް ކޮށްފައިވާ ވަރޝަން ޑެސްކްޓޮޕް އެޕް ވަރޝަން އާއި އެއްގޮތެއް ނުވެއެވެ.",
"wsl.onboarding.adding": "އިތުރުކުރަނީ...",
"server.row.noUsername": "ޔޫޒަރނޭމް އެއް ނެތެވެ",
@@ -519,7 +524,7 @@ export const dict = {
"dialog.project.edit.icon.hint": "ތަސްވީރަކަށް ކްލިކް ނުވަތަ ދަމާލާށެވެ",
"dialog.project.edit.icon.recommended": "ލަފާދެނީ: 128x128px",
"dialog.project.edit.color": "ކުލަ",
"dialog.project.edit.color.select": "{{color}} ކުލަ ހޮވާށެވެ",
"dialog.project.edit.color.select": "\u2068{{color}}\u2069 ކުލަ ހޮވާށެވެ",
"dialog.project.edit.worktree.startup": "ވޯކްސްޕޭސް ސްޓާޓްއަޕް ސްކްރިޕްޓް",
"dialog.project.edit.worktree.startup.description": "އާ ވޯކްސްޕޭސް (ވޯކްޓްރީ) އުފެއްދުމަށްފަހު ހިނގައެވެ.",
"dialog.project.edit.worktree.startup.placeholder": "މ. bun install އެވެ",
@@ -578,7 +583,7 @@ export const dict = {
"language.th": "ไทย",
"language.tr": "ތުރުކްޗޭ އެވެ",
"toast.language.title": "ބަސް",
"toast.language.description": "{{language}} އަށް ބަދަލުވެއްޖެއެވެ",
"toast.language.description": "\u2068{{language}}\u2069 އަށް ބަދަލުވެއްޖެއެވެ",
"toast.theme.title": "ތީމް ބަދަލުވިއެވެ",
"toast.scheme.title": "ކުލަ ސްކީމް",
"toast.workspace.enabled.title": "ވޯކްސްޕޭސްތައް އެނެބަލްކޮށްފައިވެއެވެ",
@@ -600,13 +605,13 @@ export const dict = {
"toast.session.unshare.failed.title": "ސެޝަން އަންޝެއާ ކުރަން ފެއިލްވެއްޖެ",
"toast.session.unshare.failed.description": "ސެޝަން އަންޝެއަރ ކުރަމުން ދިޔައިރު ގޯހެއް ދިމާވިއެވެ",
"toast.session.export.success.title": "ސެޝަން އެކްސްޕޯޓް ކޮށްފި",
"toast.session.export.success.description": "ސެޝަން ސޭވްކޮށްފައިވަނީ {{filename}} އަށެވެ",
"toast.session.export.success.description": "ސެޝަން ސޭވްކޮށްފައިވަނީ \u2068{{filename}}\u2069 އަށެވެ",
"toast.session.export.failed.title": "އެކްސްޕޯޓް ސެޝަން ނާކާމިޔާބުވެއްޖެ",
"toast.session.export.failed.description": "ސެޝަން އެކްސްޕޯޓް ކުރަމުން ދިޔައިރު ގޯހެއް ދިމާވިއެވެ",
"toast.session.listFailed.title": "{{project}} އަށް ސެޝަންތައް ލޯޑް ނުކުރެވިއްޖެއެވެ",
"toast.project.reloadFailed.title": "{{project}} ރީލޯޑް ނުކުރެވިއްޖެއެވެ",
"toast.session.listFailed.title": "\u2068{{project}}\u2069 އަށް ސެޝަންތައް ލޯޑް ނުކުރެވިއްޖެއެވެ",
"toast.project.reloadFailed.title": "\u2068{{project}}\u2069 ރީލޯޑް ނުކުރެވިއްޖެއެވެ",
"toast.update.title": "އަޕްޑޭޓް ލިބެން ހުރެއެވެ",
"toast.update.description": "OpenCode ({{version}}) ގެ އާ ވަރޝަނެއް މިހާރު އިންސްޓޯލް ކުރެވޭނެއެވެ.",
"toast.update.description": "OpenCode (\u2068{{version}}\u2069) ގެ އާ ވަރޝަނެއް މިހާރު އިންސްޓޯލް ކުރެވޭނެއެވެ.",
"toast.update.action.installRestart": "އިންސްޓޯލްކޮށް އަލުން ސްޓާޓްކުރުން",
"toast.update.action.notYet": "އަދި ނޫން",
"error.page.title": "ކަމެއް ގޯސްވީއެވެ",
@@ -619,15 +624,15 @@ export const dict = {
"error.page.action.exportLogs": "އެކްސްޕޯޓް ލޮގްސް",
"error.page.action.checking": "ޗެކް ކުރަމުން...",
"error.page.action.checkUpdates": "އަޕްޑޭޓްތައް ހުރިތޯ ބަލާށެވެ",
"error.page.action.updateTo": "{{version}} އަށް އަޕްޑޭޓް ކުރާށެވެ",
"error.page.action.updateTo": "\u2068{{version}}\u2069 އަށް އަޕްޑޭޓް ކުރާށެވެ",
"error.page.circular": "[ސާކިއުލާ]",
"error.page.report.prefix": "މި އެރަރ OpenCode ޓީމަށް ރިޕޯޓް ކުރައްވާށެވެ",
"error.page.report.discord": "Discord ގައި",
"error.page.version": "ވަރޝަން: {{version}}",
"error.page.version": "ވަރޝަން: \u2068{{version}}\u2069",
"error.dev.rootNotFound":
"ރޫޓް އެލިމެންޓް ނުފެނެއެވެ. ތިބާގެ index.html އަށް އެޑް ކުރަން ހަނދާން ނެތުނީ ހެއްޔެވެ؟ ނުވަތަ id އެޓްރިބިއުޓް ގޯސްކޮށް އިމްތިހާނު ވެދާނެ ހެއްޔެވެ؟",
"error.serverSync.connectFailed":
"ސަރވަރ އާއި ގުޅެވޭ ގޮތެއް ނުވިއެވެ. `{{url}}` ގައި ސަރވަރެއް ހިނގަމުންދާ ހެއްޔެވެ؟",
"ސަރވަރ އާއި ގުޅެވޭ ގޮތެއް ނުވިއެވެ. `\u2068{{url}}\u2069` ގައި ސަރވަރެއް ހިނގަމުންދާ ހެއްޔެވެ؟",
"error.serverSDK.noServerAvailable": "ސަރވަރެއް ނުލިބެއެވެ",
"error.serverSDK.serverNotAvailable": "ސަރވަރ ނުލިބޭ",
"error.childStore.persistedCacheCreateFailed": "ޕަރސިސްޓެޑް ކެޝް އުފެއްދުމަށް ނާކާމިޔާބުވިއެވެ",
@@ -640,29 +645,34 @@ export const dict = {
"error.server.invalidConfiguration": "ސައްހަ ނޫން ކޮންފިގްރޭޝަން",
"error.chain.causedBy": "ސަބަބަކީ:",
"error.chain.apiError": "API ގެ ގޯހެއް",
"error.chain.status": "ސްޓޭޓަސް: {{status}}",
"error.chain.retryable": "އަލުން ޓްރައި ކުރެވޭނީ: {{retryable}}",
"error.chain.responseBody": "ޖަވާބުދާރީވާ ހަށިގަނޑު:\n{{body}} އެވެ",
"error.chain.didYouMean": "ތިޔަ މާނަކުރީ: {{suggestions}} ހެއްޔެވެ",
"error.chain.modelNotFound": "މޮޑެލް ނުފެނޭ: {{provider}}/{{model}}",
"error.chain.status": "ސްޓޭޓަސް: \u2068{{status}}\u2069",
"error.chain.retryable": "އަލުން ޓްރައި ކުރެވޭނީ: \u2068{{retryable}}\u2069",
"error.chain.responseBody": "ޖަވާބުދާރީވާ ހަށިގަނޑު:\n\u2068{{body}}\u2069 އެވެ",
"error.chain.didYouMean": "ތިޔަ މާނަކުރީ: \u2068{{suggestions}}\u2069 ހެއްޔެވެ",
"error.chain.modelNotFound": "މޮޑެލް ނުފެނޭ: \u2068{{provider}}\u2069/\u2068{{model}}\u2069",
"error.chain.checkConfig": "ތިބާގެ ކޮންފިގް (opencode.json) ޕްރޮވައިޑަރ/މޮޑެލް ނަންތައް ޗެކްކުރުން",
"error.chain.mcpFailed":
'MCP ސަރވަރ "{{name}}" ފޭލްވެއްޖެއެވެ. ނޯޓް، OpenCode އިން އަދި MCP އޮތެންޓިކޭޝަން އަށް ސަޕޯޓް ނުކުރެއެވެ.',
"error.chain.providerAuthFailed": "ޕްރޮވައިޑަރ އޮތެންޓިކޭޝަން ފެއިލްވެއްޖެ ({{provider}}): {{message}}",
'MCP ސަރވަރ "\u2068{{name}}\u2069" ފޭލްވެއްޖެއެވެ. ނޯޓް، OpenCode އިން އަދި MCP އޮތެންޓިކޭޝަން އަށް ސަޕޯޓް ނުކުރެއެވެ.',
"error.chain.providerAuthFailed":
"ޕްރޮވައިޑަރ އޮތެންޓިކޭޝަން ފެއިލްވެއްޖެ (\u2068{{provider}}\u2069): \u2068{{message}}\u2069",
"error.chain.providerInitFailed":
'ޕްރޮވައިޑަރ "{{provider}}" އިނިޝިއަލައިޒް ނުކުރެވުނެވެ. ކްރެޑެންޝިއަލްސް އަދި ކޮންފިގްރޭޝަން ޗެކްކުރުން.',
"error.chain.configJsonInvalid": "{{path}} ގައި ކޮންފިގް ފައިލަކީ ސައްހަ JSON(C) އެއް ނޫނެވެ",
'ޕްރޮވައިޑަރ "\u2068{{provider}}\u2069" އިނިޝިއަލައިޒް ނުކުރެވުނެވެ. ކްރެޑެންޝިއަލްސް އަދި ކޮންފިގްރޭޝަން ޗެކްކުރުން.',
"error.chain.configJsonInvalid": "\u2068{{path}}\u2069 ގައި ކޮންފިގް ފައިލަކީ ސައްހަ JSON(C) އެއް ނޫނެވެ",
"error.chain.configJsonInvalidWithMessage":
"{{path}} ގައި ކޮންފިގް ފައިލަކީ ސައްހަ ފައިލެއް ނޫން JSON(C): {{message}}",
"\u2068{{path}}\u2069 ގައި ކޮންފިގް ފައިލަކީ ސައްހަ ފައިލެއް ނޫން JSON(C): \u2068{{message}}\u2069",
"error.chain.configDirectoryTypo":
'{{path}} ގައިވާ ޑައިރެކްޓަރީ "{{dir}}" އަކީ ސައްހަ އެއްޗެއް ނޫނެވެ. ޑައިރެކްޓަރީގެ ނަން "{{suggestion}}" އަށް ބަދަލުކޮށް ނުވަތަ ނައްތާލާށެވެ. މިއީ އާންމު ޓައިޕޯއެކެވެ.',
"error.chain.configFrontmatterError": "{{path}} ގައި ފްރޮންޓްމެޓަރ ޕާރސް ކުރަން ނާކާމިޔާބު:\n{{message}}",
"error.chain.configInvalid": "{{path}} ގައި ކޮންފިގް ފައިލް އަކީ ސައްހަ ފައިލްއެކެވެ",
"error.chain.configInvalidWithMessage": "{{path}} ގައި ކޮންފިގް ފައިލް ސައްހަ ނޫން: {{message}}",
'\u2068{{path}}\u2069 ގައިވާ ޑައިރެކްޓަރީ "\u2068{{dir}}\u2069" އަކީ ސައްހަ އެއްޗެއް ނޫނެވެ. ޑައިރެކްޓަރީގެ ނަން "\u2068{{suggestion}}\u2069" އަށް ބަދަލުކޮށް ނުވަތަ ނައްތާލާށެވެ. މިއީ އާންމު ޓައިޕޯއެކެވެ.',
"error.chain.configFrontmatterError":
"\u2068{{path}}\u2069 ގައި ފްރޮންޓްމެޓަރ ޕާރސް ކުރަން ނާކާމިޔާބު:\n\u2068{{message}}\u2069",
"error.chain.configInvalid": "\u2068{{path}}\u2069 ގައި ކޮންފިގް ފައިލް އަކީ ސައްހަ ފައިލްއެކެވެ",
"error.chain.configInvalidWithMessage":
"\u2068{{path}}\u2069 ގައި ކޮންފިގް ފައިލް ސައްހަ ނޫން: \u2068{{message}}\u2069",
"notification.permission.title": "ހުއްދަ ބޭނުންވެއެވެ",
"notification.permission.description": "{{sessionTitle}} އިން {{projectName}} އަށް ހުއްދަ ބޭނުންވެއެވެ",
"notification.permission.description":
"\u2068{{sessionTitle}}\u2069 އިން \u2068{{projectName}}\u2069 އަށް ހުއްދަ ބޭނުންވެއެވެ",
"notification.question.title": "ސުވާލު",
"notification.question.description": "{{sessionTitle}} އިން {{projectName}} ގައި ސުވާލެއް އެބައޮތެވެ",
"notification.question.description":
"\u2068{{sessionTitle}}\u2069 އިން \u2068{{projectName}}\u2069 ގައި ސުވާލެއް އެބައޮތެވެ",
"notification.action.goToSession": "ސެޝަނަށް ދާށެވެ",
"notification.session.responseReady.title": "ޖަވާބުދާރީވާން ތައްޔާރު",
"notification.session.error.title": "ސެޝަން އެރަރ",
@@ -677,9 +687,9 @@ export const dict = {
"home.server.collapse": "ސަރވަރ ޕްރޮޖެކްޓްތައް ކޮލަޕްސް ކުރުން",
"home.server.expand": "ސަރވަރ ޕްރޮޖެކްޓްތައް ފުޅާކުރުން",
"home.sessions.search.placeholder": "ސަރޗް ސެޝަންތަކެވެ",
"home.sessions.search.placeholder.scoped": "{{scope}} ގައި ސަރޗް ސެޝަންތައް",
"home.sessions.search.placeholder.scoped": "\u2068{{scope}}\u2069 ގައި ސަރޗް ސެޝަންތައް",
"home.sessions.search.sessions": "ސެޝަންތައް",
"home.sessions.search.noResults": "{{query}} އަށް އެއްވެސް ސެޝަނެއް ނުފެނެއެވެ",
"home.sessions.search.noResults": "\u2068{{query}}\u2069 އަށް އެއްވެސް ސެޝަނެއް ނުފެނެއެވެ",
"home.sessions.empty": "މިތާ އަދި އެއްވެސް ކަމެއް ނެތެވެ",
"home.sessions.empty.description": "ފެށުމަށްޓަކައި ސެޝަނެއް އުފެއްދުން",
"home.sessions.group.today": "މިއަދު",
@@ -696,7 +706,7 @@ export const dict = {
"session.error.notFound.description": "މި ޓެބުން އިޝާރާތް ކުރަނީ މި ސަރވަރގައި މިހާރު ނެތް ސެޝަނަކަށެވެ.",
"session.error.notFound.closeTab": "Tab ބަންދުކުރުން",
"session.error.serverConnection": "މި ސަރވަރ އާއި ގުޅެވޭކަށް ނެތެވެ",
"session.review.filesChanged": "ފައިލްތައް ބަދަލުވެއްޖެ {{count}}",
"session.review.filesChanged": "ފައިލްތައް ބަދަލުވެއްޖެ \u2068{{count}}\u2069",
"session.review.change.one": "ބަދަލު",
"session.review.change.other": "ބަދަލުތައް",
"session.review.loadingChanges": "ބަދަލުތައް ލޯޑް ކުރަނީ...",
@@ -720,22 +730,22 @@ export const dict = {
"session.messages.loadEarlier": "ކުރީގެ މެސެޖުތައް ލޯޑް ކުރާށެވެ",
"session.messages.loading": "މެސެޖުތައް ލޯޑް ކުރަނީ...",
"session.messages.jumpToLatest": "އެންމެ ފަހުގެ ވާހަކައަށް ފުންމާލާށެވެ",
"session.context.addToContext": "ކޮންޓެކްސްޓަށް {{selection}} އިތުރުކުރުން",
"session.context.addToContext": "ކޮންޓެކްސްޓަށް \u2068{{selection}}\u2069 އިތުރުކުރުން",
"session.todo.title": "ޓޮޑޯސް އެވެ",
"session.todo.collapse": "ހޭނެތިގެން ވެއްޓުން",
"session.todo.expand": "އިތުރުކުރުން",
"session.todo.progress": "{{done}} ގެ {{total}} ޓޯޑޯސް ނިމިއްޖެއެވެ",
"session.question.progress": "{{current}} ގެ {{total}} ސުވާލުތަކެވެ",
"session.todo.progress": "\u2068{{done}}\u2069 ގެ \u2068{{total}}\u2069 ޓޯޑޯސް ނިމިއްޖެއެވެ",
"session.question.progress": "\u2068{{current}}\u2069 ގެ \u2068{{total}}\u2069 ސުވާލުތަކެވެ",
"session.question.minimize": "ސުވާލު އެންމެ ކުޑަކުރުން",
"session.question.restore": "ސުވާލު އަލުން އާލާކުރުން",
"session.question.pending.one": "{{count}} ކުރިއަށް އޮތް ސުވާލު",
"session.question.pending.other": "{{count}} ކުރިއަށް އޮތް ސުވާލުތައް",
"session.question.pending.one": "\u2068{{count}}\u2069 ކުރިއަށް އޮތް ސުވާލު",
"session.question.pending.other": "\u2068{{count}}\u2069 ކުރިއަށް އޮތް ސުވާލުތައް",
"session.new.title": "ކޮންމެ އެއްޗެއް ބިނާކުރާށެވެ",
"session.new.project.new": "އާ މަޝްރޫއެއް",
"session.new.project.search": "ޕްރޮޖެކްޓްތައް ހޯދުން",
"session.new.project.add": "ޕްރޮޖެކްޓް އިތުރުކުރުން",
"session.new.worktree.main": "މައި ބްރާންޗް",
"session.new.worktree.mainWithBranch": "މައި ގޮފި ({{branch}})",
"session.new.worktree.mainWithBranch": "މައި ގޮފި (\u2068{{branch}}\u2069)",
"session.new.worktree.create": "އާ ވޯކްޓްރީ އުފެއްދުން",
"session.new.workspace.runIn": "ރަން ސެޝަން އިން",
"session.new.workspace.triggerLocal": "ލޯކަލް",
@@ -743,11 +753,11 @@ export const dict = {
"session.new.workspace.existing": "ވޯކްސްޕޭސް...",
"session.new.git.none": "އެއްވެސް Git އެއް ނެތެވެ",
"session.new.lastModified": "އެންމެ ފަހުން ބަދަލުކުރީ",
"session.header.search.placeholder": "{{project}} ހޯދުން",
"session.header.search.placeholder": "\u2068{{project}}\u2069 ހޯދުން",
"session.header.searchFiles": "ފައިލްތައް ހޯދުން",
"session.header.openIn": "ހުޅުވާފައި އިން",
"session.header.open.action": "{{app}} ހުޅުވާލާށެވެ",
"session.header.open.ariaLabel": "{{app}} ގައި ހުޅުވާފައި ހުރެއެވެ",
"session.header.open.action": "\u2068{{app}}\u2069 ހުޅުވާލާށެވެ",
"session.header.open.ariaLabel": "\u2068{{app}}\u2069 ގައި ހުޅުވާފައި ހުރެއެވެ",
"session.header.open.menu": "އޮޕްޝަންސް ހުޅުވާލާށެވެ",
"session.header.open.copyPath": "ކޮޕީ މަގު",
"session.header.open.finder": "Finder އެވެ",
@@ -778,24 +788,24 @@ export const dict = {
"status.popover.action.manageServers": "ސަރވަރތައް މެނޭޖްކުރުން",
"common.copied": "ކޮޕީކޮށްފައި",
"lsp.tooltip.none": "LSP ސަރވަރެއް ނެތެވެ",
"lsp.label.connected": "{{count}} LSP އެވެ",
"lsp.label.connected": "\u2068{{count}}\u2069 LSP އެވެ",
"prompt.loading": "ލޯޑިންގ ޕްރޮމްޕްޓް...",
"terminal.loading": "ލޯޑިންގ ޓާމިނަލް...",
"terminal.title": "ޓާމިނަލް އެވެ",
"terminal.title.numbered": "ޓާމިނަލް {{number}} އެވެ",
"terminal.title.numbered": "ޓާމިނަލް \u2068{{number}}\u2069 އެވެ",
"terminal.close": "ޓާމިނަލް ބަންދުކުރުން",
"terminal.connectionLost.title": "ގުޅުން ގެއްލިއްޖެއެވެ",
"terminal.connectionLost.abnormalClose": "WebSocket އާދަޔާ ހިލާފަށް ބަންދުވެފައި: {{code}}",
"terminal.connectionLost.abnormalClose": "WebSocket އާދަޔާ ހިލާފަށް ބަންދުވެފައި: \u2068{{code}}\u2069",
"terminal.connectionLost.description":
"ޓާމިނަލް ކަނެކްޝަން މެދުކެނޑުނެވެ. މިއީ ސަރވަރ އަލުން ސްޓާޓް ކުރުމުން ދިމާވެދާނެ ކަމެކެވެ.",
"terminal.connectTicket.csrfError":
"PTY ކަނެކްޓް ޓިކެޓް ރިޖެކްޓް ކޮށްފައިވަނީ އޮރިޖިން ނުވަތަ CSRF ޗެކްތަކުންނެވެ. ސަރވަރ CORS ކޮންފިގް ޗެކްކުރުން.",
"terminal.connectTicket.statusError": "PTY ކަނެކްޓް ޓިކެޓް {{status}} އާއެކު ފެއިލްވެއްޖެއެވެ",
"terminal.connectTicket.statusError": "PTY ކަނެކްޓް ޓިކެޓް \u2068{{status}}\u2069 އާއެކު ފެއިލްވެއްޖެއެވެ",
"titlebar.update": "އަޕްޑޭޓް",
"titlebar.updateVersion": "{{version}} އަޕްޑޭޓް ކުރާށެވެ",
"titlebar.updateVersion": "\u2068{{version}}\u2069 އަޕްޑޭޓް ކުރާށެވެ",
"common.closeTab": "ޓެބް ބަންދުކުރުން",
"common.dismiss": "ދުރުކޮށްލުން",
"common.moreCountSuffix": " (+{{count}} އިތުރަށް)",
"common.moreCountSuffix": " (+\u2068{{count}}\u2069 އިތުރަށް)",
"common.requestFailed": "ރިކުއެސްޓް ފެއިލްވިއެވެ",
"common.moreOptions": "އިތުރު އިޚްތިޔާރުތަކެކެވެ",
"common.learnMore": "އިތުރަށް ދަސްކުރައްވާށެވެ",
@@ -824,9 +834,9 @@ export const dict = {
"common.key.insert": "Insert އެވެ",
"common.unknown": "ނޭނގޭ އެއްޗެއް",
"common.time.justNow": "ހަމަ މިހާރު",
"common.time.minutesAgo.short": "{{count}}m މީގެ ކުރިން",
"common.time.hoursAgo.short": "{{count}}h މީގެ ކުރިން",
"common.time.daysAgo.short": "{{count}}d ކުރިން",
"common.time.minutesAgo.short": "\u2068{{count}}\u2069m މީގެ ކުރިން",
"common.time.hoursAgo.short": "\u2068{{count}}\u2069h މީގެ ކުރިން",
"common.time.daysAgo.short": "\u2068{{count}}\u2069d ކުރިން",
"sidebar.menu.toggle": "ޓޮގްލް މެނޫ",
"sidebar.nav.projectsAndSessions": "ޕްރޮޖެކްޓްތަކާއި ސެޝަންތައް",
"sidebar.settings": "ސެޓިންގސް",
@@ -855,7 +865,7 @@ export const dict = {
"debugBar.jank.tip": "ފަހު 5 ސިކުންތުގައި 32ms އަށް ވުރެ ގިނަ ވަގުތު ފްރޭމް ކުރެއެވެ.",
"debugBar.long.label": "LONG އެވެ",
"debugBar.long.tip":
"އެންމެ ފަހު 5 ސިކުންތުގައި ބްލޮކް ކުރެވިފައިވާ ވަގުތާއި ލޯންގް-ޓާސްކް ކައުންޓް. މެކްސް ޓާސްކް: {{max}}.",
"އެންމެ ފަހު 5 ސިކުންތުގައި ބްލޮކް ކުރެވިފައިވާ ވަގުތާއި ލޯންގް-ޓާސްކް ކައުންޓް. މެކްސް ޓާސްކް: \u2068{{max}}\u2069.",
"debugBar.delay.label": "DELAY އެވެ",
"debugBar.delay.tip": "އެންމެ ގޯސްކޮށް ފެނުނު އިންޕުޓް ޑިލޭ ވޭތުވެދިޔަ 5 ސިކުންތުގެ ތެރޭގައި.",
"debugBar.inp.label": "INP އެވެ",
@@ -865,7 +875,8 @@ export const dict = {
"debugBar.cls.tip": "މިހާރު ބޭނުންކުރާ އެޕް ލައިފްޓައިމް އަށް ކިއުމިއުލެޓިވް ލޭއައުޓް ޝިފްޓް.",
"debugBar.mem.label": "MEM އެވެ",
"debugBar.mem.tipUnavailable": "ބޭނުންކޮށްފައިވަނީ ޖޭއެސް ހީޕް އާއި ހީޕް ލިމިޓެވެ. Chromium އެކަނި.",
"debugBar.mem.tip": "ބޭނުންކޮށްފައިވަނީ ޖޭއެސް ހީޕް އާއި ހީޕް ލިމިޓެވެ. {{used}} ގެ {{limit}} އެވެ.",
"debugBar.mem.tip":
"ބޭނުންކޮށްފައިވަނީ ޖޭއެސް ހީޕް އާއި ހީޕް ލިމިޓެވެ. \u2068{{used}}\u2069 ގެ \u2068{{limit}}\u2069 އެވެ.",
"debugBar.focus.label": "FOCUS އެވެ",
"debugBar.focus.tip": "ފޯސް ފޯކަސް ސްޓައިލްސް ހުރިހާ އިންޓަރެކްޓިވް އެލިމެންޓްތަކަށް",
"debugBar.focus.on": "ON އެވެ",
@@ -1017,7 +1028,8 @@ export const dict = {
"settings.shortcuts.reset.toast.title": "ޝޯޓްކަޓްސް ރީސެޓް ކުރުން",
"settings.shortcuts.reset.toast.description": "ކީބޯޑް ޝޯޓްކަޓްތައް ޑިފޯލްޓްތަކަށް ރީސެޓްކޮށްފައިވެއެވެ.",
"settings.shortcuts.conflict.title": "މިހާރުވެސް ބޭނުން ކުރަމުންދާ ޝޯޓްކަޓެވެ",
"settings.shortcuts.conflict.description": "{{keybind}} މިހާރުވެސް ޙަވާލުކުރެވިފައިވަނީ {{titles}} އަށެވެ.",
"settings.shortcuts.conflict.description":
"\u2068{{keybind}}\u2069 މިހާރުވެސް ޙަވާލުކުރެވިފައިވަނީ \u2068{{titles}}\u2069 އަށެވެ.",
"settings.shortcuts.unassigned": "ޙަވާލުނުކުރެވިގެންނެވެ",
"settings.shortcuts.pressKeys": "ކީސް އަށް ފިތާލާށެވެ",
"settings.shortcuts.search.placeholder": "ސަރޗް ޝޯޓްކަޓްސް",
@@ -1086,7 +1098,7 @@ export const dict = {
"settings.permissions.tool.doom_loop.description": "އެއްގޮތް އިންޕުޓެއް އެކުލެވޭ ތަކުރާރުކޮށް ޓޫލް ކޯލްތައް ހޯދުން",
"session.delete.failed.title": "ސެޝަން ޑިލީޓް ކުރަން ނާކާމިޔާބުވިއެވެ",
"session.delete.title": "ސެޝަން ފުހެލުން",
"session.delete.confirm": 'ސެޝަން "{{name}}" ފުހެލަންތަ؟',
"session.delete.confirm": 'ސެޝަން "\u2068{{name}}\u2069" ފުހެލަންތަ؟',
"session.delete.button": "ސެޝަން ފުހެލުން",
"workspace.new": "އާ ވޯކްސްޕޭސް",
"workspace.type.local": "ލޯކަލް",
@@ -1104,14 +1116,126 @@ export const dict = {
"workspace.status.clean": "އެއްނުވާ ބަދަލެއް ފާހަގަކުރެވިފައެއް ނުވެއެވެ.",
"workspace.status.dirty": "މި ވޯކްސްޕޭސްގައި ފާހަގަ ނުކުރެވި ހުރި ބަދަލުތައް.",
"workspace.delete.title": "ވޯކްސްޕޭސް ފުހެލުން",
"workspace.delete.confirm": 'ވޯކްސްޕޭސް "{{name}}" ފުހެލަންތަ؟',
"workspace.delete.confirm": 'ވޯކްސްޕޭސް "\u2068{{name}}\u2069" ފުހެލަންތަ؟',
"workspace.delete.button": "ވޯކްސްޕޭސް ފުހެލުން",
"workspace.reset.title": "ވޯކްސްޕޭސް ރީސެޓްކުރުން",
"workspace.reset.confirm": 'ވޯކްސްޕޭސް "{{name}}" ރީސެޓްކުރުން؟',
"workspace.reset.confirm": 'ވޯކްސްޕޭސް "\u2068{{name}}\u2069" ރީސެޓްކުރުން؟',
"workspace.reset.button": "ވޯކްސްޕޭސް ރީސެޓްކުރުން",
"workspace.reset.archived.none": "އެއްވެސް އެކްޓިވް ސެޝަނެއް އަރުޝީފް ނުކުރެވޭނެއެވެ.",
"workspace.reset.archived.one": "1 ސެޝަން އަރުޝީފް ކުރެވޭނެއެވެ.",
"workspace.reset.archived.many": "{{count}} ސެޝަންތައް އަރުޝީފް ކުރެވޭނެއެވެ.",
"workspace.reset.archived.many": "\u2068{{count}}\u2069 ސެޝަންތައް އަރުޝީފް ކުރެވޭނެއެވެ.",
"workspace.reset.note": "މިއީ ޑިފޯލްޓް ބްރާންޗާ އެއްގޮތްވާ ގޮތަށް ވޯކްސްޕޭސް ރީސެޓް ކުރާނެ ކަމެކެވެ.",
"dialog.usageExceeded.dontShowAgain": "އަލުން ނުދައްކާ",
"provider.disconnect.toast.noCredentials.description":
"\u2068{{provider}}\u2069 އަށް ނައްތާލެވޭ އެއްވެސް ކްރެޑެންޝަލެއް ނުފެނުނެވެ.",
"provider.disconnect.toast.failed.description": "\u2068{{provider}}\u2069 ޑިސްކަނެކްޓް ނުކުރެވުނެވެ.",
"toast.migration.failed.title": "ޑޭޓާ މައިގްރޭޝަން ފެއިލްވެއްޖެއެވެ",
"command.session.background": "ބެކްގްރައުންޑަށް ބަދަލުކުރުން",
"session.timeline.notice.finished": "\u2068{{actor}}\u2069 ނިންމައިފިއެވެ",
"session.timeline.notice.failed": "\u2068{{actor}}\u2069 ފެއިލްވެއްޖެއެވެ",
"session.timeline.notice.cancelled": "\u2068{{actor}}\u2069 ކެންސަލްވެއްޖެއެވެ",
"settings.tab.preferences": "ޕްރެފަރެންސްތައް",
"settings.tab.notifications": "ނޮޓިފިކޭޝަންތައް",
"settings.tab.projects": "ޕްރޮޖެކްޓްތައް",
"settings.tab.extensions": "އެކްސްޓެންޝަންތައް",
"settings.preferences.description": "ޕްރެފަރެންސް، ތީމް އަދި ޑިފޯލްޓް ސުލޫކު ކަސްޓަމައިޒް ކުރާށެވެ",
"settings.appearance.description": "ތީމް އަދި ފޮންޓްތައް ކަސްޓަމައިޒް ކުރާށެވެ",
"settings.notifications.description": "ނޮޓިފިކޭޝަން ލިބޭ އަދި އަޑު އިވޭ ވަގުތު ހޮވާށެވެ",
"settings.shortcuts.description": "އާންމު އެކްޝަންތަކުގެ ޝޯޓްކަޓްތައް ކަސްޓަމައިޒް ކުރާށެވެ",
"settings.servers.description": "ސަރވަރ ކަނެކްޝަންތައް މެނޭޖްކުރާށެވެ",
"settings.projects.title": "ޕްރޮޖެކްޓްތައް",
"settings.projects.description": "މި ސަރވަރުގައި ޕްރޮޖެކްޓް ސެޓިންގްސް މެނޭޖްކުރާށެވެ",
"settings.projects.empty": "އެއްވެސް ޕްރޮޖެކްޓެއް ނުފެނުނެވެ",
"settings.projects.server.all": "ހުރިހާ ސަރވަރުތައް",
"settings.mcps.description": "Model Context Protocol (MCP) ސަރވަރތަކާއި ޓޫލްތައް މެނޭޖްކުރާށެވެ",
"settings.extensions.description": "މި ސަރވަރުގައި ލިބެންހުރި އެކްސްޓެންޝަންތައް މެނޭޖްކުރާށެވެ",
"settings.extensions.tab.mcps": "MCPs",
"settings.extensions.tab.skills": "ހުނަރުތައް",
"settings.extensions.availableAll": "ހުރިހާ ޕްރޮޖެކްޓަކަށް ލިބެންހުރި",
"settings.extensions.manageConfig": "opencode.json ގައި މެނޭޖްކުރާށެވެ",
"settings.extensions.addSkills": "ހުނަރުތައް އިތުރުކުރާނެ ގޮތް",
"dialog.server.authenticate.title": "އޮތެންޓިކޭޓް ކުރާށެވެ",
"project.settings.general.description": "ޕްރޮޖެކްޓްގެ ނަމާއި މަލަމަތި މެނޭޖްކުރާށެވެ",
"project.settings.scripts": "ސްކްރިޕްޓްތައް",
"project.settings.scripts.description": "މި ޕްރޮޖެކްޓަށް ސްކްރިޕްޓްތައް ކޮންފިގަރކުރާށެވެ",
"project.settings.extensions.description": "މި ޕްރޮޖެކްޓަށް ލިބެންހުރި އެކްސްޓެންޝަންތައް ބަލާށެވެ",
"project.settings.extensions.tab.lsps": "LSPs",
"project.settings.extensions.added": "މި ޕްރޮޖެކްޓަށް އިތުރުކޮށްފައި",
"project.settings.extensions.shared": "ހުރިހާ ޕްރޮޖެކްޓާއެކު ހިއްސާކޮށްފައި",
"project.settings.extensions.lsp.detected": "ފާހަގަކުރެވުނު ލޭންގުއޭޖް ސަރވަރތައް",
"project.settings.extensions.lsp.description": "ފައިލް ބާވަތްތަކުން އޮޓޮމެޓިކުން ފާހަގަކޮށްފައި",
"project.settings.extensions.setupRequired": "ސެޓަޕް ބޭނުންވެއެވެ",
"settings.general.section.general": "އާންމު",
"common.viewAll": "ހުރިހާ އެއްޗެއް ބަލާށެވެ",
"session.new.workspace.local.tooltip": "މިހާރުގެ ޗެކްއައުޓް ބޭނުންކުރާށެވެ",
"session.new.workspace.new.tooltip": "ވަކި ޗެކްއައުޓެއް އުފައްދާށެވެ",
"session.new.workspace.fromBranch": "\u2068{{branch}}\u2069 އިން",
"session.new.workspace.trigger.tooltip": "ސެޝަން ހިންގާނެ ތަން ހޮވާށެވެ",
"session.new.workspace.search.placeholder": "ވޯކްސްޕޭސްތައް ހޯދާށެވެ",
"settings.tab.workspaces": "ވޯކްސްޕޭސްތައް",
"settings.workspaces.filter.all": "ހުރިހާ ޕްރޮޖެކްޓްތައް",
"settings.workspaces.empty": "އެއްވެސް ވޯކްސްޕޭސެއް ނެތެވެ",
"settings.workspaces.count.one": "\u2068{{count}}\u2069 ވޯކްސްޕޭސް",
"settings.workspaces.count.other": "\u2068{{count}}\u2069 ވޯކްސްޕޭސްތައް",
"settings.workspaces.sessions.one": "\u2068{{project}}\u2069 ގައި \u2068{{count}}\u2069 ސެޝަން",
"settings.workspaces.sessions.other": "\u2068{{project}}\u2069 ގައި \u2068{{count}}\u2069 ސެޝަންތައް",
"settings.workspaces.lastActiveSession": "ފަހުގެ އެކްޓިވް ސެޝަން",
"settings.workspaces.deleteAll": "ހުރިހާ ވޯކްސްޕޭސްތައް ފުހެލާށެވެ",
"settings.workspaces.deleteAll.confirm": "ހުރިހާ \u2068{{count}}\u2069 ވޯކްސްޕޭސް ފުހެލަންތަ؟",
"settings.workspaces.delete.warning":
"ތިރީގައި ދައްކާ މާޖްނުކުރެވި ހުރި ބަދަލުތަކާއެކު ވޯކްސްޕޭސް ޑައިރެކްޓަރީ އަދި ބްރާންޗް ދާއިމީކޮށް ނައްތާލެވޭނެއެވެ.",
"settings.workspaces.deleteAll.warning":
"\u2068{{project}}\u2069 ގައި ހޮވާފައިވާ \u2068{{count}}\u2069 ވޯކްސްޕޭސް، ކޮންމެ ވޯކްސްޕޭސެއް ސާފު، އިނެކްޓިވް، އަދި ލިންކްވެފައިވާ ސެޝަނެއް ނެތްނަމަ އެކަނި ދާއިމީކޮށް ނައްތާލެވޭނެއެވެ.",
"settings.workspaces.delete.blocked.active": "އެކްޓިވް ވޯކްސްޕޭސް ފުހެލެވޭނެ ގޮތެއް ނެތެވެ.",
"settings.workspaces.delete.blocked.linked":
"ލިންކްވެފައިވާ ސެޝަންތައް ހުންނާނެ، ނަމަވެސް އޭގެ ވޯކިންގ ޑައިރެކްޓަރީ ދާއިމީކޮށް ނައްތާލެވޭނެއެވެ.",
"settings.workspaces.default.title": "ޑިފޯލްޓް މާޙައުލު",
"settings.workspaces.default.description": "އާ ސެޝަންތައް ފަށާނެ ތަން ހޮވާށެވެ",
"settings.workspaces.default.lastUsed": "ކޮންމެ ޕްރޮޖެކްޓަކަށް ވަކިން ފަހުން ބޭނުންކުރި",
"settings.workspaces.default.local": "ލޯކަލް ޑައިރެކްޓަރީ",
"settings.workspaces.default.new": "އާ ވޯކްސްޕޭސް",
"workspace.move.title": "ވޯކްސްޕޭސަށް ބަދަލުކުރާށެވެ",
"workspace.move.menu.title": "ސެޝަން ބަދަލުކުރާ ތަން",
"workspace.move.failed": "ސެޝަން ބަދަލު ނުކުރެވުނެވެ",
"workspace.lifecycle.creating": "ވޯކްސްޕޭސް އުފައްދަމުން",
"workspace.lifecycle.created": "ވޯކްސްޕޭސް އުފެދިއްޖެއެވެ",
"workspace.lifecycle.starting": "ސެޝަން ފަށަމުން",
"workspace.onboarding.title": "ވޯކްސްޕޭސްތަކުން ސެޝަންތައް ވަކިކޮށް ބަހައްޓާށެވެ",
"workspace.onboarding.description":
"ކޮންމެ ސެޝަނަކަށް ވަކި ޗެކްއައުޓެއް ލިބޭތީ ތިޔަ ލޯކަލް ރިޕޮޒިޓަރީއަށް އެއްވެސް ހުރަހެއް ނާޅާނެއެވެ",
"workspace.lifecycle.moving": "ވޯކްސްޕޭސަށް ބަދަލުކުރަމުން",
"workspace.lifecycle.set": "ވޯކްސްޕޭސް ސެޓްވެއްޖެއެވެ",
"session.summary.title": "ސެޝަންގެ ތަފްޞީލު",
"session.summary.noBranch": "އެއްވެސް ބްރާންޗެއް ނެތެވެ",
"session.summary.basedOn": "\u2068{{branch}}\u2069 އަށް ބިނާކޮށް",
"desktop.menu.installCli": "CLI އިންސްޓޯލް ކުރާށެވެ...",
"desktop.cli.installed.title": "CLI އިންސްޓޯލް ކުރެވިއްޖެ",
"desktop.cli.installed.message":
"CLI އިންސްޓޯލް ކުރެވިފައިވަނީ \u2068{{path}}\u2069 އަށެވެ\n\n'opencode2' ކޮމާންޑް ބޭނުންކުރުމަށް ޓާމިނަލް އަލުން ފަށާށެވެ.",
"desktop.cli.failed.title": "އިންސްޓޯލް ކުރުން ނާކާމިޔާބުވެއްޖެ",
"desktop.cli.failed.message": "CLI އިންސްޓޯލް ނުކުރެވުނެވެ: \u2068{{error}}\u2069",
"command.project.copyID": "ޕްރޮޖެކްޓް ID ކޮޕީކުރުން",
"command.session.copyID": "ސެޝަން ID ކޮޕީކުރުން",
"server.row.incompatible":
"މި ސަރވަރގައި ހިނގަނީ OpenCode \u2068{{version}}\u2069 އެވެ، އެއީ މި އެޕް އާއި އެއްގޮތް ނޫނެވެ. ކުރިއަށް ގެންދިއުމަށް OpenCode V2 އަށް އަޕްގްރޭޑް ކުރާށެވެ.",
"toast.session.copyID.failed.title": "ސެޝަން ID ކޮޕީ ނުކުރެވުނެވެ",
"toast.session.copyID.failed.description": "ސެޝަން ID ކޮޕީކުރަމުން ދިޔައިރު ގޯހެއް ދިމާވިއެވެ",
"toast.project.copyID.failed.title": "ޕްރޮޖެކްޓް ID ކޮޕީ ނުކުރެވުނެވެ",
"toast.project.copyID.failed.description": "ޕްރޮޖެކްޓް ID ކޮޕީކުރަމުން ދިޔައިރު ގޯހެއް ދިމާވިއެވެ",
"error.page.title.status": "ސަރވަރގެ ރިކުއެސްޓް ނާކާމިޔާބުވިއެވެ",
"error.page.description.status": "ސަރވަރުން HTTP \u2068{{status}}\u2069 ރެސްޕޮންސެއް ދީފިއެވެ.",
"error.page.action.reload": "އަލުން ލޯޑްކުރުން",
"session.error.incompatible": "މި ސަރވަރ ވަރޝަން ސަޕޯޓް ނުކުރެއެވެ",
"session.error.incompatible.description":
"\u2068{{server}}\u2069 ގައި ހިނގަނީ OpenCode \u2068{{version}}\u2069 އެވެ، އެއީ މި އެޕް އާއި އެއްގޮތް ނޫނެވެ. ކުރިއަށް ގެންދިއުމަށް ސަރވަރ OpenCode V2 އަށް އަޕްގްރޭޑް ކުރާށެވެ.",
"session.background.moveInline": "ހިނގަމުންދާ މަސައްކަތް ބެކްގްރައުންޑަށް ލުމަށް \u2068{{keybind}}\u2069 ފިއްތާށެވެ",
"session.background.running": "މަސައްކަތް ބެކްގްރައުންޑްގައި ހިނގަމުންދެއެވެ",
"session.background.runningCount.one": "\u2068{{count}}\u2069 އައިޓަމެއް ބެކްގްރައުންޑްގައި ހިނގަމުންދެއެވެ",
"session.background.runningCount.other": "\u2068{{count}}\u2069 އައިޓަމް ބެކްގްރައުންޑްގައި ހިނގަމުންދެއެވެ",
"settings.general.row.terminalPlacement.title": "ޓާމިނަލް ބަހައްޓާ ތަން",
"settings.general.row.terminalPlacement.description": "ސެޝަންތަކުގައި ޓާމިނަލް ހުޅުވާނެ ތަން ހޮވާށެވެ",
"settings.general.row.terminalPlacement.side": "އަރިމަތީގައި",
"settings.general.row.terminalPlacement.bottom": "ތިރީގައި",
"settings.general.row.showProjectIcon.title": "ޕްރޮޖެކްޓް އައިކޮން",
"settings.general.row.showProjectIcon.description": "ސެޝަން ހެޑަރގައި ޕްރޮޖެކްޓް އައިކޮން ދައްކާށެވެ",
}
+111
View File
@@ -7,6 +7,7 @@ export const dict: Record<string, string> = {
"desktop.menu.window": "སྒོང༌སྒྲིག",
"desktop.menu.help": "ཆ་རོགས་འབད་ནི",
"desktop.menu.checkForUpdates": "དུས་མཐུན་ཚུ་གི་དོན་ལུ་ཞིབ་དཔྱད་འབད།",
"desktop.menu.installCli": "CLI གཞི་བཙུགས་འབད།...",
"desktop.menu.settings": "སྒྲིག་སྟངས་ཚུ།",
"desktop.menu.reloadWebview": "ཡང་བསྐྱར་མངོན་གསལ་ Webview།",
"desktop.menu.restart": "ལོག་འགོ་བཙུགས།",
@@ -52,6 +53,11 @@ export const dict: Record<string, string> = {
"desktop.updater.dialog.ready.title": "དུས་མཐུན་བཟོ་ནི་གྲ་སྒྲིག།",
"desktop.updater.dialog.restart": "ལོག་འགོ་བཙུགས།",
"desktop.updater.dialog.later": "ཤུལ་ལས",
"desktop.cli.installed.title": "CLI གཞི་བཙུགས་འབད་ཡོདཔ།",
"desktop.cli.installed.message":
"CLI འདི་ {{path}} ལུ་གཞི་བཙུགས་འབད་ཡི།\n\n'opencode2' བརྡ་བཀོད་ལག་ལེན་འཐབ་ནིའི་དོན་ལུ་ ཊར་མི་ནཱལ་ལོག་འགོ་བཙུགས།",
"desktop.cli.failed.title": "གཞི་བཙུགས་འཐུས་ཤོར་བྱུང་ཡོདཔ།",
"desktop.cli.failed.message": "CLI གཞི་བཙུགས་འབད་མ་ཚུགས།: {{error}}",
"desktop.recovery.action.relaunch": "བསྐྱར་འགོ་བཙུགས།",
"desktop.recovery.action.exportLogs": "ཕྱིར་འདྲེན་དྲན་དེབ་ཚུ།",
"desktop.recovery.action.keepWaiting": "བསྒུག་སྡོད།",
@@ -1116,5 +1122,110 @@ export const dict: Record<string, string> = {
"workspace.reset.archived.one": "ལཱ་ཡུན་ ༡ གཏན་མཛོད་ནང་བཞག་འོང་།",
"workspace.reset.archived.many": "{{count}} ལཱ་ཡུན་ཚུ་ཡིག་མཛོད་ནང་བཞག་འོང་།",
"workspace.reset.note": "འདི་གིས་ སྔོན་སྒྲིག་ཡན་ལག་མཐུན་སྒྲིག་འབད་ནི་ལུ་ ལཱ་གི་ས་སྒོ་འདི་སླར་སྒྲིག་འབད་འོང་།",
"toast.migration.failed.title": "གནས་སྡུད་གནས་སྤོ་འཐུས་ཤོར་བྱུང་ཡོདཔ།",
"command.project.copyID": "ལས་གཞིའི་ཨའི་ཌི་འདྲ་བཤུས་འབད།",
"command.session.copyID": "ལཱ་ཡུན་གྱི་ཨའི་ཌི་འདྲ་བཤུས་འབད།",
"server.row.incompatible":
"སར་བར་འདི་གིས་ གློག་རིམ་འདི་དང་མཐུན་འགྱུར་མེད་པའི་ OpenCode {{version}} གཡོག་བཀོལ་དོ། འཕྲོ་མཐུད་ཀྱི་དོན་ལུ་ OpenCode V2 ལུ་ཡར་སེང་འབད།",
"toast.session.copyID.failed.title": "ལཱ་ཡུན་གྱི་ཨའི་ཌི་འདྲ་བཤུས་འབད་མ་ཚུགས།",
"toast.session.copyID.failed.description": "ལཱ་ཡུན་གྱི་ཨའི་ཌི་འདྲ་བཤུས་འབདཝ་ད་ འཛོལ་བ་ཅིག་བྱུང་ནུག།",
"toast.project.copyID.failed.title": "ལས་གཞིའི་ཨའི་ཌི་འདྲ་བཤུས་འབད་མ་ཚུགས།",
"toast.project.copyID.failed.description": "ལས་གཞིའི་ཨའི་ཌི་འདྲ་བཤུས་འབདཝ་ད་ འཛོལ་བ་ཅིག་བྱུང་ནུག།",
"error.page.title.status": "སར་བར་ཞུ་བ་འཐུས་ཤོར་བྱུང་ཡོདཔ།",
"error.page.description.status": "སར་བར་གྱིས་ HTTP {{status}} ལན་ཅིག་སྤྲོད་ནུག།",
"error.page.action.reload": "ཡང་བསྐྱར་མངོན་གསལ།",
"session.error.incompatible": "སར་བར་ཐོན་རིམ་འདི་ལུ་རྒྱབ་སྐྱོར་མེད།",
"session.error.incompatible.description":
"{{server}} གིས་ གློག་རིམ་འདི་དང་མཐུན་འགྱུར་མེད་པའི་ OpenCode {{version}} གཡོག་བཀོལ་དོ། འཕྲོ་མཐུད་ཀྱི་དོན་ལུ་ སར་བར་འདི་ OpenCode V2 ལུ་ཡར་སེང་འབད།",
"session.background.moveInline": "གཡོག་བཀོལ་དོ་ཡོད་པའི་ལཱ་འདི་རྒྱབ་ཁར་སྤོ་ནིའི་དོན་ལུ་ {{keybind}} ཨེབ།",
"session.background.running": "ལཱ་འདི་རྒྱབ་ཁར་གཡོག་བཀོལ་དོ།",
"session.background.runningCount.one": "རྒྱབ་ཁར་རྣམ་གྲངས་ {{count}} གཡོག་བཀོལ་དོ།",
"session.background.runningCount.other": "རྒྱབ་ཁར་རྣམ་གྲངས་ {{count}} གཡོག་བཀོལ་དོ།",
"settings.general.row.terminalPlacement.title": "ཊར་མི་ནཱལ་བཞག་ས།",
"settings.general.row.terminalPlacement.description": "ལཱ་ཡུན་ཚུ་ནང་ ཊར་མི་ནཱལ་ག་ཏེ་ཁ་ཕྱེ་ནི་ཨིན་ན་གདམ་ཁ་རྐྱབས།",
"settings.general.row.terminalPlacement.side": "ཟུར་ཁ།",
"settings.general.row.terminalPlacement.bottom": "མཇུག་ལུ།",
"settings.general.row.showProjectIcon.title": "ལས་གཞིའི་ངོས་དཔར།",
"settings.general.row.showProjectIcon.description": "ལཱ་ཡུན་གྱི་མགོ་ཡིག་ནང་ ལས་གཞིའི་ངོས་དཔར་སྟོན།",
"command.session.background": "རྒྱབ་ཁར་སྤོ།",
"session.timeline.notice.finished": "{{actor}} མཇུག་བསྡུ་ཡོདཔ།",
"session.timeline.notice.failed": "{{actor}} འཐུས་ཤོར་བྱུང་ཡོདཔ།",
"session.timeline.notice.cancelled": "{{actor}} ཆ་མེད་བཏང་ཡོདཔ།",
"settings.tab.preferences": "དགའ་གདམ་ཚུ།",
"settings.tab.notifications": "བརྡ་བསྐུལ་ཚུ།",
"settings.tab.projects": "ལས་གཞི་ཚུ།",
"settings.tab.extensions": "རྒྱ་བསྐྱེད་ཚུ།",
"settings.preferences.description": "དགའ་གདམ་དང་ བརྗོད་དོན་ དེ་ལས་ སྔོན་སྒྲིག་སྤྱོད་ལམ་སྲོལ་སྒྲིག་འབད།",
"settings.appearance.description": "བརྗོད་དོན་དང་ཡིག་གཟུགས་ཚུ་སྲོལ་སྒྲིག་འབད།",
"settings.notifications.description": "བརྡ་བསྐུལ་ནམ་ཐོབ་ནི་དང་ སྒྲ་ནམ་གོ་ནི་ཨིན་ན་གདམ་ཁ་རྐྱབས།",
"settings.shortcuts.description": "སྤྱིར་བཏང་བྱ་བ་ཚུ་གི་མགྱོགས་ཐབས་སྲོལ་སྒྲིག་འབད།",
"settings.servers.description": "སར་བར་མཐུད་ལམ་ཚུ་འཛིན་སྐྱོང་འཐབ།",
"settings.projects.title": "ལས་གཞི་ཚུ།",
"settings.projects.description": "སར་བར་འདི་གུ་ལས་གཞིའི་སྒྲིག་སྟངས་ཚུ་འཛིན་སྐྱོང་འཐབ།",
"settings.projects.empty": "ལས་གཞི་འཚོལ་མ་ཐོབ།",
"settings.projects.server.all": "སར་བར་ཆ་མཉམ།",
"settings.mcps.description": "Model Context Protocol (MCP) སར་བར་དང་ལག་ཆས་ཚུ་འཛིན་སྐྱོང་འཐབ།",
"settings.extensions.description": "སར་བར་འདི་གུ་འཐོབ་ཚུགས་པའི་རྒྱ་བསྐྱེད་ཚུ་འཛིན་སྐྱོང་འཐབ།",
"settings.extensions.tab.mcps": "MCPs",
"settings.extensions.tab.skills": "རིག་རྩལ་ཚུ།",
"settings.extensions.availableAll": "ལས་གཞི་ཆ་མཉམ་ལུ་འཐོབ་ཚུགས།",
"settings.extensions.manageConfig": "opencode.json ནང་འཛིན་སྐྱོང་འཐབ།",
"settings.extensions.addSkills": "རིག་རྩལ་ཚུ་ག་དེ་སྦེ་ཁ་སྐོང་རྐྱབ་ནི་ཨིན་ན།",
"dialog.server.authenticate.title": "བདེན་བཤད་འབད།",
"project.settings.general.description": "ལས་གཞིའི་མིང་དང་བཟོ་དབྱིབས་འཛིན་སྐྱོང་འཐབ།",
"project.settings.scripts": "ཡིག་ཚུགས་ཚུ།",
"project.settings.scripts.description": "ལས་གཞི་འདི་གི་ཡིག་ཚུགས་ཚུ་རིམ་སྒྲིག་འབད།",
"project.settings.extensions.description": "ལས་གཞི་འདི་ལུ་འཐོབ་ཚུགས་པའི་རྒྱ་བསྐྱེད་ཚུ་བལྟ།",
"project.settings.extensions.tab.lsps": "LSPs",
"project.settings.extensions.added": "ལས་གཞི་འདི་ལུ་ཁ་སྐོང་བརྐྱབ་ཡོདཔ།",
"project.settings.extensions.shared": "ལས་གཞི་ཆ་མཉམ་དང་གཅིག་ཁར་རུབ་སྤྱོད་འབད་ཡོདཔ།",
"project.settings.extensions.lsp.detected": "སྐྱོན་འཛིན་འབད་ཡོད་པའི་སྐད་ཡིག་སར་བར་ཚུ།",
"project.settings.extensions.lsp.description": "ཡིག་སྣོད་དབྱེ་བ་ལས་རང་བཞིན་གྱིས་སྐྱོན་འཛིན་འབད་ཡོདཔ།",
"project.settings.extensions.setupRequired": "གཞི་སྒྲིག་དགོཔ་ཨིན།",
"settings.general.section.general": "སྤྱིར་བཏང་།",
"common.viewAll": "ཆ་མཉམ་བལྟ།",
"session.new.workspace.local.tooltip": "ད་ལྟོའི་ checkout ལག་ལེན་འཐབ།",
"session.new.workspace.new.tooltip": "checkout སོ་སོ་ཅིག་གསར་བསྐྲུན་འབད།",
"session.new.workspace.fromBranch": "{{branch}} ལས།",
"session.new.workspace.trigger.tooltip": "ལཱ་ཡུན་ག་ཏེ་གཡོག་བཀོལ་ནི་ཨིན་ན་སེལ་འཐུ་འབད།",
"session.new.workspace.search.placeholder": "ལཱ་གི་ས་སྒོ་ཚུ་འཚོལ་ཞིབ་འབད།",
"settings.tab.workspaces": "ལཱ་གི་ས་སྒོ་ཚུ།",
"settings.workspaces.filter.all": "ལས་གཞི་ཆ་མཉམ།",
"settings.workspaces.empty": "ལཱ་གི་ས་སྒོ་མེད།",
"settings.workspaces.count.one": "{{count}} ལཱ་གི་ས་སྒོ།",
"settings.workspaces.count.other": "{{count}} ལཱ་གི་ས་སྒོ།",
"settings.workspaces.sessions.one": "{{project}} ནང་ {{count}} ལཱ་ཡུན།",
"settings.workspaces.sessions.other": "{{project}} ནང་ {{count}} ལཱ་ཡུན།",
"settings.workspaces.lastActiveSession": "མཇུག་གི་ཤུགས་ལྡན་ལཱ་ཡུན།",
"settings.workspaces.deleteAll": "ལཱ་གི་ས་སྒོ་ཆ་མཉམ་བཏོན་གཏང་།",
"settings.workspaces.deleteAll.confirm": "ལཱ་གི་ས་སྒོ་ {{count}} ཆ་མཉམ་བཏོན་གཏང་ནི་ཨིན་ན?",
"settings.workspaces.delete.warning":
"འོག་ལུ་སྟོན་ཡོད་པའི་མཉམ་བསྡོམས་མ་འབད་བའི་བསྒྱུར་བཅོས་ཚུ་རྩིས་ཏེ་ ལཱ་གི་ས་སྒོའི་སྣོད་ཐོ་དང་ཡན་ལག་གཏན་འཇགས་སྦེ་རྩ་བསྐྲད་གཏང་འོང་།",
"settings.workspaces.deleteAll.warning":
"{{project}} ནང་སེལ་འཐུ་འབད་ཡོད་པའི་ལཱ་གི་ས་སྒོ་ {{count}} འདི་ རེ་རེ་བཞིན་གཙང་མ་དང་ ཤུགས་མེད་ དེ་ལས་ འབྲེལ་མཐུད་ལཱ་ཡུན་མེད་པ་ཅིན་རྐྱངམ་ཅིག་ གཏན་འཇགས་སྦེ་རྩ་བསྐྲད་གཏང་འོང་།",
"settings.workspaces.delete.blocked.active": "ཤུགས་ལྡན་ལཱ་གི་ས་སྒོ་འདི་བཏོན་གཏང་མི་ཚུགས།",
"settings.workspaces.delete.blocked.linked":
"འབྲེལ་མཐུད་ལཱ་ཡུན་ཚུ་བཞག་འོང་ དེ་འབདཝ་ད་ དེ་ཚུ་གི་ལཱ་འབད་སའི་སྣོད་ཐོ་གཏན་འཇགས་སྦེ་རྩ་བསྐྲད་གཏང་འོང་།",
"settings.workspaces.default.title": "སྔོན་སྒྲིག་མཐའ་འཁོར།",
"settings.workspaces.default.description": "ལཱ་ཡུན་གསརཔ་ཚུ་ག་ཏེ་འགོ་བཙུགས་ནི་ཨིན་ན་གདམ་ཁ་རྐྱབས།",
"settings.workspaces.default.lastUsed": "ལས་གཞི་རེ་ལུ་མཇུག་ལག་ལེན་འཐབ་མི།",
"settings.workspaces.default.local": "ས་གནས་ཀྱི་སྣོད་ཐོ།",
"settings.workspaces.default.new": "ལཱ་གི་ས་སྒོ་གསརཔ།",
"workspace.move.title": "ལཱ་གི་ས་སྒོ་ལུ་སྤོ།",
"workspace.move.menu.title": "ལཱ་ཡུན་སྤོ་ས།",
"workspace.move.failed": "ལཱ་ཡུན་སྤོ་མ་ཚུགས།",
"workspace.lifecycle.creating": "ལཱ་གི་ས་སྒོ་གསར་བསྐྲུན་འབད་དོ།",
"workspace.lifecycle.created": "ལཱ་གི་ས་སྒོ་གསར་བསྐྲུན་འབད་ཡོདཔ།",
"workspace.lifecycle.starting": "ལཱ་ཡུན་འགོ་བཙུགས་དོ།",
"workspace.onboarding.title": "ལཱ་ཡུན་ཚུ་ལཱ་གི་ས་སྒོ་དང་གཅིག་ཁར་སོ་སོར་བཞག།",
"workspace.onboarding.description":
"རེ་རེ་ལུ་ checkout སོ་སོ་ཡོདཔ་ལས་ ཁྱོད་ཀྱི་ས་གནས་ཀྱི་མཛོད་ཁང་ལུ་ག་ནི་ཡང་བར་ཆད་མི་འབྱུང་།",
"workspace.lifecycle.moving": "ལཱ་གི་ས་སྒོ་ལུ་སྤོ་དོ།",
"workspace.lifecycle.set": "ལཱ་གི་ས་སྒོ་གཞི་སྒྲིག་འབད་ཡོདཔ།",
"session.summary.title": "ལཱ་ཡུན་ཁ་གསལ།",
"session.summary.noBranch": "ཡན་ལག་མེད།",
"session.summary.basedOn": "{{branch}} གུ་གཞི་བཞག་ཡོདཔ།",
"provider.disconnect.toast.noCredentials.description": "{{provider}} གི་བཏོན་གཏང་བཏུབ་པའི་ངོ་རྟགས་འཚོལ་མ་ཐོབ།",
"provider.disconnect.toast.failed.description": "{{provider}} མཐུད་ལམ་བཏོག་མ་ཚུགས།",
"dialog.usageExceeded.dontShowAgain": "ལོག་སྟེ་མ་སྟོན།",
}
+112
View File
@@ -1086,6 +1086,48 @@ export const dict = {
"session.delete.confirm": 'Διαγραφή συνεδρίας "{{name}}";',
"session.delete.button": "Διαγραφή συνεδρίας",
"workspace.new": "Νέος χώρος εργασίας",
"common.viewAll": "Προβολή όλων",
"session.new.workspace.local.tooltip": "Χρήση του τρέχοντος checkout",
"session.new.workspace.new.tooltip": "Δημιουργία απομονωμένου checkout",
"session.new.workspace.fromBranch": "από {{branch}}",
"session.new.workspace.trigger.tooltip": "Επιλέξτε πού θα εκτελεστεί η συνεδρία",
"session.new.workspace.search.placeholder": "Αναζήτηση χώρων εργασίας",
"settings.tab.workspaces": "Χώροι εργασίας",
"settings.workspaces.filter.all": "Όλα τα έργα",
"settings.workspaces.empty": "Δεν υπάρχουν χώροι εργασίας",
"settings.workspaces.count.one": "{{count}} χώρος εργασίας",
"settings.workspaces.count.other": "{{count}} χώροι εργασίας",
"settings.workspaces.sessions.one": "{{count}} συνεδρία στο {{project}}",
"settings.workspaces.sessions.other": "{{count}} συνεδρίες στο {{project}}",
"settings.workspaces.lastActiveSession": "Τελευταία ενεργή συνεδρία",
"settings.workspaces.deleteAll": "Διαγραφή όλων των χώρων εργασίας",
"settings.workspaces.deleteAll.confirm": "Διαγραφή και των {{count}} χώρων εργασίας;",
"settings.workspaces.delete.warning":
"Ο κατάλογος και ο κλάδος του χώρου εργασίας θα διαγραφούν οριστικά, μαζί με τυχόν μη συγχωνευμένες αλλαγές που εμφανίζονται παρακάτω.",
"settings.workspaces.deleteAll.warning":
"Οι {{count}} επιλεγμένοι χώροι εργασίας στο {{project}} θα διαγραφούν οριστικά μόνο αν είναι όλοι καθαροί, ανενεργοί και χωρίς συνδεδεμένες συνεδρίες.",
"settings.workspaces.delete.blocked.active": "Ο ενεργός χώρος εργασίας δεν μπορεί να διαγραφεί.",
"settings.workspaces.delete.blocked.linked":
"Οι συνδεδεμένες συνεδρίες θα διατηρηθούν, αλλά ο κατάλογος εργασίας τους θα διαγραφεί οριστικά.",
"settings.workspaces.default.title": "Προεπιλεγμένο περιβάλλον",
"settings.workspaces.default.description": "Επιλέξτε πού θα ξεκινούν οι νέες συνεδρίες",
"settings.workspaces.default.lastUsed": "Τελευταίο χρησιμοποιημένο ανά έργο",
"settings.workspaces.default.local": "Τοπικός κατάλογος",
"settings.workspaces.default.new": "Νέος χώρος εργασίας",
"workspace.move.title": "Μετακίνηση σε χώρο εργασίας",
"workspace.move.menu.title": "Μετακίνηση συνεδρίας σε",
"workspace.move.failed": "Αποτυχία μετακίνησης συνεδρίας",
"workspace.lifecycle.creating": "Δημιουργία χώρου εργασίας",
"workspace.lifecycle.created": "Ο χώρος εργασίας δημιουργήθηκε",
"workspace.lifecycle.starting": "Έναρξη συνεδρίας",
"workspace.onboarding.title": "Απομονώστε συνεδρίες με χώρους εργασίας",
"workspace.onboarding.description":
"Κάθε συνεδρία έχει το δικό της checkout, ώστε να μην επηρεάζεται το τοπικό αποθετήριο",
"workspace.lifecycle.moving": "Μετακίνηση σε χώρο εργασίας",
"workspace.lifecycle.set": "Ο χώρος εργασίας ορίστηκε",
"session.summary.title": "Λεπτομέρειες συνεδρίας",
"session.summary.noBranch": "Χωρίς κλάδο",
"session.summary.basedOn": "Βασίζεται στον κλάδο {{branch}}",
"workspace.type.local": "τοπικό",
"workspace.type.sandbox": "απομονωμένο περιβάλλον",
"workspace.create.failed.title": "Αποτυχία δημιουργίας χώρου εργασίας",
@@ -1111,4 +1153,74 @@ export const dict = {
"workspace.reset.archived.many": "{{count}} θα αρχειοθετηθούν οι περίοδοι σύνδεσης.",
"workspace.reset.note": "Αυτό θα επαναφέρει τον χώρο εργασίας ώστε να ταιριάζει με τον προεπιλεγμένο κλάδο.",
"dialog.usageExceeded.dontShowAgain": "Να μην εμφανιστεί ξανά",
"provider.disconnect.toast.noCredentials.description":
"Δεν βρέθηκαν διαπιστευτήρια που μπορούν να αφαιρεθούν για τον πάροχο {{provider}}.",
"provider.disconnect.toast.failed.description": "Αποτυχία αποσύνδεσης του παρόχου {{provider}}.",
"toast.migration.failed.title": "Η μετεγκατάσταση δεδομένων απέτυχε",
"command.session.background": "Μετακίνηση στο παρασκήνιο",
"session.timeline.notice.finished": "Ο χρήστης {{actor}} ολοκλήρωσε",
"session.timeline.notice.failed": "Ο χρήστης {{actor}} απέτυχε",
"session.timeline.notice.cancelled": "Ο χρήστης {{actor}} ακυρώθηκε",
"settings.tab.preferences": "Προτιμήσεις",
"settings.tab.notifications": "Ειδοποιήσεις",
"settings.tab.projects": "Έργα",
"settings.tab.extensions": "Επεκτάσεις",
"settings.preferences.description": "Προσαρμογή προτιμήσεων, θέματος και προεπιλεγμένης συμπεριφοράς",
"settings.appearance.description": "Προσαρμογή θέματος και γραμματοσειρών",
"settings.notifications.description": "Επιλέξτε πότε θα λαμβάνετε ειδοποιήσεις και θα ακούτε ήχους",
"settings.shortcuts.description": "Προσαρμογή συντομεύσεων για συνηθισμένες ενέργειες",
"settings.servers.description": "Διαχείριση συνδέσεων διακομιστή",
"settings.projects.title": "Έργα",
"settings.projects.description": "Διαχείριση ρυθμίσεων έργων σε αυτόν τον διακομιστή",
"settings.projects.empty": "Δεν βρέθηκαν έργα",
"settings.projects.server.all": "Όλοι οι διακομιστές",
"settings.mcps.description": "Διαχείριση διακομιστών και εργαλείων Model Context Protocol (MCP)",
"settings.extensions.description": "Διαχείριση επεκτάσεων που είναι διαθέσιμες σε αυτόν τον διακομιστή",
"settings.extensions.tab.mcps": "MCP",
"settings.extensions.tab.skills": "Δεξιότητες",
"settings.extensions.availableAll": "Διαθέσιμο σε όλα τα έργα",
"settings.extensions.manageConfig": "Διαχείριση στο opencode.json",
"settings.extensions.addSkills": "Πώς να προσθέσετε δεξιότητες",
"dialog.server.authenticate.title": "Έλεγχος ταυτότητας",
"project.settings.general.description": "Διαχείριση ονόματος και εμφάνισης έργου",
"project.settings.scripts": "Σενάρια",
"project.settings.scripts.description": "Διαμόρφωση σεναρίων για αυτό το έργο",
"project.settings.extensions.description": "Προβολή επεκτάσεων που είναι διαθέσιμες σε αυτό το έργο",
"project.settings.extensions.tab.lsps": "LSP",
"project.settings.extensions.added": "Προστέθηκε σε αυτό το έργο",
"project.settings.extensions.shared": "Κοινόχρηστο με όλα τα έργα",
"project.settings.extensions.lsp.detected": "Εντοπισμένοι διακομιστές γλώσσας",
"project.settings.extensions.lsp.description": "Αυτόματος εντοπισμός από τους τύπους αρχείων",
"project.settings.extensions.setupRequired": "Απαιτείται ρύθμιση",
"settings.general.section.general": "Γενικά",
"desktop.menu.installCli": "Εγκατάσταση CLI...",
"desktop.cli.installed.title": "Το CLI εγκαταστάθηκε",
"desktop.cli.installed.message":
"Το CLI εγκαταστάθηκε στη διαδρομή {{path}}\n\nΕπανεκκινήστε το τερματικό για να χρησιμοποιήσετε την εντολή 'opencode2'.",
"desktop.cli.failed.title": "Η εγκατάσταση απέτυχε",
"desktop.cli.failed.message": "Αποτυχία εγκατάστασης του CLI: {{error}}",
"command.project.copyID": "Αντιγραφή ID έργου",
"command.session.copyID": "Αντιγραφή ID συνεδρίας",
"server.row.incompatible":
"Αυτός ο διακομιστής εκτελεί το OpenCode {{version}}, το οποίο δεν είναι συμβατό με αυτήν την εφαρμογή. Αναβαθμίστε τον σε OpenCode V2 για να συνεχίσετε.",
"toast.session.copyID.failed.title": "Αποτυχία αντιγραφής του ID συνεδρίας",
"toast.session.copyID.failed.description": "Παρουσιάστηκε σφάλμα κατά την αντιγραφή του ID συνεδρίας",
"toast.project.copyID.failed.title": "Αποτυχία αντιγραφής του ID έργου",
"toast.project.copyID.failed.description": "Παρουσιάστηκε σφάλμα κατά την αντιγραφή του ID έργου",
"error.page.title.status": "Το αίτημα προς τον διακομιστή απέτυχε",
"error.page.description.status": "Ο διακομιστής επέστρεψε απόκριση HTTP {{status}}.",
"error.page.action.reload": "Επαναφόρτωση",
"session.error.incompatible": "Αυτή η έκδοση διακομιστή δεν υποστηρίζεται",
"session.error.incompatible.description":
"Ο διακομιστής {{server}} εκτελεί το OpenCode {{version}}, το οποίο δεν είναι συμβατό με αυτήν την εφαρμογή. Αναβαθμίστε τον διακομιστή σε OpenCode V2 για να συνεχίσετε.",
"session.background.moveInline": "Πατήστε {{keybind}} για να μετακινήσετε την εργασία που εκτελείται στο παρασκήνιο",
"session.background.running": "Εκτελείται εργασία στο παρασκήνιο",
"session.background.runningCount.one": "{{count}} στοιχείο εκτελείται στο παρασκήνιο",
"session.background.runningCount.other": "{{count}} στοιχεία εκτελούνται στο παρασκήνιο",
"settings.general.row.terminalPlacement.title": "Τοποθέτηση τερματικού",
"settings.general.row.terminalPlacement.description": "Επιλέξτε πού ανοίγει το τερματικό στις συνεδρίες",
"settings.general.row.terminalPlacement.side": "Στο πλάι",
"settings.general.row.terminalPlacement.bottom": "Κάτω",
"settings.general.row.showProjectIcon.title": "Εικονίδιο έργου",
"settings.general.row.showProjectIcon.description": "Εμφάνιση του εικονιδίου έργου στην κεφαλίδα της συνεδρίας",
}
+21 -8
View File
@@ -30,6 +30,7 @@ export const dict = {
"command.project.previous": "Previous project",
"command.project.next": "Next project",
"command.project.index": "Switch to project {{index}}",
"command.project.copyID": "Copy Project ID",
"command.provider.connect": "Connect provider",
"command.server.switch": "Switch server",
"command.settings.open": "Open settings",
@@ -93,6 +94,7 @@ export const dict = {
"command.session.fork.description": "Create a new session from a previous message",
"command.session.export": "Export session",
"command.session.export.description": "Export the full session transcript as JSON",
"command.session.copyID": "Copy Session ID",
"palette.search.placeholder": "Search files, commands, and sessions",
"palette.search.placeholder.home": "Search commands and sessions",
@@ -206,6 +208,8 @@ export const dict = {
"provider.disconnect.toast.disconnected.title": "{{provider}} disconnected",
"provider.disconnect.toast.disconnected.description": "{{provider}} models are no longer available.",
"provider.disconnect.toast.noCredentials.description": "No removable credentials found for {{provider}}.",
"provider.disconnect.toast.failed.description": "Failed to disconnect {{provider}}.",
"model.tag.free": "Free",
"model.tag.latest": "Latest",
@@ -544,7 +548,6 @@ export const dict = {
"toast.context.noLineSelection.title": "No line selection",
"toast.context.noLineSelection.description": "Select a line range in a file tab first.",
"toast.session.unshare.success.title": "Session unshared",
"toast.session.unshare.success.description": "Session unshared successfully!",
"toast.session.unshare.failed.title": "Failed to unshare session",
@@ -554,6 +557,10 @@ export const dict = {
"toast.session.export.success.description": "Saved session to {{filename}}",
"toast.session.export.failed.title": "Failed to export session",
"toast.session.export.failed.description": "An error occurred while exporting the session",
"toast.session.copyID.failed.title": "Failed to copy session ID",
"toast.session.copyID.failed.description": "An error occurred while copying the session ID",
"toast.project.copyID.failed.title": "Failed to copy project ID",
"toast.project.copyID.failed.description": "An error occurred while copying the project ID",
"toast.session.listFailed.title": "Failed to load sessions for {{project}}",
"toast.project.reloadFailed.title": "Failed to reload {{project}}",
@@ -565,10 +572,13 @@ export const dict = {
"toast.update.action.notYet": "Not yet",
"error.page.title": "Something went wrong",
"error.page.title.status": "Server request failed",
"error.page.description": "An error occurred while loading the application.",
"error.page.description.status": "The server returned an HTTP {{status}} response.",
"error.page.description.localServerStartup": "An error occurred while starting the local server.",
"error.page.details.label": "Error Details",
"error.page.action.restart": "Restart",
"error.page.action.reload": "Reload",
"error.page.action.report": "Report Error",
"error.page.action.reported": "Error Reported",
"error.page.action.exportLogs": "Export Logs",
@@ -655,13 +665,10 @@ export const dict = {
"session.error.incompatible": "This server version isn't supported",
"session.error.incompatible.description":
"{{server}} is running OpenCode {{version}}, which isn't compatible with this app. Upgrade the server to OpenCode V2 to continue.",
"session.background.moveTasks": "Move {{tasks}} to background",
"session.background.inBackground": "Running {{tasks}} in background",
"session.background.combine": "{{first}} and {{second}}",
"session.background.shell.one": "{{count}} shell",
"session.background.shell.other": "{{count}} shells",
"session.background.subagent.one": "{{count}} subagent",
"session.background.subagent.other": "{{count}} subagents",
"session.background.moveInline": "Press {{keybind}} to move running work to the background",
"session.background.running": "Running work in background",
"session.background.runningCount.one": "{{count}} item running in background",
"session.background.runningCount.other": "{{count}} items running in background",
"command.session.background": "Move to background",
"session.timeline.notice.finished": "{{actor}} finished",
"session.timeline.notice.failed": "{{actor}} failed",
@@ -913,6 +920,10 @@ export const dict = {
"settings.general.row.shell.description": "Shell used by the terminal and agent tools",
"settings.general.row.shell.autoDefault": "Auto (Default)",
"settings.general.row.shell.terminalOnly": "terminal only",
"settings.general.row.terminalPlacement.title": "Terminal placement",
"settings.general.row.terminalPlacement.description": "Choose where the terminal opens in sessions",
"settings.general.row.terminalPlacement.side": "Side",
"settings.general.row.terminalPlacement.bottom": "Bottom",
"settings.general.row.appearance.title": "Appearance",
"settings.general.row.appearance.description": "Customise how OpenCode looks on your device",
"settings.general.row.colorScheme.title": "Color scheme",
@@ -935,6 +946,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Show the terminal button in the desktop title bar",
"settings.general.row.showStatus.title": "Server status",
"settings.general.row.showStatus.description": "Show the server status button in the title bar",
"settings.general.row.showProjectIcon.title": "Project icon",
"settings.general.row.showProjectIcon.description": "Show the project icon in the session header",
"settings.general.row.mobileTitlebarBottom.title": "Bottom navigation",
"settings.general.row.mobileTitlebarBottom.description":
"Place the title bar and session tabs at the bottom of the screen on mobile",
+115 -1
View File
@@ -633,7 +633,6 @@ export const dict = {
"toast.context.noLineSelection.title": "Sin selección de líneas",
"toast.context.noLineSelection.description": "Primero selecciona un rango de líneas en una pestaña de archivo.",
"toast.session.unshare.success.title": "La sesión dejó de compartirse",
"toast.session.unshare.success.description": "La sesión dejó de compartirse correctamente.",
"toast.session.unshare.failed.title": "Fallo al dejar de compartir sesión",
@@ -1200,4 +1199,119 @@ export const dict = {
"error.childStore.persistedProjectIconCreateFailed": "Error al crear icono de proyecto persistente",
"error.childStore.storeCreateFailed": "Error al crear almacén",
"terminal.connectionLost.abnormalClose": "WebSocket cerrado anormalmente: {{code}}",
"provider.disconnect.toast.noCredentials.description": "No se encontraron credenciales extraíbles para {{provider}}.",
"provider.disconnect.toast.failed.description": "No se pudo desconectar {{provider}}.",
"toast.migration.failed.title": "La migración de datos falló",
"command.session.background": "Pasar al fondo",
"session.timeline.notice.finished": "{{actor}} terminado",
"session.timeline.notice.failed": "{{actor}} falló",
"session.timeline.notice.cancelled": "{{actor}} cancelado",
"settings.tab.preferences": "Preferencias",
"settings.tab.notifications": "Notificaciones",
"settings.tab.projects": "Proyectos",
"settings.tab.extensions": "Extensiones",
"settings.preferences.description": "Personalizar preferencias y tema y comportamiento predeterminado",
"settings.appearance.description": "Personaliza el tema y las fuentes",
"settings.notifications.description": "Elige cuándo recibir notificaciones y escuchar sonidos",
"settings.shortcuts.description": "Personaliza atajos para acciones comunes",
"settings.servers.description": "Administrar conexiones de servidor",
"settings.projects.title": "Proyectos",
"settings.projects.description": "Administrar la configuración del proyecto en este servidor",
"settings.projects.empty": "No se encontraron proyectos",
"settings.projects.server.all": "Todos los servidores",
"settings.mcps.description": "Administrar servidores y herramientas de Model Context Protocol (MCP)",
"settings.extensions.description": "Administrar extensiones disponibles en este servidor",
"settings.extensions.tab.mcps": "MCP",
"settings.extensions.tab.skills": "Habilidades",
"settings.extensions.availableAll": "Disponible para todos los proyectos",
"settings.extensions.manageConfig": "Administrar en opencode.json",
"settings.extensions.addSkills": "Cómo agregar habilidades",
"dialog.server.authenticate.title": "Autenticar",
"project.settings.general.description": "Administrar el nombre y la apariencia del proyecto",
"project.settings.scripts": "Guiones",
"project.settings.scripts.description": "Configurar scripts para este proyecto",
"project.settings.extensions.description": "Ver extensiones disponibles para este proyecto",
"project.settings.extensions.tab.lsps": "LSP",
"project.settings.extensions.added": "Agregado a este proyecto",
"project.settings.extensions.shared": "Compartido con todos los proyectos.",
"project.settings.extensions.lsp.detected": "Servidores de idiomas detectados",
"project.settings.extensions.lsp.description": "Detectado automáticamente a partir de tipos de archivos",
"project.settings.extensions.setupRequired": "Configuración requerida",
"settings.general.section.general": "General",
"common.viewAll": "Ver todo",
"session.new.workspace.local.tooltip": "Usar el checkout actual",
"session.new.workspace.new.tooltip": "Crear un checkout aislado",
"session.new.workspace.fromBranch": "de {{branch}}",
"session.new.workspace.trigger.tooltip": "Seleccione dónde ejecutar la sesión",
"session.new.workspace.search.placeholder": "Buscar espacios de trabajo",
"settings.tab.workspaces": "Espacios de trabajo",
"settings.workspaces.filter.all": "Todos los proyectos",
"settings.workspaces.empty": "Sin espacios de trabajo",
"settings.workspaces.count.one": "Espacio de trabajo {{count}}",
"settings.workspaces.count.other": "Espacios de trabajo {{count}}",
"settings.workspaces.sessions.one": "Sesión {{count}} en {{project}}",
"settings.workspaces.sessions.other": "Sesiones {{count}} en {{project}}",
"settings.workspaces.lastActiveSession": "Última sesión activa",
"settings.workspaces.deleteAll": "Eliminar todos los espacios de trabajo",
"settings.workspaces.deleteAll.confirm": "¿Eliminar todos los espacios de trabajo {{count}}?",
"settings.workspaces.delete.warning":
"El directorio y la rama del espacio de trabajo se eliminarán permanentemente, incluidos los cambios no combinados que se muestran a continuación.",
"settings.workspaces.deleteAll.warning":
"Los espacios de trabajo seleccionados {{count}} en {{project}} se eliminarán permanentemente solo si cada uno está limpio, inactivo y no tiene sesiones vinculadas.",
"settings.workspaces.delete.blocked.active": "El espacio de trabajo activo no se puede eliminar.",
"settings.workspaces.delete.blocked.linked":
"Las sesiones vinculadas permanecerán, pero su directorio de trabajo se eliminará permanentemente.",
"settings.workspaces.default.title": "Entorno predeterminado",
"settings.workspaces.default.description": "Elige dónde comienzan las nuevas sesiones",
"settings.workspaces.default.lastUsed": "Último usado por proyecto",
"settings.workspaces.default.local": "directorio local",
"settings.workspaces.default.new": "Nuevo espacio de trabajo",
"workspace.move.title": "Mover al espacio de trabajo",
"workspace.move.menu.title": "Mover sesión a",
"workspace.move.failed": "No se pudo mover la sesión",
"workspace.lifecycle.creating": "Creando espacio de trabajo",
"workspace.lifecycle.created": "Espacio de trabajo creado",
"workspace.lifecycle.starting": "Iniciando sesión",
"workspace.onboarding.title": "Aislar sesiones con espacios de trabajo",
"workspace.onboarding.description":
"Cada uno tiene su propio pago, por lo que nada interfiere con su repositorio local.",
"workspace.lifecycle.moving": "Moverse al espacio de trabajo",
"workspace.lifecycle.set": "Espacio de trabajo establecido",
"session.summary.title": "Detalles de la sesión",
"session.summary.noBranch": "Sin rama",
"session.summary.basedOn": "Basado en {{branch}}",
"settings.workspaces.count.many": "{{count}} espacios de trabajo",
"settings.workspaces.sessions.many": "{{count}} sesiones en {{project}}",
"desktop.menu.installCli": "Instalar CLI...",
"desktop.cli.installed.title": "CLI instalada",
"desktop.cli.installed.message":
"La CLI se ha instalado en {{path}}.\n\nReinicia el terminal para usar el comando 'opencode2'.",
"desktop.cli.failed.title": "Error de instalación",
"desktop.cli.failed.message": "No se pudo instalar la CLI: {{error}}",
"command.project.copyID": "Copiar ID del proyecto",
"command.session.copyID": "Copiar ID de la sesión",
"server.row.incompatible":
"Este servidor ejecuta OpenCode {{version}}, que no es compatible con esta aplicación. Actualízalo a OpenCode V2 para continuar.",
"toast.session.copyID.failed.title": "No se pudo copiar el ID de la sesión",
"toast.session.copyID.failed.description": "Ocurrió un error al copiar el ID de la sesión",
"toast.project.copyID.failed.title": "No se pudo copiar el ID del proyecto",
"toast.project.copyID.failed.description": "Ocurrió un error al copiar el ID del proyecto",
"error.page.title.status": "Error en la solicitud al servidor",
"error.page.description.status": "El servidor devolvió una respuesta HTTP {{status}}.",
"error.page.action.reload": "Volver a cargar",
"session.error.incompatible": "Esta versión del servidor no es compatible",
"session.error.incompatible.description":
"{{server}} ejecuta OpenCode {{version}}, que no es compatible con esta aplicación. Actualiza el servidor a OpenCode V2 para continuar.",
"session.background.moveInline": "Pulsa {{keybind}} para pasar el trabajo en curso a segundo plano",
"session.background.running": "Trabajo en curso en segundo plano",
"session.background.runningCount.one": "{{count}} elemento en curso en segundo plano",
"session.background.runningCount.other": "{{count}} elementos en curso en segundo plano",
"session.background.runningCount.many": "{{count}} de elementos en curso en segundo plano",
"settings.general.row.terminalPlacement.title": "Posición del terminal",
"settings.general.row.terminalPlacement.description": "Elige dónde se abre el terminal en las sesiones",
"settings.general.row.terminalPlacement.side": "Lateral",
"settings.general.row.terminalPlacement.bottom": "Inferior",
"settings.general.row.showProjectIcon.title": "Icono del proyecto",
"settings.general.row.showProjectIcon.description": "Mostrar el icono del proyecto en el encabezado de la sesión",
}
+110
View File
@@ -1066,6 +1066,47 @@ export const dict = {
"session.delete.confirm": 'Kas kustutada seanss "{{name}}"?',
"session.delete.button": "Kustuta seanss",
"workspace.new": "Uus tööruum",
"common.viewAll": "Kuva kõik",
"session.new.workspace.local.tooltip": "Kasuta praegust väljavõtet",
"session.new.workspace.new.tooltip": "Loo eraldatud väljavõte",
"session.new.workspace.fromBranch": "harust {{branch}}",
"session.new.workspace.trigger.tooltip": "Vali, kus seanss käivitada",
"session.new.workspace.search.placeholder": "Otsi tööruume",
"settings.tab.workspaces": "Tööruumid",
"settings.workspaces.filter.all": "Kõik projektid",
"settings.workspaces.empty": "Tööruume pole",
"settings.workspaces.count.one": "{{count}} tööruum",
"settings.workspaces.count.other": "{{count}} tööruumi",
"settings.workspaces.sessions.one": "{{count}} seanss projektis {{project}}",
"settings.workspaces.sessions.other": "{{count}} seanssi projektis {{project}}",
"settings.workspaces.lastActiveSession": "Viimane aktiivne seanss",
"settings.workspaces.deleteAll": "Kustuta kõik tööruumid",
"settings.workspaces.deleteAll.confirm": "Kas kustutada kõik {{count}} tööruumi?",
"settings.workspaces.delete.warning":
"Tööruumi kataloog ja haru eemaldatakse jäädavalt koos kõigi allpool näidatud ühendamata muudatustega.",
"settings.workspaces.deleteAll.warning":
"Projekti {{project}} valitud {{count}} tööruumi eemaldatakse jäädavalt ainult siis, kui kõik on puhtad, passiivsed ja ilma lingitud seanssideta.",
"settings.workspaces.delete.blocked.active": "Aktiivset tööruumi ei saa kustutada.",
"settings.workspaces.delete.blocked.linked":
"Lingitud seansid säilivad, kuid nende töökataloog eemaldatakse jäädavalt.",
"settings.workspaces.default.title": "Vaikekeskkond",
"settings.workspaces.default.description": "Vali, kus uued seansid algavad",
"settings.workspaces.default.lastUsed": "Viimati kasutatud projekti kohta",
"settings.workspaces.default.local": "Kohalik kataloog",
"settings.workspaces.default.new": "Uus tööruum",
"workspace.move.title": "Teisalda tööruumi",
"workspace.move.menu.title": "Teisalda seanss asukohta",
"workspace.move.failed": "Seansi teisaldamine ebaõnnestus",
"workspace.lifecycle.creating": "Tööruumi loomine",
"workspace.lifecycle.created": "Tööruum loodud",
"workspace.lifecycle.starting": "Seansi käivitamine",
"workspace.onboarding.title": "Eralda seansid tööruumidega",
"workspace.onboarding.description": "Igal seansil on oma väljavõte, nii et miski ei sega kohalikku hoidlat",
"workspace.lifecycle.moving": "Tööruumi teisaldamine",
"workspace.lifecycle.set": "Tööruum määratud",
"session.summary.title": "Seansi üksikasjad",
"session.summary.noBranch": "Haru puudub",
"session.summary.basedOn": "Põhineb harul {{branch}}",
"workspace.type.local": "kohalik",
"workspace.type.sandbox": "liivakast",
"workspace.create.failed.title": "Tööruumi loomine ebaõnnestus",
@@ -1091,4 +1132,73 @@ export const dict = {
"workspace.reset.archived.many": "{{count}} seanssi arhiivitakse.",
"workspace.reset.note": "See lähtestab tööruumi, et see vastaks vaikeharule.",
"dialog.usageExceeded.dontShowAgain": "Ära kuva enam",
"provider.disconnect.toast.noCredentials.description": "Pakkuja {{provider}} jaoks ei leitud eemaldatavaid mandaate.",
"provider.disconnect.toast.failed.description": "Pakkuja {{provider}} ühenduse katkestamine ebaõnnestus.",
"toast.migration.failed.title": "Andmete migreerimine ebaõnnestus",
"command.session.background": "Teisalda taustale",
"session.timeline.notice.finished": "{{actor}} lõpetas",
"session.timeline.notice.failed": "{{actor}} ebaõnnestus",
"session.timeline.notice.cancelled": "{{actor}} tühistati",
"settings.tab.preferences": "Eelistused",
"settings.tab.notifications": "Teavitused",
"settings.tab.projects": "Projektid",
"settings.tab.extensions": "Laiendused",
"settings.preferences.description": "Kohandage eelistusi, teemat ja vaikekäitumist",
"settings.appearance.description": "Kohandage teemat ja fonte",
"settings.notifications.description": "Valige, millal teavitusi saada ja helisid kuulda",
"settings.shortcuts.description": "Kohandage levinud toimingute otseteid",
"settings.servers.description": "Hallake serveriühendusi",
"settings.projects.title": "Projektid",
"settings.projects.description": "Hallake selle serveri projektisätteid",
"settings.projects.empty": "Projekte ei leitud",
"settings.projects.server.all": "Kõik serverid",
"settings.mcps.description": "Hallake Model Context Protocoli (MCP) servereid ja tööriistu",
"settings.extensions.description": "Hallake selles serveris saadaolevaid laiendusi",
"settings.extensions.tab.mcps": "MCP-d",
"settings.extensions.tab.skills": "Oskused",
"settings.extensions.availableAll": "Saadaval kõigile projektidele",
"settings.extensions.manageConfig": "Halda failis opencode.json",
"settings.extensions.addSkills": "Kuidas oskusi lisada",
"dialog.server.authenticate.title": "Autendi",
"project.settings.general.description": "Hallake projekti nime ja välimust",
"project.settings.scripts": "Skriptid",
"project.settings.scripts.description": "Seadistage selle projekti skripte",
"project.settings.extensions.description": "Vaadake selle projekti jaoks saadaolevaid laiendusi",
"project.settings.extensions.tab.lsps": "LSP-d",
"project.settings.extensions.added": "Lisatud sellesse projekti",
"project.settings.extensions.shared": "Jagatud kõigi projektidega",
"project.settings.extensions.lsp.detected": "Tuvastatud keeleserverid",
"project.settings.extensions.lsp.description": "Automaatselt tuvastatud failitüüpide järgi",
"project.settings.extensions.setupRequired": "Vajab seadistamist",
"settings.general.section.general": "Üldine",
"desktop.menu.installCli": "Paigalda CLI...",
"desktop.cli.installed.title": "CLI on paigaldatud",
"desktop.cli.installed.message":
"CLI paigaldati asukohta {{path}}\n\nKäsu 'opencode2' kasutamiseks taaskäivita terminal.",
"desktop.cli.failed.title": "Paigaldamine ebaõnnestus",
"desktop.cli.failed.message": "CLI paigaldamine ebaõnnestus: {{error}}",
"command.project.copyID": "Kopeeri projekti ID",
"command.session.copyID": "Kopeeri seansi ID",
"server.row.incompatible":
"See server käitab OpenCode'i versiooni {{version}}, mis ei ühildu selle rakendusega. Jätkamiseks uuenda server OpenCode V2-le.",
"toast.session.copyID.failed.title": "Seansi ID kopeerimine ebaõnnestus",
"toast.session.copyID.failed.description": "Seansi ID kopeerimisel tekkis viga",
"toast.project.copyID.failed.title": "Projekti ID kopeerimine ebaõnnestus",
"toast.project.copyID.failed.description": "Projekti ID kopeerimisel tekkis viga",
"error.page.title.status": "Serveripäring ebaõnnestus",
"error.page.description.status": "Server tagastas HTTP-vastuse {{status}}.",
"error.page.action.reload": "Laadi uuesti",
"session.error.incompatible": "Seda serveriversiooni ei toetata",
"session.error.incompatible.description":
"{{server}} käitab OpenCode'i versiooni {{version}}, mis ei ühildu selle rakendusega. Jätkamiseks uuenda server OpenCode V2-le.",
"session.background.moveInline": "Vajuta {{keybind}}, et teisaldada käimasolev töö taustale",
"session.background.running": "Töötab taustal",
"session.background.runningCount.one": "{{count}} üksus töötab taustal",
"session.background.runningCount.other": "{{count}} üksust töötab taustal",
"settings.general.row.terminalPlacement.title": "Terminali paigutus",
"settings.general.row.terminalPlacement.description": "Vali, kus terminal seanssides avaneb",
"settings.general.row.terminalPlacement.side": "Küljel",
"settings.general.row.terminalPlacement.bottom": "All",
"settings.general.row.showProjectIcon.title": "Projekti ikoon",
"settings.general.row.showProjectIcon.description": "Kuva projekti ikoon seansi päises",
}
+111
View File
@@ -1093,4 +1093,115 @@ export const dict = {
"workspace.reset.archived.many": "جلسات {{count}} بایگانی خواهد شد.",
"workspace.reset.note": "این کار فضای کاری را برای مطابقت با شاخه پیش فرض بازنشانی می کند.",
"dialog.usageExceeded.dontShowAgain": "دیگر نشان نده",
"toast.migration.failed.title": "انتقال داده‌ها ناموفق بود",
"command.session.background": "انتقال به پس‌زمینه",
"session.timeline.notice.finished": "اجرای \u2068{{actor}}\u2069 پایان یافت",
"session.timeline.notice.failed": "اجرای \u2068{{actor}}\u2069 ناموفق بود",
"session.timeline.notice.cancelled": "اجرای \u2068{{actor}}\u2069 لغو شد",
"settings.tab.preferences": "ترجیحات",
"settings.tab.notifications": "اعلان‌ها",
"settings.tab.projects": "پروژه‌ها",
"settings.tab.extensions": "افزونه‌ها",
"settings.preferences.description": "سفارشی‌سازی ترجیحات، پوسته و رفتار پیش‌فرض",
"settings.appearance.description": "سفارشی‌سازی پوسته و قلم‌ها",
"settings.notifications.description": "انتخاب زمان دریافت اعلان‌ها و پخش صداها",
"settings.shortcuts.description": "سفارشی‌سازی میانبرهای کارهای رایج",
"settings.servers.description": "مدیریت اتصال‌های سرور",
"settings.projects.title": "پروژه‌ها",
"settings.projects.description": "مدیریت تنظیمات پروژه در این سرور",
"settings.projects.empty": "پروژه‌ای یافت نشد",
"settings.projects.server.all": "همه سرورها",
"settings.mcps.description": "مدیریت سرورها و ابزارهای پروتکل زمینه مدل (MCP)",
"settings.extensions.description": "مدیریت افزونه‌های موجود در این سرور",
"settings.extensions.tab.mcps": "MCPها",
"settings.extensions.tab.skills": "مهارت‌ها",
"settings.extensions.availableAll": "در دسترس همه پروژه‌ها",
"settings.extensions.manageConfig": "مدیریت در opencode.json",
"settings.extensions.addSkills": "نحوه افزودن مهارت‌ها",
"dialog.server.authenticate.title": "احراز هویت",
"project.settings.general.description": "مدیریت نام و ظاهر پروژه",
"project.settings.scripts": "اسکریپت‌ها",
"project.settings.scripts.description": "پیکربندی اسکریپت‌های این پروژه",
"project.settings.extensions.description": "مشاهده افزونه‌های موجود برای این پروژه",
"project.settings.extensions.tab.lsps": "LSPها",
"project.settings.extensions.added": "افزوده‌شده به این پروژه",
"project.settings.extensions.shared": "مشترک با همه پروژه‌ها",
"project.settings.extensions.lsp.detected": "سرورهای زبان شناسایی‌شده",
"project.settings.extensions.lsp.description": "شناسایی خودکار بر اساس نوع فایل‌ها",
"project.settings.extensions.setupRequired": "نیازمند راه‌اندازی",
"settings.general.section.general": "عمومی",
"common.viewAll": "مشاهده همه",
"session.new.workspace.local.tooltip": "استفاده از نسخه کاری فعلی",
"session.new.workspace.new.tooltip": "ایجاد نسخه کاری ایزوله",
"session.new.workspace.fromBranch": "از \u2068{{branch}}\u2069",
"session.new.workspace.trigger.tooltip": "محل اجرای جلسه را انتخاب کنید",
"session.new.workspace.search.placeholder": "جستجوی فضاهای کاری",
"settings.tab.workspaces": "فضاهای کاری",
"settings.workspaces.filter.all": "همه پروژه‌ها",
"settings.workspaces.empty": "فضای کاری وجود ندارد",
"settings.workspaces.count.one": "{{count}} فضای کاری",
"settings.workspaces.count.other": "{{count}} فضای کاری",
"settings.workspaces.sessions.one": "{{count}} جلسه در \u2068{{project}}\u2069",
"settings.workspaces.sessions.other": "{{count}} جلسه در \u2068{{project}}\u2069",
"settings.workspaces.lastActiveSession": "آخرین جلسه فعال",
"settings.workspaces.deleteAll": "حذف همه فضاهای کاری",
"settings.workspaces.deleteAll.confirm": "همه {{count}} فضای کاری حذف شوند؟",
"settings.workspaces.delete.warning":
"شاخه و پوشه فضای کاری برای همیشه حذف می‌شوند؛ از جمله تغییرات ادغام‌نشده‌ای که در زیر نشان داده شده‌اند.",
"settings.workspaces.deleteAll.warning":
"{{count}} فضای کاری انتخاب‌شده در \u2068{{project}}\u2069 تنها در صورتی برای همیشه حذف می‌شوند که همگی پاک و غیرفعال باشند و جلسه پیوندشده‌ای نداشته باشند.",
"settings.workspaces.delete.blocked.active": "فضای کاری فعال را نمی‌توان حذف کرد.",
"settings.workspaces.delete.blocked.linked":
"جلسه‌های پیوندشده باقی می‌مانند، اما پوشه کاری آن‌ها برای همیشه حذف می‌شود.",
"settings.workspaces.default.title": "محیط پیش‌فرض",
"settings.workspaces.default.description": "محل شروع جلسه‌های جدید را انتخاب کنید",
"settings.workspaces.default.lastUsed": "آخرین مورد استفاده در هر پروژه",
"settings.workspaces.default.local": "پوشه محلی",
"settings.workspaces.default.new": "فضای کاری جدید",
"workspace.move.title": "انتقال به فضای کاری",
"workspace.move.menu.title": "انتقال جلسه به",
"workspace.move.failed": "انتقال جلسه ناموفق بود",
"workspace.lifecycle.creating": "در حال ایجاد فضای کاری",
"workspace.lifecycle.created": "فضای کاری ایجاد شد",
"workspace.lifecycle.starting": "در حال شروع جلسه",
"workspace.onboarding.title": "جداسازی جلسه‌ها با فضاهای کاری",
"workspace.onboarding.description": "هر جلسه نسخه کاری خودش را دارد، بنابراین چیزی با مخزن محلی شما تداخل نمی‌کند",
"workspace.lifecycle.moving": "در حال انتقال به فضای کاری",
"workspace.lifecycle.set": "فضای کاری تنظیم شد",
"session.summary.title": "جزئیات جلسه",
"session.summary.noBranch": "بدون شاخه",
"session.summary.basedOn": "بر پایه \u2068{{branch}}\u2069",
"provider.disconnect.toast.noCredentials.description":
"اطلاعات ورود قابل حذفی برای \u2068{{provider}}\u2069 یافت نشد.",
"provider.disconnect.toast.failed.description": "قطع اتصال از \u2068{{provider}}\u2069 ناموفق بود.",
"command.project.copyID": "کپی شناسه پروژه",
"command.session.copyID": "کپی شناسه جلسه",
"server.row.incompatible":
"این سرور OpenCode \u2068{{version}}\u2069 را اجرا می‌کند که با این برنامه سازگار نیست. برای ادامه، آن را به OpenCode V2 ارتقا دهید.",
"toast.session.copyID.failed.title": "کپی شناسه جلسه ناموفق بود",
"toast.session.copyID.failed.description": "هنگام کپی شناسه جلسه خطایی رخ داد",
"toast.project.copyID.failed.title": "کپی شناسه پروژه ناموفق بود",
"toast.project.copyID.failed.description": "هنگام کپی شناسه پروژه خطایی رخ داد",
"error.page.title.status": "درخواست سرور ناموفق بود",
"error.page.description.status": "سرور پاسخ HTTP با وضعیت \u2068{{status}}\u2069 برگرداند.",
"error.page.action.reload": "بارگیری مجدد",
"session.error.incompatible": "این نسخه سرور پشتیبانی نمی‌شود",
"session.error.incompatible.description":
"\u2068{{server}}\u2069 نسخه OpenCode \u2068{{version}}\u2069 را اجرا می‌کند که با این برنامه سازگار نیست. برای ادامه، سرور را به OpenCode V2 ارتقا دهید.",
"session.background.moveInline": "برای انتقال کار در حال اجرا به پس‌زمینه، \u2068{{keybind}}\u2069 را فشار دهید",
"session.background.running": "کار در پس‌زمینه در حال اجراست",
"session.background.runningCount.one": "{{count}} مورد در پس‌زمینه در حال اجراست",
"session.background.runningCount.other": "{{count}} مورد در پس‌زمینه در حال اجراست",
"settings.general.row.terminalPlacement.title": "جایگاه ترمینال",
"settings.general.row.terminalPlacement.description": "محل باز شدن ترمینال در جلسه‌ها را انتخاب کنید",
"settings.general.row.terminalPlacement.side": "کنار",
"settings.general.row.terminalPlacement.bottom": "پایین",
"settings.general.row.showProjectIcon.title": "نماد پروژه",
"settings.general.row.showProjectIcon.description": "نمایش نماد پروژه در سربرگ جلسه",
"desktop.menu.installCli": "نصب CLI...",
"desktop.cli.installed.title": "CLI نصب شد",
"desktop.cli.installed.message":
"CLI در \u2068{{path}}\u2069 نصب شد\n\nبرای استفاده از دستور 'opencode2'، ترمینال را دوباره راه‌اندازی کنید.",
"desktop.cli.failed.title": "نصب ناموفق بود",
"desktop.cli.failed.message": "نصب CLI ناموفق بود: \u2068{{error}}\u2069",
}
+112
View File
@@ -1107,4 +1107,116 @@ export const dict = {
"desktop.picker.error.notSelected": "Tiedostovalitsimessa ei valittu tiedostoa",
"desktop.picker.error.sizeLimit": "Valitut liitteet ylittävät {{limit}} Mt:n rajan",
"command.logs.export": "Vie lokit",
"provider.disconnect.toast.noCredentials.description":
"{{provider}}:lle ei löytynyt irrotettavia kirjautumistietoja.",
"provider.disconnect.toast.failed.description": "{{provider}}:n irrottaminen epäonnistui.",
"toast.migration.failed.title": "Tietojen siirto epäonnistui",
"command.session.background": "Siirrä taustalle",
"session.timeline.notice.finished": "{{actor}} valmis",
"session.timeline.notice.failed": "{{actor}} epäonnistui",
"session.timeline.notice.cancelled": "{{actor}} peruutettu",
"settings.tab.preferences": "Asetukset",
"settings.tab.notifications": "Ilmoitukset",
"settings.tab.projects": "Projektit",
"settings.tab.extensions": "Laajennukset",
"settings.preferences.description": "Mukauta asetuksia ja teemaa ja oletuskäyttäytymistä",
"settings.appearance.description": "Mukauta teemaa ja fontteja",
"settings.notifications.description": "Valitse, milloin haluat vastaanottaa ilmoituksia ja kuulla ääniä",
"settings.shortcuts.description": "Mukauta yleisten toimintojen pikakuvakkeita",
"settings.servers.description": "Hallinnoi palvelinyhteyksiä",
"settings.projects.title": "Projektit",
"settings.projects.description": "Hallinnoi tämän palvelimen projektiasetuksia",
"settings.projects.empty": "Projekteja ei löytynyt",
"settings.projects.server.all": "Kaikki palvelimet",
"settings.mcps.description": "Hallitse Model Context Protocol (MCP) -palvelimia ja -työkaluja",
"settings.extensions.description": "Hallinnoi tällä palvelimella saatavilla olevia laajennuksia",
"settings.extensions.tab.mcps": "MCP:t",
"settings.extensions.tab.skills": "Taidot",
"settings.extensions.availableAll": "Saatavilla kaikkiin projekteihin",
"settings.extensions.manageConfig": "Hallinnoi osoitteessa opencode.json",
"settings.extensions.addSkills": "Kuinka lisätä taitoja",
"dialog.server.authenticate.title": "Todentaa",
"project.settings.general.description": "Hallitse projektin nimeä ja ulkoasua",
"project.settings.scripts": "Käsikirjoitukset",
"project.settings.scripts.description": "Määritä komentosarjat tälle projektille",
"project.settings.extensions.description": "Näytä tämän projektin saatavilla olevat laajennukset",
"project.settings.extensions.tab.lsps": "LSP:t",
"project.settings.extensions.added": "Lisätty tähän projektiin",
"project.settings.extensions.shared": "Jaettu kaikkien projektien kanssa",
"project.settings.extensions.lsp.detected": "Havaitut kielipalvelimet",
"project.settings.extensions.lsp.description": "Havaittu automaattisesti tiedostotyypeistä",
"project.settings.extensions.setupRequired": "Asennus vaaditaan",
"settings.general.section.general": "Kenraali",
"common.viewAll": "Näytä kaikki",
"session.new.workspace.local.tooltip": "Käytä nykyistä checkoutia",
"session.new.workspace.new.tooltip": "Luo erillinen checkout",
"session.new.workspace.fromBranch": "alkaen {{branch}}",
"session.new.workspace.trigger.tooltip": "Valitse, missä istunto suoritetaan",
"session.new.workspace.search.placeholder": "Hae työtiloja",
"settings.tab.workspaces": "Työtilat",
"settings.workspaces.filter.all": "Kaikki projektit",
"settings.workspaces.empty": "Ei työtiloja",
"settings.workspaces.count.one": "{{count}} työtila",
"settings.workspaces.count.other": "{{count}} työtilat",
"settings.workspaces.sessions.one": "{{count}}-istunto {{project}}:ssä",
"settings.workspaces.sessions.other": "{{count}}-istunnot {{project}}:ssä",
"settings.workspaces.lastActiveSession": "Viimeinen aktiivinen istunto",
"settings.workspaces.deleteAll": "Poista kaikki työtilat",
"settings.workspaces.deleteAll.confirm": "Poistetaanko kaikki {{count}}-työtilat?",
"settings.workspaces.delete.warning":
"Työtilan hakemisto ja haara poistetaan pysyvästi, mukaan lukien alla näkyvät yhdistämättömät muutokset.",
"settings.workspaces.deleteAll.warning":
"{{count}}:n valitut työtilat {{project}}:ssä poistetaan pysyvästi vain, jos jokainen on puhdas, ei-aktiivinen eikä siinä ole linkitettyjä istuntoja.",
"settings.workspaces.delete.blocked.active": "Aktiivista työtilaa ei voi poistaa.",
"settings.workspaces.delete.blocked.linked":
"Linkitetyt istunnot säilyvät, mutta niiden työhakemisto poistetaan pysyvästi.",
"settings.workspaces.default.title": "Oletusympäristö",
"settings.workspaces.default.description": "Valitse, missä uudet istunnot alkavat",
"settings.workspaces.default.lastUsed": "Viimeksi käytetty projektia kohti",
"settings.workspaces.default.local": "Paikallinen hakemisto",
"settings.workspaces.default.new": "Uusi työtila",
"workspace.move.title": "Siirrä työtilaan",
"workspace.move.menu.title": "Siirrä istunto kohteeseen",
"workspace.move.failed": "Istunnon siirtäminen epäonnistui",
"workspace.lifecycle.creating": "Työtilan luominen",
"workspace.lifecycle.created": "Työtila luotu",
"workspace.lifecycle.starting": "Aloitetaan istunto",
"workspace.onboarding.title": "Eristä istunnot työtiloilla",
"workspace.onboarding.description": "Jokainen saa oman checkoutin, joten mikään ei häiritse paikallista säilöäsi",
"workspace.lifecycle.moving": "Siirretään työtilaan",
"workspace.lifecycle.set": "Työtila asetettu",
"session.summary.title": "Istunnon tiedot",
"session.summary.noBranch": "Ei haaraa",
"session.summary.basedOn": "Perustuu haaraan {{branch}}",
"desktop.menu.installCli": "Asenna CLI...",
"desktop.cli.installed.title": "CLI asennettu",
"desktop.cli.installed.message":
"CLI asennettiin kohteeseen {{path}}.\n\nKäynnistä terminaali uudelleen, jotta voit käyttää 'opencode2'-komentoa.",
"desktop.cli.failed.title": "Asennus epäonnistui",
"desktop.cli.failed.message": "CLI:n asennus epäonnistui: {{error}}",
"command.project.copyID": "Kopioi projektin tunnus",
"command.session.copyID": "Kopioi istunnon tunnus",
"server.row.incompatible":
"Tässä palvelimessa on käytössä OpenCode {{version}}, joka ei ole yhteensopiva tämän sovelluksen kanssa. Jatka päivittämällä palvelin OpenCode V2:een.",
"toast.session.copyID.failed.title": "Istunnon tunnuksen kopiointi epäonnistui",
"toast.session.copyID.failed.description": "Istunnon tunnusta kopioitaessa tapahtui virhe",
"toast.project.copyID.failed.title": "Projektin tunnuksen kopiointi epäonnistui",
"toast.project.copyID.failed.description": "Projektin tunnusta kopioitaessa tapahtui virhe",
"error.page.title.status": "Palvelinpyyntö epäonnistui",
"error.page.description.status": "Palvelin palautti HTTP {{status}} -vastauksen.",
"error.page.action.reload": "Lataa uudelleen",
"session.error.incompatible": "Tätä palvelinversiota ei tueta",
"session.error.incompatible.description":
"Palvelimessa {{server}} on käytössä OpenCode {{version}}, joka ei ole yhteensopiva tämän sovelluksen kanssa. Jatka päivittämällä palvelin OpenCode V2:een.",
"session.background.moveInline": "Siirrä käynnissä oleva työ taustalle painamalla {{keybind}}",
"session.background.running": "Työ suoritetaan taustalla",
"session.background.runningCount.one": "{{count}} kohde suoritetaan taustalla",
"session.background.runningCount.other": "{{count}} kohdetta suoritetaan taustalla",
"settings.general.row.terminalPlacement.title": "Terminaalin sijainti",
"settings.general.row.terminalPlacement.description": "Valitse, mihin terminaali avautuu istunnoissa",
"settings.general.row.terminalPlacement.side": "Sivulla",
"settings.general.row.terminalPlacement.bottom": "Alhaalla",
"settings.general.row.showProjectIcon.title": "Projektikuvake",
"settings.general.row.showProjectIcon.description": "Näytä projektikuvake istunnon otsikossa",
}
+111
View File
@@ -1068,6 +1068,47 @@ export const dict = {
"session.delete.confirm": 'Strika setuna "{{name}}"?',
"session.delete.button": "Strika setu",
"workspace.new": "Nýtt workspace",
"common.viewAll": "Vís øll",
"session.new.workspace.local.tooltip": "Brúka núverandi checkout",
"session.new.workspace.new.tooltip": "Stovna avbyrgdan checkout",
"session.new.workspace.fromBranch": "frá {{branch}}",
"session.new.workspace.trigger.tooltip": "Vel, hvar setan skal koyra",
"session.new.workspace.search.placeholder": "Leita eftir workspaces",
"settings.tab.workspaces": "Workspaces",
"settings.workspaces.filter.all": "Allar verkætlanir",
"settings.workspaces.empty": "Eingi workspaces",
"settings.workspaces.count.one": "{{count}} workspace",
"settings.workspaces.count.other": "{{count}} workspaces",
"settings.workspaces.sessions.one": "{{count}} seta í {{project}}",
"settings.workspaces.sessions.other": "{{count}} setur í {{project}}",
"settings.workspaces.lastActiveSession": "Seinasta virkna seta",
"settings.workspaces.deleteAll": "Strika øll workspaces",
"settings.workspaces.deleteAll.confirm": "Strika øll {{count}} workspaces?",
"settings.workspaces.delete.warning":
"Workspace-mappan og greinin verða strikaðar fyri altíð, eisini ósamanlagdar broytingar, sum síggjast niðanfyri.",
"settings.workspaces.deleteAll.warning":
"Tey {{count}} valdu workspaces í {{project}} verða bert strikað fyri altíð, um øll eru rein, óvirkin og uttan knýttar setur.",
"settings.workspaces.delete.blocked.active": "Virkna workspace kann ikki strikast.",
"settings.workspaces.delete.blocked.linked":
"Knýttar setur verða varðveittar, men arbeiðsmappan hjá teimum verður strikað fyri altíð.",
"settings.workspaces.default.title": "Forsett umhvørvi",
"settings.workspaces.default.description": "Vel, hvar nýggjar setur byrja",
"settings.workspaces.default.lastUsed": "Seinast brúkt fyri hvørja verkætlan",
"settings.workspaces.default.local": "Lokal mappa",
"settings.workspaces.default.new": "Nýtt workspace",
"workspace.move.title": "Flyt til workspace",
"workspace.move.menu.title": "Flyt setu til",
"workspace.move.failed": "Tað eydnaðist ikki at flyta setuna",
"workspace.lifecycle.creating": "Stovnar workspace",
"workspace.lifecycle.created": "Workspace stovnað",
"workspace.lifecycle.starting": "Byrjar setu",
"workspace.onboarding.title": "Avbyrg setur við workspaces",
"workspace.onboarding.description": "Hvør seta fær sítt egna checkout, so einki órógvar lokalu goymsluna",
"workspace.lifecycle.moving": "Flytur til workspace",
"workspace.lifecycle.set": "Workspace sett",
"session.summary.title": "Setuupplýsingar",
"session.summary.noBranch": "Eingin grein",
"session.summary.basedOn": "Grundað á {{branch}}",
"workspace.type.local": "lokalt",
"workspace.type.sandbox": "sandkassi",
"workspace.create.failed.title": "Tað eydnaðist ikki at stovna workspace",
@@ -1093,4 +1134,74 @@ export const dict = {
"workspace.reset.archived.many": "{{count}} setur verða arkiveraðar.",
"workspace.reset.note": "Hetta nullstillar workspace til at passa til forsettu greinina.",
"dialog.usageExceeded.dontShowAgain": "Vís ikki aftur",
"provider.disconnect.toast.noCredentials.description":
"Eingi trúnaðarupplýsingar, ið kunnu strikast, funnar fyri {{provider}}.",
"provider.disconnect.toast.failed.description": "Tað eydnaðist ikki at slíta sambandið við {{provider}}.",
"toast.migration.failed.title": "Dataflytingin miseydnaðist",
"command.session.background": "Flyt í bakgrundina",
"session.timeline.notice.finished": "{{actor}} varð liðugur",
"session.timeline.notice.failed": "{{actor}} miseydnaðist",
"session.timeline.notice.cancelled": "{{actor}} varð avlýstur",
"settings.tab.preferences": "Sertokkar",
"settings.tab.notifications": "Fráboðanir",
"settings.tab.projects": "Verkætlanir",
"settings.tab.extensions": "Víðkanir",
"settings.preferences.description": "Tillaga sertokkar, tema og vanliga atferð",
"settings.appearance.description": "Tillaga tema og skriftsløg",
"settings.notifications.description": "Vel nær tú vilt fáa fráboðanir og hoyra ljóð",
"settings.shortcuts.description": "Tillaga snarvegir til vanligar gerðir",
"settings.servers.description": "Umsit ambætarasambond",
"settings.projects.title": "Verkætlanir",
"settings.projects.description": "Umsit verkætlanarinnstillingar á hesum ambætaranum",
"settings.projects.empty": "Ongar verkætlanir funnar",
"settings.projects.server.all": "Allir ambætarar",
"settings.mcps.description": "Umsit Model Context Protocol (MCP) ambætarar og tól",
"settings.extensions.description": "Umsit víðkanir, sum eru tøkar á hesum ambætaranum",
"settings.extensions.tab.mcps": "MCP-ar",
"settings.extensions.tab.skills": "Førleikar",
"settings.extensions.availableAll": "Tøkt fyri allar verkætlanir",
"settings.extensions.manageConfig": "Umsit í opencode.json",
"settings.extensions.addSkills": "Hvussu førleikar verða lagdir afturat",
"dialog.server.authenticate.title": "Samgilda",
"project.settings.general.description": "Umsit verkætlanarnavn og útsjónd",
"project.settings.scripts": "Skript",
"project.settings.scripts.description": "Set skript upp fyri hesa verkætlanina",
"project.settings.extensions.description": "Vís víðkanir, sum eru tøkar fyri hesa verkætlanina",
"project.settings.extensions.tab.lsps": "LSP-ar",
"project.settings.extensions.added": "Lagt afturat hesi verkætlanini",
"project.settings.extensions.shared": "Deilt við allar verkætlanir",
"project.settings.extensions.lsp.detected": "Uppdagaðir málambætarar",
"project.settings.extensions.lsp.description": "Sjálvvirkandi uppdagað eftir fíluslagi",
"project.settings.extensions.setupRequired": "Uppseting krevst",
"settings.general.section.general": "Alment",
"desktop.menu.installCli": "Set CLI upp...",
"desktop.cli.installed.title": "CLI er sett upp",
"desktop.cli.installed.message":
"CLI varð sett upp í {{path}}\n\nEndurbyrja terminalin fyri at brúka 'opencode2'-stýriboðið.",
"desktop.cli.failed.title": "Uppsetingin miseydnaðist",
"desktop.cli.failed.message": "Tað eydnaðist ikki at seta CLI upp: {{error}}",
"command.project.copyID": "Avrita verkætlanar-ID",
"command.session.copyID": "Avrita setu-ID",
"server.row.incompatible":
"Hesin ambætarin koyrir OpenCode {{version}}, sum ikki samsvarar við hetta forritið. Dagfør hann til OpenCode V2 fyri at halda fram.",
"toast.session.copyID.failed.title": "Tað eydnaðist ikki at avrita setu-ID",
"toast.session.copyID.failed.description": "Ein villa kom fyri, meðan setu-ID varð avritað",
"toast.project.copyID.failed.title": "Tað eydnaðist ikki at avrita verkætlanar-ID",
"toast.project.copyID.failed.description": "Ein villa kom fyri, meðan verkætlanar-ID varð avritað",
"error.page.title.status": "Fyrispurningurin til ambætaran miseydnaðist",
"error.page.description.status": "Ambætarin svaraði við HTTP {{status}}.",
"error.page.action.reload": "Les inn aftur",
"session.error.incompatible": "Henda ambætaraútgávan verður ikki stuðlað",
"session.error.incompatible.description":
"{{server}} koyrir OpenCode {{version}}, sum ikki samsvarar við hetta forritið. Dagfør ambætaran til OpenCode V2 fyri at halda fram.",
"session.background.moveInline": "Trýst á {{keybind}} fyri at flyta arbeiðið, ið koyrir, í bakgrundina",
"session.background.running": "Arbeiði koyrir í bakgrundini",
"session.background.runningCount.one": "{{count}} arbeiði koyrir í bakgrundini",
"session.background.runningCount.other": "{{count}} arbeiði koyra í bakgrundini",
"settings.general.row.terminalPlacement.title": "Staðseting av terminali",
"settings.general.row.terminalPlacement.description": "Vel, hvar terminalurin skal lata upp í setum",
"settings.general.row.terminalPlacement.side": "Á síðuni",
"settings.general.row.terminalPlacement.bottom": "Niðast",
"settings.general.row.showProjectIcon.title": "Verkætlanarímynd",
"settings.general.row.showProjectIcon.description": "Vís verkætlanarímyndina í setuhøvdinum",
}
+116
View File
@@ -1134,4 +1134,120 @@ export const dict = {
"titlebar.update": "Mettre à jour",
"titlebar.updateVersion": "Mettre à jour {{version}}",
"provider.disconnect.toast.noCredentials.description":
"Aucune information d'identification amovible trouvée pour {{provider}}.",
"provider.disconnect.toast.failed.description": "Échec de la déconnexion de {{provider}}.",
"toast.migration.failed.title": "Échec de la migration des données",
"command.session.background": "Passer en arrière-plan",
"session.timeline.notice.finished": "{{actor}} terminé",
"session.timeline.notice.failed": "Échec du {{actor}}",
"session.timeline.notice.cancelled": "{{actor}} annulé",
"settings.tab.preferences": "Préférences",
"settings.tab.notifications": "Notifications",
"settings.tab.projects": "Projets",
"settings.tab.extensions": "Extensions",
"settings.preferences.description": "Personnaliser les préférences, le thème et le comportement par défaut",
"settings.appearance.description": "Personnaliser le thème et les polices",
"settings.notifications.description": "Choisissez quand recevoir des notifications et entendre des sons",
"settings.shortcuts.description": "Personnaliser les raccourcis pour les actions courantes",
"settings.servers.description": "Gérer les connexions au serveur",
"settings.projects.title": "Projets",
"settings.projects.description": "Gérer les paramètres du projet sur ce serveur",
"settings.projects.empty": "Aucun projet trouvé",
"settings.projects.server.all": "Tous les serveurs",
"settings.mcps.description": "Gérer les serveurs et les outils Model Context Protocol (MCP)",
"settings.extensions.description": "Gérer les extensions disponibles sur ce serveur",
"settings.extensions.tab.mcps": "MCP",
"settings.extensions.tab.skills": "Compétences",
"settings.extensions.availableAll": "Disponible pour tous les projets",
"settings.extensions.manageConfig": "Gérer dans opencode.json",
"settings.extensions.addSkills": "Comment ajouter des compétences",
"dialog.server.authenticate.title": "Authentifier",
"project.settings.general.description": "Gérer le nom et l'apparence du projet",
"project.settings.scripts": "Scripts",
"project.settings.scripts.description": "Configurer des scripts pour ce projet",
"project.settings.extensions.description": "Afficher les extensions disponibles pour ce projet",
"project.settings.extensions.tab.lsps": "LSP",
"project.settings.extensions.added": "Ajouté à ce projet",
"project.settings.extensions.shared": "Partagé avec tous les projets",
"project.settings.extensions.lsp.detected": "Serveurs de langue détectés",
"project.settings.extensions.lsp.description": "Détection automatique à partir des types de fichiers",
"project.settings.extensions.setupRequired": "Configuration requise",
"settings.general.section.general": "Général",
"common.viewAll": "Voir tout",
"session.new.workspace.local.tooltip": "Utiliser le checkout actuel",
"session.new.workspace.new.tooltip": "Créer un checkout isolé",
"session.new.workspace.fromBranch": "de {{branch}}",
"session.new.workspace.trigger.tooltip": "Sélectionnez où exécuter la session",
"session.new.workspace.search.placeholder": "Rechercher des espaces de travail",
"settings.tab.workspaces": "Espaces de travail",
"settings.workspaces.filter.all": "Tous les projets",
"settings.workspaces.empty": "Aucun espace de travail",
"settings.workspaces.count.one": "Espace de travail {{count}}",
"settings.workspaces.count.other": "Espaces de travail {{count}}",
"settings.workspaces.sessions.one": "Séance {{count}} dans {{project}}",
"settings.workspaces.sessions.other": "Séances {{count}} dans {{project}}",
"settings.workspaces.lastActiveSession": "Dernière session active",
"settings.workspaces.deleteAll": "Supprimer tous les espaces de travail",
"settings.workspaces.deleteAll.confirm": "Supprimer tous les espaces de travail {{count}} ?",
"settings.workspaces.delete.warning":
"Le répertoire et la branche de l'espace de travail seront définitivement supprimés, y compris toutes les modifications non fusionnées indiquées ci-dessous.",
"settings.workspaces.deleteAll.warning":
"Les espaces de travail sélectionnés {{count}} dans {{project}} seront définitivement supprimés uniquement si chacun est propre, inactif et n'a aucune session liée.",
"settings.workspaces.delete.blocked.active": "L'espace de travail actif ne peut pas être supprimé.",
"settings.workspaces.delete.blocked.linked":
"Les sessions liées resteront, mais leur répertoire de travail sera définitivement supprimé.",
"settings.workspaces.default.title": "Environnement par défaut",
"settings.workspaces.default.description": "Choisissez où commencent les nouvelles sessions",
"settings.workspaces.default.lastUsed": "Dernière utilisation par projet",
"settings.workspaces.default.local": "Annuaire local",
"settings.workspaces.default.new": "Nouvel espace de travail",
"workspace.move.title": "Déplacer vers l'espace de travail",
"workspace.move.menu.title": "Déplacer la séance vers",
"workspace.move.failed": "Échec du déplacement de la session",
"workspace.lifecycle.creating": "Création d'un espace de travail",
"workspace.lifecycle.created": "Espace de travail créé",
"workspace.lifecycle.starting": "Démarrage de la session",
"workspace.onboarding.title": "Isoler les sessions avec des espaces de travail",
"workspace.onboarding.description":
"Chacun a sa propre extraction, donc rien n'interfère avec votre référentiel local",
"workspace.lifecycle.moving": "Passer à l'espace de travail",
"workspace.lifecycle.set": "Espace de travail défini",
"session.summary.title": "Détails de la session",
"session.summary.noBranch": "Aucune branche",
"session.summary.basedOn": "Basé sur {{branch}}",
"settings.workspaces.count.many": "{{count}} espaces de travail",
"settings.workspaces.sessions.many": "{{count}} sessions dans {{project}}",
"desktop.menu.installCli": "Installer la CLI...",
"desktop.cli.installed.title": "CLI installée",
"desktop.cli.installed.message":
"La CLI a été installée dans {{path}}.\n\nRedémarrez votre terminal pour utiliser la commande 'opencode2'.",
"desktop.cli.failed.title": "Échec de l'installation",
"desktop.cli.failed.message": "Échec de l'installation de la CLI : {{error}}",
"command.project.copyID": "Copier l'ID du projet",
"command.session.copyID": "Copier l'ID de la session",
"server.row.incompatible":
"Ce serveur exécute OpenCode {{version}}, qui n'est pas compatible avec cette application. Mettez-le à niveau vers OpenCode V2 pour continuer.",
"toast.session.copyID.failed.title": "Échec de la copie de l'ID de la session",
"toast.session.copyID.failed.description": "Une erreur s'est produite lors de la copie de l'ID de la session",
"toast.project.copyID.failed.title": "Échec de la copie de l'ID du projet",
"toast.project.copyID.failed.description": "Une erreur s'est produite lors de la copie de l'ID du projet",
"error.page.title.status": "Échec de la requête au serveur",
"error.page.description.status": "Le serveur a renvoyé une réponse HTTP {{status}}.",
"error.page.action.reload": "Recharger",
"session.error.incompatible": "Cette version du serveur n'est pas prise en charge",
"session.error.incompatible.description":
"{{server}} exécute OpenCode {{version}}, qui n'est pas compatible avec cette application. Mettez le serveur à niveau vers OpenCode V2 pour continuer.",
"session.background.moveInline": "Appuyez sur {{keybind}} pour placer la tâche en cours en arrière-plan",
"session.background.running": "Tâche en cours d'exécution en arrière-plan",
"session.background.runningCount.one": "{{count}} tâche en cours d'exécution en arrière-plan",
"session.background.runningCount.other": "{{count}} tâches en cours d'exécution en arrière-plan",
"session.background.runningCount.many": "{{count}} de tâches en cours d'exécution en arrière-plan",
"settings.general.row.terminalPlacement.title": "Position du terminal",
"settings.general.row.terminalPlacement.description": "Choisissez où le terminal s'ouvre dans les sessions",
"settings.general.row.terminalPlacement.side": "Côté",
"settings.general.row.terminalPlacement.bottom": "Bas",
"settings.general.row.showProjectIcon.title": "Icône du projet",
"settings.general.row.showProjectIcon.description": "Afficher l'icône du projet dans l'en-tête de la session",
}
+35 -9
View File
@@ -679,13 +679,6 @@ export const dict = {
"session.error.notFound": "לא ניתן למצוא את ההפעלה הזו",
"session.error.notFound.description": "כרטיסייה זו מצביעה על הפעלה שאינה קיימת עוד בשרת זה.",
"session.error.notFound.closeTab": "סגור כרטיסייה",
"session.background.moveTasks": "העבר את {{tasks}} לרקע",
"session.background.inBackground": "הפעלת {{tasks}} ברקע",
"session.background.combine": "{{first}} ו-{{second}}",
"session.background.shell.one": "{{count}} פקודת מעטפת",
"session.background.shell.other": "{{count}} פקודות מעטפת",
"session.background.subagent.one": "{{count}} סוכן משנה",
"session.background.subagent.other": "{{count}} סוכני משנה",
"command.session.background": "העבר לרקע",
"session.timeline.notice.finished": "ההרצה של {{actor}} הסתיימה",
"session.timeline.notice.failed": "ההרצה של {{actor}} נכשלה",
@@ -1167,6 +1160,39 @@ export const dict = {
"session.question.pending.two": "{{count}} שאלות ממתינות",
"settings.workspaces.count.two": "{{count}} סביבות עבודה",
"settings.workspaces.sessions.two": "{{count}} הפעלות ב-{{project}}",
"session.background.shell.two": "{{count}} פקודות מעטפת",
"session.background.subagent.two": "{{count}} סוכני משנה",
"toast.migration.failed.title": "העברת הנתונים נכשלה",
"provider.disconnect.toast.noCredentials.description":
"לא נמצאו פרטי התחברות שניתן להסיר עבור \u2068{{provider}}\u2069.",
"provider.disconnect.toast.failed.description": "הניתוק של \u2068{{provider}}\u2069 נכשל.",
"command.project.copyID": "העתק מזהה פרויקט",
"command.session.copyID": "העתק מזהה הפעלה",
"server.row.incompatible":
"שרת זה מריץ את OpenCode \u2068{{version}}\u2069, שאינו תואם ליישום זה. יש לשדרג אותו ל-OpenCode V2 כדי להמשיך.",
"toast.session.copyID.failed.title": "העתקת מזהה ההפעלה נכשלה",
"toast.session.copyID.failed.description": "אירעה שגיאה בעת העתקת מזהה ההפעלה",
"toast.project.copyID.failed.title": "העתקת מזהה הפרויקט נכשלה",
"toast.project.copyID.failed.description": "אירעה שגיאה בעת העתקת מזהה הפרויקט",
"error.page.title.status": "בקשת השרת נכשלה",
"error.page.description.status": "השרת החזיר תגובת HTTP במצב \u2068{{status}}\u2069.",
"error.page.action.reload": "טען מחדש",
"session.error.incompatible": "גרסת השרת הזו אינה נתמכת",
"session.error.incompatible.description":
"\u2068{{server}}\u2069 מריץ את OpenCode \u2068{{version}}\u2069, שאינו תואם ליישום זה. יש לשדרג את השרת ל-OpenCode V2 כדי להמשיך.",
"session.background.moveInline": "יש ללחוץ על \u2068{{keybind}}\u2069 כדי להעביר עבודה פעילה לרקע",
"session.background.running": "עבודה מתבצעת ברקע",
"session.background.runningCount.one": "{{count}} פריט פועל ברקע",
"session.background.runningCount.two": "{{count}} פריטים פועלים ברקע",
"session.background.runningCount.other": "{{count}} פריטים פועלים ברקע",
"settings.general.row.terminalPlacement.title": "מיקום המסוף",
"settings.general.row.terminalPlacement.description": "בחר היכן המסוף ייפתח בהפעלות",
"settings.general.row.terminalPlacement.side": "בצד",
"settings.general.row.terminalPlacement.bottom": "למטה",
"settings.general.row.showProjectIcon.title": "סמל הפרויקט",
"settings.general.row.showProjectIcon.description": "הצג את סמל הפרויקט בכותרת ההפעלה",
"desktop.menu.installCli": "התקנת CLI...",
"desktop.cli.installed.title": "CLI הותקן",
"desktop.cli.installed.message":
"CLI הותקן ב-\u2068{{path}}\u2069\n\nיש להפעיל מחדש את המסוף כדי להשתמש בפקודה 'opencode2'.",
"desktop.cli.failed.title": "ההתקנה נכשלה",
"desktop.cli.failed.message": "התקנת CLI נכשלה: \u2068{{error}}\u2069",
}
+111
View File
@@ -1102,4 +1102,115 @@ export const dict = {
"workspace.reset.archived.one": "1 सेशन संग्रहित किया जाएगा।",
"workspace.reset.archived.many": "{{count}} सेशन संग्रहित किए जाएँगे।",
"workspace.reset.note": "यह डिफ़ॉल्ट शाखा से मिलान करने के लिए वर्कस्पेस को रीसेट कर देगा।",
"toast.migration.failed.title": "डेटा माइग्रेशन विफल रहा",
"command.session.background": "बैकग्राउंड में ले जाएँ",
"session.timeline.notice.finished": "{{actor}} ने पूरा किया",
"session.timeline.notice.failed": "{{actor}} विफल रहा",
"session.timeline.notice.cancelled": "{{actor}} रद्द हुआ",
"settings.tab.preferences": "प्राथमिकताएँ",
"settings.tab.notifications": "सूचनाएँ",
"settings.tab.projects": "प्रोजेक्ट",
"settings.tab.extensions": "एक्सटेंशन",
"settings.preferences.description": "प्राथमिकताएँ, थीम और डिफ़ॉल्ट व्यवहार कस्टमाइज़ करें",
"settings.appearance.description": "थीम और फ़ॉन्ट कस्टमाइज़ करें",
"settings.notifications.description": "चुनें कि सूचनाएँ कब मिलें और ध्वनियाँ कब सुनाई दें",
"settings.shortcuts.description": "सामान्य कार्रवाइयों के शॉर्टकट कस्टमाइज़ करें",
"settings.servers.description": "सर्वर कनेक्शन प्रबंधित करें",
"settings.projects.title": "प्रोजेक्ट",
"settings.projects.description": "इस सर्वर पर प्रोजेक्ट सेटिंग्स प्रबंधित करें",
"settings.projects.empty": "कोई प्रोजेक्ट नहीं मिला",
"settings.projects.server.all": "सभी सर्वर",
"settings.mcps.description": "Model Context Protocol (MCP) सर्वर और टूल प्रबंधित करें",
"settings.extensions.description": "इस सर्वर पर उपलब्ध एक्सटेंशन प्रबंधित करें",
"settings.extensions.tab.mcps": "MCPs",
"settings.extensions.tab.skills": "कौशल",
"settings.extensions.availableAll": "सभी प्रोजेक्ट के लिए उपलब्ध",
"settings.extensions.manageConfig": "opencode.json में प्रबंधित करें",
"settings.extensions.addSkills": "कौशल कैसे जोड़ें",
"dialog.server.authenticate.title": "प्रमाणित करें",
"project.settings.general.description": "प्रोजेक्ट का नाम और स्वरूप प्रबंधित करें",
"project.settings.scripts": "स्क्रिप्ट",
"project.settings.scripts.description": "इस प्रोजेक्ट के लिए स्क्रिप्ट कॉन्फ़िगर करें",
"project.settings.extensions.description": "इस प्रोजेक्ट के लिए उपलब्ध एक्सटेंशन देखें",
"project.settings.extensions.tab.lsps": "LSPs",
"project.settings.extensions.added": "इस प्रोजेक्ट में जोड़ा गया",
"project.settings.extensions.shared": "सभी प्रोजेक्ट के साथ साझा",
"project.settings.extensions.lsp.detected": "पहचाने गए भाषा सर्वर",
"project.settings.extensions.lsp.description": "फ़ाइल प्रकारों से स्वतः पहचाने गए",
"project.settings.extensions.setupRequired": "सेटअप आवश्यक है",
"settings.general.section.general": "सामान्य",
"common.viewAll": "सभी देखें",
"session.new.workspace.local.tooltip": "वर्तमान चेकआउट का उपयोग करें",
"session.new.workspace.new.tooltip": "अलग चेकआउट बनाएँ",
"session.new.workspace.fromBranch": "{{branch}} से",
"session.new.workspace.trigger.tooltip": "चुनें कि सेशन कहाँ चलाना है",
"session.new.workspace.search.placeholder": "वर्कस्पेस खोजें",
"settings.tab.workspaces": "वर्कस्पेस",
"settings.workspaces.filter.all": "सभी प्रोजेक्ट",
"settings.workspaces.empty": "कोई वर्कस्पेस नहीं",
"settings.workspaces.count.one": "{{count}} वर्कस्पेस",
"settings.workspaces.count.other": "{{count}} वर्कस्पेस",
"settings.workspaces.sessions.one": "{{project}} में {{count}} सेशन",
"settings.workspaces.sessions.other": "{{project}} में {{count}} सेशन",
"settings.workspaces.lastActiveSession": "अंतिम सक्रिय सेशन",
"settings.workspaces.deleteAll": "सभी वर्कस्पेस हटाएँ",
"settings.workspaces.deleteAll.confirm": "सभी {{count}} वर्कस्पेस हटाएँ?",
"settings.workspaces.delete.warning":
"नीचे दिखाए गए मर्ज न किए गए परिवर्तनों सहित वर्कस्पेस निर्देशिका और शाखा स्थायी रूप से हटा दी जाएगी।",
"settings.workspaces.deleteAll.warning":
"{{project}} में चुने गए {{count}} वर्कस्पेस तभी स्थायी रूप से हटेंगे जब प्रत्येक साफ़, निष्क्रिय और किसी सेशन से जुड़ा न हो।",
"settings.workspaces.delete.blocked.active": "सक्रिय वर्कस्पेस हटाया नहीं जा सकता।",
"settings.workspaces.delete.blocked.linked":
"जुड़े सेशन बने रहेंगे, लेकिन उनकी कार्यशील निर्देशिका स्थायी रूप से हटा दी जाएगी।",
"settings.workspaces.default.title": "डिफ़ॉल्ट परिवेश",
"settings.workspaces.default.description": "चुनें कि नए सेशन कहाँ शुरू हों",
"settings.workspaces.default.lastUsed": "हर प्रोजेक्ट में अंतिम बार उपयोग किया गया",
"settings.workspaces.default.local": "स्थानीय निर्देशिका",
"settings.workspaces.default.new": "नया वर्कस्पेस",
"workspace.move.title": "वर्कस्पेस में ले जाएँ",
"workspace.move.menu.title": "सेशन यहाँ ले जाएँ",
"workspace.move.failed": "सेशन ले जाने में विफल",
"workspace.lifecycle.creating": "वर्कस्पेस बनाया जा रहा है",
"workspace.lifecycle.created": "वर्कस्पेस बनाया गया",
"workspace.lifecycle.starting": "सेशन शुरू हो रहा है",
"workspace.onboarding.title": "वर्कस्पेस से सेशन अलग रखें",
"workspace.onboarding.description":
"हर सेशन को अपना चेकआउट मिलता है, इसलिए आपकी स्थानीय रिपॉजिटरी में कोई हस्तक्षेप नहीं होता",
"workspace.lifecycle.moving": "वर्कस्पेस में ले जाया जा रहा है",
"workspace.lifecycle.set": "वर्कस्पेस सेट किया गया",
"session.summary.title": "सेशन विवरण",
"session.summary.noBranch": "कोई शाखा नहीं",
"session.summary.basedOn": "{{branch}} पर आधारित",
"provider.disconnect.toast.noCredentials.description": "{{provider}} के लिए हटाने योग्य कोई क्रेडेंशियल नहीं मिला।",
"provider.disconnect.toast.failed.description": "{{provider}} से डिस्कनेक्ट करने में विफल।",
"desktop.menu.installCli": "CLI इंस्टॉल करें...",
"desktop.cli.installed.title": "CLI इंस्टॉल हो गया",
"desktop.cli.installed.message":
"CLI को {{path}} पर इंस्टॉल किया गया\n\n'opencode2' कमांड का उपयोग करने के लिए अपना टर्मिनल पुनः आरंभ करें।",
"desktop.cli.failed.title": "इंस्टॉलेशन विफल",
"desktop.cli.failed.message": "CLI इंस्टॉल नहीं हो सका: {{error}}",
"command.project.copyID": "प्रोजेक्ट ID कॉपी करें",
"command.session.copyID": "सेशन ID कॉपी करें",
"server.row.incompatible":
"यह सर्वर OpenCode {{version}} चला रहा है, जो इस ऐप के साथ संगत नहीं है। जारी रखने के लिए इसे OpenCode V2 में अपग्रेड करें।",
"toast.session.copyID.failed.title": "सेशन ID कॉपी करने में विफल",
"toast.session.copyID.failed.description": "सेशन ID कॉपी करते समय एक त्रुटि हुई",
"toast.project.copyID.failed.title": "प्रोजेक्ट ID कॉपी करने में विफल",
"toast.project.copyID.failed.description": "प्रोजेक्ट ID कॉपी करते समय एक त्रुटि हुई",
"error.page.title.status": "सर्वर अनुरोध विफल",
"error.page.description.status": "सर्वर ने HTTP {{status}} प्रतिक्रिया दी।",
"error.page.action.reload": "पुनः लोड करें",
"session.error.incompatible": "यह सर्वर संस्करण समर्थित नहीं है",
"session.error.incompatible.description":
"{{server}} पर OpenCode {{version}} चल रहा है, जो इस ऐप के साथ संगत नहीं है। जारी रखने के लिए सर्वर को OpenCode V2 में अपग्रेड करें।",
"session.background.moveInline": "चल रहे कार्य को बैकग्राउंड में ले जाने के लिए {{keybind}} दबाएँ",
"session.background.running": "कार्य बैकग्राउंड में चल रहा है",
"session.background.runningCount.one": "बैकग्राउंड में {{count}} आइटम चल रहा है",
"session.background.runningCount.other": "बैकग्राउंड में {{count}} आइटम चल रहे हैं",
"settings.general.row.terminalPlacement.title": "टर्मिनल का स्थान",
"settings.general.row.terminalPlacement.description": "चुनें कि सेशन में टर्मिनल कहाँ खुले",
"settings.general.row.terminalPlacement.side": "किनारे",
"settings.general.row.terminalPlacement.bottom": "नीचे",
"settings.general.row.showProjectIcon.title": "प्रोजेक्ट आइकन",
"settings.general.row.showProjectIcon.description": "सेशन हेडर में प्रोजेक्ट आइकन दिखाएँ",
}
+112
View File
@@ -9,6 +9,7 @@ const desktop = [
"Prozor",
"Pomoć",
"Provjeri ima li ažuriranja...",
"Instaliraj CLI...",
"Postavke",
"Ponovno učitaj Webview",
"Ponovno pokreni",
@@ -54,6 +55,10 @@ const desktop = [
"Ažuriranje je spremno",
"Ponovno pokreni",
"Kasnije",
"CLI je instaliran",
"CLI je instaliran u {{path}}\n\nPonovno pokrenite terminal kako biste mogli koristiti naredbu 'opencode2'.",
"Instalacija nije uspjela",
"Instalacija CLI-ja nije uspjela: {{error}}",
"Ponovno pokreni aplikaciju",
"Izvezi zapisnike",
"Nastavi čekati",
@@ -1106,4 +1111,111 @@ export const dict = {
"workspace.reset.archived.many": "{{count}} sesije će biti arhivirane.",
"workspace.reset.note": "Ovo će resetirati radni prostor kako bi odgovarao zadanoj grani.",
"dialog.usageExceeded.dontShowAgain": "Ne prikazuj ponovno",
"provider.disconnect.toast.noCredentials.description": "Nisu pronađene uklonjive vjerodajnice za {{provider}}.",
"provider.disconnect.toast.failed.description": "Odspajanje {{provider}} nije uspjelo.",
"toast.migration.failed.title": "Migracija podataka nije uspjela",
"command.session.background": "Premjesti u pozadinu",
"session.timeline.notice.finished": "{{actor}} završio",
"session.timeline.notice.failed": "{{actor}} nije uspio",
"session.timeline.notice.cancelled": "{{actor}} otkazan",
"settings.tab.preferences": "Postavke",
"settings.tab.notifications": "Obavijesti",
"settings.tab.projects": "Projekti",
"settings.tab.extensions": "Ekstenzije",
"settings.preferences.description": "Prilagodite postavke i temu i zadano ponašanje",
"settings.appearance.description": "Prilagodite temu i fontove",
"settings.notifications.description": "Odaberite kada želite primati obavijesti i čuti zvukove",
"settings.shortcuts.description": "Prilagodite prečace za uobičajene radnje",
"settings.servers.description": "Upravljanje vezama poslužitelja",
"settings.projects.title": "Projekti",
"settings.projects.description": "Upravljanje postavkama projekta na ovom poslužitelju",
"settings.projects.empty": "Nema pronađenih projekata",
"settings.projects.server.all": "Svi poslužitelji",
"settings.mcps.description": "Upravljanje poslužiteljima i alatima Model Context Protocol (MCP).",
"settings.extensions.description": "Upravljajte proširenjima dostupnim na ovom poslužitelju",
"settings.extensions.tab.mcps": "MCP-ovi",
"settings.extensions.tab.skills": "Vještine",
"settings.extensions.availableAll": "Dostupno za sve projekte",
"settings.extensions.manageConfig": "Upravljanje u opencode.json",
"settings.extensions.addSkills": "Kako dodati vještine",
"dialog.server.authenticate.title": "Autentificirati",
"project.settings.general.description": "Upravljanje imenom i izgledom projekta",
"project.settings.scripts": "Skripte",
"project.settings.scripts.description": "Konfigurirajte skripte za ovaj projekt",
"project.settings.extensions.description": "Pogledajte proširenja dostupna za ovaj projekt",
"project.settings.extensions.tab.lsps": "LSP-ovi",
"project.settings.extensions.added": "Dodano ovom projektu",
"project.settings.extensions.shared": "Dijeli se sa svim projektima",
"project.settings.extensions.lsp.detected": "Otkriveni jezični poslužitelji",
"project.settings.extensions.lsp.description": "Automatski otkriveno iz vrsta datoteka",
"project.settings.extensions.setupRequired": "Potrebna postavka",
"settings.general.section.general": "Općenito",
"common.viewAll": "Pogledaj sve",
"session.new.workspace.local.tooltip": "Upotrijebi trenutačnu radnu kopiju",
"session.new.workspace.new.tooltip": "Stvori izoliranu radnu kopiju",
"session.new.workspace.fromBranch": "od {{branch}}",
"session.new.workspace.trigger.tooltip": "Odaberite gdje želite pokrenuti sesiju",
"session.new.workspace.search.placeholder": "Pretražite radne prostore",
"settings.tab.workspaces": "Radni prostori",
"settings.workspaces.filter.all": "Svi projekti",
"settings.workspaces.empty": "Nema radnih prostora",
"settings.workspaces.count.one": "{{count}} radni prostor",
"settings.workspaces.count.other": "{{count}} radni prostori",
"settings.workspaces.sessions.one": "{{count}} sesija u {{project}}",
"settings.workspaces.sessions.other": "{{count}} sesije u {{project}}",
"settings.workspaces.lastActiveSession": "Zadnja aktivna sesija",
"settings.workspaces.deleteAll": "Izbriši sve radne prostore",
"settings.workspaces.deleteAll.confirm": "Izbrisati sve radne prostore {{count}}?",
"settings.workspaces.delete.warning":
"Direktorij i grana radnog prostora bit će trajno uklonjeni, uključujući sve nespojene promjene prikazane u nastavku.",
"settings.workspaces.deleteAll.warning":
"{{count}} odabrani radni prostori u {{project}} bit će trajno uklonjeni samo ako je svaki čist, neaktivan i nema povezanih sesija.",
"settings.workspaces.delete.blocked.active": "Aktivni radni prostor ne može se izbrisati.",
"settings.workspaces.delete.blocked.linked":
"Povezane sesije će ostati, ali će njihov radni direktorij biti trajno uklonjen.",
"settings.workspaces.default.title": "Zadano okruženje",
"settings.workspaces.default.description": "Odaberite gdje počinju nove sesije",
"settings.workspaces.default.lastUsed": "Zadnje korišteno po projektu",
"settings.workspaces.default.local": "Lokalni imenik",
"settings.workspaces.default.new": "Novi radni prostor",
"workspace.move.title": "Prijeđite na radni prostor",
"workspace.move.menu.title": "Premjesti sesiju na",
"workspace.move.failed": "Premještanje sesije nije uspjelo",
"workspace.lifecycle.creating": "Stvaranje radnog prostora",
"workspace.lifecycle.created": "Radni prostor stvoren",
"workspace.lifecycle.starting": "Početak sesije",
"workspace.onboarding.title": "Izolirajte sesije s radnim prostorima",
"workspace.onboarding.description": "Svaka dobiva vlastitu radnu kopiju, tako da ništa ne ometa lokalni repozitorij",
"workspace.lifecycle.moving": "Prelazak na radni prostor",
"workspace.lifecycle.set": "Radni prostor postavljen",
"session.summary.title": "Detalji sesije",
"session.summary.noBranch": "Nema grane",
"session.summary.basedOn": "Na temelju {{branch}}",
"settings.workspaces.count.few": "{{count}} radna prostora",
"settings.workspaces.sessions.few": "{{count}} sesije u {{project}}",
"command.project.copyID": "Kopiraj ID projekta",
"command.session.copyID": "Kopiraj ID sesije",
"server.row.incompatible":
"Ovaj poslužitelj koristi OpenCode {{version}}, koji nije kompatibilan s ovom aplikacijom. Nadogradite ga na OpenCode V2 kako biste nastavili.",
"toast.session.copyID.failed.title": "Kopiranje ID-a sesije nije uspjelo",
"toast.session.copyID.failed.description": "Došlo je do pogreške pri kopiranju ID-a sesije",
"toast.project.copyID.failed.title": "Kopiranje ID-a projekta nije uspjelo",
"toast.project.copyID.failed.description": "Došlo je do pogreške pri kopiranju ID-a projekta",
"error.page.title.status": "Zahtjev poslužitelju nije uspio",
"error.page.description.status": "Poslužitelj je vratio HTTP odgovor {{status}}.",
"error.page.action.reload": "Ponovno učitaj",
"session.error.incompatible": "Ova verzija poslužitelja nije podržana",
"session.error.incompatible.description":
"{{server}} koristi OpenCode {{version}}, koji nije kompatibilan s ovom aplikacijom. Nadogradite poslužitelj na OpenCode V2 kako biste nastavili.",
"session.background.moveInline": "Pritisnite {{keybind}} za premještanje aktivnog rada u pozadinu",
"session.background.running": "Rad se izvršava u pozadini",
"session.background.runningCount.one": "{{count}} stavka izvršava se u pozadini",
"session.background.runningCount.few": "{{count}} stavke izvršavaju se u pozadini",
"session.background.runningCount.other": "{{count}} stavki izvršava se u pozadini",
"settings.general.row.terminalPlacement.title": "Položaj terminala",
"settings.general.row.terminalPlacement.description": "Odaberite gdje se terminal otvara u sesijama",
"settings.general.row.terminalPlacement.side": "Sa strane",
"settings.general.row.terminalPlacement.bottom": "Pri dnu",
"settings.general.row.showProjectIcon.title": "Ikona projekta",
"settings.general.row.showProjectIcon.description": "Prikaži ikonu projekta u zaglavlju sesije",
}
+110
View File
@@ -9,6 +9,7 @@ const desktop = [
"Ablak",
"Súgó",
"Frissítések keresése...",
"CLI telepítése...",
"Beállítások",
"Webview újratöltése",
"Újraindítás",
@@ -54,6 +55,10 @@ const desktop = [
"A frissítés készen áll",
"Újraindítás",
"Később",
"CLI telepítve",
"A CLI telepítve ide: {{path}}\n\nIndítsa újra a terminált az 'opencode2' parancs használatához.",
"A telepítés nem sikerült",
"Nem sikerült telepíteni a CLI-t: {{error}}",
"Újraindítás",
"Naplók exportálása",
"Várakozás folytatása",
@@ -1084,6 +1089,47 @@ export const dict = {
"session.delete.confirm": "Delete „{{name}}” munkamenet?",
"session.delete.button": "Delete munkamenet",
"workspace.new": "Új munkaterület",
"common.viewAll": "Összes megtekintése",
"session.new.workspace.local.tooltip": "A jelenlegi checkout használata",
"session.new.workspace.new.tooltip": "Elkülönített checkout létrehozása",
"session.new.workspace.fromBranch": "innen: {{branch}}",
"session.new.workspace.trigger.tooltip": "Válassza ki, hol fusson a munkamenet",
"session.new.workspace.search.placeholder": "Munkaterületek keresése",
"settings.tab.workspaces": "Munkaterületek",
"settings.workspaces.filter.all": "Minden projekt",
"settings.workspaces.empty": "Nincsenek munkaterületek",
"settings.workspaces.count.one": "{{count}} munkaterület",
"settings.workspaces.count.other": "{{count}} munkaterület",
"settings.workspaces.sessions.one": "{{count}} munkamenet a(z) {{project}} projektben",
"settings.workspaces.sessions.other": "{{count}} munkamenet a(z) {{project}} projektben",
"settings.workspaces.lastActiveSession": "Legutóbbi aktív munkamenet",
"settings.workspaces.deleteAll": "Összes munkaterület törlése",
"settings.workspaces.deleteAll.confirm": "Törli mind a(z) {{count}} munkaterületet?",
"settings.workspaces.delete.warning":
"A munkaterület könyvtára és ága véglegesen törlődik, beleértve az alább látható nem egyesített módosításokat is.",
"settings.workspaces.deleteAll.warning":
"A(z) {{project}} projekt {{count}} kijelölt munkaterülete csak akkor törlődik véglegesen, ha mindegyik tiszta, inaktív, és nincs hozzá kapcsolt munkamenet.",
"settings.workspaces.delete.blocked.active": "Az aktív munkaterület nem törölhető.",
"settings.workspaces.delete.blocked.linked":
"A kapcsolt munkamenetek megmaradnak, de a munkakönyvtáruk véglegesen törlődik.",
"settings.workspaces.default.title": "Alapértelmezett környezet",
"settings.workspaces.default.description": "Válassza ki, hol induljanak az új munkamenetek",
"settings.workspaces.default.lastUsed": "Projektenként legutóbb használt",
"settings.workspaces.default.local": "Helyi könyvtár",
"settings.workspaces.default.new": "Új munkaterület",
"workspace.move.title": "Áthelyezés munkaterületre",
"workspace.move.menu.title": "Munkamenet áthelyezése ide:",
"workspace.move.failed": "Nem sikerült áthelyezni a munkamenetet",
"workspace.lifecycle.creating": "Munkaterület létrehozása",
"workspace.lifecycle.created": "Munkaterület létrehozva",
"workspace.lifecycle.starting": "Munkamenet indítása",
"workspace.onboarding.title": "Munkamenetek elkülönítése munkaterületekkel",
"workspace.onboarding.description": "Mindegyik saját checkoutot kap, így semmi sem zavarja a helyi adattárat",
"workspace.lifecycle.moving": "Áthelyezés munkaterületre",
"workspace.lifecycle.set": "Munkaterület beállítva",
"session.summary.title": "Munkamenet részletei",
"session.summary.noBranch": "Nincs ág",
"session.summary.basedOn": "Alapja: {{branch}}",
"workspace.type.local": "helyi",
"workspace.type.sandbox": "homokozó",
"workspace.create.failed.title": "Nem sikerült létrehozni a munkaterületet",
@@ -1109,4 +1155,68 @@ export const dict = {
"workspace.reset.archived.many": "A {{count}} munkamenetek archiválva lesznek.",
"workspace.reset.note": "Ezzel visszaállítja a munkaterületet, hogy megfeleljen az alapértelmezett ágnak.",
"dialog.usageExceeded.dontShowAgain": "Ne jelenjen meg újra",
"provider.disconnect.toast.noCredentials.description":
"Nem találhatók eltávolítható hitelesítő adatok a következőhöz: {{provider}}.",
"provider.disconnect.toast.failed.description": "Nem sikerült leválasztani a következőt: {{provider}}.",
"toast.migration.failed.title": "Az adatok áttelepítése nem sikerült",
"command.session.background": "Áthelyezés a háttérbe",
"session.timeline.notice.finished": "{{actor}} befejezte",
"session.timeline.notice.failed": "{{actor}} művelete sikertelen",
"session.timeline.notice.cancelled": "{{actor}} művelete megszakítva",
"settings.tab.preferences": "Beállítások",
"settings.tab.notifications": "Értesítések",
"settings.tab.projects": "Projektek",
"settings.tab.extensions": "Bővítmények",
"settings.preferences.description": "A beállítások, a téma és az alapértelmezett viselkedés testreszabása",
"settings.appearance.description": "A téma és a betűtípusok testreszabása",
"settings.notifications.description": "Válassza ki, mikor kapjon értesítéseket és halljon hangokat",
"settings.shortcuts.description": "A gyakori műveletek billentyűparancsainak testreszabása",
"settings.servers.description": "Szerverkapcsolatok kezelése",
"settings.projects.title": "Projektek",
"settings.projects.description": "A projektek beállításainak kezelése ezen a szerveren",
"settings.projects.empty": "Nem találhatók projektek",
"settings.projects.server.all": "Minden szerver",
"settings.mcps.description": "A Model Context Protocol (MCP) szervereinek és eszközeinek kezelése",
"settings.extensions.description": "A szerveren elérhető bővítmények kezelése",
"settings.extensions.tab.mcps": "MCP-k",
"settings.extensions.tab.skills": "Készségek",
"settings.extensions.availableAll": "Minden projekt számára elérhető",
"settings.extensions.manageConfig": "Kezelés az opencode.json fájlban",
"settings.extensions.addSkills": "Készségek hozzáadása",
"dialog.server.authenticate.title": "Hitelesítés",
"project.settings.general.description": "A projekt nevének és megjelenésének kezelése",
"project.settings.scripts": "Szkriptek",
"project.settings.scripts.description": "Szkriptek beállítása ehhez a projekthez",
"project.settings.extensions.description": "A projekthez elérhető bővítmények megtekintése",
"project.settings.extensions.tab.lsps": "LSP-k",
"project.settings.extensions.added": "Hozzáadva ehhez a projekthez",
"project.settings.extensions.shared": "Minden projekttel megosztva",
"project.settings.extensions.lsp.detected": "Észlelt nyelvi szerverek",
"project.settings.extensions.lsp.description": "Automatikusan észlelve a fájltípusok alapján",
"project.settings.extensions.setupRequired": "Beállítás szükséges",
"settings.general.section.general": "Általános",
"command.project.copyID": "Projektazonosító másolása",
"command.session.copyID": "Munkamenet-azonosító másolása",
"server.row.incompatible":
"Ez a kiszolgáló az OpenCode {{version}} verzióját futtatja, amely nem kompatibilis ezzel az alkalmazással. A folytatáshoz frissítse OpenCode V2-re.",
"toast.session.copyID.failed.title": "Nem sikerült másolni a munkamenet-azonosítót",
"toast.session.copyID.failed.description": "Hiba történt a munkamenet-azonosító másolásakor",
"toast.project.copyID.failed.title": "Nem sikerült másolni a projektazonosítót",
"toast.project.copyID.failed.description": "Hiba történt a projektazonosító másolásakor",
"error.page.title.status": "A kiszolgálói kérés sikertelen",
"error.page.description.status": "A kiszolgáló HTTP {{status}} választ adott.",
"error.page.action.reload": "Újratöltés",
"session.error.incompatible": "Ez a kiszolgálóverzió nem támogatott",
"session.error.incompatible.description":
"A(z) {{server}} az OpenCode {{version}} verzióját futtatja, amely nem kompatibilis ezzel az alkalmazással. A folytatáshoz frissítse a kiszolgálót OpenCode V2-re.",
"session.background.moveInline": "Nyomja meg a(z) {{keybind}} billentyűt a futó munka háttérbe helyezéséhez",
"session.background.running": "Munka fut a háttérben",
"session.background.runningCount.one": "{{count}} elem fut a háttérben",
"session.background.runningCount.other": "{{count}} elem fut a háttérben",
"settings.general.row.terminalPlacement.title": "Terminál elhelyezése",
"settings.general.row.terminalPlacement.description": "Válassza ki, hol nyíljon meg a terminál a munkamenetekben",
"settings.general.row.terminalPlacement.side": "Oldalt",
"settings.general.row.terminalPlacement.bottom": "Alul",
"settings.general.row.showProjectIcon.title": "Projektikon",
"settings.general.row.showProjectIcon.description": "A projektikon megjelenítése a munkamenet fejlécében",
}

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