Compare commits

...

401 Commits

Author SHA1 Message Date
Dax Raad
fcd5ff7ebe sync 2026-01-24 00:04:37 -05:00
Dax Raad
c2e234ec4d sync 2026-01-24 00:04:10 -05:00
Dax Raad
38f735bfc6 sync 2026-01-24 00:02:32 -05:00
Dax Raad
a4183c3b2c sync 2026-01-23 23:57:20 -05:00
Dax Raad
2c234b8d62 core: migrate project table from JSON to structured columns for better query performance 2026-01-23 23:55:18 -05:00
Github Action
9f96d8aa78 Update aarch64-darwin hash 2026-01-23 23:54:07 -05:00
Github Action
4007e57c52 Update Nix flake.lock and x86_64-linux hash 2026-01-23 23:53:59 -05:00
Dax Raad
d472512eba core: consolidate session-related SQL tables into single file 2026-01-23 23:53:29 -05:00
Dax Raad
f6b28b61c7 core: fix message ordering and add custom storage dir support for migration 2026-01-23 23:53:29 -05:00
Github Action
0bf9d66da5 Update aarch64-darwin hash 2026-01-23 23:53:28 -05:00
Github Action
eabd78cab6 Update Nix flake.lock and x86_64-linux hash 2026-01-23 23:52:47 -05:00
Dax Raad
7bc8851fc4 commit 2026-01-23 23:51:53 -05:00
Frank
af5e405391 zen: remove grok code model 2026-01-23 23:25:34 -05:00
Alex Yaroshuk
8a216a6ad5 fix(app): normalize path separators for session diff filtering on Windows (#10291) 2026-01-23 16:17:47 -06:00
Ariane Emory
225b72ca36 feat: always center selected item in selection dialogs (resolves #10209) (#10207) 2026-01-23 11:59:39 -06:00
Rahul A Mistry
8105f186dc fix(app): center checkbox indicator in provider selection (#10267) 2026-01-23 10:23:24 -06:00
GitHub Action
4f1bdf1c59 chore: generate 2026-01-23 16:22:07 +00:00
Frank
472695caca zen: fix balance not shown 2026-01-23 11:21:08 -05:00
GitHub Action
469fd43c71 chore: generate 2026-01-23 15:59:00 +00:00
Frank
24d942349f zen: use balance after rate limited 2026-01-23 10:58:00 -05:00
Edin
65c236c071 feat(app): auto-open oauth links for codex and copilot (#10258) 2026-01-23 09:35:44 -06:00
GitHub Action
d6c5ddd6dc ignore: update download stats 2026-01-23 2026-01-23 12:05:37 +00:00
Adam
e5fe50f7da fix(app): close delete workspace dialog immediately 2026-01-23 05:41:51 -06:00
Adam
b6beda1569 fix: type error 2026-01-23 05:32:37 -06:00
GitHub Action
f34b509fe7 chore: generate 2026-01-23 11:19:35 +00:00
Adam
2a2d800ac4 fix: type error 2026-01-23 05:18:57 -06:00
Adam
4afb46f571 perf(app): don't remount directory layout 2026-01-23 05:18:57 -06:00
Adam
c4d223eb99 perf(app): faster workspace creation 2026-01-23 05:18:42 -06:00
GitHub Action
3fbda54045 chore: generate 2026-01-23 11:10:22 +00:00
Shantur Rathore
41ede06b20 docs(ecosystem): Add CodeNomad entry to ecosystem documentation (#10222) 2026-01-23 05:09:38 -06:00
Adam
82ec84982e Reapply "wip(app): line selection"
This reverts commit df7b6792cd.
2026-01-23 05:01:10 -06:00
Adam
df7b6792cd Revert "wip(app): line selection"
This reverts commit 1780bab1ce.
2026-01-23 04:58:41 -06:00
Devin Griffin
c72d9a473c fix(app): View all sessions flakiness (#10149) 2026-01-23 04:57:10 -06:00
GitHub Action
d3688b150a chore: generate 2026-01-23 10:55:37 +00:00
Rahul A Mistry
e376e1de16 fix(app): enable dialog dismiss on model selector (dialog.tsx) (#10203) 2026-01-23 04:55:00 -06:00
opencode
c130dd425a release: v1.1.34 2026-01-23 07:27:35 +00:00
Eric Guo
b298982268 fix(desktop): Fixed a reactive feedback loop in the global project cache sync (#10139) 2026-01-23 15:23:06 +08:00
Frank
47a2b9e8df zen: glm 4.7 2026-01-23 01:21:41 -05:00
GitHub Action
213b823c69 chore: generate 2026-01-23 05:28:47 +00:00
Frank
c0dc8ea39e wip: zen black 2026-01-23 00:27:54 -05:00
GitHub Action
077ebdbfda chore: generate 2026-01-23 04:12:51 +00:00
Adam
1780bab1ce wip(app): line selection 2026-01-22 22:12:12 -06:00
Adam
d35fabf5db chore: cleanup 2026-01-22 22:12:12 -06:00
Adam
82f718b3cf wip(app): line selection 2026-01-22 22:12:12 -06:00
Adam
0eb523631d wip(app): line selection 2026-01-22 22:12:12 -06:00
Adam
99e15caaf6 wip(app): line selection 2026-01-22 22:12:12 -06:00
Adam
1e1872aada wip(app): line selection 2026-01-22 22:12:12 -06:00
Adam
cb481d9ac8 wip(app): line selection 2026-01-22 22:12:12 -06:00
Adam
0ce0cacb28 wip(app): line selection 2026-01-22 22:12:12 -06:00
Adam
640d1f1ecc wip(app): line selection 2026-01-22 22:12:12 -06:00
opencode
2e53697da0 release: v1.1.33 2026-01-23 02:38:33 +00:00
Adam
71cd59932e fix(app): session shouldn't be keyed 2026-01-22 20:28:10 -06:00
Adam
14db336e3a fix(app): flash of fallback icon for projects 2026-01-22 20:17:50 -06:00
Adam
2b9b98e9c2 fix(app): project icon color flash on load 2026-01-22 20:17:50 -06:00
Adam
07015aae07 fix(app): folder suggestions missing last part 2026-01-22 20:17:50 -06:00
Adam
972cb01d5c fix(app): allow adding projects from any depth 2026-01-22 20:09:18 -06:00
Adam
a8018dcc43 fix(app): allow adding projects from root 2026-01-22 20:09:18 -06:00
zerone0x
31094cd5a4 fix(provider): add thinking presets for Google Vertex Anthropic (#9953)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-22 19:44:18 -06:00
Adam
bcf7a65e36 fix(app): non-git projects should be renameable 2026-01-22 18:07:57 -06:00
Github Action
7c80ac072b chore: update nix node_modules hashes 2026-01-22 22:29:41 +00:00
Vladimir Glafirov
515391e9c7 feat(gitlab): Added support for OpenAI based GitLab Duo models (#10108) 2026-01-22 16:26:25 -06:00
Idris Gadi
510f595e25 fix(tui): add weight to fuzzy search to maintain title priority (#10106) 2026-01-22 16:15:11 -06:00
Frank
1b244bf850 wip: zen black 2026-01-22 17:06:47 -05:00
GitHub Action
c128579cfc chore: generate 2026-01-22 22:04:55 +00:00
Frank
5f3ab9395f wip: zen black 2026-01-22 17:02:46 -05:00
Alex Yaroshuk
fdac21688c feat(app): add app version display to settings (#10095) 2026-01-22 14:41:29 -06:00
GitHub Action
dd5a601eda chore: generate 2026-01-22 19:43:50 +00:00
iltenahmet
3eaf6f3baf fix(ui): show file path in apply_patch request permission screen (#10079) 2026-01-22 13:43:11 -06:00
opencode
71ef43f9a0 release: v1.1.32 2026-01-22 19:22:26 +00:00
Greg Pstrucha
8ebb766470 fix(attach): allow remote --dir (#8969) 2026-01-22 13:19:08 -06:00
Adam
46de1ed3b6 fix(app): windows path handling issues 2026-01-22 12:41:02 -06:00
Alex Yaroshuk
3c7d5174b3 fix(ui): prevent copy buttons from stealing focus from prompt input (#10084) 2026-01-22 12:37:13 -06:00
Ygor Simões
32f72f49a8 feat(i18n): add br locale support (#10086) 2026-01-22 12:36:33 -06:00
Shubh Porwal
923e3da973 feat(ui): add aura theme (#10056) 2026-01-22 12:28:41 -06:00
GitHub Action
c96c25a72c chore: generate 2026-01-22 18:24:16 +00:00
Aryan "LAG" Gupta
cda7d3dd78 fix: make 'Learn More' link functional in theme settings (#10078) 2026-01-22 12:23:29 -06:00
Adam
9802ceb94f chore: cleanup 2026-01-22 12:22:10 -06:00
Adam
62115832f5 feat(app): render audio players in session review 2026-01-22 12:22:10 -06:00
Adam
496bbd70f4 feat(app): render images in session review 2026-01-22 12:22:10 -06:00
Adam
93044cc7d1 test(app): fix windows paths 2026-01-22 12:19:57 -06:00
GitHub Action
5a4eec5b08 chore: generate 2026-01-22 18:17:10 +00:00
Frank
e17b875641 zen: cancel waitlist 2026-01-22 13:02:28 -05:00
Frank
a890d51bbc wip: zen black 2026-01-22 13:02:28 -05:00
Adam
bb582416f2 chore: cleanup 2026-01-22 11:21:14 -06:00
Adam
b8526eca67 chore: cleanup 2026-01-22 11:19:53 -06:00
Adam
9c45746bd2 fix(app): new session button 2026-01-22 11:17:55 -06:00
Adam
c4971e48c4 chore(app): translations 2026-01-22 11:06:51 -06:00
Adam
de6582b38b feat(app): delete sessions 2026-01-22 11:06:51 -06:00
Adam
fc53abe589 feat(app): close projects from hover card 2026-01-22 11:03:49 -06:00
Adam
7b23bf7c1b fix(app): don't auto nav to workspace after reset 2026-01-22 10:57:43 -06:00
Adam
c0d3dd51b1 chore: upload playwright assets on test failure 2026-01-22 10:45:06 -06:00
Adam
a96f3d153b Revert "fix: handle special characters in paths and git snapshot reading logic(#9804) (#9807)"
This reverts commit cf6ad4c407.
2026-01-22 10:37:13 -06:00
Adam
31f3a508dc Revert "fix(core): snapshot regression"
This reverts commit bb710e9ea1.
2026-01-22 10:35:31 -06:00
Aiden Cline
3b7c347b2e tweak: bash tool, ensure cat will trigger external_directory perm 2026-01-22 10:26:06 -06:00
Aiden Cline
2e09d7d835 ignore: git ignore the lockfiles for .opencode 2026-01-22 10:06:39 -06:00
karta0807913
29cebd73e5 feat(mcp log): print mcp stderr to opencode log file (#9982)
Co-authored-by: chuxuan.liang <chuxuan.liang@bytedance.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-22 10:02:26 -06:00
Cas
e4286ae7a3 fix(codex): write refresh tokens to openai auth (#10010) (#10011) 2026-01-22 09:53:09 -06:00
bewareoftheleopard
c3f393bcc1 fix(desktop): Expand font stacks to include macOS Nerd Font default names (#10045) 2026-01-22 08:56:19 -06:00
Ryan Miville
9aa54fd71b fix(app): support ctrl-n/p in lists (#10036) 2026-01-22 08:33:35 -06:00
Yash Rathore
e85b953087 fix(app): clear session hover state on navigation (#10031) 2026-01-22 08:32:13 -06:00
Brendan Allan
b776ba6b76 fix(desktop): correct NO_PROXY syntax 2026-01-22 22:18:08 +08:00
Brendan Allan
224b2c37d7 fix(desktop): attempt to improve connection reliability 2026-01-22 22:06:28 +08:00
GitHub Action
16a8f5a9c3 chore: generate 2026-01-22 13:43:36 +00:00
Adam
16fad51b5e feat(app): add workspace startup script to projects 2026-01-22 07:42:56 -06:00
Adam
287511c9b1 test(app): terminal smoke test 2026-01-22 07:34:44 -06:00
Adam
0a678eeacc test(app): file viewer smoke test 2026-01-22 07:34:44 -06:00
Adam
c031139b89 test(app): model picker smoke test 2026-01-22 07:34:44 -06:00
Adam
710dc4fa94 test(app): @ attachment smoke test 2026-01-22 07:34:44 -06:00
Adam
ec53a7962e test(app): slash command smoke tests 2026-01-22 07:34:44 -06:00
Adam
6f7d710129 test(app): settings smoke tests 2026-01-22 07:34:44 -06:00
Adam
513a8a3d26 test(app): smoke tests spec 2026-01-22 07:34:44 -06:00
Adam
c41c9a366f fix: type error 2026-01-22 07:24:13 -06:00
Adam
4385f03053 fix: satisfies 2026-01-22 07:19:41 -06:00
Adam
8e3b459d77 fix(app): hover-card scrolling 2026-01-22 07:16:02 -06:00
Adam
3807523f49 fix(app): auto-scroll 2026-01-22 07:16:02 -06:00
Adam
09997bb6c8 fix(app): auto-scroll 2026-01-22 07:16:01 -06:00
Alex Yaroshuk
aa17729008 feat(app): add scrollbar styling to session page (#10020) 2026-01-22 06:53:55 -06:00
GitHub Action
b59f3e6811 chore: generate 2026-01-22 12:47:58 +00:00
Sondre
8427f40e8d feat: Add support for Norwegian translations (#10018) 2026-01-22 06:47:19 -06:00
GitHub Action
e9c6a4a2d4 chore: generate 2026-01-22 12:30:20 +00:00
Adam
fb007d6bab feat(app): copy buttons for assistant messages and code blocks 2026-01-22 06:29:38 -06:00
GitHub Action
4ca088ed12 ignore: update download stats 2026-01-22 2026-01-22 12:05:29 +00:00
Adam
ae2693425e fix(app): snap to bottom on prompt 2026-01-22 05:45:53 -06:00
Adam
d9b9485019 fix(app): a11y translations 2026-01-22 05:36:38 -06:00
Rahul A Mistry
366da595af fix(desktop): change project path tooltip position to bottom (#9497) 2026-01-22 05:23:37 -06:00
Ariane Emory
fb3d8e83c5 chore: add plans directory to .opencode gitignore (resolves #10005) (#10006) 2026-01-22 05:19:13 -06:00
GitHub Action
d14735ef4b chore: generate 2026-01-22 11:11:32 +00:00
Nolan Darilek
3435327bc0 fix(app): session screen accessibility improvements (#9907) 2026-01-22 05:10:53 -06:00
Adam
8a043edfd5 chore: update website stats 2026-01-22 04:53:12 -06:00
GitHub Action
de07cf26e8 chore: generate 2026-01-22 10:49:25 +00:00
Shoubhit Dash
c737776958 refactor(desktop): move markdown rendering to rust (#10000) 2026-01-22 04:48:39 -06:00
David Hill
7b0ad87781 fix: add 8px left margin to sidebar toggle on desktop 2026-01-22 09:50:13 +00:00
David Hill
3b92d5c1c6 fix: match terminal toggle button size with sidebar and review toggles 2026-01-22 09:48:10 +00:00
David Hill
cf1fc02d27 update jump to latest button with circular design and animation
- add arrow-down-to-line icon
- circular 32px button centered above prompt input
- fade/scale/translate animation on show/hide
- fix duplicate language.ru keys in i18n files
2026-01-22 09:41:28 +00:00
NourEldin Osama
ba2e35e29c feat(i18n): add Arabic language support (#9947) 2026-01-22 02:14:01 -06:00
DNGriffin
9afc067152 feat(app): always show Toggle-Review button (#9944) 2026-01-22 02:09:55 -06:00
Idris Gadi
9fc182baf2 docs: add API server section in CONTRIBUTING.md (#9888) 2026-01-22 00:36:57 -06:00
Aiden Cline
c2844697f3 fix: ensure images are properly returned as tool results 2026-01-21 23:54:44 -06:00
Alex Sadleir
fc0210c2fd fix(acp): rename setSessionModel to unstable_setSessionModel (#9940) 2026-01-21 22:11:09 -06:00
Caleb Norton
f1df6f2d18 chore: update flake.lock (#9938) 2026-01-21 22:10:55 -06:00
luo jiyin
c3415b79fe fix: correct spelling 'supercedes' to 'supersedes' (#9935)
Signed-off-by: luojiyin <luojiyin@hotmail.com>
2026-01-21 22:10:40 -06:00
Ronan Kearns
af1e2887bd fix(app): open terminal pane when creating new terminal (#9926) 2026-01-21 21:09:08 -06:00
dpuyosa
65e267ed3a feat: Add promptCacheKey for Venice provider (#9915) 2026-01-21 20:28:04 -06:00
Daniel Rodriguez
6d574549bc fix: include _noop tool in activeTools for LiteLLM proxy compatibility (#9912) 2026-01-21 18:46:41 -06:00
GitHub Action
f7c5b62ba3 chore: generate 2026-01-22 00:22:40 +00:00
Ryan Vogel
8c230fee62 fix: scope PR recap to only PRs from today (#9905) 2026-01-22 00:22:10 +00:00
opencode
59ceca3e51 release: v1.1.31 2026-01-22 00:22:10 +00:00
Adam
877b0412c9 fix(app): use message diffs, not session diffs 2026-01-21 18:18:57 -06:00
Ryan Vogel
a0d71bf8ef feat: add daily Discord recaps for issues and PRs (#9904) 2026-01-21 18:35:22 -05:00
Aiden Cline
19fe3e265a mark subagent sessions as agent initiated to ensure they dont count against quota (got the ok from copilot team) 2026-01-21 16:33:43 -06:00
Frank
20b6cc279f zen: show subscription usage in graph 2026-01-21 17:21:34 -05:00
Frank
80c808d186 zen: show subscription usage in usage history 2026-01-21 17:21:34 -05:00
Frank
a132b2a138 Zen: disable autoreload by default 2026-01-21 17:21:34 -05:00
Suad Wolgram
936f3ebe95 feat(ui): add gruvbox theme (Web/App) (#9855) 2026-01-21 15:34:27 -06:00
Alex Yaroshuk
23daac2170 feat(i18n): add Traditional Chinese language support & rename 'Chinese' to 'Chinese (Simplified)' (#9887) 2026-01-21 15:33:26 -06:00
Alex Yaroshuk
383c2787f9 feat(i18n): add Russian language support (#9882) 2026-01-21 15:30:12 -06:00
Aiden Cline
c89f6e7ac6 add chat.headers hook, adjust codex and copilot plugins to use it 2026-01-21 15:10:08 -06:00
GitHub Action
17a5f75b54 chore: generate 2026-01-21 21:05:26 +00:00
Filip
5ca28b6454 feat(app): polish translations (#9884) 2026-01-21 15:04:25 -06:00
opencode
09d2fd57ff release: v1.1.30 2026-01-21 20:55:35 +00:00
Adam
bcdec15fb4 fix(web): favicon rename again 2026-01-21 14:52:21 -06:00
Caleb Norton
0b63cae1ae fix: update pre-push hook to allow caret version differences (#9876)
Co-authored-by: randymcmillan <randymcmillan@protonmail.com>
2026-01-21 14:40:01 -06:00
Adam
b7b2eae20c fix(web): favicon rename again 2026-01-21 14:36:21 -06:00
Adam
1b98f26794 fix(web): missing favicons 2026-01-21 14:27:17 -06:00
Adam
fa91337723 fix(app): provider connect oauth error handling 2026-01-21 14:26:14 -06:00
Adam
6d656e4827 fix(app): querySelector errors, more defensive scroll-to-item 2026-01-21 14:21:59 -06:00
Adam
ae8cff22e5 fix(app): renaming non-git projects shouldn't affect other projects 2026-01-21 14:21:58 -06:00
Adam
52535654e7 fix(app): tab should select suggestion 2026-01-21 14:21:58 -06:00
Github Action
7e609cc612 chore: update nix node_modules hashes 2026-01-21 20:17:47 +00:00
Tommy D. Rossi
416aaff488 feat(acp): add session/list and session/fork support (#7976) 2026-01-21 14:14:56 -06:00
Aiden Cline
aa599b4a7d fix: when dropping unsupported metadata match on providerID/model.id instead of providerID/model.api.id to prevent regression when using legacy model ids (pre-variant) 2026-01-21 13:39:55 -06:00
Adam
b33cec485a fix: type error 2026-01-21 13:25:43 -06:00
Adam
3ba1111ed0 fix(app): terminal issues/regression 2026-01-21 13:23:50 -06:00
Aiden Cline
6f7a1c69a5 tweak: adjust textVerbosity and reasoningEffort defaults to better match codex cli 2026-01-21 13:23:04 -06:00
Allan Hvam
13405aedea fix(app): remove terminal button border to align with close button (#9874) 2026-01-21 13:05:32 -06:00
Daniel Polito
df2ed99231 fix(desktop): Navigation with Big Sessions (#9529) 2026-01-21 13:01:18 -06:00
Adam
c69e3bbde7 fix(app): auto-scroll ux 2026-01-21 12:53:24 -06:00
Daniel Olowoniyi
2a370f8038 feat: implement home directory expansion for permission patterns using ~ and $HOME prefixes. (#9813) 2026-01-21 12:52:21 -06:00
Aiden Cline
d9f0287d74 tweak: add back todo list tools for openai models 2026-01-21 12:14:09 -06:00
Noam Bressler
301e74d953 fix: Persist loaded model and mode on ACP session load (#9829) 2026-01-21 12:10:54 -06:00
Github Action
51126f081d chore: update nix node_modules hashes 2026-01-21 17:26:10 +00:00
GitHub Action
d03cac2351 chore: generate 2026-01-21 17:24:14 +00:00
Vladimir Glafirov
1820569818 chore(deps): update GitLab packages for better self-hosted instance support (#9856) 2026-01-21 11:23:33 -06:00
Bart Broere
8df09abb1b feat: Make the models.dev domain configurable for offline environments (#9258) 2026-01-21 11:23:07 -06:00
GitHub Action
95b17bcf5e chore: generate 2026-01-21 17:17:33 +00:00
opencode
c4c489a5bc release: v1.1.29 2026-01-21 17:17:32 +00:00
Adam
cd34f5e07c feat(app): new sound effects, downmixed to mono 2026-01-21 11:14:11 -06:00
Adam
621550ac77 fix(app): keybind search height 2026-01-21 11:14:11 -06:00
Adam
b746c006cf feat(app): new sounds 2026-01-21 11:14:11 -06:00
Adam
850d50eb64 fix(app): missing i18n keys 2026-01-21 11:14:11 -06:00
Aiden Cline
ab3d412a81 tweak: adjust skill tool description to make it more clear which skills are available 2026-01-21 11:08:28 -06:00
Stephen Collings
0e1a8a1839 fix: Claude w/bedrock custom inference profile - caching support (#9838)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-21 10:42:13 -06:00
GitHub Action
178767af70 chore: generate 2026-01-21 16:36:03 +00:00
Rahul A Mistry
b8a0e420f8 feat(app): search on settings shortcuts (#9850) 2026-01-21 10:35:15 -06:00
nuno maduro
bfbcbc8863 feat(formatters): add laravel pint as a .php formatter (#7312) 2026-01-21 10:31:39 -06:00
Spoon
fd77d31b49 tweak(session title): change prompt to have the response with user language (#9847) 2026-01-21 10:05:09 -06:00
Rahul A Mistry
9f02ffe02d fix(app): new workspace button with all languages (#9848) 2026-01-21 10:03:29 -06:00
GitHub Action
b10f423743 chore: generate 2026-01-21 15:58:36 +00:00
Nolan Darilek
0059fdc1f5 fix(app): add aria-labels to titlebar and sidebar buttons (#9843) 2026-01-21 09:57:50 -06:00
Adam
f7f2d9700a test(app): fix e2e 2026-01-21 09:46:24 -06:00
Frank
97e0e79f1a wip: black 2026-01-21 10:42:58 -05:00
Adam
4fc7bcf09e fix: type error 2026-01-21 09:30:40 -06:00
Adam
63da3a338a fix(app): breaking out of auto-scroll 2026-01-21 09:28:56 -06:00
GitHub Action
f736751a8c chore: generate 2026-01-21 15:26:15 +00:00
Ronan Kearns
6ac8c85b34 feat(app): model tooltip metadata in chooser (per Figma request) (#9707) 2026-01-21 09:25:34 -06:00
GitHub Action
19f68382fd chore: generate 2026-01-21 15:23:21 +00:00
DNGriffin
368cd2af4c fix(app): workspaces padding wonkiness (#9772) 2026-01-21 09:22:40 -06:00
Brendan Allan
d00b8df770 feat(desktop): properly integrate window controls on windows (#9835) 2026-01-21 08:35:05 -06:00
Adam
7ed448a7e8 test(app): fix e2e 2026-01-21 07:48:15 -06:00
Halil Tezcan KARABULUT
87d91c29e2 fix(app): terminal improvements - focus, rename, error state, CSP (#9700) 2026-01-21 06:49:46 -06:00
Adam
259b2a3c2d fix(app): japanese language support 2026-01-21 06:16:32 -06:00
Brendan Allan
ab705bbc31 fix(desktop): add workaround for nushell 2026-01-21 20:15:19 +08:00
Adam
e237f06c96 test(app): fix e2e 2026-01-21 06:10:01 -06:00
Adam
bb710e9ea1 fix(core): snapshot regression 2026-01-21 06:10:01 -06:00
GitHub Action
34e4d077cd ignore: update download stats 2026-01-21 2026-01-21 12:05:58 +00:00
Adam
4b8335160b test(app): fix e2e 2026-01-21 06:00:21 -06:00
Adam
8b0353cb2a feat(app): danish translations 2026-01-21 05:50:25 -06:00
Adam
4a386906dd feat(app): japanese translations 2026-01-21 05:50:25 -06:00
Adam
efff52714d feat(app): french translations 2026-01-21 05:50:25 -06:00
Adam
09a9556c70 feat(app): spanish translations 2026-01-21 05:24:38 -06:00
Adam
118b4f65da feat(app): german translations 2026-01-21 05:24:38 -06:00
Adam
e6438aa3f6 feat(app): korean translations 2026-01-21 05:24:38 -06:00
Adam
64c80f1b51 fix(app): don't show notification on session if active 2026-01-21 05:15:19 -06:00
Adam
7b8fad6202 test(app): fix e2e 2026-01-21 05:15:19 -06:00
Ronan Kearns
996eeb1f68 feat(app): add manage models icon to selector (per Figma request) (#9722) 2026-01-21 04:44:17 -06:00
zerone0x
2e5fe6d5c8 fix(ui): preserve filename casing in edit/write tool titles (#9752) 2026-01-21 04:41:45 -06:00
DNGriffin
8e8fb6a54b feat(app): allow users to select directory text on new session (#9760) 2026-01-21 04:41:03 -06:00
GitHub Action
79aa931a05 chore: generate 2026-01-21 10:36:00 +00:00
shirukai
cf6ad4c407 fix: handle special characters in paths and git snapshot reading logic(#9804) (#9807) 2026-01-21 04:35:23 -06:00
Adam
d6caaee816 fix(desktop): no proxy for connecting to sidecar (#9690)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-01-21 16:21:21 +08:00
Dax Raad
65938baf00 core: update session summary after revert to show file changes 2026-01-21 01:01:20 -05:00
Github Action
a639961973 chore: update nix node_modules hashes 2026-01-21 05:40:07 +00:00
Michael H
0f979bb87c chore(opencode): Use Bun.semver instead of node-semver (#9773) 2026-01-20 23:37:33 -06:00
GitHub Action
96e9c89cc6 chore: generate 2026-01-21 05:37:18 +00:00
Kenny
a18ae2c8b7 feat: add OPENCODE_DISABLE_PROJECT_CONFIG env var (#8093)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-20 23:36:42 -06:00
luo jiyin
c9ea966805 feat: add OPENCODE_DISABLE_FILETIME_CHECK flag (#6581)
Signed-off-by: luojiyin <luojiyin@hotmail.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-20 23:03:07 -06:00
GitHub Action
2049af4d6f chore: generate 2026-01-21 04:56:39 +00:00
Aiden Cline
74bd52e8a7 fix: ensure apply patch tool emits edited events 2026-01-20 22:55:50 -06:00
Aiden Cline
9dc95c4c69 tweak: ensure synthetic user message following subtasks is only added when user manually invoked subtask 2026-01-20 22:51:54 -06:00
Vinicius da Motta
b93f33eaa4 fix(tui): responsive layout for narrow screens (#9703) 2026-01-20 22:22:38 -06:00
Caleb Norton
34d473c0f5 chore: revert "Update flake.lock" (#9725) 2026-01-20 22:20:24 -06:00
GitHub Action
217e4850db chore: generate 2026-01-21 04:07:26 +00:00
Frank
be9a0bfee7 wip: support 2026-01-20 23:06:08 -05:00
GitHub Action
dac73572e0 chore: generate 2026-01-21 02:39:31 +00:00
Ariane Emory
cbe20d22d3 fix: don't update session timestamp for metadata-only changes (resolves #9494) (#9495) 2026-01-20 20:38:54 -06:00
yash
3723e1b8d2 fix: correct dot prefix display in directory names for RTL text rendering issue #9579 (#9591) 2026-01-20 20:36:41 -06:00
Jacob Bahn
65d9e829e7 feat(desktop): standardize desktop layout icons (#9685) 2026-01-20 20:34:33 -06:00
GitHub Action
6793b4a6fd chore: generate 2026-01-21 02:28:43 +00:00
Ryan Vogel
a71c40c717 fix(app): fix numbered list rendering in web markdown (#9723) 2026-01-20 20:28:01 -06:00
Alex Yaroshuk
489f2d3709 fix(ui): remove portal spacer and fix terminal toggle padding (#9728) 2026-01-20 20:27:33 -06:00
Github Action
20c8624bb7 chore: update nix node_modules hashes 2026-01-21 00:00:53 +00:00
GitHub Action
bb8bf32abe chore: generate 2026-01-20 23:58:59 +00:00
Adam
233d003b49 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
6037e88ddf wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
b13c269162 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
ef36af0e55 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
f86c37f579 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
9b7d9c8173 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
0f2e8ea2b4 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
be493e8be0 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
7e8e4d9938 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
7a359ff67c wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
835fea6bb1 wip(app): i18n prompt input 2026-01-20 17:58:06 -06:00
Adam
7138bd021c chore: spec 2026-01-20 17:58:06 -06:00
Adam
a68e5a1c17 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
92beae1410 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam
0470717c7f feat(app): initial i18n stubbing 2026-01-20 17:58:06 -06:00
Ryan Vogel
7f50b27996 docs: add Anthropic subscription warning and update feature list to highlight GitHub Copilot (#9721) 2026-01-20 18:22:47 -05:00
Aiden Cline
021e42c0bb core: fix issue when switching models (mainly between providers) where past reasoning/metadata would be sent to server and cause 400 errors since they came from another account/provider 2026-01-20 16:39:00 -06:00
Aiden Cline
0c4ffec857 chore: rename toModelMessage -> toModelMessages 2026-01-20 16:16:23 -06:00
GitHub Action
5c3e9cfa2c chore: generate 2026-01-20 22:13:03 +00:00
Adam
85ef23a098 fix(app): don't interfere with scroll when using message nav 2026-01-20 16:12:15 -06:00
David Hill
3b46f90124 fix: icon size in sidbar 2026-01-20 22:04:13 +00:00
David Hill
80dc74a0ec add keyboard shortcut (mod+,) to open settings dialog 2026-01-20 22:04:13 +00:00
Adam
a0636fcd50 fix(app): auto-scroll ux 2026-01-20 22:04:13 +00:00
opencode
d2fcdef571 release: v1.1.28 2026-01-20 22:04:12 +00:00
Adam
8137e4dd9c chore: agents.md 2026-01-20 15:59:23 -06:00
David Hill
7be6671e6e refactor Select component to use settings variant for settings modal styling 2026-01-20 21:53:17 +00:00
David Hill
575cc59b37 fix: increase sidebar icon size by removing 16px constraint 2026-01-20 21:53:17 +00:00
David Hill
4350b8fd6b fix: show View all sessions button for active project and close hovercard on click 2026-01-20 21:53:17 +00:00
David Hill
2111473746 fix: remove close delay on hover cards to stop overlapping 2026-01-20 21:53:17 +00:00
David Hill
8c5c377680 fix: review empty state font size 2026-01-20 21:53:17 +00:00
Kenny
d51089b52f docs(web): add KDCO plugins to ecosystem (#7694) 2026-01-20 15:47:07 -06:00
Trevor Walker
694695050a fix(opencode): preserve tool input from running state for MCP tool results (#9667) 2026-01-20 15:12:15 -06:00
DNGriffin
1f3b2b5951 fix(app): Edit-project name race condition (#9551) 2026-01-20 15:10:00 -06:00
David Hill
de87694867 fix: resolve Select children type conflict with ButtonProps 2026-01-20 21:09:20 +00:00
David Hill
ecd28fd520 fix: prompt agent button style 2026-01-20 21:08:50 +00:00
David Hill
bf9047ccd1 fix settings sidebar active tab hover to use consistent background 2026-01-20 21:08:50 +00:00
Adam
96a9744347 fix: type error 2026-01-20 15:08:03 -06:00
Caleb Norton
c4594c4c1f fix(opencode): relax bun version requirement (#9682) 2026-01-20 14:57:19 -06:00
Caleb Norton
eea70be21a chore: follow conventional commit in nix CI (#9672) 2026-01-20 14:56:37 -06:00
Idris Gadi
e83c01ad36 fix(tui): prevent sidebar height from overflowing. (#9689) 2026-01-20 14:56:18 -06:00
David Hill
7eb724f4e9 fix: dialog shadow 2026-01-20 20:34:46 +00:00
David Hill
7653e2d4d8 update: add new border and shadow style 2026-01-20 20:34:45 +00:00
David Hill
261b1eca2e update keyboard shortcuts panel to match general settings styling 2026-01-20 20:34:45 +00:00
David Hill
dbc15d4816 add color scheme preview on hover in appearance dropdown 2026-01-20 20:34:45 +00:00
David Hill
602b6be4d4 update settings panel padding and make content full width 2026-01-20 20:34:45 +00:00
David Hill
7f4277695d set 32px spacing between main title and group title 2026-01-20 20:34:45 +00:00
David Hill
39afc055bf add fade gradient to settings panel headers 2026-01-20 20:34:45 +00:00
David Hill
73b1bc42f4 increase specificity of select trigger hover/expanded states 2026-01-20 20:34:45 +00:00
David Hill
3eea1d424e set select trigger value font weight to regular 2026-01-20 20:34:45 +00:00
David Hill
1092cf4034 increase gap between label and icon in select trigger to 12px 2026-01-20 20:34:45 +00:00
David Hill
0c270b4743 reset select trigger to default state after selection 2026-01-20 20:34:45 +00:00
David Hill
715860f997 set default select trigger background to transparent 2026-01-20 20:34:45 +00:00
David Hill
26f66b5f5d update: color token 2026-01-20 20:34:45 +00:00
David Hill
f250a229c9 update select trigger icon styling and spacing 2026-01-20 20:34:45 +00:00
David Hill
c2c2bb1fa9 add 4px left padding to sidebar section title 2026-01-20 20:34:45 +00:00
David Hill
19ac6f1948 set hover background of active sidebar item to surface-raised-base 2026-01-20 20:34:45 +00:00
David Hill
0d9ce6ad7b set settings sidebar width to 200px 2026-01-20 20:34:45 +00:00
David Hill
2b95956132 add x-large dialog size and use it for settings modal 2026-01-20 20:34:45 +00:00
David Hill
36bbe809fa add 4px gutter between select trigger and dropdown 2026-01-20 20:34:45 +00:00
David Hill
a8113ee0df update select trigger and dropdown styling 2026-01-20 20:34:45 +00:00
David Hill
bcb8d970f1 add selector icon and use it for select dropdown trigger 2026-01-20 20:34:44 +00:00
David Hill
c57491ba48 add triggerStyle prop to Select and use it for font selector 2026-01-20 20:34:44 +00:00
David Hill
9ffb7141e5 set select dropdown border-radius to 8px 2026-01-20 20:34:44 +00:00
David Hill
f3b0f312bf adjust select dropdown positioning and padding structure 2026-01-20 20:34:44 +00:00
David Hill
09a6107649 set select dropdown min-width to 180px 2026-01-20 20:34:44 +00:00
David Hill
af8d91117c update select item styling: 4px radius, default cursor, 8px 2px padding 2026-01-20 20:34:44 +00:00
David Hill
0ffc2c2b39 increase select dropdown padding to 4px 2026-01-20 20:34:44 +00:00
David Hill
f9c951aa8b render font options in their respective fonts 2026-01-20 20:34:44 +00:00
David Hill
78bcbda2fa wrap settings row groups with styled container 2026-01-20 20:34:44 +00:00
David Hill
262aca1bca remove border and background from settings panel headers 2026-01-20 20:34:44 +00:00
David Hill
0cbbe5af77 remove subheader from General settings panel 2026-01-20 20:34:44 +00:00
David Hill
ecae24f426 use medium font weight for settings tab labels 2026-01-20 20:34:44 +00:00
David Hill
745206ffbb increase gap between icon and label in settings tabs to 12px 2026-01-20 20:34:44 +00:00
David Hill
83557e9b66 add keyboard icon and use it for Shortcuts settings tab 2026-01-20 20:34:44 +00:00
David Hill
1a4abe85e8 add sliders icon and use it for General settings tab 2026-01-20 20:34:44 +00:00
David Hill
175313922b use active background color for selected settings tab 2026-01-20 20:34:44 +00:00
David Hill
74ad6dd4c9 update settings tabs layout and spacing 2026-01-20 20:34:44 +00:00
David Hill
a94667e8e7 increase icon letter size to 32px in edit project dialog 2026-01-20 20:34:44 +00:00
David Hill
ff8abd8c2b increase session messages popover open delay to 1000ms 2026-01-20 20:34:44 +00:00
Adam
95e9407e63 test(app): fix e2e 2026-01-20 14:02:09 -06:00
Adam
1466b43c5c test(app): windows fixes 2026-01-20 14:02:09 -06:00
Adam
f73d7e67d3 test(app): windows fixes 2026-01-20 14:02:09 -06:00
Adam
1ac0980c80 test(app): windows e2e 2026-01-20 14:02:09 -06:00
Adam
1d6f650f53 fix(app): ayu theme colors 2026-01-20 13:59:04 -06:00
Adam
0b9b85ea6e wip(app): ayu colors 2026-01-20 13:59:04 -06:00
Adam
5521d66bb8 wip(app): ayu colors 2026-01-20 13:59:04 -06:00
Rahul A Mistry
281c9d1870 fix(app): change terminal.new keybind to ctrl+alt+t (#9670) 2026-01-20 13:03:20 -06:00
Rahul A Mistry
80481c2247 fix(app): cleanup pty.exited event listener on unmount (#9671) 2026-01-20 13:03:01 -06:00
drunkpiano
156ce54362 fix(ui): prevent Enter key action during IME composition (#9564) 2026-01-20 13:01:56 -06:00
Aiden Cline
1bc919dc74 ignore: add bun/file io skill just for our repo 2026-01-20 12:37:23 -06:00
Github Action
17438a2e90 Update node_modules hashes 2026-01-20 17:36:35 +00:00
Michael Banucu
17c4202ea8 fix(opencode): Allow compatible Bun versions in packageManager field (#9597)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-20 11:34:00 -06:00
Adam
7170983ef2 fix(app): duplicate session loads 2026-01-20 11:25:58 -06:00
Dax Raad
b05d88a730 docs: clarify that malicious config files are not an attack vector 2026-01-20 12:17:58 -05:00
zerone0x
a3a06ffc4f fix(ui): show filename in Edit/Write permission titles (#9662)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-20 11:14:47 -06:00
Tommy D. Rossi
68e41a1ee7 fix: pass arguments to commands without explicit placeholders (#9606) 2026-01-20 11:12:43 -06:00
Aiden Cline
5622c53e1f tweak: adjust codex prompt to discourage unnecessary question asking and encourage more autonomy 2026-01-20 11:08:04 -06:00
GitHub Action
dfe6ce211d chore: generate 2026-01-20 16:57:52 +00:00
Rahul A Mistry
8639b0767a feat(app): add tooltips to sidebar new session/workspace buttons (#9652) 2026-01-20 10:57:11 -06:00
Adam
5f67e6fd12 fix(app): don't jump accordion on expand/collapse 2026-01-20 10:54:04 -06:00
Adam
86b2002deb fix: type error 2026-01-20 10:34:10 -06:00
Adam
7f862533d8 fix(app): better pending states for workspace operations 2026-01-20 10:31:57 -06:00
msvechla
8f62d4a5e3 fix(mcp): register OAuth callback before opening browser (#9646) 2026-01-20 10:18:49 -06:00
GitHub Action
733226de9d chore: generate 2026-01-20 16:11:56 +00:00
Noam Bressler
e8b0a65c63 feat: Support ACP audience by mapping to ignore and synthetic (#9593)
Co-authored-by: noam-v <noam@bespo.ai>
2026-01-20 10:11:02 -06:00
GitHub Action
cd2125eecd chore: generate 2026-01-20 16:01:54 +00:00
Adam
8595dae1a4 fix(app): session loading loop 2026-01-20 10:01:04 -06:00
Rahul Mishra
c365f0a7c1 feat: add restart and reload menu items on macOS (#9212) 2026-01-20 09:44:15 -06:00
Rahul A Mistry
01b12949e3 fix(app): terminal no longer hangs on exit or ctrl + D and closes the pane (#9506) 2026-01-20 09:42:20 -06:00
Adam
ac7e674a87 fix(app): broken 2026-01-20 09:05:04 -06:00
GitHub Action
47fa496701 chore: generate 2026-01-20 13:34:20 +00:00
Adam
d77cbf9c46 chore: cleanup 2026-01-20 07:33:44 -06:00
Adam
340285575b chore: cleanup 2026-01-20 07:33:44 -06:00
Adam
dd5b5f5482 chore: cleanup 2026-01-20 07:33:44 -06:00
Adam
924fc9ed80 wip(app): settings 2026-01-20 07:33:44 -06:00
Adam
df094a10ff wip(app): settings 2026-01-20 07:33:44 -06:00
Adam
de3641e8eb wip(app): settings 2026-01-20 07:33:44 -06:00
Adam
8bcbfd6396 wip(app): settings 2026-01-20 07:33:44 -06:00
opencode
e521fee002 release: v1.1.27 2026-01-20 12:13:48 +00:00
Adam
04e60f2b3d fix(app): no flash of home page on start 2026-01-20 06:10:53 -06:00
GitHub Action
23d71e125c ignore: update download stats 2026-01-20 2026-01-20 12:05:55 +00:00
GitHub Action
27406cf8ef chore: generate 2026-01-20 11:40:31 +00:00
Adam
0596b02f19 chore: cleanup 2026-01-20 05:39:54 -06:00
Adam
5145b72c4a chore: cleanup 2026-01-20 05:37:15 -06:00
Adam
347cd8ac63 chore: cleanup 2026-01-20 05:35:24 -06:00
Adam
b711ca57f2 fix(app): localStorage quota 2026-01-20 05:21:33 -06:00
Adam
353115a895 fix(app): user message expand on click 2026-01-20 05:21:33 -06:00
Adam
5f0372183a fix(app): persist quota 2026-01-20 05:21:32 -06:00
GitHub Action
616329ae97 chore: generate 2026-01-20 06:33:16 +00:00
Aiden Cline
9706aaf552 rm filetime assertions from patch tool 2026-01-20 00:32:29 -06:00
Brendan Allan
8b379329a6 fix(desktop): completely disable pinch to zoom 2026-01-20 14:07:39 +08:00
Craig Jellick
68d1755a9e fix: add space toggle hint to tool selection prompt (#9535) 2026-01-19 23:38:26 -06:00
Aiden Cline
419004992d chore: remove duplicate prompt file 2026-01-19 23:22:57 -06:00
Aiden Cline
0d49df46ef fix: ensure truncation handling applies to mcp servers too 2026-01-19 23:19:24 -06:00
James Meng
36f5ba52e9 fix(batch): update batch tool definition to outline correct value for max tool calls (#9517) 2026-01-19 22:15:02 -06:00
GitHub Action
088b537657 chore: generate 2026-01-20 01:42:14 +00:00
Filip
4ddfa86e7f fix(app): message list overflow & scrolling (#9530) 2026-01-19 19:41:42 -06:00
David Hill
b91b76e9eb add 8px padding to recent sessions popover 2026-01-20 01:41:36 +00:00
David Hill
6ed656a615 remove top padding from edit project dialog form 2026-01-20 01:36:34 +00:00
David Hill
7b336add88 update session messages popover gutter to 28px 2026-01-20 01:21:36 +00:00
David Hill
7f9ffe57f9 update thinking text styling in desktop app 2026-01-20 00:42:55 +00:00
David Hill
ad31b555a8 position session messages popover at top 2026-01-20 00:27:03 +00:00
David Hill
a05c334702 retain session hover state when popover open and update border radius 2026-01-20 00:24:51 +00:00
David Hill
cf284e32aa update session hover popover styling 2026-01-20 00:21:11 +00:00
David Hill
054ccee78d update review session empty state styling 2026-01-20 00:15:13 +00:00
DNGriffin
bfa986d45e feat(app): Add ability to select project directory text to web (#9344) 2026-01-19 17:38:52 -06:00
Dax Raad
aa4b06e165 tui: fix message history cleanup to prevent memory leaks 2026-01-19 18:22:19 -05:00
GitHub Action
2542693f7b chore: generate 2026-01-19 22:13:58 +00:00
Adam
bec294b781 fix(app): remove copy button from summary 2026-01-19 16:13:16 -06:00
407 changed files with 28348 additions and 3546 deletions

166
.github/workflows/daily-issues-recap.yml vendored Normal file
View File

@@ -0,0 +1,166 @@
name: Daily Issues Recap
on:
schedule:
# Run at 6 PM EST (23:00 UTC, or 22:00 UTC during daylight saving)
- cron: "0 23 * * *"
workflow_dispatch: # Allow manual trigger for testing
jobs:
daily-recap:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- name: Generate daily issues recap
id: recap
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCODE_PERMISSION: |
{
"bash": {
"*": "deny",
"gh issue*": "allow",
"gh search*": "allow"
},
"webfetch": "deny",
"edit": "deny",
"write": "deny"
}
run: |
# Get today's date range
TODAY=$(date -u +%Y-%m-%d)
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the OpenCode repository.
TODAY'S DATE: ${TODAY}
STEP 1: Gather today's issues
Search for all issues created today (${TODAY}) using:
gh issue list --repo ${{ github.repository }} --state all --search \"created:${TODAY}\" --json number,title,body,labels,state,comments,createdAt,author --limit 500
STEP 2: Analyze and categorize
For each issue created today, categorize it:
**Severity Assessment:**
- CRITICAL: Crashes, data loss, security issues, blocks major functionality
- HIGH: Significant bugs affecting many users, important features broken
- MEDIUM: Bugs with workarounds, minor features broken
- LOW: Minor issues, cosmetic, nice-to-haves
**Activity Assessment:**
- Note issues with high comment counts or engagement
- Note issues from repeat reporters (check if author has filed before)
STEP 3: Cross-reference with existing issues
For issues that seem like feature requests or recurring bugs:
- Search for similar older issues to identify patterns
- Note if this is a frequently requested feature
- Identify any issues that are duplicates of long-standing requests
STEP 4: Generate the recap
Create a structured recap with these sections:
===DISCORD_START===
**Daily Issues Recap - ${TODAY}**
**Summary Stats**
- Total issues opened today: [count]
- By category: [bugs/features/questions]
**Critical/High Priority Issues**
[List any CRITICAL or HIGH severity issues with brief descriptions and issue numbers]
**Most Active/Discussed**
[Issues with significant engagement or from active community members]
**Trending Topics**
[Patterns noticed - e.g., 'Multiple reports about X', 'Continued interest in Y feature']
**Duplicates & Related**
[Issues that relate to existing open issues]
===DISCORD_END===
STEP 5: Format for Discord
Format the recap as a Discord-compatible message:
- Use Discord markdown (**, __, etc.)
- BE EXTREMELY CONCISE - this is an EOD summary, not a detailed report
- Use hyperlinked issue numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/issues/1234>)
- Group related issues on single lines where possible
- Add emoji sparingly for critical items only
- HARD LIMIT: Keep under 1800 characters total
- Skip sections that have nothing notable (e.g., if no critical issues, omit that section)
- Prioritize signal over completeness - only surface what matters
OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/recap_raw.txt
# Extract only the Discord message between markers
sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/recap_raw.txt | grep -v '===DISCORD' > /tmp/recap.txt
echo "recap_file=/tmp/recap.txt" >> $GITHUB_OUTPUT
- name: Post to Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
run: |
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
cat /tmp/recap.txt
exit 0
fi
# Read the recap
RECAP_RAW=$(cat /tmp/recap.txt)
RECAP_LENGTH=${#RECAP_RAW}
echo "Recap length: ${RECAP_LENGTH} chars"
# Function to post a message to Discord
post_to_discord() {
local msg="$1"
local content=$(echo "$msg" | jq -Rs '.')
curl -s -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": ${content}}" \
"$DISCORD_WEBHOOK_URL"
sleep 1
}
# If under limit, send as single message
if [ "$RECAP_LENGTH" -le 1950 ]; then
post_to_discord "$RECAP_RAW"
else
echo "Splitting into multiple messages..."
remaining="$RECAP_RAW"
while [ ${#remaining} -gt 0 ]; do
if [ ${#remaining} -le 1950 ]; then
post_to_discord "$remaining"
break
else
chunk="${remaining:0:1900}"
last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
chunk="${remaining:0:$last_newline}"
remaining="${remaining:$((last_newline+1))}"
else
chunk="${remaining:0:1900}"
remaining="${remaining:1900}"
fi
post_to_discord "$chunk"
fi
done
fi
echo "Posted daily recap to Discord"

169
.github/workflows/daily-pr-recap.yml vendored Normal file
View File

@@ -0,0 +1,169 @@
name: Daily PR Recap
on:
schedule:
# Run at 5pm EST (22:00 UTC, or 21:00 UTC during daylight saving)
- cron: "0 22 * * *"
workflow_dispatch: # Allow manual trigger for testing
jobs:
pr-recap:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- name: Generate daily PR recap
id: recap
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCODE_PERMISSION: |
{
"bash": {
"*": "deny",
"gh pr*": "allow",
"gh search*": "allow"
},
"webfetch": "deny",
"edit": "deny",
"write": "deny"
}
run: |
TODAY=$(date -u +%Y-%m-%d)
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the OpenCode repository.
TODAY'S DATE: ${TODAY}
STEP 1: Gather PR data
Run these commands to gather PR information. ONLY include PRs created or updated TODAY (${TODAY}):
# PRs created today
gh pr list --repo ${{ github.repository }} --state all --search \"created:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100
# PRs with activity today (updated today)
gh pr list --repo ${{ github.repository }} --state open --search \"updated:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100
STEP 2: For high-activity PRs, check comment counts
For promising PRs, run:
gh pr view [NUMBER] --repo ${{ github.repository }} --json comments --jq '[.comments[] | select(.author.login != \"copilot-pull-request-reviewer\" and .author.login != \"github-actions\")] | length'
IMPORTANT: When counting comments/activity, EXCLUDE these bot accounts:
- copilot-pull-request-reviewer
- github-actions
STEP 3: Identify what matters (ONLY from today's PRs)
**Bug Fixes From Today:**
- PRs with 'fix' or 'bug' in title created/updated today
- Small bug fixes (< 100 lines changed) that are easy to review
- Bug fixes from community contributors
**High Activity Today:**
- PRs with significant human comments today (excluding bots listed above)
- PRs with back-and-forth discussion today
**Quick Wins:**
- Small PRs (< 50 lines) that are approved or nearly approved
- PRs that just need a final review
STEP 4: Generate the recap
Create a structured recap:
===DISCORD_START===
**Daily PR Recap - ${TODAY}**
**New PRs Today**
[PRs opened today - group by type: bug fixes, features, etc.]
**Active PRs Today**
[PRs with activity/updates today - significant discussion]
**Quick Wins**
[Small PRs ready to merge]
===DISCORD_END===
STEP 5: Format for Discord
- Use Discord markdown (**, __, etc.)
- BE EXTREMELY CONCISE - surface what we might miss
- Use hyperlinked PR numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/pull/1234>)
- Include PR author: [#1234](<url>) (@author)
- For bug fixes, add brief description of what it fixes
- Show line count for quick wins: \"(+15/-3 lines)\"
- HARD LIMIT: Keep under 1800 characters total
- Skip empty sections
- Focus on PRs that need human eyes
OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/pr_recap_raw.txt
# Extract only the Discord message between markers
sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/pr_recap_raw.txt | grep -v '===DISCORD' > /tmp/pr_recap.txt
echo "recap_file=/tmp/pr_recap.txt" >> $GITHUB_OUTPUT
- name: Post to Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
run: |
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
cat /tmp/pr_recap.txt
exit 0
fi
# Read the recap
RECAP_RAW=$(cat /tmp/pr_recap.txt)
RECAP_LENGTH=${#RECAP_RAW}
echo "Recap length: ${RECAP_LENGTH} chars"
# Function to post a message to Discord
post_to_discord() {
local msg="$1"
local content=$(echo "$msg" | jq -Rs '.')
curl -s -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": ${content}}" \
"$DISCORD_WEBHOOK_URL"
sleep 1
}
# If under limit, send as single message
if [ "$RECAP_LENGTH" -le 1950 ]; then
post_to_discord "$RECAP_RAW"
else
echo "Splitting into multiple messages..."
remaining="$RECAP_RAW"
while [ ${#remaining} -gt 0 ]; do
if [ ${#remaining} -le 1950 ]; then
post_to_discord "$remaining"
break
else
chunk="${remaining:0:1900}"
last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
chunk="${remaining:0:$last_newline}"
remaining="${remaining:$((last_newline+1))}"
else
chunk="${remaining:0:1900}"
remaining="${remaining:1900}"
fi
post_to_discord "$chunk"
fi
done
fi
echo "Posted daily PR recap to Discord"

View File

@@ -8,7 +8,29 @@ on:
workflow_dispatch:
jobs:
test:
runs-on: blacksmith-4vcpu-ubuntu-2404
name: test (${{ matrix.settings.name }})
strategy:
fail-fast: false
matrix:
settings:
- name: linux
host: blacksmith-4vcpu-ubuntu-2404
playwright: bunx playwright install --with-deps
workdir: .
command: |
git config --global user.email "bot@opencode.ai"
git config --global user.name "opencode"
bun turbo typecheck
bun turbo test
- name: windows
host: windows-latest
playwright: bunx playwright install
workdir: packages/app
command: bun test:e2e:local
runs-on: ${{ matrix.settings.host }}
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -20,74 +42,106 @@ jobs:
- name: Install Playwright browsers
working-directory: packages/app
run: bunx playwright install --with-deps
run: ${{ matrix.settings.playwright }}
- name: Set OS-specific paths
run: |
if [ "${{ runner.os }}" = "Windows" ]; then
printf '%s\n' "OPENCODE_E2E_ROOT=${{ runner.temp }}\\opencode-e2e" >> "$GITHUB_ENV"
printf '%s\n' "OPENCODE_TEST_HOME=${{ runner.temp }}\\opencode-e2e\\home" >> "$GITHUB_ENV"
printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}\\opencode-e2e\\share" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}\\opencode-e2e\\cache" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}\\opencode-e2e\\config" >> "$GITHUB_ENV"
printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}\\opencode-e2e\\state" >> "$GITHUB_ENV"
printf '%s\n' "MODELS_DEV_API_JSON=${{ github.workspace }}\\packages\\opencode\\test\\tool\\fixtures\\models-api.json" >> "$GITHUB_ENV"
else
printf '%s\n' "OPENCODE_E2E_ROOT=${{ runner.temp }}/opencode-e2e" >> "$GITHUB_ENV"
printf '%s\n' "OPENCODE_TEST_HOME=${{ runner.temp }}/opencode-e2e/home" >> "$GITHUB_ENV"
printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}/opencode-e2e/share" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}/opencode-e2e/cache" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}/opencode-e2e/config" >> "$GITHUB_ENV"
printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}/opencode-e2e/state" >> "$GITHUB_ENV"
printf '%s\n' "MODELS_DEV_API_JSON=${{ github.workspace }}/packages/opencode/test/tool/fixtures/models-api.json" >> "$GITHUB_ENV"
fi
- name: Seed opencode data
if: matrix.settings.name != 'windows'
working-directory: packages/opencode
run: bun script/seed-e2e.ts
env:
MODELS_DEV_API_JSON: ${{ github.workspace }}/packages/opencode/test/tool/fixtures/models-api.json
MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }}
OPENCODE_DISABLE_MODELS_FETCH: "true"
OPENCODE_DISABLE_SHARE: "true"
OPENCODE_DISABLE_LSP_DOWNLOAD: "true"
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true"
OPENCODE_TEST_HOME: ${{ runner.temp }}/opencode-e2e/home
XDG_DATA_HOME: ${{ runner.temp }}/opencode-e2e/share
XDG_CACHE_HOME: ${{ runner.temp }}/opencode-e2e/cache
XDG_CONFIG_HOME: ${{ runner.temp }}/opencode-e2e/config
XDG_STATE_HOME: ${{ runner.temp }}/opencode-e2e/state
OPENCODE_TEST_HOME: ${{ env.OPENCODE_TEST_HOME }}
XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }}
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
OPENCODE_E2E_PROJECT_DIR: ${{ github.workspace }}
OPENCODE_E2E_SESSION_TITLE: "E2E Session"
OPENCODE_E2E_MESSAGE: "Seeded for UI e2e"
OPENCODE_E2E_MODEL: "opencode/gpt-5-nano"
- name: Run opencode server
run: bun run dev -- --print-logs --log-level WARN serve --port 4096 --hostname 0.0.0.0 &
if: matrix.settings.name != 'windows'
working-directory: packages/opencode
run: bun dev -- --print-logs --log-level WARN serve --port 4096 --hostname 127.0.0.1 &
env:
MODELS_DEV_API_JSON: ${{ github.workspace }}/packages/opencode/test/tool/fixtures/models-api.json
MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }}
OPENCODE_DISABLE_MODELS_FETCH: "true"
OPENCODE_DISABLE_SHARE: "true"
OPENCODE_DISABLE_LSP_DOWNLOAD: "true"
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true"
OPENCODE_TEST_HOME: ${{ runner.temp }}/opencode-e2e/home
XDG_DATA_HOME: ${{ runner.temp }}/opencode-e2e/share
XDG_CACHE_HOME: ${{ runner.temp }}/opencode-e2e/cache
XDG_CONFIG_HOME: ${{ runner.temp }}/opencode-e2e/config
XDG_STATE_HOME: ${{ runner.temp }}/opencode-e2e/state
OPENCODE_TEST_HOME: ${{ env.OPENCODE_TEST_HOME }}
XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }}
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
OPENCODE_CLIENT: "app"
- name: Wait for opencode server
if: matrix.settings.name != 'windows'
run: |
for i in {1..60}; do
curl -fsS "http://localhost:4096/global/health" > /dev/null && exit 0
for i in {1..120}; do
curl -fsS "http://127.0.0.1:4096/global/health" > /dev/null && exit 0
sleep 1
done
exit 1
- name: run
run: |
git config --global user.email "bot@opencode.ai"
git config --global user.name "opencode"
bun turbo typecheck
bun turbo test
working-directory: ${{ matrix.settings.workdir }}
run: ${{ matrix.settings.command }}
env:
CI: true
MODELS_DEV_API_JSON: ${{ github.workspace }}/packages/opencode/test/tool/fixtures/models-api.json
MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }}
OPENCODE_DISABLE_MODELS_FETCH: "true"
OPENCODE_DISABLE_SHARE: "true"
OPENCODE_DISABLE_LSP_DOWNLOAD: "true"
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true"
OPENCODE_TEST_HOME: ${{ runner.temp }}/opencode-e2e/home
XDG_DATA_HOME: ${{ runner.temp }}/opencode-e2e/share
XDG_CACHE_HOME: ${{ runner.temp }}/opencode-e2e/cache
XDG_CONFIG_HOME: ${{ runner.temp }}/opencode-e2e/config
XDG_STATE_HOME: ${{ runner.temp }}/opencode-e2e/state
PLAYWRIGHT_SERVER_HOST: "localhost"
OPENCODE_TEST_HOME: ${{ env.OPENCODE_TEST_HOME }}
XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }}
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
PLAYWRIGHT_SERVER_HOST: "127.0.0.1"
PLAYWRIGHT_SERVER_PORT: "4096"
VITE_OPENCODE_SERVER_HOST: "localhost"
VITE_OPENCODE_SERVER_HOST: "127.0.0.1"
VITE_OPENCODE_SERVER_PORT: "4096"
OPENCODE_CLIENT: "app"
timeout-minutes: 30
- name: Upload Playwright artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }}
if-no-files-found: ignore
retention-days: 7
path: |
packages/app/e2e/test-results
packages/app/e2e/playwright-report

View File

@@ -128,7 +128,7 @@ jobs:
echo "Changes detected:"
echo "$STATUS"
git add "${FILES[@]}"
git commit -m "Update $TITLE"
git commit -m "chore: update nix node_modules hashes"
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
git pull --rebase --autostash origin "$BRANCH"

View File

@@ -1,9 +1,20 @@
#!/bin/sh
set -e
# Check if bun version matches package.json
EXPECTED_VERSION=$(grep '"packageManager"' package.json | sed 's/.*"bun@\([^"]*\)".*/\1/')
CURRENT_VERSION=$(bun --version)
if [ "$CURRENT_VERSION" != "$EXPECTED_VERSION" ]; then
echo "Error: Bun version $CURRENT_VERSION does not match expected version $EXPECTED_VERSION from package.json"
exit 1
fi
# keep in sync with packages/script/src/index.ts semver qualifier
bun -e '
import { semver } from "bun";
const pkg = await Bun.file("package.json").json();
const expectedBunVersion = pkg.packageManager?.split("@")[1];
if (!expectedBunVersion) {
throw new Error("packageManager field not found in root package.json");
}
const expectedBunVersionRange = `^${expectedBunVersion}`;
if (!semver.satisfies(process.versions.bun, expectedBunVersionRange)) {
throw new Error(`This script requires bun@${expectedBunVersionRange}, but you are using bun@${process.versions.bun}`);
}
if (process.versions.bun !== expectedBunVersion) {
console.warn(`Warning: Bun version ${process.versions.bun} differs from expected ${expectedBunVersion}`);
}
'
bun typecheck

3
.opencode/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
plans/
bun.lock
package.json

View File

@@ -0,0 +1,39 @@
---
name: bun-file-io
description: Use this when you are working on file operations like reading, writing, scanning, or deleting files. It summarizes the preferred file APIs and patterns used in this repo. It also notes when to use filesystem helpers for directories.
---
## Use this when
- Editing file I/O or scans in `packages/opencode`
- Handling directory operations or external tools
## Bun file APIs (from Bun docs)
- `Bun.file(path)` is lazy; call `text`, `json`, `stream`, `arrayBuffer`, `bytes`, `exists` to read.
- Metadata: `file.size`, `file.type`, `file.name`.
- `Bun.write(dest, input)` writes strings, buffers, Blobs, Responses, or files.
- `Bun.file(...).delete()` deletes a file.
- `file.writer()` returns a FileSink for incremental writes.
- `Bun.Glob` + `Array.fromAsync(glob.scan({ cwd, absolute, onlyFiles, dot }))` for scans.
- Use `Bun.which` to find a binary, then `Bun.spawn` to run it.
- `Bun.readableStreamToText/Bytes/JSON` for stream output.
## When to use node:fs
- Use `node:fs/promises` for directories (`mkdir`, `readdir`, recursive operations).
## Repo patterns
- Prefer Bun APIs over Node `fs` for file access.
- Check `Bun.file(...).exists()` before reading.
- For binary/large files use `arrayBuffer()` and MIME checks via `file.type`.
- Use `Bun.Glob` + `Array.fromAsync` for scans.
- Decode tool stderr with `Bun.readableStreamToText`.
- For large writes, use `Bun.write(Bun.file(path), text)`.
## Quick checklist
- Use Bun APIs first.
- Use `path.join`/`path.resolve` for paths.
- Prefer promise `.catch(...)` over `try/catch` when possible.

View File

@@ -1,6 +0,0 @@
---
name: test-skill
description: use this when asked to test skill
---
woah this is a test skill

View File

@@ -71,15 +71,50 @@ Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
- `packages/desktop`: The native desktop app, built with Tauri (wraps `packages/app`)
- `packages/plugin`: Source for `@opencode-ai/plugin`
### Understanding bun dev vs opencode
During development, `bun dev` is the local equivalent of the built `opencode` command. Both run the same CLI interface:
```bash
# Development (from project root)
bun dev --help # Show all available commands
bun dev serve # Start headless API server
bun dev web # Start server + open web interface
bun dev <directory> # Start TUI in specific directory
# Production
opencode --help # Show all available commands
opencode serve # Start headless API server
opencode web # Start server + open web interface
opencode <directory> # Start TUI in specific directory
```
### Running the API Server
To start the OpenCode headless API server:
```bash
bun dev serve
```
This starts the headless server on port 4096 by default. You can specify a different port:
```bash
bun dev serve --port 8080
```
### Running the Web App
To test UI changes during development, run the web app:
To test UI changes during development:
1. **First, start the OpenCode server** (see [Running the API Server](#running-the-api-server) section above)
2. **Then run the web app:**
```bash
bun run --cwd packages/app dev
```
This starts a local dev server at http://localhost:5173 (or similar port shown in output). Most UI changes can be tested here.
This starts a local dev server at http://localhost:5173 (or similar port shown in output). Most UI changes can be tested here, but the server must be running for full functionality.
### Running the Desktop App
@@ -127,9 +162,9 @@ Caveats:
- If you want to run the OpenCode TUI and have breakpoints triggered in the server code, you might need to run `bun dev spawn` instead of
the usual `bun dev`. This is because `bun dev` runs the server in a worker thread and breakpoints might not work there.
- If `spawn` does not work for you, you can debug the server separately:
- Debug server: `bun run --inspect=ws://localhost:6499/ ./src/index.ts serve --port 4096`,
- Debug server: `bun run --inspect=ws://localhost:6499/ --cwd packages/opencode ./src/index.ts serve --port 4096`,
then attach TUI with `opencode attach http://localhost:4096`
- Debug TUI: `bun run --inspect=ws://localhost:6499/ --conditions=browser ./src/index.ts`
- Debug TUI: `bun run --inspect=ws://localhost:6499/ --cwd packages/opencode --conditions=browser ./src/index.ts`
Other tips and tricks:

View File

@@ -24,6 +24,7 @@ Server mode is opt-in only. When enabled, set `OPENCODE_SERVER_PASSWORD` to requ
| **Sandbox escapes** | The permission system is not a sandbox (see above) |
| **LLM provider data handling** | Data sent to your configured LLM provider is governed by their policies |
| **MCP server behavior** | External MCP servers you configure are outside our trust boundary |
| **Malicious config files** | Users control their own config; modifying it is not an attack vector |
---

View File

@@ -205,3 +205,7 @@
| 2026-01-17 | 4,389,558 (+268,008) | 1,805,315 (+50,897) | 6,194,873 (+318,905) |
| 2026-01-18 | 4,627,623 (+238,065) | 1,839,171 (+33,856) | 6,466,794 (+271,921) |
| 2026-01-19 | 4,861,108 (+233,485) | 1,863,112 (+23,941) | 6,724,220 (+257,426) |
| 2026-01-20 | 5,128,999 (+267,891) | 1,903,665 (+40,553) | 7,032,664 (+308,444) |
| 2026-01-21 | 5,444,842 (+315,843) | 1,962,531 (+58,866) | 7,407,373 (+374,709) |
| 2026-01-22 | 5,766,340 (+321,498) | 2,029,487 (+66,956) | 7,795,827 (+388,454) |
| 2026-01-23 | 6,096,236 (+329,896) | 2,096,235 (+66,748) | 8,192,471 (+396,644) |

164
bun.lock
View File

@@ -16,13 +16,14 @@
"@tsconfig/bun": "catalog:",
"husky": "9.1.7",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.17.23",
"turbo": "2.5.6",
},
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -32,6 +33,7 @@
"@solid-primitives/active-element": "2.1.3",
"@solid-primitives/audio": "1.4.2",
"@solid-primitives/event-bus": "1.1.2",
"@solid-primitives/i18n": "2.2.1",
"@solid-primitives/media": "2.3.3",
"@solid-primitives/resize-observer": "2.1.3",
"@solid-primitives/scroll": "2.1.3",
@@ -71,7 +73,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -105,7 +107,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -132,7 +134,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -156,7 +158,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -180,7 +182,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -209,7 +211,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -238,7 +240,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -254,14 +256,14 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.1.26",
"version": "1.1.34",
"bin": {
"opencode": "./bin/opencode",
},
"dependencies": {
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.5.1",
"@agentclientprotocol/sdk": "0.12.0",
"@ai-sdk/amazon-bedrock": "3.0.73",
"@ai-sdk/anthropic": "2.0.57",
"@ai-sdk/azure": "2.0.91",
@@ -282,7 +284,7 @@
"@ai-sdk/vercel": "1.0.31",
"@ai-sdk/xai": "2.0.51",
"@clack/prompts": "1.0.0-alpha.1",
"@gitlab/gitlab-ai-provider": "3.1.2",
"@gitlab/gitlab-ai-provider": "3.2.0",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
"@modelcontextprotocol/sdk": "1.25.2",
@@ -309,6 +311,7 @@
"clipboardy": "4.0.0",
"decimal.js": "10.5.0",
"diff": "catalog:",
"drizzle-orm": "0.45.1",
"fuzzysort": "3.1.0",
"gray-matter": "4.0.3",
"hono": "catalog:",
@@ -350,6 +353,8 @@
"@types/turndown": "5.0.5",
"@types/yargs": "17.0.33",
"@typescript/native-preview": "catalog:",
"better-sqlite3": "12.6.0",
"drizzle-kit": "0.31.8",
"typescript": "catalog:",
"vscode-languageserver-types": "3.17.5",
"why-is-node-running": "3.2.2",
@@ -358,7 +363,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -378,7 +383,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.1.26",
"version": "1.1.34",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.4",
"@tsconfig/node22": "catalog:",
@@ -389,7 +394,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -402,7 +407,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -443,7 +448,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"zod": "catalog:",
},
@@ -454,7 +459,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.1.26",
"version": "1.1.34",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -514,6 +519,7 @@
"@types/bun": "1.3.5",
"@types/luxon": "3.7.1",
"@types/node": "22.13.9",
"@types/semver": "7.7.1",
"@typescript/native-preview": "7.0.0-dev.20251207.1",
"ai": "5.0.119",
"diff": "8.0.2",
@@ -551,7 +557,7 @@
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.12.0", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-V8uH/KK1t7utqyJmTA7y7DzKu6+jKFIXM+ZVouz8E55j8Ej2RV42rEvPKn3/PpBJlliI5crcGk1qQhZ7VwaepA=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.73", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.57", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EAAGJ/dfbAZaqIhK3w52hq6cftSLZwXdC6uHKh8Cls1T0N4MxS6ykDf54UyFO3bZWkQxR+Mdw1B3qireGOxtJQ=="],
@@ -919,7 +925,7 @@
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.1.2", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-p0NZhZJSavWDX9r/Px/mOK2YIC803GZa8iRzcg3f1C6S0qfea/HBTe4/NWvT2+2kWIwhCePGuI4FN2UFiUWXUg=="],
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.2.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-sqP34jDSWWEHygmYbM2rzIcRjhA+1FHVHj8mxUvVz1s7o2Cgb1NnOaUXU7eWTI0AGhO+tPYHDTqI/mRC4cdjlQ=="],
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
@@ -1631,6 +1637,8 @@
"@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="],
"@solid-primitives/i18n": ["@solid-primitives/i18n@2.2.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-TnTnE2Ku11MGYZ1JzhJ8pYscwg1fr9MteoYxPwsfxWfh9Jp5K7RRJncJn9BhOHvNLwROjqOHZ46PT7sPHqbcXw=="],
"@solid-primitives/keyed": ["@solid-primitives/keyed@1.5.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-BgoEdqPw48URnI+L5sZIHdF4ua4Las1eWEBBPaoSFs42kkhnHue+rwCBPL2Z9ebOyQ75sUhUfOETdJfmv0D6Kg=="],
"@solid-primitives/map": ["@solid-primitives/map@0.4.13", "", { "dependencies": { "@solid-primitives/trigger": "^1.1.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew=="],
@@ -2037,12 +2045,18 @@
"before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
"better-sqlite3": ["better-sqlite3@12.6.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-FXI191x+D6UPWSze5IzZjhz+i9MK9nsuHsmTX9bXVl52k06AfZ2xql0lrgIUuzsMsJ7Vgl5kIptvDgBLIV3ZSQ=="],
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
"blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="],
@@ -2249,6 +2263,10 @@
"decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="],
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="],
@@ -2341,6 +2359,8 @@
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"engine.io-client": ["engine.io-client@6.6.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="],
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
@@ -2425,6 +2445,8 @@
"exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
@@ -2459,6 +2481,8 @@
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="],
@@ -2497,6 +2521,8 @@
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
@@ -2547,6 +2573,8 @@
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
@@ -3085,6 +3113,8 @@
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
"miniflare": ["miniflare@4.20251118.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251118.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ=="],
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
@@ -3097,6 +3127,8 @@
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -3115,6 +3147,8 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
@@ -3127,6 +3161,8 @@
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
"node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
@@ -3323,6 +3359,8 @@
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="],
@@ -3345,6 +3383,8 @@
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
"punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="],
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
@@ -3361,6 +3401,8 @@
"raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="],
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
"react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="],
@@ -3547,6 +3589,10 @@
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
"simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
"simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
"simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="],
@@ -3651,6 +3697,8 @@
"strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
"strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
"stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="],
"strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
@@ -3677,6 +3725,8 @@
"tar": ["tar@7.5.2", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg=="],
"tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="],
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
"terracotta": ["terracotta@1.0.6", "", { "dependencies": { "solid-use": "^0.9.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-yVrmT/Lg6a3tEbeYEJH8ksb1PYkR5FA9k5gr1TchaSNIiA2ZWs5a+koEbePXwlBP0poaV7xViZ/v50bQFcMgqw=="],
@@ -3741,6 +3791,8 @@
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"turbo": ["turbo@2.5.6", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.6", "turbo-darwin-arm64": "2.5.6", "turbo-linux-64": "2.5.6", "turbo-linux-arm64": "2.5.6", "turbo-windows-64": "2.5.6", "turbo-windows-arm64": "2.5.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w=="],
"turbo-darwin-64": ["turbo-darwin-64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-3C1xEdo4aFwMJAPvtlPqz1Sw/+cddWIOmsalHFMrsqqydcptwBfu26WW2cDm3u93bUzMbBJ8k3zNKFqxJ9ei2A=="],
@@ -3973,8 +4025,6 @@
"@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"@agentclientprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
"@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
@@ -4077,6 +4127,8 @@
"@expressive-code/plugin-shiki/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
"@gitlab/gitlab-ai-provider/openai": ["openai@6.16.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg=="],
"@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@hey-api/json-schema-ref-parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
@@ -4305,6 +4357,10 @@
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
@@ -4409,6 +4465,10 @@
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
"opencode/drizzle-kit": ["drizzle-kit@0.31.8", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="],
"opencode/drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="],
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
"opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="],
@@ -4439,6 +4499,8 @@
"postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"prebuild-install/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
@@ -4487,6 +4549,10 @@
"tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
"tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
@@ -4941,6 +5007,8 @@
"babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
@@ -5015,6 +5083,8 @@
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"opencode/drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
@@ -5041,6 +5111,8 @@
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"tw-to-css/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"tw-to-css/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
@@ -5187,6 +5259,56 @@
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"opencode/drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
"opencode/drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
"opencode/drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
"opencode/drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
"opencode/drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
"opencode/drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
"opencode/drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
"opencode/drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
"opencode/drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
"opencode/drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
"opencode/drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
"opencode/drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
"opencode/drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
"opencode/drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
"opencode/drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
"opencode/drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
"opencode/drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
"opencode/drizzle-kit/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
"opencode/drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
"opencode/drizzle-kit/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
"opencode/drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
"opencode/drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
"opencode/drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
"opencode/drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
"opencode/drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1768569498,
"narHash": "sha256-bB6Nt99Cj8Nu5nIUq0GLmpiErIT5KFshMQJGMZwgqUo=",
"lastModified": 1768393167,
"narHash": "sha256-n2063BRjHde6DqAz2zavhOOiLUwA3qXt7jQYHyETjX8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "be5afa0fcb31f0a96bf9ecba05a516c66fcd8114",
"rev": "2f594d5af95d4fdac67fba60376ec11e482041cb",
"type": "github"
},
"original": {

View File

@@ -4,6 +4,10 @@ const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
export const EMAILOCTOPUS_API_KEY = new sst.Secret("EMAILOCTOPUS_API_KEY")
const ADMIN_SECRET = new sst.Secret("ADMIN_SECRET")
const DISCORD_SUPPORT_BOT_TOKEN = new sst.Secret("DISCORD_SUPPORT_BOT_TOKEN")
const DISCORD_SUPPORT_CHANNEL_ID = new sst.Secret("DISCORD_SUPPORT_CHANNEL_ID")
const FEISHU_APP_ID = new sst.Secret("FEISHU_APP_ID")
const FEISHU_APP_SECRET = new sst.Secret("FEISHU_APP_SECRET")
const bucket = new sst.cloudflare.Bucket("Bucket")
export const api = new sst.cloudflare.Worker("Api", {
@@ -13,7 +17,16 @@ export const api = new sst.cloudflare.Worker("Api", {
WEB_DOMAIN: domain,
},
url: true,
link: [bucket, GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, ADMIN_SECRET],
link: [
bucket,
GITHUB_APP_ID,
GITHUB_APP_PRIVATE_KEY,
ADMIN_SECRET,
DISCORD_SUPPORT_BOT_TOKEN,
DISCORD_SUPPORT_CHANNEL_ID,
FEISHU_APP_ID,
FEISHU_APP_SECRET,
],
transform: {
worker: (args) => {
args.logpush = true

View File

@@ -101,15 +101,26 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
const zenProduct = new stripe.Product("ZenBlack", {
name: "OpenCode Black",
})
const zenPrice = new stripe.Price("ZenBlackPrice", {
const zenPriceProps = {
product: zenProduct.id,
unitAmount: 20000,
currency: "usd",
recurring: {
interval: "month",
intervalCount: 1,
},
}
const zenPrice200 = new stripe.Price("ZenBlackPrice", { ...zenPriceProps, unitAmount: 20000 })
const zenPrice100 = new stripe.Price("ZenBlack100Price", { ...zenPriceProps, unitAmount: 10000 })
const zenPrice20 = new stripe.Price("ZenBlack20Price", { ...zenPriceProps, unitAmount: 2000 })
const ZEN_BLACK_PRICE = new sst.Linkable("ZEN_BLACK_PRICE", {
properties: {
product: zenProduct.id,
plan200: zenPrice200.id,
plan100: zenPrice100.id,
plan20: zenPrice20.id,
},
})
const ZEN_BLACK_LIMITS = new sst.Secret("ZEN_BLACK_LIMITS")
const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS1"),
@@ -121,7 +132,6 @@ const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS7"),
new sst.Secret("ZEN_MODELS8"),
]
const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
const STRIPE_PUBLISHABLE_KEY = new sst.Secret("STRIPE_PUBLISHABLE_KEY")
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
@@ -164,7 +174,8 @@ new sst.cloudflare.x.SolidStart("Console", {
EMAILOCTOPUS_API_KEY,
AWS_SES_ACCESS_KEY_ID,
AWS_SES_SECRET_ACCESS_KEY,
ZEN_BLACK,
ZEN_BLACK_PRICE,
ZEN_BLACK_LIMITS,
new sst.Secret("ZEN_SESSION_SECRET"),
...ZEN_MODELS,
...($dev

View File

@@ -6,7 +6,7 @@ export const domain = (() => {
export const zoneID = "430ba34c138cfb5360826c4909f99be8"
new cloudflare.RegionalHostname("RegionalHostname", {
new cloudflxare.RegionalHostname("RegionalHostname", {
hostname: domain,
regionKey: "us",
zoneId: zoneID,

View File

@@ -1,8 +1,17 @@
{
"nodeModules": {
"x86_64-linux": "sha256-80+b7FwUy4mRWTzEjPrBWuR5Um67I1Rn4U/n/s/lBjs=",
"aarch64-linux": "sha256-xH/Grwh3b+HWsUkKN8LMcyMaMcmnIJYlgp38WJCat5E=",
"aarch64-darwin": "sha256-Izv6PE9gNaeYYfcqDwjTU/WYtD1y+j65annwvLzkMD8=",
"x86_64-darwin": "sha256-EG1Z0uAeyFiOeVsv0Sz1sa8/mdXuw/uvbYYrkFR3EAg="
<<<<<<< HEAD
"x86_64-linux": "sha256-H8QVUC5shGI97Ut/wDSYsSuprHpwssJ1MHSHojn+zNI=",
"aarch64-linux": "sha256-4BlpH/oIXRJEjkQydXDv1oi1Yx7li3k1dKHUy2/Gb10=",
"aarch64-darwin": "sha256-IOgZ/LP4lvFX3OlalaFuQFYAEFwP+lxz3BRwvu4Hmj4=",
"x86_64-darwin": "sha256-CHrE2z+LqY2WXTQeGWG5LNMF1AY4UGSwViJAy4IwIVw="
=======
"x86_64-linux": "sha256-9QHW6Ue9VO1VKsu6sg4gRtxgifQGNJlfVVXaa0Uc0XQ=",
<<<<<<< HEAD
"aarch64-darwin": "sha256-IOgZ/LP4lvFX3OlalaFuQFYAEFwP+lxz3BRwvu4Hmj4="
>>>>>>> 6e0a58c50 (Update Nix flake.lock and x86_64-linux hash)
=======
"aarch64-darwin": "sha256-G8tTkuUSFQNOmjbu6cIi6qeyNWtGogtUVNi2CSgcgX0="
>>>>>>> 8a0e3e909 (Update aarch64-darwin hash)
}
}

View File

@@ -28,6 +28,7 @@
"@kobalte/core": "0.13.11",
"@types/luxon": "3.7.1",
"@types/node": "22.13.9",
"@types/semver": "7.7.1",
"@tsconfig/node22": "22.0.2",
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
@@ -66,6 +67,7 @@
"@tsconfig/bun": "catalog:",
"husky": "9.1.7",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.17.23",
"turbo": "2.5.6"
},

View File

@@ -1,9 +1,15 @@
## Debugging
- To test the opencode app, use the playwright MCP server, the app is already
running at http://localhost:3000
- NEVER try to restart the app, or the server process, EVER.
## Local Dev
- `opencode dev web` proxies `https://app.opencode.ai`, so local UI/CSS changes will not show there.
- For local UI changes, run the backend and app dev servers separately.
- Backend (from `packages/opencode`): `bun run --conditions=browser ./src/index.ts serve --port 4096`
- App (from `packages/app`): `bun dev -- --port 4444`
- Open `http://localhost:4444` to verify UI changes (it targets the backend at `http://localhost:4096`).
## SolidJS
- Always prefer `createStore` over multiple `createSignal` calls
@@ -11,3 +17,14 @@
## Tool Calling
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
## Browser Automation
Use `agent-browser` for web automation. Run `agent-browser --help` for all commands.
Core workflow:
1. `agent-browser open <url>` - Navigate to page
2. `agent-browser snapshot -i` - Get interactive elements with refs (@e1, @e2)
3. `agent-browser click @e1` / `fill @e2 "text"` - Interact using refs
4. Re-snapshot after page changes

View File

@@ -31,18 +31,20 @@ Your app is ready to be deployed!
## E2E Testing
The Playwright runner expects the app already running at `http://localhost:3000`.
Playwright starts the Vite dev server automatically via `webServer`, and UI tests need an opencode backend (defaults to `localhost:4096`).
Use the local runner to create a temp sandbox, seed data, and run the tests.
```bash
bun add -D @playwright/test
bunx playwright install
bun run test:e2e
bun run test:e2e:local
bun run test:e2e:local -- --grep "settings"
```
Environment options:
- `PLAYWRIGHT_BASE_URL` (default: `http://localhost:3000`)
- `PLAYWRIGHT_PORT` (default: `3000`)
- `PLAYWRIGHT_SERVER_HOST` / `PLAYWRIGHT_SERVER_PORT` (backend address, default: `localhost:4096`)
- `PLAYWRIGHT_PORT` (Vite dev server port, default: `3000`)
- `PLAYWRIGHT_BASE_URL` (override base URL, default: `http://localhost:<PLAYWRIGHT_PORT>`)
## Deployment

View File

@@ -0,0 +1,35 @@
import { test, expect } from "./fixtures"
import { modKey } from "./utils"
test("smoke file viewer renders real file content", async ({ page, gotoSession }) => {
await gotoSession()
const sep = process.platform === "win32" ? "\\" : "/"
const file = ["packages", "app", "package.json"].join(sep)
await page.keyboard.press(`${modKey}+P`)
const dialog = page.getByRole("dialog")
await expect(dialog).toBeVisible()
const input = dialog.getByRole("textbox").first()
await input.fill(file)
const fileItem = dialog
.locator(
'[data-slot="list-item"][data-key^="file:"][data-key*="packages"][data-key*="app"][data-key$="package.json"]',
)
.first()
await expect(fileItem).toBeVisible()
await fileItem.click()
await expect(dialog).toHaveCount(0)
const tab = page.getByRole("tab", { name: "package.json" })
await expect(tab).toBeVisible()
await tab.click()
const code = page.locator('[data-component="code"]').first()
await expect(code).toBeVisible()
await expect(code.getByText("@opencode-ai/app")).toBeVisible()
})

View File

@@ -0,0 +1,43 @@
import { test, expect } from "./fixtures"
import { promptSelector } from "./utils"
test("smoke model selection updates prompt footer", async ({ page, gotoSession }) => {
await gotoSession()
await page.locator(promptSelector).click()
await page.keyboard.type("/model")
const command = page.locator('[data-slash-id="model.choose"]')
await expect(command).toBeVisible()
await command.hover()
await page.keyboard.press("Enter")
const dialog = page.getByRole("dialog")
await expect(dialog).toBeVisible()
const input = dialog.getByRole("textbox").first()
const selected = dialog.locator('[data-slot="list-item"][data-selected="true"]').first()
await expect(selected).toBeVisible()
const other = dialog.locator('[data-slot="list-item"]:not([data-selected="true"])').first()
const target = (await other.count()) > 0 ? other : selected
const key = await target.getAttribute("data-key")
if (!key) throw new Error("Failed to resolve model key from list item")
const name = (await target.locator("span").first().innerText()).trim()
const model = key.split(":").slice(1).join(":")
await input.fill(model)
const item = dialog.locator(`[data-slot="list-item"][data-key="${key}"]`)
await expect(item).toBeVisible()
await item.click()
await expect(dialog).toHaveCount(0)
const form = page.locator(promptSelector).locator("xpath=ancestor::form[1]")
await expect(form.locator('[data-component="button"]').filter({ hasText: name }).first()).toBeVisible()
})

View File

@@ -0,0 +1,26 @@
import { test, expect } from "./fixtures"
import { promptSelector } from "./utils"
test("smoke @mention inserts file pill token", async ({ page, gotoSession }) => {
await gotoSession()
await page.locator(promptSelector).click()
const sep = process.platform === "win32" ? "\\" : "/"
const file = ["packages", "app", "package.json"].join(sep)
const filePattern = /packages[\\/]+app[\\/]+\s*package\.json/
await page.keyboard.type(`@${file}`)
const suggestion = page.getByRole("button", { name: filePattern }).first()
await expect(suggestion).toBeVisible()
await suggestion.hover()
await page.keyboard.press("Tab")
const pill = page.locator(`${promptSelector} [data-type="file"]`).first()
await expect(pill).toBeVisible()
await expect(pill).toHaveAttribute("data-path", filePattern)
await page.keyboard.type(" ok")
await expect(page.locator(promptSelector)).toContainText("ok")
})

View File

@@ -0,0 +1,22 @@
import { test, expect } from "./fixtures"
import { promptSelector } from "./utils"
test("smoke /open opens file picker dialog", async ({ page, gotoSession }) => {
await gotoSession()
await page.locator(promptSelector).click()
await page.keyboard.type("/open")
const command = page.locator('[data-slash-id="file.open"]')
await expect(command).toBeVisible()
await command.hover()
await page.keyboard.press("Enter")
const dialog = page.getByRole("dialog")
await expect(dialog).toBeVisible()
await expect(dialog.getByRole("textbox").first()).toBeVisible()
await page.keyboard.press("Escape")
await expect(dialog).toHaveCount(0)
})

View File

@@ -0,0 +1,62 @@
import { test, expect } from "./fixtures"
import { promptSelector } from "./utils"
function sessionIDFromUrl(url: string) {
const match = /\/session\/([^/?#]+)/.exec(url)
return match?.[1]
}
test("can send a prompt and receive a reply", async ({ page, sdk, gotoSession }) => {
test.setTimeout(120_000)
const pageErrors: string[] = []
const onPageError = (err: Error) => {
pageErrors.push(err.message)
}
page.on("pageerror", onPageError)
await gotoSession()
const token = `E2E_OK_${Date.now()}`
const prompt = page.locator(promptSelector)
await prompt.click()
await page.keyboard.type(`Reply with exactly: ${token}`)
await page.keyboard.press("Enter")
await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
const sessionID = (() => {
const id = sessionIDFromUrl(page.url())
if (!id) throw new Error(`Failed to parse session id from url: ${page.url()}`)
return id
})()
try {
await expect
.poll(
async () => {
const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
return messages
.filter((m) => m.info.role === "assistant")
.flatMap((m) => m.parts)
.filter((p) => p.type === "text")
.map((p) => p.text)
.join("\n")
},
{ timeout: 90_000 },
)
.toContain(token)
const reply = page.locator('[data-slot="session-turn-summary-section"]').filter({ hasText: token }).first()
await expect(reply).toBeVisible({ timeout: 90_000 })
} finally {
page.off("pageerror", onPageError)
await sdk.session.delete({ sessionID }).catch(() => undefined)
}
if (pageErrors.length > 0) {
throw new Error(`Page error(s):\n${pageErrors.join("\n")}`)
}
})

View File

@@ -0,0 +1,44 @@
import { test, expect } from "./fixtures"
import { modKey } from "./utils"
test("smoke settings dialog opens, switches tabs, closes", async ({ page, gotoSession }) => {
await gotoSession()
const dialog = page.getByRole("dialog")
await page.keyboard.press(`${modKey}+Comma`).catch(() => undefined)
const opened = await dialog
.waitFor({ state: "visible", timeout: 3000 })
.then(() => true)
.catch(() => false)
if (!opened) {
await page.getByRole("button", { name: "Settings" }).first().click()
await expect(dialog).toBeVisible()
}
await dialog.getByRole("tab", { name: "Shortcuts" }).click()
await expect(dialog.getByRole("button", { name: "Reset to defaults" })).toBeVisible()
await expect(dialog.getByPlaceholder("Search shortcuts")).toBeVisible()
await page.keyboard.press("Escape")
const closed = await dialog
.waitFor({ state: "detached", timeout: 1500 })
.then(() => true)
.catch(() => false)
if (closed) return
await page.keyboard.press("Escape")
const closedSecond = await dialog
.waitFor({ state: "detached", timeout: 1500 })
.then(() => true)
.catch(() => false)
if (closedSecond) return
await page.locator('[data-component="dialog-overlay"]').click({ position: { x: 5, y: 5 } })
await expect(dialog).toHaveCount(0)
})

View File

@@ -0,0 +1,25 @@
import { test, expect } from "./fixtures"
import { promptSelector, terminalSelector, terminalToggleKey } from "./utils"
test("smoke terminal mounts and can create a second tab", async ({ page, gotoSession }) => {
await gotoSession()
const terminals = page.locator(terminalSelector)
const opened = await terminals.first().isVisible()
if (!opened) {
await page.keyboard.press(terminalToggleKey)
}
await expect(terminals.first()).toBeVisible()
await expect(terminals.first().locator("textarea")).toHaveCount(1)
await expect(terminals).toHaveCount(1)
// Ghostty captures a lot of keybinds when focused; move focus back
// to the app shell before triggering `terminal.new`.
await page.locator(promptSelector).click()
await page.keyboard.press("Control+Alt+T")
await expect(terminals).toHaveCount(2)
await expect(terminals.nth(1).locator("textarea")).toHaveCount(1)
})

View File

@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"types": ["node"]
},
"include": ["./**/*.ts"]
}

View File

@@ -4,10 +4,10 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OpenCode</title>
<link rel="icon" type="image/png" href="/favicon-96x96-v2.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon-v2.svg" />
<link rel="shortcut icon" href="/favicon-v2.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-v2.png" />
<link rel="icon" type="image/png" href="/favicon-96x96-v3.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon-v3.svg" />
<link rel="shortcut icon" href="/favicon-v3.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-v3.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#F8F7F7" />
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.1.26",
"version": "1.1.34",
"description": "",
"type": "module",
"exports": {
@@ -15,6 +15,7 @@
"serve": "vite preview",
"test": "playwright test",
"test:e2e": "playwright test",
"test:e2e:local": "bun script/e2e-local.ts",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report e2e/playwright-report"
},
@@ -41,6 +42,7 @@
"@shikijs/transformers": "3.9.2",
"@solid-primitives/active-element": "2.1.3",
"@solid-primitives/audio": "1.4.2",
"@solid-primitives/i18n": "2.2.1",
"@solid-primitives/event-bus": "1.1.2",
"@solid-primitives/media": "2.3.3",
"@solid-primitives/resize-observer": "2.1.3",

View File

@@ -1 +0,0 @@
../../ui/src/assets/favicon/apple-touch-icon-v2.png

View File

@@ -0,0 +1 @@
../../ui/src/assets/favicon/apple-touch-icon-v3.png

View File

@@ -1 +0,0 @@
../../ui/src/assets/favicon/favicon-96x96-v2.png

View File

@@ -0,0 +1 @@
../../ui/src/assets/favicon/favicon-96x96-v3.png

View File

@@ -1 +0,0 @@
../../ui/src/assets/favicon/favicon-v2.ico

View File

@@ -1 +0,0 @@
../../ui/src/assets/favicon/favicon-v2.svg

View File

@@ -0,0 +1 @@
../../ui/src/assets/favicon/favicon-v3.ico

View File

@@ -0,0 +1 @@
../../ui/src/assets/favicon/favicon-v3.svg

View File

@@ -0,0 +1,143 @@
import fs from "node:fs/promises"
import net from "node:net"
import os from "node:os"
import path from "node:path"
async function freePort() {
return await new Promise<number>((resolve, reject) => {
const server = net.createServer()
server.once("error", reject)
server.listen(0, () => {
const address = server.address()
if (!address || typeof address === "string") {
server.close(() => reject(new Error("Failed to acquire a free port")))
return
}
server.close((err) => {
if (err) {
reject(err)
return
}
resolve(address.port)
})
})
})
}
async function waitForHealth(url: string) {
const timeout = Date.now() + 120_000
const errors: string[] = []
while (Date.now() < timeout) {
const result = await fetch(url)
.then((r) => ({ ok: r.ok, error: undefined }))
.catch((error) => ({
ok: false,
error: error instanceof Error ? error.message : String(error),
}))
if (result.ok) return
if (result.error) errors.push(result.error)
await new Promise((r) => setTimeout(r, 250))
}
const last = errors.length ? ` (last error: ${errors[errors.length - 1]})` : ""
throw new Error(`Timed out waiting for server health: ${url}${last}`)
}
const appDir = process.cwd()
const repoDir = path.resolve(appDir, "../..")
const opencodeDir = path.join(repoDir, "packages", "opencode")
const modelsJson = path.join(opencodeDir, "test", "tool", "fixtures", "models-api.json")
const extraArgs = (() => {
const args = process.argv.slice(2)
if (args[0] === "--") return args.slice(1)
return args
})()
const [serverPort, webPort] = await Promise.all([freePort(), freePort()])
const sandbox = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-"))
const serverEnv = {
...process.env,
MODELS_DEV_API_JSON: modelsJson,
OPENCODE_DISABLE_MODELS_FETCH: "true",
OPENCODE_DISABLE_SHARE: "true",
OPENCODE_DISABLE_LSP_DOWNLOAD: "true",
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true",
OPENCODE_TEST_HOME: path.join(sandbox, "home"),
XDG_DATA_HOME: path.join(sandbox, "share"),
XDG_CACHE_HOME: path.join(sandbox, "cache"),
XDG_CONFIG_HOME: path.join(sandbox, "config"),
XDG_STATE_HOME: path.join(sandbox, "state"),
OPENCODE_E2E_PROJECT_DIR: repoDir,
OPENCODE_E2E_SESSION_TITLE: "E2E Session",
OPENCODE_E2E_MESSAGE: "Seeded for UI e2e",
OPENCODE_E2E_MODEL: "opencode/gpt-5-nano",
OPENCODE_CLIENT: "app",
} satisfies Record<string, string>
const runnerEnv = {
...serverEnv,
PLAYWRIGHT_SERVER_HOST: "127.0.0.1",
PLAYWRIGHT_SERVER_PORT: String(serverPort),
VITE_OPENCODE_SERVER_HOST: "127.0.0.1",
VITE_OPENCODE_SERVER_PORT: String(serverPort),
PLAYWRIGHT_PORT: String(webPort),
} satisfies Record<string, string>
const seed = Bun.spawn(["bun", "script/seed-e2e.ts"], {
cwd: opencodeDir,
env: serverEnv,
stdout: "inherit",
stderr: "inherit",
})
const seedExit = await seed.exited
if (seedExit !== 0) {
process.exit(seedExit)
}
Object.assign(process.env, serverEnv)
process.env.AGENT = "1"
process.env.OPENCODE = "1"
const log = await import("../../opencode/src/util/log")
const install = await import("../../opencode/src/installation")
await log.Log.init({
print: true,
dev: install.Installation.isLocal(),
level: "WARN",
})
const servermod = await import("../../opencode/src/server/server")
const inst = await import("../../opencode/src/project/instance")
const server = servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" })
console.log(`opencode server listening on http://127.0.0.1:${serverPort}`)
const result = await (async () => {
try {
await waitForHealth(`http://127.0.0.1:${serverPort}/global/health`)
const runner = Bun.spawn(["bun", "test:e2e", ...extraArgs], {
cwd: appDir,
env: runnerEnv,
stdout: "inherit",
stderr: "inherit",
})
return { code: await runner.exited }
} catch (error) {
return { error }
} finally {
await inst.Instance.disposeAll()
await server.stop()
}
})()
if ("error" in result) {
console.error(result.error)
process.exit(1)
}
process.exit(result.code)

View File

@@ -6,6 +6,7 @@ import { Font } from "@opencode-ai/ui/font"
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
import { CodeComponentProvider } from "@opencode-ai/ui/context/code"
import { I18nProvider } from "@opencode-ai/ui/context"
import { Diff } from "@opencode-ai/ui/diff"
import { Code } from "@opencode-ai/ui/code"
import { ThemeProvider } from "@opencode-ai/ui/theme"
@@ -14,12 +15,16 @@ import { PermissionProvider } from "@/context/permission"
import { LayoutProvider } from "@/context/layout"
import { GlobalSDKProvider } from "@/context/global-sdk"
import { ServerProvider, useServer } from "@/context/server"
import { SettingsProvider } from "@/context/settings"
import { TerminalProvider } from "@/context/terminal"
import { PromptProvider } from "@/context/prompt"
import { FileProvider } from "@/context/file"
import { CommentsProvider } from "@/context/comments"
import { NotificationProvider } from "@/context/notification"
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
import { CommandProvider } from "@/context/command"
import { LanguageProvider, useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { Logo } from "@opencode-ai/ui/logo"
import Layout from "@/pages/layout"
import DirectoryLayout from "@/pages/directory-layout"
@@ -31,26 +36,40 @@ const Home = lazy(() => import("@/pages/home"))
const Session = lazy(() => import("@/pages/session"))
const Loading = () => <div class="size-full" />
function UiI18nBridge(props: ParentProps) {
const language = useLanguage()
return <I18nProvider value={{ locale: language.locale, t: language.t }}>{props.children}</I18nProvider>
}
declare global {
interface Window {
__OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string }
}
}
function MarkedProviderWithNativeParser(props: ParentProps) {
const platform = usePlatform()
return <MarkedProvider nativeParser={platform.parseMarkdown}>{props.children}</MarkedProvider>
}
export function AppBaseProviders(props: ParentProps) {
return (
<MetaProvider>
<Font />
<ThemeProvider>
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
<DialogProvider>
<MarkedProvider>
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>{props.children}</CodeComponentProvider>
</DiffComponentProvider>
</MarkedProvider>
</DialogProvider>
</ErrorBoundary>
<LanguageProvider>
<UiI18nBridge>
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
<DialogProvider>
<MarkedProviderWithNativeParser>
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>{props.children}</CodeComponentProvider>
</DiffComponentProvider>
</MarkedProviderWithNativeParser>
</DialogProvider>
</ErrorBoundary>
</UiI18nBridge>
</LanguageProvider>
</ThemeProvider>
</MetaProvider>
)
@@ -82,15 +101,17 @@ export function AppInterface(props: { defaultUrl?: string }) {
<GlobalSyncProvider>
<Router
root={(props) => (
<PermissionProvider>
<LayoutProvider>
<NotificationProvider>
<CommandProvider>
<Layout>{props.children}</Layout>
</CommandProvider>
</NotificationProvider>
</LayoutProvider>
</PermissionProvider>
<SettingsProvider>
<PermissionProvider>
<LayoutProvider>
<NotificationProvider>
<CommandProvider>
<Layout>{props.children}</Layout>
</CommandProvider>
</NotificationProvider>
</LayoutProvider>
</PermissionProvider>
</SettingsProvider>
)}
>
<Route
@@ -105,16 +126,20 @@ export function AppInterface(props: { defaultUrl?: string }) {
<Route path="/" component={() => <Navigate href="session" />} />
<Route
path="/session/:id?"
component={() => (
<TerminalProvider>
<FileProvider>
<PromptProvider>
<Suspense fallback={<Loading />}>
<Session />
</Suspense>
</PromptProvider>
</FileProvider>
</TerminalProvider>
component={(p) => (
<Show when={p.params.id ?? "new"}>
<TerminalProvider>
<FileProvider>
<PromptProvider>
<CommentsProvider>
<Suspense fallback={<Loading />}>
<Session />
</Suspense>
</CommentsProvider>
</PromptProvider>
</FileProvider>
</TerminalProvider>
</Show>
)}
/>
</Route>

View File

@@ -14,6 +14,7 @@ import { iife } from "@opencode-ai/util/iife"
import { createMemo, Match, onCleanup, onMount, Switch } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { Link } from "@/components/link"
import { useLanguage } from "@/context/language"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { usePlatform } from "@/context/platform"
@@ -25,13 +26,14 @@ export function DialogConnectProvider(props: { provider: string }) {
const globalSync = useGlobalSync()
const globalSDK = useGlobalSDK()
const platform = usePlatform()
const language = useLanguage()
const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === props.provider)!)
const methods = createMemo(
() =>
globalSync.data.provider_auth[props.provider] ?? [
{
type: "api",
label: "API key",
label: language.t("provider.connect.method.apiKey"),
},
],
)
@@ -44,6 +46,12 @@ export function DialogConnectProvider(props: { provider: string }) {
const method = createMemo(() => (store.methodIndex !== undefined ? methods().at(store.methodIndex!) : undefined))
const methodLabel = (value?: { type?: string; label?: string }) => {
if (!value) return ""
if (value.type === "api") return language.t("provider.connect.method.apiKey")
return value.label ?? ""
}
async function selectMethod(index: number) {
const method = methods()[index]
setStore(
@@ -112,8 +120,8 @@ export function DialogConnectProvider(props: { provider: string }) {
showToast({
variant: "success",
icon: "circle-check",
title: `${provider().name} connected`,
description: `${provider().name} models are now available to use.`,
title: language.t("provider.connect.toast.connected.title", { provider: provider().name }),
description: language.t("provider.connect.toast.connected.description", { provider: provider().name }),
})
}
@@ -135,23 +143,35 @@ export function DialogConnectProvider(props: { provider: string }) {
}
return (
<Dialog title={<IconButton tabIndex={-1} icon="arrow-left" variant="ghost" onClick={goBack} />}>
<Dialog
title={
<IconButton
tabIndex={-1}
icon="arrow-left"
variant="ghost"
onClick={goBack}
aria-label={language.t("common.goBack")}
/>
}
>
<div class="flex flex-col gap-6 px-2.5 pb-3">
<div class="px-2.5 flex gap-4 items-center">
<ProviderIcon id={props.provider as IconName} class="size-5 shrink-0 icon-strong-base" />
<div class="text-16-medium text-text-strong">
<Switch>
<Match when={props.provider === "anthropic" && method()?.label?.toLowerCase().includes("max")}>
Login with Claude Pro/Max
{language.t("provider.connect.title.anthropicProMax")}
</Match>
<Match when={true}>Connect {provider().name}</Match>
<Match when={true}>{language.t("provider.connect.title", { provider: provider().name })}</Match>
</Switch>
</div>
</div>
<div class="px-2.5 pb-10 flex flex-col gap-6">
<Switch>
<Match when={store.methodIndex === undefined}>
<div class="text-14-regular text-text-base">Select login method for {provider().name}.</div>
<div class="text-14-regular text-text-base">
{language.t("provider.connect.selectMethod", { provider: provider().name })}
</div>
<div class="">
<List
ref={(ref) => {
@@ -167,9 +187,9 @@ export function DialogConnectProvider(props: { provider: string }) {
{(i) => (
<div class="w-full flex items-center gap-x-2">
<div class="w-4 h-2 rounded-[1px] bg-input-base shadow-xs-border-base flex items-center justify-center">
<div class="w-2.5 h-0.5 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
<div class="w-2.5 h-0.5 ml-0 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
</div>
<span>{i.label}</span>
<span>{methodLabel(i)}</span>
</div>
)}
</List>
@@ -179,7 +199,7 @@ export function DialogConnectProvider(props: { provider: string }) {
<div class="text-14-regular text-text-base">
<div class="flex items-center gap-x-2">
<Spinner />
<span>Authorization in progress...</span>
<span>{language.t("provider.connect.status.inProgress")}</span>
</div>
</div>
</Match>
@@ -187,7 +207,7 @@ export function DialogConnectProvider(props: { provider: string }) {
<div class="text-14-regular text-text-base">
<div class="flex items-center gap-x-2">
<Icon name="circle-ban-sign" class="text-icon-critical-base" />
<span>Authorization failed: {store.error}</span>
<span>{language.t("provider.connect.status.failed", { error: store.error ?? "" })}</span>
</div>
</div>
</Match>
@@ -206,7 +226,7 @@ export function DialogConnectProvider(props: { provider: string }) {
const apiKey = formData.get("apiKey") as string
if (!apiKey?.trim()) {
setFormStore("error", "API key is required")
setFormStore("error", language.t("provider.connect.apiKey.required"))
return
}
@@ -227,25 +247,23 @@ export function DialogConnectProvider(props: { provider: string }) {
<Match when={provider().id === "opencode"}>
<div class="flex flex-col gap-4">
<div class="text-14-regular text-text-base">
OpenCode Zen gives you access to a curated set of reliable optimized models for coding
agents.
{language.t("provider.connect.opencodeZen.line1")}
</div>
<div class="text-14-regular text-text-base">
With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more.
{language.t("provider.connect.opencodeZen.line2")}
</div>
<div class="text-14-regular text-text-base">
Visit{" "}
{language.t("provider.connect.opencodeZen.visit.prefix")}
<Link href="https://opencode.ai/zen" tabIndex={-1}>
opencode.ai/zen
</Link>{" "}
to collect your API key.
{language.t("provider.connect.opencodeZen.visit.link")}
</Link>
{language.t("provider.connect.opencodeZen.visit.suffix")}
</div>
</div>
</Match>
<Match when={true}>
<div class="text-14-regular text-text-base">
Enter your {provider().name} API key to connect your account and use {provider().name} models
in OpenCode.
{language.t("provider.connect.apiKey.description", { provider: provider().name })}
</div>
</Match>
</Switch>
@@ -253,8 +271,8 @@ export function DialogConnectProvider(props: { provider: string }) {
<TextField
autofocus
type="text"
label={`${provider().name} API key`}
placeholder="API key"
label={language.t("provider.connect.apiKey.label", { provider: provider().name })}
placeholder={language.t("provider.connect.apiKey.placeholder")}
name="apiKey"
value={formStore.value}
onChange={setFormStore.bind(null, "value")}
@@ -262,7 +280,7 @@ export function DialogConnectProvider(props: { provider: string }) {
error={formStore.error}
/>
<Button class="w-auto" type="submit" size="large" variant="primary">
Submit
{language.t("common.submit")}
</Button>
</form>
</div>
@@ -292,35 +310,44 @@ export function DialogConnectProvider(props: { provider: string }) {
const code = formData.get("code") as string
if (!code?.trim()) {
setFormStore("error", "Authorization code is required")
setFormStore("error", language.t("provider.connect.oauth.code.required"))
return
}
setFormStore("error", undefined)
const { error } = await globalSDK.client.provider.oauth.callback({
providerID: props.provider,
method: store.methodIndex,
code,
})
if (!error) {
const result = await globalSDK.client.provider.oauth
.callback({
providerID: props.provider,
method: store.methodIndex,
code,
})
.then((value) =>
value.error ? { ok: false as const, error: value.error } : { ok: true as const },
)
.catch((error) => ({ ok: false as const, error }))
if (result.ok) {
await complete()
return
}
setFormStore("error", "Invalid authorization code")
const message = result.error instanceof Error ? result.error.message : String(result.error)
setFormStore("error", message || language.t("provider.connect.oauth.code.invalid"))
}
return (
<div class="flex flex-col gap-6">
<div class="text-14-regular text-text-base">
Visit <Link href={store.authorization!.url}>this link</Link> to collect your authorization
code to connect your account and use {provider().name} models in OpenCode.
{language.t("provider.connect.oauth.code.visit.prefix")}
<Link href={store.authorization!.url}>
{language.t("provider.connect.oauth.code.visit.link")}
</Link>
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
</div>
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
<TextField
autofocus
type="text"
label={`${method()?.label} authorization code`}
placeholder="Authorization code"
label={language.t("provider.connect.oauth.code.label", { method: method()?.label ?? "" })}
placeholder={language.t("provider.connect.oauth.code.placeholder")}
name="code"
value={formStore.value}
onChange={setFormStore.bind(null, "value")}
@@ -328,7 +355,7 @@ export function DialogConnectProvider(props: { provider: string }) {
error={formStore.error}
/>
<Button class="w-auto" type="submit" size="large" variant="primary">
Submit
{language.t("common.submit")}
</Button>
</form>
</div>
@@ -346,13 +373,22 @@ export function DialogConnectProvider(props: { provider: string }) {
})
onMount(async () => {
const result = await globalSDK.client.provider.oauth.callback({
providerID: props.provider,
method: store.methodIndex,
})
if (result.error) {
// TODO: show error
dialog.close()
if (store.authorization?.url) {
platform.openLink(store.authorization.url)
}
const result = await globalSDK.client.provider.oauth
.callback({
providerID: props.provider,
method: store.methodIndex,
})
.then((value) =>
value.error ? { ok: false as const, error: value.error } : { ok: true as const },
)
.catch((error) => ({ ok: false as const, error }))
if (!result.ok) {
const message = result.error instanceof Error ? result.error.message : String(result.error)
setStore("state", "error")
setStore("error", message)
return
}
await complete()
@@ -361,13 +397,22 @@ export function DialogConnectProvider(props: { provider: string }) {
return (
<div class="flex flex-col gap-6">
<div class="text-14-regular text-text-base">
Visit <Link href={store.authorization!.url}>this link</Link> and enter the code below to
connect your account and use {provider().name} models in OpenCode.
{language.t("provider.connect.oauth.auto.visit.prefix")}
<Link href={store.authorization!.url}>
{language.t("provider.connect.oauth.auto.visit.link")}
</Link>
{language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })}
</div>
<TextField label="Confirmation code" class="font-mono" value={code()} readOnly copyable />
<TextField
label={language.t("provider.connect.oauth.auto.confirmationCode")}
class="font-mono"
value={code()}
readOnly
copyable
/>
<div class="text-14-regular text-text-base flex items-center gap-4">
<Spinner />
<span>Waiting for authorization...</span>
<span>{language.t("provider.connect.status.waiting")}</span>
</div>
</div>
)

View File

@@ -6,15 +6,19 @@ import { Icon } from "@opencode-ai/ui/icon"
import { createMemo, createSignal, For, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { type LocalProject, getAvatarColors } from "@/context/layout"
import { getFilename } from "@opencode-ai/util/path"
import { Avatar } from "@opencode-ai/ui/avatar"
import { useLanguage } from "@/context/language"
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
export function DialogEditProject(props: { project: LocalProject }) {
const dialog = useDialog()
const globalSDK = useGlobalSDK()
const globalSync = useGlobalSync()
const language = useLanguage()
const folderName = createMemo(() => getFilename(props.project.worktree))
const defaultName = createMemo(() => props.project.name || folderName())
@@ -23,6 +27,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
name: defaultName(),
color: props.project.icon?.color || "pink",
iconUrl: props.project.icon?.override || "",
startup: props.project.commands?.start ?? "",
saving: false,
})
@@ -67,34 +72,49 @@ export function DialogEditProject(props: { project: LocalProject }) {
async function handleSubmit(e: SubmitEvent) {
e.preventDefault()
if (!props.project.id) return
setStore("saving", true)
const name = store.name.trim() === folderName() ? "" : store.name.trim()
await globalSDK.client.project.update({
projectID: props.project.id,
const start = store.startup.trim()
if (props.project.id && props.project.id !== "global") {
await globalSDK.client.project.update({
projectID: props.project.id,
directory: props.project.worktree,
name,
icon: { color: store.color, override: store.iconUrl },
commands: { start },
})
globalSync.project.icon(props.project.worktree, store.iconUrl || undefined)
setStore("saving", false)
dialog.close()
return
}
globalSync.project.meta(props.project.worktree, {
name,
icon: { color: store.color, override: store.iconUrl },
icon: { color: store.color, override: store.iconUrl || undefined },
commands: { start: start || undefined },
})
setStore("saving", false)
dialog.close()
}
return (
<Dialog title="Edit project" class="w-full max-w-[480px] mx-auto">
<form onSubmit={handleSubmit} class="flex flex-col gap-6 p-6">
<Dialog title={language.t("dialog.project.edit.title")} class="w-full max-w-[480px] mx-auto">
<form onSubmit={handleSubmit} class="flex flex-col gap-6 p-6 pt-0">
<div class="flex flex-col gap-4">
<TextField
autofocus
type="text"
label="Name"
label={language.t("dialog.project.edit.name")}
placeholder={folderName()}
value={store.name}
onChange={(v) => setStore("name", v)}
/>
<div class="flex flex-col gap-2">
<label class="text-12-medium text-text-weak">Icon</label>
<label class="text-12-medium text-text-weak">{language.t("dialog.project.edit.icon")}</label>
<div class="flex gap-3 items-start">
<div class="relative" onMouseEnter={() => setIconHover(true)} onMouseLeave={() => setIconHover(false)}>
<div
@@ -123,11 +143,16 @@ export function DialogEditProject(props: { project: LocalProject }) {
fallback={store.name || defaultName()}
{...getAvatarColors(store.color)}
class="size-full"
style={{ "font-size": "32px" }}
/>
</div>
}
>
<img src={store.iconUrl} alt="Project icon" class="size-full object-cover" />
<img
src={store.iconUrl}
alt={language.t("dialog.project.edit.icon.alt")}
class="size-full object-cover"
/>
</Show>
</div>
<div
@@ -171,19 +196,22 @@ export function DialogEditProject(props: { project: LocalProject }) {
</div>
<input id="icon-upload" type="file" accept="image/*" class="hidden" onChange={handleInputChange} />
<div class="flex flex-col gap-1.5 text-12-regular text-text-weak self-center">
<span>Recommended size 128x128px</span>
<span>{language.t("dialog.project.edit.icon.hint")}</span>
<span>{language.t("dialog.project.edit.icon.recommended")}</span>
</div>
</div>
</div>
<Show when={!store.iconUrl}>
<div class="flex flex-col gap-2">
<label class="text-12-medium text-text-weak">Color</label>
<label class="text-12-medium text-text-weak">{language.t("dialog.project.edit.color")}</label>
<div class="flex gap-1.5">
<For each={AVATAR_COLOR_KEYS}>
{(color) => (
<button
type="button"
aria-label={language.t("dialog.project.edit.color.select", { color })}
aria-pressed={store.color === color}
classList={{
"flex items-center justify-center size-10 p-0.5 rounded-lg overflow-hidden transition-colors cursor-default": true,
"bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover":
@@ -204,14 +232,25 @@ export function DialogEditProject(props: { project: LocalProject }) {
</div>
</div>
</Show>
<TextField
multiline
label={language.t("dialog.project.edit.worktree.startup")}
description={language.t("dialog.project.edit.worktree.startup.description")}
placeholder={language.t("dialog.project.edit.worktree.startup.placeholder")}
value={store.startup}
onChange={(v) => setStore("startup", v)}
spellcheck={false}
class="max-h-40 w-full font-mono text-xs no-scrollbar"
/>
</div>
<div class="flex justify-end gap-2">
<Button type="button" variant="ghost" size="large" onClick={() => dialog.close()}>
Cancel
{language.t("common.cancel")}
</Button>
<Button type="submit" variant="primary" size="large" disabled={store.saving}>
{store.saving ? "Saving..." : "Save"}
{store.saving ? language.t("common.saving") : language.t("common.save")}
</Button>
</div>
</form>

View File

@@ -9,6 +9,7 @@ import { List } from "@opencode-ai/ui/list"
import { extractPromptFromParts } from "@/utils/prompt"
import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client"
import { base64Encode } from "@opencode-ai/util/encode"
import { useLanguage } from "@/context/language"
interface ForkableMessage {
id: string
@@ -27,6 +28,7 @@ export const DialogFork: Component = () => {
const sdk = useSDK()
const prompt = usePrompt()
const dialog = useDialog()
const language = useLanguage()
const messages = createMemo((): ForkableMessage[] => {
const sessionID = params.id
@@ -59,7 +61,10 @@ export const DialogFork: Component = () => {
if (!sessionID) return
const parts = sync.data.part[item.id] ?? []
const restored = extractPromptFromParts(parts, { directory: sdk.directory })
const restored = extractPromptFromParts(parts, {
directory: sdk.directory,
attachmentName: language.t("common.attachment"),
})
dialog.close()
@@ -73,11 +78,11 @@ export const DialogFork: Component = () => {
}
return (
<Dialog title="Fork from message">
<Dialog title={language.t("command.session.fork")}>
<List
class="flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
search={{ placeholder: "Search", autofocus: true }}
emptyMessage="No messages to fork from"
search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.fork.empty")}
key={(x) => x.id}
items={messages}
filterKeys={["text"]}

View File

@@ -4,14 +4,16 @@ import { Switch } from "@opencode-ai/ui/switch"
import type { Component } from "solid-js"
import { useLocal } from "@/context/local"
import { popularProviders } from "@/hooks/use-providers"
import { useLanguage } from "@/context/language"
export const DialogManageModels: Component = () => {
const local = useLocal()
const language = useLanguage()
return (
<Dialog title="Manage models" description="Customize which models appear in the model selector.">
<Dialog title={language.t("dialog.model.manage")} description={language.t("dialog.model.manage.description")}>
<List
search={{ placeholder: "Search models", autofocus: true }}
emptyMessage="No model results"
search={{ placeholder: language.t("dialog.model.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.model.empty")}
key={(x) => `${x?.provider?.id}:${x?.id}`}
items={local.model.list()}
filterKeys={["provider.name", "name", "id"]}

View File

@@ -3,9 +3,11 @@ import { Dialog } from "@opencode-ai/ui/dialog"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { List } from "@opencode-ai/ui/list"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import fuzzysort from "fuzzysort"
import { createMemo } from "solid-js"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language"
interface DialogSelectDirectoryProps {
title?: string
@@ -17,74 +19,166 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
const sync = useGlobalSync()
const sdk = useGlobalSDK()
const dialog = useDialog()
const language = useLanguage()
const home = createMemo(() => sync.data.path.home)
const root = createMemo(() => sync.data.path.home || sync.data.path.directory)
const start = createMemo(() => sync.data.path.home || sync.data.path.directory)
const cache = new Map<string, Promise<Array<{ name: string; absolute: string }>>>()
function normalize(input: string) {
const v = input.replaceAll("\\", "/")
if (v.startsWith("//") && !v.startsWith("///")) return "//" + v.slice(2).replace(/\/+/g, "/")
return v.replace(/\/+/g, "/")
}
function normalizeDriveRoot(input: string) {
const v = normalize(input)
if (/^[A-Za-z]:$/.test(v)) return v + "/"
return v
}
function trimTrailing(input: string) {
const v = normalizeDriveRoot(input)
if (v === "/") return v
if (v === "//") return v
if (/^[A-Za-z]:\/$/.test(v)) return v
return v.replace(/\/+$/, "")
}
function join(base: string | undefined, rel: string) {
const b = (base ?? "").replace(/[\\/]+$/, "")
const r = rel.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")
const b = trimTrailing(base ?? "")
const r = trimTrailing(rel).replace(/^\/+/, "")
if (!b) return r
if (!r) return b
if (b.endsWith("/")) return b + r
return b + "/" + r
}
function display(rel: string) {
const full = join(root(), rel)
function rootOf(input: string) {
const v = normalizeDriveRoot(input)
if (v.startsWith("//")) return "//"
if (v.startsWith("/")) return "/"
if (/^[A-Za-z]:\//.test(v)) return v.slice(0, 3)
return ""
}
function display(path: string) {
const full = trimTrailing(path)
const h = home()
if (!h) return full
if (full === h) return "~"
if (full.startsWith(h + "/") || full.startsWith(h + "\\")) {
return "~" + full.slice(h.length)
}
const hn = trimTrailing(h)
const lc = full.toLowerCase()
const hc = hn.toLowerCase()
if (lc === hc) return "~"
if (lc.startsWith(hc + "/")) return "~" + full.slice(hn.length)
return full
}
function normalizeQuery(query: string) {
function scoped(filter: string) {
const base = start()
if (!base) return
const raw = normalizeDriveRoot(filter.trim())
if (!raw) return { directory: trimTrailing(base), path: "" }
const h = home()
if (raw === "~") return { directory: trimTrailing(h ?? base), path: "" }
if (raw.startsWith("~/")) return { directory: trimTrailing(h ?? base), path: raw.slice(2) }
if (!query) return query
if (query.startsWith("~/")) return query.slice(2)
if (h) {
const lc = query.toLowerCase()
const hc = h.toLowerCase()
if (lc === hc || lc.startsWith(hc + "/") || lc.startsWith(hc + "\\")) {
return query.slice(h.length).replace(/^[\\/]+/, "")
}
}
return query
const root = rootOf(raw)
if (root) return { directory: trimTrailing(root), path: raw.slice(root.length) }
return { directory: trimTrailing(base), path: raw }
}
async function fetchDirs(query: string) {
const directory = root()
if (!directory) return [] as string[]
async function dirs(dir: string) {
const key = trimTrailing(dir)
const existing = cache.get(key)
if (existing) return existing
const results = await sdk.client.find
.files({ directory, query, type: "directory", limit: 50 })
const request = sdk.client.file
.list({ directory: key, path: "" })
.then((x) => x.data ?? [])
.catch(() => [])
.then((nodes) =>
nodes
.filter((n) => n.type === "directory")
.map((n) => ({
name: n.name,
absolute: trimTrailing(normalizeDriveRoot(n.absolute)),
})),
)
return results.map((x) => x.replace(/[\\/]+$/, ""))
cache.set(key, request)
return request
}
async function match(dir: string, query: string, limit: number) {
const items = await dirs(dir)
if (!query) return items.slice(0, limit).map((x) => x.absolute)
return fuzzysort.go(query, items, { key: "name", limit }).map((x) => x.obj.absolute)
}
const directories = async (filter: string) => {
const query = normalizeQuery(filter.trim())
return fetchDirs(query)
const input = scoped(filter)
if (!input) return [] as string[]
const raw = normalizeDriveRoot(filter.trim())
const isPath = raw.startsWith("~") || !!rootOf(raw) || raw.includes("/")
const query = normalizeDriveRoot(input.path)
if (!isPath) {
const results = await sdk.client.find
.files({ directory: input.directory, query, type: "directory", limit: 50 })
.then((x) => x.data ?? [])
.catch(() => [])
return results.map((rel) => join(input.directory, rel)).slice(0, 50)
}
const segments = query.replace(/^\/+/, "").split("/")
const head = segments.slice(0, segments.length - 1).filter((x) => x && x !== ".")
const tail = segments[segments.length - 1] ?? ""
const cap = 12
const branch = 4
let paths = [input.directory]
for (const part of head) {
if (part === "..") {
paths = paths.map((p) => {
const v = trimTrailing(p)
if (v === "/") return v
if (/^[A-Za-z]:\/$/.test(v)) return v
const i = v.lastIndexOf("/")
if (i <= 0) return "/"
return v.slice(0, i)
})
continue
}
const next = (await Promise.all(paths.map((p) => match(p, part, branch)))).flat()
paths = Array.from(new Set(next)).slice(0, cap)
if (paths.length === 0) return [] as string[]
}
const out = (await Promise.all(paths.map((p) => match(p, tail, 50)))).flat()
return Array.from(new Set(out)).slice(0, 50)
}
function resolve(rel: string) {
const absolute = join(root(), rel)
function resolve(absolute: string) {
props.onSelect(props.multiple ? [absolute] : absolute)
dialog.close()
}
return (
<Dialog title={props.title ?? "Open project"}>
<Dialog title={props.title ?? language.t("command.project.open")}>
<List
search={{ placeholder: "Search folders", autofocus: true }}
emptyMessage="No folders found"
search={{ placeholder: language.t("dialog.directory.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.directory.empty")}
loadingMessage={language.t("common.loading")}
items={directories}
key={(x) => x}
onSelect={(path) => {
@@ -92,12 +186,12 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
resolve(path)
}}
>
{(rel) => {
const path = display(rel)
{(absolute) => {
const path = display(absolute)
return (
<div class="w-full flex items-center justify-between rounded-md">
<div class="flex items-center gap-x-3 grow min-w-0">
<FileIcon node={{ path: rel, type: "directory" }} class="shrink-0 size-4" />
<FileIcon node={{ path: absolute, type: "directory" }} class="shrink-0 size-4" />
<div class="flex items-center text-14-regular min-w-0">
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
{getDirectory(path)}

View File

@@ -9,6 +9,7 @@ import { createMemo, createSignal, onCleanup, Show } from "solid-js"
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
import { useLayout } from "@/context/layout"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
type EntryType = "command" | "file"
@@ -18,23 +19,31 @@ type Entry = {
title: string
description?: string
keybind?: string
category: "Commands" | "Files"
category: string
option?: CommandOption
path?: string
}
export function DialogSelectFile() {
const command = useCommand()
const language = useLanguage()
const layout = useLayout()
const file = useFile()
const dialog = useDialog()
const params = useParams()
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey()))
const view = createMemo(() => layout.view(sessionKey()))
const tabs = createMemo(() => layout.tabs(sessionKey))
const view = createMemo(() => layout.view(sessionKey))
const state = { cleanup: undefined as (() => void) | void, committed: false }
const [grouped, setGrouped] = createSignal(false)
const common = ["session.new", "session.previous", "session.next", "terminal.toggle", "review.toggle"]
const common = [
"session.new",
"workspace.new",
"session.previous",
"session.next",
"terminal.toggle",
"review.toggle",
]
const limit = 5
const allowed = createMemo(() =>
@@ -49,7 +58,7 @@ export function DialogSelectFile() {
title: option.title,
description: option.description,
keybind: option.keybind,
category: "Commands",
category: language.t("palette.group.commands"),
option,
})
@@ -57,7 +66,7 @@ export function DialogSelectFile() {
id: "file:" + path,
type: "file",
title: path,
category: "Files",
category: language.t("palette.group.files"),
path,
})
@@ -136,8 +145,14 @@ export function DialogSelectFile() {
return (
<Dialog class="pt-3 pb-0 !max-h-[480px]">
<List
search={{ placeholder: "Search files and commands", autofocus: true, hideIcon: true, class: "pl-3 pr-2 !mb-0" }}
emptyMessage="No results found"
search={{
placeholder: language.t("palette.search.placeholder"),
autofocus: true,
hideIcon: true,
class: "pl-3 pr-2 !mb-0",
}}
emptyMessage={language.t("palette.empty")}
loadingMessage={language.t("common.loading")}
items={items}
key={(item) => item.id}
filterKeys={["title", "description", "category"]}

View File

@@ -4,10 +4,12 @@ import { useSDK } from "@/context/sdk"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Switch } from "@opencode-ai/ui/switch"
import { useLanguage } from "@/context/language"
export const DialogSelectMcp: Component = () => {
const sync = useSync()
const sdk = useSDK()
const language = useLanguage()
const [loading, setLoading] = createSignal<string | null>(null)
const items = createMemo(() =>
@@ -34,10 +36,13 @@ export const DialogSelectMcp: Component = () => {
const totalCount = createMemo(() => items().length)
return (
<Dialog title="MCPs" description={`${enabledCount()} of ${totalCount()} enabled`}>
<Dialog
title={language.t("dialog.mcp.title")}
description={language.t("dialog.mcp.description", { enabled: enabledCount(), total: totalCount() })}
>
<List
search={{ placeholder: "Search", autofocus: true }}
emptyMessage="No MCPs configured"
search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.mcp.empty")}
key={(x) => x?.name ?? ""}
items={items}
filterKeys={["name", "status"]}
@@ -60,19 +65,19 @@ export const DialogSelectMcp: Component = () => {
<div class="flex items-center gap-2">
<span class="truncate">{i.name}</span>
<Show when={status() === "connected"}>
<span class="text-11-regular text-text-weaker">connected</span>
<span class="text-11-regular text-text-weaker">{language.t("mcp.status.connected")}</span>
</Show>
<Show when={status() === "failed"}>
<span class="text-11-regular text-text-weaker">failed</span>
<span class="text-11-regular text-text-weaker">{language.t("mcp.status.failed")}</span>
</Show>
<Show when={status() === "needs_auth"}>
<span class="text-11-regular text-text-weaker">needs auth</span>
<span class="text-11-regular text-text-weaker">{language.t("mcp.status.needs_auth")}</span>
</Show>
<Show when={status() === "disabled"}>
<span class="text-11-regular text-text-weaker">disabled</span>
<span class="text-11-regular text-text-weaker">{language.t("mcp.status.disabled")}</span>
</Show>
<Show when={loading() === i.name}>
<span class="text-11-regular text-text-weak">...</span>
<span class="text-11-regular text-text-weak">{language.t("common.loading.ellipsis")}</span>
</Show>
</div>
<Show when={error()}>

View File

@@ -5,16 +5,20 @@ import type { IconName } from "@opencode-ai/ui/icons/provider"
import { List, type ListRef } from "@opencode-ai/ui/list"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { Tag } from "@opencode-ai/ui/tag"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { type Component, onCleanup, onMount, Show } from "solid-js"
import { useLocal } from "@/context/local"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { DialogConnectProvider } from "./dialog-connect-provider"
import { DialogSelectProvider } from "./dialog-select-provider"
import { ModelTooltip } from "./model-tooltip"
import { useLanguage } from "@/context/language"
export const DialogSelectModelUnpaid: Component = () => {
const local = useLocal()
const dialog = useDialog()
const providers = useProviders()
const language = useLanguage()
let listRef: ListRef | undefined
const handleKey = (e: KeyboardEvent) => {
@@ -30,14 +34,30 @@ export const DialogSelectModelUnpaid: Component = () => {
})
return (
<Dialog title="Select model">
<Dialog title={language.t("dialog.model.select.title")}>
<div class="flex flex-col gap-3 px-2.5">
<div class="text-14-medium text-text-base px-2.5">Free models provided by OpenCode</div>
<div class="text-14-medium text-text-base px-2.5">{language.t("dialog.model.unpaid.freeModels.title")}</div>
<List
ref={(ref) => (listRef = ref)}
items={local.model.list}
current={local.model.current()}
key={(x) => `${x.provider.id}:${x.id}`}
itemWrapper={(item, node) => (
<Tooltip
class="w-full"
placement="right-start"
gutter={12}
value={
<ModelTooltip
model={item}
latest={item.latest}
free={item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)}
/>
}
>
{node}
</Tooltip>
)}
onSelect={(x) => {
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
recent: true,
@@ -48,9 +68,9 @@ export const DialogSelectModelUnpaid: Component = () => {
{(i) => (
<div class="w-full flex items-center gap-x-2.5">
<span>{i.name}</span>
<Tag>Free</Tag>
<Tag>{language.t("model.tag.free")}</Tag>
<Show when={i.latest}>
<Tag>Latest</Tag>
<Tag>{language.t("model.tag.latest")}</Tag>
</Show>
</div>
)}
@@ -61,7 +81,7 @@ export const DialogSelectModelUnpaid: Component = () => {
<div class="px-1.5 pb-1.5">
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
<div class="w-full flex flex-col items-start gap-4 px-1.5 pt-4 pb-4">
<div class="px-2 text-14-medium text-text-base">Add more models from popular providers</div>
<div class="px-2 text-14-medium text-text-base">{language.t("dialog.model.unpaid.addMore.title")}</div>
<div class="w-full">
<List
class="w-full px-0"
@@ -83,10 +103,10 @@ export const DialogSelectModelUnpaid: Component = () => {
<ProviderIcon data-slot="list-item-extra-icon" id={i.id as IconName} />
<span>{i.name}</span>
<Show when={i.id === "opencode"}>
<Tag>Recommended</Tag>
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
</Show>
<Show when={i.id === "anthropic"}>
<div class="text-14-regular text-text-weak">Connect with Claude Pro/Max or API key</div>
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
</Show>
</div>
)}
@@ -99,7 +119,7 @@ export const DialogSelectModelUnpaid: Component = () => {
dialog.show(() => <DialogSelectProvider />)
}}
>
View all providers
{language.t("dialog.provider.viewAll")}
</Button>
</div>
</div>

View File

@@ -1,21 +1,27 @@
import { Popover as Kobalte } from "@kobalte/core/popover"
import { Component, createMemo, createSignal, JSX, Show } from "solid-js"
import { Component, ComponentProps, createMemo, createSignal, JSX, Show, ValidComponent } from "solid-js"
import { useLocal } from "@/context/local"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { popularProviders } from "@/hooks/use-providers"
import { Button } from "@opencode-ai/ui/button"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Tag } from "@opencode-ai/ui/tag"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { DialogSelectProvider } from "./dialog-select-provider"
import { DialogManageModels } from "./dialog-manage-models"
import { ModelTooltip } from "./model-tooltip"
import { useLanguage } from "@/context/language"
const ModelList: Component<{
provider?: string
class?: string
onSelect: () => void
action?: JSX.Element
}> = (props) => {
const local = useLocal()
const language = useLanguage()
const models = createMemo(() =>
local.model
@@ -27,8 +33,8 @@ const ModelList: Component<{
return (
<List
class={`flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0 ${props.class ?? ""}`}
search={{ placeholder: "Search models", autofocus: true }}
emptyMessage="No model results"
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={local.model.current()}
@@ -36,14 +42,28 @@ const ModelList: Component<{
sortBy={(a, b) => a.name.localeCompare(b.name)}
groupBy={(x) => x.provider.name}
sortGroupsBy={(a, b) => {
if (a.category === "Recent" && b.category !== "Recent") return -1
if (b.category === "Recent" && a.category !== "Recent") return 1
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
class="w-full"
placement="right-start"
gutter={12}
value={
<ModelTooltip
model={item}
latest={item.latest}
free={item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)}
/>
}
>
{node}
</Tooltip>
)}
onSelect={(x) => {
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
recent: true,
@@ -55,10 +75,10 @@ const ModelList: Component<{
<div class="w-full flex items-center gap-x-2 text-13-regular">
<span class="truncate">{i.name}</span>
<Show when={i.provider.id === "opencode" && (!i.cost || i.cost?.input === 0)}>
<Tag>Free</Tag>
<Tag>{language.t("model.tag.free")}</Tag>
</Show>
<Show when={i.latest}>
<Tag>Latest</Tag>
<Tag>{language.t("model.tag.latest")}</Tag>
</Show>
</div>
)}
@@ -66,19 +86,45 @@ const ModelList: Component<{
)
}
export const ModelSelectorPopover: Component<{
export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
provider?: string
children: JSX.Element
}> = (props) => {
children?: JSX.Element
triggerAs?: T
triggerProps?: ComponentProps<T>
}) {
const [open, setOpen] = createSignal(false)
const dialog = useDialog()
const handleManage = () => {
setOpen(false)
dialog.show(() => <DialogManageModels />)
}
const language = useLanguage()
return (
<Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
<Kobalte.Trigger as="div">{props.children}</Kobalte.Trigger>
<Kobalte.Trigger as={props.triggerAs ?? "div"} {...(props.triggerProps as any)}>
{props.children}
</Kobalte.Trigger>
<Kobalte.Portal>
<Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden">
<Kobalte.Title class="sr-only">Select model</Kobalte.Title>
<ModelList provider={props.provider} onSelect={() => setOpen(false)} class="p-1" />
<Kobalte.Title class="sr-only">{language.t("dialog.model.select.title")}</Kobalte.Title>
<ModelList
provider={props.provider}
onSelect={() => setOpen(false)}
class="p-1"
action={
<IconButton
icon="sliders"
variant="ghost"
iconSize="normal"
class="size-6"
aria-label={language.t("dialog.model.manage")}
title={language.t("dialog.model.manage")}
onClick={handleManage}
/>
}
/>
</Kobalte.Content>
</Kobalte.Portal>
</Kobalte>
@@ -87,10 +133,11 @@ export const ModelSelectorPopover: Component<{
export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
const dialog = useDialog()
const language = useLanguage()
return (
<Dialog
title="Select model"
title={language.t("dialog.model.select.title")}
action={
<Button
class="h-7 -my-1 text-14-medium"
@@ -98,7 +145,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
tabIndex={-1}
onClick={() => dialog.show(() => <DialogSelectProvider />)}
>
Connect provider
{language.t("command.provider.connect")}
</Button>
}
>
@@ -108,7 +155,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
class="ml-3 mt-5 mb-6 text-text-base self-start"
onClick={() => dialog.show(() => <DialogManageModels />)}
>
Manage models
{language.t("dialog.model.manage")}
</Button>
</Dialog>
)

View File

@@ -7,28 +7,38 @@ import { Tag } from "@opencode-ai/ui/tag"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { IconName } from "@opencode-ai/ui/icons/provider"
import { DialogConnectProvider } from "./dialog-connect-provider"
import { useLanguage } from "@/context/language"
export const DialogSelectProvider: Component = () => {
const dialog = useDialog()
const providers = useProviders()
const language = useLanguage()
const popularGroup = () => language.t("dialog.provider.group.popular")
const otherGroup = () => language.t("dialog.provider.group.other")
return (
<Dialog title="Connect provider">
<Dialog title={language.t("command.provider.connect")}>
<List
search={{ placeholder: "Search providers", autofocus: true }}
search={{ placeholder: language.t("dialog.provider.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.provider.empty")}
activeIcon="plus-small"
key={(x) => x?.id}
items={providers.all}
items={() => {
language.locale()
return providers.all()
}}
filterKeys={["id", "name"]}
groupBy={(x) => (popularProviders.includes(x.id) ? "Popular" : "Other")}
groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())}
sortBy={(a, b) => {
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
return a.name.localeCompare(b.name)
}}
sortGroupsBy={(a, b) => {
if (a.category === "Popular" && b.category !== "Popular") return -1
if (b.category === "Popular" && a.category !== "Popular") return 1
const popular = popularGroup()
if (a.category === popular && b.category !== popular) return -1
if (b.category === popular && a.category !== popular) return 1
return 0
}}
onSelect={(x) => {
@@ -41,10 +51,16 @@ export const DialogSelectProvider: Component = () => {
<ProviderIcon data-slot="list-item-extra-icon" id={i.id as IconName} />
<span>{i.name}</span>
<Show when={i.id === "opencode"}>
<Tag>Recommended</Tag>
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
</Show>
<Show when={i.id === "anthropic"}>
<div class="text-14-regular text-text-weak">Connect with Claude Pro/Max or API key</div>
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
</Show>
<Show when={i.id === "openai"}>
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.openai.note")}</div>
</Show>
<Show when={i.id.startsWith("github-copilot")}>
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.copilot.note")}</div>
</Show>
</div>
)}

View File

@@ -10,6 +10,7 @@ import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/serv
import { usePlatform } from "@/context/platform"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { useNavigate } from "@solidjs/router"
import { useLanguage } from "@/context/language"
type ServerStatus = { healthy: boolean; version?: string }
@@ -30,6 +31,7 @@ export function DialogSelectServer() {
const dialog = useDialog()
const server = useServer()
const platform = usePlatform()
const language = useLanguage()
const [store, setStore] = createStore({
url: "",
adding: false,
@@ -109,7 +111,7 @@ export function DialogSelectServer() {
setStore("adding", false)
if (!result.healthy) {
setStore("error", "Could not connect to server")
setStore("error", language.t("dialog.server.add.error"))
return
}
@@ -122,11 +124,11 @@ export function DialogSelectServer() {
}
return (
<Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
<Dialog title={language.t("dialog.server.title")} description={language.t("dialog.server.description")}>
<div class="flex flex-col gap-4 pb-4">
<List
search={{ placeholder: "Search servers", autofocus: true }}
emptyMessage="No servers yet"
search={{ placeholder: language.t("dialog.server.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.server.empty")}
items={sortedItems}
key={(x) => x}
current={current()}
@@ -156,6 +158,7 @@ export function DialogSelectServer() {
icon="circle-x"
variant="ghost"
class="bg-transparent transition-opacity shrink-0 hover:scale-110"
aria-label={language.t("dialog.server.action.remove")}
onClick={(e) => {
e.stopPropagation()
handleRemove(i)
@@ -168,16 +171,16 @@ export function DialogSelectServer() {
<div class="mt-6 px-3 flex flex-col gap-1.5">
<div class="px-3">
<h3 class="text-14-regular text-text-weak">Add a server</h3>
<h3 class="text-14-regular text-text-weak">{language.t("dialog.server.add.title")}</h3>
</div>
<form onSubmit={handleSubmit}>
<div class="flex items-start gap-2">
<div class="flex-1 min-w-0 h-auto">
<TextField
type="text"
label="Server URL"
label={language.t("dialog.server.add.url")}
hideLabel
placeholder="http://localhost:4096"
placeholder={language.t("dialog.server.add.placeholder")}
value={store.url}
onChange={(v) => {
setStore("url", v)
@@ -188,7 +191,7 @@ export function DialogSelectServer() {
/>
</div>
<Button type="submit" variant="secondary" icon="plus-small" size="large" disabled={store.adding}>
{store.adding ? "Checking..." : "Add"}
{store.adding ? language.t("dialog.server.add.checking") : language.t("dialog.server.add.button")}
</Button>
</div>
</form>
@@ -197,10 +200,8 @@ export function DialogSelectServer() {
<Show when={isDesktop}>
<div class="mt-6 px-3 flex flex-col gap-1.5">
<div class="px-3">
<h3 class="text-14-regular text-text-weak">Default server</h3>
<p class="text-12-regular text-text-weak mt-1">
Connect to this server on app launch instead of starting a local server. Requires restart.
</p>
<h3 class="text-14-regular text-text-weak">{language.t("dialog.server.default.title")}</h3>
<p class="text-12-regular text-text-weak mt-1">{language.t("dialog.server.default.description")}</p>
</div>
<div class="flex items-center gap-2 px-3 py-2">
<Show
@@ -208,7 +209,9 @@ export function DialogSelectServer() {
fallback={
<Show
when={server.url}
fallback={<span class="text-14-regular text-text-weak">No server selected</span>}
fallback={
<span class="text-14-regular text-text-weak">{language.t("dialog.server.default.none")}</span>
}
>
<Button
variant="secondary"
@@ -218,7 +221,7 @@ export function DialogSelectServer() {
defaultUrlActions.refetch(server.url)
}}
>
Set current server as default
{language.t("dialog.server.default.set")}
</Button>
</Show>
}
@@ -234,7 +237,7 @@ export function DialogSelectServer() {
defaultUrlActions.refetch()
}}
>
Clear
{language.t("dialog.server.default.clear")}
</Button>
</Show>
</div>

View File

@@ -0,0 +1,112 @@
import { Component } from "solid-js"
import { Dialog } from "@opencode-ai/ui/dialog"
import { Tabs } from "@opencode-ai/ui/tabs"
import { Icon } from "@opencode-ai/ui/icon"
import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { SettingsGeneral } from "./settings-general"
import { SettingsKeybinds } from "./settings-keybinds"
import { SettingsPermissions } from "./settings-permissions"
import { SettingsProviders } from "./settings-providers"
import { SettingsModels } from "./settings-models"
import { SettingsAgents } from "./settings-agents"
import { SettingsCommands } from "./settings-commands"
import { SettingsMcp } from "./settings-mcp"
export const DialogSettings: Component = () => {
const language = useLanguage()
const platform = usePlatform()
return (
<Dialog size="x-large">
<Tabs orientation="vertical" variant="settings" defaultValue="general" class="h-full settings-dialog">
<Tabs.List>
<div
style={{
display: "flex",
"flex-direction": "column",
"justify-content": "space-between",
height: "100%",
width: "100%",
}}
>
<div
style={{
display: "flex",
"flex-direction": "column",
gap: "12px",
width: "100%",
"padding-top": "12px",
}}
>
<Tabs.SectionTitle>{language.t("settings.section.desktop")}</Tabs.SectionTitle>
<div style={{ display: "flex", "flex-direction": "column", gap: "6px", width: "100%" }}>
<Tabs.Trigger value="general">
<Icon name="sliders" />
{language.t("settings.tab.general")}
</Tabs.Trigger>
<Tabs.Trigger value="shortcuts">
<Icon name="keyboard" />
{language.t("settings.tab.shortcuts")}
</Tabs.Trigger>
</div>
</div>
<div class="flex flex-col gap-1 pl-1 py-1 text-12-medium text-text-weak">
<span>OpenCode Desktop</span>
<span class="text-11-regular">v{platform.version}</span>
</div>
</div>
{/* <Tabs.SectionTitle>Server</Tabs.SectionTitle> */}
{/* <Tabs.Trigger value="permissions"> */}
{/* <Icon name="checklist" /> */}
{/* Permissions */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="providers"> */}
{/* <Icon name="server" /> */}
{/* Providers */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="models"> */}
{/* <Icon name="brain" /> */}
{/* Models */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="agents"> */}
{/* <Icon name="task" /> */}
{/* Agents */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="commands"> */}
{/* <Icon name="console" /> */}
{/* Commands */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="mcp"> */}
{/* <Icon name="mcp" /> */}
{/* MCP */}
{/* </Tabs.Trigger> */}
</Tabs.List>
<Tabs.Content value="general" class="no-scrollbar">
<SettingsGeneral />
</Tabs.Content>
<Tabs.Content value="shortcuts" class="no-scrollbar">
<SettingsKeybinds />
</Tabs.Content>
{/* <Tabs.Content value="permissions" class="no-scrollbar"> */}
{/* <SettingsPermissions /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="providers" class="no-scrollbar"> */}
{/* <SettingsProviders /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="models" class="no-scrollbar"> */}
{/* <SettingsModels /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="agents" class="no-scrollbar"> */}
{/* <SettingsAgents /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="commands" class="no-scrollbar"> */}
{/* <SettingsCommands /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="mcp" class="no-scrollbar"> */}
{/* <SettingsMcp /> */}
{/* </Tabs.Content> */}
</Tabs>
</Dialog>
)
}

View File

@@ -0,0 +1,91 @@
import { Show, type Component } from "solid-js"
import { useLanguage } from "@/context/language"
type InputKey = "text" | "image" | "audio" | "video" | "pdf"
type InputMap = Record<InputKey, boolean>
type ModelInfo = {
id: string
name: string
provider: {
name: string
}
capabilities?: {
reasoning: boolean
input: InputMap
}
modalities?: {
input: Array<string>
}
reasoning?: boolean
limit: {
context: number
}
}
export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean }> = (props) => {
const language = useLanguage()
const sourceName = (model: ModelInfo) => {
const value = `${model.id} ${model.name}`.toLowerCase()
if (/claude|anthropic/.test(value)) return language.t("model.provider.anthropic")
if (/gpt|o[1-4]|codex|openai/.test(value)) return language.t("model.provider.openai")
if (/gemini|palm|bard|google/.test(value)) return language.t("model.provider.google")
if (/grok|xai/.test(value)) return language.t("model.provider.xai")
if (/llama|meta/.test(value)) return language.t("model.provider.meta")
return model.provider.name
}
const inputLabel = (value: string) => {
if (value === "text") return language.t("model.input.text")
if (value === "image") return language.t("model.input.image")
if (value === "audio") return language.t("model.input.audio")
if (value === "video") return language.t("model.input.video")
if (value === "pdf") return language.t("model.input.pdf")
return value
}
const title = () => {
const tags: Array<string> = []
if (props.latest) tags.push(language.t("model.tag.latest"))
if (props.free) tags.push(language.t("model.tag.free"))
const suffix = tags.length ? ` (${tags.join(", ")})` : ""
return `${sourceName(props.model)} ${props.model.name}${suffix}`
}
const inputs = () => {
if (props.model.capabilities) {
const input = props.model.capabilities.input
const order: Array<InputKey> = ["text", "image", "audio", "video", "pdf"]
const entries = order.filter((key) => input[key]).map((key) => inputLabel(key))
return entries.length ? entries.join(", ") : undefined
}
const raw = props.model.modalities?.input
if (!raw) return
const entries = raw.map((value) => inputLabel(value))
return entries.length ? entries.join(", ") : undefined
}
const reasoning = () => {
if (props.model.capabilities)
return props.model.capabilities.reasoning
? language.t("model.tooltip.reasoning.allowed")
: language.t("model.tooltip.reasoning.none")
return props.model.reasoning
? language.t("model.tooltip.reasoning.allowed")
: language.t("model.tooltip.reasoning.none")
}
const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() })
return (
<div class="flex flex-col gap-1 py-1">
<div class="text-13-medium">{title()}</div>
<Show when={inputs()}>
{(value) => (
<div class="text-12-regular text-text-invert-base">
{language.t("model.tooltip.allows", { inputs: value() })}
</div>
)}
</Show>
<div class="text-12-regular text-text-invert-base">{reasoning()}</div>
<div class="text-12-regular text-text-invert-base">{context()}</div>
</div>
)
}

View File

@@ -30,6 +30,7 @@ import { useLayout } from "@/context/layout"
import { useSDK } from "@/context/sdk"
import { useNavigate, useParams } from "@solidjs/router"
import { useSync } from "@/context/sync"
import { useComments } from "@/context/comments"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
@@ -47,8 +48,10 @@ import { useProviders } from "@/hooks/use-providers"
import { useCommand } from "@/context/command"
import { Persist, persisted } from "@/utils/persist"
import { Identifier } from "@/utils/id"
import { Worktree as WorktreeState } from "@/utils/worktree"
import { SessionContextUsage } from "@/components/session-context-usage"
import { usePermission } from "@/context/permission"
import { useLanguage } from "@/context/language"
import { useGlobalSync } from "@/context/global-sync"
import { usePlatform } from "@/context/platform"
import { createOpencodeClient, type Message, type Part } from "@opencode-ai/sdk/v2/client"
@@ -59,40 +62,48 @@ import { base64Encode } from "@opencode-ai/util/encode"
const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]
const ACCEPTED_FILE_TYPES = [...ACCEPTED_IMAGE_TYPES, "application/pdf"]
type PendingPrompt = {
abort: AbortController
cleanup: VoidFunction
}
const pending = new Map<string, PendingPrompt>()
interface PromptInputProps {
class?: string
ref?: (el: HTMLDivElement) => void
newSessionWorktree?: string
onNewSessionWorktreeReset?: () => void
onSubmit?: () => void
}
const PLACEHOLDERS = [
"Fix a TODO in the codebase",
"What is the tech stack of this project?",
"Fix broken tests",
"Explain how authentication works",
"Find and fix security vulnerabilities",
"Add unit tests for the user service",
"Refactor this function to be more readable",
"What does this error mean?",
"Help me debug this issue",
"Generate API documentation",
"Optimize database queries",
"Add input validation",
"Create a new component for...",
"How do I deploy this project?",
"Review my code for best practices",
"Add error handling to this function",
"Explain this regex pattern",
"Convert this to TypeScript",
"Add logging throughout the codebase",
"What dependencies are outdated?",
"Help me write a migration script",
"Implement caching for this endpoint",
"Add pagination to this list",
"Create a CLI command for...",
"How do environment variables work here?",
]
const EXAMPLES = [
"prompt.example.1",
"prompt.example.2",
"prompt.example.3",
"prompt.example.4",
"prompt.example.5",
"prompt.example.6",
"prompt.example.7",
"prompt.example.8",
"prompt.example.9",
"prompt.example.10",
"prompt.example.11",
"prompt.example.12",
"prompt.example.13",
"prompt.example.14",
"prompt.example.15",
"prompt.example.16",
"prompt.example.17",
"prompt.example.18",
"prompt.example.19",
"prompt.example.20",
"prompt.example.21",
"prompt.example.22",
"prompt.example.23",
"prompt.example.24",
"prompt.example.25",
] as const
interface SlashCommand {
id: string
@@ -113,11 +124,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const files = useFile()
const prompt = usePrompt()
const layout = useLayout()
const comments = useComments()
const params = useParams()
const dialog = useDialog()
const providers = useProviders()
const command = useCommand()
const permission = usePermission()
const language = useLanguage()
let editorRef!: HTMLDivElement
let fileInputRef!: HTMLInputElement
let scrollRef!: HTMLDivElement
@@ -154,11 +167,25 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey()))
const activeFile = createMemo(() => {
const tab = tabs().active()
if (!tab) return
return files.pathFromTab(tab)
const tabs = createMemo(() => layout.tabs(sessionKey))
const view = createMemo(() => layout.view(sessionKey))
const recent = createMemo(() => {
const all = tabs().all()
const active = tabs().active()
const order = active ? [active, ...all.filter((x) => x !== active)] : all
const seen = new Set<string>()
const paths: string[] = []
for (const tab of order) {
const path = files.pathFromTab(tab)
if (!path) continue
if (seen.has(path)) continue
seen.add(path)
paths.push(path)
}
return paths
})
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
const status = createMemo(
@@ -184,7 +211,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
popover: null,
historyIndex: -1,
savedPrompt: null,
placeholder: Math.floor(Math.random() * PLACEHOLDERS.length),
placeholder: Math.floor(Math.random() * EXAMPLES.length),
dragging: false,
mode: "normal",
applyingHistory: false,
@@ -255,10 +282,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
createEffect(() => {
params.id
editorRef.focus()
if (params.id) return
const interval = setInterval(() => {
setStore("placeholder", (prev) => (prev + 1) % PLACEHOLDERS.length)
setStore("placeholder", (prev) => (prev + 1) % EXAMPLES.length)
}, 6500)
onCleanup(() => clearInterval(interval))
})
@@ -313,8 +339,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (fileItems.length > 0) {
showToast({
title: "Unsupported paste",
description: "Only images or PDFs can be pasted here.",
title: language.t("prompt.toast.pasteUnsupported.title"),
description: language.t("prompt.toast.pasteUnsupported.description"),
})
return
}
@@ -380,7 +406,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!isFocused()) setComposing(false)
})
type AtOption = { type: "agent"; name: string; display: string } | { type: "file"; path: string; display: string }
type AtOption =
| { type: "agent"; name: string; display: string }
| { type: "file"; path: string; display: string; recent?: boolean }
const agentList = createMemo(() =>
sync.data.agent
@@ -411,12 +439,30 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
} = useFilteredList<AtOption>({
items: async (query) => {
const agents = agentList()
const open = recent()
const seen = new Set(open)
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
const paths = await files.searchFilesAndDirectories(query)
const fileOptions: AtOption[] = paths.map((path) => ({ type: "file", path, display: path }))
return [...agents, ...fileOptions]
const fileOptions: AtOption[] = paths
.filter((path) => !seen.has(path))
.map((path) => ({ type: "file", path, display: path }))
return [...agents, ...pinned, ...fileOptions]
},
key: atKey,
filterKeys: ["display"],
groupBy: (item) => {
if (item.type === "agent") return "agent"
if (item.recent) return "recent"
return "file"
},
sortGroupsBy: (a, b) => {
const rank = (category: string) => {
if (category === "agent") return 0
if (category === "recent") return 1
return 2
}
return rank(a.category) - rank(b.category)
},
onSelect: handleAtSelect,
})
@@ -549,6 +595,25 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
})
const selectPopoverActive = () => {
if (store.popover === "at") {
const items = atFlat()
if (items.length === 0) return
const active = atActive()
const item = items.find((entry) => atKey(entry) === active) ?? items[0]
handleAtSelect(item)
return
}
if (store.popover === "slash") {
const items = slashFlat()
if (items.length === 0) return
const active = slashActive()
const item = items.find((entry) => entry.id === active) ?? items[0]
handleSlashSelect(item)
}
}
createEffect(
on(
() => prompt.current(),
@@ -789,12 +854,22 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
setStore("popover", null)
}
const abort = () =>
sdk.client.session
const abort = async () => {
const sessionID = params.id
if (!sessionID) return Promise.resolve()
const queued = pending.get(sessionID)
if (queued) {
queued.abort.abort()
queued.cleanup()
pending.delete(sessionID)
return Promise.resolve()
}
return sdk.client.session
.abort({
sessionID: params.id!,
sessionID,
})
.catch(() => {})
}
const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
const text = prompt
@@ -909,14 +984,24 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
return
}
if (store.popover && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter")) {
if (store.popover === "at") {
atOnKeyDown(event)
} else {
slashOnKeyDown(event)
if (store.popover) {
if (event.key === "Tab") {
selectPopoverActive()
event.preventDefault()
return
}
if (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter") {
if (store.popover === "at") {
atOnKeyDown(event)
event.preventDefault()
return
}
if (store.popover === "slash") {
slashOnKeyDown(event)
}
event.preventDefault()
return
}
event.preventDefault()
return
}
const ctrl = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey
@@ -998,8 +1083,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const currentAgent = local.agent.current()
if (!currentModel || !currentAgent) {
showToast({
title: "Select an agent and model",
description: "Choose an agent and model before sending a prompt.",
title: language.t("prompt.toast.modelAgentRequired.title"),
description: language.t("prompt.toast.modelAgentRequired.description"),
})
return
}
@@ -1010,7 +1095,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (data?.message) return data.message
}
if (err instanceof Error) return err.message
return "Request failed"
return language.t("common.requestFailed")
}
addToHistory(currentPrompt, mode)
@@ -1031,7 +1116,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
.then((x) => x.data)
.catch((err) => {
showToast({
title: "Failed to create worktree",
title: language.t("prompt.toast.worktreeCreateFailed.title"),
description: errorMessage(err),
})
return undefined
@@ -1039,11 +1124,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!createdWorktree?.directory) {
showToast({
title: "Failed to create worktree",
description: "Request failed",
title: language.t("prompt.toast.worktreeCreateFailed.title"),
description: language.t("common.requestFailed"),
})
return
}
WorktreeState.pending(createdWorktree.directory)
sessionDirectory = createdWorktree.directory
}
@@ -1071,7 +1157,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
.then((x) => x.data ?? undefined)
.catch((err) => {
showToast({
title: "Failed to create session",
title: language.t("prompt.toast.sessionCreateFailed.title"),
description: errorMessage(err),
})
return undefined
@@ -1080,6 +1166,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
if (!session) return
props.onSubmit?.()
const model = {
modelID: currentModel.id,
providerID: currentModel.provider.id,
@@ -1115,7 +1203,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
.catch((err) => {
showToast({
title: "Failed to send shell command",
title: language.t("prompt.toast.shellSendFailed.title"),
description: errorMessage(err),
})
restoreInput()
@@ -1147,7 +1235,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
.catch((err) => {
showToast({
title: "Failed to send command",
title: language.t("prompt.toast.commandSendFailed.title"),
description: errorMessage(err),
})
restoreInput()
@@ -1198,37 +1286,69 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const usedUrls = new Set(fileAttachmentParts.map((part) => part.url))
const contextFileParts: Array<{
id: string
type: "file"
mime: string
url: string
filename?: string
}> = []
const context = prompt.context.items().slice()
const addContextFile = (path: string, selection?: FileSelection) => {
const absolute = toAbsolutePath(path)
const query = selection ? `?start=${selection.startLine}&end=${selection.endLine}` : ""
const commentItems = context.filter((item) => item.type === "file" && !!item.comment?.trim())
const contextParts: Array<
| {
id: string
type: "text"
text: string
synthetic?: boolean
}
| {
id: string
type: "file"
mime: string
url: string
filename?: string
}
> = []
const commentNote = (path: string, selection: FileSelection | undefined, comment: string) => {
const start = selection ? Math.min(selection.startLine, selection.endLine) : undefined
const end = selection ? Math.max(selection.startLine, selection.endLine) : undefined
const range =
start === undefined || end === undefined
? "this file"
: start === end
? `line ${start}`
: `lines ${start} through ${end}`
return `The user made the following comment regarding ${range} of ${path}: ${comment}`
}
const addContextFile = (input: { path: string; selection?: FileSelection; comment?: string }) => {
const absolute = toAbsolutePath(input.path)
const query = input.selection ? `?start=${input.selection.startLine}&end=${input.selection.endLine}` : ""
const url = `file://${absolute}${query}`
if (usedUrls.has(url)) return
const comment = input.comment?.trim()
if (!comment && usedUrls.has(url)) return
usedUrls.add(url)
contextFileParts.push({
if (comment) {
contextParts.push({
id: Identifier.ascending("part"),
type: "text",
text: commentNote(input.path, input.selection, comment),
synthetic: true,
})
}
contextParts.push({
id: Identifier.ascending("part"),
type: "file",
mime: "text/plain",
url,
filename: getFilename(path),
filename: getFilename(input.path),
})
}
const activePath = activeFile()
if (activePath && prompt.context.activeTab()) {
addContextFile(activePath)
}
for (const item of prompt.context.items()) {
for (const item of context) {
if (item.type !== "file") continue
addContextFile(item.path, item.selection)
addContextFile({ path: item.path, selection: item.selection, comment: item.comment })
}
const imageAttachmentParts = images.map((attachment) => ({
@@ -1248,7 +1368,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const requestParts = [
textPart,
...fileAttachmentParts,
...contextFileParts,
...contextParts,
...agentAttachmentParts,
...imageAttachmentParts,
]
@@ -1268,10 +1388,27 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
model,
}
const setSyncStore = sessionDirectory === projectDirectory ? sync.set : globalSync.child(sessionDirectory)[1]
const addOptimisticMessage = () => {
setSyncStore(
if (sessionDirectory === projectDirectory) {
sync.set(
produce((draft) => {
const messages = draft.message[session.id]
if (!messages) {
draft.message[session.id] = [optimisticMessage]
} else {
const result = Binary.search(messages, messageID, (m) => m.id)
messages.splice(result.index, 0, optimisticMessage)
}
draft.part[messageID] = optimisticParts
.filter((p) => !!p?.id)
.slice()
.sort((a, b) => a.id.localeCompare(b.id))
}),
)
return
}
globalSync.child(sessionDirectory)[1](
produce((draft) => {
const messages = draft.message[session.id]
if (!messages) {
@@ -1289,7 +1426,21 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
const removeOptimisticMessage = () => {
setSyncStore(
if (sessionDirectory === projectDirectory) {
sync.set(
produce((draft) => {
const messages = draft.message[session.id]
if (messages) {
const result = Binary.search(messages, messageID, (m) => m.id)
if (result.found) messages.splice(result.index, 1)
}
delete draft.part[messageID]
}),
)
return
}
globalSync.child(sessionDirectory)[1](
produce((draft) => {
const messages = draft.message[session.id]
if (messages) {
@@ -1301,11 +1452,75 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
)
}
for (const item of commentItems) {
prompt.context.remove(item.key)
}
clearInput()
addOptimisticMessage()
client.session
.prompt({
const waitForWorktree = async () => {
const worktree = WorktreeState.get(sessionDirectory)
if (!worktree || worktree.status !== "pending") return true
if (sessionDirectory === projectDirectory) {
sync.set("session_status", session.id, { type: "busy" })
}
const controller = new AbortController()
const cleanup = () => {
if (sessionDirectory === projectDirectory) {
sync.set("session_status", session.id, { type: "idle" })
}
removeOptimisticMessage()
for (const item of commentItems) {
prompt.context.add({
type: "file",
path: item.path,
selection: item.selection,
comment: item.comment,
commentID: item.commentID,
preview: item.preview,
})
}
restoreInput()
}
pending.set(session.id, { abort: controller, cleanup })
const abort = new Promise<Awaited<ReturnType<typeof WorktreeState.wait>>>((resolve) => {
if (controller.signal.aborted) {
resolve({ status: "failed", message: "aborted" })
return
}
controller.signal.addEventListener(
"abort",
() => {
resolve({ status: "failed", message: "aborted" })
},
{ once: true },
)
})
const timeoutMs = 5 * 60 * 1000
const timeout = new Promise<Awaited<ReturnType<typeof WorktreeState.wait>>>((resolve) => {
setTimeout(() => {
resolve({ status: "failed", message: "Workspace is still preparing" })
}, timeoutMs)
})
const result = await Promise.race([WorktreeState.wait(sessionDirectory), abort, timeout])
pending.delete(session.id)
if (controller.signal.aborted) return false
if (result.status === "failed") throw new Error(result.message)
return true
}
const send = async () => {
const ok = await waitForWorktree()
if (!ok) return
await client.session.prompt({
sessionID: session.id,
agent,
model,
@@ -1313,14 +1528,30 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
parts: requestParts,
variant,
})
.catch((err) => {
showToast({
title: "Failed to send prompt",
description: errorMessage(err),
})
removeOptimisticMessage()
restoreInput()
}
void send().catch((err) => {
pending.delete(session.id)
if (sessionDirectory === projectDirectory) {
sync.set("session_status", session.id, { type: "idle" })
}
showToast({
title: language.t("prompt.toast.promptSendFailed.title"),
description: errorMessage(err),
})
removeOptimisticMessage()
for (const item of commentItems) {
prompt.context.add({
type: "file",
path: item.path,
selection: item.selection,
comment: item.comment,
commentID: item.commentID,
preview: item.preview,
})
}
restoreInput()
})
}
return (
@@ -1339,7 +1570,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Match when={store.popover === "at"}>
<Show
when={atFlat().length > 0}
fallback={<div class="text-text-weak px-2 py-1">No matching results</div>}
fallback={<div class="text-text-weak px-2 py-1">{language.t("prompt.popover.emptyResults")}</div>}
>
<For each={atFlat().slice(0, 10)}>
{(item) => (
@@ -1361,7 +1592,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
/>
<div class="flex items-center text-14-regular min-w-0">
<span class="text-text-weak whitespace-nowrap truncate min-w-0">
{getDirectory((item as { type: "file"; path: string }).path)}
{(() => {
const path = (item as { type: "file"; path: string }).path
return path.endsWith("/") ? path : getDirectory(path)
})()}
</span>
<Show when={!(item as { type: "file"; path: string }).path.endsWith("/")}>
<span class="text-text-strong whitespace-nowrap">
@@ -1385,7 +1619,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Match when={store.popover === "slash"}>
<Show
when={slashFlat().length > 0}
fallback={<div class="text-text-weak px-2 py-1">No matching commands</div>}
fallback={<div class="text-text-weak px-2 py-1">{language.t("prompt.popover.emptyCommands")}</div>}
>
<For each={slashFlat()}>
{(cmd) => (
@@ -1407,7 +1641,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<div class="flex items-center gap-2 shrink-0">
<Show when={cmd.type === "custom"}>
<span class="text-11-regular text-text-subtle px-1.5 py-0.5 bg-surface-base rounded">
custom
{language.t("prompt.slash.badge.custom")}
</span>
</Show>
<Show when={command.keybind(cmd.id)}>
@@ -1436,67 +1670,61 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 pointer-events-none">
<div class="flex flex-col items-center gap-2 text-text-weak">
<Icon name="photo" class="size-8" />
<span class="text-14-regular">Drop images or PDFs here</span>
<span class="text-14-regular">{language.t("prompt.dropzone.label")}</span>
</div>
</div>
</Show>
<Show when={false && (prompt.context.items().length > 0 || !!activeFile())}>
<div class="flex flex-wrap items-center gap-2 px-3 pt-3">
<Show when={prompt.context.activeTab() ? activeFile() : undefined}>
{(path) => (
<div class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base max-w-full">
<FileIcon node={{ path: path(), type: "file" }} class="shrink-0 size-4" />
<div class="flex items-center text-12-regular min-w-0">
<span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(path())}</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(path())}</span>
<span class="text-text-weak whitespace-nowrap ml-1">active</span>
</div>
<IconButton
type="button"
icon="close"
variant="ghost"
class="h-6 w-6"
onClick={() => prompt.context.removeActive()}
/>
</div>
)}
</Show>
<Show when={!prompt.context.activeTab() && !!activeFile()}>
<button
type="button"
class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base text-12-regular text-text-weak hover:bg-surface-raised-base-hover"
onClick={() => prompt.context.addActive()}
>
<Icon name="plus-small" size="small" />
<span>Include active file</span>
</button>
</Show>
<Show when={prompt.context.items().length > 0}>
<div class="flex flex-nowrap items-start gap-1.5 px-3 pt-3 overflow-x-auto no-scrollbar">
<For each={prompt.context.items()}>
{(item) => (
<div class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base max-w-full">
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" />
<div class="flex items-center text-12-regular min-w-0">
<span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(item.path)}</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(item.path)}</span>
<Show when={item.selection}>
{(sel) => (
<span class="text-text-weak whitespace-nowrap ml-1">
{sel().startLine === sel().endLine
? `:${sel().startLine}`
: `:${sel().startLine}-${sel().endLine}`}
</span>
)}
{(item) => {
return (
<div
classList={{
"shrink-0 flex flex-col gap-1 rounded-md bg-surface-base border border-border-base px-2 py-1 max-w-[320px]": true,
"cursor-pointer hover:bg-surface-raised-base-hover": !!item.commentID,
}}
onClick={() => {
if (!item.commentID) return
comments.setFocus({ file: item.path, id: item.commentID })
view().reviewPanel.open()
tabs().open("review")
}}
>
<div class="flex items-center gap-1.5">
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" />
<div class="flex items-center text-11-regular min-w-0">
<span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(item.path)}</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(item.path)}</span>
<Show when={item.selection}>
{(sel) => (
<span class="text-text-weak whitespace-nowrap ml-1">
{sel().startLine === sel().endLine
? `:${sel().startLine}`
: `:${sel().startLine}-${sel().endLine}`}
</span>
)}
</Show>
</div>
<IconButton
type="button"
icon="close"
variant="ghost"
class="h-5 w-5"
onClick={(e) => {
e.stopPropagation()
if (item.commentID) comments.remove(item.path, item.commentID)
prompt.context.remove(item.key)
}}
aria-label={language.t("prompt.context.removeFile")}
/>
</div>
<Show when={item.comment}>
{(comment) => <div class="text-11-regular text-text-strong">{comment()}</div>}
</Show>
</div>
<IconButton
type="button"
icon="close"
variant="ghost"
class="h-6 w-6"
onClick={() => prompt.context.remove(item.key)}
/>
</div>
)}
)
}}
</For>
</div>
</Show>
@@ -1526,6 +1754,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
type="button"
onClick={() => removeImageAttachment(attachment.id)}
class="absolute -top-1.5 -right-1.5 size-5 rounded-full bg-surface-raised-stronger-non-alpha border border-border-base flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity hover:bg-surface-raised-base-hover"
aria-label={language.t("prompt.attachment.remove")}
>
<Icon name="close" class="size-3 text-text-weak" />
</button>
@@ -1544,6 +1773,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
editorRef = el
props.ref?.(el)
}}
role="textbox"
aria-multiline="true"
aria-label={
store.mode === "shell"
? language.t("prompt.placeholder.shell")
: language.t("prompt.placeholder.normal", { example: language.t(EXAMPLES[store.placeholder]) })
}
contenteditable="true"
onInput={handleInput}
onPaste={handlePaste}
@@ -1561,8 +1797,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Show when={!prompt.dirty()}>
<div class="absolute top-0 inset-x-0 px-5 py-3 pr-12 text-14-regular text-text-weak pointer-events-none whitespace-nowrap truncate">
{store.mode === "shell"
? "Enter shell command..."
: `Ask anything... "${PLACEHOLDERS[store.placeholder]}"`}
? language.t("prompt.placeholder.shell")
: language.t("prompt.placeholder.normal", { example: language.t(EXAMPLES[store.placeholder]) })}
</div>
</Show>
</div>
@@ -1572,12 +1808,16 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Match when={store.mode === "shell"}>
<div class="flex items-center gap-2 px-2 h-6">
<Icon name="console" size="small" class="text-icon-primary" />
<span class="text-12-regular text-text-primary">Shell</span>
<span class="text-12-regular text-text-weak">esc to exit</span>
<span class="text-12-regular text-text-primary">{language.t("prompt.mode.shell")}</span>
<span class="text-12-regular text-text-weak">{language.t("prompt.mode.shell.exit")}</span>
</div>
</Match>
<Match when={store.mode === "normal"}>
<TooltipKeybind placement="top" title="Cycle agent" keybind={command.keybind("agent.cycle")}>
<TooltipKeybind
placement="top"
title={language.t("command.agent.cycle")}
keybind={command.keybind("agent.cycle")}
>
<Select
options={local.agent.list().map((agent) => agent.name)}
current={local.agent.current()?.name ?? ""}
@@ -1589,33 +1829,39 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Show
when={providers.paid().length > 0}
fallback={
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
<TooltipKeybind
placement="top"
title={language.t("command.model.choose")}
keybind={command.keybind("model.choose")}
>
<Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
<Show when={local.model.current()?.provider?.id}>
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
</Show>
{local.model.current()?.name ?? "Select model"}
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
<Icon name="chevron-down" size="small" />
</Button>
</TooltipKeybind>
}
>
<ModelSelectorPopover>
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
<Button as="div" variant="ghost">
<Show when={local.model.current()?.provider?.id}>
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
</Show>
{local.model.current()?.name ?? "Select model"}
<Icon name="chevron-down" size="small" />
</Button>
</TooltipKeybind>
</ModelSelectorPopover>
<TooltipKeybind
placement="top"
title={language.t("command.model.choose")}
keybind={command.keybind("model.choose")}
>
<ModelSelectorPopover triggerAs={Button} triggerProps={{ variant: "ghost" }}>
<Show when={local.model.current()?.provider?.id}>
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
</Show>
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
<Icon name="chevron-down" size="small" />
</ModelSelectorPopover>
</TooltipKeybind>
</Show>
<Show when={local.model.variant.list().length > 0}>
<TooltipKeybind
placement="top"
title="Thinking effort"
title={language.t("command.model.variant.cycle")}
keybind={command.keybind("model.variant.cycle")}
>
<Button
@@ -1623,14 +1869,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
onClick={() => local.model.variant.cycle()}
>
{local.model.variant.current() ?? "Default"}
{local.model.variant.current() ?? language.t("common.default")}
</Button>
</TooltipKeybind>
</Show>
<Show when={permission.permissionsEnabled() && params.id}>
<TooltipKeybind
placement="top"
title="Auto-accept edits"
title={language.t("command.permissions.autoaccept.enable")}
keybind={command.keybind("permissions.autoaccept")}
>
<Button
@@ -1641,6 +1887,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
"text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory),
"hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory),
}}
aria-label={
permission.isAutoAccepting(params.id!, sdk.directory)
? language.t("command.permissions.autoaccept.disable")
: language.t("command.permissions.autoaccept.enable")
}
aria-pressed={permission.isAutoAccepting(params.id!, sdk.directory)}
>
<Icon
name="chevron-double-right"
@@ -1668,8 +1920,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<div class="flex items-center gap-2">
<SessionContextUsage />
<Show when={store.mode === "normal"}>
<Tooltip placement="top" value="Attach file">
<Button type="button" variant="ghost" class="size-6" onClick={() => fileInputRef.click()}>
<Tooltip placement="top" value={language.t("prompt.action.attachFile")}>
<Button
type="button"
variant="ghost"
class="size-6"
onClick={() => fileInputRef.click()}
aria-label={language.t("prompt.action.attachFile")}
>
<Icon name="photo" class="size-4.5" />
</Button>
</Tooltip>
@@ -1682,13 +1940,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Switch>
<Match when={working()}>
<div class="flex items-center gap-2">
<span>Stop</span>
<span class="text-icon-base text-12-medium text-[10px]!">ESC</span>
<span>{language.t("prompt.action.stop")}</span>
<span class="text-icon-base text-12-medium text-[10px]!">{language.t("common.key.esc")}</span>
</div>
</Match>
<Match when={true}>
<div class="flex items-center gap-2">
<span>Send</span>
<span>{language.t("prompt.action.send")}</span>
<Icon name="enter" size="small" class="text-icon-base" />
</div>
</Match>
@@ -1701,6 +1959,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
icon={working() ? "stop" : "arrow-up"}
variant="primary"
class="h-6 w-4.5"
aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")}
/>
</Tooltip>
</div>

View File

@@ -7,6 +7,7 @@ import { AssistantMessage } from "@opencode-ai/sdk/v2/client"
import { useLayout } from "@/context/layout"
import { useSync } from "@/context/sync"
import { useLanguage } from "@/context/language"
interface SessionContextUsageProps {
variant?: "button" | "indicator"
@@ -16,22 +17,25 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
const sync = useSync()
const params = useParams()
const layout = useLayout()
const language = useLanguage()
const variant = createMemo(() => props.variant ?? "button")
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey()))
const view = createMemo(() => layout.view(sessionKey()))
const tabs = createMemo(() => layout.tabs(sessionKey))
const view = createMemo(() => layout.view(sessionKey))
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
const cost = createMemo(() => {
const locale = language.locale()
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat("en-US", {
return new Intl.NumberFormat(locale, {
style: "currency",
currency: "USD",
}).format(total)
})
const context = createMemo(() => {
const locale = language.locale()
const last = messages().findLast((x) => {
if (x.role !== "assistant") return false
const total = x.tokens.input + x.tokens.output + x.tokens.reasoning + x.tokens.cache.read + x.tokens.cache.write
@@ -42,7 +46,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
const model = sync.data.provider.all.find((x) => x.id === last.providerID)?.models[last.modelID]
return {
tokens: total.toLocaleString(),
tokens: total.toLocaleString(locale),
percentage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null,
}
})
@@ -67,21 +71,21 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
<>
<div class="flex items-center gap-2">
<span class="text-text-invert-strong">{ctx().tokens}</span>
<span class="text-text-invert-base">Tokens</span>
<span class="text-text-invert-base">{language.t("context.usage.tokens")}</span>
</div>
<div class="flex items-center gap-2">
<span class="text-text-invert-strong">{ctx().percentage ?? 0}%</span>
<span class="text-text-invert-base">Usage</span>
<span class="text-text-invert-base">{language.t("context.usage.usage")}</span>
</div>
</>
)}
</Show>
<div class="flex items-center gap-2">
<span class="text-text-invert-strong">{cost()}</span>
<span class="text-text-invert-base">Cost</span>
<span class="text-text-invert-base">{language.t("context.usage.cost")}</span>
</div>
<Show when={variant() === "button"}>
<div class="text-11-regular text-text-invert-base mt-1">Click to view context</div>
<div class="text-11-regular text-text-invert-base mt-1">{language.t("context.usage.clickToView")}</div>
</Show>
</div>
)
@@ -92,7 +96,13 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
<Switch>
<Match when={variant() === "indicator"}>{circle()}</Match>
<Match when={true}>
<Button type="button" variant="ghost" class="size-6" onClick={openContext}>
<Button
type="button"
variant="ghost"
class="size-6"
onClick={openContext}
aria-label={language.t("context.usage.view")}
>
{circle()}
</Button>
</Match>

View File

@@ -1,9 +1,11 @@
import { createMemo, Show } from "solid-js"
import { useSync } from "@/context/sync"
import { useLanguage } from "@/context/language"
import { Tooltip } from "@opencode-ai/ui/tooltip"
export function SessionLspIndicator() {
const sync = useSync()
const language = useLanguage()
const lspStats = createMemo(() => {
const lsp = sync.data.lsp ?? []
@@ -15,7 +17,7 @@ export function SessionLspIndicator() {
const tooltipContent = createMemo(() => {
const lsp = sync.data.lsp ?? []
if (lsp.length === 0) return "No LSP servers"
if (lsp.length === 0) return language.t("lsp.tooltip.none")
return lsp.map((s) => s.name).join(", ")
})
@@ -30,7 +32,9 @@ export function SessionLspIndicator() {
"bg-icon-success-base": !lspStats().hasError && lspStats().connected > 0,
}}
/>
<span class="text-12-regular text-text-weak">{lspStats().connected} LSP</span>
<span class="text-12-regular text-text-weak">
{language.t("lsp.label.connected", { count: lspStats().connected })}
</span>
</div>
</Tooltip>
</Show>

View File

@@ -11,6 +11,7 @@ import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
import { Code } from "@opencode-ai/ui/code"
import { Markdown } from "@opencode-ai/ui/markdown"
import type { AssistantMessage, Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
import { useLanguage } from "@/context/language"
interface SessionContextTabProps {
messages: () => Message[]
@@ -22,6 +23,7 @@ interface SessionContextTabProps {
export function SessionContextTab(props: SessionContextTabProps) {
const params = useParams()
const sync = useSync()
const language = useLanguage()
const ctx = createMemo(() => {
const last = props.messages().findLast((x) => {
@@ -59,8 +61,9 @@ export function SessionContextTab(props: SessionContextTabProps) {
})
const cost = createMemo(() => {
const locale = language.locale()
const total = props.messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat("en-US", {
return new Intl.NumberFormat(locale, {
style: "currency",
currency: "USD",
}).format(total)
@@ -89,18 +92,18 @@ export function SessionContextTab(props: SessionContextTabProps) {
const number = (value: number | null | undefined) => {
if (value === undefined) return "—"
if (value === null) return "—"
return value.toLocaleString()
return value.toLocaleString(language.locale())
}
const percent = (value: number | null | undefined) => {
if (value === undefined) return "—"
if (value === null) return "—"
return value.toString() + "%"
return value.toLocaleString(language.locale()) + "%"
}
const time = (value: number | undefined) => {
if (!value) return "—"
return DateTime.fromMillis(value).toLocaleString(DateTime.DATETIME_MED)
return DateTime.fromMillis(value).setLocale(language.locale()).toLocaleString(DateTime.DATETIME_MED)
}
const providerLabel = createMemo(() => {
@@ -172,7 +175,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
return [
{
key: "system",
label: "System",
label: language.t("context.breakdown.system"),
tokens: tokens.system,
width: pct(tokens.system),
percent: pctLabel(tokens.system),
@@ -180,7 +183,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
},
{
key: "user",
label: "User",
label: language.t("context.breakdown.user"),
tokens: tokens.user,
width: pct(tokens.user),
percent: pctLabel(tokens.user),
@@ -188,7 +191,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
},
{
key: "assistant",
label: "Assistant",
label: language.t("context.breakdown.assistant"),
tokens: tokens.assistant,
width: pct(tokens.assistant),
percent: pctLabel(tokens.assistant),
@@ -196,7 +199,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
},
{
key: "tool",
label: "Tool Calls",
label: language.t("context.breakdown.tool"),
tokens: tokens.tool,
width: pct(tokens.tool),
percent: pctLabel(tokens.tool),
@@ -204,7 +207,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
},
{
key: "other",
label: "Other",
label: language.t("context.breakdown.other"),
tokens: tokens.other,
width: pct(tokens.other),
percent: pctLabel(tokens.other),
@@ -243,22 +246,28 @@ export function SessionContextTab(props: SessionContextTabProps) {
const c = ctx()
const count = counts()
return [
{ label: "Session", value: props.info()?.title ?? params.id ?? "—" },
{ label: "Messages", value: count.all.toLocaleString() },
{ label: "Provider", value: providerLabel() },
{ label: "Model", value: modelLabel() },
{ label: "Context Limit", value: number(c?.limit) },
{ label: "Total Tokens", value: number(c?.total) },
{ label: "Usage", value: percent(c?.usage) },
{ label: "Input Tokens", value: number(c?.input) },
{ label: "Output Tokens", value: number(c?.output) },
{ label: "Reasoning Tokens", value: number(c?.reasoning) },
{ label: "Cache Tokens (read/write)", value: `${number(c?.cacheRead)} / ${number(c?.cacheWrite)}` },
{ label: "User Messages", value: count.user.toLocaleString() },
{ label: "Assistant Messages", value: count.assistant.toLocaleString() },
{ label: "Total Cost", value: cost() },
{ label: "Session Created", value: time(props.info()?.time.created) },
{ label: "Last Activity", value: time(c?.message.time.created) },
{ label: language.t("context.stats.session"), value: props.info()?.title ?? params.id ?? "—" },
{ label: language.t("context.stats.messages"), value: count.all.toLocaleString(language.locale()) },
{ label: language.t("context.stats.provider"), value: providerLabel() },
{ label: language.t("context.stats.model"), value: modelLabel() },
{ label: language.t("context.stats.limit"), value: number(c?.limit) },
{ label: language.t("context.stats.totalTokens"), value: number(c?.total) },
{ label: language.t("context.stats.usage"), value: percent(c?.usage) },
{ label: language.t("context.stats.inputTokens"), value: number(c?.input) },
{ label: language.t("context.stats.outputTokens"), value: number(c?.output) },
{ label: language.t("context.stats.reasoningTokens"), value: number(c?.reasoning) },
{
label: language.t("context.stats.cacheTokens"),
value: `${number(c?.cacheRead)} / ${number(c?.cacheWrite)}`,
},
{ label: language.t("context.stats.userMessages"), value: count.user.toLocaleString(language.locale()) },
{
label: language.t("context.stats.assistantMessages"),
value: count.assistant.toLocaleString(language.locale()),
},
{ label: language.t("context.stats.totalCost"), value: cost() },
{ label: language.t("context.stats.sessionCreated"), value: time(props.info()?.time.created) },
{ label: language.t("context.stats.lastActivity"), value: time(c?.message.time.created) },
] satisfies { label: string; value: JSX.Element }[]
})
@@ -273,7 +282,9 @@ export function SessionContextTab(props: SessionContextTabProps) {
}
})
return <Code file={file()} overflow="wrap" class="select-text" />
return (
<Code file={file()} overflow="wrap" class="select-text" onRendered={() => requestAnimationFrame(restoreScroll)} />
)
}
function RawMessage(msgProps: { message: Message }) {
@@ -305,19 +316,13 @@ export function SessionContextTab(props: SessionContextTabProps) {
let frame: number | undefined
let pending: { x: number; y: number } | undefined
const restoreScroll = (retries = 0) => {
const restoreScroll = () => {
const el = scroll
if (!el) return
const s = props.view()?.scroll("context")
if (!s) return
// Wait for content to be scrollable - content may not have rendered yet
if (el.scrollHeight <= el.clientHeight && retries < 10) {
requestAnimationFrame(() => restoreScroll(retries + 1))
return
}
if (el.scrollTop !== s.y) el.scrollTop = s.y
if (el.scrollLeft !== s.x) el.scrollLeft = s.x
}
@@ -371,7 +376,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
<Show when={breakdown().length > 0}>
<div class="flex flex-col gap-2">
<div class="text-12-regular text-text-weak">Context Breakdown</div>
<div class="text-12-regular text-text-weak">{language.t("context.breakdown.title")}</div>
<div class="h-2 w-full rounded-full bg-surface-base overflow-hidden flex">
<For each={breakdown()}>
{(segment) => (
@@ -396,16 +401,14 @@ export function SessionContextTab(props: SessionContextTabProps) {
)}
</For>
</div>
<div class="hidden text-11-regular text-text-weaker">
Approximate breakdown of input tokens. "Other" includes tool definitions and overhead.
</div>
<div class="hidden text-11-regular text-text-weaker">{language.t("context.breakdown.note")}</div>
</div>
</Show>
<Show when={systemPrompt()}>
{(prompt) => (
<div class="flex flex-col gap-2">
<div class="text-12-regular text-text-weak">System Prompt</div>
<div class="text-12-regular text-text-weak">{language.t("context.systemPrompt.title")}</div>
<div class="border border-border-base rounded-md bg-surface-base px-3 py-2">
<Markdown text={prompt()} class="text-12-regular" />
</div>
@@ -414,7 +417,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
</Show>
<div class="flex flex-col gap-2">
<div class="text-12-regular text-text-weak">Raw messages</div>
<div class="text-12-regular text-text-weak">{language.t("context.rawMessages.title")}</div>
<Accordion multiple>
<For each={props.messages()}>{(message) => <RawMessage message={message} />}</For>
</Accordion>

View File

@@ -4,6 +4,7 @@ import { Portal } from "solid-js/web"
import { useParams } from "@solidjs/router"
import { useLayout } from "@/context/layout"
import { useCommand } from "@/context/command"
import { useLanguage } from "@/context/language"
// import { useServer } from "@/context/server"
// import { useDialog } from "@opencode-ai/ui/context/dialog"
import { usePlatform } from "@/context/platform"
@@ -29,6 +30,7 @@ export function SessionHeader() {
// const dialog = useDialog()
const sync = useSync()
const platform = usePlatform()
const language = useLanguage()
const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
const project = createMemo(() => {
@@ -45,8 +47,10 @@ export function SessionHeader() {
const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
const showShare = createMemo(() => shareEnabled() && !!currentSession())
const showReview = createMemo(() => !!currentSession())
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const view = createMemo(() => layout.view(sessionKey()))
const view = createMemo(() => layout.view(sessionKey))
const [state, setState] = createStore({
share: false,
@@ -132,11 +136,12 @@ export function SessionHeader() {
type="button"
class="hidden md:flex w-[320px] p-1 pl-1.5 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-raised-base transition-colors cursor-default hover:bg-surface-raised-base-hover focus:bg-surface-raised-base-hover active:bg-surface-raised-base-active"
onClick={() => command.trigger("file.open")}
aria-label={language.t("session.header.searchFiles")}
>
<div class="flex min-w-0 flex-1 items-center gap-2 overflow-visible">
<Icon name="magnifying-glass" size="normal" class="icon-base shrink-0" />
<span class="flex-1 min-w-0 text-14-regular text-text-weak truncate h-4.5 flex items-center">
Search {name()}
{language.t("session.header.search.placeholder", { project: name() })}
</span>
</div>
@@ -172,46 +177,52 @@ export function SessionHeader() {
{/* <SessionMcpIndicator /> */}
{/* </div> */}
<div class="flex items-center gap-1">
<Show when={currentSession()?.summary?.files}>
<div class="hidden md:block shrink-0">
<TooltipKeybind
class="hidden md:block shrink-0"
title="Toggle review"
title={language.t("command.review.toggle")}
keybind={command.keybind("review.toggle")}
>
<Button
variant="ghost"
class="group/review-toggle size-6 p-0"
onClick={() => view().reviewPanel.toggle()}
aria-label={language.t("command.review.toggle")}
aria-expanded={view().reviewPanel.opened()}
aria-controls="review-panel"
tabIndex={showReview() ? 0 : -1}
>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
name={view().reviewPanel.opened() ? "layout-right" : "layout-left"}
size="small"
name={view().reviewPanel.opened() ? "layout-right-full" : "layout-right"}
class="group-hover/review-toggle:hidden"
/>
<Icon
name={view().reviewPanel.opened() ? "layout-right-partial" : "layout-left-partial"}
size="small"
name="layout-right-partial"
class="hidden group-hover/review-toggle:inline-block"
/>
<Icon
name={view().reviewPanel.opened() ? "layout-right-full" : "layout-left-full"}
size="small"
name={view().reviewPanel.opened() ? "layout-right" : "layout-right-full"}
class="hidden group-active/review-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
</Show>
</div>
<TooltipKeybind
class="hidden md:block shrink-0"
title="Toggle terminal"
title={language.t("command.terminal.toggle")}
keybind={command.keybind("terminal.toggle")}
>
<Button
variant="ghost"
class="group/terminal-toggle size-6 p-0"
onClick={() => view().terminal.toggle()}
aria-label={language.t("command.terminal.toggle")}
aria-expanded={view().terminal.opened()}
aria-controls="terminal-panel"
>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
@@ -233,26 +244,22 @@ export function SessionHeader() {
</Button>
</TooltipKeybind>
</div>
<Show when={shareEnabled() && currentSession()}>
<Show when={showShare()}>
<div class="flex items-center">
<Popover
title="Publish on web"
title={language.t("session.share.popover.title")}
description={
shareUrl()
? "This session is public on the web. It is accessible to anyone with the link."
: "Share session publicly on the web. It will be accessible to anyone with the link."
}
trigger={
<Tooltip class="shrink-0" value="Share session">
<Button
variant="secondary"
classList={{ "rounded-r-none": shareUrl() !== undefined }}
style={{ scale: 1 }}
>
Share
</Button>
</Tooltip>
? language.t("session.share.popover.description.shared")
: language.t("session.share.popover.description.unshared")
}
triggerAs={Button}
triggerProps={{
variant: "secondary",
classList: { "rounded-r-none": shareUrl() !== undefined },
style: { scale: 1 },
}}
trigger={language.t("session.share.action.share")}
>
<div class="flex flex-col gap-2">
<Show
@@ -266,7 +273,9 @@ export function SessionHeader() {
onClick={shareSession}
disabled={state.share}
>
{state.share ? "Publishing..." : "Publish"}
{state.share
? language.t("session.share.action.publishing")
: language.t("session.share.action.publish")}
</Button>
</div>
}
@@ -281,7 +290,9 @@ export function SessionHeader() {
onClick={unshareSession}
disabled={state.unshare}
>
{state.unshare ? "Unpublishing..." : "Unpublish"}
{state.unshare
? language.t("session.share.action.unpublishing")
: language.t("session.share.action.unpublish")}
</Button>
<Button
size="large"
@@ -290,7 +301,7 @@ export function SessionHeader() {
onClick={viewShare}
disabled={state.unshare}
>
View
{language.t("session.share.action.view")}
</Button>
</div>
</div>
@@ -298,13 +309,26 @@ export function SessionHeader() {
</div>
</Popover>
<Show when={shareUrl()} fallback={<div class="size-6" aria-hidden="true" />}>
<Tooltip value={state.copied ? "Copied" : "Copy link"} placement="top" gutter={8}>
<Tooltip
value={
state.copied
? language.t("session.share.copy.copied")
: language.t("session.share.copy.copyLink")
}
placement="top"
gutter={8}
>
<IconButton
icon={state.copied ? "check" : "copy"}
variant="secondary"
class="rounded-l-none"
onClick={copyLink}
disabled={state.unshare}
aria-label={
state.copied
? language.t("session.share.copy.copied")
: language.t("session.share.copy.copyLink")
}
/>
</Tooltip>
</Show>

View File

@@ -1,6 +1,7 @@
import { Show, createMemo } from "solid-js"
import { DateTime } from "luxon"
import { useSync } from "@/context/sync"
import { useLanguage } from "@/context/language"
import { Icon } from "@opencode-ai/ui/icon"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { Select } from "@opencode-ai/ui/select"
@@ -15,6 +16,7 @@ interface NewSessionViewProps {
export function NewSessionView(props: NewSessionViewProps) {
const sync = useSync()
const language = useLanguage()
const sandboxes = createMemo(() => sync.project?.sandboxes ?? [])
const options = createMemo(() => [MAIN_WORKTREE, ...sandboxes(), CREATE_WORKTREE])
@@ -32,13 +34,13 @@ export function NewSessionView(props: NewSessionViewProps) {
const label = (value: string) => {
if (value === MAIN_WORKTREE) {
if (isWorktree()) return "Main branch"
if (isWorktree()) return language.t("session.new.worktree.main")
const branch = sync.data.vcs?.branch
if (branch) return `Main branch (${branch})`
return "Main branch"
if (branch) return language.t("session.new.worktree.mainWithBranch", { branch })
return language.t("session.new.worktree.main")
}
if (value === CREATE_WORKTREE) return "Create new worktree"
if (value === CREATE_WORKTREE) return language.t("session.new.worktree.create")
return getFilename(value)
}
@@ -48,10 +50,10 @@ export function NewSessionView(props: NewSessionViewProps) {
class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto px-6"
style={{ "padding-bottom": "calc(var(--prompt-height, 11.25rem) + 64px)" }}
>
<div class="text-20-medium text-text-weaker">New session</div>
<div class="text-20-medium text-text-weaker">{language.t("command.session.new")}</div>
<div class="flex justify-center items-center gap-3">
<Icon name="folder" size="small" />
<div class="text-12-medium text-text-weak">
<div class="text-12-medium text-text-weak select-text">
{getDirectory(projectRoot())}
<span class="text-text-strong">{getFilename(projectRoot())}</span>
</div>
@@ -76,9 +78,11 @@ export function NewSessionView(props: NewSessionViewProps) {
<div class="flex justify-center items-center gap-3">
<Icon name="pencil-line" size="small" />
<div class="text-12-medium text-text-weak">
Last modified&nbsp;
{language.t("session.new.lastModified")}&nbsp;
<span class="text-text-strong">
{DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()}
{DateTime.fromMillis(project().time.updated ?? project().time.created)
.setLocale(language.locale())
.toRelative()}
</span>
</div>
</div>

View File

@@ -7,6 +7,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Tabs } from "@opencode-ai/ui/tabs"
import { getFilename } from "@opencode-ai/util/path"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
export function FileVisual(props: { path: string; active?: boolean }): JSX.Element {
return (
@@ -25,6 +26,7 @@ export function FileVisual(props: { path: string; active?: boolean }): JSX.Eleme
export function SortableTab(props: { tab: string; onTabClose: (tab: string) => void }): JSX.Element {
const file = useFile()
const language = useLanguage()
const sortable = createSortable(props.tab)
const path = createMemo(() => file.pathFromTab(props.tab))
return (
@@ -34,8 +36,13 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
<Tabs.Trigger
value={props.tab}
closeButton={
<Tooltip value="Close tab" placement="bottom">
<IconButton icon="close" variant="ghost" onClick={() => props.onTabClose(props.tab)} />
<Tooltip value={language.t("common.closeTab")} placement="bottom">
<IconButton
icon="close"
variant="ghost"
onClick={() => props.onTabClose(props.tab)}
aria-label={language.t("common.closeTab")}
/>
</Tooltip>
}
hideCloseButton

View File

@@ -1,26 +1,186 @@
import type { JSX } from "solid-js"
import { createSignal, Show } from "solid-js"
import { createSortable } from "@thisbeyond/solid-dnd"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Tabs } from "@opencode-ai/ui/tabs"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Icon } from "@opencode-ai/ui/icon"
import { useTerminal, type LocalPTY } from "@/context/terminal"
import { useLanguage } from "@/context/language"
export function SortableTerminalTab(props: { terminal: LocalPTY }): JSX.Element {
export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => void }): JSX.Element {
const terminal = useTerminal()
const language = useLanguage()
const sortable = createSortable(props.terminal.id)
const [editing, setEditing] = createSignal(false)
const [title, setTitle] = createSignal(props.terminal.title)
const [menuOpen, setMenuOpen] = createSignal(false)
const [menuPosition, setMenuPosition] = createSignal({ x: 0, y: 0 })
const [blurEnabled, setBlurEnabled] = createSignal(false)
const isDefaultTitle = () => {
const number = props.terminal.titleNumber
if (!Number.isFinite(number) || number <= 0) return false
const match = props.terminal.title.match(/^Terminal (\d+)$/)
if (!match) return false
const parsed = Number(match[1])
if (!Number.isFinite(parsed) || parsed <= 0) return false
return parsed === number
}
const label = () => {
language.locale()
if (props.terminal.title && !isDefaultTitle()) return props.terminal.title
const number = props.terminal.titleNumber
if (Number.isFinite(number) && number > 0) return language.t("terminal.title.numbered", { number })
if (props.terminal.title) return props.terminal.title
return language.t("terminal.title")
}
const close = () => {
const count = terminal.all().length
terminal.close(props.terminal.id)
if (count === 1) {
props.onClose?.()
}
}
const focus = () => {
if (editing()) return
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur()
}
const wrapper = document.getElementById(`terminal-wrapper-${props.terminal.id}`)
const element = wrapper?.querySelector('[data-component="terminal"]') as HTMLElement
if (!element) return
const textarea = element.querySelector("textarea") as HTMLTextAreaElement
if (textarea) {
textarea.focus()
return
}
element.focus()
element.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true }))
}
const edit = (e?: Event) => {
if (e) {
e.stopPropagation()
e.preventDefault()
}
setBlurEnabled(false)
setTitle(props.terminal.title)
setEditing(true)
setTimeout(() => {
const input = document.getElementById(`terminal-title-input-${props.terminal.id}`) as HTMLInputElement
if (!input) return
input.focus()
input.select()
setTimeout(() => setBlurEnabled(true), 100)
}, 10)
}
const save = () => {
if (!blurEnabled()) return
const value = title().trim()
if (value && value !== props.terminal.title) {
terminal.update({ id: props.terminal.id, title: value })
}
setEditing(false)
}
const keydown = (e: KeyboardEvent) => {
if (e.key === "Enter") {
e.preventDefault()
save()
return
}
if (e.key === "Escape") {
e.preventDefault()
setEditing(false)
}
}
const menu = (e: MouseEvent) => {
e.preventDefault()
setMenuPosition({ x: e.clientX, y: e.clientY })
setMenuOpen(true)
}
return (
// @ts-ignore
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
<div
// @ts-ignore
use:sortable
class="outline-none focus:outline-none focus-visible:outline-none"
classList={{
"h-full": true,
"opacity-0": sortable.isActiveDraggable,
}}
>
<div class="relative h-full">
<Tabs.Trigger
value={props.terminal.id}
onClick={focus}
onMouseDown={(e) => e.preventDefault()}
onContextMenu={menu}
class="!shadow-none"
classes={{
button: "border-0 outline-none focus:outline-none focus-visible:outline-none !shadow-none !ring-0",
}}
closeButton={
terminal.all().length > 1 && (
<IconButton icon="close" variant="ghost" onClick={() => terminal.close(props.terminal.id)} />
)
<IconButton
icon="close"
variant="ghost"
onClick={(e) => {
e.stopPropagation()
close()
}}
aria-label={language.t("terminal.close")}
/>
}
>
{props.terminal.title}
<span onDblClick={edit} style={{ visibility: editing() ? "hidden" : "visible" }}>
{label()}
</span>
</Tabs.Trigger>
<Show when={editing()}>
<div class="absolute inset-0 flex items-center px-3 bg-muted z-10 pointer-events-auto">
<input
id={`terminal-title-input-${props.terminal.id}`}
type="text"
value={title()}
onInput={(e) => setTitle(e.currentTarget.value)}
onBlur={save}
onKeyDown={keydown}
onMouseDown={(e) => e.stopPropagation()}
class="bg-transparent border-none outline-none text-sm min-w-0 flex-1"
/>
</div>
</Show>
<DropdownMenu open={menuOpen()} onOpenChange={setMenuOpen}>
<DropdownMenu.Portal>
<DropdownMenu.Content
style={{
position: "fixed",
left: `${menuPosition().x}px`,
top: `${menuPosition().y}px`,
}}
>
<DropdownMenu.Item onSelect={edit}>
<Icon name="edit" class="w-4 h-4 mr-2" />
{language.t("common.rename")}
</DropdownMenu.Item>
<DropdownMenu.Item onSelect={close}>
<Icon name="close" class="w-4 h-4 mr-2" />
{language.t("common.close")}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
</div>
</div>
)

View File

@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsAgents: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.agents.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.agents.description")}</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsCommands: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.commands.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.commands.description")}</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,296 @@
import { Component, createMemo, type JSX } from "solid-js"
import { Select } from "@opencode-ai/ui/select"
import { Switch } from "@opencode-ai/ui/switch"
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
import { useLanguage } from "@/context/language"
import { useSettings, monoFontFamily } from "@/context/settings"
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
import { Link } from "./link"
export const SettingsGeneral: Component = () => {
const theme = useTheme()
const language = useLanguage()
const settings = useSettings()
const themeOptions = createMemo(() =>
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
)
const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [
{ value: "system", label: language.t("theme.scheme.system") },
{ value: "light", label: language.t("theme.scheme.light") },
{ value: "dark", label: language.t("theme.scheme.dark") },
])
const languageOptions = createMemo(() =>
language.locales.map((locale) => ({
value: locale,
label: language.label(locale),
})),
)
const fontOptions = [
{ value: "ibm-plex-mono", label: "font.option.ibmPlexMono" },
{ value: "cascadia-code", label: "font.option.cascadiaCode" },
{ value: "fira-code", label: "font.option.firaCode" },
{ value: "hack", label: "font.option.hack" },
{ value: "inconsolata", label: "font.option.inconsolata" },
{ value: "intel-one-mono", label: "font.option.intelOneMono" },
{ value: "jetbrains-mono", label: "font.option.jetbrainsMono" },
{ value: "meslo-lgs", label: "font.option.mesloLgs" },
{ value: "roboto-mono", label: "font.option.robotoMono" },
{ value: "source-code-pro", label: "font.option.sourceCodePro" },
{ value: "ubuntu-mono", label: "font.option.ubuntuMono" },
] as const
const fontOptionsList = [...fontOptions]
const soundOptions = [...SOUND_OPTIONS]
return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}>
<div
class="sticky top-0 z-10"
style={{
background:
"linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
}}
>
<div class="flex flex-col gap-1 pt-6 pb-8">
<h2 class="text-16-medium text-text-strong">{language.t("settings.tab.general")}</h2>
</div>
</div>
<div class="flex flex-col gap-8 w-full">
{/* Appearance Section */}
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.appearance")}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<SettingsRow
title={language.t("settings.general.row.language.title")}
description={language.t("settings.general.row.language.description")}
>
<Select
options={languageOptions()}
current={languageOptions().find((o) => o.value === language.locale())}
value={(o) => o.value}
label={(o) => o.label}
onSelect={(option) => option && language.setLocale(option.value)}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.appearance.title")}
description={language.t("settings.general.row.appearance.description")}
>
<Select
options={colorSchemeOptions()}
current={colorSchemeOptions().find((o) => o.value === theme.colorScheme())}
value={(o) => o.value}
label={(o) => o.label}
onSelect={(option) => option && theme.setColorScheme(option.value)}
onHighlight={(option) => {
if (!option) return
theme.previewColorScheme(option.value)
return () => theme.cancelPreview()
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.theme.title")}
description={
<>
{language.t("settings.general.row.theme.description")}{" "}
<Link href="https://opencode.ai/docs/themes/">{language.t("common.learnMore")}</Link>
</>
}
>
<Select
options={themeOptions()}
current={themeOptions().find((o) => o.id === theme.themeId())}
value={(o) => o.id}
label={(o) => o.name}
onSelect={(option) => {
if (!option) return
theme.setTheme(option.id)
}}
onHighlight={(option) => {
if (!option) return
theme.previewTheme(option.id)
return () => theme.cancelPreview()
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.font.title")}
description={language.t("settings.general.row.font.description")}
>
<Select
options={fontOptionsList}
current={fontOptionsList.find((o) => o.value === settings.appearance.font())}
value={(o) => o.value}
label={(o) => language.t(o.label)}
onSelect={(option) => option && settings.appearance.setFont(option.value)}
variant="secondary"
size="small"
triggerVariant="settings"
triggerStyle={{ "font-family": monoFontFamily(settings.appearance.font()), "min-width": "180px" }}
>
{(option) => (
<span style={{ "font-family": monoFontFamily(option?.value) }}>
{option ? language.t(option.label) : ""}
</span>
)}
</Select>
</SettingsRow>
</div>
</div>
{/* System notifications Section */}
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.notifications")}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<SettingsRow
title={language.t("settings.general.notifications.agent.title")}
description={language.t("settings.general.notifications.agent.description")}
>
<Switch
checked={settings.notifications.agent()}
onChange={(checked) => settings.notifications.setAgent(checked)}
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.notifications.permissions.title")}
description={language.t("settings.general.notifications.permissions.description")}
>
<Switch
checked={settings.notifications.permissions()}
onChange={(checked) => settings.notifications.setPermissions(checked)}
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.notifications.errors.title")}
description={language.t("settings.general.notifications.errors.description")}
>
<Switch
checked={settings.notifications.errors()}
onChange={(checked) => settings.notifications.setErrors(checked)}
/>
</SettingsRow>
</div>
</div>
{/* Sound effects Section */}
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.sounds")}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<SettingsRow
title={language.t("settings.general.sounds.agent.title")}
description={language.t("settings.general.sounds.agent.description")}
>
<Select
options={soundOptions}
current={soundOptions.find((o) => o.id === settings.sounds.agent())}
value={(o) => o.id}
label={(o) => language.t(o.label)}
onHighlight={(option) => {
if (!option) return
playSound(option.src)
}}
onSelect={(option) => {
if (!option) return
settings.sounds.setAgent(option.id)
playSound(option.src)
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.sounds.permissions.title")}
description={language.t("settings.general.sounds.permissions.description")}
>
<Select
options={soundOptions}
current={soundOptions.find((o) => o.id === settings.sounds.permissions())}
value={(o) => o.id}
label={(o) => language.t(o.label)}
onHighlight={(option) => {
if (!option) return
playSound(option.src)
}}
onSelect={(option) => {
if (!option) return
settings.sounds.setPermissions(option.id)
playSound(option.src)
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.sounds.errors.title")}
description={language.t("settings.general.sounds.errors.description")}
>
<Select
options={soundOptions}
current={soundOptions.find((o) => o.id === settings.sounds.errors())}
value={(o) => o.id}
label={(o) => language.t(o.label)}
onHighlight={(option) => {
if (!option) return
playSound(option.src)
}}
onSelect={(option) => {
if (!option) return
settings.sounds.setErrors(option.id)
playSound(option.src)
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
</div>
</div>
</div>
</div>
)
}
interface SettingsRowProps {
title: string
description: string | JSX.Element
children: JSX.Element
}
const SettingsRow: Component<SettingsRowProps> = (props) => {
return (
<div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
<div class="flex flex-col gap-0.5">
<span class="text-14-medium text-text-strong">{props.title}</span>
<span class="text-12-regular text-text-weak">{props.description}</span>
</div>
<div class="flex-shrink-0">{props.children}</div>
</div>
)
}

View File

@@ -0,0 +1,437 @@
import { Component, For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { TextField } from "@opencode-ai/ui/text-field"
import { showToast } from "@opencode-ai/ui/toast"
import fuzzysort from "fuzzysort"
import { formatKeybind, parseKeybind, useCommand } from "@/context/command"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
const PALETTE_ID = "command.palette"
const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"
type KeybindGroup = "General" | "Session" | "Navigation" | "Model and agent" | "Terminal" | "Prompt"
type KeybindMeta = {
title: string
group: KeybindGroup
}
const GROUPS: KeybindGroup[] = ["General", "Session", "Navigation", "Model and agent", "Terminal", "Prompt"]
type GroupKey =
| "settings.shortcuts.group.general"
| "settings.shortcuts.group.session"
| "settings.shortcuts.group.navigation"
| "settings.shortcuts.group.modelAndAgent"
| "settings.shortcuts.group.terminal"
| "settings.shortcuts.group.prompt"
const groupKey: Record<KeybindGroup, GroupKey> = {
General: "settings.shortcuts.group.general",
Session: "settings.shortcuts.group.session",
Navigation: "settings.shortcuts.group.navigation",
"Model and agent": "settings.shortcuts.group.modelAndAgent",
Terminal: "settings.shortcuts.group.terminal",
Prompt: "settings.shortcuts.group.prompt",
}
function groupFor(id: string): KeybindGroup {
if (id === PALETTE_ID) return "General"
if (id.startsWith("terminal.")) return "Terminal"
if (id.startsWith("model.") || id.startsWith("agent.") || id.startsWith("mcp.")) return "Model and agent"
if (id.startsWith("file.")) return "Navigation"
if (id.startsWith("prompt.")) return "Prompt"
if (
id.startsWith("session.") ||
id.startsWith("message.") ||
id.startsWith("permissions.") ||
id.startsWith("steps.") ||
id.startsWith("review.")
)
return "Session"
return "General"
}
function isModifier(key: string) {
return key === "Shift" || key === "Control" || key === "Alt" || key === "Meta"
}
function normalizeKey(key: string) {
if (key === ",") return "comma"
if (key === "+") return "plus"
if (key === " ") return "space"
return key.toLowerCase()
}
function recordKeybind(event: KeyboardEvent) {
if (isModifier(event.key)) return
const parts: string[] = []
const mod = IS_MAC ? event.metaKey : event.ctrlKey
if (mod) parts.push("mod")
if (IS_MAC && event.ctrlKey) parts.push("ctrl")
if (!IS_MAC && event.metaKey) parts.push("meta")
if (event.altKey) parts.push("alt")
if (event.shiftKey) parts.push("shift")
const key = normalizeKey(event.key)
if (!key) return
parts.push(key)
return parts.join("+")
}
function signatures(config: string | undefined) {
if (!config) return []
const sigs: string[] = []
for (const kb of parseKeybind(config)) {
const parts: string[] = []
if (kb.ctrl) parts.push("ctrl")
if (kb.alt) parts.push("alt")
if (kb.shift) parts.push("shift")
if (kb.meta) parts.push("meta")
if (kb.key) parts.push(kb.key)
if (parts.length === 0) continue
sigs.push(parts.join("+"))
}
return sigs
}
export const SettingsKeybinds: Component = () => {
const command = useCommand()
const language = useLanguage()
const settings = useSettings()
const [active, setActive] = createSignal<string | null>(null)
const [filter, setFilter] = createSignal("")
const stop = () => {
if (!active()) return
setActive(null)
command.keybinds(true)
}
const start = (id: string) => {
if (active() === id) {
stop()
return
}
if (active()) stop()
setActive(id)
command.keybinds(false)
}
const hasOverrides = createMemo(() => {
const keybinds = settings.current.keybinds as Record<string, string | undefined> | undefined
if (!keybinds) return false
return Object.values(keybinds).some((x) => typeof x === "string")
})
const resetAll = () => {
stop()
settings.keybinds.resetAll()
showToast({
title: language.t("settings.shortcuts.reset.toast.title"),
description: language.t("settings.shortcuts.reset.toast.description"),
})
}
const list = createMemo(() => {
language.locale()
const out = new Map<string, KeybindMeta>()
out.set(PALETTE_ID, { title: language.t("command.palette"), group: "General" })
for (const opt of command.catalog) {
if (opt.id.startsWith("suggested.")) continue
out.set(opt.id, { title: opt.title, group: groupFor(opt.id) })
}
for (const opt of command.options) {
if (opt.id.startsWith("suggested.")) continue
out.set(opt.id, { title: opt.title, group: groupFor(opt.id) })
}
const keybinds = settings.current.keybinds as Record<string, string | undefined> | undefined
if (keybinds) {
for (const [id, value] of Object.entries(keybinds)) {
if (typeof value !== "string") continue
if (out.has(id)) continue
out.set(id, { title: id, group: groupFor(id) })
}
}
return out
})
const title = (id: string) => list().get(id)?.title ?? ""
const grouped = createMemo(() => {
const map = list()
const out = new Map<KeybindGroup, string[]>()
for (const group of GROUPS) out.set(group, [])
for (const [id, item] of map) {
const ids = out.get(item.group)
if (!ids) continue
ids.push(id)
}
for (const group of GROUPS) {
const ids = out.get(group)
if (!ids) continue
ids.sort((a, b) => {
const at = map.get(a)?.title ?? ""
const bt = map.get(b)?.title ?? ""
return at.localeCompare(bt)
})
}
return out
})
const filtered = createMemo(() => {
const query = filter().toLowerCase().trim()
if (!query) return grouped()
const map = list()
const out = new Map<KeybindGroup, string[]>()
for (const group of GROUPS) out.set(group, [])
const items = Array.from(map.entries()).map(([id, meta]) => ({
id,
title: meta.title,
group: meta.group,
keybind: command.keybind(id) || "",
}))
const results = fuzzysort.go(query, items, {
keys: ["title", "keybind"],
threshold: -10000,
})
for (const result of results) {
const item = result.obj
const ids = out.get(item.group)
if (!ids) continue
ids.push(item.id)
}
return out
})
const hasResults = createMemo(() => {
for (const group of GROUPS) {
const ids = filtered().get(group) ?? []
if (ids.length > 0) return true
}
return false
})
const used = createMemo(() => {
const map = new Map<string, { id: string; title: string }[]>()
const add = (key: string, value: { id: string; title: string }) => {
const list = map.get(key)
if (!list) {
map.set(key, [value])
return
}
list.push(value)
}
const palette = settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND
for (const sig of signatures(palette)) {
add(sig, { id: PALETTE_ID, title: title(PALETTE_ID) })
}
const valueFor = (id: string) => {
const custom = settings.keybinds.get(id)
if (typeof custom === "string") return custom
const live = command.options.find((x) => x.id === id)
if (live?.keybind) return live.keybind
const meta = command.catalog.find((x) => x.id === id)
return meta?.keybind
}
for (const id of list().keys()) {
if (id === PALETTE_ID) continue
for (const sig of signatures(valueFor(id))) {
add(sig, { id, title: title(id) })
}
}
return map
})
const setKeybind = (id: string, keybind: string) => {
settings.keybinds.set(id, keybind)
}
onMount(() => {
const handle = (event: KeyboardEvent) => {
const id = active()
if (!id) return
event.preventDefault()
event.stopPropagation()
event.stopImmediatePropagation()
if (event.key === "Escape") {
stop()
return
}
const clear =
(event.key === "Backspace" || event.key === "Delete") &&
!event.ctrlKey &&
!event.metaKey &&
!event.altKey &&
!event.shiftKey
if (clear) {
setKeybind(id, "none")
stop()
return
}
const next = recordKeybind(event)
if (!next) return
const map = used()
const conflicts = new Map<string, string>()
for (const sig of signatures(next)) {
const list = map.get(sig) ?? []
for (const item of list) {
if (item.id === id) continue
conflicts.set(item.id, item.title)
}
}
if (conflicts.size > 0) {
showToast({
title: language.t("settings.shortcuts.conflict.title"),
description: language.t("settings.shortcuts.conflict.description", {
keybind: formatKeybind(next),
titles: [...conflicts.values()].join(", "),
}),
})
return
}
setKeybind(id, next)
stop()
}
document.addEventListener("keydown", handle, true)
onCleanup(() => {
document.removeEventListener("keydown", handle, true)
})
})
onCleanup(() => {
if (active()) command.keybinds(true)
})
return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}>
<div
class="sticky top-0 z-10"
style={{
background:
"linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
}}
>
<div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
<div class="flex items-center justify-between gap-4">
<h2 class="text-16-medium text-text-strong">{language.t("settings.shortcuts.title")}</h2>
<Button size="small" variant="secondary" onClick={resetAll} disabled={!hasOverrides()}>
{language.t("settings.shortcuts.reset.button")}
</Button>
</div>
<div class="flex items-center gap-2 px-3 h-9 rounded-lg bg-surface-base">
<Icon name="magnifying-glass" class="text-icon-weak-base flex-shrink-0" />
<TextField
variant="ghost"
type="text"
value={filter()}
onChange={setFilter}
placeholder={language.t("settings.shortcuts.search.placeholder")}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
class="flex-1"
/>
<Show when={filter()}>
<IconButton icon="circle-x" variant="ghost" onClick={() => setFilter("")} />
</Show>
</div>
</div>
</div>
<div class="flex flex-col gap-8 max-w-[720px]">
<For each={GROUPS}>
{(group) => (
<Show when={(filtered().get(group) ?? []).length > 0}>
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t(groupKey[group])}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<For each={filtered().get(group) ?? []}>
{(id) => (
<div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
<span class="text-14-regular text-text-strong">{title(id)}</span>
<button
type="button"
classList={{
"h-8 px-3 rounded-md text-12-regular": true,
"bg-surface-base text-text-subtle hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active":
active() !== id,
"border border-border-weak-base bg-surface-inset-base text-text-weak": active() === id,
}}
onClick={() => start(id)}
>
<Show
when={active() === id}
fallback={command.keybind(id) || language.t("settings.shortcuts.unassigned")}
>
{language.t("settings.shortcuts.pressKeys")}
</Show>
</button>
</div>
)}
</For>
</div>
</div>
</Show>
)}
</For>
<Show when={filter() && !hasResults()}>
<div class="flex flex-col items-center justify-center py-12 text-center">
<span class="text-14-regular text-text-weak">{language.t("settings.shortcuts.search.empty")}</span>
<Show when={filter()}>
<span class="text-14-regular text-text-strong mt-1">"{filter()}"</span>
</Show>
</div>
</Show>
</div>
</div>
)
}

View File

@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsMcp: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.mcp.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.mcp.description")}</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsModels: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.models.description")}</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,234 @@
import { Select } from "@opencode-ai/ui/select"
import { showToast } from "@opencode-ai/ui/toast"
import { Component, For, createMemo, type JSX } from "solid-js"
import { useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language"
type PermissionAction = "allow" | "ask" | "deny"
type PermissionObject = Record<string, PermissionAction>
type PermissionValue = PermissionAction | PermissionObject | string[] | undefined
type PermissionMap = Record<string, PermissionValue>
type PermissionItem = {
id: string
title: string
description: string
}
const ACTIONS = [
{ value: "allow", label: "settings.permissions.action.allow" },
{ value: "ask", label: "settings.permissions.action.ask" },
{ value: "deny", label: "settings.permissions.action.deny" },
] as const
const ITEMS = [
{
id: "read",
title: "settings.permissions.tool.read.title",
description: "settings.permissions.tool.read.description",
},
{
id: "edit",
title: "settings.permissions.tool.edit.title",
description: "settings.permissions.tool.edit.description",
},
{
id: "glob",
title: "settings.permissions.tool.glob.title",
description: "settings.permissions.tool.glob.description",
},
{
id: "grep",
title: "settings.permissions.tool.grep.title",
description: "settings.permissions.tool.grep.description",
},
{
id: "list",
title: "settings.permissions.tool.list.title",
description: "settings.permissions.tool.list.description",
},
{
id: "bash",
title: "settings.permissions.tool.bash.title",
description: "settings.permissions.tool.bash.description",
},
{
id: "task",
title: "settings.permissions.tool.task.title",
description: "settings.permissions.tool.task.description",
},
{
id: "skill",
title: "settings.permissions.tool.skill.title",
description: "settings.permissions.tool.skill.description",
},
{
id: "lsp",
title: "settings.permissions.tool.lsp.title",
description: "settings.permissions.tool.lsp.description",
},
{
id: "todoread",
title: "settings.permissions.tool.todoread.title",
description: "settings.permissions.tool.todoread.description",
},
{
id: "todowrite",
title: "settings.permissions.tool.todowrite.title",
description: "settings.permissions.tool.todowrite.description",
},
{
id: "webfetch",
title: "settings.permissions.tool.webfetch.title",
description: "settings.permissions.tool.webfetch.description",
},
{
id: "websearch",
title: "settings.permissions.tool.websearch.title",
description: "settings.permissions.tool.websearch.description",
},
{
id: "codesearch",
title: "settings.permissions.tool.codesearch.title",
description: "settings.permissions.tool.codesearch.description",
},
{
id: "external_directory",
title: "settings.permissions.tool.external_directory.title",
description: "settings.permissions.tool.external_directory.description",
},
{
id: "doom_loop",
title: "settings.permissions.tool.doom_loop.title",
description: "settings.permissions.tool.doom_loop.description",
},
] as const
const VALID_ACTIONS = new Set<PermissionAction>(["allow", "ask", "deny"])
function toMap(value: unknown): PermissionMap {
if (value && typeof value === "object" && !Array.isArray(value)) return value as PermissionMap
const action = getAction(value)
if (action) return { "*": action }
return {}
}
function getAction(value: unknown): PermissionAction | undefined {
if (typeof value === "string" && VALID_ACTIONS.has(value as PermissionAction)) return value as PermissionAction
return
}
function getRuleDefault(value: unknown): PermissionAction | undefined {
const action = getAction(value)
if (action) return action
if (!value || typeof value !== "object" || Array.isArray(value)) return
return getAction((value as Record<string, unknown>)["*"])
}
export const SettingsPermissions: Component = () => {
const globalSync = useGlobalSync()
const language = useLanguage()
const actions = createMemo(
(): Array<{ value: PermissionAction; label: string }> =>
ACTIONS.map((action) => ({
value: action.value,
label: language.t(action.label),
})),
)
const permission = createMemo(() => {
return toMap(globalSync.data.config.permission)
})
const actionFor = (id: string): PermissionAction => {
const value = permission()[id]
const direct = getRuleDefault(value)
if (direct) return direct
const wildcard = getRuleDefault(permission()["*"])
if (wildcard) return wildcard
return "allow"
}
const setPermission = async (id: string, action: PermissionAction) => {
const before = globalSync.data.config.permission
const map = toMap(before)
const existing = map[id]
const nextValue =
existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing, "*": action } : action
globalSync.set("config", "permission", { ...map, [id]: nextValue })
globalSync.updateConfig({ permission: { [id]: nextValue } }).catch((err: unknown) => {
globalSync.set("config", "permission", before)
const message = err instanceof Error ? err.message : String(err)
showToast({ title: language.t("settings.permissions.toast.updateFailed.title"), description: message })
})
}
return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar">
<div
class="sticky top-0 z-10"
style={{
background:
"linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
}}
>
<div class="flex flex-col gap-1 p-8 max-w-[720px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.permissions.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.permissions.description")}</p>
</div>
</div>
<div class="flex flex-col gap-6 p-8 pt-6 max-w-[720px]">
<div class="flex flex-col gap-2">
<h3 class="text-14-medium text-text-strong">{language.t("settings.permissions.section.tools")}</h3>
<div class="border border-border-weak-base rounded-lg overflow-hidden">
<For each={ITEMS}>
{(item) => (
<SettingsRow title={language.t(item.title)} description={language.t(item.description)}>
<Select
options={actions()}
current={actions().find((o) => o.value === actionFor(item.id))}
value={(o) => o.value}
label={(o) => o.label}
onSelect={(option) => option && setPermission(item.id, option.value)}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
)}
</For>
</div>
</div>
</div>
</div>
)
}
interface SettingsRowProps {
title: string
description: string
children: JSX.Element
}
const SettingsRow: Component<SettingsRowProps> = (props) => {
return (
<div class="flex items-center justify-between gap-4 px-4 py-3 border-b border-border-weak-base last:border-none">
<div class="flex flex-col gap-0.5">
<span class="text-14-medium text-text-strong">{props.title}</span>
<span class="text-12-regular text-text-weak">{props.description}</span>
</div>
<div class="flex-shrink-0">{props.children}</div>
</div>
)
}

View File

@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsProviders: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.providers.description")}</p>
</div>
</div>
)
}

View File

@@ -1,6 +1,7 @@
import type { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
import { ComponentProps, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
import { useSDK } from "@/context/sdk"
import { monoFontFamily, useSettings } from "@/context/settings"
import { SerializeAddon } from "@/addons/serialize"
import { LocalPTY } from "@/context/terminal"
import { resolveThemeVariant, useTheme, withAlpha, type HexColor } from "@opencode-ai/ui/theme"
@@ -9,6 +10,7 @@ export interface TerminalProps extends ComponentProps<"div"> {
pty: LocalPTY
onSubmit?: () => void
onCleanup?: (pty: LocalPTY) => void
onConnect?: () => void
onConnectError?: (error: unknown) => void
}
@@ -36,9 +38,10 @@ const DEFAULT_TERMINAL_COLORS: Record<"light" | "dark", TerminalColors> = {
export const Terminal = (props: TerminalProps) => {
const sdk = useSDK()
const settings = useSettings()
const theme = useTheme()
let container!: HTMLDivElement
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"])
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnect", "onConnectError"])
let ws: WebSocket | undefined
let term: Term | undefined
let ghostty: Ghostty
@@ -82,6 +85,14 @@ export const Terminal = (props: TerminalProps) => {
setOption("theme", colors)
})
createEffect(() => {
const font = monoFontFamily(settings.appearance.font())
if (!term) return
const setOption = (term as unknown as { setOption?: (key: string, value: string) => void }).setOption
if (!setOption) return
setOption("fontFamily", font)
})
const focusTerminal = () => {
const t = term
if (!t) return
@@ -112,7 +123,7 @@ export const Terminal = (props: TerminalProps) => {
cursorBlink: true,
cursorStyle: "bar",
fontSize: 14,
fontFamily: "IBM Plex Mono, monospace",
fontFamily: monoFontFamily(settings.appearance.font()),
allowTransparency: true,
theme: terminalColors(),
scrollback: 10_000,
@@ -231,7 +242,7 @@ export const Terminal = (props: TerminalProps) => {
// console.log("Scroll position:", ydisp)
// })
socket.addEventListener("open", () => {
console.log("WebSocket connected")
local.onConnect?.()
sdk.client.pty
.update({
ptyID: local.pty.id,
@@ -246,15 +257,22 @@ export const Terminal = (props: TerminalProps) => {
t.write(event.data)
})
socket.addEventListener("error", (error) => {
if (disposed) return
console.error("WebSocket error:", error)
props.onConnectError?.(error)
local.onConnectError?.(error)
})
socket.addEventListener("close", () => {
console.log("WebSocket disconnected")
socket.addEventListener("close", (event) => {
if (disposed) return
// Normal closure (code 1000) means PTY process exited - server event handles cleanup
// For other codes (network issues, server restart), trigger error handler
if (event.code !== 1000) {
local.onConnectError?.(new Error(`WebSocket closed abnormally: ${event.code}`))
}
})
})
onCleanup(() => {
disposed = true
if (handleResize) {
window.removeEventListener("resize", handleResize)
}
@@ -283,6 +301,7 @@ export const Terminal = (props: TerminalProps) => {
ref={container}
data-component="terminal"
data-prevent-autofocus
tabIndex={-1}
style={{ "background-color": terminalColors().background }}
classList={{
...(local.classList ?? {}),

View File

@@ -1,19 +1,24 @@
import { createEffect, createMemo, Show } from "solid-js"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Icon } from "@opencode-ai/ui/icon"
import { Button } from "@opencode-ai/ui/button"
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { useTheme } from "@opencode-ai/ui/theme"
import { useLayout } from "@/context/layout"
import { usePlatform } from "@/context/platform"
import { useCommand } from "@/context/command"
import { useLanguage } from "@/context/language"
export function Titlebar() {
const layout = useLayout()
const platform = usePlatform()
const command = useCommand()
const language = useLanguage()
const theme = useTheme()
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
const reserve = createMemo(
() => platform.platform === "desktop" && (platform.os === "windows" || platform.os === "linux"),
)
@@ -71,43 +76,76 @@ export function Titlebar() {
}
return (
<header class="h-10 shrink-0 bg-background-base flex items-center relative">
<header class="h-10 shrink-0 bg-background-base flex items-center relative" data-tauri-drag-region>
<div
classList={{
"flex items-center w-full min-w-0 pr-2": true,
"flex items-center w-full min-w-0": true,
"pl-2": !mac(),
"pr-2": !windows(),
}}
onMouseDown={drag}
data-tauri-drag-region
>
<Show when={mac()}>
<div class="w-[72px] h-full shrink-0" data-tauri-drag-region />
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
<IconButton icon="menu" variant="ghost" class="size-8 rounded-md" onClick={layout.mobileSidebar.toggle} />
<IconButton
icon="menu"
variant="ghost"
class="size-8 rounded-md"
onClick={layout.mobileSidebar.toggle}
aria-label={language.t("sidebar.menu.toggle")}
/>
</div>
</Show>
<Show when={!mac()}>
<div class="xl:hidden w-[48px] shrink-0 flex items-center justify-center">
<IconButton icon="menu" variant="ghost" class="size-8 rounded-md" onClick={layout.mobileSidebar.toggle} />
<IconButton
icon="menu"
variant="ghost"
class="size-8 rounded-md"
onClick={layout.mobileSidebar.toggle}
aria-label={language.t("sidebar.menu.toggle")}
/>
</div>
</Show>
<TooltipKeybind
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0"}
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
placement="bottom"
title="Toggle sidebar"
title={language.t("command.sidebar.toggle")}
keybind={command.keybind("sidebar.toggle")}
>
<IconButton
icon={layout.sidebar.opened() ? "layout-left" : "layout-right"}
<Button
variant="ghost"
class="size-8 rounded-md"
class="group/sidebar-toggle size-6 p-0"
onClick={layout.sidebar.toggle}
/>
aria-label={language.t("command.sidebar.toggle")}
aria-expanded={layout.sidebar.opened()}
>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
size="small"
name={layout.sidebar.opened() ? "layout-left-full" : "layout-left"}
class="group-hover/sidebar-toggle:hidden"
/>
<Icon size="small" name="layout-left-partial" class="hidden group-hover/sidebar-toggle:inline-block" />
<Icon
size="small"
name={layout.sidebar.opened() ? "layout-left" : "layout-left-full"}
class="hidden group-active/sidebar-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" data-tauri-drag-region />
<div class="flex-1 h-full" data-tauri-drag-region />
<div id="opencode-titlebar-right" class="flex items-center gap-3 shrink-0" />
<Show when={reserve()}>
<div class="w-[120px] h-full shrink-0" data-tauri-drag-region />
<div
id="opencode-titlebar-right"
class="flex items-center gap-3 shrink-0 flex-1 justify-end"
data-tauri-drag-region
/>
<Show when={windows()}>
<div data-tauri-decorum-tb class="flex flex-row" />
</Show>
</div>
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">

View File

@@ -1,9 +1,29 @@
import { createMemo, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
import { createEffect, createMemo, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
import { Persist, persisted } from "@/utils/persist"
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
const PALETTE_ID = "command.palette"
const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"
const SUGGESTED_PREFIX = "suggested."
function actionId(id: string) {
if (!id.startsWith(SUGGESTED_PREFIX)) return id
return id.slice(SUGGESTED_PREFIX.length)
}
function normalizeKey(key: string) {
if (key === ",") return "comma"
if (key === "+") return "plus"
if (key === " ") return "space"
return key.toLowerCase()
}
export type KeybindConfig = string
export interface Keybind {
@@ -27,6 +47,14 @@ export interface CommandOption {
onHighlight?: () => (() => void) | void
}
export type CommandCatalogItem = {
title: string
description?: string
category?: string
keybind?: KeybindConfig
slash?: string
}
export function parseKeybind(config: string): Keybind[] {
if (!config || config === "none") return []
@@ -73,7 +101,7 @@ export function parseKeybind(config: string): Keybind[] {
}
export function matchKeybind(keybinds: Keybind[], event: KeyboardEvent): boolean {
const eventKey = event.key.toLowerCase()
const eventKey = normalizeKey(event.key)
for (const kb of keybinds) {
const keyMatch = kb.key === eventKey
@@ -105,15 +133,17 @@ export function formatKeybind(config: string): string {
if (kb.meta) parts.push(IS_MAC ? "⌘" : "Meta")
if (kb.key) {
const arrows: Record<string, string> = {
const keys: Record<string, string> = {
arrowup: "↑",
arrowdown: "↓",
arrowleft: "←",
arrowright: "→",
comma: ",",
plus: "+",
space: "Space",
}
const displayKey =
arrows[kb.key.toLowerCase()] ??
(kb.key.length === 1 ? kb.key.toUpperCase() : kb.key.charAt(0).toUpperCase() + kb.key.slice(1))
const key = kb.key.toLowerCase()
const displayKey = keys[key] ?? (key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1))
parts.push(displayKey)
}
@@ -124,10 +154,24 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
name: "Command",
init: () => {
const dialog = useDialog()
const settings = useSettings()
const language = useLanguage()
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
const [suspendCount, setSuspendCount] = createSignal(0)
const options = createMemo(() => {
const [catalog, setCatalog, _, catalogReady] = persisted(
Persist.global("command.catalog.v1"),
createStore<Record<string, CommandCatalogItem>>({}),
)
const bind = (id: string, def: KeybindConfig | undefined) => {
const custom = settings.keybinds.get(actionId(id))
const config = custom ?? def
if (!config || config === "none") return
return config
}
const registered = createMemo(() => {
const seen = new Set<string>()
const all: CommandOption[] = []
@@ -139,15 +183,41 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
}
}
const suggested = all.filter((x) => x.suggested && !x.disabled)
return all
})
createEffect(() => {
if (!catalogReady()) return
for (const opt of registered()) {
const id = actionId(opt.id)
setCatalog(id, {
title: opt.title,
description: opt.description,
category: opt.category,
keybind: opt.keybind,
slash: opt.slash,
})
}
})
const catalogOptions = createMemo(() => Object.entries(catalog).map(([id, meta]) => ({ id, ...meta })))
const options = createMemo(() => {
const resolved = registered().map((opt) => ({
...opt,
keybind: bind(opt.id, opt.keybind),
}))
const suggested = resolved.filter((x) => x.suggested && !x.disabled)
return [
...suggested.map((x) => ({
...x,
id: "suggested." + x.id,
category: "Suggested",
id: SUGGESTED_PREFIX + x.id,
category: language.t("command.category.suggested"),
})),
...all,
...resolved,
]
})
@@ -169,7 +239,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
const handleKeyDown = (event: KeyboardEvent) => {
if (suspended() || dialog.active) return
const paletteKeybinds = parseKeybind("mod+shift+p")
const paletteKeybinds = parseKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND)
if (matchKeybind(paletteKeybinds, event)) {
event.preventDefault()
showPalette()
@@ -209,15 +279,27 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
run(id, source)
},
keybind(id: string) {
const option = options().find((x) => x.id === id || x.id === "suggested." + id)
if (!option?.keybind) return ""
return formatKeybind(option.keybind)
if (id === PALETTE_ID) {
return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND)
}
const base = actionId(id)
const option = options().find((x) => actionId(x.id) === base)
if (option?.keybind) return formatKeybind(option.keybind)
const meta = catalog[base]
const config = bind(base, meta?.keybind)
if (!config) return ""
return formatKeybind(config)
},
show: showPalette,
keybinds(enabled: boolean) {
setSuspendCount((count) => count + (enabled ? -1 : 1))
},
suspended,
get catalog() {
return catalogOptions()
},
get options() {
return options()
},

View File

@@ -0,0 +1,140 @@
import { batch, createMemo, createRoot, createSignal, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useParams } from "@solidjs/router"
import { Persist, persisted } from "@/utils/persist"
import type { SelectedLineRange } from "@/context/file"
export type LineComment = {
id: string
file: string
selection: SelectedLineRange
comment: string
time: number
}
type CommentFocus = { file: string; id: string }
const WORKSPACE_KEY = "__workspace__"
const MAX_COMMENT_SESSIONS = 20
type CommentSession = ReturnType<typeof createCommentSession>
type CommentCacheEntry = {
value: CommentSession
dispose: VoidFunction
}
function createCommentSession(dir: string, id: string | undefined) {
const legacy = `${dir}/comments${id ? "/" + id : ""}.v1`
const [store, setStore, _, ready] = persisted(
Persist.scoped(dir, id, "comments", [legacy]),
createStore<{
comments: Record<string, LineComment[]>
}>({
comments: {},
}),
)
const [focus, setFocus] = createSignal<CommentFocus | null>(null)
const list = (file: string) => store.comments[file] ?? []
const add = (input: Omit<LineComment, "id" | "time">) => {
const next: LineComment = {
id: crypto.randomUUID(),
time: Date.now(),
...input,
}
batch(() => {
setStore("comments", input.file, (items) => [...(items ?? []), next])
setFocus({ file: input.file, id: next.id })
})
return next
}
const remove = (file: string, id: string) => {
setStore("comments", file, (items) => (items ?? []).filter((x) => x.id !== id))
setFocus((current) => (current?.id === id ? null : current))
}
const all = createMemo(() => {
const files = Object.keys(store.comments)
const items = files.flatMap((file) => store.comments[file] ?? [])
return items.slice().sort((a, b) => a.time - b.time)
})
return {
ready,
list,
all,
add,
remove,
focus: createMemo(() => focus()),
setFocus,
clearFocus: () => setFocus(null),
}
}
export const { use: useComments, provider: CommentsProvider } = createSimpleContext({
name: "Comments",
gate: false,
init: () => {
const params = useParams()
const cache = new Map<string, CommentCacheEntry>()
const disposeAll = () => {
for (const entry of cache.values()) {
entry.dispose()
}
cache.clear()
}
onCleanup(disposeAll)
const prune = () => {
while (cache.size > MAX_COMMENT_SESSIONS) {
const first = cache.keys().next().value
if (!first) return
const entry = cache.get(first)
entry?.dispose()
cache.delete(first)
}
}
const load = (dir: string, id: string | undefined) => {
const key = `${dir}:${id ?? WORKSPACE_KEY}`
const existing = cache.get(key)
if (existing) {
cache.delete(key)
cache.set(key, existing)
return existing.value
}
const entry = createRoot((dispose) => ({
value: createCommentSession(dir, id),
dispose,
}))
cache.set(key, entry)
prune()
return entry.value
}
const session = createMemo(() => load(params.dir!, params.id))
return {
ready: () => session().ready(),
list: (file: string) => session().list(file),
all: () => session().all(),
add: (input: Omit<LineComment, "id" | "time">) => session().add(input),
remove: (file: string, id: string) => session().remove(file, id),
focus: () => session().focus(),
setFocus: (focus: CommentFocus | null) => session().setFocus(focus),
clearFocus: () => session().clearFocus(),
}
},
})

View File

@@ -7,6 +7,7 @@ import { useParams } from "@solidjs/router"
import { getFilename } from "@opencode-ai/util/path"
import { useSDK } from "./sdk"
import { useSync } from "./sync"
import { useLanguage } from "@/context/language"
import { Persist, persisted } from "@/utils/persist"
export type FileSelection = {
@@ -186,6 +187,9 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
const sdk = useSDK()
const sync = useSync()
const params = useParams()
const language = useLanguage()
const scope = createMemo(() => sdk.directory)
const directory = createMemo(() => sync.data.path.directory)
@@ -232,6 +236,12 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
file: {},
})
createEffect(() => {
scope()
inflight.clear()
setStore("file", {})
})
const viewCache = new Map<string, ViewCacheEntry>()
const disposeViews = () => {
@@ -282,12 +292,16 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
const path = normalize(input)
if (!path) return Promise.resolve()
const directory = scope()
const key = `${directory}\n${path}`
const client = sdk.client
ensure(path)
const current = store.file[path]
if (!options?.force && current?.loaded) return Promise.resolve()
const pending = inflight.get(path)
const pending = inflight.get(key)
if (pending) return pending
setStore(
@@ -299,9 +313,10 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
}),
)
const promise = sdk.client.file
const promise = client.file
.read({ path })
.then((x) => {
if (scope() !== directory) return
setStore(
"file",
path,
@@ -313,6 +328,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
)
})
.catch((e) => {
if (scope() !== directory) return
setStore(
"file",
path,
@@ -323,15 +339,15 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
)
showToast({
variant: "error",
title: "Failed to load file",
title: language.t("toast.file.loadFailed.title"),
description: e.message,
})
})
.finally(() => {
inflight.delete(path)
inflight.delete(key)
})
inflight.set(path, promise)
inflight.set(key, promise)
return promise
}

View File

@@ -28,6 +28,7 @@ import {
batch,
createContext,
createEffect,
untrack,
getOwner,
runWithOwner,
useContext,
@@ -41,13 +42,27 @@ import {
import { showToast } from "@opencode-ai/ui/toast"
import { getFilename } from "@opencode-ai/util/path"
import { usePlatform } from "./platform"
import { useLanguage } from "@/context/language"
import { Persist, persisted } from "@/utils/persist"
type ProjectMeta = {
name?: string
icon?: {
override?: string
color?: string
}
commands?: {
start?: string
}
}
type State = {
status: "loading" | "partial" | "complete"
agent: Agent[]
command: Command[]
project: string
projectMeta: ProjectMeta | undefined
icon: string | undefined
provider: ProviderListResponse
config: Config
path: Path
@@ -88,12 +103,48 @@ type VcsCache = {
ready: Accessor<boolean>
}
type MetaCache = {
store: Store<{ value: ProjectMeta | undefined }>
setStore: SetStoreFunction<{ value: ProjectMeta | undefined }>
ready: Accessor<boolean>
}
type IconCache = {
store: Store<{ value: string | undefined }>
setStore: SetStoreFunction<{ value: string | undefined }>
ready: Accessor<boolean>
}
type ChildOptions = {
bootstrap?: boolean
}
function createGlobalSync() {
const globalSDK = useGlobalSDK()
const platform = usePlatform()
const language = useLanguage()
const owner = getOwner()
if (!owner) throw new Error("GlobalSync must be created within owner")
const vcsCache = new Map<string, VcsCache>()
const metaCache = new Map<string, MetaCache>()
const iconCache = new Map<string, IconCache>()
const [projectCache, setProjectCache, , projectCacheReady] = persisted(
Persist.global("globalSync.project", ["globalSync.project.v1"]),
createStore({ value: [] as Project[] }),
)
const sanitizeProject = (project: Project) => {
if (!project.icon?.url && !project.icon?.override) return project
return {
...project,
icon: {
...project.icon,
url: undefined,
override: undefined,
},
}
}
const [globalStore, setGlobalStore] = createStore<{
ready: boolean
error?: InitError
@@ -101,17 +152,55 @@ function createGlobalSync() {
project: Project[]
provider: ProviderListResponse
provider_auth: ProviderAuthResponse
config: Config
reload: undefined | "pending" | "complete"
}>({
ready: false,
path: { state: "", config: "", worktree: "", directory: "", home: "" },
project: [],
project: projectCache.value,
provider: { all: [], connected: [], default: {} },
provider_auth: {},
config: {},
reload: undefined,
})
let bootstrapQueue: string[] = []
createEffect(() => {
if (!projectCacheReady()) return
if (globalStore.project.length !== 0) return
const cached = projectCache.value
if (cached.length === 0) return
setGlobalStore("project", cached)
})
createEffect(() => {
if (!projectCacheReady()) return
const projects = globalStore.project
if (projects.length === 0) {
const cachedLength = untrack(() => projectCache.value.length)
if (cachedLength !== 0) return
}
setProjectCache("value", projects.map(sanitizeProject))
})
createEffect(async () => {
if (globalStore.reload !== "complete") return
if (bootstrapQueue.length) {
for (const directory of bootstrapQueue) {
bootstrapInstance(directory)
}
bootstrap()
}
bootstrapQueue = []
setGlobalStore("reload", undefined)
})
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
const booting = new Map<string, Promise<void>>()
const sessionLoads = new Map<string, Promise<void>>()
const sessionMeta = new Map<string, { limit: number }>()
function child(directory: string) {
function ensureChild(directory: string) {
if (!directory) console.error("No directory provided")
if (!children[directory]) {
const cache = runWithOwner(owner, () =>
@@ -123,9 +212,29 @@ function createGlobalSync() {
if (!cache) throw new Error("Failed to create persisted cache")
vcsCache.set(directory, { store: cache[0], setStore: cache[1], ready: cache[3] })
const meta = runWithOwner(owner, () =>
persisted(
Persist.workspace(directory, "project", ["project.v1"]),
createStore({ value: undefined as ProjectMeta | undefined }),
),
)
if (!meta) throw new Error("Failed to create persisted project metadata")
metaCache.set(directory, { store: meta[0], setStore: meta[1], ready: meta[3] })
const icon = runWithOwner(owner, () =>
persisted(
Persist.workspace(directory, "icon", ["icon.v1"]),
createStore({ value: undefined as string | undefined }),
),
)
if (!icon) throw new Error("Failed to create persisted project icon")
iconCache.set(directory, { store: icon[0], setStore: icon[1], ready: icon[3] })
const init = () => {
children[directory] = createStore<State>({
const child = createStore<State>({
project: "",
projectMeta: meta[0].value,
icon: icon[0].value,
provider: { all: [], connected: [], default: {} },
config: {},
path: { state: "", config: "", worktree: "", directory: "", home: "" },
@@ -146,7 +255,16 @@ function createGlobalSync() {
message: {},
part: {},
})
bootstrapInstance(directory)
children[directory] = child
createEffect(() => {
child[1]("projectMeta", meta[0].value)
})
createEffect(() => {
child[1]("icon", icon[0].value)
})
}
runWithOwner(owner, init)
@@ -156,11 +274,24 @@ function createGlobalSync() {
return childStore
}
async function loadSessions(directory: string) {
const [store, setStore] = child(directory)
const limit = store.limit
function child(directory: string, options: ChildOptions = {}) {
const childStore = ensureChild(directory)
const shouldBootstrap = options.bootstrap ?? true
if (shouldBootstrap && childStore[0].status === "loading") {
void bootstrapInstance(directory)
}
return childStore
}
return globalSDK.client.session
async function loadSessions(directory: string) {
const pending = sessionLoads.get(directory)
if (pending) return pending
const [store, setStore] = child(directory, { bootstrap: false })
const meta = sessionMeta.get(directory)
if (meta && meta.limit >= store.limit) return
const promise = globalSDK.client.session
.list({ directory, roots: true })
.then((x) => {
const nonArchived = (x.data ?? [])
@@ -169,9 +300,15 @@ function createGlobalSync() {
.slice()
.sort((a, b) => a.id.localeCompare(b.id))
// Read the current limit at resolve-time so callers that bump the limit while
// a request is in-flight still get the expanded result.
const limit = store.limit
const sandboxWorkspace = globalStore.project.some((p) => (p.sandboxes ?? []).includes(directory))
if (sandboxWorkspace) {
setStore("sessionTotal", nonArchived.length)
setStore("session", reconcile(nonArchived, { key: "id" }))
sessionMeta.set(directory, { limit })
return
}
@@ -185,136 +322,168 @@ function createGlobalSync() {
// Store total session count (used for "load more" pagination)
setStore("sessionTotal", nonArchived.length)
setStore("session", reconcile(sessions, { key: "id" }))
sessionMeta.set(directory, { limit })
})
.catch((err) => {
console.error("Failed to load sessions", err)
const project = getFilename(directory)
showToast({ title: `Failed to load sessions for ${project}`, description: err.message })
showToast({ title: language.t("toast.session.listFailed.title", { project }), description: err.message })
})
sessionLoads.set(directory, promise)
promise.finally(() => {
sessionLoads.delete(directory)
})
return promise
}
async function bootstrapInstance(directory: string) {
if (!directory) return
const [store, setStore] = child(directory)
const cache = vcsCache.get(directory)
if (!cache) return
const sdk = createOpencodeClient({
baseUrl: globalSDK.url,
fetch: platform.fetch,
directory,
throwOnError: true,
})
const pending = booting.get(directory)
if (pending) return pending
createEffect(() => {
if (!cache.ready()) return
const cached = cache.store.value
if (!cached?.branch) return
setStore("vcs", (value) => value ?? cached)
})
const promise = (async () => {
const [store, setStore] = ensureChild(directory)
const cache = vcsCache.get(directory)
if (!cache) return
const meta = metaCache.get(directory)
if (!meta) return
const sdk = createOpencodeClient({
baseUrl: globalSDK.url,
fetch: platform.fetch,
directory,
throwOnError: true,
})
const blockingRequests = {
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
provider: () =>
sdk.provider.list().then((x) => {
const data = x.data!
setStore("provider", {
...data,
all: data.all.map((provider) => ({
...provider,
models: Object.fromEntries(
Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated"),
),
})),
setStore("status", "loading")
createEffect(() => {
if (!cache.ready()) return
const cached = cache.store.value
if (!cached?.branch) return
setStore("vcs", (value) => value ?? cached)
})
// projectMeta is synced from persisted storage in ensureChild.
const blockingRequests = {
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
provider: () =>
sdk.provider.list().then((x) => {
const data = x.data!
setStore("provider", {
...data,
all: data.all.map((provider) => ({
...provider,
models: Object.fromEntries(
Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated"),
),
})),
})
}),
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
}
try {
await Promise.all(Object.values(blockingRequests).map((p) => retry(p)))
} catch (err) {
console.error("Failed to bootstrap instance", err)
const project = getFilename(directory)
const message = err instanceof Error ? err.message : String(err)
showToast({ title: `Failed to reload ${project}`, description: message })
setStore("status", "partial")
return
}
if (store.status !== "complete") setStore("status", "partial")
Promise.all([
sdk.path.get().then((x) => setStore("path", x.data!)),
sdk.command.list().then((x) => setStore("command", x.data ?? [])),
sdk.session.status().then((x) => setStore("session_status", x.data!)),
loadSessions(directory),
sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
sdk.vcs.get().then((x) => {
const next = x.data ?? store.vcs
setStore("vcs", next)
if (next?.branch) cache.setStore("value", next)
}),
sdk.permission.list().then((x) => {
const grouped: Record<string, PermissionRequest[]> = {}
for (const perm of x.data ?? []) {
if (!perm?.id || !perm.sessionID) continue
const existing = grouped[perm.sessionID]
if (existing) {
existing.push(perm)
continue
}
grouped[perm.sessionID] = [perm]
}
batch(() => {
for (const sessionID of Object.keys(store.permission)) {
if (grouped[sessionID]) continue
setStore("permission", sessionID, [])
}
for (const [sessionID, permissions] of Object.entries(grouped)) {
setStore(
"permission",
sessionID,
reconcile(
permissions
.filter((p) => !!p?.id)
.slice()
.sort((a, b) => a.id.localeCompare(b.id)),
{ key: "id" },
),
)
}
})
}),
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
}
await Promise.all(Object.values(blockingRequests).map((p) => retry(p).catch((e) => setGlobalStore("error", e))))
.then(() => {
if (store.status !== "complete") setStore("status", "partial")
// non-blocking
Promise.all([
sdk.path.get().then((x) => setStore("path", x.data!)),
sdk.command.list().then((x) => setStore("command", x.data ?? [])),
sdk.session.status().then((x) => setStore("session_status", x.data!)),
loadSessions(directory),
sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
sdk.vcs.get().then((x) => {
const next = x.data ?? store.vcs
setStore("vcs", next)
if (next?.branch) cache.setStore("value", next)
}),
sdk.permission.list().then((x) => {
const grouped: Record<string, PermissionRequest[]> = {}
for (const perm of x.data ?? []) {
if (!perm?.id || !perm.sessionID) continue
const existing = grouped[perm.sessionID]
if (existing) {
existing.push(perm)
continue
}
grouped[perm.sessionID] = [perm]
sdk.question.list().then((x) => {
const grouped: Record<string, QuestionRequest[]> = {}
for (const question of x.data ?? []) {
if (!question?.id || !question.sessionID) continue
const existing = grouped[question.sessionID]
if (existing) {
existing.push(question)
continue
}
grouped[question.sessionID] = [question]
}
batch(() => {
for (const sessionID of Object.keys(store.permission)) {
if (grouped[sessionID]) continue
setStore("permission", sessionID, [])
}
for (const [sessionID, permissions] of Object.entries(grouped)) {
setStore(
"permission",
sessionID,
reconcile(
permissions
.filter((p) => !!p?.id)
.slice()
.sort((a, b) => a.id.localeCompare(b.id)),
{ key: "id" },
),
)
}
})
}),
sdk.question.list().then((x) => {
const grouped: Record<string, QuestionRequest[]> = {}
for (const question of x.data ?? []) {
if (!question?.id || !question.sessionID) continue
const existing = grouped[question.sessionID]
if (existing) {
existing.push(question)
continue
}
grouped[question.sessionID] = [question]
batch(() => {
for (const sessionID of Object.keys(store.question)) {
if (grouped[sessionID]) continue
setStore("question", sessionID, [])
}
batch(() => {
for (const sessionID of Object.keys(store.question)) {
if (grouped[sessionID]) continue
setStore("question", sessionID, [])
}
for (const [sessionID, questions] of Object.entries(grouped)) {
setStore(
"question",
sessionID,
reconcile(
questions
.filter((q) => !!q?.id)
.slice()
.sort((a, b) => a.id.localeCompare(b.id)),
{ key: "id" },
),
)
}
})
}),
]).then(() => {
setStore("status", "complete")
})
for (const [sessionID, questions] of Object.entries(grouped)) {
setStore(
"question",
sessionID,
reconcile(
questions
.filter((q) => !!q?.id)
.slice()
.sort((a, b) => a.id.localeCompare(b.id)),
{ key: "id" },
),
)
}
})
}),
]).then(() => {
setStore("status", "complete")
})
.catch((e) => setGlobalStore("error", e))
})()
booting.set(directory, promise)
promise.finally(() => {
booting.delete(directory)
})
return promise
}
const unsub = globalSDK.event.listen((e) => {
@@ -324,6 +493,7 @@ function createGlobalSync() {
if (directory === "global") {
switch (event?.type) {
case "global.disposed": {
if (globalStore.reload) return
bootstrap()
break
}
@@ -345,9 +515,16 @@ function createGlobalSync() {
return
}
const [store, setStore] = child(directory)
const existing = children[directory]
if (!existing) return
const [store, setStore] = existing
switch (event.type) {
case "server.instance.disposed": {
if (globalStore.reload) {
bootstrapQueue.push(directory)
return
}
bootstrapInstance(directory)
break
}
@@ -395,6 +572,20 @@ function createGlobalSync() {
)
break
}
case "session.deleted": {
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
if (result.found) {
setStore(
"session",
produce((draft) => {
draft.splice(result.index, 1)
}),
)
}
if (event.properties.info.parentID) break
setStore("sessionTotal", (value) => Math.max(0, value - 1))
break
}
case "session.diff":
setStore("session_diff", event.properties.sessionID, reconcile(event.properties.diff, { key: "file" }))
break
@@ -578,10 +769,7 @@ function createGlobalSync() {
.then((x) => x.data)
.catch(() => undefined)
if (!health?.healthy) {
setGlobalStore(
"error",
new Error(`Could not connect to server. Is there a server running at \`${globalSDK.url}\`?`),
)
setGlobalStore("error", new Error(language.t("error.globalSync.connectFailed", { url: globalSDK.url })))
return
}
@@ -591,6 +779,11 @@ function createGlobalSync() {
setGlobalStore("path", x.data!)
}),
),
retry(() =>
globalSDK.client.config.get().then((x) => {
setGlobalStore("config", x.data!)
}),
),
retry(() =>
globalSDK.client.project.list().then(async (x) => {
const projects = (x.data ?? [])
@@ -629,8 +822,35 @@ function createGlobalSync() {
bootstrap()
})
function projectMeta(directory: string, patch: ProjectMeta) {
const [store, setStore] = ensureChild(directory)
const cached = metaCache.get(directory)
if (!cached) return
const previous = store.projectMeta ?? {}
const icon = patch.icon ? { ...(previous.icon ?? {}), ...patch.icon } : previous.icon
const commands = patch.commands ? { ...(previous.commands ?? {}), ...patch.commands } : previous.commands
const next = {
...previous,
...patch,
icon,
commands,
}
cached.setStore("value", next)
setStore("projectMeta", next)
}
function projectIcon(directory: string, value: string | undefined) {
const [store, setStore] = ensureChild(directory)
const cached = iconCache.get(directory)
if (!cached) return
if (store.icon === value) return
cached.setStore("value", value)
setStore("icon", value)
}
return {
data: globalStore,
set: setGlobalStore,
get ready() {
return globalStore.ready
},
@@ -639,8 +859,18 @@ function createGlobalSync() {
},
child,
bootstrap,
updateConfig: async (config: Config) => {
setGlobalStore("reload", "pending")
const response = await globalSDK.client.config.update({ config })
setTimeout(() => {
setGlobalStore("reload", "complete")
}, 1000)
return response
},
project: {
loadSessions,
meta: projectMeta,
icon: projectIcon,
},
}
}

View File

@@ -0,0 +1,161 @@
import * as i18n from "@solid-primitives/i18n"
import { createEffect, createMemo } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { Persist, persisted } from "@/utils/persist"
import { dict as en } from "@/i18n/en"
import { dict as zh } from "@/i18n/zh"
import { dict as zht } from "@/i18n/zht"
import { dict as ko } from "@/i18n/ko"
import { dict as de } from "@/i18n/de"
import { dict as es } from "@/i18n/es"
import { dict as fr } from "@/i18n/fr"
import { dict as da } from "@/i18n/da"
import { dict as ja } from "@/i18n/ja"
import { dict as pl } from "@/i18n/pl"
import { dict as ru } from "@/i18n/ru"
import { dict as ar } from "@/i18n/ar"
import { dict as no } from "@/i18n/no"
import { dict as br } from "@/i18n/br"
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
import { dict as uiKo } from "@opencode-ai/ui/i18n/ko"
import { dict as uiDe } from "@opencode-ai/ui/i18n/de"
import { dict as uiEs } from "@opencode-ai/ui/i18n/es"
import { dict as uiFr } from "@opencode-ai/ui/i18n/fr"
import { dict as uiDa } from "@opencode-ai/ui/i18n/da"
import { dict as uiJa } from "@opencode-ai/ui/i18n/ja"
import { dict as uiPl } from "@opencode-ai/ui/i18n/pl"
import { dict as uiRu } from "@opencode-ai/ui/i18n/ru"
import { dict as uiAr } from "@opencode-ai/ui/i18n/ar"
import { dict as uiNo } from "@opencode-ai/ui/i18n/no"
import { dict as uiBr } from "@opencode-ai/ui/i18n/br"
export type Locale = "en" | "zh" | "zht" | "ko" | "de" | "es" | "fr" | "da" | "ja" | "pl" | "ru" | "ar" | "no" | "br"
type RawDictionary = typeof en & typeof uiEn
type Dictionary = i18n.Flatten<RawDictionary>
const LOCALES: readonly Locale[] = ["en", "zh", "zht", "ko", "de", "es", "fr", "da", "ja", "pl", "ru", "ar", "no", "br"]
function detectLocale(): Locale {
if (typeof navigator !== "object") return "en"
const languages = navigator.languages?.length ? navigator.languages : [navigator.language]
for (const language of languages) {
if (!language) continue
if (language.toLowerCase().startsWith("zh")) {
if (language.toLowerCase().includes("hant")) return "zht"
return "zh"
}
if (language.toLowerCase().startsWith("ko")) return "ko"
if (language.toLowerCase().startsWith("de")) return "de"
if (language.toLowerCase().startsWith("es")) return "es"
if (language.toLowerCase().startsWith("fr")) return "fr"
if (language.toLowerCase().startsWith("da")) return "da"
if (language.toLowerCase().startsWith("ja")) return "ja"
if (language.toLowerCase().startsWith("pl")) return "pl"
if (language.toLowerCase().startsWith("ru")) return "ru"
if (language.toLowerCase().startsWith("ar")) return "ar"
if (
language.toLowerCase().startsWith("no") ||
language.toLowerCase().startsWith("nb") ||
language.toLowerCase().startsWith("nn")
)
return "no"
if (language.toLowerCase().startsWith("pt")) return "br"
}
return "en"
}
export const { use: useLanguage, provider: LanguageProvider } = createSimpleContext({
name: "Language",
init: () => {
const [store, setStore, _, ready] = persisted(
Persist.global("language", ["language.v1"]),
createStore({
locale: detectLocale() as Locale,
}),
)
const locale = createMemo<Locale>(() => {
if (store.locale === "zh") return "zh"
if (store.locale === "zht") return "zht"
if (store.locale === "ko") return "ko"
if (store.locale === "de") return "de"
if (store.locale === "es") return "es"
if (store.locale === "fr") return "fr"
if (store.locale === "da") return "da"
if (store.locale === "ja") return "ja"
if (store.locale === "pl") return "pl"
if (store.locale === "ru") return "ru"
if (store.locale === "ar") return "ar"
if (store.locale === "no") return "no"
if (store.locale === "br") return "br"
return "en"
})
createEffect(() => {
const current = locale()
if (store.locale === current) return
setStore("locale", current)
})
const base = i18n.flatten({ ...en, ...uiEn })
const dict = createMemo<Dictionary>(() => {
if (locale() === "en") return base
if (locale() === "zh") return { ...base, ...i18n.flatten({ ...zh, ...uiZh }) }
if (locale() === "zht") return { ...base, ...i18n.flatten({ ...zht, ...uiZht }) }
if (locale() === "de") return { ...base, ...i18n.flatten({ ...de, ...uiDe }) }
if (locale() === "es") return { ...base, ...i18n.flatten({ ...es, ...uiEs }) }
if (locale() === "fr") return { ...base, ...i18n.flatten({ ...fr, ...uiFr }) }
if (locale() === "da") return { ...base, ...i18n.flatten({ ...da, ...uiDa }) }
if (locale() === "ja") return { ...base, ...i18n.flatten({ ...ja, ...uiJa }) }
if (locale() === "pl") return { ...base, ...i18n.flatten({ ...pl, ...uiPl }) }
if (locale() === "ru") return { ...base, ...i18n.flatten({ ...ru, ...uiRu }) }
if (locale() === "ar") return { ...base, ...i18n.flatten({ ...ar, ...uiAr }) }
if (locale() === "no") return { ...base, ...i18n.flatten({ ...no, ...uiNo }) }
if (locale() === "br") return { ...base, ...i18n.flatten({ ...br, ...uiBr }) }
return { ...base, ...i18n.flatten({ ...ko, ...uiKo }) }
})
const t = i18n.translator(dict, i18n.resolveTemplate)
const labelKey: Record<Locale, keyof Dictionary> = {
en: "language.en",
zh: "language.zh",
zht: "language.zht",
ko: "language.ko",
de: "language.de",
es: "language.es",
fr: "language.fr",
da: "language.da",
ja: "language.ja",
pl: "language.pl",
ru: "language.ru",
ar: "language.ar",
no: "language.no",
br: "language.br",
}
const label = (value: Locale) => t(labelKey[value])
createEffect(() => {
if (typeof document !== "object") return
document.documentElement.lang = locale()
})
return {
ready,
locale,
locales: LOCALES,
label,
t,
setLocale(next: Locale) {
setStore("locale", next)
},
}
},
})

View File

@@ -1,5 +1,5 @@
import { createStore, produce } from "solid-js/store"
import { batch, createEffect, createMemo, onCleanup, onMount } from "solid-js"
import { batch, createEffect, createMemo, on, onCleanup, onMount, type Accessor } from "solid-js"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSync } from "./global-sync"
import { useGlobalSDK } from "./global-sdk"
@@ -222,15 +222,38 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const metadata = projectID
? globalSync.data.project.find((x) => x.id === projectID)
: globalSync.data.project.find((x) => x.worktree === project.worktree)
return {
const local = childStore.projectMeta
const localOverride =
local?.name !== undefined ||
local?.commands?.start !== undefined ||
local?.icon?.override !== undefined ||
local?.icon?.color !== undefined
const base = {
...(metadata ?? {}),
...project,
icon: {
url: metadata?.icon?.url,
override: metadata?.icon?.override,
override: metadata?.icon?.override ?? childStore.icon,
color: metadata?.icon?.color,
},
}
const isGlobal = projectID === "global" || (metadata?.id === undefined && localOverride)
if (!isGlobal) return base
return {
...base,
id: base.id ?? "global",
name: local?.name,
commands: local?.commands,
icon: {
url: base.icon?.url,
override: local?.icon?.override,
color: local?.icon?.color,
},
}
}
const roots = createMemo(() => {
@@ -283,6 +306,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const projects = enriched()
if (projects.length === 0) return
if (globalSync.ready) {
for (const project of projects) {
if (!project.id) continue
if (project.id === "global") continue
globalSync.project.icon(project.worktree, project.icon?.override)
}
}
const used = new Set<string>()
for (const project of projects) {
const color = project.icon?.color ?? colors[project.worktree]
@@ -291,12 +322,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
for (const project of projects) {
if (project.icon?.color) continue
if (colors[project.worktree]) continue
const color = pickAvailableColor(used)
used.add(color)
setColors(project.worktree, color)
const existing = colors[project.worktree]
const color = existing ?? pickAvailableColor(used)
if (!existing) {
used.add(color)
setColors(project.worktree, color)
}
if (!project.id) continue
void globalSdk.client.project.update({ projectID: project.id, icon: { color } })
if (project.id === "global") {
globalSync.project.meta(project.worktree, { icon: { color } })
continue
}
void globalSdk.client.project.update({ projectID: project.id, directory: project.worktree, icon: { color } })
}
})
@@ -395,10 +432,24 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setStore("mobileSidebar", "opened", (x) => !x)
},
},
view(sessionKey: string) {
touch(sessionKey)
scroll.seed(sessionKey)
const s = createMemo(() => store.sessionView[sessionKey] ?? { scroll: {} })
view(sessionKey: string | Accessor<string>) {
const key = typeof sessionKey === "function" ? sessionKey : () => sessionKey
touch(key())
scroll.seed(key())
createEffect(
on(
key,
(value) => {
touch(value)
scroll.seed(value)
},
{ defer: true },
),
)
const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? true)
@@ -428,10 +479,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
return {
scroll(tab: string) {
return scroll.scroll(sessionKey, tab)
return scroll.scroll(key(), tab)
},
setScroll(tab: string, pos: SessionScroll) {
scroll.setScroll(sessionKey, tab, pos)
scroll.setScroll(key(), tab, pos)
},
terminal: {
opened: terminalOpened,
@@ -460,9 +511,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
review: {
open: createMemo(() => s().reviewOpen),
setOpen(open: string[]) {
const current = store.sessionView[sessionKey]
const session = key()
const current = store.sessionView[session]
if (!current) {
setStore("sessionView", sessionKey, {
setStore("sessionView", session, {
scroll: {},
reviewOpen: open,
})
@@ -470,93 +522,111 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
}
if (same(current.reviewOpen, open)) return
setStore("sessionView", sessionKey, "reviewOpen", open)
setStore("sessionView", session, "reviewOpen", open)
},
},
}
},
tabs(sessionKey: string) {
touch(sessionKey)
const tabs = createMemo(() => store.sessionTabs[sessionKey] ?? { all: [] })
tabs(sessionKey: string | Accessor<string>) {
const key = typeof sessionKey === "function" ? sessionKey : () => sessionKey
touch(key())
createEffect(
on(
key,
(value) => {
touch(value)
},
{ defer: true },
),
)
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
return {
tabs,
active: createMemo(() => tabs().active),
all: createMemo(() => tabs().all),
setActive(tab: string | undefined) {
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all: [], active: tab })
const session = key()
if (!store.sessionTabs[session]) {
setStore("sessionTabs", session, { all: [], active: tab })
} else {
setStore("sessionTabs", sessionKey, "active", tab)
setStore("sessionTabs", session, "active", tab)
}
},
setAll(all: string[]) {
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all, active: undefined })
const session = key()
if (!store.sessionTabs[session]) {
setStore("sessionTabs", session, { all, active: undefined })
} else {
setStore("sessionTabs", sessionKey, "all", all)
setStore("sessionTabs", session, "all", all)
}
},
async open(tab: string) {
const current = store.sessionTabs[sessionKey] ?? { all: [] }
const session = key()
const current = store.sessionTabs[session] ?? { all: [] }
if (tab === "review") {
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all: [], active: tab })
if (!store.sessionTabs[session]) {
setStore("sessionTabs", session, { all: [], active: tab })
return
}
setStore("sessionTabs", sessionKey, "active", tab)
setStore("sessionTabs", session, "active", tab)
return
}
if (tab === "context") {
const all = [tab, ...current.all.filter((x) => x !== tab)]
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all, active: tab })
if (!store.sessionTabs[session]) {
setStore("sessionTabs", session, { all, active: tab })
return
}
setStore("sessionTabs", sessionKey, "all", all)
setStore("sessionTabs", sessionKey, "active", tab)
setStore("sessionTabs", session, "all", all)
setStore("sessionTabs", session, "active", tab)
return
}
if (!current.all.includes(tab)) {
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all: [tab], active: tab })
if (!store.sessionTabs[session]) {
setStore("sessionTabs", session, { all: [tab], active: tab })
return
}
setStore("sessionTabs", sessionKey, "all", [...current.all, tab])
setStore("sessionTabs", sessionKey, "active", tab)
setStore("sessionTabs", session, "all", [...current.all, tab])
setStore("sessionTabs", session, "active", tab)
return
}
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all: current.all, active: tab })
if (!store.sessionTabs[session]) {
setStore("sessionTabs", session, { all: current.all, active: tab })
return
}
setStore("sessionTabs", sessionKey, "active", tab)
setStore("sessionTabs", session, "active", tab)
},
close(tab: string) {
const current = store.sessionTabs[sessionKey]
const session = key()
const current = store.sessionTabs[session]
if (!current) return
const all = current.all.filter((x) => x !== tab)
batch(() => {
setStore("sessionTabs", sessionKey, "all", all)
setStore("sessionTabs", session, "all", all)
if (current.active !== tab) return
const index = current.all.findIndex((f) => f === tab)
const next = all[index - 1] ?? all[0]
setStore("sessionTabs", sessionKey, "active", next)
setStore("sessionTabs", session, "active", next)
})
},
move(tab: string, to: number) {
const current = store.sessionTabs[sessionKey]
const session = key()
const current = store.sessionTabs[session]
if (!current) return
const index = current.all.findIndex((f) => f === tab)
if (index === -1) return
setStore(
"sessionTabs",
sessionKey,
session,
"all",
produce((opened) => {
opened.splice(to, 0, opened.splice(index, 1)[0])

View File

@@ -1,5 +1,5 @@
import { createStore, produce, reconcile } from "solid-js/store"
import { batch, createMemo, onCleanup } from "solid-js"
import { batch, createEffect, createMemo, onCleanup } from "solid-js"
import { filter, firstBy, flat, groupBy, mapValues, pipe, uniqueBy, values } from "remeda"
import type { FileContent, FileNode, Model, Provider, File as FileStatus } from "@opencode-ai/sdk/v2"
import { createSimpleContext } from "@opencode-ai/ui/context"
@@ -10,6 +10,7 @@ import { useProviders } from "@/hooks/use-providers"
import { DateTime } from "luxon"
import { Persist, persisted } from "@/utils/persist"
import { showToast } from "@opencode-ai/ui/toast"
import { useLanguage } from "@/context/language"
export type LocalFile = FileNode &
Partial<{
@@ -42,6 +43,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const sdk = useSDK()
const sync = useSync()
const providers = useProviders()
const language = useLanguage()
function isModelValid(model: ModelKey) {
const provider = providers.all().find((x) => x.id === model.providerID)
@@ -336,6 +338,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
node: {}, // Object.fromEntries(sync.data.node.map((x) => [x.path, x])),
})
const scope = createMemo(() => sdk.directory)
createEffect(() => {
scope()
setStore("node", {})
})
// const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path)))
// const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b)))
@@ -392,10 +400,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const relative = (path: string) => path.replace(sync.data.path.directory + "/", "")
const load = async (path: string) => {
const directory = scope()
const client = sdk.client
const relativePath = relative(path)
await sdk.client.file
await client.file
.read({ path: relativePath })
.then((x) => {
if (scope() !== directory) return
if (!store.node[relativePath]) return
setStore(
"node",
@@ -407,9 +418,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
)
})
.catch((e) => {
if (scope() !== directory) return
showToast({
variant: "error",
title: "Failed to load file",
title: language.t("toast.file.loadFailed.title"),
description: e.message,
})
})
@@ -451,9 +463,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
const list = async (path: string) => {
return sdk.client.file
const directory = scope()
const client = sdk.client
return client.file
.list({ path: path + "/" })
.then((x) => {
if (scope() !== directory) return
setStore(
"node",
produce((draft) => {

View File

@@ -1,16 +1,17 @@
import { createStore } from "solid-js/store"
import { createEffect, onCleanup } from "solid-js"
import { useParams } from "@solidjs/router"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSDK } from "./global-sdk"
import { useGlobalSync } from "./global-sync"
import { usePlatform } from "@/context/platform"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
import { Binary } from "@opencode-ai/util/binary"
import { base64Encode } from "@opencode-ai/util/encode"
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
import { EventSessionError } from "@opencode-ai/sdk/v2"
import { makeAudioPlayer } from "@solid-primitives/audio"
import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac"
import errorSound from "@opencode-ai/ui/audio/nope-03.aac"
import { Persist, persisted } from "@/utils/persist"
import { playSound, soundSrc } from "@/utils/sound"
type NotificationBase = {
directory?: string
@@ -44,19 +45,12 @@ function pruneNotifications(list: Notification[]) {
export const { use: useNotification, provider: NotificationProvider } = createSimpleContext({
name: "Notification",
init: () => {
let idlePlayer: ReturnType<typeof makeAudioPlayer> | undefined
let errorPlayer: ReturnType<typeof makeAudioPlayer> | undefined
try {
idlePlayer = makeAudioPlayer(idleSound)
errorPlayer = makeAudioPlayer(errorSound)
} catch (err) {
console.log("Failed to load audio", err)
}
const params = useParams()
const globalSDK = useGlobalSDK()
const globalSync = useGlobalSync()
const platform = usePlatform()
const settings = useSettings()
const language = useLanguage()
const [store, setStore, _, ready] = persisted(
Persist.global("notification", ["notification.v1"]),
@@ -81,10 +75,15 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
const unsub = globalSDK.event.listen((e) => {
const directory = e.name
const event = e.details
const base = {
directory,
time: Date.now(),
viewed: false,
const time = Date.now()
const activeDirectory = params.dir ? base64Decode(params.dir) : undefined
const activeSession = params.id
const viewed = (sessionID?: string) => {
if (!activeDirectory) return false
if (!activeSession) return false
if (!sessionID) return false
if (directory !== activeDirectory) return false
return sessionID === activeSession
}
switch (event.type) {
case "session.idle": {
@@ -93,16 +92,25 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
const session = match.found ? syncStore.session[match.index] : undefined
if (session?.parentID) break
try {
idlePlayer?.play()
} catch {}
playSound(soundSrc(settings.sounds.agent()))
append({
...base,
directory,
time,
viewed: viewed(sessionID),
type: "turn-complete",
session: sessionID,
})
const href = `/${base64Encode(directory)}/session/${sessionID}`
void platform.notify("Response ready", session?.title ?? sessionID, href)
if (settings.notifications.agent()) {
void platform.notify(
language.t("notification.session.responseReady.title"),
session?.title ?? sessionID,
href,
)
}
break
}
case "session.error": {
@@ -111,19 +119,25 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
const match = sessionID ? Binary.search(syncStore.session, sessionID, (s) => s.id) : undefined
const session = sessionID && match?.found ? syncStore.session[match.index] : undefined
if (session?.parentID) break
try {
errorPlayer?.play()
} catch {}
playSound(soundSrc(settings.sounds.errors()))
const error = "error" in event.properties ? event.properties.error : undefined
append({
...base,
directory,
time,
viewed: viewed(sessionID),
type: "error",
session: sessionID ?? "global",
error,
})
const description = session?.title ?? (typeof error === "string" ? error : "An error occurred")
const description =
session?.title ??
(typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
void platform.notify("Session error", description, href)
if (settings.notifications.errors()) {
void platform.notify(language.t("notification.session.error.title"), description, href)
}
break
}
}

View File

@@ -46,6 +46,9 @@ export type Platform = {
/** Set the default server URL to use on app startup (desktop only) */
setDefaultServerUrl?(url: string | null): Promise<void>
/** Parse markdown to HTML using native parser (desktop only, returns unprocessed code blocks) */
parseMarkdown?(markdown: string): Promise<string>
}
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({

View File

@@ -4,6 +4,7 @@ import { batch, createMemo, createRoot, onCleanup } from "solid-js"
import { useParams } from "@solidjs/router"
import type { FileSelection } from "@/context/file"
import { Persist, persisted } from "@/utils/persist"
import { checksum } from "@opencode-ai/util/encode"
interface PartBase {
content: string
@@ -41,6 +42,9 @@ export type FileContextItem = {
type: "file"
path: string
selection?: FileSelection
comment?: string
commentID?: string
preview?: string
}
export type ContextItem = FileContextItem
@@ -118,14 +122,12 @@ function createPromptSession(dir: string, id: string | undefined) {
prompt: Prompt
cursor?: number
context: {
activeTab: boolean
items: (ContextItem & { key: string })[]
}
}>({
prompt: clonePrompt(DEFAULT_PROMPT),
cursor: undefined,
context: {
activeTab: true,
items: [],
},
}),
@@ -135,7 +137,16 @@ function createPromptSession(dir: string, id: string | undefined) {
if (item.type !== "file") return item.type
const start = item.selection?.startLine
const end = item.selection?.endLine
return `${item.type}:${item.path}:${start}:${end}`
const key = `${item.type}:${item.path}:${start}:${end}`
if (item.commentID) {
return `${key}:c=${item.commentID}`
}
const comment = item.comment?.trim()
if (!comment) return key
const digest = checksum(comment) ?? comment
return `${key}:c=${digest.slice(0, 8)}`
}
return {
@@ -144,14 +155,7 @@ function createPromptSession(dir: string, id: string | undefined) {
cursor: createMemo(() => store.cursor),
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
context: {
activeTab: createMemo(() => store.context.activeTab),
items: createMemo(() => store.context.items),
addActive() {
setStore("context", "activeTab", true)
},
removeActive() {
setStore("context", "activeTab", false)
},
add(item: ContextItem) {
const key = keyForItem(item)
if (store.context.items.find((x) => x.key === key)) return
@@ -230,10 +234,7 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
cursor: () => session().cursor(),
dirty: () => session().dirty(),
context: {
activeTab: () => session().context.activeTab(),
items: () => session().context.items(),
addActive: () => session().context.addActive(),
removeActive: () => session().context.removeActive(),
add: (item: ContextItem) => session().context.add(item),
remove: (key: string) => session().context.remove(key),
},

View File

@@ -1,7 +1,7 @@
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2/client"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { createGlobalEmitter } from "@solid-primitives/event-bus"
import { onCleanup } from "solid-js"
import { createEffect, createMemo, onCleanup } from "solid-js"
import { useGlobalSDK } from "./global-sdk"
import { usePlatform } from "./platform"
@@ -10,22 +10,39 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
init: (props: { directory: string }) => {
const platform = usePlatform()
const globalSDK = useGlobalSDK()
const sdk = createOpencodeClient({
baseUrl: globalSDK.url,
fetch: platform.fetch,
directory: props.directory,
throwOnError: true,
})
const directory = createMemo(() => props.directory)
const client = createMemo(() =>
createOpencodeClient({
baseUrl: globalSDK.url,
fetch: platform.fetch,
directory: directory(),
throwOnError: true,
}),
)
const emitter = createGlobalEmitter<{
[key in Event["type"]]: Extract<Event, { type: key }>
}>()
const unsub = globalSDK.event.on(props.directory, (event) => {
emitter.emit(event.type, event)
createEffect(() => {
const unsub = globalSDK.event.on(directory(), (event) => {
emitter.emit(event.type, event)
})
onCleanup(unsub)
})
onCleanup(unsub)
return { directory: props.directory, client: sdk, event: emitter, url: globalSDK.url }
return {
get directory() {
return directory()
},
get client() {
return client()
},
event: emitter,
get url() {
return globalSDK.url
},
}
},
})

View File

@@ -0,0 +1,158 @@
import { createStore, reconcile } from "solid-js/store"
import { createEffect, createMemo } from "solid-js"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { persisted } from "@/utils/persist"
export interface NotificationSettings {
agent: boolean
permissions: boolean
errors: boolean
}
export interface SoundSettings {
agent: string
permissions: string
errors: string
}
export interface Settings {
general: {
autoSave: boolean
}
appearance: {
fontSize: number
font: string
}
keybinds: Record<string, string>
permissions: {
autoApprove: boolean
}
notifications: NotificationSettings
sounds: SoundSettings
}
const defaultSettings: Settings = {
general: {
autoSave: true,
},
appearance: {
fontSize: 14,
font: "ibm-plex-mono",
},
keybinds: {},
permissions: {
autoApprove: false,
},
notifications: {
agent: true,
permissions: true,
errors: false,
},
sounds: {
agent: "staplebops-01",
permissions: "staplebops-02",
errors: "nope-03",
},
}
const monoFallback =
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
const monoFonts: Record<string, string> = {
"ibm-plex-mono": `"IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"cascadia-code": `"Cascadia Code Nerd Font", "Cascadia Code NF", "Cascadia Mono NF", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"fira-code": `"Fira Code Nerd Font", "FiraMono Nerd Font", "FiraMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
hack: `"Hack Nerd Font", "Hack Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
inconsolata: `"Inconsolata Nerd Font", "Inconsolata Nerd Font Mono","IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"intel-one-mono": `"Intel One Mono Nerd Font", "IntoneMono Nerd Font", "IntoneMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"jetbrains-mono": `"JetBrains Mono Nerd Font", "JetBrainsMono Nerd Font Mono", "JetBrainsMonoNL Nerd Font", "JetBrainsMonoNL Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"meslo-lgs": `"Meslo LGS Nerd Font", "MesloLGS Nerd Font", "MesloLGM Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"roboto-mono": `"Roboto Mono Nerd Font", "RobotoMono Nerd Font", "RobotoMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"source-code-pro": `"Source Code Pro Nerd Font", "SauceCodePro Nerd Font", "SauceCodePro Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"ubuntu-mono": `"Ubuntu Mono Nerd Font", "UbuntuMono Nerd Font", "UbuntuMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
}
export function monoFontFamily(font: string | undefined) {
return monoFonts[font ?? defaultSettings.appearance.font] ?? monoFonts[defaultSettings.appearance.font]
}
export const { use: useSettings, provider: SettingsProvider } = createSimpleContext({
name: "Settings",
init: () => {
const [store, setStore, _, ready] = persisted("settings.v3", createStore<Settings>(defaultSettings))
createEffect(() => {
if (typeof document === "undefined") return
document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.font))
})
return {
ready,
get current() {
return store
},
general: {
autoSave: createMemo(() => store.general?.autoSave ?? defaultSettings.general.autoSave),
setAutoSave(value: boolean) {
setStore("general", "autoSave", value)
},
},
appearance: {
fontSize: createMemo(() => store.appearance?.fontSize ?? defaultSettings.appearance.fontSize),
setFontSize(value: number) {
setStore("appearance", "fontSize", value)
},
font: createMemo(() => store.appearance?.font ?? defaultSettings.appearance.font),
setFont(value: string) {
setStore("appearance", "font", value)
},
},
keybinds: {
get: (action: string) => store.keybinds?.[action],
set(action: string, keybind: string) {
setStore("keybinds", action, keybind)
},
reset(action: string) {
setStore("keybinds", action, undefined!)
},
resetAll() {
setStore("keybinds", reconcile({}))
},
},
permissions: {
autoApprove: createMemo(() => store.permissions?.autoApprove ?? defaultSettings.permissions.autoApprove),
setAutoApprove(value: boolean) {
setStore("permissions", "autoApprove", value)
},
},
notifications: {
agent: createMemo(() => store.notifications?.agent ?? defaultSettings.notifications.agent),
setAgent(value: boolean) {
setStore("notifications", "agent", value)
},
permissions: createMemo(() => store.notifications?.permissions ?? defaultSettings.notifications.permissions),
setPermissions(value: boolean) {
setStore("notifications", "permissions", value)
},
errors: createMemo(() => store.notifications?.errors ?? defaultSettings.notifications.errors),
setErrors(value: boolean) {
setStore("notifications", "errors", value)
},
},
sounds: {
agent: createMemo(() => store.sounds?.agent ?? defaultSettings.sounds.agent),
setAgent(value: string) {
setStore("sounds", "agent", value)
},
permissions: createMemo(() => store.sounds?.permissions ?? defaultSettings.sounds.permissions),
setPermissions(value: string) {
setStore("sounds", "permissions", value)
},
errors: createMemo(() => store.sounds?.errors ?? defaultSettings.sounds.errors),
setErrors(value: string) {
setStore("sounds", "errors", value)
},
},
}
},
})

View File

@@ -7,13 +7,20 @@ import { useGlobalSync } from "./global-sync"
import { useSDK } from "./sdk"
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
const keyFor = (directory: string, id: string) => `${directory}\n${id}`
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
name: "Sync",
init: () => {
const globalSync = useGlobalSync()
const sdk = useSDK()
const [store, setStore] = globalSync.child(sdk.directory)
const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
type Child = ReturnType<(typeof globalSync)["child"]>
type Store = Child[0]
type Setter = Child[1]
const current = createMemo(() => globalSync.child(sdk.directory))
const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/")
const chunk = 400
const inflight = new Map<string, Promise<void>>()
const inflightDiff = new Map<string, Promise<void>>()
@@ -25,6 +32,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
})
const getSession = (sessionID: string) => {
const store = current()[0]
const match = Binary.search(store.session, sessionID, (s) => s.id)
if (match.found) return store.session[match.index]
return undefined
@@ -35,22 +43,30 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
return Math.ceil(count / chunk) * chunk
}
const hydrateMessages = (sessionID: string) => {
if (meta.limit[sessionID] !== undefined) return
const hydrateMessages = (directory: string, store: Store, sessionID: string) => {
const key = keyFor(directory, sessionID)
if (meta.limit[key] !== undefined) return
const messages = store.message[sessionID]
if (!messages) return
const limit = limitFor(messages.length)
setMeta("limit", sessionID, limit)
setMeta("complete", sessionID, messages.length < limit)
setMeta("limit", key, limit)
setMeta("complete", key, messages.length < limit)
}
const loadMessages = async (sessionID: string, limit: number) => {
if (meta.loading[sessionID]) return
const loadMessages = async (input: {
directory: string
client: typeof sdk.client
setStore: Setter
sessionID: string
limit: number
}) => {
const key = keyFor(input.directory, input.sessionID)
if (meta.loading[key]) return
setMeta("loading", sessionID, true)
await retry(() => sdk.client.session.messages({ sessionID, limit }))
setMeta("loading", key, true)
await retry(() => input.client.session.messages({ sessionID: input.sessionID, limit: input.limit }))
.then((messages) => {
const items = (messages.data ?? []).filter((x) => !!x?.info?.id)
const next = items
@@ -60,10 +76,10 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
.sort((a, b) => a.id.localeCompare(b.id))
batch(() => {
setStore("message", sessionID, reconcile(next, { key: "id" }))
input.setStore("message", input.sessionID, reconcile(next, { key: "id" }))
for (const message of items) {
setStore(
input.setStore(
"part",
message.info.id,
reconcile(
@@ -76,25 +92,30 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
)
}
setMeta("limit", sessionID, limit)
setMeta("complete", sessionID, next.length < limit)
setMeta("limit", key, input.limit)
setMeta("complete", key, next.length < input.limit)
})
})
.finally(() => {
setMeta("loading", sessionID, false)
setMeta("loading", key, false)
})
}
return {
data: store,
set: setStore,
get data() {
return current()[0]
},
get set(): Setter {
return current()[1]
},
get status() {
return store.status
return current()[0].status
},
get ready() {
return store.status !== "loading"
return current()[0].status !== "loading"
},
get project() {
const store = current()[0]
const match = Binary.search(globalSync.data.project, store.project, (p) => p.id)
if (match.found) return globalSync.data.project[match.index]
return undefined
@@ -116,7 +137,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
agent: input.agent,
model: input.model,
}
setStore(
current()[1](
produce((draft) => {
const messages = draft.message[input.sessionID]
if (!messages) {
@@ -133,20 +154,28 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
)
},
async sync(sessionID: string) {
const hasSession = getSession(sessionID) !== undefined
hydrateMessages(sessionID)
const directory = sdk.directory
const client = sdk.client
const [store, setStore] = globalSync.child(directory)
const hasSession = (() => {
const match = Binary.search(store.session, sessionID, (s) => s.id)
return match.found
})()
hydrateMessages(directory, store, sessionID)
const hasMessages = store.message[sessionID] !== undefined
if (hasSession && hasMessages) return
const pending = inflight.get(sessionID)
const key = keyFor(directory, sessionID)
const pending = inflight.get(key)
if (pending) return pending
const limit = meta.limit[sessionID] ?? chunk
const limit = meta.limit[key] ?? chunk
const sessionReq = hasSession
? Promise.resolve()
: retry(() => sdk.client.session.get({ sessionID })).then((session) => {
: retry(() => client.session.get({ sessionID })).then((session) => {
const data = session.data
if (!data) return
setStore(
@@ -162,72 +191,104 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
)
})
const messagesReq = hasMessages ? Promise.resolve() : loadMessages(sessionID, limit)
const messagesReq = hasMessages
? Promise.resolve()
: loadMessages({
directory,
client,
setStore,
sessionID,
limit,
})
const promise = Promise.all([sessionReq, messagesReq])
.then(() => {})
.finally(() => {
inflight.delete(sessionID)
inflight.delete(key)
})
inflight.set(sessionID, promise)
inflight.set(key, promise)
return promise
},
async diff(sessionID: string) {
const directory = sdk.directory
const client = sdk.client
const [store, setStore] = globalSync.child(directory)
if (store.session_diff[sessionID] !== undefined) return
const pending = inflightDiff.get(sessionID)
const key = keyFor(directory, sessionID)
const pending = inflightDiff.get(key)
if (pending) return pending
const promise = retry(() => sdk.client.session.diff({ sessionID }))
const promise = retry(() => client.session.diff({ sessionID }))
.then((diff) => {
setStore("session_diff", sessionID, reconcile(diff.data ?? [], { key: "file" }))
})
.finally(() => {
inflightDiff.delete(sessionID)
inflightDiff.delete(key)
})
inflightDiff.set(sessionID, promise)
inflightDiff.set(key, promise)
return promise
},
async todo(sessionID: string) {
const directory = sdk.directory
const client = sdk.client
const [store, setStore] = globalSync.child(directory)
if (store.todo[sessionID] !== undefined) return
const pending = inflightTodo.get(sessionID)
const key = keyFor(directory, sessionID)
const pending = inflightTodo.get(key)
if (pending) return pending
const promise = retry(() => sdk.client.session.todo({ sessionID }))
const promise = retry(() => client.session.todo({ sessionID }))
.then((todo) => {
setStore("todo", sessionID, reconcile(todo.data ?? [], { key: "id" }))
})
.finally(() => {
inflightTodo.delete(sessionID)
inflightTodo.delete(key)
})
inflightTodo.set(sessionID, promise)
inflightTodo.set(key, promise)
return promise
},
history: {
more(sessionID: string) {
const store = current()[0]
const key = keyFor(sdk.directory, sessionID)
if (store.message[sessionID] === undefined) return false
if (meta.limit[sessionID] === undefined) return false
if (meta.complete[sessionID]) return false
if (meta.limit[key] === undefined) return false
if (meta.complete[key]) return false
return true
},
loading(sessionID: string) {
return meta.loading[sessionID] ?? false
const key = keyFor(sdk.directory, sessionID)
return meta.loading[key] ?? false
},
async loadMore(sessionID: string, count = chunk) {
if (meta.loading[sessionID]) return
if (meta.complete[sessionID]) return
const directory = sdk.directory
const client = sdk.client
const [, setStore] = globalSync.child(directory)
const key = keyFor(directory, sessionID)
if (meta.loading[key]) return
if (meta.complete[key]) return
const current = meta.limit[sessionID] ?? chunk
await loadMessages(sessionID, current + count)
const currentLimit = meta.limit[key] ?? chunk
await loadMessages({
directory,
client,
setStore,
sessionID,
limit: currentLimit + count,
})
},
},
fetch: async (count = 10) => {
const directory = sdk.directory
const client = sdk.client
const [store, setStore] = globalSync.child(directory)
setStore("limit", (x) => x + count)
await sdk.client.session.list().then((x) => {
await client.session.list().then((x) => {
const sessions = (x.data ?? [])
.filter((s) => !!s?.id)
.slice()
@@ -236,9 +297,12 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
setStore("session", reconcile(sessions, { key: "id" }))
})
},
more: createMemo(() => store.session.length >= store.limit),
more: createMemo(() => current()[0].session.length >= current()[0].limit),
archive: async (sessionID: string) => {
await sdk.client.session.update({ sessionID, time: { archived: Date.now() } })
const directory = sdk.directory
const client = sdk.client
const [, setStore] = globalSync.child(directory)
await client.session.update({ sessionID, time: { archived: Date.now() } })
setStore(
produce((draft) => {
const match = Binary.search(draft.session, sessionID, (s) => s.id)
@@ -249,7 +313,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
},
absolute,
get directory() {
return store.path.directory
return current()[0].path.directory
},
}
},

View File

@@ -1,6 +1,6 @@
import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { batch, createMemo, createRoot, onCleanup } from "solid-js"
import { batch, createEffect, createMemo, createRoot, onCleanup } from "solid-js"
import { useParams } from "@solidjs/router"
import { useSDK } from "./sdk"
import { Persist, persisted } from "@/utils/persist"
@@ -13,6 +13,7 @@ export type LocalPTY = {
cols?: number
buffer?: string
scrollY?: number
error?: boolean
}
const WORKSPACE_KEY = "__workspace__"
@@ -28,6 +29,14 @@ type TerminalCacheEntry = {
function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, session?: string) {
const legacy = session ? [`${dir}/terminal/${session}.v1`, `${dir}/terminal.v1`] : [`${dir}/terminal.v1`]
const numberFromTitle = (title: string) => {
const match = title.match(/^Terminal (\d+)$/)
if (!match) return
const value = Number(match[1])
if (!Number.isFinite(value) || value <= 0) return
return value
}
const [store, setStore, _, ready] = persisted(
Persist.workspace(dir, "terminal", legacy),
createStore<{
@@ -38,24 +47,52 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, sess
}),
)
const unsub = sdk.event.on("pty.exited", (event) => {
const id = event.properties.id
if (!store.all.some((x) => x.id === id)) return
batch(() => {
setStore(
"all",
store.all.filter((x) => x.id !== id),
)
if (store.active === id) {
const remaining = store.all.filter((x) => x.id !== id)
setStore("active", remaining[0]?.id)
}
})
})
onCleanup(unsub)
const meta = { migrated: false }
createEffect(() => {
if (!ready()) return
if (meta.migrated) return
meta.migrated = true
setStore("all", (all) => {
const next = all.map((pty) => {
const direct = Number.isFinite(pty.titleNumber) && pty.titleNumber > 0 ? pty.titleNumber : undefined
if (direct !== undefined) return pty
const parsed = numberFromTitle(pty.title)
if (parsed === undefined) return pty
return { ...pty, titleNumber: parsed }
})
if (next.every((pty, index) => pty === all[index])) return all
return next
})
})
return {
ready,
all: createMemo(() => Object.values(store.all)),
active: createMemo(() => store.active),
new() {
const parse = (title: string) => {
const match = title.match(/^Terminal (\d+)$/)
if (!match) return
const value = Number(match[1])
if (!Number.isFinite(value) || value <= 0) return
return value
}
const existingTitleNumbers = new Set(
store.all.flatMap((pty) => {
const direct = Number.isFinite(pty.titleNumber) && pty.titleNumber > 0 ? pty.titleNumber : undefined
if (direct !== undefined) return [direct]
const parsed = parse(pty.title)
const parsed = numberFromTitle(pty.title)
if (parsed === undefined) return []
return [parsed]
}),
@@ -71,14 +108,15 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, sess
.then((pty) => {
const id = pty.data?.id
if (!id) return
setStore("all", [
...store.all,
{
id,
title: pty.data?.title ?? "Terminal",
titleNumber: nextNumber,
},
])
const newTerminal = {
id,
title: pty.data?.title ?? "Terminal",
titleNumber: nextNumber,
}
setStore("all", (all) => {
const newAll = [...all, newTerminal]
return newAll
})
setStore("active", id)
})
.catch((e) => {
@@ -86,7 +124,10 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, sess
})
},
update(pty: Partial<LocalPTY> & { id: string }) {
setStore("all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x)))
const index = store.all.findIndex((x) => x.id === pty.id)
if (index !== -1) {
setStore("all", index, (existing) => ({ ...existing, ...pty }))
}
sdk.client.pty
.update({
ptyID: pty.id,
@@ -121,18 +162,29 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, sess
open(id: string) {
setStore("active", id)
},
next() {
const index = store.all.findIndex((x) => x.id === store.active)
if (index === -1) return
const nextIndex = (index + 1) % store.all.length
setStore("active", store.all[nextIndex]?.id)
},
previous() {
const index = store.all.findIndex((x) => x.id === store.active)
if (index === -1) return
const prevIndex = index === 0 ? store.all.length - 1 : index - 1
setStore("active", store.all[prevIndex]?.id)
},
async close(id: string) {
batch(() => {
setStore(
"all",
store.all.filter((x) => x.id !== id),
)
const filtered = store.all.filter((x) => x.id !== id)
if (store.active === id) {
const index = store.all.findIndex((f) => f.id === id)
const previous = store.all[Math.max(0, index - 1)]
setStore("active", previous?.id)
const next = index > 0 ? index - 1 : 0
setStore("active", filtered[next]?.id)
}
setStore("all", filtered)
})
await sdk.client.pty.remove({ ptyID: id }).catch((e) => {
console.error("Failed to close terminal", e)
})
@@ -208,6 +260,8 @@ export const { use: useTerminal, provider: TerminalProvider } = createSimpleCont
open: (id: string) => workspace().open(id),
close: (id: string) => workspace().close(id),
move: (id: string, to: number) => workspace().move(id, to),
next: () => workspace().next(),
previous: () => workspace().previous(),
}
},
})

View File

@@ -2,13 +2,25 @@
import { render } from "solid-js/web"
import { AppBaseProviders, AppInterface } from "@/app"
import { Platform, PlatformProvider } from "@/context/platform"
import { dict as en } from "@/i18n/en"
import { dict as zh } from "@/i18n/zh"
import pkg from "../package.json"
const root = document.getElementById("root")
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
throw new Error(
"Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
)
const locale = (() => {
if (typeof navigator !== "object") return "en" as const
const languages = navigator.languages?.length ? navigator.languages : [navigator.language]
for (const language of languages) {
if (!language) continue
if (language.toLowerCase().startsWith("zh")) return "zh" as const
}
return "en" as const
})()
const key = "error.dev.rootNotFound" as const
const message = locale === "zh" ? (zh[key] ?? en[key]) : en[key]
throw new Error(message)
}
const platform: Platform = {
@@ -37,7 +49,7 @@ const platform: Platform = {
.then(() => {
const notification = new Notification(title, {
body: description ?? "",
icon: "https://opencode.ai/favicon-96x96-v2.png",
icon: "https://opencode.ai/favicon-96x96-v3.png",
})
notification.onclick = () => {
window.focus()

656
packages/app/src/i18n/ar.ts Normal file
View File

@@ -0,0 +1,656 @@
export const dict = {
"command.category.suggested": "مقترح",
"command.category.view": "عرض",
"command.category.project": "مشروع",
"command.category.provider": "موفر",
"command.category.server": "خادم",
"command.category.session": "جلسة",
"command.category.theme": "سمة",
"command.category.language": "لغة",
"command.category.file": "ملف",
"command.category.terminal": "محطة طرفية",
"command.category.model": "نموذج",
"command.category.mcp": "MCP",
"command.category.agent": "وكيل",
"command.category.permissions": "أذونات",
"command.category.workspace": "مساحة عمل",
"command.category.settings": "إعدادات",
"theme.scheme.system": "نظام",
"theme.scheme.light": "فاتح",
"theme.scheme.dark": "داكن",
"command.sidebar.toggle": "تبديل الشريط الجانبي",
"command.project.open": "فتح مشروع",
"command.provider.connect": "اتصال بموفر",
"command.server.switch": "تبديل الخادم",
"command.settings.open": "فتح الإعدادات",
"command.session.previous": "الجلسة السابقة",
"command.session.next": "الجلسة التالية",
"command.session.archive": "أرشفة الجلسة",
"command.palette": "لوحة الأوامر",
"command.theme.cycle": "تغيير السمة",
"command.theme.set": "استخدام السمة: {{theme}}",
"command.theme.scheme.cycle": "تغيير مخطط الألوان",
"command.theme.scheme.set": "استخدام مخطط الألوان: {{scheme}}",
"command.language.cycle": "تغيير اللغة",
"command.language.set": "استخدام اللغة: {{language}}",
"command.session.new": "جلسة جديدة",
"command.file.open": "فتح ملف",
"command.file.open.description": "البحث في الملفات والأوامر",
"command.terminal.toggle": "تبديل المحطة الطرفية",
"command.review.toggle": "تبديل المراجعة",
"command.terminal.new": "محطة طرفية جديدة",
"command.terminal.new.description": "إنشاء علامة تبويب جديدة للمحطة الطرفية",
"command.steps.toggle": "تبديل الخطوات",
"command.steps.toggle.description": "إظهار أو إخفاء خطوات الرسالة الحالية",
"command.message.previous": "الرسالة السابقة",
"command.message.previous.description": "انتقل إلى رسالة المستخدم السابقة",
"command.message.next": "الرسالة التالية",
"command.message.next.description": "انتقل إلى رسالة المستخدم التالية",
"command.model.choose": "اختيار نموذج",
"command.model.choose.description": "حدد نموذجًا مختلفًا",
"command.mcp.toggle": "تبديل MCPs",
"command.mcp.toggle.description": "تبديل MCPs",
"command.agent.cycle": "تغيير الوكيل",
"command.agent.cycle.description": "التبديل إلى الوكيل التالي",
"command.agent.cycle.reverse": "تغيير الوكيل للخلف",
"command.agent.cycle.reverse.description": "التبديل إلى الوكيل السابق",
"command.model.variant.cycle": "تغيير جهد التفكير",
"command.model.variant.cycle.description": "التبديل إلى مستوى الجهد التالي",
"command.permissions.autoaccept.enable": "قبول التعديلات تلقائيًا",
"command.permissions.autoaccept.disable": "إيقاف قبول التعديلات تلقائيًا",
"command.session.undo": "تراجع",
"command.session.undo.description": "تراجع عن الرسالة الأخيرة",
"command.session.redo": "إعادة",
"command.session.redo.description": "إعادة الرسالة التي تم التراجع عنها",
"command.session.compact": "ضغط الجلسة",
"command.session.compact.description": "تلخيص الجلسة لتقليل حجم السياق",
"command.session.fork": "تشعب من الرسالة",
"command.session.fork.description": "إنشاء جلسة جديدة من رسالة سابقة",
"command.session.share": "مشاركة الجلسة",
"command.session.share.description": "مشاركة هذه الجلسة ونسخ الرابط إلى الحافظة",
"command.session.unshare": "إلغاء مشاركة الجلسة",
"command.session.unshare.description": "إيقاف مشاركة هذه الجلسة",
"palette.search.placeholder": "البحث في الملفات والأوامر",
"palette.empty": "لا توجد نتائج",
"palette.group.commands": "الأوامر",
"palette.group.files": "الملفات",
"dialog.provider.search.placeholder": "البحث عن موفرين",
"dialog.provider.empty": "لم يتم العثور على موفرين",
"dialog.provider.group.popular": "شائع",
"dialog.provider.group.other": "آخر",
"dialog.provider.tag.recommended": "موصى به",
"dialog.provider.anthropic.note": "اتصل باستخدام Claude Pro/Max أو مفتاح API",
"dialog.provider.openai.note": "اتصل باستخدام ChatGPT Pro/Plus أو مفتاح API",
"dialog.provider.copilot.note": "اتصل باستخدام Copilot أو مفتاح API",
"dialog.model.select.title": "تحديد نموذج",
"dialog.model.search.placeholder": "البحث عن نماذج",
"dialog.model.empty": "لا توجد نتائج للنماذج",
"dialog.model.manage": "إدارة النماذج",
"dialog.model.manage.description": "تخصيص النماذج التي تظهر في محدد النماذج.",
"dialog.model.unpaid.freeModels.title": "نماذج مجانية مقدمة من OpenCode",
"dialog.model.unpaid.addMore.title": "إضافة المزيد من النماذج من موفرين مشهورين",
"dialog.provider.viewAll": "عرض جميع الموفرين",
"provider.connect.title": "اتصال {{provider}}",
"provider.connect.title.anthropicProMax": "تسجيل الدخول باستخدام Claude Pro/Max",
"provider.connect.selectMethod": "حدد طريقة تسجيل الدخول لـ {{provider}}.",
"provider.connect.method.apiKey": "مفتاح API",
"provider.connect.status.inProgress": "جارٍ التفويض...",
"provider.connect.status.waiting": "في انتظار التفويض...",
"provider.connect.status.failed": "فشل التفويض: {{error}}",
"provider.connect.apiKey.description":
"أدخل مفتاح واجهة برمجة تطبيقات {{provider}} الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
"provider.connect.apiKey.label": "مفتاح واجهة برمجة تطبيقات {{provider}}",
"provider.connect.apiKey.placeholder": "مفتاح API",
"provider.connect.apiKey.required": "مفتاح API مطلوب",
"provider.connect.opencodeZen.line1":
"يمنحك OpenCode Zen الوصول إلى مجموعة مختارة من النماذج الموثوقة والمحسنة لوكلاء البرمجة.",
"provider.connect.opencodeZen.line2":
"باستخدام مفتاح API واحد، ستحصل على إمكانية الوصول إلى نماذج مثل Claude و GPT و Gemini و GLM والمزيد.",
"provider.connect.opencodeZen.visit.prefix": "قم بزيارة ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " للحصول على مفتاح API الخاص بك.",
"provider.connect.oauth.code.visit.prefix": "قم بزيارة ",
"provider.connect.oauth.code.visit.link": "هذا الرابط",
"provider.connect.oauth.code.visit.suffix":
" للحصول على رمز التفويض الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
"provider.connect.oauth.code.label": "رمز تفويض {{method}}",
"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":
" وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "رمز التأكيد",
"provider.connect.toast.connected.title": "تم توصيل {{provider}}",
"provider.connect.toast.connected.description": "نماذج {{provider}} متاحة الآن للاستخدام.",
"model.tag.free": "مجاني",
"model.tag.latest": "الأحدث",
"model.provider.anthropic": "Anthropic",
"model.provider.openai": "OpenAI",
"model.provider.google": "Google",
"model.provider.xai": "xAI",
"model.provider.meta": "Meta",
"model.input.text": "نص",
"model.input.image": "صورة",
"model.input.audio": "صوت",
"model.input.video": "فيديو",
"model.input.pdf": "pdf",
"model.tooltip.allows": "يسمح: {{inputs}}",
"model.tooltip.reasoning.allowed": "يسمح بالاستنتاج",
"model.tooltip.reasoning.none": "بدون استنتاج",
"model.tooltip.context": "حد السياق {{limit}}",
"common.search.placeholder": "بحث",
"common.goBack": "رجوع",
"common.loading": "جارٍ التحميل",
"common.loading.ellipsis": "...",
"common.cancel": "إلغاء",
"common.submit": "إرسال",
"common.save": "حفظ",
"common.saving": "جارٍ الحفظ...",
"common.default": "افتراضي",
"common.attachment": "مرفق",
"prompt.placeholder.shell": "أدخل أمر shell...",
"prompt.placeholder.normal": 'اسأل أي شيء... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "esc للخروج",
"prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية",
"prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟",
"prompt.example.3": "إصلاح الاختبارات المعطلة",
"prompt.example.4": "اشرح كيف تعمل المصادقة",
"prompt.example.5": "البحث عن وإصلاح الثغرات الأمنية",
"prompt.example.6": "إضافة اختبارات وحدة لخدمة المستخدم",
"prompt.example.7": "إعادة هيكلة هذه الدالة لتكون أكثر قابلية للقراءة",
"prompt.example.8": "ماذا يعني هذا الخطأ؟",
"prompt.example.9": "ساعدني في تصحيح هذه المشكلة",
"prompt.example.10": "توليد وثائق API",
"prompt.example.11": "تحسين استعلامات قاعدة البيانات",
"prompt.example.12": "إضافة التحقق من صحة الإدخال",
"prompt.example.13": "إنشاء مكون جديد لـ...",
"prompt.example.14": "كيف أقوم بنشر هذا المشروع؟",
"prompt.example.15": "مراجعة الكود الخاص بي لأفضل الممارسات",
"prompt.example.16": "إضافة معالجة الأخطاء لهذه الدالة",
"prompt.example.17": "اشرح نمط regex هذا",
"prompt.example.18": "تحويل هذا إلى TypeScript",
"prompt.example.19": "إضافة تسجيل الدخول (logging) في جميع أنحاء قاعدة التعليمات البرمجية",
"prompt.example.20": "ما هي التبعيات القديمة؟",
"prompt.example.21": "ساعدني في كتابة برنامج نصي للهجرة",
"prompt.example.22": "تنفيذ التخزين المؤقت لهذه النقطة النهائية",
"prompt.example.23": "إضافة ترقيم الصفحات إلى هذه القائمة",
"prompt.example.24": "إنشاء أمر CLI لـ...",
"prompt.example.25": "كيف تعمل متغيرات البيئة هنا؟",
"prompt.popover.emptyResults": "لا توجد نتائج مطابقة",
"prompt.popover.emptyCommands": "لا توجد أوامر مطابقة",
"prompt.dropzone.label": "أفلت الصور أو ملفات PDF هنا",
"prompt.slash.badge.custom": "مخصص",
"prompt.context.active": "نشط",
"prompt.context.includeActiveFile": "تضمين الملف النشط",
"prompt.context.removeActiveFile": "إزالة الملف النشط من السياق",
"prompt.context.removeFile": "إزالة الملف من السياق",
"prompt.action.attachFile": "إرفاق ملف",
"prompt.attachment.remove": "إزالة المرفق",
"prompt.action.send": "إرسال",
"prompt.action.stop": "توقف",
"prompt.toast.pasteUnsupported.title": "لصق غير مدعوم",
"prompt.toast.pasteUnsupported.description": "يمكن لصق الصور أو ملفات PDF فقط هنا.",
"prompt.toast.modelAgentRequired.title": "حدد وكيلاً ونموذجاً",
"prompt.toast.modelAgentRequired.description": "اختر وكيلاً ونموذجاً قبل إرسال الموجه.",
"prompt.toast.worktreeCreateFailed.title": "فشل إنشاء شجرة العمل",
"prompt.toast.sessionCreateFailed.title": "فشل إنشاء الجلسة",
"prompt.toast.shellSendFailed.title": "فشل إرسال أمر shell",
"prompt.toast.commandSendFailed.title": "فشل إرسال الأمر",
"prompt.toast.promptSendFailed.title": "فشل إرسال الموجه",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} من {{total}} مفعل",
"dialog.mcp.empty": "لم يتم تكوين MCPs",
"mcp.status.connected": "متصل",
"mcp.status.failed": "فشل",
"mcp.status.needs_auth": "يحتاج إلى مصادقة",
"mcp.status.disabled": "معطل",
"dialog.fork.empty": "لا توجد رسائل للتفرع منها",
"dialog.directory.search.placeholder": "البحث في المجلدات",
"dialog.directory.empty": "لم يتم العثور على مجلدات",
"dialog.server.title": "الخوادم",
"dialog.server.description": "تبديل خادم OpenCode الذي يتصل به هذا التطبيق.",
"dialog.server.search.placeholder": "البحث في الخوادم",
"dialog.server.empty": "لا توجد خوادم بعد",
"dialog.server.add.title": "إضافة خادم",
"dialog.server.add.url": "عنوان URL للخادم",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "تعذر الاتصال بالخادم",
"dialog.server.add.checking": "جارٍ التحقق...",
"dialog.server.add.button": "إضافة",
"dialog.server.default.title": "الخادم الافتراضي",
"dialog.server.default.description":
"الاتصال بهذا الخادم عند بدء تشغيل التطبيق بدلاً من بدء خادم محلي. يتطلب إعادة التشغيل.",
"dialog.server.default.none": "لم يتم تحديد خادم",
"dialog.server.default.set": "تعيين الخادم الحالي كافتراضي",
"dialog.server.default.clear": "مسح",
"dialog.server.action.remove": "إزالة الخادم",
"dialog.project.edit.title": "تحرير المشروع",
"dialog.project.edit.name": "الاسم",
"dialog.project.edit.icon": "أيقونة",
"dialog.project.edit.icon.alt": "أيقونة المشروع",
"dialog.project.edit.icon.hint": "انقر أو اسحب صورة",
"dialog.project.edit.icon.recommended": "موصى به: 128x128px",
"dialog.project.edit.color": "لون",
"dialog.project.edit.color.select": "اختر لون {{color}}",
"context.breakdown.title": "تفصيل السياق",
"context.breakdown.note": 'تفصيل تقريبي لرموز الإدخال. يشمل "أخرى" تعريفات الأدوات والنفقات العامة.',
"context.breakdown.system": "النظام",
"context.breakdown.user": "المستخدم",
"context.breakdown.assistant": "المساعد",
"context.breakdown.tool": "استدعاءات الأداة",
"context.breakdown.other": "أخرى",
"context.systemPrompt.title": "موجه النظام",
"context.rawMessages.title": "الرسائل الخام",
"context.stats.session": "جلسة",
"context.stats.messages": "رسائل",
"context.stats.provider": "موفر",
"context.stats.model": "نموذج",
"context.stats.limit": "حد السياق",
"context.stats.totalTokens": "إجمالي الرموز",
"context.stats.usage": "استخدام",
"context.stats.inputTokens": "رموز الإدخال",
"context.stats.outputTokens": "رموز الإخراج",
"context.stats.reasoningTokens": "رموز الاستنتاج",
"context.stats.cacheTokens": "رموز التخزين المؤقت (قراءة/كتابة)",
"context.stats.userMessages": "رسائل المستخدم",
"context.stats.assistantMessages": "رسائل المساعد",
"context.stats.totalCost": "التكلفة الإجمالية",
"context.stats.sessionCreated": "تم إنشاء الجلسة",
"context.stats.lastActivity": "آخر نشاط",
"context.usage.tokens": "رموز",
"context.usage.usage": "استخدام",
"context.usage.cost": "تكلفة",
"context.usage.clickToView": "انقر لعرض السياق",
"context.usage.view": "عرض استخدام السياق",
"language.en": "الإنجليزية",
"language.zh": "الصينية (المبسطة)",
"language.zht": "الصينية (التقليدية)",
"language.ko": "الكورية",
"language.de": "الألمانية",
"language.es": "الإسبانية",
"language.fr": "الفرنسية",
"language.ja": "اليابانية",
"language.da": "الدانماركية",
"language.ru": "الروسية",
"language.pl": "البولندية",
"language.ar": "العربية",
"language.no": "النرويجية",
"language.br": "البرتغالية (البرازيل)",
"toast.language.title": "لغة",
"toast.language.description": "تم التبديل إلى {{language}}",
"toast.theme.title": "تم تبديل السمة",
"toast.scheme.title": "مخطط الألوان",
"toast.permissions.autoaccept.on.title": "قبول التعديلات تلقائيًا",
"toast.permissions.autoaccept.on.description": "سيتم الموافقة تلقائيًا على أذونات التحرير والكتابة",
"toast.permissions.autoaccept.off.title": "توقف قبول التعديلات تلقائيًا",
"toast.permissions.autoaccept.off.description": "ستتطلب أذونات التحرير والكتابة موافقة",
"toast.model.none.title": "لم يتم تحديد نموذج",
"toast.model.none.description": "قم بتوصيل موفر لتلخيص هذه الجلسة",
"toast.file.loadFailed.title": "فشل تحميل الملف",
"toast.session.share.copyFailed.title": "فشل نسخ عنوان URL إلى الحافظة",
"toast.session.share.success.title": "تمت مشاركة الجلسة",
"toast.session.share.success.description": "تم نسخ عنوان URL للمشاركة إلى الحافظة!",
"toast.session.share.failed.title": "فشل مشاركة الجلسة",
"toast.session.share.failed.description": "حدث خطأ أثناء مشاركة الجلسة",
"toast.session.unshare.success.title": "تم إلغاء مشاركة الجلسة",
"toast.session.unshare.success.description": "تم إلغاء مشاركة الجلسة بنجاح!",
"toast.session.unshare.failed.title": "فشل إلغاء مشاركة الجلسة",
"toast.session.unshare.failed.description": "حدث خطأ أثناء إلغاء مشاركة الجلسة",
"toast.session.listFailed.title": "فشل تحميل الجلسات لـ {{project}}",
"toast.update.title": "تحديث متاح",
"toast.update.description": "نسخة جديدة من OpenCode ({{version}}) متاحة الآن للتثبيت.",
"toast.update.action.installRestart": "تثبيت وإعادة تشغيل",
"toast.update.action.notYet": "ليس الآن",
"error.page.title": "حدث خطأ ما",
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
"error.page.details.label": "تفاصيل الخطأ",
"error.page.action.restart": "إعادة تشغيل",
"error.page.action.checking": "جارٍ التحقق...",
"error.page.action.checkUpdates": "التحقق من وجود تحديثات",
"error.page.action.updateTo": "تحديث إلى {{version}}",
"error.page.report.prefix": "يرجى الإبلاغ عن هذا الخطأ لفريق OpenCode",
"error.page.report.discord": "على Discord",
"error.page.version": "الإصدار: {{version}}",
"error.dev.rootNotFound":
"لم يتم العثور على العنصر الجذري. هل نسيت إضافته إلى index.html؟ أو ربما تمت كتابة سمة id بشكل خاطئ؟",
"error.globalSync.connectFailed": "تعذر الاتصال بالخادم. هل هناك خادم يعمل في `{{url}}`؟",
"error.chain.unknown": "خطأ غير معروف",
"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.checkConfig": "تحقق من أسماء الموفر/النموذج في التكوين (opencode.json)",
"error.chain.mcpFailed": 'فشل خادم MCP "{{name}}". لاحظ أن OpenCode لا يدعم مصادقة MCP بعد.',
"error.chain.providerAuthFailed": "فشلت مصادقة الموفر ({{provider}}): {{message}}",
"error.chain.providerInitFailed": 'فشل تهيئة الموفر "{{provider}}". تحقق من بيانات الاعتماد والتكوين.',
"error.chain.configJsonInvalid": "ملف التكوين في {{path}} ليس JSON(C) صالحًا",
"error.chain.configJsonInvalidWithMessage": "ملف التكوين في {{path}} ليس JSON(C) صالحًا: {{message}}",
"error.chain.configDirectoryTypo":
'الدليل "{{dir}}" في {{path}} غير صالح. أعد تسمية الدليل إلى "{{suggestion}}" أو قم بإزالته. هذا خطأ مطبعي شائع.',
"error.chain.configFrontmatterError": "فشل تحليل frontmatter في {{path}}:\n{{message}}",
"error.chain.configInvalid": "ملف التكوين في {{path}} غير صالح",
"error.chain.configInvalidWithMessage": "ملف التكوين في {{path}} غير صالح: {{message}}",
"notification.permission.title": "مطلوب إذن",
"notification.permission.description": "{{sessionTitle}} في {{projectName}} يحتاج إلى إذن",
"notification.question.title": "سؤال",
"notification.question.description": "{{sessionTitle}} في {{projectName}} لديه سؤال",
"notification.action.goToSession": "انتقل إلى الجلسة",
"notification.session.responseReady.title": "الاستجابة جاهزة",
"notification.session.error.title": "خطأ في الجلسة",
"notification.session.error.fallbackDescription": "حدث خطأ",
"home.recentProjects": "المشاريع الحديثة",
"home.empty.title": "لا توجد مشاريع حديثة",
"home.empty.description": "ابدأ بفتح مشروع محلي",
"session.tab.session": "جلسة",
"session.tab.review": "مراجعة",
"session.tab.context": "سياق",
"session.panel.reviewAndFiles": "المراجعة والملفات",
"session.review.filesChanged": "تم تغيير {{count}} ملفات",
"session.review.loadingChanges": "جارٍ تحميل التغييرات...",
"session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد",
"session.messages.renderEarlier": "عرض الرسائل السابقة",
"session.messages.loadingEarlier": "جارٍ تحميل الرسائل السابقة...",
"session.messages.loadEarlier": "تحميل الرسائل السابقة",
"session.messages.loading": "جارٍ تحميل الرسائل...",
"session.messages.jumpToLatest": "الانتقال إلى الأحدث",
"session.context.addToContext": "إضافة {{selection}} إلى السياق",
"session.new.worktree.main": "الفرع الرئيسي",
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
"session.new.worktree.create": "إنشاء شجرة عمل جديدة",
"session.new.lastModified": "آخر تعديل",
"session.header.search.placeholder": "بحث {{project}}",
"session.header.searchFiles": "بحث عن الملفات",
"session.share.popover.title": "نشر على الويب",
"session.share.popover.description.shared": "هذه الجلسة عامة على الويب. يمكن لأي شخص لديه الرابط الوصول إليها.",
"session.share.popover.description.unshared": "شارك الجلسة علنًا على الويب. ستكون متاحة لأي شخص لديه الرابط.",
"session.share.action.share": "مشاركة",
"session.share.action.publish": "نشر",
"session.share.action.publishing": "جارٍ النشر...",
"session.share.action.unpublish": "إلغاء النشر",
"session.share.action.unpublishing": "جارٍ إلغاء النشر...",
"session.share.action.view": "عرض",
"session.share.copy.copied": "تم النسخ",
"session.share.copy.copyLink": "نسخ الرابط",
"lsp.tooltip.none": "لا توجد خوادم LSP",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "جارٍ تحميل الموجه...",
"terminal.loading": "جارٍ تحميل المحطة الطرفية...",
"terminal.title": "محطة طرفية",
"terminal.title.numbered": "محطة طرفية {{number}}",
"terminal.close": "إغلاق المحطة الطرفية",
"terminal.connectionLost.title": "فقد الاتصال",
"terminal.connectionLost.description": "انقطع اتصال المحطة الطرفية. يمكن أن يحدث هذا عند إعادة تشغيل الخادم.",
"common.closeTab": "إغلاق علامة التبويب",
"common.dismiss": "رفض",
"common.requestFailed": "فشل الطلب",
"common.moreOptions": "مزيد من الخيارات",
"common.learnMore": "اعرف المزيد",
"common.rename": "إعادة تسمية",
"common.reset": "إعادة تعيين",
"common.archive": "أرشفة",
"common.delete": "حذف",
"common.close": "إغلاق",
"common.edit": "تحرير",
"common.loadMore": "تحميل المزيد",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "تبديل القائمة",
"sidebar.nav.projectsAndSessions": "المشاريع والجلسات",
"sidebar.settings": "الإعدادات",
"sidebar.help": "مساعدة",
"sidebar.workspaces.enable": "تمكين مساحات العمل",
"sidebar.workspaces.disable": "تعطيل مساحات العمل",
"sidebar.gettingStarted.title": "البدء",
"sidebar.gettingStarted.line1": "يتضمن OpenCode نماذج مجانية حتى تتمكن من البدء فورًا.",
"sidebar.gettingStarted.line2": "قم بتوصيل أي موفر لاستخدام النماذج، بما في ذلك Claude و GPT و Gemini وما إلى ذلك.",
"sidebar.project.recentSessions": "الجلسات الحديثة",
"sidebar.project.viewAllSessions": "عرض جميع الجلسات",
"settings.section.desktop": "سطح المكتب",
"settings.tab.general": "عام",
"settings.tab.shortcuts": "اختصارات",
"settings.general.section.appearance": "المظهر",
"settings.general.section.notifications": "إشعارات النظام",
"settings.general.section.sounds": "المؤثرات الصوتية",
"settings.general.row.language.title": "اللغة",
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
"settings.general.row.appearance.title": "المظهر",
"settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك",
"settings.general.row.theme.title": "السمة",
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
"settings.general.row.font.title": "الخط",
"settings.general.row.font.description": "تخصيص الخط الأحادي المستخدم في كتل التعليمات البرمجية",
"font.option.ibmPlexMono": "IBM Plex Mono",
"font.option.cascadiaCode": "Cascadia Code",
"font.option.firaCode": "Fira Code",
"font.option.hack": "Hack",
"font.option.inconsolata": "Inconsolata",
"font.option.intelOneMono": "Intel One Mono",
"font.option.jetbrainsMono": "JetBrains Mono",
"font.option.mesloLgs": "Meslo LGS",
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"sound.option.alert01": "تنبيه 01",
"sound.option.alert02": "تنبيه 02",
"sound.option.alert03": "تنبيه 03",
"sound.option.alert04": "تنبيه 04",
"sound.option.alert05": "تنبيه 05",
"sound.option.alert06": "تنبيه 06",
"sound.option.alert07": "تنبيه 07",
"sound.option.alert08": "تنبيه 08",
"sound.option.alert09": "تنبيه 09",
"sound.option.alert10": "تنبيه 10",
"sound.option.bipbop01": "بيب بوب 01",
"sound.option.bipbop02": "بيب بوب 02",
"sound.option.bipbop03": "بيب بوب 03",
"sound.option.bipbop04": "بيب بوب 04",
"sound.option.bipbop05": "بيب بوب 05",
"sound.option.bipbop06": "بيب بوب 06",
"sound.option.bipbop07": "بيب بوب 07",
"sound.option.bipbop08": "بيب بوب 08",
"sound.option.bipbop09": "بيب بوب 09",
"sound.option.bipbop10": "بيب بوب 10",
"sound.option.staplebops01": "ستابل بوبس 01",
"sound.option.staplebops02": "ستابل بوبس 02",
"sound.option.staplebops03": "ستابل بوبس 03",
"sound.option.staplebops04": "ستابل بوبس 04",
"sound.option.staplebops05": "ستابل بوبس 05",
"sound.option.staplebops06": "ستابل بوبس 06",
"sound.option.staplebops07": "ستابل بوبس 07",
"sound.option.nope01": "كلا 01",
"sound.option.nope02": "كلا 02",
"sound.option.nope03": "كلا 03",
"sound.option.nope04": "كلا 04",
"sound.option.nope05": "كلا 05",
"sound.option.nope06": "كلا 06",
"sound.option.nope07": "كلا 07",
"sound.option.nope08": "كلا 08",
"sound.option.nope09": "كلا 09",
"sound.option.nope10": "كلا 10",
"sound.option.nope11": "كلا 11",
"sound.option.nope12": "كلا 12",
"sound.option.yup01": "نعم 01",
"sound.option.yup02": "نعم 02",
"sound.option.yup03": "نعم 03",
"sound.option.yup04": "نعم 04",
"sound.option.yup05": "نعم 05",
"sound.option.yup06": "نعم 06",
"settings.general.notifications.agent.title": "وكيل",
"settings.general.notifications.agent.description": "عرض إشعار النظام عندما يكتمل الوكيل أو يحتاج إلى اهتمام",
"settings.general.notifications.permissions.title": "أذونات",
"settings.general.notifications.permissions.description": "عرض إشعار النظام عند الحاجة إلى إذن",
"settings.general.notifications.errors.title": "أخطاء",
"settings.general.notifications.errors.description": "عرض إشعار النظام عند حدوث خطأ",
"settings.general.sounds.agent.title": "وكيل",
"settings.general.sounds.agent.description": "تشغيل صوت عندما يكتمل الوكيل أو يحتاج إلى اهتمام",
"settings.general.sounds.permissions.title": "أذونات",
"settings.general.sounds.permissions.description": "تشغيل صوت عند الحاجة إلى إذن",
"settings.general.sounds.errors.title": "أخطاء",
"settings.general.sounds.errors.description": "تشغيل صوت عند حدوث خطأ",
"settings.shortcuts.title": "اختصارات لوحة المفاتيح",
"settings.shortcuts.reset.button": "إعادة التعيين إلى الافتراضيات",
"settings.shortcuts.reset.toast.title": "تم إعادة تعيين الاختصارات",
"settings.shortcuts.reset.toast.description": "تم إعادة تعيين اختصارات لوحة المفاتيح إلى الافتراضيات.",
"settings.shortcuts.conflict.title": "الاختصار قيد الاستخدام بالفعل",
"settings.shortcuts.conflict.description": "{{keybind}} معين بالفعل لـ {{titles}}.",
"settings.shortcuts.unassigned": "غير معين",
"settings.shortcuts.pressKeys": "اضغط على المفاتيح",
"settings.shortcuts.search.placeholder": "البحث في الاختصارات",
"settings.shortcuts.search.empty": "لم يتم العثور على اختصارات",
"settings.shortcuts.group.general": "عام",
"settings.shortcuts.group.session": "جلسة",
"settings.shortcuts.group.navigation": "تصفح",
"settings.shortcuts.group.modelAndAgent": "النموذج والوكيل",
"settings.shortcuts.group.terminal": "المحطة الطرفية",
"settings.shortcuts.group.prompt": "موجه",
"settings.providers.title": "الموفرون",
"settings.providers.description": "ستكون إعدادات الموفر قابلة للتكوين هنا.",
"settings.models.title": "النماذج",
"settings.models.description": "ستكون إعدادات النموذج قابلة للتكوين هنا.",
"settings.agents.title": "الوكلاء",
"settings.agents.description": "ستكون إعدادات الوكيل قابلة للتكوين هنا.",
"settings.commands.title": "الأوامر",
"settings.commands.description": "ستكون إعدادات الأمر قابلة للتكوين هنا.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "ستكون إعدادات MCP قابلة للتكوين هنا.",
"settings.permissions.title": "الأذونات",
"settings.permissions.description": "تحكم في الأدوات التي يمكن للخادم استخدامها بشكل افتراضي.",
"settings.permissions.section.tools": "الأدوات",
"settings.permissions.toast.updateFailed.title": "فشل تحديث الأذونات",
"settings.permissions.action.allow": "سماح",
"settings.permissions.action.ask": "سؤال",
"settings.permissions.action.deny": "رفض",
"settings.permissions.tool.read.title": "قراءة",
"settings.permissions.tool.read.description": "قراءة ملف (يطابق مسار الملف)",
"settings.permissions.tool.edit.title": "تحرير",
"settings.permissions.tool.edit.description":
"تعديل الملفات، بما في ذلك التحرير والكتابة والتصحيحات والتحرير المتعدد",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "مطابقة الملفات باستخدام أنماط glob",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "البحث في محتويات الملف باستخدام التعبيرات العادية",
"settings.permissions.tool.list.title": "قائمة",
"settings.permissions.tool.list.description": "سرد الملفات داخل دليل",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "تشغيل أوامر shell",
"settings.permissions.tool.task.title": "Task",
"settings.permissions.tool.task.description": "تشغيل الوكلاء الفرعيين",
"settings.permissions.tool.skill.title": "Skill",
"settings.permissions.tool.skill.description": "تحميل مهارة بالاسم",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "تشغيل استعلامات خادم اللغة",
"settings.permissions.tool.todoread.title": "قراءة المهام",
"settings.permissions.tool.todoread.description": "قراءة قائمة المهام",
"settings.permissions.tool.todowrite.title": "كتابة المهام",
"settings.permissions.tool.todowrite.description": "تحديث قائمة المهام",
"settings.permissions.tool.webfetch.title": "جلب الويب",
"settings.permissions.tool.webfetch.description": "جلب محتوى من عنوان URL",
"settings.permissions.tool.websearch.title": "بحث الويب",
"settings.permissions.tool.websearch.description": "البحث في الويب",
"settings.permissions.tool.codesearch.title": "بحث الكود",
"settings.permissions.tool.codesearch.description": "البحث عن كود على الويب",
"settings.permissions.tool.external_directory.title": "دليل خارجي",
"settings.permissions.tool.external_directory.description": "الوصول إلى الملفات خارج دليل المشروع",
"settings.permissions.tool.doom_loop.title": "حلقة الموت",
"settings.permissions.tool.doom_loop.description": "اكتشاف استدعاءات الأدوات المتكررة بمدخلات متطابقة",
"session.delete.failed.title": "فشل حذف الجلسة",
"session.delete.title": "حذف الجلسة",
"session.delete.confirm": 'حذف الجلسة "{{name}}"؟',
"session.delete.button": "حذف الجلسة",
"workspace.new": "مساحة عمل جديدة",
"workspace.type.local": "محلي",
"workspace.type.sandbox": "صندوق رمل",
"workspace.create.failed.title": "فشل إنشاء مساحة العمل",
"workspace.delete.failed.title": "فشل حذف مساحة العمل",
"workspace.resetting.title": "إعادة تعيين مساحة العمل",
"workspace.resetting.description": "قد يستغرق هذا دقيقة.",
"workspace.reset.failed.title": "فشل إعادة تعيين مساحة العمل",
"workspace.reset.success.title": "تمت إعادة تعيين مساحة العمل",
"workspace.reset.success.description": "مساحة العمل تطابق الآن الفرع الافتراضي.",
"workspace.status.checking": "التحقق من التغييرات غير المدمجة...",
"workspace.status.error": "تعذر التحقق من حالة git.",
"workspace.status.clean": "لم يتم اكتشاف تغييرات غير مدمجة.",
"workspace.status.dirty": "تم اكتشاف تغييرات غير مدمجة في مساحة العمل هذه.",
"workspace.delete.title": "حذف مساحة العمل",
"workspace.delete.confirm": 'حذف مساحة العمل "{{name}}"؟',
"workspace.delete.button": "حذف مساحة العمل",
"workspace.reset.title": "إعادة تعيين مساحة العمل",
"workspace.reset.confirm": 'إعادة تعيين مساحة العمل "{{name}}"؟',
"workspace.reset.button": "إعادة تعيين مساحة العمل",
"workspace.reset.archived.none": "لن تتم أرشفة أي جلسات نشطة.",
"workspace.reset.archived.one": "ستتم أرشفة جلسة واحدة.",
"workspace.reset.archived.many": "ستتم أرشفة {{count}} جلسات.",
"workspace.reset.note": "سيؤدي هذا إلى إعادة تعيين مساحة العمل لتتطابق مع الفرع الافتراضي.",
}

667
packages/app/src/i18n/br.ts Normal file
View File

@@ -0,0 +1,667 @@
export const dict = {
"command.category.suggested": "Sugerido",
"command.category.view": "Visualizar",
"command.category.project": "Projeto",
"command.category.provider": "Provedor",
"command.category.server": "Servidor",
"command.category.session": "Sessão",
"command.category.theme": "Tema",
"command.category.language": "Idioma",
"command.category.file": "Arquivo",
"command.category.terminal": "Terminal",
"command.category.model": "Modelo",
"command.category.mcp": "MCP",
"command.category.agent": "Agente",
"command.category.permissions": "Permissões",
"command.category.workspace": "Espaço de trabalho",
"command.category.settings": "Configurações",
"theme.scheme.system": "Sistema",
"theme.scheme.light": "Claro",
"theme.scheme.dark": "Escuro",
"command.sidebar.toggle": "Alternar barra lateral",
"command.project.open": "Abrir projeto",
"command.provider.connect": "Conectar provedor",
"command.server.switch": "Trocar servidor",
"command.settings.open": "Abrir configurações",
"command.session.previous": "Sessão anterior",
"command.session.next": "Próxima sessão",
"command.session.archive": "Arquivar sessão",
"command.palette": "Paleta de comandos",
"command.theme.cycle": "Alternar tema",
"command.theme.set": "Usar tema: {{theme}}",
"command.theme.scheme.cycle": "Alternar esquema de cores",
"command.theme.scheme.set": "Usar esquema de cores: {{scheme}}",
"command.language.cycle": "Alternar idioma",
"command.language.set": "Usar idioma: {{language}}",
"command.session.new": "Nova sessão",
"command.file.open": "Abrir arquivo",
"command.file.open.description": "Buscar arquivos e comandos",
"command.terminal.toggle": "Alternar terminal",
"command.review.toggle": "Alternar revisão",
"command.terminal.new": "Novo terminal",
"command.terminal.new.description": "Criar uma nova aba de terminal",
"command.steps.toggle": "Alternar passos",
"command.steps.toggle.description": "Mostrar ou ocultar passos da mensagem atual",
"command.message.previous": "Mensagem anterior",
"command.message.previous.description": "Ir para a mensagem de usuário anterior",
"command.message.next": "Próxima mensagem",
"command.message.next.description": "Ir para a próxima mensagem de usuário",
"command.model.choose": "Escolher modelo",
"command.model.choose.description": "Selecionar um modelo diferente",
"command.mcp.toggle": "Alternar MCPs",
"command.mcp.toggle.description": "Alternar MCPs",
"command.agent.cycle": "Alternar agente",
"command.agent.cycle.description": "Mudar para o próximo agente",
"command.agent.cycle.reverse": "Alternar agente (reverso)",
"command.agent.cycle.reverse.description": "Mudar para o agente anterior",
"command.model.variant.cycle": "Alternar nível de raciocínio",
"command.model.variant.cycle.description": "Mudar para o próximo nível de esforço",
"command.permissions.autoaccept.enable": "Aceitar edições automaticamente",
"command.permissions.autoaccept.disable": "Parar de aceitar edições automaticamente",
"command.session.undo": "Desfazer",
"command.session.undo.description": "Desfazer a última mensagem",
"command.session.redo": "Refazer",
"command.session.redo.description": "Refazer a última mensagem desfeita",
"command.session.compact": "Compactar sessão",
"command.session.compact.description": "Resumir a sessão para reduzir o tamanho do contexto",
"command.session.fork": "Bifurcar da mensagem",
"command.session.fork.description": "Criar uma nova sessão a partir de uma mensagem anterior",
"command.session.share": "Compartilhar sessão",
"command.session.share.description": "Compartilhar esta sessão e copiar a URL para a área de transferência",
"command.session.unshare": "Parar de compartilhar sessão",
"command.session.unshare.description": "Parar de compartilhar esta sessão",
"palette.search.placeholder": "Buscar arquivos e comandos",
"palette.empty": "Nenhum resultado encontrado",
"palette.group.commands": "Comandos",
"palette.group.files": "Arquivos",
"dialog.provider.search.placeholder": "Buscar provedores",
"dialog.provider.empty": "Nenhum provedor encontrado",
"dialog.provider.group.popular": "Popular",
"dialog.provider.group.other": "Outro",
"dialog.provider.tag.recommended": "Recomendado",
"dialog.provider.anthropic.note": "Conectar com Claude Pro/Max ou chave de API",
"dialog.provider.openai.note": "Conectar com ChatGPT Pro/Plus ou chave de API",
"dialog.provider.copilot.note": "Conectar com Copilot ou chave de API",
"dialog.model.select.title": "Selecionar modelo",
"dialog.model.search.placeholder": "Buscar modelos",
"dialog.model.empty": "Nenhum resultado de modelo",
"dialog.model.manage": "Gerenciar modelos",
"dialog.model.manage.description": "Personalizar quais modelos aparecem no seletor de modelos.",
"dialog.model.unpaid.freeModels.title": "Modelos gratuitos fornecidos pelo OpenCode",
"dialog.model.unpaid.addMore.title": "Adicionar mais modelos de provedores populares",
"dialog.provider.viewAll": "Ver todos os provedores",
"provider.connect.title": "Conectar {{provider}}",
"provider.connect.title.anthropicProMax": "Entrar com Claude Pro/Max",
"provider.connect.selectMethod": "Selecionar método de login para {{provider}}.",
"provider.connect.method.apiKey": "Chave de API",
"provider.connect.status.inProgress": "Autorização em andamento...",
"provider.connect.status.waiting": "Aguardando autorização...",
"provider.connect.status.failed": "Autorização falhou: {{error}}",
"provider.connect.apiKey.description":
"Digite sua chave de API do {{provider}} para conectar sua conta e usar modelos do {{provider}} no OpenCode.",
"provider.connect.apiKey.label": "Chave de API do {{provider}}",
"provider.connect.apiKey.placeholder": "Chave de API",
"provider.connect.apiKey.required": "A chave de API é obrigatória",
"provider.connect.opencodeZen.line1":
"OpenCode Zen oferece acesso a um conjunto selecionado de modelos confiáveis otimizados para agentes de código.",
"provider.connect.opencodeZen.line2":
"Com uma única chave de API você terá acesso a modelos como Claude, GPT, Gemini, GLM e mais.",
"provider.connect.opencodeZen.visit.prefix": "Visite ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " para obter sua chave de API.",
"provider.connect.oauth.code.visit.prefix": "Visite ",
"provider.connect.oauth.code.visit.link": "este link",
"provider.connect.oauth.code.visit.suffix":
" para obter seu código de autorização e conectar sua conta para usar modelos do {{provider}} no OpenCode.",
"provider.connect.oauth.code.label": "Código de autorização {{method}}",
"provider.connect.oauth.code.placeholder": "Código de autorização",
"provider.connect.oauth.code.required": "O código de autorização é obrigatório",
"provider.connect.oauth.code.invalid": "Código de autorização inválido",
"provider.connect.oauth.auto.visit.prefix": "Visite ",
"provider.connect.oauth.auto.visit.link": "este link",
"provider.connect.oauth.auto.visit.suffix":
" e digite o código abaixo para conectar sua conta e usar modelos do {{provider}} no OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Código de confirmação",
"provider.connect.toast.connected.title": "{{provider}} conectado",
"provider.connect.toast.connected.description": "Modelos do {{provider}} agora estão disponíveis para uso.",
"model.tag.free": "Grátis",
"model.tag.latest": "Mais recente",
"model.provider.anthropic": "Anthropic",
"model.provider.openai": "OpenAI",
"model.provider.google": "Google",
"model.provider.xai": "xAI",
"model.provider.meta": "Meta",
"model.input.text": "texto",
"model.input.image": "imagem",
"model.input.audio": "áudio",
"model.input.video": "vídeo",
"model.input.pdf": "pdf",
"model.tooltip.allows": "Permite: {{inputs}}",
"model.tooltip.reasoning.allowed": "Permite raciocínio",
"model.tooltip.reasoning.none": "Sem raciocínio",
"model.tooltip.context": "Limite de contexto {{limit}}",
"common.search.placeholder": "Buscar",
"common.goBack": "Voltar",
"common.loading": "Carregando",
"common.loading.ellipsis": "...",
"common.cancel": "Cancelar",
"common.submit": "Enviar",
"common.save": "Salvar",
"common.saving": "Salvando...",
"common.default": "Padrão",
"common.attachment": "anexo",
"prompt.placeholder.shell": "Digite comando do shell...",
"prompt.placeholder.normal": 'Pergunte qualquer coisa... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "esc para sair",
"prompt.example.1": "Corrigir um TODO no código",
"prompt.example.2": "Qual é a stack tecnológica deste projeto?",
"prompt.example.3": "Corrigir testes quebrados",
"prompt.example.4": "Explicar como funciona a autenticação",
"prompt.example.5": "Encontrar e corrigir vulnerabilidades de segurança",
"prompt.example.6": "Adicionar testes unitários para o serviço de usuário",
"prompt.example.7": "Refatorar esta função para melhor legibilidade",
"prompt.example.8": "O que significa este erro?",
"prompt.example.9": "Me ajude a depurar este problema",
"prompt.example.10": "Gerar documentação da API",
"prompt.example.11": "Otimizar consultas ao banco de dados",
"prompt.example.12": "Adicionar validação de entrada",
"prompt.example.13": "Criar um novo componente para...",
"prompt.example.14": "Como faço o deploy deste projeto?",
"prompt.example.15": "Revisar meu código para boas práticas",
"prompt.example.16": "Adicionar tratamento de erros a esta função",
"prompt.example.17": "Explicar este padrão regex",
"prompt.example.18": "Converter isto para TypeScript",
"prompt.example.19": "Adicionar logging em todo o código",
"prompt.example.20": "Quais dependências estão desatualizadas?",
"prompt.example.21": "Me ajude a escrever um script de migração",
"prompt.example.22": "Implementar cache para este endpoint",
"prompt.example.23": "Adicionar paginação a esta lista",
"prompt.example.24": "Criar um comando CLI para...",
"prompt.example.25": "Como funcionam as variáveis de ambiente aqui?",
"prompt.popover.emptyResults": "Nenhum resultado correspondente",
"prompt.popover.emptyCommands": "Nenhum comando correspondente",
"prompt.dropzone.label": "Solte imagens ou PDFs aqui",
"prompt.slash.badge.custom": "personalizado",
"prompt.context.active": "ativo",
"prompt.context.includeActiveFile": "Incluir arquivo ativo",
"prompt.context.removeActiveFile": "Remover arquivo ativo do contexto",
"prompt.context.removeFile": "Remover arquivo do contexto",
"prompt.action.attachFile": "Anexar arquivo",
"prompt.attachment.remove": "Remover anexo",
"prompt.action.send": "Enviar",
"prompt.action.stop": "Parar",
"prompt.toast.pasteUnsupported.title": "Colagem não suportada",
"prompt.toast.pasteUnsupported.description": "Somente imagens ou PDFs podem ser colados aqui.",
"prompt.toast.modelAgentRequired.title": "Selecione um agente e modelo",
"prompt.toast.modelAgentRequired.description": "Escolha um agente e modelo antes de enviar um prompt.",
"prompt.toast.worktreeCreateFailed.title": "Falha ao criar worktree",
"prompt.toast.sessionCreateFailed.title": "Falha ao criar sessão",
"prompt.toast.shellSendFailed.title": "Falha ao enviar comando shell",
"prompt.toast.commandSendFailed.title": "Falha ao enviar comando",
"prompt.toast.promptSendFailed.title": "Falha ao enviar prompt",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} de {{total}} habilitados",
"dialog.mcp.empty": "Nenhum MCP configurado",
"mcp.status.connected": "conectado",
"mcp.status.failed": "falhou",
"mcp.status.needs_auth": "precisa de autenticação",
"mcp.status.disabled": "desabilitado",
"dialog.fork.empty": "Nenhuma mensagem para bifurcar",
"dialog.directory.search.placeholder": "Buscar pastas",
"dialog.directory.empty": "Nenhuma pasta encontrada",
"dialog.server.title": "Servidores",
"dialog.server.description": "Trocar para qual servidor OpenCode este aplicativo se conecta.",
"dialog.server.search.placeholder": "Buscar servidores",
"dialog.server.empty": "Nenhum servidor ainda",
"dialog.server.add.title": "Adicionar um servidor",
"dialog.server.add.url": "URL do servidor",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Não foi possível conectar ao servidor",
"dialog.server.add.checking": "Verificando...",
"dialog.server.add.button": "Adicionar",
"dialog.server.default.title": "Servidor padrão",
"dialog.server.default.description":
"Conectar a este servidor na inicialização do aplicativo ao invés de iniciar um servidor local. Requer reinicialização.",
"dialog.server.default.none": "Nenhum servidor selecionado",
"dialog.server.default.set": "Definir servidor atual como padrão",
"dialog.server.default.clear": "Limpar",
"dialog.server.action.remove": "Remover servidor",
"dialog.project.edit.title": "Editar projeto",
"dialog.project.edit.name": "Nome",
"dialog.project.edit.icon": "Ícone",
"dialog.project.edit.icon.alt": "Ícone do projeto",
"dialog.project.edit.icon.hint": "Clique ou arraste uma imagem",
"dialog.project.edit.icon.recommended": "Recomendado: 128x128px",
"dialog.project.edit.color": "Cor",
"dialog.project.edit.color.select": "Selecionar cor {{color}}",
"dialog.project.edit.worktree.startup": "Script de inicialização do espaço de trabalho",
"dialog.project.edit.worktree.startup.description": "Executa após criar um novo espaço de trabalho (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "ex: bun install",
"context.breakdown.title": "Detalhamento do Contexto",
"context.breakdown.note":
'Detalhamento aproximado dos tokens de entrada. "Outros" inclui definições de ferramentas e overhead.',
"context.breakdown.system": "Sistema",
"context.breakdown.user": "Usuário",
"context.breakdown.assistant": "Assistente",
"context.breakdown.tool": "Chamadas de Ferramentas",
"context.breakdown.other": "Outros",
"context.systemPrompt.title": "Prompt do Sistema",
"context.rawMessages.title": "Mensagens brutas",
"context.stats.session": "Sessão",
"context.stats.messages": "Mensagens",
"context.stats.provider": "Provedor",
"context.stats.model": "Modelo",
"context.stats.limit": "Limite de Contexto",
"context.stats.totalTokens": "Total de Tokens",
"context.stats.usage": "Uso",
"context.stats.inputTokens": "Tokens de Entrada",
"context.stats.outputTokens": "Tokens de Saída",
"context.stats.reasoningTokens": "Tokens de Raciocínio",
"context.stats.cacheTokens": "Tokens de Cache (leitura/escrita)",
"context.stats.userMessages": "Mensagens de Usuário",
"context.stats.assistantMessages": "Mensagens do Assistente",
"context.stats.totalCost": "Custo Total",
"context.stats.sessionCreated": "Sessão Criada",
"context.stats.lastActivity": "Última Atividade",
"context.usage.tokens": "Tokens",
"context.usage.usage": "Uso",
"context.usage.cost": "Custo",
"context.usage.clickToView": "Clique para ver o contexto",
"context.usage.view": "Ver uso do contexto",
"language.en": "Inglês",
"language.zh": "Chinês (Simplificado)",
"language.zht": "Chinês (Tradicional)",
"language.ko": "Coreano",
"language.de": "Alemão",
"language.es": "Espanhol",
"language.fr": "Francês",
"language.ja": "Japonês",
"language.da": "Dinamarquês",
"language.ru": "Russo",
"language.pl": "Polonês",
"language.ar": "Árabe",
"language.no": "Norueguês",
"language.br": "Português (Brasil)",
"toast.language.title": "Idioma",
"toast.language.description": "Alterado para {{language}}",
"toast.theme.title": "Tema alterado",
"toast.scheme.title": "Esquema de cores",
"toast.permissions.autoaccept.on.title": "Aceitando edições automaticamente",
"toast.permissions.autoaccept.on.description": "Permissões de edição e escrita serão aprovadas automaticamente",
"toast.permissions.autoaccept.off.title": "Parou de aceitar edições automaticamente",
"toast.permissions.autoaccept.off.description": "Permissões de edição e escrita exigirão aprovação",
"toast.model.none.title": "Nenhum modelo selecionado",
"toast.model.none.description": "Conecte um provedor para resumir esta sessão",
"toast.file.loadFailed.title": "Falha ao carregar arquivo",
"toast.session.share.copyFailed.title": "Falha ao copiar URL para a área de transferência",
"toast.session.share.success.title": "Sessão compartilhada",
"toast.session.share.success.description": "URL compartilhada copiada para a área de transferência!",
"toast.session.share.failed.title": "Falha ao compartilhar sessão",
"toast.session.share.failed.description": "Ocorreu um erro ao compartilhar a sessão",
"toast.session.unshare.success.title": "Sessão não compartilhada",
"toast.session.unshare.success.description": "Sessão deixou de ser compartilhada com sucesso!",
"toast.session.unshare.failed.title": "Falha ao parar de compartilhar sessão",
"toast.session.unshare.failed.description": "Ocorreu um erro ao parar de compartilhar a sessão",
"toast.session.listFailed.title": "Falha ao carregar sessões para {{project}}",
"toast.update.title": "Atualização disponível",
"toast.update.description": "Uma nova versão do OpenCode ({{version}}) está disponível para instalação.",
"toast.update.action.installRestart": "Instalar e reiniciar",
"toast.update.action.notYet": "Agora não",
"error.page.title": "Algo deu errado",
"error.page.description": "Ocorreu um erro ao carregar a aplicação.",
"error.page.details.label": "Detalhes do Erro",
"error.page.action.restart": "Reiniciar",
"error.page.action.checking": "Verificando...",
"error.page.action.checkUpdates": "Verificar atualizações",
"error.page.action.updateTo": "Atualizar para {{version}}",
"error.page.report.prefix": "Por favor, reporte este erro para a equipe do OpenCode",
"error.page.report.discord": "no Discord",
"error.page.version": "Versão: {{version}}",
"error.dev.rootNotFound":
"Elemento raiz não encontrado. Você esqueceu de adicioná-lo ao seu index.html? Ou talvez o atributo id foi escrito incorretamente?",
"error.globalSync.connectFailed": "Não foi possível conectar ao servidor. Há um servidor executando em `{{url}}`?",
"error.chain.unknown": "Erro desconhecido",
"error.chain.causedBy": "Causado por:",
"error.chain.apiError": "Erro de API",
"error.chain.status": "Status: {{status}}",
"error.chain.retryable": "Pode tentar novamente: {{retryable}}",
"error.chain.responseBody": "Corpo da resposta:\n{{body}}",
"error.chain.didYouMean": "Você quis dizer: {{suggestions}}",
"error.chain.modelNotFound": "Modelo não encontrado: {{provider}}/{{model}}",
"error.chain.checkConfig": "Verifique os nomes de provedor/modelo na sua configuração (opencode.json)",
"error.chain.mcpFailed": 'Servidor MCP "{{name}}" falhou. Nota: OpenCode ainda não suporta autenticação MCP.',
"error.chain.providerAuthFailed": "Autenticação do provedor falhou ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'Falha ao inicializar provedor "{{provider}}". Verifique credenciais e configuração.',
"error.chain.configJsonInvalid": "Arquivo de configuração em {{path}} não é um JSON(C) válido",
"error.chain.configJsonInvalidWithMessage":
"Arquivo de configuração em {{path}} não é um JSON(C) válido: {{message}}",
"error.chain.configDirectoryTypo":
'Diretório "{{dir}}" em {{path}} não é válido. Renomeie o diretório para "{{suggestion}}" ou remova-o. Este é um erro de digitação comum.',
"error.chain.configFrontmatterError": "Falha ao analisar frontmatter em {{path}}:\n{{message}}",
"error.chain.configInvalid": "Arquivo de configuração em {{path}} é inválido",
"error.chain.configInvalidWithMessage": "Arquivo de configuração em {{path}} é inválido: {{message}}",
"notification.permission.title": "Permissão necessária",
"notification.permission.description": "{{sessionTitle}} em {{projectName}} precisa de permissão",
"notification.question.title": "Pergunta",
"notification.question.description": "{{sessionTitle}} em {{projectName}} tem uma pergunta",
"notification.action.goToSession": "Ir para sessão",
"notification.session.responseReady.title": "Resposta pronta",
"notification.session.error.title": "Erro na sessão",
"notification.session.error.fallbackDescription": "Ocorreu um erro",
"home.recentProjects": "Projetos recentes",
"home.empty.title": "Nenhum projeto recente",
"home.empty.description": "Comece abrindo um projeto local",
"session.tab.session": "Sessão",
"session.tab.review": "Revisão",
"session.tab.context": "Contexto",
"session.panel.reviewAndFiles": "Revisão e arquivos",
"session.review.filesChanged": "{{count}} Arquivos Alterados",
"session.review.loadingChanges": "Carregando alterações...",
"session.review.empty": "Nenhuma alteração nesta sessão ainda",
"session.messages.renderEarlier": "Renderizar mensagens anteriores",
"session.messages.loadingEarlier": "Carregando mensagens anteriores...",
"session.messages.loadEarlier": "Carregar mensagens anteriores",
"session.messages.loading": "Carregando mensagens...",
"session.messages.jumpToLatest": "Ir para a mais recente",
"session.context.addToContext": "Adicionar {{selection}} ao contexto",
"session.new.worktree.main": "Branch principal",
"session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
"session.new.worktree.create": "Criar novo worktree",
"session.new.lastModified": "Última modificação",
"session.header.search.placeholder": "Buscar {{project}}",
"session.header.searchFiles": "Buscar arquivos",
"session.share.popover.title": "Publicar na web",
"session.share.popover.description.shared":
"Esta sessão é pública na web. Está acessível para qualquer pessoa com o link.",
"session.share.popover.description.unshared":
"Compartilhar sessão publicamente na web. Estará acessível para qualquer pessoa com o link.",
"session.share.action.share": "Compartilhar",
"session.share.action.publish": "Publicar",
"session.share.action.publishing": "Publicando...",
"session.share.action.unpublish": "Cancelar publicação",
"session.share.action.unpublishing": "Cancelando publicação...",
"session.share.action.view": "Ver",
"session.share.copy.copied": "Copiado",
"session.share.copy.copyLink": "Copiar link",
"lsp.tooltip.none": "Nenhum servidor LSP",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Carregando prompt...",
"terminal.loading": "Carregando terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Fechar terminal",
"terminal.connectionLost.title": "Conexão Perdida",
"terminal.connectionLost.description":
"A conexão do terminal foi interrompida. Isso pode acontecer quando o servidor reinicia.",
"common.closeTab": "Fechar aba",
"common.dismiss": "Descartar",
"common.requestFailed": "Requisição falhou",
"common.moreOptions": "Mais opções",
"common.learnMore": "Saiba mais",
"common.rename": "Renomear",
"common.reset": "Redefinir",
"common.archive": "Arquivar",
"common.delete": "Excluir",
"common.close": "Fechar",
"common.edit": "Editar",
"common.loadMore": "Carregar mais",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Alternar menu",
"sidebar.nav.projectsAndSessions": "Projetos e sessões",
"sidebar.settings": "Configurações",
"sidebar.help": "Ajuda",
"sidebar.workspaces.enable": "Habilitar espaços de trabalho",
"sidebar.workspaces.disable": "Desabilitar espaços de trabalho",
"sidebar.gettingStarted.title": "Começando",
"sidebar.gettingStarted.line1": "OpenCode inclui modelos gratuitos para você começar imediatamente.",
"sidebar.gettingStarted.line2": "Conecte qualquer provedor para usar modelos, incluindo Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sessões recentes",
"sidebar.project.viewAllSessions": "Ver todas as sessões",
"settings.section.desktop": "Desktop",
"settings.tab.general": "Geral",
"settings.tab.shortcuts": "Atalhos",
"settings.general.section.appearance": "Aparência",
"settings.general.section.notifications": "Notificações do sistema",
"settings.general.section.sounds": "Efeitos sonoros",
"settings.general.row.language.title": "Idioma",
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
"settings.general.row.appearance.title": "Aparência",
"settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo",
"settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
"settings.general.row.font.title": "Fonte",
"settings.general.row.font.description": "Personalize a fonte monoespaçada usada em blocos de código",
"font.option.ibmPlexMono": "IBM Plex Mono",
"font.option.cascadiaCode": "Cascadia Code",
"font.option.firaCode": "Fira Code",
"font.option.hack": "Hack",
"font.option.inconsolata": "Inconsolata",
"font.option.intelOneMono": "Intel One Mono",
"font.option.jetbrainsMono": "JetBrains Mono",
"font.option.mesloLgs": "Meslo LGS",
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"sound.option.alert01": "Alerta 01",
"sound.option.alert02": "Alerta 02",
"sound.option.alert03": "Alerta 03",
"sound.option.alert04": "Alerta 04",
"sound.option.alert05": "Alerta 05",
"sound.option.alert06": "Alerta 06",
"sound.option.alert07": "Alerta 07",
"sound.option.alert08": "Alerta 08",
"sound.option.alert09": "Alerta 09",
"sound.option.alert10": "Alerta 10",
"sound.option.bipbop01": "Bip-bop 01",
"sound.option.bipbop02": "Bip-bop 02",
"sound.option.bipbop03": "Bip-bop 03",
"sound.option.bipbop04": "Bip-bop 04",
"sound.option.bipbop05": "Bip-bop 05",
"sound.option.bipbop06": "Bip-bop 06",
"sound.option.bipbop07": "Bip-bop 07",
"sound.option.bipbop08": "Bip-bop 08",
"sound.option.bipbop09": "Bip-bop 09",
"sound.option.bipbop10": "Bip-bop 10",
"sound.option.staplebops01": "Staplebops 01",
"sound.option.staplebops02": "Staplebops 02",
"sound.option.staplebops03": "Staplebops 03",
"sound.option.staplebops04": "Staplebops 04",
"sound.option.staplebops05": "Staplebops 05",
"sound.option.staplebops06": "Staplebops 06",
"sound.option.staplebops07": "Staplebops 07",
"sound.option.nope01": "Não 01",
"sound.option.nope02": "Não 02",
"sound.option.nope03": "Não 03",
"sound.option.nope04": "Não 04",
"sound.option.nope05": "Não 05",
"sound.option.nope06": "Não 06",
"sound.option.nope07": "Não 07",
"sound.option.nope08": "Não 08",
"sound.option.nope09": "Não 09",
"sound.option.nope10": "Não 10",
"sound.option.nope11": "Não 11",
"sound.option.nope12": "Não 12",
"sound.option.yup01": "Sim 01",
"sound.option.yup02": "Sim 02",
"sound.option.yup03": "Sim 03",
"sound.option.yup04": "Sim 04",
"sound.option.yup05": "Sim 05",
"sound.option.yup06": "Sim 06",
"settings.general.notifications.agent.title": "Agente",
"settings.general.notifications.agent.description":
"Mostrar notificação do sistema quando o agente estiver completo ou precisar de atenção",
"settings.general.notifications.permissions.title": "Permissões",
"settings.general.notifications.permissions.description":
"Mostrar notificação do sistema quando uma permissão for necessária",
"settings.general.notifications.errors.title": "Erros",
"settings.general.notifications.errors.description": "Mostrar notificação do sistema quando ocorrer um erro",
"settings.general.sounds.agent.title": "Agente",
"settings.general.sounds.agent.description": "Reproduzir som quando o agente estiver completo ou precisar de atenção",
"settings.general.sounds.permissions.title": "Permissões",
"settings.general.sounds.permissions.description": "Reproduzir som quando uma permissão for necessária",
"settings.general.sounds.errors.title": "Erros",
"settings.general.sounds.errors.description": "Reproduzir som quando ocorrer um erro",
"settings.shortcuts.title": "Atalhos de teclado",
"settings.shortcuts.reset.button": "Redefinir para padrões",
"settings.shortcuts.reset.toast.title": "Atalhos redefinidos",
"settings.shortcuts.reset.toast.description": "Atalhos de teclado foram redefinidos para os padrões.",
"settings.shortcuts.conflict.title": "Atalho já em uso",
"settings.shortcuts.conflict.description": "{{keybind}} já está atribuído a {{titles}}.",
"settings.shortcuts.unassigned": "Não atribuído",
"settings.shortcuts.pressKeys": "Pressione teclas",
"settings.shortcuts.search.placeholder": "Buscar atalhos",
"settings.shortcuts.search.empty": "Nenhum atalho encontrado",
"settings.shortcuts.group.general": "Geral",
"settings.shortcuts.group.session": "Sessão",
"settings.shortcuts.group.navigation": "Navegação",
"settings.shortcuts.group.modelAndAgent": "Modelo e agente",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Prompt",
"settings.providers.title": "Provedores",
"settings.providers.description": "Configurações de provedores estarão disponíveis aqui.",
"settings.models.title": "Modelos",
"settings.models.description": "Configurações de modelos estarão disponíveis aqui.",
"settings.agents.title": "Agentes",
"settings.agents.description": "Configurações de agentes estarão disponíveis aqui.",
"settings.commands.title": "Comandos",
"settings.commands.description": "Configurações de comandos estarão disponíveis aqui.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "Configurações de MCP estarão disponíveis aqui.",
"settings.permissions.title": "Permissões",
"settings.permissions.description": "Controle quais ferramentas o servidor pode usar por padrão.",
"settings.permissions.section.tools": "Ferramentas",
"settings.permissions.toast.updateFailed.title": "Falha ao atualizar permissões",
"settings.permissions.action.allow": "Permitir",
"settings.permissions.action.ask": "Perguntar",
"settings.permissions.action.deny": "Negar",
"settings.permissions.tool.read.title": "Ler",
"settings.permissions.tool.read.description": "Ler um arquivo (corresponde ao caminho do arquivo)",
"settings.permissions.tool.edit.title": "Editar",
"settings.permissions.tool.edit.description":
"Modificar arquivos, incluindo edições, escritas, patches e multi-edições",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Corresponder arquivos usando padrões glob",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Buscar conteúdo de arquivos usando expressões regulares",
"settings.permissions.tool.list.title": "Listar",
"settings.permissions.tool.list.description": "Listar arquivos dentro de um diretório",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Executar comandos shell",
"settings.permissions.tool.task.title": "Tarefa",
"settings.permissions.tool.task.description": "Lançar sub-agentes",
"settings.permissions.tool.skill.title": "Habilidade",
"settings.permissions.tool.skill.description": "Carregar uma habilidade por nome",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Executar consultas de servidor de linguagem",
"settings.permissions.tool.todoread.title": "Ler Tarefas",
"settings.permissions.tool.todoread.description": "Ler a lista de tarefas",
"settings.permissions.tool.todowrite.title": "Escrever Tarefas",
"settings.permissions.tool.todowrite.description": "Atualizar a lista de tarefas",
"settings.permissions.tool.webfetch.title": "Buscar Web",
"settings.permissions.tool.webfetch.description": "Buscar conteúdo de uma URL",
"settings.permissions.tool.websearch.title": "Pesquisa Web",
"settings.permissions.tool.websearch.description": "Pesquisar na web",
"settings.permissions.tool.codesearch.title": "Pesquisa de Código",
"settings.permissions.tool.codesearch.description": "Pesquisar código na web",
"settings.permissions.tool.external_directory.title": "Diretório Externo",
"settings.permissions.tool.external_directory.description": "Acessar arquivos fora do diretório do projeto",
"settings.permissions.tool.doom_loop.title": "Loop Infinito",
"settings.permissions.tool.doom_loop.description": "Detectar chamadas de ferramentas repetidas com entrada idêntica",
"session.delete.failed.title": "Falha ao excluir sessão",
"session.delete.title": "Excluir sessão",
"session.delete.confirm": 'Excluir sessão "{{name}}"?',
"session.delete.button": "Excluir sessão",
"workspace.new": "Novo espaço de trabalho",
"workspace.type.local": "local",
"workspace.type.sandbox": "sandbox",
"workspace.create.failed.title": "Falha ao criar espaço de trabalho",
"workspace.delete.failed.title": "Falha ao excluir espaço de trabalho",
"workspace.resetting.title": "Redefinindo espaço de trabalho",
"workspace.resetting.description": "Isso pode levar um minuto.",
"workspace.reset.failed.title": "Falha ao redefinir espaço de trabalho",
"workspace.reset.success.title": "Espaço de trabalho redefinido",
"workspace.reset.success.description": "Espaço de trabalho agora corresponde ao branch padrão.",
"workspace.status.checking": "Verificando alterações não mescladas...",
"workspace.status.error": "Não foi possível verificar o status do git.",
"workspace.status.clean": "Nenhuma alteração não mesclada detectada.",
"workspace.status.dirty": "Alterações não mescladas detectadas neste espaço de trabalho.",
"workspace.delete.title": "Excluir espaço de trabalho",
"workspace.delete.confirm": 'Excluir espaço de trabalho "{{name}}"?',
"workspace.delete.button": "Excluir espaço de trabalho",
"workspace.reset.title": "Redefinir espaço de trabalho",
"workspace.reset.confirm": 'Redefinir espaço de trabalho "{{name}}"?',
"workspace.reset.button": "Redefinir espaço de trabalho",
"workspace.reset.archived.none": "Nenhuma sessão ativa será arquivada.",
"workspace.reset.archived.one": "1 sessão será arquivada.",
"workspace.reset.archived.many": "{{count}} sessões serão arquivadas.",
"workspace.reset.note": "Isso redefinirá o espaço de trabalho para corresponder ao branch padrão.",
}

582
packages/app/src/i18n/da.ts Normal file
View File

@@ -0,0 +1,582 @@
export const dict = {
"command.category.suggested": "Foreslået",
"command.category.view": "Vis",
"command.category.project": "Projekt",
"command.category.provider": "Udbyder",
"command.category.server": "Server",
"command.category.session": "Session",
"command.category.theme": "Tema",
"command.category.language": "Sprog",
"command.category.file": "Fil",
"command.category.terminal": "Terminal",
"command.category.model": "Model",
"command.category.mcp": "MCP",
"command.category.agent": "Agent",
"command.category.permissions": "Tilladelser",
"command.category.workspace": "Arbejdsområde",
"theme.scheme.system": "System",
"theme.scheme.light": "Lys",
"theme.scheme.dark": "Mørk",
"command.sidebar.toggle": "Skift sidebjælke",
"command.project.open": "Åbn projekt",
"command.provider.connect": "Tilslut udbyder",
"command.server.switch": "Skift server",
"command.session.previous": "Forrige session",
"command.session.next": "Næste session",
"command.session.archive": "Arkivér session",
"command.palette": "Kommandopalette",
"command.theme.cycle": "Skift tema",
"command.theme.set": "Brug tema: {{theme}}",
"command.theme.scheme.cycle": "Skift farveskema",
"command.theme.scheme.set": "Brug farveskema: {{scheme}}",
"command.language.cycle": "Skift sprog",
"command.language.set": "Brug sprog: {{language}}",
"command.session.new": "Ny session",
"command.file.open": "Åbn fil",
"command.file.open.description": "Søg i filer og kommandoer",
"command.terminal.toggle": "Skift terminal",
"command.review.toggle": "Skift gennemgang",
"command.terminal.new": "Ny terminal",
"command.terminal.new.description": "Opret en ny terminalfane",
"command.steps.toggle": "Skift trin",
"command.steps.toggle.description": "Vis eller skjul trin for den aktuelle besked",
"command.message.previous": "Forrige besked",
"command.message.previous.description": "Gå til den forrige brugerbesked",
"command.message.next": "Næste besked",
"command.message.next.description": "Gå til den næste brugerbesked",
"command.model.choose": "Vælg model",
"command.model.choose.description": "Vælg en anden model",
"command.mcp.toggle": "Skift MCP'er",
"command.mcp.toggle.description": "Skift MCP'er",
"command.agent.cycle": "Skift agent",
"command.agent.cycle.description": "Skift til næste agent",
"command.agent.cycle.reverse": "Skift agent baglæns",
"command.agent.cycle.reverse.description": "Skift til forrige agent",
"command.model.variant.cycle": "Skift tænkeindsats",
"command.model.variant.cycle.description": "Skift til næste indsatsniveau",
"command.permissions.autoaccept.enable": "Accepter ændringer automatisk",
"command.permissions.autoaccept.disable": "Stop automatisk accept af ændringer",
"command.session.undo": "Fortryd",
"command.session.undo.description": "Fortryd den sidste besked",
"command.session.redo": "Omgør",
"command.session.redo.description": "Omgør den sidste fortrudte besked",
"command.session.compact": "Komprimér session",
"command.session.compact.description": "Opsummer sessionen for at reducere kontekststørrelsen",
"command.session.fork": "Forgren fra besked",
"command.session.fork.description": "Opret en ny session fra en tidligere besked",
"command.session.share": "Del session",
"command.session.share.description": "Del denne session og kopier URL'en til udklipsholderen",
"command.session.unshare": "Stop deling af session",
"command.session.unshare.description": "Stop med at dele denne session",
"palette.search.placeholder": "Søg i filer og kommandoer",
"palette.empty": "Ingen resultater fundet",
"palette.group.commands": "Kommandoer",
"palette.group.files": "Filer",
"dialog.provider.search.placeholder": "Søg udbydere",
"dialog.provider.empty": "Ingen udbydere fundet",
"dialog.provider.group.popular": "Populære",
"dialog.provider.group.other": "Andre",
"dialog.provider.tag.recommended": "Anbefalet",
"dialog.provider.anthropic.note": "Forbind med Claude Pro/Max eller API-nøgle",
"dialog.provider.openai.note": "Forbind med ChatGPT Pro/Plus eller API-nøgle",
"dialog.provider.copilot.note": "Forbind med Copilot eller API-nøgle",
"dialog.model.select.title": "Vælg model",
"dialog.model.search.placeholder": "Søg modeller",
"dialog.model.empty": "Ingen modeller fundet",
"dialog.model.manage": "Administrer modeller",
"dialog.model.manage.description": "Tilpas hvilke modeller der vises i modelvælgeren.",
"dialog.model.unpaid.freeModels.title": "Gratis modeller leveret af OpenCode",
"dialog.model.unpaid.addMore.title": "Tilføj flere modeller fra populære udbydere",
"dialog.provider.viewAll": "Vis alle udbydere",
"provider.connect.title": "Forbind {{provider}}",
"provider.connect.title.anthropicProMax": "Log ind med Claude Pro/Max",
"provider.connect.selectMethod": "Vælg loginmetode for {{provider}}.",
"provider.connect.method.apiKey": "API-nøgle",
"provider.connect.status.inProgress": "Godkendelse i gang...",
"provider.connect.status.waiting": "Venter på godkendelse...",
"provider.connect.status.failed": "Godkendelse mislykkedes: {{error}}",
"provider.connect.apiKey.description":
"Indtast din {{provider}} API-nøgle for at forbinde din konto og bruge {{provider}} modeller i OpenCode.",
"provider.connect.apiKey.label": "{{provider}} API-nøgle",
"provider.connect.apiKey.placeholder": "API-nøgle",
"provider.connect.apiKey.required": "API-nøgle er påkrævet",
"provider.connect.opencodeZen.line1":
"OpenCode Zen giver dig adgang til et udvalg af pålidelige optimerede modeller til kodningsagenter.",
"provider.connect.opencodeZen.line2":
"Med en enkelt API-nøgle får du adgang til modeller som Claude, GPT, Gemini, GLM og flere.",
"provider.connect.opencodeZen.visit.prefix": "Besøg ",
"provider.connect.opencodeZen.visit.suffix": " for at hente din API-nøgle.",
"provider.connect.oauth.code.visit.prefix": "Besøg ",
"provider.connect.oauth.code.visit.link": "dette link",
"provider.connect.oauth.code.visit.suffix":
" for at hente din godkendelseskode for at forbinde din konto og bruge {{provider}} modeller i OpenCode.",
"provider.connect.oauth.code.label": "{{method}} godkendelseskode",
"provider.connect.oauth.code.placeholder": "Godkendelseskode",
"provider.connect.oauth.code.required": "Godkendelseskode er påkrævet",
"provider.connect.oauth.code.invalid": "Ugyldig godkendelseskode",
"provider.connect.oauth.auto.visit.prefix": "Besøg ",
"provider.connect.oauth.auto.visit.link": "dette link",
"provider.connect.oauth.auto.visit.suffix":
" og indtast koden nedenfor for at forbinde din konto og bruge {{provider}} modeller i OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Bekræftelseskode",
"provider.connect.toast.connected.title": "{{provider}} forbundet",
"provider.connect.toast.connected.description": "{{provider}} modeller er nu tilgængelige.",
"model.tag.free": "Gratis",
"model.tag.latest": "Nyeste",
"common.search.placeholder": "Søg",
"common.goBack": "Gå tilbage",
"common.loading": "Indlæser",
"common.cancel": "Annuller",
"common.submit": "Indsend",
"common.save": "Gem",
"common.saving": "Gemmer...",
"common.default": "Standard",
"common.attachment": "vedhæftning",
"prompt.placeholder.shell": "Indtast shell-kommando...",
"prompt.placeholder.normal": 'Spørg om hvad som helst... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "esc for at afslutte",
"prompt.example.1": "Ret en TODO i koden",
"prompt.example.2": "Hvad er teknologistakken for dette projekt?",
"prompt.example.3": "Ret ødelagte tests",
"prompt.example.4": "Forklar hvordan godkendelse fungerer",
"prompt.example.5": "Find og ret sikkerhedshuller",
"prompt.example.6": "Tilføj enhedstests for brugerservice",
"prompt.example.7": "Refaktorer denne funktion så den er mere læsbar",
"prompt.example.8": "Hvad betyder denne fejl?",
"prompt.example.9": "Hjælp mig med at debugge dette problem",
"prompt.example.10": "Generer API-dokumentation",
"prompt.example.11": "Optimer databaseforespørgsler",
"prompt.example.12": "Tilføj validering af input",
"prompt.example.13": "Opret en ny komponent til...",
"prompt.example.14": "Hvordan deployerer jeg dette projekt?",
"prompt.example.15": "Gennemgå min kode for bedste praksis",
"prompt.example.16": "Tilføj fejlhåndtering til denne funktion",
"prompt.example.17": "Forklar dette regex-mønster",
"prompt.example.18": "Konverter dette til TypeScript",
"prompt.example.19": "Tilføj logning i hele koden",
"prompt.example.20": "Hvilke afhængigheder er forældede?",
"prompt.example.21": "Hjælp mig med at skrive et migreringsscript",
"prompt.example.22": "Implementer caching for dette endpoint",
"prompt.example.23": "Tilføj sideinddeling til denne liste",
"prompt.example.24": "Opret en CLI-kommando til...",
"prompt.example.25": "Hvordan fungerer miljøvariabler her?",
"prompt.popover.emptyResults": "Ingen matchende resultater",
"prompt.popover.emptyCommands": "Ingen matchende kommandoer",
"prompt.dropzone.label": "Slip billeder eller PDF'er her",
"prompt.slash.badge.custom": "brugerdefineret",
"prompt.context.active": "aktiv",
"prompt.context.includeActiveFile": "Inkluder aktiv fil",
"prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
"prompt.context.removeFile": "Fjern fil fra kontekst",
"prompt.action.attachFile": "Vedhæft fil",
"prompt.attachment.remove": "Fjern vedhæftning",
"prompt.action.send": "Send",
"prompt.action.stop": "Stop",
"prompt.toast.pasteUnsupported.title": "Ikke understøttet indsæt",
"prompt.toast.pasteUnsupported.description": "Kun billeder eller PDF'er kan indsættes her.",
"prompt.toast.modelAgentRequired.title": "Vælg en agent og model",
"prompt.toast.modelAgentRequired.description": "Vælg en agent og model før du sender en forespørgsel.",
"prompt.toast.worktreeCreateFailed.title": "Kunne ikke oprette worktree",
"prompt.toast.sessionCreateFailed.title": "Kunne ikke oprette session",
"prompt.toast.shellSendFailed.title": "Kunne ikke sende shell-kommando",
"prompt.toast.commandSendFailed.title": "Kunne ikke sende kommando",
"prompt.toast.promptSendFailed.title": "Kunne ikke sende forespørgsel",
"dialog.mcp.title": "MCP'er",
"dialog.mcp.description": "{{enabled}} af {{total}} aktiveret",
"dialog.mcp.empty": "Ingen MCP'er konfigureret",
"mcp.status.connected": "forbundet",
"mcp.status.failed": "mislykkedes",
"mcp.status.needs_auth": "kræver godkendelse",
"mcp.status.disabled": "deaktiveret",
"dialog.fork.empty": "Ingen beskeder at forgrene fra",
"dialog.directory.search.placeholder": "Søg mapper",
"dialog.directory.empty": "Ingen mapper fundet",
"dialog.server.title": "Servere",
"dialog.server.description": "Skift hvilken OpenCode-server denne app forbinder til.",
"dialog.server.search.placeholder": "Søg servere",
"dialog.server.empty": "Ingen servere endnu",
"dialog.server.add.title": "Tilføj en server",
"dialog.server.add.url": "Server URL",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Kunne ikke forbinde til server",
"dialog.server.add.checking": "Tjekker...",
"dialog.server.add.button": "Tilføj",
"dialog.server.default.title": "Standardserver",
"dialog.server.default.description":
"Forbind til denne server ved start af app i stedet for at starte en lokal server. Kræver genstart.",
"dialog.server.default.none": "Ingen server valgt",
"dialog.server.default.set": "Sæt nuværende server som standard",
"dialog.server.default.clear": "Ryd",
"dialog.server.action.remove": "Fjern server",
"dialog.project.edit.title": "Rediger projekt",
"dialog.project.edit.name": "Navn",
"dialog.project.edit.icon": "Ikon",
"dialog.project.edit.icon.alt": "Projektikon",
"dialog.project.edit.icon.hint": "Klik eller træk et billede",
"dialog.project.edit.icon.recommended": "Anbefalet: 128x128px",
"dialog.project.edit.color": "Farve",
"dialog.project.edit.color.select": "Vælg farven {{color}}",
"context.breakdown.title": "Kontekstfordeling",
"context.breakdown.note":
'Omtrentlig fordeling af input-tokens. "Andre" inkluderer værktøjsdefinitioner og overhead.',
"context.breakdown.system": "System",
"context.breakdown.user": "Bruger",
"context.breakdown.assistant": "Assistent",
"context.breakdown.tool": "Værktøjskald",
"context.breakdown.other": "Andre",
"context.systemPrompt.title": "Systemprompt",
"context.rawMessages.title": "Rå beskeder",
"context.stats.session": "Session",
"context.stats.messages": "Beskeder",
"context.stats.provider": "Udbyder",
"context.stats.model": "Model",
"context.stats.limit": "Kontekstgrænse",
"context.stats.totalTokens": "Total Tokens",
"context.stats.usage": "Forbrug",
"context.stats.inputTokens": "Input Tokens",
"context.stats.outputTokens": "Output Tokens",
"context.stats.reasoningTokens": "Tænke Tokens",
"context.stats.cacheTokens": "Cache Tokens (læs/skriv)",
"context.stats.userMessages": "Brugerbeskeder",
"context.stats.assistantMessages": "Assistentbeskeder",
"context.stats.totalCost": "Samlede omkostninger",
"context.stats.sessionCreated": "Session oprettet",
"context.stats.lastActivity": "Seneste aktivitet",
"context.usage.tokens": "Tokens",
"context.usage.usage": "Forbrug",
"context.usage.cost": "Omkostning",
"context.usage.clickToView": "Klik for at se kontekst",
"context.usage.view": "Se kontekstforbrug",
"language.en": "Engelsk",
"language.zh": "Kinesisk (forenklet)",
"language.zht": "Kinesisk (traditionelt)",
"language.ko": "Koreansk",
"language.de": "Tysk",
"language.es": "Spansk",
"language.fr": "Fransk",
"language.ja": "Japansk",
"language.da": "Dansk",
"language.ru": "Russisk",
"language.pl": "Polsk",
"language.ar": "Arabisk",
"language.no": "Norsk",
"language.br": "Portugisisk (Brasilien)",
"toast.language.title": "Sprog",
"toast.language.description": "Skiftede til {{language}}",
"toast.theme.title": "Tema skiftet",
"toast.scheme.title": "Farveskema",
"toast.permissions.autoaccept.on.title": "Accepterer ændringer automatisk",
"toast.permissions.autoaccept.on.description": "Redigerings- og skrivetilladelser vil automatisk blive godkendt",
"toast.permissions.autoaccept.off.title": "Stoppede automatisk accept af ændringer",
"toast.permissions.autoaccept.off.description": "Redigerings- og skrivetilladelser vil kræve godkendelse",
"toast.model.none.title": "Ingen model valgt",
"toast.model.none.description": "Forbind en udbyder for at opsummere denne session",
"toast.file.loadFailed.title": "Kunne ikke indlæse fil",
"toast.session.share.copyFailed.title": "Kunne ikke kopiere URL til udklipsholder",
"toast.session.share.success.title": "Session delt",
"toast.session.share.success.description": "Delings-URL kopieret til udklipsholder!",
"toast.session.share.failed.title": "Kunne ikke dele session",
"toast.session.share.failed.description": "Der opstod en fejl under deling af sessionen",
"toast.session.unshare.success.title": "Deling af session stoppet",
"toast.session.unshare.success.description": "Deling af session blev stoppet!",
"toast.session.unshare.failed.title": "Kunne ikke stoppe deling af session",
"toast.session.unshare.failed.description": "Der opstod en fejl under stop af sessionsdeling",
"toast.session.listFailed.title": "Kunne ikke indlæse sessioner for {{project}}",
"toast.update.title": "Opdatering tilgængelig",
"toast.update.description": "En ny version af OpenCode ({{version}}) er nu tilgængelig til installation.",
"toast.update.action.installRestart": "Installer og genstart",
"toast.update.action.notYet": "Ikke endnu",
"error.page.title": "Noget gik galt",
"error.page.description": "Der opstod en fejl under indlæsning af applikationen.",
"error.page.details.label": "Fejldetaljer",
"error.page.action.restart": "Genstart",
"error.page.action.checking": "Tjekker...",
"error.page.action.checkUpdates": "Tjek for opdateringer",
"error.page.action.updateTo": "Opdater til {{version}}",
"error.page.report.prefix": "Rapporter venligst denne fejl til OpenCode-teamet",
"error.page.report.discord": "på Discord",
"error.page.version": "Version: {{version}}",
"error.dev.rootNotFound":
"Rodelement ikke fundet. Har du glemt at tilføje det til din index.html? Eller måske er id-attributten stavet forkert?",
"error.globalSync.connectFailed": "Kunne ikke forbinde til server. Kører der en server på `{{url}}`?",
"error.chain.unknown": "Ukendt fejl",
"error.chain.causedBy": "Forårsaget af:",
"error.chain.apiError": "API-fejl",
"error.chain.status": "Status: {{status}}",
"error.chain.retryable": "Kan forsøges igen: {{retryable}}",
"error.chain.responseBody": "Svarindhold:\n{{body}}",
"error.chain.didYouMean": "Mente du: {{suggestions}}",
"error.chain.modelNotFound": "Model ikke fundet: {{provider}}/{{model}}",
"error.chain.checkConfig": "Tjek dine konfigurations (opencode.json) udbyder/modelnavne",
"error.chain.mcpFailed": 'MCP-server "{{name}}" fejlede. Bemærk, OpenCode understøtter ikke MCP-godkendelse endnu.',
"error.chain.providerAuthFailed": "Udbydergodkendelse mislykkedes ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'Kunne ikke initialisere udbyder "{{provider}}". Tjek legitimationsoplysninger og konfiguration.',
"error.chain.configJsonInvalid": "Konfigurationsfil på {{path}} er ikke gyldig JSON(C)",
"error.chain.configJsonInvalidWithMessage": "Konfigurationsfil på {{path}} er ikke gyldig JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'Mappe "{{dir}}" i {{path}} er ikke gyldig. Omdøb mappen til "{{suggestion}}" eller fjern den. Dette er en almindelig slåfejl.',
"error.chain.configFrontmatterError": "Kunne ikke parse frontmatter i {{path}}:\n{{message}}",
"error.chain.configInvalid": "Konfigurationsfil på {{path}} er ugyldig",
"error.chain.configInvalidWithMessage": "Konfigurationsfil på {{path}} er ugyldig: {{message}}",
"notification.permission.title": "Tilladelse påkrævet",
"notification.permission.description": "{{sessionTitle}} i {{projectName}} kræver tilladelse",
"notification.question.title": "Spørgsmål",
"notification.question.description": "{{sessionTitle}} i {{projectName}} har et spørgsmål",
"notification.action.goToSession": "Gå til session",
"notification.session.responseReady.title": "Svar klar",
"notification.session.error.title": "Sessionsfejl",
"notification.session.error.fallbackDescription": "Der opstod en fejl",
"home.recentProjects": "Seneste projekter",
"home.empty.title": "Ingen seneste projekter",
"home.empty.description": "Kom i gang ved at åbne et lokalt projekt",
"session.tab.session": "Session",
"session.tab.review": "Gennemgang",
"session.tab.context": "Kontekst",
"session.panel.reviewAndFiles": "Gennemgang og filer",
"session.review.filesChanged": "{{count}} Filer ændret",
"session.review.loadingChanges": "Indlæser ændringer...",
"session.review.empty": "Ingen ændringer i denne session endnu",
"session.messages.renderEarlier": "Vis tidligere beskeder",
"session.messages.loadingEarlier": "Indlæser tidligere beskeder...",
"session.messages.loadEarlier": "Indlæs tidligere beskeder",
"session.messages.loading": "Indlæser beskeder...",
"session.context.addToContext": "Tilføj {{selection}} til kontekst",
"session.new.worktree.main": "Hovedgren",
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
"session.new.worktree.create": "Opret nyt worktree",
"session.new.lastModified": "Sidst ændret",
"session.header.search.placeholder": "Søg {{project}}",
"session.header.searchFiles": "Søg efter filer",
"session.share.popover.title": "Udgiv på nettet",
"session.share.popover.description.shared":
"Denne session er offentlig på nettet. Den er tilgængelig for alle med linket.",
"session.share.popover.description.unshared":
"Del session offentligt på nettet. Den vil være tilgængelig for alle med linket.",
"session.share.action.share": "Del",
"session.share.action.publish": "Udgiv",
"session.share.action.publishing": "Udgiver...",
"session.share.action.unpublish": "Afpublicer",
"session.share.action.unpublishing": "Afpublicerer...",
"session.share.action.view": "Vis",
"session.share.copy.copied": "Kopieret",
"session.share.copy.copyLink": "Kopier link",
"lsp.tooltip.none": "Ingen LSP-servere",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Indlæser prompt...",
"terminal.loading": "Indlæser terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Luk terminal",
"common.closeTab": "Luk fane",
"common.dismiss": "Afvis",
"common.requestFailed": "Forespørgsel mislykkedes",
"common.moreOptions": "Flere muligheder",
"common.learnMore": "Lær mere",
"common.rename": "Omdøb",
"common.reset": "Nulstil",
"common.archive": "Arkivér",
"common.delete": "Slet",
"common.close": "Luk",
"common.edit": "Rediger",
"common.loadMore": "Indlæs flere",
"sidebar.nav.projectsAndSessions": "Projekter og sessioner",
"sidebar.settings": "Indstillinger",
"sidebar.help": "Hjælp",
"sidebar.workspaces.enable": "Aktiver arbejdsområder",
"sidebar.workspaces.disable": "Deaktiver arbejdsområder",
"sidebar.gettingStarted.title": "Kom i gang",
"sidebar.gettingStarted.line1": "OpenCode inkluderer gratis modeller så du kan starte med det samme.",
"sidebar.gettingStarted.line2": "Forbind enhver udbyder for at bruge modeller, inkl. Claude, GPT, Gemini osv.",
"sidebar.project.recentSessions": "Seneste sessioner",
"sidebar.project.viewAllSessions": "Vis alle sessioner",
"settings.section.desktop": "Desktop",
"settings.tab.general": "Generelt",
"settings.tab.shortcuts": "Genveje",
"settings.general.section.appearance": "Udseende",
"settings.general.section.notifications": "Systemmeddelelser",
"settings.general.section.sounds": "Lydeffekter",
"settings.general.row.language.title": "Sprog",
"settings.general.row.language.description": "Ændr visningssproget for OpenCode",
"settings.general.row.appearance.title": "Udseende",
"settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed",
"settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
"settings.general.row.font.title": "Skrifttype",
"settings.general.row.font.description": "Tilpas mono-skrifttypen brugt i kodeblokke",
"settings.general.notifications.agent.title": "Agent",
"settings.general.notifications.agent.description":
"Vis systemmeddelelse når agenten er færdig eller kræver opmærksomhed",
"settings.general.notifications.permissions.title": "Tilladelser",
"settings.general.notifications.permissions.description": "Vis systemmeddelelse når en tilladelse er påkrævet",
"settings.general.notifications.errors.title": "Fejl",
"settings.general.notifications.errors.description": "Vis systemmeddelelse når der opstår en fejl",
"settings.general.sounds.agent.title": "Agent",
"settings.general.sounds.agent.description": "Afspil lyd når agenten er færdig eller kræver opmærksomhed",
"settings.general.sounds.permissions.title": "Tilladelser",
"settings.general.sounds.permissions.description": "Afspil lyd når en tilladelse er påkrævet",
"settings.general.sounds.errors.title": "Fejl",
"settings.general.sounds.errors.description": "Afspil lyd når der opstår en fejl",
"settings.shortcuts.title": "Tastaturgenveje",
"settings.shortcuts.reset.button": "Nulstil til standard",
"settings.shortcuts.reset.toast.title": "Genveje nulstillet",
"settings.shortcuts.reset.toast.description": "Tastaturgenveje er blevet nulstillet til standard.",
"settings.shortcuts.conflict.title": "Genvej allerede i brug",
"settings.shortcuts.conflict.description": "{{keybind}} er allerede tildelt til {{titles}}.",
"settings.shortcuts.unassigned": "Ikke tildelt",
"settings.shortcuts.pressKeys": "Tryk på taster",
"settings.shortcuts.search.placeholder": "Søg genveje",
"settings.shortcuts.search.empty": "Ingen genveje fundet",
"settings.shortcuts.group.general": "Generelt",
"settings.shortcuts.group.session": "Session",
"settings.shortcuts.group.navigation": "Navigation",
"settings.shortcuts.group.modelAndAgent": "Model og agent",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Prompt",
"settings.providers.title": "Udbydere",
"settings.providers.description": "Udbyderindstillinger vil kunne konfigureres her.",
"settings.models.title": "Modeller",
"settings.models.description": "Modelindstillinger vil kunne konfigureres her.",
"settings.agents.title": "Agenter",
"settings.agents.description": "Agentindstillinger vil kunne konfigureres her.",
"settings.commands.title": "Kommandoer",
"settings.commands.description": "Kommandoindstillinger vil kunne konfigureres her.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP-indstillinger vil kunne konfigureres her.",
"settings.permissions.title": "Tilladelser",
"settings.permissions.description": "Styr hvilke værktøjer serveren kan bruge som standard.",
"settings.permissions.section.tools": "Værktøjer",
"settings.permissions.toast.updateFailed.title": "Kunne ikke opdatere tilladelser",
"settings.permissions.action.allow": "Tillad",
"settings.permissions.action.ask": "Spørg",
"settings.permissions.action.deny": "Afvis",
"settings.permissions.tool.read.title": "Læs",
"settings.permissions.tool.read.description": "Læsning af en fil (matcher filstien)",
"settings.permissions.tool.edit.title": "Rediger",
"settings.permissions.tool.edit.description":
"Ændre filer, herunder redigeringer, skrivninger, patches og multi-redigeringer",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Match filer ved hjælp af glob-mønstre",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Søg i filindhold ved hjælp af regulære udtryk",
"settings.permissions.tool.list.title": "Liste",
"settings.permissions.tool.list.description": "List filer i en mappe",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Kør shell-kommandoer",
"settings.permissions.tool.task.title": "Opgave",
"settings.permissions.tool.task.description": "Start underagenter",
"settings.permissions.tool.skill.title": "Færdighed",
"settings.permissions.tool.skill.description": "Indlæs en færdighed efter navn",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Kør sprogserverforespørgsler",
"settings.permissions.tool.todoread.title": "Læs To-do",
"settings.permissions.tool.todoread.description": "Læs to-do listen",
"settings.permissions.tool.todowrite.title": "Skriv To-do",
"settings.permissions.tool.todowrite.description": "Opdater to-do listen",
"settings.permissions.tool.webfetch.title": "Webhentning",
"settings.permissions.tool.webfetch.description": "Hent indhold fra en URL",
"settings.permissions.tool.websearch.title": "Websøgning",
"settings.permissions.tool.websearch.description": "Søg på nettet",
"settings.permissions.tool.codesearch.title": "Kodesøgning",
"settings.permissions.tool.codesearch.description": "Søg kode på nettet",
"settings.permissions.tool.external_directory.title": "Ekstern mappe",
"settings.permissions.tool.external_directory.description": "Få adgang til filer uden for projektmappen",
"settings.permissions.tool.doom_loop.title": "Doom Loop",
"settings.permissions.tool.doom_loop.description": "Opdag gentagne værktøjskald med identisk input",
"session.delete.failed.title": "Kunne ikke slette session",
"session.delete.title": "Slet session",
"session.delete.confirm": 'Slet session "{{name}}"?',
"session.delete.button": "Slet session",
"workspace.new": "Nyt arbejdsområde",
"workspace.type.local": "lokal",
"workspace.type.sandbox": "sandkasse",
"workspace.create.failed.title": "Kunne ikke oprette arbejdsområde",
"workspace.delete.failed.title": "Kunne ikke slette arbejdsområde",
"workspace.resetting.title": "Nulstiller arbejdsområde",
"workspace.resetting.description": "Dette kan tage et minut.",
"workspace.reset.failed.title": "Kunne ikke nulstille arbejdsområde",
"workspace.reset.success.title": "Arbejdsområde nulstillet",
"workspace.reset.success.description": "Arbejdsområdet matcher nu hovedgrenen.",
"workspace.status.checking": "Tjekker for uflettede ændringer...",
"workspace.status.error": "Kunne ikke bekræfte git-status.",
"workspace.status.clean": "Ingen uflettede ændringer fundet.",
"workspace.status.dirty": "Uflettede ændringer fundet i dette arbejdsområde.",
"workspace.delete.title": "Slet arbejdsområde",
"workspace.delete.confirm": 'Slet arbejdsområde "{{name}}"?',
"workspace.delete.button": "Slet arbejdsområde",
"workspace.reset.title": "Nulstil arbejdsområde",
"workspace.reset.confirm": 'Nulstil arbejdsområde "{{name}}"?',
"workspace.reset.button": "Nulstil arbejdsområde",
"workspace.reset.archived.none": "Ingen aktive sessioner vil blive arkiveret.",
"workspace.reset.archived.one": "1 session vil blive arkiveret.",
"workspace.reset.archived.many": "{{count}} sessioner vil blive arkiveret.",
"workspace.reset.note": "Dette vil nulstille arbejdsområdet til at matche hovedgrenen.",
}

591
packages/app/src/i18n/de.ts Normal file
View File

@@ -0,0 +1,591 @@
import { dict as en } from "./en"
type Keys = keyof typeof en
export const dict = {
"command.category.suggested": "Vorgeschlagen",
"command.category.view": "Ansicht",
"command.category.project": "Projekt",
"command.category.provider": "Anbieter",
"command.category.server": "Server",
"command.category.session": "Sitzung",
"command.category.theme": "Thema",
"command.category.language": "Sprache",
"command.category.file": "Datei",
"command.category.terminal": "Terminal",
"command.category.model": "Modell",
"command.category.mcp": "MCP",
"command.category.agent": "Agent",
"command.category.permissions": "Berechtigungen",
"command.category.workspace": "Arbeitsbereich",
"theme.scheme.system": "System",
"theme.scheme.light": "Hell",
"theme.scheme.dark": "Dunkel",
"command.sidebar.toggle": "Seitenleiste umschalten",
"command.project.open": "Projekt öffnen",
"command.provider.connect": "Anbieter verbinden",
"command.server.switch": "Server wechseln",
"command.session.previous": "Vorherige Sitzung",
"command.session.next": "Nächste Sitzung",
"command.session.archive": "Sitzung archivieren",
"command.palette": "Befehlspalette",
"command.theme.cycle": "Thema wechseln",
"command.theme.set": "Thema verwenden: {{theme}}",
"command.theme.scheme.cycle": "Farbschema wechseln",
"command.theme.scheme.set": "Farbschema verwenden: {{scheme}}",
"command.language.cycle": "Sprache wechseln",
"command.language.set": "Sprache verwenden: {{language}}",
"command.session.new": "Neue Sitzung",
"command.file.open": "Datei öffnen",
"command.file.open.description": "Dateien und Befehle durchsuchen",
"command.terminal.toggle": "Terminal umschalten",
"command.review.toggle": "Überprüfung umschalten",
"command.terminal.new": "Neues Terminal",
"command.terminal.new.description": "Neuen Terminal-Tab erstellen",
"command.steps.toggle": "Schritte umschalten",
"command.steps.toggle.description": "Schritte für die aktuelle Nachricht anzeigen oder ausblenden",
"command.message.previous": "Vorherige Nachricht",
"command.message.previous.description": "Zur vorherigen Benutzernachricht gehen",
"command.message.next": "Nächste Nachricht",
"command.message.next.description": "Zur nächsten Benutzernachricht gehen",
"command.model.choose": "Modell wählen",
"command.model.choose.description": "Ein anderes Modell auswählen",
"command.mcp.toggle": "MCPs umschalten",
"command.mcp.toggle.description": "MCPs umschalten",
"command.agent.cycle": "Agent wechseln",
"command.agent.cycle.description": "Zum nächsten Agenten wechseln",
"command.agent.cycle.reverse": "Agent rückwärts wechseln",
"command.agent.cycle.reverse.description": "Zum vorherigen Agenten wechseln",
"command.model.variant.cycle": "Denkaufwand wechseln",
"command.model.variant.cycle.description": "Zum nächsten Aufwandslevel wechseln",
"command.permissions.autoaccept.enable": "Änderungen automatisch akzeptieren",
"command.permissions.autoaccept.disable": "Automatische Annahme von Änderungen stoppen",
"command.session.undo": "Rückgängig",
"command.session.undo.description": "Letzte Nachricht rückgängig machen",
"command.session.redo": "Wiederherstellen",
"command.session.redo.description": "Letzte rückgängig gemachte Nachricht wiederherstellen",
"command.session.compact": "Sitzung komprimieren",
"command.session.compact.description": "Sitzung zusammenfassen, um die Kontextgröße zu reduzieren",
"command.session.fork": "Von Nachricht abzweigen",
"command.session.fork.description": "Neue Sitzung aus einer früheren Nachricht erstellen",
"command.session.share": "Sitzung teilen",
"command.session.share.description": "Diese Sitzung teilen und URL in die Zwischenablage kopieren",
"command.session.unshare": "Teilen der Sitzung aufheben",
"command.session.unshare.description": "Teilen dieser Sitzung beenden",
"palette.search.placeholder": "Dateien und Befehle durchsuchen",
"palette.empty": "Keine Ergebnisse gefunden",
"palette.group.commands": "Befehle",
"palette.group.files": "Dateien",
"dialog.provider.search.placeholder": "Anbieter durchsuchen",
"dialog.provider.empty": "Keine Anbieter gefunden",
"dialog.provider.group.popular": "Beliebt",
"dialog.provider.group.other": "Andere",
"dialog.provider.tag.recommended": "Empfohlen",
"dialog.provider.anthropic.note": "Mit Claude Pro/Max oder API-Schlüssel verbinden",
"dialog.provider.openai.note": "Mit ChatGPT Pro/Plus oder API-Schlüssel verbinden",
"dialog.provider.copilot.note": "Mit Copilot oder API-Schlüssel verbinden",
"dialog.model.select.title": "Modell auswählen",
"dialog.model.search.placeholder": "Modelle durchsuchen",
"dialog.model.empty": "Keine Modellergebnisse",
"dialog.model.manage": "Modelle verwalten",
"dialog.model.manage.description": "Anpassen, welche Modelle in der Modellauswahl erscheinen.",
"dialog.model.unpaid.freeModels.title": "Kostenlose Modelle von OpenCode",
"dialog.model.unpaid.addMore.title": "Weitere Modelle von beliebten Anbietern hinzufügen",
"dialog.provider.viewAll": "Alle Anbieter anzeigen",
"provider.connect.title": "{{provider}} verbinden",
"provider.connect.title.anthropicProMax": "Mit Claude Pro/Max anmelden",
"provider.connect.selectMethod": "Anmeldemethode für {{provider}} auswählen.",
"provider.connect.method.apiKey": "API-Schlüssel",
"provider.connect.status.inProgress": "Autorisierung läuft...",
"provider.connect.status.waiting": "Warten auf Autorisierung...",
"provider.connect.status.failed": "Autorisierung fehlgeschlagen: {{error}}",
"provider.connect.apiKey.description":
"Geben Sie Ihren {{provider}} API-Schlüssel ein, um Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.",
"provider.connect.apiKey.label": "{{provider}} API-Schlüssel",
"provider.connect.apiKey.placeholder": "API-Schlüssel",
"provider.connect.apiKey.required": "API-Schlüssel ist erforderlich",
"provider.connect.opencodeZen.line1":
"OpenCode Zen bietet Ihnen Zugriff auf eine kuratierte Auswahl zuverlässiger, optimierter Modelle für Coding-Agenten.",
"provider.connect.opencodeZen.line2":
"Mit einem einzigen API-Schlüssel erhalten Sie Zugriff auf Modelle wie Claude, GPT, Gemini, GLM und mehr.",
"provider.connect.opencodeZen.visit.prefix": "Besuchen Sie ",
"provider.connect.opencodeZen.visit.suffix": ", um Ihren API-Schlüssel zu erhalten.",
"provider.connect.oauth.code.visit.prefix": "Besuchen Sie ",
"provider.connect.oauth.code.visit.link": "diesen Link",
"provider.connect.oauth.code.visit.suffix":
", um Ihren Autorisierungscode zu erhalten, Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.",
"provider.connect.oauth.code.label": "{{method}} Autorisierungscode",
"provider.connect.oauth.code.placeholder": "Autorisierungscode",
"provider.connect.oauth.code.required": "Autorisierungscode ist erforderlich",
"provider.connect.oauth.code.invalid": "Ungültiger Autorisierungscode",
"provider.connect.oauth.auto.visit.prefix": "Besuchen Sie ",
"provider.connect.oauth.auto.visit.link": "diesen Link",
"provider.connect.oauth.auto.visit.suffix":
" und geben Sie den untenstehenden Code ein, um Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.",
"provider.connect.oauth.auto.confirmationCode": "Bestätigungscode",
"provider.connect.toast.connected.title": "{{provider}} verbunden",
"provider.connect.toast.connected.description": "{{provider}} Modelle sind jetzt verfügbar.",
"model.tag.free": "Kostenlos",
"model.tag.latest": "Neueste",
"common.search.placeholder": "Suchen",
"common.goBack": "Zurück",
"common.loading": "Laden",
"common.cancel": "Abbrechen",
"common.submit": "Absenden",
"common.save": "Speichern",
"common.saving": "Speichert...",
"common.default": "Standard",
"common.attachment": "Anhang",
"prompt.placeholder.shell": "Shell-Befehl eingeben...",
"prompt.placeholder.normal": 'Fragen Sie alles... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "esc zum Verlassen",
"prompt.example.1": "Ein TODO in der Codebasis beheben",
"prompt.example.2": "Was ist der Tech-Stack dieses Projekts?",
"prompt.example.3": "Fehlerhafte Tests beheben",
"prompt.example.4": "Erkläre, wie die Authentifizierung funktioniert",
"prompt.example.5": "Sicherheitslücken finden und beheben",
"prompt.example.6": "Unit-Tests für den Benutzerdienst hinzufügen",
"prompt.example.7": "Diese Funktion lesbarer gestalten",
"prompt.example.8": "Was bedeutet dieser Fehler?",
"prompt.example.9": "Hilf mir, dieses Problem zu debuggen",
"prompt.example.10": "API-Dokumentation generieren",
"prompt.example.11": "Datenbankabfragen optimieren",
"prompt.example.12": "Eingabevalidierung hinzufügen",
"prompt.example.13": "Neue Komponente erstellen für...",
"prompt.example.14": "Wie deploye ich dieses Projekt?",
"prompt.example.15": "Meinen Code auf Best Practices überprüfen",
"prompt.example.16": "Fehlerbehandlung zu dieser Funktion hinzufügen",
"prompt.example.17": "Erkläre dieses Regex-Muster",
"prompt.example.18": "Dies in TypeScript konvertieren",
"prompt.example.19": "Logging in der gesamten Codebasis hinzufügen",
"prompt.example.20": "Welche Abhängigkeiten sind veraltet?",
"prompt.example.21": "Hilf mir, ein Migrationsskript zu schreiben",
"prompt.example.22": "Caching für diesen Endpunkt implementieren",
"prompt.example.23": "Paginierung zu dieser Liste hinzufügen",
"prompt.example.24": "CLI-Befehl erstellen für...",
"prompt.example.25": "Wie funktionieren Umgebungsvariablen hier?",
"prompt.popover.emptyResults": "Keine passenden Ergebnisse",
"prompt.popover.emptyCommands": "Keine passenden Befehle",
"prompt.dropzone.label": "Bilder oder PDFs hier ablegen",
"prompt.slash.badge.custom": "benutzerdefiniert",
"prompt.context.active": "aktiv",
"prompt.context.includeActiveFile": "Aktive Datei einbeziehen",
"prompt.context.removeActiveFile": "Aktive Datei aus dem Kontext entfernen",
"prompt.context.removeFile": "Datei aus dem Kontext entfernen",
"prompt.action.attachFile": "Datei anhängen",
"prompt.attachment.remove": "Anhang entfernen",
"prompt.action.send": "Senden",
"prompt.action.stop": "Stopp",
"prompt.toast.pasteUnsupported.title": "Nicht unterstütztes Einfügen",
"prompt.toast.pasteUnsupported.description": "Hier können nur Bilder oder PDFs eingefügt werden.",
"prompt.toast.modelAgentRequired.title": "Wählen Sie einen Agenten und ein Modell",
"prompt.toast.modelAgentRequired.description":
"Wählen Sie einen Agenten und ein Modell, bevor Sie eine Eingabe senden.",
"prompt.toast.worktreeCreateFailed.title": "Worktree konnte nicht erstellt werden",
"prompt.toast.sessionCreateFailed.title": "Sitzung konnte nicht erstellt werden",
"prompt.toast.shellSendFailed.title": "Shell-Befehl konnte nicht gesendet werden",
"prompt.toast.commandSendFailed.title": "Befehl konnte nicht gesendet werden",
"prompt.toast.promptSendFailed.title": "Eingabe konnte nicht gesendet werden",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} von {{total}} aktiviert",
"dialog.mcp.empty": "Keine MCPs konfiguriert",
"mcp.status.connected": "verbunden",
"mcp.status.failed": "fehlgeschlagen",
"mcp.status.needs_auth": "benötigt Authentifizierung",
"mcp.status.disabled": "deaktiviert",
"dialog.fork.empty": "Keine Nachrichten zum Abzweigen vorhanden",
"dialog.directory.search.placeholder": "Ordner durchsuchen",
"dialog.directory.empty": "Keine Ordner gefunden",
"dialog.server.title": "Server",
"dialog.server.description": "Wechseln Sie den OpenCode-Server, mit dem sich diese App verbindet.",
"dialog.server.search.placeholder": "Server durchsuchen",
"dialog.server.empty": "Noch keine Server",
"dialog.server.add.title": "Server hinzufügen",
"dialog.server.add.url": "Server-URL",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Verbindung zum Server fehlgeschlagen",
"dialog.server.add.checking": "Prüfen...",
"dialog.server.add.button": "Hinzufügen",
"dialog.server.default.title": "Standardserver",
"dialog.server.default.description":
"Beim App-Start mit diesem Server verbinden, anstatt einen lokalen Server zu starten. Erfordert Neustart.",
"dialog.server.default.none": "Kein Server ausgewählt",
"dialog.server.default.set": "Aktuellen Server als Standard setzen",
"dialog.server.default.clear": "Löschen",
"dialog.server.action.remove": "Server entfernen",
"dialog.project.edit.title": "Projekt bearbeiten",
"dialog.project.edit.name": "Name",
"dialog.project.edit.icon": "Icon",
"dialog.project.edit.icon.alt": "Projekt-Icon",
"dialog.project.edit.icon.hint": "Klicken oder Bild ziehen",
"dialog.project.edit.icon.recommended": "Empfohlen: 128x128px",
"dialog.project.edit.color": "Farbe",
"dialog.project.edit.color.select": "{{color}}-Farbe auswählen",
"context.breakdown.title": "Kontext-Aufschlüsselung",
"context.breakdown.note":
'Ungefähre Aufschlüsselung der Eingabe-Token. "Andere" beinhaltet Werkzeugdefinitionen und Overhead.',
"context.breakdown.system": "System",
"context.breakdown.user": "Benutzer",
"context.breakdown.assistant": "Assistent",
"context.breakdown.tool": "Werkzeugaufrufe",
"context.breakdown.other": "Andere",
"context.systemPrompt.title": "System-Prompt",
"context.rawMessages.title": "Rohdaten der Nachrichten",
"context.stats.session": "Sitzung",
"context.stats.messages": "Nachrichten",
"context.stats.provider": "Anbieter",
"context.stats.model": "Modell",
"context.stats.limit": "Kontextlimit",
"context.stats.totalTokens": "Gesamt-Token",
"context.stats.usage": "Nutzung",
"context.stats.inputTokens": "Eingabe-Token",
"context.stats.outputTokens": "Ausgabe-Token",
"context.stats.reasoningTokens": "Reasoning-Token",
"context.stats.cacheTokens": "Cache-Token (lesen/schreiben)",
"context.stats.userMessages": "Benutzernachrichten",
"context.stats.assistantMessages": "Assistentennachrichten",
"context.stats.totalCost": "Gesamtkosten",
"context.stats.sessionCreated": "Sitzung erstellt",
"context.stats.lastActivity": "Letzte Aktivität",
"context.usage.tokens": "Token",
"context.usage.usage": "Nutzung",
"context.usage.cost": "Kosten",
"context.usage.clickToView": "Klicken, um Kontext anzuzeigen",
"context.usage.view": "Kontextnutzung anzeigen",
"language.en": "Englisch",
"language.zh": "Chinesisch (Vereinfacht)",
"language.zht": "Chinesisch (Traditionell)",
"language.ko": "Koreanisch",
"language.de": "Deutsch",
"language.es": "Spanisch",
"language.fr": "Französisch",
"language.ja": "Japanisch",
"language.da": "Dänisch",
"language.ru": "Russisch",
"language.pl": "Polnisch",
"language.ar": "Arabisch",
"language.no": "Norwegisch",
"language.br": "Portugiesisch (Brasilien)",
"toast.language.title": "Sprache",
"toast.language.description": "Zu {{language}} gewechselt",
"toast.theme.title": "Thema gewechselt",
"toast.scheme.title": "Farbschema",
"toast.permissions.autoaccept.on.title": "Änderungen werden automatisch akzeptiert",
"toast.permissions.autoaccept.on.description": "Bearbeitungs- und Schreibrechte werden automatisch genehmigt",
"toast.permissions.autoaccept.off.title": "Automatische Annahme von Änderungen gestoppt",
"toast.permissions.autoaccept.off.description": "Bearbeitungs- und Schreibrechte erfordern Genehmigung",
"toast.model.none.title": "Kein Modell ausgewählt",
"toast.model.none.description": "Verbinden Sie einen Anbieter, um diese Sitzung zusammenzufassen",
"toast.file.loadFailed.title": "Datei konnte nicht geladen werden",
"toast.session.share.copyFailed.title": "URL konnte nicht in die Zwischenablage kopiert werden",
"toast.session.share.success.title": "Sitzung geteilt",
"toast.session.share.success.description": "Teilen-URL in die Zwischenablage kopiert!",
"toast.session.share.failed.title": "Sitzung konnte nicht geteilt werden",
"toast.session.share.failed.description": "Beim Teilen der Sitzung ist ein Fehler aufgetreten",
"toast.session.unshare.success.title": "Teilen der Sitzung aufgehoben",
"toast.session.unshare.success.description": "Teilen der Sitzung erfolgreich aufgehoben!",
"toast.session.unshare.failed.title": "Aufheben des Teilens fehlgeschlagen",
"toast.session.unshare.failed.description": "Beim Aufheben des Teilens ist ein Fehler aufgetreten",
"toast.session.listFailed.title": "Sitzungen für {{project}} konnten nicht geladen werden",
"toast.update.title": "Update verfügbar",
"toast.update.description": "Eine neue Version von OpenCode ({{version}}) ist zur Installation verfügbar.",
"toast.update.action.installRestart": "Installieren und neu starten",
"toast.update.action.notYet": "Noch nicht",
"error.page.title": "Etwas ist schiefgelaufen",
"error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.",
"error.page.details.label": "Fehlerdetails",
"error.page.action.restart": "Neustart",
"error.page.action.checking": "Prüfen...",
"error.page.action.checkUpdates": "Nach Updates suchen",
"error.page.action.updateTo": "Auf {{version}} aktualisieren",
"error.page.report.prefix": "Bitte melden Sie diesen Fehler dem OpenCode-Team",
"error.page.report.discord": "auf Discord",
"error.page.version": "Version: {{version}}",
"error.dev.rootNotFound":
"Wurzelelement nicht gefunden. Haben Sie vergessen, es in Ihre index.html aufzunehmen? Oder wurde das id-Attribut falsch geschrieben?",
"error.globalSync.connectFailed": "Verbindung zum Server fehlgeschlagen. Läuft ein Server unter `{{url}}`?",
"error.chain.unknown": "Unbekannter Fehler",
"error.chain.causedBy": "Verursacht durch:",
"error.chain.apiError": "API-Fehler",
"error.chain.status": "Status: {{status}}",
"error.chain.retryable": "Wiederholbar: {{retryable}}",
"error.chain.responseBody": "Antwort-Body:\n{{body}}",
"error.chain.didYouMean": "Meinten Sie: {{suggestions}}",
"error.chain.modelNotFound": "Modell nicht gefunden: {{provider}}/{{model}}",
"error.chain.checkConfig": "Überprüfen Sie Ihre Konfiguration (opencode.json) auf Anbieter-/Modellnamen",
"error.chain.mcpFailed":
'MCP-Server "{{name}}" fehlgeschlagen. Hinweis: OpenCode unterstützt noch keine MCP-Authentifizierung.',
"error.chain.providerAuthFailed": "Anbieter-Authentifizierung fehlgeschlagen ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'Anbieter "{{provider}}" konnte nicht initialisiert werden. Überprüfen Sie Anmeldeinformationen und Konfiguration.',
"error.chain.configJsonInvalid": "Konfigurationsdatei unter {{path}} ist kein gültiges JSON(C)",
"error.chain.configJsonInvalidWithMessage":
"Konfigurationsdatei unter {{path}} ist kein gültiges JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'Verzeichnis "{{dir}}" in {{path}} ist ungültig. Benennen Sie das Verzeichnis in "{{suggestion}}" um oder entfernen Sie es. Dies ist ein häufiger Tippfehler.',
"error.chain.configFrontmatterError": "Frontmatter in {{path}} konnte nicht geparst werden:\n{{message}}",
"error.chain.configInvalid": "Konfigurationsdatei unter {{path}} ist ungültig",
"error.chain.configInvalidWithMessage": "Konfigurationsdatei unter {{path}} ist ungültig: {{message}}",
"notification.permission.title": "Berechtigung erforderlich",
"notification.permission.description": "{{sessionTitle}} in {{projectName}} benötigt Berechtigung",
"notification.question.title": "Frage",
"notification.question.description": "{{sessionTitle}} in {{projectName}} hat eine Frage",
"notification.action.goToSession": "Zur Sitzung gehen",
"notification.session.responseReady.title": "Antwort bereit",
"notification.session.error.title": "Sitzungsfehler",
"notification.session.error.fallbackDescription": "Ein Fehler ist aufgetreten",
"home.recentProjects": "Letzte Projekte",
"home.empty.title": "Keine letzten Projekte",
"home.empty.description": "Starten Sie, indem Sie ein lokales Projekt öffnen",
"session.tab.session": "Sitzung",
"session.tab.review": "Überprüfung",
"session.tab.context": "Kontext",
"session.panel.reviewAndFiles": "Überprüfung und Dateien",
"session.review.filesChanged": "{{count}} Dateien geändert",
"session.review.loadingChanges": "Lade Änderungen...",
"session.review.empty": "Noch keine Änderungen in dieser Sitzung",
"session.messages.renderEarlier": "Frühere Nachrichten rendern",
"session.messages.loadingEarlier": "Lade frühere Nachrichten...",
"session.messages.loadEarlier": "Frühere Nachrichten laden",
"session.messages.loading": "Lade Nachrichten...",
"session.context.addToContext": "{{selection}} zum Kontext hinzufügen",
"session.new.worktree.main": "Haupt-Branch",
"session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
"session.new.worktree.create": "Neuen Worktree erstellen",
"session.new.lastModified": "Zuletzt geändert",
"session.header.search.placeholder": "{{project}} durchsuchen",
"session.header.searchFiles": "Dateien suchen",
"session.share.popover.title": "Im Web veröffentlichen",
"session.share.popover.description.shared":
"Diese Sitzung ist öffentlich im Web. Sie ist für jeden mit dem Link zugänglich.",
"session.share.popover.description.unshared":
"Sitzung öffentlich im Web teilen. Sie wird für jeden mit dem Link zugänglich sein.",
"session.share.action.share": "Teilen",
"session.share.action.publish": "Veröffentlichen",
"session.share.action.publishing": "Veröffentliche...",
"session.share.action.unpublish": "Veröffentlichung aufheben",
"session.share.action.unpublishing": "Hebe Veröffentlichung auf...",
"session.share.action.view": "Ansehen",
"session.share.copy.copied": "Kopiert",
"session.share.copy.copyLink": "Link kopieren",
"lsp.tooltip.none": "Keine LSP-Server",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Lade Prompt...",
"terminal.loading": "Lade Terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Terminal schließen",
"common.closeTab": "Tab schließen",
"common.dismiss": "Verwerfen",
"common.requestFailed": "Anfrage fehlgeschlagen",
"common.moreOptions": "Weitere Optionen",
"common.learnMore": "Mehr erfahren",
"common.rename": "Umbenennen",
"common.reset": "Zurücksetzen",
"common.archive": "Archivieren",
"common.delete": "Löschen",
"common.close": "Schließen",
"common.edit": "Bearbeiten",
"common.loadMore": "Mehr laden",
"sidebar.nav.projectsAndSessions": "Projekte und Sitzungen",
"sidebar.settings": "Einstellungen",
"sidebar.help": "Hilfe",
"sidebar.workspaces.enable": "Arbeitsbereiche aktivieren",
"sidebar.workspaces.disable": "Arbeitsbereiche deaktivieren",
"sidebar.gettingStarted.title": "Erste Schritte",
"sidebar.gettingStarted.line1": "OpenCode enthält kostenlose Modelle, damit Sie sofort loslegen können.",
"sidebar.gettingStarted.line2":
"Verbinden Sie einen beliebigen Anbieter, um Modelle wie Claude, GPT, Gemini usw. zu nutzen.",
"sidebar.project.recentSessions": "Letzte Sitzungen",
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
"settings.section.desktop": "Desktop",
"settings.tab.general": "Allgemein",
"settings.tab.shortcuts": "Tastenkombinationen",
"settings.general.section.appearance": "Erscheinungsbild",
"settings.general.section.notifications": "Systembenachrichtigungen",
"settings.general.section.sounds": "Soundeffekte",
"settings.general.row.language.title": "Sprache",
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
"settings.general.row.appearance.title": "Erscheinungsbild",
"settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht",
"settings.general.row.theme.title": "Thema",
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
"settings.general.row.font.title": "Schriftart",
"settings.general.row.font.description": "Die in Codeblöcken verwendete Monospace-Schriftart anpassen",
"settings.general.notifications.agent.title": "Agent",
"settings.general.notifications.agent.description":
"Systembenachrichtigung anzeigen, wenn der Agent fertig ist oder Aufmerksamkeit benötigt",
"settings.general.notifications.permissions.title": "Berechtigungen",
"settings.general.notifications.permissions.description":
"Systembenachrichtigung anzeigen, wenn eine Berechtigung erforderlich ist",
"settings.general.notifications.errors.title": "Fehler",
"settings.general.notifications.errors.description": "Systembenachrichtigung anzeigen, wenn ein Fehler auftritt",
"settings.general.sounds.agent.title": "Agent",
"settings.general.sounds.agent.description": "Ton abspielen, wenn der Agent fertig ist oder Aufmerksamkeit benötigt",
"settings.general.sounds.permissions.title": "Berechtigungen",
"settings.general.sounds.permissions.description": "Ton abspielen, wenn eine Berechtigung erforderlich ist",
"settings.general.sounds.errors.title": "Fehler",
"settings.general.sounds.errors.description": "Ton abspielen, wenn ein Fehler auftritt",
"settings.shortcuts.title": "Tastenkombinationen",
"settings.shortcuts.reset.button": "Auf Standard zurücksetzen",
"settings.shortcuts.reset.toast.title": "Tastenkombinationen zurückgesetzt",
"settings.shortcuts.reset.toast.description": "Die Tastenkombinationen wurden auf die Standardwerte zurückgesetzt.",
"settings.shortcuts.conflict.title": "Tastenkombination bereits in Verwendung",
"settings.shortcuts.conflict.description": "{{keybind}} ist bereits {{titles}} zugewiesen.",
"settings.shortcuts.unassigned": "Nicht zugewiesen",
"settings.shortcuts.pressKeys": "Tasten drücken",
"settings.shortcuts.search.placeholder": "Tastenkürzel suchen",
"settings.shortcuts.search.empty": "Keine Tastenkürzel gefunden",
"settings.shortcuts.group.general": "Allgemein",
"settings.shortcuts.group.session": "Sitzung",
"settings.shortcuts.group.navigation": "Navigation",
"settings.shortcuts.group.modelAndAgent": "Modell und Agent",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Prompt",
"settings.providers.title": "Anbieter",
"settings.providers.description": "Anbietereinstellungen können hier konfiguriert werden.",
"settings.models.title": "Modelle",
"settings.models.description": "Modelleinstellungen können hier konfiguriert werden.",
"settings.agents.title": "Agenten",
"settings.agents.description": "Agenteneinstellungen können hier konfiguriert werden.",
"settings.commands.title": "Befehle",
"settings.commands.description": "Befehlseinstellungen können hier konfiguriert werden.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP-Einstellungen können hier konfiguriert werden.",
"settings.permissions.title": "Berechtigungen",
"settings.permissions.description": "Steuern Sie, welche Tools der Server standardmäßig verwenden darf.",
"settings.permissions.section.tools": "Tools",
"settings.permissions.toast.updateFailed.title": "Berechtigungen konnten nicht aktualisiert werden",
"settings.permissions.action.allow": "Erlauben",
"settings.permissions.action.ask": "Fragen",
"settings.permissions.action.deny": "Verweigern",
"settings.permissions.tool.read.title": "Lesen",
"settings.permissions.tool.read.description": "Lesen einer Datei (stimmt mit dem Dateipfad überein)",
"settings.permissions.tool.edit.title": "Bearbeiten",
"settings.permissions.tool.edit.description":
"Dateien ändern, einschließlich Bearbeitungen, Schreibvorgängen, Patches und Mehrfachbearbeitungen",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Dateien mithilfe von Glob-Mustern abgleichen",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Dateiinhalte mit regulären Ausdrücken durchsuchen",
"settings.permissions.tool.list.title": "Auflisten",
"settings.permissions.tool.list.description": "Dateien in einem Verzeichnis auflisten",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Shell-Befehle ausführen",
"settings.permissions.tool.task.title": "Aufgabe",
"settings.permissions.tool.task.description": "Unteragenten starten",
"settings.permissions.tool.skill.title": "Fähigkeit",
"settings.permissions.tool.skill.description": "Eine Fähigkeit nach Namen laden",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Language-Server-Abfragen ausführen",
"settings.permissions.tool.todoread.title": "Todo lesen",
"settings.permissions.tool.todoread.description": "Die Todo-Liste lesen",
"settings.permissions.tool.todowrite.title": "Todo schreiben",
"settings.permissions.tool.todowrite.description": "Die Todo-Liste aktualisieren",
"settings.permissions.tool.webfetch.title": "Web-Abruf",
"settings.permissions.tool.webfetch.description": "Inhalt von einer URL abrufen",
"settings.permissions.tool.websearch.title": "Web-Suche",
"settings.permissions.tool.websearch.description": "Das Web durchsuchen",
"settings.permissions.tool.codesearch.title": "Code-Suche",
"settings.permissions.tool.codesearch.description": "Code im Web durchsuchen",
"settings.permissions.tool.external_directory.title": "Externes Verzeichnis",
"settings.permissions.tool.external_directory.description": "Zugriff auf Dateien außerhalb des Projektverzeichnisses",
"settings.permissions.tool.doom_loop.title": "Doom Loop",
"settings.permissions.tool.doom_loop.description": "Wiederholte Tool-Aufrufe mit identischer Eingabe erkennen",
"session.delete.failed.title": "Sitzung konnte nicht gelöscht werden",
"session.delete.title": "Sitzung löschen",
"session.delete.confirm": 'Sitzung "{{name}}" löschen?',
"session.delete.button": "Sitzung löschen",
"workspace.new": "Neuer Arbeitsbereich",
"workspace.type.local": "lokal",
"workspace.type.sandbox": "Sandbox",
"workspace.create.failed.title": "Arbeitsbereich konnte nicht erstellt werden",
"workspace.delete.failed.title": "Arbeitsbereich konnte nicht gelöscht werden",
"workspace.resetting.title": "Arbeitsbereich wird zurückgesetzt",
"workspace.resetting.description": "Dies kann eine Minute dauern.",
"workspace.reset.failed.title": "Arbeitsbereich konnte nicht zurückgesetzt werden",
"workspace.reset.success.title": "Arbeitsbereich zurückgesetzt",
"workspace.reset.success.description": "Der Arbeitsbereich entspricht jetzt dem Standard-Branch.",
"workspace.status.checking": "Suche nach nicht zusammengeführten Änderungen...",
"workspace.status.error": "Git-Status konnte nicht überprüft werden.",
"workspace.status.clean": "Keine nicht zusammengeführten Änderungen erkannt.",
"workspace.status.dirty": "Nicht zusammengeführte Änderungen in diesem Arbeitsbereich erkannt.",
"workspace.delete.title": "Arbeitsbereich löschen",
"workspace.delete.confirm": 'Arbeitsbereich "{{name}}" löschen?',
"workspace.delete.button": "Arbeitsbereich löschen",
"workspace.reset.title": "Arbeitsbereich zurücksetzen",
"workspace.reset.confirm": 'Arbeitsbereich "{{name}}" zurücksetzen?',
"workspace.reset.button": "Arbeitsbereich zurücksetzen",
"workspace.reset.archived.none": "Keine aktiven Sitzungen werden archiviert.",
"workspace.reset.archived.one": "1 Sitzung wird archiviert.",
"workspace.reset.archived.many": "{{count}} Sitzungen werden archiviert.",
"workspace.reset.note": "Dadurch wird der Arbeitsbereich auf den Standard-Branch zurückgesetzt.",
} satisfies Partial<Record<Keys, string>>

663
packages/app/src/i18n/en.ts Normal file
View File

@@ -0,0 +1,663 @@
export const dict = {
"command.category.suggested": "Suggested",
"command.category.view": "View",
"command.category.project": "Project",
"command.category.provider": "Provider",
"command.category.server": "Server",
"command.category.session": "Session",
"command.category.theme": "Theme",
"command.category.language": "Language",
"command.category.file": "File",
"command.category.terminal": "Terminal",
"command.category.model": "Model",
"command.category.mcp": "MCP",
"command.category.agent": "Agent",
"command.category.permissions": "Permissions",
"command.category.workspace": "Workspace",
"command.category.settings": "Settings",
"theme.scheme.system": "System",
"theme.scheme.light": "Light",
"theme.scheme.dark": "Dark",
"command.sidebar.toggle": "Toggle sidebar",
"command.project.open": "Open project",
"command.provider.connect": "Connect provider",
"command.server.switch": "Switch server",
"command.settings.open": "Open settings",
"command.session.previous": "Previous session",
"command.session.next": "Next session",
"command.session.archive": "Archive session",
"command.palette": "Command palette",
"command.theme.cycle": "Cycle theme",
"command.theme.set": "Use theme: {{theme}}",
"command.theme.scheme.cycle": "Cycle color scheme",
"command.theme.scheme.set": "Use color scheme: {{scheme}}",
"command.language.cycle": "Cycle language",
"command.language.set": "Use language: {{language}}",
"command.session.new": "New session",
"command.file.open": "Open file",
"command.file.open.description": "Search files and commands",
"command.terminal.toggle": "Toggle terminal",
"command.review.toggle": "Toggle review",
"command.terminal.new": "New terminal",
"command.terminal.new.description": "Create a new terminal tab",
"command.steps.toggle": "Toggle steps",
"command.steps.toggle.description": "Show or hide steps for the current message",
"command.message.previous": "Previous message",
"command.message.previous.description": "Go to the previous user message",
"command.message.next": "Next message",
"command.message.next.description": "Go to the next user message",
"command.model.choose": "Choose model",
"command.model.choose.description": "Select a different model",
"command.mcp.toggle": "Toggle MCPs",
"command.mcp.toggle.description": "Toggle MCPs",
"command.agent.cycle": "Cycle agent",
"command.agent.cycle.description": "Switch to the next agent",
"command.agent.cycle.reverse": "Cycle agent backwards",
"command.agent.cycle.reverse.description": "Switch to the previous agent",
"command.model.variant.cycle": "Cycle thinking effort",
"command.model.variant.cycle.description": "Switch to the next effort level",
"command.permissions.autoaccept.enable": "Auto-accept edits",
"command.permissions.autoaccept.disable": "Stop auto-accepting edits",
"command.session.undo": "Undo",
"command.session.undo.description": "Undo the last message",
"command.session.redo": "Redo",
"command.session.redo.description": "Redo the last undone message",
"command.session.compact": "Compact session",
"command.session.compact.description": "Summarize the session to reduce context size",
"command.session.fork": "Fork from message",
"command.session.fork.description": "Create a new session from a previous message",
"command.session.share": "Share session",
"command.session.share.description": "Share this session and copy the URL to clipboard",
"command.session.unshare": "Unshare session",
"command.session.unshare.description": "Stop sharing this session",
"palette.search.placeholder": "Search files and commands",
"palette.empty": "No results found",
"palette.group.commands": "Commands",
"palette.group.files": "Files",
"dialog.provider.search.placeholder": "Search providers",
"dialog.provider.empty": "No providers found",
"dialog.provider.group.popular": "Popular",
"dialog.provider.group.other": "Other",
"dialog.provider.tag.recommended": "Recommended",
"dialog.provider.anthropic.note": "Connect with Claude Pro/Max or API key",
"dialog.provider.openai.note": "Connect with ChatGPT Pro/Plus or API key",
"dialog.provider.copilot.note": "Connect with Copilot or API key",
"dialog.model.select.title": "Select model",
"dialog.model.search.placeholder": "Search models",
"dialog.model.empty": "No model results",
"dialog.model.manage": "Manage models",
"dialog.model.manage.description": "Customize which models appear in the model selector.",
"dialog.model.unpaid.freeModels.title": "Free models provided by OpenCode",
"dialog.model.unpaid.addMore.title": "Add more models from popular providers",
"dialog.provider.viewAll": "View all providers",
"provider.connect.title": "Connect {{provider}}",
"provider.connect.title.anthropicProMax": "Login with Claude Pro/Max",
"provider.connect.selectMethod": "Select login method for {{provider}}.",
"provider.connect.method.apiKey": "API key",
"provider.connect.status.inProgress": "Authorization in progress...",
"provider.connect.status.waiting": "Waiting for authorization...",
"provider.connect.status.failed": "Authorization failed: {{error}}",
"provider.connect.apiKey.description":
"Enter your {{provider}} API key to connect your account and use {{provider}} models in OpenCode.",
"provider.connect.apiKey.label": "{{provider}} API key",
"provider.connect.apiKey.placeholder": "API key",
"provider.connect.apiKey.required": "API key is required",
"provider.connect.opencodeZen.line1":
"OpenCode Zen gives you access to a curated set of reliable optimized models for coding agents.",
"provider.connect.opencodeZen.line2":
"With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more.",
"provider.connect.opencodeZen.visit.prefix": "Visit ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " to collect your API key.",
"provider.connect.oauth.code.visit.prefix": "Visit ",
"provider.connect.oauth.code.visit.link": "this link",
"provider.connect.oauth.code.visit.suffix":
" to collect your authorization code to connect your account and use {{provider}} models in OpenCode.",
"provider.connect.oauth.code.label": "{{method}} authorization code",
"provider.connect.oauth.code.placeholder": "Authorization code",
"provider.connect.oauth.code.required": "Authorization code is required",
"provider.connect.oauth.code.invalid": "Invalid authorization code",
"provider.connect.oauth.auto.visit.prefix": "Visit ",
"provider.connect.oauth.auto.visit.link": "this link",
"provider.connect.oauth.auto.visit.suffix":
" and enter the code below to connect your account and use {{provider}} models in OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Confirmation code",
"provider.connect.toast.connected.title": "{{provider}} connected",
"provider.connect.toast.connected.description": "{{provider}} models are now available to use.",
"model.tag.free": "Free",
"model.tag.latest": "Latest",
"model.provider.anthropic": "Anthropic",
"model.provider.openai": "OpenAI",
"model.provider.google": "Google",
"model.provider.xai": "xAI",
"model.provider.meta": "Meta",
"model.input.text": "text",
"model.input.image": "image",
"model.input.audio": "audio",
"model.input.video": "video",
"model.input.pdf": "pdf",
"model.tooltip.allows": "Allows: {{inputs}}",
"model.tooltip.reasoning.allowed": "Allows reasoning",
"model.tooltip.reasoning.none": "No reasoning",
"model.tooltip.context": "Context limit {{limit}}",
"common.search.placeholder": "Search",
"common.goBack": "Go back",
"common.loading": "Loading",
"common.loading.ellipsis": "...",
"common.cancel": "Cancel",
"common.submit": "Submit",
"common.save": "Save",
"common.saving": "Saving...",
"common.default": "Default",
"common.attachment": "attachment",
"prompt.placeholder.shell": "Enter shell command...",
"prompt.placeholder.normal": 'Ask anything... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "esc to exit",
"prompt.example.1": "Fix a TODO in the codebase",
"prompt.example.2": "What is the tech stack of this project?",
"prompt.example.3": "Fix broken tests",
"prompt.example.4": "Explain how authentication works",
"prompt.example.5": "Find and fix security vulnerabilities",
"prompt.example.6": "Add unit tests for the user service",
"prompt.example.7": "Refactor this function to be more readable",
"prompt.example.8": "What does this error mean?",
"prompt.example.9": "Help me debug this issue",
"prompt.example.10": "Generate API documentation",
"prompt.example.11": "Optimize database queries",
"prompt.example.12": "Add input validation",
"prompt.example.13": "Create a new component for...",
"prompt.example.14": "How do I deploy this project?",
"prompt.example.15": "Review my code for best practices",
"prompt.example.16": "Add error handling to this function",
"prompt.example.17": "Explain this regex pattern",
"prompt.example.18": "Convert this to TypeScript",
"prompt.example.19": "Add logging throughout the codebase",
"prompt.example.20": "What dependencies are outdated?",
"prompt.example.21": "Help me write a migration script",
"prompt.example.22": "Implement caching for this endpoint",
"prompt.example.23": "Add pagination to this list",
"prompt.example.24": "Create a CLI command for...",
"prompt.example.25": "How do environment variables work here?",
"prompt.popover.emptyResults": "No matching results",
"prompt.popover.emptyCommands": "No matching commands",
"prompt.dropzone.label": "Drop images or PDFs here",
"prompt.slash.badge.custom": "custom",
"prompt.context.active": "active",
"prompt.context.includeActiveFile": "Include active file",
"prompt.context.removeActiveFile": "Remove active file from context",
"prompt.context.removeFile": "Remove file from context",
"prompt.action.attachFile": "Attach file",
"prompt.attachment.remove": "Remove attachment",
"prompt.action.send": "Send",
"prompt.action.stop": "Stop",
"prompt.toast.pasteUnsupported.title": "Unsupported paste",
"prompt.toast.pasteUnsupported.description": "Only images or PDFs can be pasted here.",
"prompt.toast.modelAgentRequired.title": "Select an agent and model",
"prompt.toast.modelAgentRequired.description": "Choose an agent and model before sending a prompt.",
"prompt.toast.worktreeCreateFailed.title": "Failed to create worktree",
"prompt.toast.sessionCreateFailed.title": "Failed to create session",
"prompt.toast.shellSendFailed.title": "Failed to send shell command",
"prompt.toast.commandSendFailed.title": "Failed to send command",
"prompt.toast.promptSendFailed.title": "Failed to send prompt",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} of {{total}} enabled",
"dialog.mcp.empty": "No MCPs configured",
"mcp.status.connected": "connected",
"mcp.status.failed": "failed",
"mcp.status.needs_auth": "needs auth",
"mcp.status.disabled": "disabled",
"dialog.fork.empty": "No messages to fork from",
"dialog.directory.search.placeholder": "Search folders",
"dialog.directory.empty": "No folders found",
"dialog.server.title": "Servers",
"dialog.server.description": "Switch which OpenCode server this app connects to.",
"dialog.server.search.placeholder": "Search servers",
"dialog.server.empty": "No servers yet",
"dialog.server.add.title": "Add a server",
"dialog.server.add.url": "Server URL",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Could not connect to server",
"dialog.server.add.checking": "Checking...",
"dialog.server.add.button": "Add",
"dialog.server.default.title": "Default server",
"dialog.server.default.description":
"Connect to this server on app launch instead of starting a local server. Requires restart.",
"dialog.server.default.none": "No server selected",
"dialog.server.default.set": "Set current server as default",
"dialog.server.default.clear": "Clear",
"dialog.server.action.remove": "Remove server",
"dialog.project.edit.title": "Edit project",
"dialog.project.edit.name": "Name",
"dialog.project.edit.icon": "Icon",
"dialog.project.edit.icon.alt": "Project icon",
"dialog.project.edit.icon.hint": "Click or drag an image",
"dialog.project.edit.icon.recommended": "Recommended: 128x128px",
"dialog.project.edit.color": "Color",
"dialog.project.edit.color.select": "Select {{color}} color",
"dialog.project.edit.worktree.startup": "Workspace startup script",
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "e.g. bun install",
"context.breakdown.title": "Context Breakdown",
"context.breakdown.note": 'Approximate breakdown of input tokens. "Other" includes tool definitions and overhead.',
"context.breakdown.system": "System",
"context.breakdown.user": "User",
"context.breakdown.assistant": "Assistant",
"context.breakdown.tool": "Tool Calls",
"context.breakdown.other": "Other",
"context.systemPrompt.title": "System Prompt",
"context.rawMessages.title": "Raw messages",
"context.stats.session": "Session",
"context.stats.messages": "Messages",
"context.stats.provider": "Provider",
"context.stats.model": "Model",
"context.stats.limit": "Context Limit",
"context.stats.totalTokens": "Total Tokens",
"context.stats.usage": "Usage",
"context.stats.inputTokens": "Input Tokens",
"context.stats.outputTokens": "Output Tokens",
"context.stats.reasoningTokens": "Reasoning Tokens",
"context.stats.cacheTokens": "Cache Tokens (read/write)",
"context.stats.userMessages": "User Messages",
"context.stats.assistantMessages": "Assistant Messages",
"context.stats.totalCost": "Total Cost",
"context.stats.sessionCreated": "Session Created",
"context.stats.lastActivity": "Last Activity",
"context.usage.tokens": "Tokens",
"context.usage.usage": "Usage",
"context.usage.cost": "Cost",
"context.usage.clickToView": "Click to view context",
"context.usage.view": "View context usage",
"language.en": "English",
"language.zh": "Chinese (Simplified)",
"language.zht": "Chinese (Traditional)",
"language.ko": "Korean",
"language.de": "German",
"language.es": "Spanish",
"language.fr": "French",
"language.ja": "Japanese",
"language.da": "Danish",
"language.ru": "Russian",
"language.pl": "Polish",
"language.ar": "Arabic",
"language.no": "Norwegian",
"language.br": "Portuguese (Brazil)",
"toast.language.title": "Language",
"toast.language.description": "Switched to {{language}}",
"toast.theme.title": "Theme switched",
"toast.scheme.title": "Color scheme",
"toast.permissions.autoaccept.on.title": "Auto-accepting edits",
"toast.permissions.autoaccept.on.description": "Edit and write permissions will be automatically approved",
"toast.permissions.autoaccept.off.title": "Stopped auto-accepting edits",
"toast.permissions.autoaccept.off.description": "Edit and write permissions will require approval",
"toast.model.none.title": "No model selected",
"toast.model.none.description": "Connect a provider to summarize this session",
"toast.file.loadFailed.title": "Failed to load file",
"toast.session.share.copyFailed.title": "Failed to copy URL to clipboard",
"toast.session.share.success.title": "Session shared",
"toast.session.share.success.description": "Share URL copied to clipboard!",
"toast.session.share.failed.title": "Failed to share session",
"toast.session.share.failed.description": "An error occurred while sharing the session",
"toast.session.unshare.success.title": "Session unshared",
"toast.session.unshare.success.description": "Session unshared successfully!",
"toast.session.unshare.failed.title": "Failed to unshare session",
"toast.session.unshare.failed.description": "An error occurred while unsharing the session",
"toast.session.listFailed.title": "Failed to load sessions for {{project}}",
"toast.update.title": "Update available",
"toast.update.description": "A new version of OpenCode ({{version}}) is now available to install.",
"toast.update.action.installRestart": "Install and restart",
"toast.update.action.notYet": "Not yet",
"error.page.title": "Something went wrong",
"error.page.description": "An error occurred while loading the application.",
"error.page.details.label": "Error Details",
"error.page.action.restart": "Restart",
"error.page.action.checking": "Checking...",
"error.page.action.checkUpdates": "Check for updates",
"error.page.action.updateTo": "Update to {{version}}",
"error.page.report.prefix": "Please report this error to the OpenCode team",
"error.page.report.discord": "on Discord",
"error.page.version": "Version: {{version}}",
"error.dev.rootNotFound":
"Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
"error.globalSync.connectFailed": "Could not connect to server. Is there a server running at `{{url}}`?",
"error.chain.unknown": "Unknown error",
"error.chain.causedBy": "Caused by:",
"error.chain.apiError": "API error",
"error.chain.status": "Status: {{status}}",
"error.chain.retryable": "Retryable: {{retryable}}",
"error.chain.responseBody": "Response body:\n{{body}}",
"error.chain.didYouMean": "Did you mean: {{suggestions}}",
"error.chain.modelNotFound": "Model not found: {{provider}}/{{model}}",
"error.chain.checkConfig": "Check your config (opencode.json) provider/model names",
"error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.',
"error.chain.providerAuthFailed": "Provider authentication failed ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'Failed to initialize provider "{{provider}}". Check credentials and configuration.',
"error.chain.configJsonInvalid": "Config file at {{path}} is not valid JSON(C)",
"error.chain.configJsonInvalidWithMessage": "Config file at {{path}} is not valid JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'Directory "{{dir}}" in {{path}} is not valid. Rename the directory to "{{suggestion}}" or remove it. This is a common typo.',
"error.chain.configFrontmatterError": "Failed to parse frontmatter in {{path}}:\n{{message}}",
"error.chain.configInvalid": "Config file at {{path}} is invalid",
"error.chain.configInvalidWithMessage": "Config file at {{path}} is invalid: {{message}}",
"notification.permission.title": "Permission required",
"notification.permission.description": "{{sessionTitle}} in {{projectName}} needs permission",
"notification.question.title": "Question",
"notification.question.description": "{{sessionTitle}} in {{projectName}} has a question",
"notification.action.goToSession": "Go to session",
"notification.session.responseReady.title": "Response ready",
"notification.session.error.title": "Session error",
"notification.session.error.fallbackDescription": "An error occurred",
"home.recentProjects": "Recent projects",
"home.empty.title": "No recent projects",
"home.empty.description": "Get started by opening a local project",
"session.tab.session": "Session",
"session.tab.review": "Review",
"session.tab.context": "Context",
"session.panel.reviewAndFiles": "Review and files",
"session.review.filesChanged": "{{count}} Files Changed",
"session.review.loadingChanges": "Loading changes...",
"session.review.empty": "No changes in this session yet",
"session.messages.renderEarlier": "Render earlier messages",
"session.messages.loadingEarlier": "Loading earlier messages...",
"session.messages.loadEarlier": "Load earlier messages",
"session.messages.loading": "Loading messages...",
"session.messages.jumpToLatest": "Jump to latest",
"session.context.addToContext": "Add {{selection}} to context",
"session.new.worktree.main": "Main branch",
"session.new.worktree.mainWithBranch": "Main branch ({{branch}})",
"session.new.worktree.create": "Create new worktree",
"session.new.lastModified": "Last modified",
"session.header.search.placeholder": "Search {{project}}",
"session.header.searchFiles": "Search files",
"session.share.popover.title": "Publish on web",
"session.share.popover.description.shared":
"This session is public on the web. It is accessible to anyone with the link.",
"session.share.popover.description.unshared":
"Share session publicly on the web. It will be accessible to anyone with the link.",
"session.share.action.share": "Share",
"session.share.action.publish": "Publish",
"session.share.action.publishing": "Publishing...",
"session.share.action.unpublish": "Unpublish",
"session.share.action.unpublishing": "Unpublishing...",
"session.share.action.view": "View",
"session.share.copy.copied": "Copied",
"session.share.copy.copyLink": "Copy link",
"lsp.tooltip.none": "No LSP servers",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Loading prompt...",
"terminal.loading": "Loading terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Close terminal",
"terminal.connectionLost.title": "Connection Lost",
"terminal.connectionLost.description":
"The terminal connection was interrupted. This can happen when the server restarts.",
"common.closeTab": "Close tab",
"common.dismiss": "Dismiss",
"common.requestFailed": "Request failed",
"common.moreOptions": "More options",
"common.learnMore": "Learn more",
"common.rename": "Rename",
"common.reset": "Reset",
"common.archive": "Archive",
"common.delete": "Delete",
"common.close": "Close",
"common.edit": "Edit",
"common.loadMore": "Load more",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Toggle menu",
"sidebar.nav.projectsAndSessions": "Projects and sessions",
"sidebar.settings": "Settings",
"sidebar.help": "Help",
"sidebar.workspaces.enable": "Enable workspaces",
"sidebar.workspaces.disable": "Disable workspaces",
"sidebar.gettingStarted.title": "Getting started",
"sidebar.gettingStarted.line1": "OpenCode includes free models so you can start immediately.",
"sidebar.gettingStarted.line2": "Connect any provider to use models, inc. Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Recent sessions",
"sidebar.project.viewAllSessions": "View all sessions",
"settings.section.desktop": "Desktop",
"settings.tab.general": "General",
"settings.tab.shortcuts": "Shortcuts",
"settings.general.section.appearance": "Appearance",
"settings.general.section.notifications": "System notifications",
"settings.general.section.sounds": "Sound effects",
"settings.general.row.language.title": "Language",
"settings.general.row.language.description": "Change the display language for OpenCode",
"settings.general.row.appearance.title": "Appearance",
"settings.general.row.appearance.description": "Customise how OpenCode looks on your device",
"settings.general.row.theme.title": "Theme",
"settings.general.row.theme.description": "Customise how OpenCode is themed.",
"settings.general.row.font.title": "Font",
"settings.general.row.font.description": "Customise the mono font used in code blocks",
"font.option.ibmPlexMono": "IBM Plex Mono",
"font.option.cascadiaCode": "Cascadia Code",
"font.option.firaCode": "Fira Code",
"font.option.hack": "Hack",
"font.option.inconsolata": "Inconsolata",
"font.option.intelOneMono": "Intel One Mono",
"font.option.jetbrainsMono": "JetBrains Mono",
"font.option.mesloLgs": "Meslo LGS",
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"sound.option.alert01": "Alert 01",
"sound.option.alert02": "Alert 02",
"sound.option.alert03": "Alert 03",
"sound.option.alert04": "Alert 04",
"sound.option.alert05": "Alert 05",
"sound.option.alert06": "Alert 06",
"sound.option.alert07": "Alert 07",
"sound.option.alert08": "Alert 08",
"sound.option.alert09": "Alert 09",
"sound.option.alert10": "Alert 10",
"sound.option.bipbop01": "Bip-bop 01",
"sound.option.bipbop02": "Bip-bop 02",
"sound.option.bipbop03": "Bip-bop 03",
"sound.option.bipbop04": "Bip-bop 04",
"sound.option.bipbop05": "Bip-bop 05",
"sound.option.bipbop06": "Bip-bop 06",
"sound.option.bipbop07": "Bip-bop 07",
"sound.option.bipbop08": "Bip-bop 08",
"sound.option.bipbop09": "Bip-bop 09",
"sound.option.bipbop10": "Bip-bop 10",
"sound.option.staplebops01": "Staplebops 01",
"sound.option.staplebops02": "Staplebops 02",
"sound.option.staplebops03": "Staplebops 03",
"sound.option.staplebops04": "Staplebops 04",
"sound.option.staplebops05": "Staplebops 05",
"sound.option.staplebops06": "Staplebops 06",
"sound.option.staplebops07": "Staplebops 07",
"sound.option.nope01": "Nope 01",
"sound.option.nope02": "Nope 02",
"sound.option.nope03": "Nope 03",
"sound.option.nope04": "Nope 04",
"sound.option.nope05": "Nope 05",
"sound.option.nope06": "Nope 06",
"sound.option.nope07": "Nope 07",
"sound.option.nope08": "Nope 08",
"sound.option.nope09": "Nope 09",
"sound.option.nope10": "Nope 10",
"sound.option.nope11": "Nope 11",
"sound.option.nope12": "Nope 12",
"sound.option.yup01": "Yup 01",
"sound.option.yup02": "Yup 02",
"sound.option.yup03": "Yup 03",
"sound.option.yup04": "Yup 04",
"sound.option.yup05": "Yup 05",
"sound.option.yup06": "Yup 06",
"settings.general.notifications.agent.title": "Agent",
"settings.general.notifications.agent.description":
"Show system notification when the agent is complete or needs attention",
"settings.general.notifications.permissions.title": "Permissions",
"settings.general.notifications.permissions.description": "Show system notification when a permission is required",
"settings.general.notifications.errors.title": "Errors",
"settings.general.notifications.errors.description": "Show system notification when an error occurs",
"settings.general.sounds.agent.title": "Agent",
"settings.general.sounds.agent.description": "Play sound when the agent is complete or needs attention",
"settings.general.sounds.permissions.title": "Permissions",
"settings.general.sounds.permissions.description": "Play sound when a permission is required",
"settings.general.sounds.errors.title": "Errors",
"settings.general.sounds.errors.description": "Play sound when an error occurs",
"settings.shortcuts.title": "Keyboard shortcuts",
"settings.shortcuts.reset.button": "Reset to defaults",
"settings.shortcuts.reset.toast.title": "Shortcuts reset",
"settings.shortcuts.reset.toast.description": "Keyboard shortcuts have been reset to defaults.",
"settings.shortcuts.conflict.title": "Shortcut already in use",
"settings.shortcuts.conflict.description": "{{keybind}} is already assigned to {{titles}}.",
"settings.shortcuts.unassigned": "Unassigned",
"settings.shortcuts.pressKeys": "Press keys",
"settings.shortcuts.search.placeholder": "Search shortcuts",
"settings.shortcuts.search.empty": "No shortcuts found",
"settings.shortcuts.group.general": "General",
"settings.shortcuts.group.session": "Session",
"settings.shortcuts.group.navigation": "Navigation",
"settings.shortcuts.group.modelAndAgent": "Model and agent",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Prompt",
"settings.providers.title": "Providers",
"settings.providers.description": "Provider settings will be configurable here.",
"settings.models.title": "Models",
"settings.models.description": "Model settings will be configurable here.",
"settings.agents.title": "Agents",
"settings.agents.description": "Agent settings will be configurable here.",
"settings.commands.title": "Commands",
"settings.commands.description": "Command settings will be configurable here.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP settings will be configurable here.",
"settings.permissions.title": "Permissions",
"settings.permissions.description": "Control what tools the server can use by default.",
"settings.permissions.section.tools": "Tools",
"settings.permissions.toast.updateFailed.title": "Failed to update permissions",
"settings.permissions.action.allow": "Allow",
"settings.permissions.action.ask": "Ask",
"settings.permissions.action.deny": "Deny",
"settings.permissions.tool.read.title": "Read",
"settings.permissions.tool.read.description": "Reading a file (matches the file path)",
"settings.permissions.tool.edit.title": "Edit",
"settings.permissions.tool.edit.description": "Modify files, including edits, writes, patches, and multi-edits",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Match files using glob patterns",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Search file contents using regular expressions",
"settings.permissions.tool.list.title": "List",
"settings.permissions.tool.list.description": "List files within a directory",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Run shell commands",
"settings.permissions.tool.task.title": "Task",
"settings.permissions.tool.task.description": "Launch sub-agents",
"settings.permissions.tool.skill.title": "Skill",
"settings.permissions.tool.skill.description": "Load a skill by name",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Run language server queries",
"settings.permissions.tool.todoread.title": "Todo Read",
"settings.permissions.tool.todoread.description": "Read the todo list",
"settings.permissions.tool.todowrite.title": "Todo Write",
"settings.permissions.tool.todowrite.description": "Update the todo list",
"settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "Fetch content from a URL",
"settings.permissions.tool.websearch.title": "Web Search",
"settings.permissions.tool.websearch.description": "Search the web",
"settings.permissions.tool.codesearch.title": "Code Search",
"settings.permissions.tool.codesearch.description": "Search code on the web",
"settings.permissions.tool.external_directory.title": "External Directory",
"settings.permissions.tool.external_directory.description": "Access files outside the project directory",
"settings.permissions.tool.doom_loop.title": "Doom Loop",
"settings.permissions.tool.doom_loop.description": "Detect repeated tool calls with identical input",
"session.delete.failed.title": "Failed to delete session",
"session.delete.title": "Delete session",
"session.delete.confirm": 'Delete session "{{name}}"?',
"session.delete.button": "Delete session",
"workspace.new": "New workspace",
"workspace.type.local": "local",
"workspace.type.sandbox": "sandbox",
"workspace.create.failed.title": "Failed to create workspace",
"workspace.delete.failed.title": "Failed to delete workspace",
"workspace.resetting.title": "Resetting workspace",
"workspace.resetting.description": "This may take a minute.",
"workspace.reset.failed.title": "Failed to reset workspace",
"workspace.reset.success.title": "Workspace reset",
"workspace.reset.success.description": "Workspace now matches the default branch.",
"workspace.status.checking": "Checking for unmerged changes...",
"workspace.status.error": "Unable to verify git status.",
"workspace.status.clean": "No unmerged changes detected.",
"workspace.status.dirty": "Unmerged changes detected in this workspace.",
"workspace.delete.title": "Delete workspace",
"workspace.delete.confirm": 'Delete workspace "{{name}}"?',
"workspace.delete.button": "Delete workspace",
"workspace.reset.title": "Reset workspace",
"workspace.reset.confirm": 'Reset workspace "{{name}}"?',
"workspace.reset.button": "Reset workspace",
"workspace.reset.archived.none": "No active sessions will be archived.",
"workspace.reset.archived.one": "1 session will be archived.",
"workspace.reset.archived.many": "{{count}} sessions will be archived.",
"workspace.reset.note": "This will reset the workspace to match the default branch.",
}

585
packages/app/src/i18n/es.ts Normal file
View File

@@ -0,0 +1,585 @@
export const dict = {
"command.category.suggested": "Sugerido",
"command.category.view": "Ver",
"command.category.project": "Proyecto",
"command.category.provider": "Proveedor",
"command.category.server": "Servidor",
"command.category.session": "Sesión",
"command.category.theme": "Tema",
"command.category.language": "Idioma",
"command.category.file": "Archivo",
"command.category.terminal": "Terminal",
"command.category.model": "Modelo",
"command.category.mcp": "MCP",
"command.category.agent": "Agente",
"command.category.permissions": "Permisos",
"command.category.workspace": "Espacio de trabajo",
"theme.scheme.system": "Sistema",
"theme.scheme.light": "Claro",
"theme.scheme.dark": "Oscuro",
"command.sidebar.toggle": "Alternar barra lateral",
"command.project.open": "Abrir proyecto",
"command.provider.connect": "Conectar proveedor",
"command.server.switch": "Cambiar servidor",
"command.session.previous": "Sesión anterior",
"command.session.next": "Siguiente sesión",
"command.session.archive": "Archivar sesión",
"command.palette": "Paleta de comandos",
"command.theme.cycle": "Alternar tema",
"command.theme.set": "Usar tema: {{theme}}",
"command.theme.scheme.cycle": "Alternar esquema de color",
"command.theme.scheme.set": "Usar esquema de color: {{scheme}}",
"command.language.cycle": "Alternar idioma",
"command.language.set": "Usar idioma: {{language}}",
"command.session.new": "Nueva sesión",
"command.file.open": "Abrir archivo",
"command.file.open.description": "Buscar archivos y comandos",
"command.terminal.toggle": "Alternar terminal",
"command.review.toggle": "Alternar revisión",
"command.terminal.new": "Nueva terminal",
"command.terminal.new.description": "Crear una nueva pestaña de terminal",
"command.steps.toggle": "Alternar pasos",
"command.steps.toggle.description": "Mostrar u ocultar pasos para el mensaje actual",
"command.message.previous": "Mensaje anterior",
"command.message.previous.description": "Ir al mensaje de usuario anterior",
"command.message.next": "Siguiente mensaje",
"command.message.next.description": "Ir al siguiente mensaje de usuario",
"command.model.choose": "Elegir modelo",
"command.model.choose.description": "Seleccionar un modelo diferente",
"command.mcp.toggle": "Alternar MCPs",
"command.mcp.toggle.description": "Alternar MCPs",
"command.agent.cycle": "Alternar agente",
"command.agent.cycle.description": "Cambiar al siguiente agente",
"command.agent.cycle.reverse": "Alternar agente hacia atrás",
"command.agent.cycle.reverse.description": "Cambiar al agente anterior",
"command.model.variant.cycle": "Alternar esfuerzo de pensamiento",
"command.model.variant.cycle.description": "Cambiar al siguiente nivel de esfuerzo",
"command.permissions.autoaccept.enable": "Aceptar ediciones automáticamente",
"command.permissions.autoaccept.disable": "Dejar de aceptar ediciones automáticamente",
"command.session.undo": "Deshacer",
"command.session.undo.description": "Deshacer el último mensaje",
"command.session.redo": "Rehacer",
"command.session.redo.description": "Rehacer el último mensaje deshecho",
"command.session.compact": "Compactar sesión",
"command.session.compact.description": "Resumir la sesión para reducir el tamaño del contexto",
"command.session.fork": "Bifurcar desde mensaje",
"command.session.fork.description": "Crear una nueva sesión desde un mensaje anterior",
"command.session.share": "Compartir sesión",
"command.session.share.description": "Compartir esta sesión y copiar la URL al portapapeles",
"command.session.unshare": "Dejar de compartir sesión",
"command.session.unshare.description": "Dejar de compartir esta sesión",
"palette.search.placeholder": "Buscar archivos y comandos",
"palette.empty": "No se encontraron resultados",
"palette.group.commands": "Comandos",
"palette.group.files": "Archivos",
"dialog.provider.search.placeholder": "Buscar proveedores",
"dialog.provider.empty": "No se encontraron proveedores",
"dialog.provider.group.popular": "Popular",
"dialog.provider.group.other": "Otro",
"dialog.provider.tag.recommended": "Recomendado",
"dialog.provider.anthropic.note": "Conectar con Claude Pro/Max o clave API",
"dialog.provider.openai.note": "Conectar con ChatGPT Pro/Plus o clave API",
"dialog.provider.copilot.note": "Conectar con Copilot o clave API",
"dialog.model.select.title": "Seleccionar modelo",
"dialog.model.search.placeholder": "Buscar modelos",
"dialog.model.empty": "Sin resultados de modelos",
"dialog.model.manage": "Gestionar modelos",
"dialog.model.manage.description": "Personalizar qué modelos aparecen en el selector de modelos.",
"dialog.model.unpaid.freeModels.title": "Modelos gratuitos proporcionados por OpenCode",
"dialog.model.unpaid.addMore.title": "Añadir más modelos de proveedores populares",
"dialog.provider.viewAll": "Ver todos los proveedores",
"provider.connect.title": "Conectar {{provider}}",
"provider.connect.title.anthropicProMax": "Iniciar sesión con Claude Pro/Max",
"provider.connect.selectMethod": "Seleccionar método de inicio de sesión para {{provider}}.",
"provider.connect.method.apiKey": "Clave API",
"provider.connect.status.inProgress": "Autorización en progreso...",
"provider.connect.status.waiting": "Esperando autorización...",
"provider.connect.status.failed": "Autorización fallida: {{error}}",
"provider.connect.apiKey.description":
"Introduce tu clave API de {{provider}} para conectar tu cuenta y usar modelos de {{provider}} en OpenCode.",
"provider.connect.apiKey.label": "Clave API de {{provider}}",
"provider.connect.apiKey.placeholder": "Clave API",
"provider.connect.apiKey.required": "La clave API es obligatoria",
"provider.connect.opencodeZen.line1":
"OpenCode Zen te da acceso a un conjunto curado de modelos fiables optimizados para agentes de programación.",
"provider.connect.opencodeZen.line2":
"Con una sola clave API obtendrás acceso a modelos como Claude, GPT, Gemini, GLM y más.",
"provider.connect.opencodeZen.visit.prefix": "Visita ",
"provider.connect.opencodeZen.visit.suffix": " para obtener tu clave API.",
"provider.connect.oauth.code.visit.prefix": "Visita ",
"provider.connect.oauth.code.visit.link": "este enlace",
"provider.connect.oauth.code.visit.suffix":
" para obtener tu código de autorización para conectar tu cuenta y usar modelos de {{provider}} en OpenCode.",
"provider.connect.oauth.code.label": "Código de autorización {{method}}",
"provider.connect.oauth.code.placeholder": "Código de autorización",
"provider.connect.oauth.code.required": "El código de autorización es obligatorio",
"provider.connect.oauth.code.invalid": "Código de autorización inválido",
"provider.connect.oauth.auto.visit.prefix": "Visita ",
"provider.connect.oauth.auto.visit.link": "este enlace",
"provider.connect.oauth.auto.visit.suffix":
" e introduce el código a continuación para conectar tu cuenta y usar modelos de {{provider}} en OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Código de confirmación",
"provider.connect.toast.connected.title": "{{provider}} conectado",
"provider.connect.toast.connected.description": "Los modelos de {{provider}} ahora están disponibles para usar.",
"model.tag.free": "Gratis",
"model.tag.latest": "Último",
"common.search.placeholder": "Buscar",
"common.goBack": "Volver",
"common.loading": "Cargando",
"common.cancel": "Cancelar",
"common.submit": "Enviar",
"common.save": "Guardar",
"common.saving": "Guardando...",
"common.default": "Predeterminado",
"common.attachment": "adjunto",
"prompt.placeholder.shell": "Introduce comando de shell...",
"prompt.placeholder.normal": 'Pregunta cualquier cosa... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "esc para salir",
"prompt.example.1": "Arreglar un TODO en el código",
"prompt.example.2": "¿Cuál es el stack tecnológico de este proyecto?",
"prompt.example.3": "Arreglar pruebas rotas",
"prompt.example.4": "Explicar cómo funciona la autenticación",
"prompt.example.5": "Encontrar y arreglar vulnerabilidades de seguridad",
"prompt.example.6": "Añadir pruebas unitarias para el servicio de usuario",
"prompt.example.7": "Refactorizar esta función para que sea más legible",
"prompt.example.8": "¿Qué significa este error?",
"prompt.example.9": "Ayúdame a depurar este problema",
"prompt.example.10": "Generar documentación de API",
"prompt.example.11": "Optimizar consultas a la base de datos",
"prompt.example.12": "Añadir validación de entrada",
"prompt.example.13": "Crear un nuevo componente para...",
"prompt.example.14": "¿Cómo despliego este proyecto?",
"prompt.example.15": "Revisar mi código para mejores prácticas",
"prompt.example.16": "Añadir manejo de errores a esta función",
"prompt.example.17": "Explicar este patrón de regex",
"prompt.example.18": "Convertir esto a TypeScript",
"prompt.example.19": "Añadir logging en todo el código",
"prompt.example.20": "¿Qué dependencias están desactualizadas?",
"prompt.example.21": "Ayúdame a escribir un script de migración",
"prompt.example.22": "Implementar caché para este endpoint",
"prompt.example.23": "Añadir paginación a esta lista",
"prompt.example.24": "Crear un comando CLI para...",
"prompt.example.25": "¿Cómo funcionan las variables de entorno aquí?",
"prompt.popover.emptyResults": "Sin resultados coincidentes",
"prompt.popover.emptyCommands": "Sin comandos coincidentes",
"prompt.dropzone.label": "Suelta imágenes o PDFs aquí",
"prompt.slash.badge.custom": "personalizado",
"prompt.context.active": "activo",
"prompt.context.includeActiveFile": "Incluir archivo activo",
"prompt.context.removeActiveFile": "Eliminar archivo activo del contexto",
"prompt.context.removeFile": "Eliminar archivo del contexto",
"prompt.action.attachFile": "Adjuntar archivo",
"prompt.attachment.remove": "Eliminar adjunto",
"prompt.action.send": "Enviar",
"prompt.action.stop": "Detener",
"prompt.toast.pasteUnsupported.title": "Pegado no soportado",
"prompt.toast.pasteUnsupported.description": "Solo se pueden pegar imágenes o PDFs aquí.",
"prompt.toast.modelAgentRequired.title": "Selecciona un agente y modelo",
"prompt.toast.modelAgentRequired.description": "Elige un agente y modelo antes de enviar un prompt.",
"prompt.toast.worktreeCreateFailed.title": "Fallo al crear el árbol de trabajo",
"prompt.toast.sessionCreateFailed.title": "Fallo al crear la sesión",
"prompt.toast.shellSendFailed.title": "Fallo al enviar comando de shell",
"prompt.toast.commandSendFailed.title": "Fallo al enviar comando",
"prompt.toast.promptSendFailed.title": "Fallo al enviar prompt",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} de {{total}} habilitados",
"dialog.mcp.empty": "No hay MCPs configurados",
"mcp.status.connected": "conectado",
"mcp.status.failed": "fallido",
"mcp.status.needs_auth": "necesita auth",
"mcp.status.disabled": "deshabilitado",
"dialog.fork.empty": "No hay mensajes desde donde bifurcar",
"dialog.directory.search.placeholder": "Buscar carpetas",
"dialog.directory.empty": "No se encontraron carpetas",
"dialog.server.title": "Servidores",
"dialog.server.description": "Cambiar a qué servidor de OpenCode se conecta esta app.",
"dialog.server.search.placeholder": "Buscar servidores",
"dialog.server.empty": "No hay servidores aún",
"dialog.server.add.title": "Añadir un servidor",
"dialog.server.add.url": "URL del servidor",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "No se pudo conectar al servidor",
"dialog.server.add.checking": "Comprobando...",
"dialog.server.add.button": "Añadir",
"dialog.server.default.title": "Servidor predeterminado",
"dialog.server.default.description":
"Conectar a este servidor al iniciar la app en lugar de iniciar un servidor local. Requiere reinicio.",
"dialog.server.default.none": "Ningún servidor seleccionado",
"dialog.server.default.set": "Establecer servidor actual como predeterminado",
"dialog.server.default.clear": "Limpiar",
"dialog.server.action.remove": "Eliminar servidor",
"dialog.project.edit.title": "Editar proyecto",
"dialog.project.edit.name": "Nombre",
"dialog.project.edit.icon": "Icono",
"dialog.project.edit.icon.alt": "Icono del proyecto",
"dialog.project.edit.icon.hint": "Haz clic o arrastra una imagen",
"dialog.project.edit.icon.recommended": "Recomendado: 128x128px",
"dialog.project.edit.color": "Color",
"dialog.project.edit.color.select": "Seleccionar color {{color}}",
"context.breakdown.title": "Desglose de Contexto",
"context.breakdown.note":
'Desglose aproximado de tokens de entrada. "Otro" incluye definiciones de herramientas y sobrecarga.',
"context.breakdown.system": "Sistema",
"context.breakdown.user": "Usuario",
"context.breakdown.assistant": "Asistente",
"context.breakdown.tool": "Llamadas a herramientas",
"context.breakdown.other": "Otro",
"context.systemPrompt.title": "Prompt del Sistema",
"context.rawMessages.title": "Mensajes en bruto",
"context.stats.session": "Sesión",
"context.stats.messages": "Mensajes",
"context.stats.provider": "Proveedor",
"context.stats.model": "Modelo",
"context.stats.limit": "Límite de Contexto",
"context.stats.totalTokens": "Tokens Totales",
"context.stats.usage": "Uso",
"context.stats.inputTokens": "Tokens de Entrada",
"context.stats.outputTokens": "Tokens de Salida",
"context.stats.reasoningTokens": "Tokens de Razonamiento",
"context.stats.cacheTokens": "Tokens de Caché (lectura/escritura)",
"context.stats.userMessages": "Mensajes de Usuario",
"context.stats.assistantMessages": "Mensajes de Asistente",
"context.stats.totalCost": "Costo Total",
"context.stats.sessionCreated": "Sesión Creada",
"context.stats.lastActivity": "Última Actividad",
"context.usage.tokens": "Tokens",
"context.usage.usage": "Uso",
"context.usage.cost": "Costo",
"context.usage.clickToView": "Haz clic para ver contexto",
"context.usage.view": "Ver uso del contexto",
"language.en": "Inglés",
"language.zh": "Chino (simplificado)",
"language.zht": "Chino (tradicional)",
"language.ko": "Coreano",
"language.de": "Alemán",
"language.es": "Español",
"language.fr": "Francés",
"language.ja": "Japonés",
"language.da": "Danés",
"language.ru": "Ruso",
"language.pl": "Polaco",
"language.ar": "Árabe",
"language.no": "Noruego",
"language.br": "Portugués (Brasil)",
"toast.language.title": "Idioma",
"toast.language.description": "Cambiado a {{language}}",
"toast.theme.title": "Tema cambiado",
"toast.scheme.title": "Esquema de color",
"toast.permissions.autoaccept.on.title": "Aceptando ediciones automáticamente",
"toast.permissions.autoaccept.on.description": "Los permisos de edición y escritura serán aprobados automáticamente",
"toast.permissions.autoaccept.off.title": "Se dejó de aceptar ediciones automáticamente",
"toast.permissions.autoaccept.off.description": "Los permisos de edición y escritura requerirán aprobación",
"toast.model.none.title": "Ningún modelo seleccionado",
"toast.model.none.description": "Conecta un proveedor para resumir esta sesión",
"toast.file.loadFailed.title": "Fallo al cargar archivo",
"toast.session.share.copyFailed.title": "Fallo al copiar URL al portapapeles",
"toast.session.share.success.title": "Sesión compartida",
"toast.session.share.success.description": "¡URL compartida copiada al portapapeles!",
"toast.session.share.failed.title": "Fallo al compartir sesión",
"toast.session.share.failed.description": "Ocurrió un error al compartir la sesión",
"toast.session.unshare.success.title": "Sesión dejó de compartirse",
"toast.session.unshare.success.description": "¡La sesión dejó de compartirse exitosamente!",
"toast.session.unshare.failed.title": "Fallo al dejar de compartir sesión",
"toast.session.unshare.failed.description": "Ocurrió un error al dejar de compartir la sesión",
"toast.session.listFailed.title": "Fallo al cargar sesiones para {{project}}",
"toast.update.title": "Actualización disponible",
"toast.update.description": "Una nueva versión de OpenCode ({{version}}) está disponible para instalar.",
"toast.update.action.installRestart": "Instalar y reiniciar",
"toast.update.action.notYet": "Todavía no",
"error.page.title": "Algo salió mal",
"error.page.description": "Ocurrió un error al cargar la aplicación.",
"error.page.details.label": "Detalles del error",
"error.page.action.restart": "Reiniciar",
"error.page.action.checking": "Comprobando...",
"error.page.action.checkUpdates": "Buscar actualizaciones",
"error.page.action.updateTo": "Actualizar a {{version}}",
"error.page.report.prefix": "Por favor reporta este error al equipo de OpenCode",
"error.page.report.discord": "en Discord",
"error.page.version": "Versión: {{version}}",
"error.dev.rootNotFound":
"Elemento raíz no encontrado. ¿Olvidaste añadirlo a tu index.html? ¿O tal vez el atributo id está mal escrito?",
"error.globalSync.connectFailed": "No se pudo conectar al servidor. ¿Hay un servidor ejecutándose en `{{url}}`?",
"error.chain.unknown": "Error desconocido",
"error.chain.causedBy": "Causado por:",
"error.chain.apiError": "Error de API",
"error.chain.status": "Estado: {{status}}",
"error.chain.retryable": "Reintentable: {{retryable}}",
"error.chain.responseBody": "Cuerpo de la respuesta:\n{{body}}",
"error.chain.didYouMean": "¿Quisiste decir: {{suggestions}}",
"error.chain.modelNotFound": "Modelo no encontrado: {{provider}}/{{model}}",
"error.chain.checkConfig": "Comprueba los nombres de proveedor/modelo en tu configuración (opencode.json)",
"error.chain.mcpFailed": 'El servidor MCP "{{name}}" falló. Nota, OpenCode no soporta autenticación MCP todavía.',
"error.chain.providerAuthFailed": "Autenticación de proveedor fallida ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'Fallo al inicializar proveedor "{{provider}}". Comprueba credenciales y configuración.',
"error.chain.configJsonInvalid": "El archivo de configuración en {{path}} no es un JSON(C) válido",
"error.chain.configJsonInvalidWithMessage":
"El archivo de configuración en {{path}} no es un JSON(C) válido: {{message}}",
"error.chain.configDirectoryTypo":
'El directorio "{{dir}}" en {{path}} no es válido. Renombra el directorio a "{{suggestion}}" o elimínalo. Esto es un error tipográfico común.',
"error.chain.configFrontmatterError": "Fallo al analizar frontmatter en {{path}}:\n{{message}}",
"error.chain.configInvalid": "El archivo de configuración en {{path}} es inválido",
"error.chain.configInvalidWithMessage": "El archivo de configuración en {{path}} es inválido: {{message}}",
"notification.permission.title": "Permiso requerido",
"notification.permission.description": "{{sessionTitle}} en {{projectName}} necesita permiso",
"notification.question.title": "Pregunta",
"notification.question.description": "{{sessionTitle}} en {{projectName}} tiene una pregunta",
"notification.action.goToSession": "Ir a sesión",
"notification.session.responseReady.title": "Respuesta lista",
"notification.session.error.title": "Error de sesión",
"notification.session.error.fallbackDescription": "Ocurrió un error",
"home.recentProjects": "Proyectos recientes",
"home.empty.title": "Sin proyectos recientes",
"home.empty.description": "Empieza abriendo un proyecto local",
"session.tab.session": "Sesión",
"session.tab.review": "Revisión",
"session.tab.context": "Contexto",
"session.panel.reviewAndFiles": "Revisión y archivos",
"session.review.filesChanged": "{{count}} Archivos Cambiados",
"session.review.loadingChanges": "Cargando cambios...",
"session.review.empty": "No hay cambios en esta sesión aún",
"session.messages.renderEarlier": "Renderizar mensajes anteriores",
"session.messages.loadingEarlier": "Cargando mensajes anteriores...",
"session.messages.loadEarlier": "Cargar mensajes anteriores",
"session.messages.loading": "Cargando mensajes...",
"session.context.addToContext": "Añadir {{selection}} al contexto",
"session.new.worktree.main": "Rama principal",
"session.new.worktree.mainWithBranch": "Rama principal ({{branch}})",
"session.new.worktree.create": "Crear nuevo árbol de trabajo",
"session.new.lastModified": "Última modificación",
"session.header.search.placeholder": "Buscar {{project}}",
"session.header.searchFiles": "Buscar archivos",
"session.share.popover.title": "Publicar en web",
"session.share.popover.description.shared":
"Esta sesión es pública en la web. Es accesible para cualquiera con el enlace.",
"session.share.popover.description.unshared":
"Compartir sesión públicamente en la web. Será accesible para cualquiera con el enlace.",
"session.share.action.share": "Compartir",
"session.share.action.publish": "Publicar",
"session.share.action.publishing": "Publicando...",
"session.share.action.unpublish": "Despublicar",
"session.share.action.unpublishing": "Despublicando...",
"session.share.action.view": "Ver",
"session.share.copy.copied": "Copiado",
"session.share.copy.copyLink": "Copiar enlace",
"lsp.tooltip.none": "Sin servidores LSP",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Cargando prompt...",
"terminal.loading": "Cargando terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Cerrar terminal",
"common.closeTab": "Cerrar pestaña",
"common.dismiss": "Descartar",
"common.requestFailed": "Solicitud fallida",
"common.moreOptions": "Más opciones",
"common.learnMore": "Saber más",
"common.rename": "Renombrar",
"common.reset": "Restablecer",
"common.archive": "Archivar",
"common.delete": "Eliminar",
"common.close": "Cerrar",
"common.edit": "Editar",
"common.loadMore": "Cargar más",
"sidebar.nav.projectsAndSessions": "Proyectos y sesiones",
"sidebar.settings": "Ajustes",
"sidebar.help": "Ayuda",
"sidebar.workspaces.enable": "Habilitar espacios de trabajo",
"sidebar.workspaces.disable": "Deshabilitar espacios de trabajo",
"sidebar.gettingStarted.title": "Empezando",
"sidebar.gettingStarted.line1": "OpenCode incluye modelos gratuitos para que puedas empezar inmediatamente.",
"sidebar.gettingStarted.line2": "Conecta cualquier proveedor para usar modelos, inc. Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sesiones recientes",
"sidebar.project.viewAllSessions": "Ver todas las sesiones",
"settings.section.desktop": "Escritorio",
"settings.tab.general": "General",
"settings.tab.shortcuts": "Atajos",
"settings.general.section.appearance": "Apariencia",
"settings.general.section.notifications": "Notificaciones del sistema",
"settings.general.section.sounds": "Efectos de sonido",
"settings.general.row.language.title": "Idioma",
"settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode",
"settings.general.row.appearance.title": "Apariencia",
"settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo",
"settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
"settings.general.row.font.title": "Fuente",
"settings.general.row.font.description": "Personaliza la fuente mono usada en bloques de código",
"settings.general.notifications.agent.title": "Agente",
"settings.general.notifications.agent.description":
"Mostrar notificación del sistema cuando el agente termine o necesite atención",
"settings.general.notifications.permissions.title": "Permisos",
"settings.general.notifications.permissions.description":
"Mostrar notificación del sistema cuando se requiera un permiso",
"settings.general.notifications.errors.title": "Errores",
"settings.general.notifications.errors.description": "Mostrar notificación del sistema cuando ocurra un error",
"settings.general.sounds.agent.title": "Agente",
"settings.general.sounds.agent.description": "Reproducir sonido cuando el agente termine o necesite atención",
"settings.general.sounds.permissions.title": "Permisos",
"settings.general.sounds.permissions.description": "Reproducir sonido cuando se requiera un permiso",
"settings.general.sounds.errors.title": "Errores",
"settings.general.sounds.errors.description": "Reproducir sonido cuando ocurra un error",
"settings.shortcuts.title": "Atajos de teclado",
"settings.shortcuts.reset.button": "Restablecer a valores predeterminados",
"settings.shortcuts.reset.toast.title": "Atajos restablecidos",
"settings.shortcuts.reset.toast.description":
"Los atajos de teclado han sido restablecidos a los valores predeterminados.",
"settings.shortcuts.conflict.title": "Atajo ya en uso",
"settings.shortcuts.conflict.description": "{{keybind}} ya está asignado a {{titles}}.",
"settings.shortcuts.unassigned": "Sin asignar",
"settings.shortcuts.pressKeys": "Presiona teclas",
"settings.shortcuts.search.placeholder": "Buscar atajos",
"settings.shortcuts.search.empty": "No se encontraron atajos",
"settings.shortcuts.group.general": "General",
"settings.shortcuts.group.session": "Sesión",
"settings.shortcuts.group.navigation": "Navegación",
"settings.shortcuts.group.modelAndAgent": "Modelo y agente",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Prompt",
"settings.providers.title": "Proveedores",
"settings.providers.description": "La configuración de proveedores estará disponible aquí.",
"settings.models.title": "Modelos",
"settings.models.description": "La configuración de modelos estará disponible aquí.",
"settings.agents.title": "Agentes",
"settings.agents.description": "La configuración de agentes estará disponible aquí.",
"settings.commands.title": "Comandos",
"settings.commands.description": "La configuración de comandos estará disponible aquí.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "La configuración de MCP estará disponible aquí.",
"settings.permissions.title": "Permisos",
"settings.permissions.description": "Controla qué herramientas puede usar el servidor por defecto.",
"settings.permissions.section.tools": "Herramientas",
"settings.permissions.toast.updateFailed.title": "Fallo al actualizar permisos",
"settings.permissions.action.allow": "Permitir",
"settings.permissions.action.ask": "Preguntar",
"settings.permissions.action.deny": "Denegar",
"settings.permissions.tool.read.title": "Leer",
"settings.permissions.tool.read.description": "Leer un archivo (coincide con la ruta del archivo)",
"settings.permissions.tool.edit.title": "Editar",
"settings.permissions.tool.edit.description":
"Modificar archivos, incluyendo ediciones, escrituras, parches y multi-ediciones",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Coincidir archivos usando patrones glob",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Buscar contenidos de archivo usando expresiones regulares",
"settings.permissions.tool.list.title": "Listar",
"settings.permissions.tool.list.description": "Listar archivos dentro de un directorio",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Ejecutar comandos de shell",
"settings.permissions.tool.task.title": "Tarea",
"settings.permissions.tool.task.description": "Lanzar sub-agentes",
"settings.permissions.tool.skill.title": "Habilidad",
"settings.permissions.tool.skill.description": "Cargar una habilidad por nombre",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Ejecutar consultas de servidor de lenguaje",
"settings.permissions.tool.todoread.title": "Leer Todo",
"settings.permissions.tool.todoread.description": "Leer la lista de tareas",
"settings.permissions.tool.todowrite.title": "Escribir Todo",
"settings.permissions.tool.todowrite.description": "Actualizar la lista de tareas",
"settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "Obtener contenido de una URL",
"settings.permissions.tool.websearch.title": "Búsqueda Web",
"settings.permissions.tool.websearch.description": "Buscar en la web",
"settings.permissions.tool.codesearch.title": "Búsqueda de Código",
"settings.permissions.tool.codesearch.description": "Buscar código en la web",
"settings.permissions.tool.external_directory.title": "Directorio Externo",
"settings.permissions.tool.external_directory.description": "Acceder a archivos fuera del directorio del proyecto",
"settings.permissions.tool.doom_loop.title": "Bucle Infinito",
"settings.permissions.tool.doom_loop.description": "Detectar llamadas a herramientas repetidas con entrada idéntica",
"session.delete.failed.title": "Fallo al eliminar sesión",
"session.delete.title": "Eliminar sesión",
"session.delete.confirm": '¿Eliminar sesión "{{name}}"?',
"session.delete.button": "Eliminar sesión",
"workspace.new": "Nuevo espacio de trabajo",
"workspace.type.local": "local",
"workspace.type.sandbox": "sandbox",
"workspace.create.failed.title": "Fallo al crear espacio de trabajo",
"workspace.delete.failed.title": "Fallo al eliminar espacio de trabajo",
"workspace.resetting.title": "Restableciendo espacio de trabajo",
"workspace.resetting.description": "Esto puede tomar un minuto.",
"workspace.reset.failed.title": "Fallo al restablecer espacio de trabajo",
"workspace.reset.success.title": "Espacio de trabajo restablecido",
"workspace.reset.success.description": "El espacio de trabajo ahora coincide con la rama predeterminada.",
"workspace.status.checking": "Comprobando cambios no fusionados...",
"workspace.status.error": "No se pudo verificar el estado de git.",
"workspace.status.clean": "No se detectaron cambios no fusionados.",
"workspace.status.dirty": "Cambios no fusionados detectados en este espacio de trabajo.",
"workspace.delete.title": "Eliminar espacio de trabajo",
"workspace.delete.confirm": '¿Eliminar espacio de trabajo "{{name}}"?',
"workspace.delete.button": "Eliminar espacio de trabajo",
"workspace.reset.title": "Restablecer espacio de trabajo",
"workspace.reset.confirm": '¿Restablecer espacio de trabajo "{{name}}"?',
"workspace.reset.button": "Restablecer espacio de trabajo",
"workspace.reset.archived.none": "No se archivarán sesiones activas.",
"workspace.reset.archived.one": "1 sesión será archivada.",
"workspace.reset.archived.many": "{{count}} sesiones serán archivadas.",
"workspace.reset.note": "Esto restablecerá el espacio de trabajo para coincidir con la rama predeterminada.",
}

592
packages/app/src/i18n/fr.ts Normal file
View File

@@ -0,0 +1,592 @@
export const dict = {
"command.category.suggested": "Suggéré",
"command.category.view": "Affichage",
"command.category.project": "Projet",
"command.category.provider": "Fournisseur",
"command.category.server": "Serveur",
"command.category.session": "Session",
"command.category.theme": "Thème",
"command.category.language": "Langue",
"command.category.file": "Fichier",
"command.category.terminal": "Terminal",
"command.category.model": "Modèle",
"command.category.mcp": "MCP",
"command.category.agent": "Agent",
"command.category.permissions": "Permissions",
"command.category.workspace": "Espace de travail",
"theme.scheme.system": "Système",
"theme.scheme.light": "Clair",
"theme.scheme.dark": "Sombre",
"command.sidebar.toggle": "Basculer la barre latérale",
"command.project.open": "Ouvrir un projet",
"command.provider.connect": "Connecter un fournisseur",
"command.server.switch": "Changer de serveur",
"command.session.previous": "Session précédente",
"command.session.next": "Session suivante",
"command.session.archive": "Archiver la session",
"command.palette": "Palette de commandes",
"command.theme.cycle": "Changer de thème",
"command.theme.set": "Utiliser le thème : {{theme}}",
"command.theme.scheme.cycle": "Changer de schéma de couleurs",
"command.theme.scheme.set": "Utiliser le schéma de couleurs : {{scheme}}",
"command.language.cycle": "Changer de langue",
"command.language.set": "Utiliser la langue : {{language}}",
"command.session.new": "Nouvelle session",
"command.file.open": "Ouvrir un fichier",
"command.file.open.description": "Rechercher des fichiers et des commandes",
"command.terminal.toggle": "Basculer le terminal",
"command.review.toggle": "Basculer la revue",
"command.terminal.new": "Nouveau terminal",
"command.terminal.new.description": "Créer un nouvel onglet de terminal",
"command.steps.toggle": "Basculer les étapes",
"command.steps.toggle.description": "Afficher ou masquer les étapes du message actuel",
"command.message.previous": "Message précédent",
"command.message.previous.description": "Aller au message utilisateur précédent",
"command.message.next": "Message suivant",
"command.message.next.description": "Aller au message utilisateur suivant",
"command.model.choose": "Choisir le modèle",
"command.model.choose.description": "Sélectionner un modèle différent",
"command.mcp.toggle": "Basculer MCP",
"command.mcp.toggle.description": "Basculer les MCPs",
"command.agent.cycle": "Changer d'agent",
"command.agent.cycle.description": "Passer à l'agent suivant",
"command.agent.cycle.reverse": "Changer d'agent (inverse)",
"command.agent.cycle.reverse.description": "Passer à l'agent précédent",
"command.model.variant.cycle": "Changer l'effort de réflexion",
"command.model.variant.cycle.description": "Passer au niveau d'effort suivant",
"command.permissions.autoaccept.enable": "Accepter automatiquement les modifications",
"command.permissions.autoaccept.disable": "Arrêter l'acceptation automatique des modifications",
"command.session.undo": "Annuler",
"command.session.undo.description": "Annuler le dernier message",
"command.session.redo": "Rétablir",
"command.session.redo.description": "Rétablir le dernier message annulé",
"command.session.compact": "Compacter la session",
"command.session.compact.description": "Résumer la session pour réduire la taille du contexte",
"command.session.fork": "Bifurquer à partir du message",
"command.session.fork.description": "Créer une nouvelle session à partir d'un message précédent",
"command.session.share": "Partager la session",
"command.session.share.description": "Partager cette session et copier l'URL dans le presse-papiers",
"command.session.unshare": "Ne plus partager la session",
"command.session.unshare.description": "Arrêter de partager cette session",
"palette.search.placeholder": "Rechercher des fichiers et des commandes",
"palette.empty": "Aucun résultat trouvé",
"palette.group.commands": "Commandes",
"palette.group.files": "Fichiers",
"dialog.provider.search.placeholder": "Rechercher des fournisseurs",
"dialog.provider.empty": "Aucun fournisseur trouvé",
"dialog.provider.group.popular": "Populaire",
"dialog.provider.group.other": "Autre",
"dialog.provider.tag.recommended": "Recommandé",
"dialog.provider.anthropic.note": "Connectez-vous avec Claude Pro/Max ou une clé API",
"dialog.provider.openai.note": "Connectez-vous avec ChatGPT Pro/Plus ou une clé API",
"dialog.provider.copilot.note": "Connectez-vous avec Copilot ou une clé API",
"dialog.model.select.title": "Sélectionner un modèle",
"dialog.model.search.placeholder": "Rechercher des modèles",
"dialog.model.empty": "Aucun résultat de modèle",
"dialog.model.manage": "Gérer les modèles",
"dialog.model.manage.description": "Personnalisez les modèles qui apparaissent dans le sélecteur.",
"dialog.model.unpaid.freeModels.title": "Modèles gratuits fournis par OpenCode",
"dialog.model.unpaid.addMore.title": "Ajouter plus de modèles de fournisseurs populaires",
"dialog.provider.viewAll": "Voir tous les fournisseurs",
"provider.connect.title": "Connecter {{provider}}",
"provider.connect.title.anthropicProMax": "Connexion avec Claude Pro/Max",
"provider.connect.selectMethod": "Sélectionnez la méthode de connexion pour {{provider}}.",
"provider.connect.method.apiKey": "Clé API",
"provider.connect.status.inProgress": "Autorisation en cours...",
"provider.connect.status.waiting": "En attente d'autorisation...",
"provider.connect.status.failed": "Échec de l'autorisation : {{error}}",
"provider.connect.apiKey.description":
"Entrez votre clé API {{provider}} pour connecter votre compte et utiliser les modèles {{provider}} dans OpenCode.",
"provider.connect.apiKey.label": "Clé API {{provider}}",
"provider.connect.apiKey.placeholder": "Clé API",
"provider.connect.apiKey.required": "La clé API est requise",
"provider.connect.opencodeZen.line1":
"OpenCode Zen vous donne accès à un ensemble sélectionné de modèles fiables et optimisés pour les agents de codage.",
"provider.connect.opencodeZen.line2":
"Avec une seule clé API, vous aurez accès à des modèles tels que Claude, GPT, Gemini, GLM et plus encore.",
"provider.connect.opencodeZen.visit.prefix": "Visitez ",
"provider.connect.opencodeZen.visit.suffix": " pour récupérer votre clé API.",
"provider.connect.oauth.code.visit.prefix": "Visitez ",
"provider.connect.oauth.code.visit.link": "ce lien",
"provider.connect.oauth.code.visit.suffix":
" pour récupérer votre code d'autorisation afin de connecter votre compte et utiliser les modèles {{provider}} dans OpenCode.",
"provider.connect.oauth.code.label": "Code d'autorisation {{method}}",
"provider.connect.oauth.code.placeholder": "Code d'autorisation",
"provider.connect.oauth.code.required": "Le code d'autorisation est requis",
"provider.connect.oauth.code.invalid": "Code d'autorisation invalide",
"provider.connect.oauth.auto.visit.prefix": "Visitez ",
"provider.connect.oauth.auto.visit.link": "ce lien",
"provider.connect.oauth.auto.visit.suffix":
" et entrez le code ci-dessous pour connecter votre compte et utiliser les modèles {{provider}} dans OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Code de confirmation",
"provider.connect.toast.connected.title": "{{provider}} connecté",
"provider.connect.toast.connected.description": "Les modèles {{provider}} sont maintenant disponibles.",
"model.tag.free": "Gratuit",
"model.tag.latest": "Dernier",
"common.search.placeholder": "Rechercher",
"common.goBack": "Retour",
"common.loading": "Chargement",
"common.cancel": "Annuler",
"common.submit": "Soumettre",
"common.save": "Enregistrer",
"common.saving": "Enregistrement...",
"common.default": "Défaut",
"common.attachment": "pièce jointe",
"prompt.placeholder.shell": "Entrez une commande shell...",
"prompt.placeholder.normal": 'Demandez n\'importe quoi... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "esc pour quitter",
"prompt.example.1": "Corriger un TODO dans la base de code",
"prompt.example.2": "Quelle est la pile technique de ce projet ?",
"prompt.example.3": "Réparer les tests échoués",
"prompt.example.4": "Expliquer comment fonctionne l'authentification",
"prompt.example.5": "Trouver et corriger les vulnérabilités de sécurité",
"prompt.example.6": "Ajouter des tests unitaires pour le service utilisateur",
"prompt.example.7": "Refactoriser cette fonction pour être plus lisible",
"prompt.example.8": "Que signifie cette erreur ?",
"prompt.example.9": "Aidez-moi à déboguer ce problème",
"prompt.example.10": "Générer la documentation de l'API",
"prompt.example.11": "Optimiser les requêtes de base de données",
"prompt.example.12": "Ajouter une validation d'entrée",
"prompt.example.13": "Créer un nouveau composant pour...",
"prompt.example.14": "Comment déployer ce projet ?",
"prompt.example.15": "Vérifier mon code pour les meilleures pratiques",
"prompt.example.16": "Ajouter la gestion des erreurs à cette fonction",
"prompt.example.17": "Expliquer ce modèle regex",
"prompt.example.18": "Convertir ceci en TypeScript",
"prompt.example.19": "Ajouter des logs dans toute la base de code",
"prompt.example.20": "Quelles dépendances sont obsolètes ?",
"prompt.example.21": "Aidez-moi à écrire un script de migration",
"prompt.example.22": "Implémenter la mise en cache pour ce point de terminaison",
"prompt.example.23": "Ajouter la pagination à cette liste",
"prompt.example.24": "Créer une commande CLI pour...",
"prompt.example.25": "Comment fonctionnent les variables d'environnement ici ?",
"prompt.popover.emptyResults": "Aucun résultat correspondant",
"prompt.popover.emptyCommands": "Aucune commande correspondante",
"prompt.dropzone.label": "Déposez des images ou des PDF ici",
"prompt.slash.badge.custom": "personnalisé",
"prompt.context.active": "actif",
"prompt.context.includeActiveFile": "Inclure le fichier actif",
"prompt.context.removeActiveFile": "Retirer le fichier actif du contexte",
"prompt.context.removeFile": "Retirer le fichier du contexte",
"prompt.action.attachFile": "Joindre un fichier",
"prompt.attachment.remove": "Supprimer la pièce jointe",
"prompt.action.send": "Envoyer",
"prompt.action.stop": "Arrêter",
"prompt.toast.pasteUnsupported.title": "Collage non supporté",
"prompt.toast.pasteUnsupported.description": "Seules les images ou les PDF peuvent être collés ici.",
"prompt.toast.modelAgentRequired.title": "Sélectionnez un agent et un modèle",
"prompt.toast.modelAgentRequired.description": "Choisissez un agent et un modèle avant d'envoyer un message.",
"prompt.toast.worktreeCreateFailed.title": "Échec de la création de l'arbre de travail",
"prompt.toast.sessionCreateFailed.title": "Échec de la création de la session",
"prompt.toast.shellSendFailed.title": "Échec de l'envoi de la commande shell",
"prompt.toast.commandSendFailed.title": "Échec de l'envoi de la commande",
"prompt.toast.promptSendFailed.title": "Échec de l'envoi du message",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} sur {{total}} activés",
"dialog.mcp.empty": "Aucun MCP configuré",
"mcp.status.connected": "connecté",
"mcp.status.failed": "échoué",
"mcp.status.needs_auth": "nécessite auth",
"mcp.status.disabled": "désactivé",
"dialog.fork.empty": "Aucun message à partir duquel bifurquer",
"dialog.directory.search.placeholder": "Rechercher des dossiers",
"dialog.directory.empty": "Aucun dossier trouvé",
"dialog.server.title": "Serveurs",
"dialog.server.description": "Changez le serveur OpenCode auquel cette application se connecte.",
"dialog.server.search.placeholder": "Rechercher des serveurs",
"dialog.server.empty": "Aucun serveur pour l'instant",
"dialog.server.add.title": "Ajouter un serveur",
"dialog.server.add.url": "URL du serveur",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Impossible de se connecter au serveur",
"dialog.server.add.checking": "Vérification...",
"dialog.server.add.button": "Ajouter",
"dialog.server.default.title": "Serveur par défaut",
"dialog.server.default.description":
"Se connecter à ce serveur au lancement de l'application au lieu de démarrer un serveur local. Nécessite un redémarrage.",
"dialog.server.default.none": "Aucun serveur sélectionné",
"dialog.server.default.set": "Définir le serveur actuel comme défaut",
"dialog.server.default.clear": "Effacer",
"dialog.server.action.remove": "Supprimer le serveur",
"dialog.project.edit.title": "Modifier le projet",
"dialog.project.edit.name": "Nom",
"dialog.project.edit.icon": "Icône",
"dialog.project.edit.icon.alt": "Icône du projet",
"dialog.project.edit.icon.hint": "Cliquez ou faites glisser une image",
"dialog.project.edit.icon.recommended": "Recommandé : 128x128px",
"dialog.project.edit.color": "Couleur",
"dialog.project.edit.color.select": "Sélectionner la couleur {{color}}",
"context.breakdown.title": "Répartition du contexte",
"context.breakdown.note":
"Répartition approximative des jetons d'entrée. \"Autre\" inclut les définitions d'outils et les frais généraux.",
"context.breakdown.system": "Système",
"context.breakdown.user": "Utilisateur",
"context.breakdown.assistant": "Assistant",
"context.breakdown.tool": "Appels d'outils",
"context.breakdown.other": "Autre",
"context.systemPrompt.title": "Prompt système",
"context.rawMessages.title": "Messages bruts",
"context.stats.session": "Session",
"context.stats.messages": "Messages",
"context.stats.provider": "Fournisseur",
"context.stats.model": "Modèle",
"context.stats.limit": "Limite de contexte",
"context.stats.totalTokens": "Total des jetons",
"context.stats.usage": "Utilisation",
"context.stats.inputTokens": "Jetons d'entrée",
"context.stats.outputTokens": "Jetons de sortie",
"context.stats.reasoningTokens": "Jetons de raisonnement",
"context.stats.cacheTokens": "Jetons de cache (lecture/écriture)",
"context.stats.userMessages": "Messages utilisateur",
"context.stats.assistantMessages": "Messages assistant",
"context.stats.totalCost": "Coût total",
"context.stats.sessionCreated": "Session créée",
"context.stats.lastActivity": "Dernière activité",
"context.usage.tokens": "Jetons",
"context.usage.usage": "Utilisation",
"context.usage.cost": "Coût",
"context.usage.clickToView": "Cliquez pour voir le contexte",
"context.usage.view": "Voir l'utilisation du contexte",
"language.en": "Anglais",
"language.zh": "Chinois (simplifié)",
"language.zht": "Chinois (traditionnel)",
"language.ko": "Coréen",
"language.de": "Allemand",
"language.es": "Espagnol",
"language.fr": "Français",
"language.ja": "Japonais",
"language.da": "Danois",
"language.ru": "Russe",
"language.pl": "Polonais",
"language.ar": "Arabe",
"language.no": "Norvégien",
"language.br": "Portugais (Brésil)",
"toast.language.title": "Langue",
"toast.language.description": "Passé à {{language}}",
"toast.theme.title": "Thème changé",
"toast.scheme.title": "Schéma de couleurs",
"toast.permissions.autoaccept.on.title": "Acceptation auto des modifications",
"toast.permissions.autoaccept.on.description":
"Les permissions de modification et d'écriture seront automatiquement approuvées",
"toast.permissions.autoaccept.off.title": "Arrêt acceptation auto des modifications",
"toast.permissions.autoaccept.off.description":
"Les permissions de modification et d'écriture nécessiteront une approbation",
"toast.model.none.title": "Aucun modèle sélectionné",
"toast.model.none.description": "Connectez un fournisseur pour résumer cette session",
"toast.file.loadFailed.title": "Échec du chargement du fichier",
"toast.session.share.copyFailed.title": "Échec de la copie de l'URL dans le presse-papiers",
"toast.session.share.success.title": "Session partagée",
"toast.session.share.success.description": "URL de partage copiée dans le presse-papiers !",
"toast.session.share.failed.title": "Échec du partage de la session",
"toast.session.share.failed.description": "Une erreur s'est produite lors du partage de la session",
"toast.session.unshare.success.title": "Session non partagée",
"toast.session.unshare.success.description": "Session non partagée avec succès !",
"toast.session.unshare.failed.title": "Échec de l'annulation du partage",
"toast.session.unshare.failed.description": "Une erreur s'est produite lors de l'annulation du partage de la session",
"toast.session.listFailed.title": "Échec du chargement des sessions pour {{project}}",
"toast.update.title": "Mise à jour disponible",
"toast.update.description":
"Une nouvelle version d'OpenCode ({{version}}) est maintenant disponible pour installation.",
"toast.update.action.installRestart": "Installer et redémarrer",
"toast.update.action.notYet": "Pas encore",
"error.page.title": "Quelque chose s'est mal passé",
"error.page.description": "Une erreur s'est produite lors du chargement de l'application.",
"error.page.details.label": "Détails de l'erreur",
"error.page.action.restart": "Redémarrer",
"error.page.action.checking": "Vérification...",
"error.page.action.checkUpdates": "Vérifier les mises à jour",
"error.page.action.updateTo": "Mettre à jour vers {{version}}",
"error.page.report.prefix": "Veuillez signaler cette erreur à l'équipe OpenCode",
"error.page.report.discord": "sur Discord",
"error.page.version": "Version : {{version}}",
"error.dev.rootNotFound":
"Élément racine introuvable. Avez-vous oublié de l'ajouter à votre index.html ? Ou peut-être que l'attribut id est mal orthographié ?",
"error.globalSync.connectFailed":
"Impossible de se connecter au serveur. Y a-t-il un serveur en cours d'exécution à `{{url}}` ?",
"error.chain.unknown": "Erreur inconnue",
"error.chain.causedBy": "Causé par :",
"error.chain.apiError": "Erreur API",
"error.chain.status": "Statut : {{status}}",
"error.chain.retryable": "Réessayable : {{retryable}}",
"error.chain.responseBody": "Corps de la réponse :\n{{body}}",
"error.chain.didYouMean": "Vouliez-vous dire : {{suggestions}}",
"error.chain.modelNotFound": "Modèle introuvable : {{provider}}/{{model}}",
"error.chain.checkConfig": "Vérifiez votre configuration (opencode.json) pour les noms de fournisseur/modèle",
"error.chain.mcpFailed":
"Le serveur MCP \"{{name}}\" a échoué. Notez qu'OpenCode ne supporte pas encore l'authentification MCP.",
"error.chain.providerAuthFailed": "Échec de l'authentification du fournisseur ({{provider}}) : {{message}}",
"error.chain.providerInitFailed":
'Échec de l\'initialisation du fournisseur "{{provider}}". Vérifiez les identifiants et la configuration.',
"error.chain.configJsonInvalid": "Le fichier de configuration à {{path}} n'est pas un JSON(C) valide",
"error.chain.configJsonInvalidWithMessage":
"Le fichier de configuration à {{path}} n'est pas un JSON(C) valide : {{message}}",
"error.chain.configDirectoryTypo":
'Le répertoire "{{dir}}" dans {{path}} n\'est pas valide. Renommez le répertoire en "{{suggestion}}" ou supprimez-le. C\'est une faute de frappe courante.',
"error.chain.configFrontmatterError": "Échec de l'analyse du frontmatter dans {{path}} :\n{{message}}",
"error.chain.configInvalid": "Le fichier de configuration à {{path}} est invalide",
"error.chain.configInvalidWithMessage": "Le fichier de configuration à {{path}} est invalide : {{message}}",
"notification.permission.title": "Permission requise",
"notification.permission.description": "{{sessionTitle}} dans {{projectName}} a besoin d'une permission",
"notification.question.title": "Question",
"notification.question.description": "{{sessionTitle}} dans {{projectName}} a une question",
"notification.action.goToSession": "Aller à la session",
"notification.session.responseReady.title": "Réponse prête",
"notification.session.error.title": "Erreur de session",
"notification.session.error.fallbackDescription": "Une erreur s'est produite",
"home.recentProjects": "Projets récents",
"home.empty.title": "Aucun projet récent",
"home.empty.description": "Commencez par ouvrir un projet local",
"session.tab.session": "Session",
"session.tab.review": "Revue",
"session.tab.context": "Contexte",
"session.panel.reviewAndFiles": "Revue et fichiers",
"session.review.filesChanged": "{{count}} fichiers modifiés",
"session.review.loadingChanges": "Chargement des modifications...",
"session.review.empty": "Aucune modification dans cette session pour l'instant",
"session.messages.renderEarlier": "Afficher les messages précédents",
"session.messages.loadingEarlier": "Chargement des messages précédents...",
"session.messages.loadEarlier": "Charger les messages précédents",
"session.messages.loading": "Chargement des messages...",
"session.context.addToContext": "Ajouter {{selection}} au contexte",
"session.new.worktree.main": "Branche principale",
"session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
"session.new.worktree.create": "Créer un nouvel arbre de travail",
"session.new.lastModified": "Dernière modification",
"session.header.search.placeholder": "Rechercher {{project}}",
"session.header.searchFiles": "Rechercher des fichiers",
"session.share.popover.title": "Publier sur le web",
"session.share.popover.description.shared":
"Cette session est publique sur le web. Elle est accessible à toute personne disposant du lien.",
"session.share.popover.description.unshared":
"Partager la session publiquement sur le web. Elle sera accessible à toute personne disposant du lien.",
"session.share.action.share": "Partager",
"session.share.action.publish": "Publier",
"session.share.action.publishing": "Publication...",
"session.share.action.unpublish": "Dépublier",
"session.share.action.unpublishing": "Dépublication...",
"session.share.action.view": "Voir",
"session.share.copy.copied": "Copié",
"session.share.copy.copyLink": "Copier le lien",
"lsp.tooltip.none": "Aucun serveur LSP",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Chargement du prompt...",
"terminal.loading": "Chargement du terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Fermer le terminal",
"common.closeTab": "Fermer l'onglet",
"common.dismiss": "Ignorer",
"common.requestFailed": "La demande a échoué",
"common.moreOptions": "Plus d'options",
"common.learnMore": "En savoir plus",
"common.rename": "Renommer",
"common.reset": "Réinitialiser",
"common.archive": "Archiver",
"common.delete": "Supprimer",
"common.close": "Fermer",
"common.edit": "Modifier",
"common.loadMore": "Charger plus",
"sidebar.nav.projectsAndSessions": "Projets et sessions",
"sidebar.settings": "Paramètres",
"sidebar.help": "Aide",
"sidebar.workspaces.enable": "Activer les espaces de travail",
"sidebar.workspaces.disable": "Désactiver les espaces de travail",
"sidebar.gettingStarted.title": "Commencer",
"sidebar.gettingStarted.line1":
"OpenCode inclut des modèles gratuits pour que vous puissiez commencer immédiatement.",
"sidebar.gettingStarted.line2":
"Connectez n'importe quel fournisseur pour utiliser des modèles, y compris Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sessions récentes",
"sidebar.project.viewAllSessions": "Voir toutes les sessions",
"settings.section.desktop": "Bureau",
"settings.tab.general": "Général",
"settings.tab.shortcuts": "Raccourcis",
"settings.general.section.appearance": "Apparence",
"settings.general.section.notifications": "Notifications système",
"settings.general.section.sounds": "Effets sonores",
"settings.general.row.language.title": "Langue",
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
"settings.general.row.appearance.title": "Apparence",
"settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil",
"settings.general.row.theme.title": "Thème",
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
"settings.general.row.font.title": "Police",
"settings.general.row.font.description": "Personnaliser la police mono utilisée dans les blocs de code",
"settings.general.notifications.agent.title": "Agent",
"settings.general.notifications.agent.description":
"Afficher une notification système lorsque l'agent a terminé ou nécessite une attention",
"settings.general.notifications.permissions.title": "Permissions",
"settings.general.notifications.permissions.description":
"Afficher une notification système lorsqu'une permission est requise",
"settings.general.notifications.errors.title": "Erreurs",
"settings.general.notifications.errors.description": "Afficher une notification système lorsqu'une erreur se produit",
"settings.general.sounds.agent.title": "Agent",
"settings.general.sounds.agent.description": "Jouer un son lorsque l'agent a terminé ou nécessite une attention",
"settings.general.sounds.permissions.title": "Permissions",
"settings.general.sounds.permissions.description": "Jouer un son lorsqu'une permission est requise",
"settings.general.sounds.errors.title": "Erreurs",
"settings.general.sounds.errors.description": "Jouer un son lorsqu'une erreur se produit",
"settings.shortcuts.title": "Raccourcis clavier",
"settings.shortcuts.reset.button": "Rétablir les défauts",
"settings.shortcuts.reset.toast.title": "Raccourcis réinitialisés",
"settings.shortcuts.reset.toast.description": "Les raccourcis clavier ont été réinitialisés aux valeurs par défaut.",
"settings.shortcuts.conflict.title": "Raccourci déjà utilisé",
"settings.shortcuts.conflict.description": "{{keybind}} est déjà assigné à {{titles}}.",
"settings.shortcuts.unassigned": "Non assigné",
"settings.shortcuts.pressKeys": "Appuyez sur les touches",
"settings.shortcuts.search.placeholder": "Rechercher des raccourcis",
"settings.shortcuts.search.empty": "Aucun raccourci trouvé",
"settings.shortcuts.group.general": "Général",
"settings.shortcuts.group.session": "Session",
"settings.shortcuts.group.navigation": "Navigation",
"settings.shortcuts.group.modelAndAgent": "Modèle et agent",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Prompt",
"settings.providers.title": "Fournisseurs",
"settings.providers.description": "Les paramètres des fournisseurs seront configurables ici.",
"settings.models.title": "Modèles",
"settings.models.description": "Les paramètres des modèles seront configurables ici.",
"settings.agents.title": "Agents",
"settings.agents.description": "Les paramètres des agents seront configurables ici.",
"settings.commands.title": "Commandes",
"settings.commands.description": "Les paramètres des commandes seront configurables ici.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "Les paramètres MCP seront configurables ici.",
"settings.permissions.title": "Permissions",
"settings.permissions.description": "Contrôlez les outils que le serveur peut utiliser par défaut.",
"settings.permissions.section.tools": "Outils",
"settings.permissions.toast.updateFailed.title": "Échec de la mise à jour des permissions",
"settings.permissions.action.allow": "Autoriser",
"settings.permissions.action.ask": "Demander",
"settings.permissions.action.deny": "Refuser",
"settings.permissions.tool.read.title": "Lire",
"settings.permissions.tool.read.description": "Lecture d'un fichier (correspond au chemin du fichier)",
"settings.permissions.tool.edit.title": "Modifier",
"settings.permissions.tool.edit.description":
"Modifier des fichiers, y compris les modifications, écritures, patchs et multi-modifications",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Correspondre aux fichiers utilisant des modèles glob",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description":
"Rechercher dans le contenu des fichiers à l'aide d'expressions régulières",
"settings.permissions.tool.list.title": "Lister",
"settings.permissions.tool.list.description": "Lister les fichiers dans un répertoire",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Exécuter des commandes shell",
"settings.permissions.tool.task.title": "Tâche",
"settings.permissions.tool.task.description": "Lancer des sous-agents",
"settings.permissions.tool.skill.title": "Compétence",
"settings.permissions.tool.skill.description": "Charger une compétence par son nom",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Exécuter des requêtes de serveur de langage",
"settings.permissions.tool.todoread.title": "Lire Todo",
"settings.permissions.tool.todoread.description": "Lire la liste de tâches",
"settings.permissions.tool.todowrite.title": "Écrire Todo",
"settings.permissions.tool.todowrite.description": "Mettre à jour la liste de tâches",
"settings.permissions.tool.webfetch.title": "Récupération Web",
"settings.permissions.tool.webfetch.description": "Récupérer le contenu d'une URL",
"settings.permissions.tool.websearch.title": "Recherche Web",
"settings.permissions.tool.websearch.description": "Rechercher sur le web",
"settings.permissions.tool.codesearch.title": "Recherche de code",
"settings.permissions.tool.codesearch.description": "Rechercher du code sur le web",
"settings.permissions.tool.external_directory.title": "Répertoire externe",
"settings.permissions.tool.external_directory.description": "Accéder aux fichiers en dehors du répertoire du projet",
"settings.permissions.tool.doom_loop.title": "Boucle infernale",
"settings.permissions.tool.doom_loop.description": "Détecter les appels d'outils répétés avec une entrée identique",
"session.delete.failed.title": "Échec de la suppression de la session",
"session.delete.title": "Supprimer la session",
"session.delete.confirm": 'Supprimer la session "{{name}}" ?',
"session.delete.button": "Supprimer la session",
"workspace.new": "Nouvel espace de travail",
"workspace.type.local": "local",
"workspace.type.sandbox": "bac à sable",
"workspace.create.failed.title": "Échec de la création de l'espace de travail",
"workspace.delete.failed.title": "Échec de la suppression de l'espace de travail",
"workspace.resetting.title": "Réinitialisation de l'espace de travail",
"workspace.resetting.description": "Cela peut prendre une minute.",
"workspace.reset.failed.title": "Échec de la réinitialisation de l'espace de travail",
"workspace.reset.success.title": "Espace de travail réinitialisé",
"workspace.reset.success.description": "L'espace de travail correspond maintenant à la branche par défaut.",
"workspace.status.checking": "Vérification des modifications non fusionnées...",
"workspace.status.error": "Impossible de vérifier le statut git.",
"workspace.status.clean": "Aucune modification non fusionnée détectée.",
"workspace.status.dirty": "Modifications non fusionnées détectées dans cet espace de travail.",
"workspace.delete.title": "Supprimer l'espace de travail",
"workspace.delete.confirm": 'Supprimer l\'espace de travail "{{name}}" ?',
"workspace.delete.button": "Supprimer l'espace de travail",
"workspace.reset.title": "Réinitialiser l'espace de travail",
"workspace.reset.confirm": 'Réinitialiser l\'espace de travail "{{name}}" ?',
"workspace.reset.button": "Réinitialiser l'espace de travail",
"workspace.reset.archived.none": "Aucune session active ne sera archivée.",
"workspace.reset.archived.one": "1 session sera archivée.",
"workspace.reset.archived.many": "{{count}} sessions seront archivées.",
"workspace.reset.note": "Cela réinitialisera l'espace de travail pour correspondre à la branche par défaut.",
}

579
packages/app/src/i18n/ja.ts Normal file
View File

@@ -0,0 +1,579 @@
export const dict = {
"command.category.suggested": "おすすめ",
"command.category.view": "表示",
"command.category.project": "プロジェクト",
"command.category.provider": "プロバイダー",
"command.category.server": "サーバー",
"command.category.session": "セッション",
"command.category.theme": "テーマ",
"command.category.language": "言語",
"command.category.file": "ファイル",
"command.category.terminal": "ターミナル",
"command.category.model": "モデル",
"command.category.mcp": "MCP",
"command.category.agent": "エージェント",
"command.category.permissions": "権限",
"command.category.workspace": "ワークスペース",
"theme.scheme.system": "システム",
"theme.scheme.light": "ライト",
"theme.scheme.dark": "ダーク",
"command.sidebar.toggle": "サイドバーの切り替え",
"command.project.open": "プロジェクトを開く",
"command.provider.connect": "プロバイダーに接続",
"command.server.switch": "サーバーの切り替え",
"command.session.previous": "前のセッション",
"command.session.next": "次のセッション",
"command.session.archive": "セッションをアーカイブ",
"command.palette": "コマンドパレット",
"command.theme.cycle": "テーマの切り替え",
"command.theme.set": "テーマを使用: {{theme}}",
"command.theme.scheme.cycle": "配色の切り替え",
"command.theme.scheme.set": "配色を使用: {{scheme}}",
"command.language.cycle": "言語の切り替え",
"command.language.set": "言語を使用: {{language}}",
"command.session.new": "新しいセッション",
"command.file.open": "ファイルを開く",
"command.file.open.description": "ファイルとコマンドを検索",
"command.terminal.toggle": "ターミナルの切り替え",
"command.review.toggle": "レビューの切り替え",
"command.terminal.new": "新しいターミナル",
"command.terminal.new.description": "新しいターミナルタブを作成",
"command.steps.toggle": "ステップの切り替え",
"command.steps.toggle.description": "現在のメッセージのステップを表示または非表示",
"command.message.previous": "前のメッセージ",
"command.message.previous.description": "前のユーザーメッセージに移動",
"command.message.next": "次のメッセージ",
"command.message.next.description": "次のユーザーメッセージに移動",
"command.model.choose": "モデルを選択",
"command.model.choose.description": "別のモデルを選択",
"command.mcp.toggle": "MCPの切り替え",
"command.mcp.toggle.description": "MCPを切り替える",
"command.agent.cycle": "エージェントの切り替え",
"command.agent.cycle.description": "次のエージェントに切り替え",
"command.agent.cycle.reverse": "エージェントを逆順に切り替え",
"command.agent.cycle.reverse.description": "前のエージェントに切り替え",
"command.model.variant.cycle": "思考レベルの切り替え",
"command.model.variant.cycle.description": "次の思考レベルに切り替え",
"command.permissions.autoaccept.enable": "編集を自動承認",
"command.permissions.autoaccept.disable": "編集の自動承認を停止",
"command.session.undo": "元に戻す",
"command.session.undo.description": "最後のメッセージを元に戻す",
"command.session.redo": "やり直す",
"command.session.redo.description": "元に戻したメッセージをやり直す",
"command.session.compact": "セッションを圧縮",
"command.session.compact.description": "セッションを要約してコンテキストサイズを削減",
"command.session.fork": "メッセージからフォーク",
"command.session.fork.description": "以前のメッセージから新しいセッションを作成",
"command.session.share": "セッションを共有",
"command.session.share.description": "このセッションを共有しURLをクリップボードにコピー",
"command.session.unshare": "セッションの共有を停止",
"command.session.unshare.description": "このセッションの共有を停止",
"palette.search.placeholder": "ファイルとコマンドを検索",
"palette.empty": "結果が見つかりません",
"palette.group.commands": "コマンド",
"palette.group.files": "ファイル",
"dialog.provider.search.placeholder": "プロバイダーを検索",
"dialog.provider.empty": "プロバイダーが見つかりません",
"dialog.provider.group.popular": "人気",
"dialog.provider.group.other": "その他",
"dialog.provider.tag.recommended": "推奨",
"dialog.provider.anthropic.note": "Claude Pro/MaxまたはAPIキーで接続",
"dialog.provider.openai.note": "ChatGPT Pro/PlusまたはAPIキーで接続",
"dialog.provider.copilot.note": "CopilotまたはAPIキーで接続",
"dialog.model.select.title": "モデルを選択",
"dialog.model.search.placeholder": "モデルを検索",
"dialog.model.empty": "モデルが見つかりません",
"dialog.model.manage": "モデルを管理",
"dialog.model.manage.description": "モデルセレクターに表示するモデルをカスタマイズします。",
"dialog.model.unpaid.freeModels.title": "OpenCodeが提供する無料モデル",
"dialog.model.unpaid.addMore.title": "人気のプロバイダーからモデルを追加",
"dialog.provider.viewAll": "すべてのプロバイダーを表示",
"provider.connect.title": "{{provider}}を接続",
"provider.connect.title.anthropicProMax": "Claude Pro/Maxでログイン",
"provider.connect.selectMethod": "{{provider}}のログイン方法を選択してください。",
"provider.connect.method.apiKey": "APIキー",
"provider.connect.status.inProgress": "認証中...",
"provider.connect.status.waiting": "認証を待機中...",
"provider.connect.status.failed": "認証に失敗しました: {{error}}",
"provider.connect.apiKey.description":
"{{provider}}のAPIキーを入力してアカウントを接続し、OpenCodeで{{provider}}モデルを使用します。",
"provider.connect.apiKey.label": "{{provider}} APIキー",
"provider.connect.apiKey.placeholder": "APIキー",
"provider.connect.apiKey.required": "APIキーが必要です",
"provider.connect.opencodeZen.line1":
"OpenCode Zenは、コーディングエージェント向けに最適化された信頼性の高いモデルへのアクセスを提供します。",
"provider.connect.opencodeZen.line2": "1つのAPIキーで、Claude、GPT、Gemini、GLMなどのモデルにアクセスできます。",
"provider.connect.opencodeZen.visit.prefix": " ",
"provider.connect.opencodeZen.visit.suffix": " にアクセスしてAPIキーを取得してください。",
"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}} 認証コード",
"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}}モデルを使用してください。",
"provider.connect.oauth.auto.confirmationCode": "確認コード",
"provider.connect.toast.connected.title": "{{provider}}が接続されました",
"provider.connect.toast.connected.description": "{{provider}}モデルが使用可能になりました。",
"model.tag.free": "無料",
"model.tag.latest": "最新",
"common.search.placeholder": "検索",
"common.goBack": "戻る",
"common.loading": "読み込み中",
"common.cancel": "キャンセル",
"common.submit": "送信",
"common.save": "保存",
"common.saving": "保存中...",
"common.default": "デフォルト",
"common.attachment": "添付ファイル",
"prompt.placeholder.shell": "シェルコマンドを入力...",
"prompt.placeholder.normal": '何でも聞いてください... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "escで終了",
"prompt.example.1": "コードベースのTODOを修正",
"prompt.example.2": "このプロジェクトの技術スタックは何ですか?",
"prompt.example.3": "壊れたテストを修正",
"prompt.example.4": "認証の仕組みを説明して",
"prompt.example.5": "セキュリティの脆弱性を見つけて修正",
"prompt.example.6": "ユーザーサービスのユニットテストを追加",
"prompt.example.7": "この関数を読みやすくリファクタリング",
"prompt.example.8": "このエラーはどういう意味ですか?",
"prompt.example.9": "この問題のデバッグを手伝って",
"prompt.example.10": "APIドキュメントを生成",
"prompt.example.11": "データベースクエリを最適化",
"prompt.example.12": "入力バリデーションを追加",
"prompt.example.13": "〜の新しいコンポーネントを作成",
"prompt.example.14": "このプロジェクトをデプロイするには?",
"prompt.example.15": "ベストプラクティスの観点でコードをレビュー",
"prompt.example.16": "この関数にエラーハンドリングを追加",
"prompt.example.17": "この正規表現パターンを説明して",
"prompt.example.18": "これをTypeScriptに変換",
"prompt.example.19": "コードベース全体にログを追加",
"prompt.example.20": "古い依存関係はどれですか?",
"prompt.example.21": "マイグレーションスクリプトの作成を手伝って",
"prompt.example.22": "このエンドポイントにキャッシュを実装",
"prompt.example.23": "このリストにページネーションを追加",
"prompt.example.24": "〜のCLIコマンドを作成",
"prompt.example.25": "ここでは環境変数はどう機能しますか?",
"prompt.popover.emptyResults": "一致する結果がありません",
"prompt.popover.emptyCommands": "一致するコマンドがありません",
"prompt.dropzone.label": "画像またはPDFをここにドロップ",
"prompt.slash.badge.custom": "カスタム",
"prompt.context.active": "アクティブ",
"prompt.context.includeActiveFile": "アクティブなファイルを含める",
"prompt.context.removeActiveFile": "コンテキストからアクティブなファイルを削除",
"prompt.context.removeFile": "コンテキストからファイルを削除",
"prompt.action.attachFile": "ファイルを添付",
"prompt.attachment.remove": "添付ファイルを削除",
"prompt.action.send": "送信",
"prompt.action.stop": "停止",
"prompt.toast.pasteUnsupported.title": "サポートされていない貼り付け",
"prompt.toast.pasteUnsupported.description": "ここでは画像またはPDFのみ貼り付け可能です。",
"prompt.toast.modelAgentRequired.title": "エージェントとモデルを選択",
"prompt.toast.modelAgentRequired.description": "プロンプトを送信する前にエージェントとモデルを選択してください。",
"prompt.toast.worktreeCreateFailed.title": "ワークツリーの作成に失敗しました",
"prompt.toast.sessionCreateFailed.title": "セッションの作成に失敗しました",
"prompt.toast.shellSendFailed.title": "シェルコマンドの送信に失敗しました",
"prompt.toast.commandSendFailed.title": "コマンドの送信に失敗しました",
"prompt.toast.promptSendFailed.title": "プロンプトの送信に失敗しました",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{total}}個中{{enabled}}個が有効",
"dialog.mcp.empty": "MCPが設定されていません",
"mcp.status.connected": "接続済み",
"mcp.status.failed": "失敗",
"mcp.status.needs_auth": "認証が必要",
"mcp.status.disabled": "無効",
"dialog.fork.empty": "フォーク元のメッセージがありません",
"dialog.directory.search.placeholder": "フォルダを検索",
"dialog.directory.empty": "フォルダが見つかりません",
"dialog.server.title": "サーバー",
"dialog.server.description": "このアプリが接続するOpenCodeサーバーを切り替えます。",
"dialog.server.search.placeholder": "サーバーを検索",
"dialog.server.empty": "サーバーはまだありません",
"dialog.server.add.title": "サーバーを追加",
"dialog.server.add.url": "サーバーURL",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "サーバーに接続できませんでした",
"dialog.server.add.checking": "確認中...",
"dialog.server.add.button": "追加",
"dialog.server.default.title": "デフォルトサーバー",
"dialog.server.default.description":
"ローカルサーバーを起動する代わりに、アプリ起動時にこのサーバーに接続します。再起動が必要です。",
"dialog.server.default.none": "サーバーが選択されていません",
"dialog.server.default.set": "現在のサーバーをデフォルトに設定",
"dialog.server.default.clear": "クリア",
"dialog.server.action.remove": "サーバーを削除",
"dialog.project.edit.title": "プロジェクトを編集",
"dialog.project.edit.name": "名前",
"dialog.project.edit.icon": "アイコン",
"dialog.project.edit.icon.alt": "プロジェクトアイコン",
"dialog.project.edit.icon.hint": "クリックまたは画像をドラッグ",
"dialog.project.edit.icon.recommended": "推奨: 128x128px",
"dialog.project.edit.color": "色",
"dialog.project.edit.color.select": "{{color}}の色を選択",
"context.breakdown.title": "コンテキストの内訳",
"context.breakdown.note": '入力トークンのおおよその内訳です。"その他"にはツールの定義やオーバーヘッドが含まれます。',
"context.breakdown.system": "システム",
"context.breakdown.user": "ユーザー",
"context.breakdown.assistant": "アシスタント",
"context.breakdown.tool": "ツール呼び出し",
"context.breakdown.other": "その他",
"context.systemPrompt.title": "システムプロンプト",
"context.rawMessages.title": "生のメッセージ",
"context.stats.session": "セッション",
"context.stats.messages": "メッセージ",
"context.stats.provider": "プロバイダー",
"context.stats.model": "モデル",
"context.stats.limit": "コンテキスト制限",
"context.stats.totalTokens": "総トークン数",
"context.stats.usage": "使用量",
"context.stats.inputTokens": "入力トークン",
"context.stats.outputTokens": "出力トークン",
"context.stats.reasoningTokens": "推論トークン",
"context.stats.cacheTokens": "キャッシュトークン (読込/書込)",
"context.stats.userMessages": "ユーザーメッセージ",
"context.stats.assistantMessages": "アシスタントメッセージ",
"context.stats.totalCost": "総コスト",
"context.stats.sessionCreated": "セッション作成日時",
"context.stats.lastActivity": "最終アクティビティ",
"context.usage.tokens": "トークン",
"context.usage.usage": "使用量",
"context.usage.cost": "コスト",
"context.usage.clickToView": "クリックしてコンテキストを表示",
"context.usage.view": "コンテキスト使用量を表示",
"language.en": "英語",
"language.zh": "中国語(簡体字)",
"language.zht": "中国語(繁体字)",
"language.ko": "韓国語",
"language.de": "ドイツ語",
"language.es": "スペイン語",
"language.fr": "フランス語",
"language.ja": "日本語",
"language.da": "デンマーク語",
"language.ru": "ロシア語",
"language.pl": "ポーランド語",
"language.ar": "アラビア語",
"language.no": "ノルウェー語",
"language.br": "ポルトガル語(ブラジル)",
"toast.language.title": "言語",
"toast.language.description": "{{language}}に切り替えました",
"toast.theme.title": "テーマが切り替わりました",
"toast.scheme.title": "配色",
"toast.permissions.autoaccept.on.title": "編集を自動承認中",
"toast.permissions.autoaccept.on.description": "編集と書き込みの権限は自動的に承認されます",
"toast.permissions.autoaccept.off.title": "編集の自動承認を停止しました",
"toast.permissions.autoaccept.off.description": "編集と書き込みの権限には承認が必要です",
"toast.model.none.title": "モデルが選択されていません",
"toast.model.none.description": "このセッションを要約するにはプロバイダーを接続してください",
"toast.file.loadFailed.title": "ファイルの読み込みに失敗しました",
"toast.session.share.copyFailed.title": "URLのコピーに失敗しました",
"toast.session.share.success.title": "セッションを共有しました",
"toast.session.share.success.description": "共有URLをクリップボードにコピーしました",
"toast.session.share.failed.title": "セッションの共有に失敗しました",
"toast.session.share.failed.description": "セッションの共有中にエラーが発生しました",
"toast.session.unshare.success.title": "セッションの共有を解除しました",
"toast.session.unshare.success.description": "セッションの共有解除に成功しました!",
"toast.session.unshare.failed.title": "セッションの共有解除に失敗しました",
"toast.session.unshare.failed.description": "セッションの共有解除中にエラーが発生しました",
"toast.session.listFailed.title": "{{project}}のセッション読み込みに失敗しました",
"toast.update.title": "アップデートが利用可能です",
"toast.update.description": "OpenCodeの新しいバージョン ({{version}}) がインストール可能です。",
"toast.update.action.installRestart": "インストールして再起動",
"toast.update.action.notYet": "今はしない",
"error.page.title": "問題が発生しました",
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
"error.page.details.label": "エラー詳細",
"error.page.action.restart": "再起動",
"error.page.action.checking": "確認中...",
"error.page.action.checkUpdates": "アップデートを確認",
"error.page.action.updateTo": "{{version}}にアップデート",
"error.page.report.prefix": "このエラーをOpenCodeチームに報告してください: ",
"error.page.report.discord": "Discord",
"error.page.version": "バージョン: {{version}}",
"error.dev.rootNotFound":
"ルート要素が見つかりません。index.htmlに追加するのを忘れていませんかまたはid属性のスペルが間違っていませんか",
"error.globalSync.connectFailed": "サーバーに接続できませんでした。`{{url}}`でサーバーが実行されていますか?",
"error.chain.unknown": "不明なエラー",
"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.checkConfig": "config (opencode.json) のプロバイダー/モデル名を確認してください",
"error.chain.mcpFailed": 'MCPサーバー "{{name}}" が失敗しました。注意: OpenCodeはまだMCP認証をサポートしていません。',
"error.chain.providerAuthFailed": "プロバイダー認証に失敗しました ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'プロバイダー "{{provider}}" の初期化に失敗しました。認証情報と設定を確認してください。',
"error.chain.configJsonInvalid": "{{path}} の設定ファイルは有効なJSON(C)ではありません",
"error.chain.configJsonInvalidWithMessage": "{{path}} の設定ファイルは有効なJSON(C)ではありません: {{message}}",
"error.chain.configDirectoryTypo":
'{{path}} 内のディレクトリ "{{dir}}" は無効です。"{{suggestion}}" に名前を変更するか削除してください。これはよくあるタイプミスです。',
"error.chain.configFrontmatterError": "{{path}} のフロントマターの解析に失敗しました:\n{{message}}",
"error.chain.configInvalid": "{{path}} の設定ファイルが無効です",
"error.chain.configInvalidWithMessage": "{{path}} の設定ファイルが無効です: {{message}}",
"notification.permission.title": "権限が必要です",
"notification.permission.description": "{{projectName}} の {{sessionTitle}} が権限を必要としています",
"notification.question.title": "質問",
"notification.question.description": "{{projectName}} の {{sessionTitle}} から質問があります",
"notification.action.goToSession": "セッションへ移動",
"notification.session.responseReady.title": "応答の準備ができました",
"notification.session.error.title": "セッションエラー",
"notification.session.error.fallbackDescription": "エラーが発生しました",
"home.recentProjects": "最近のプロジェクト",
"home.empty.title": "最近のプロジェクトはありません",
"home.empty.description": "ローカルプロジェクトを開いて始めましょう",
"session.tab.session": "セッション",
"session.tab.review": "レビュー",
"session.tab.context": "コンテキスト",
"session.panel.reviewAndFiles": "レビューとファイル",
"session.review.filesChanged": "{{count}} ファイル変更",
"session.review.loadingChanges": "変更を読み込み中...",
"session.review.empty": "このセッションでの変更はまだありません",
"session.messages.renderEarlier": "以前のメッセージを表示",
"session.messages.loadingEarlier": "以前のメッセージを読み込み中...",
"session.messages.loadEarlier": "以前のメッセージを読み込む",
"session.messages.loading": "メッセージを読み込み中...",
"session.context.addToContext": "{{selection}}をコンテキストに追加",
"session.new.worktree.main": "メインブランチ",
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
"session.new.worktree.create": "新しいワークツリーを作成",
"session.new.lastModified": "最終更新",
"session.header.search.placeholder": "{{project}}を検索",
"session.header.searchFiles": "ファイルを検索",
"session.share.popover.title": "ウェブで公開",
"session.share.popover.description.shared":
"このセッションはウェブで公開されています。リンクを知っている人なら誰でもアクセスできます。",
"session.share.popover.description.unshared":
"セッションをウェブで公開します。リンクを知っている人なら誰でもアクセスできるようになります。",
"session.share.action.share": "共有",
"session.share.action.publish": "公開",
"session.share.action.publishing": "公開中...",
"session.share.action.unpublish": "非公開にする",
"session.share.action.unpublishing": "非公開にしています...",
"session.share.action.view": "表示",
"session.share.copy.copied": "コピーしました",
"session.share.copy.copyLink": "リンクをコピー",
"lsp.tooltip.none": "LSPサーバーなし",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "プロンプトを読み込み中...",
"terminal.loading": "ターミナルを読み込み中...",
"terminal.title": "ターミナル",
"terminal.title.numbered": "ターミナル {{number}}",
"terminal.close": "ターミナルを閉じる",
"common.closeTab": "タブを閉じる",
"common.dismiss": "閉じる",
"common.requestFailed": "リクエスト失敗",
"common.moreOptions": "その他のオプション",
"common.learnMore": "詳細",
"common.rename": "名前変更",
"common.reset": "リセット",
"common.archive": "アーカイブ",
"common.delete": "削除",
"common.close": "閉じる",
"common.edit": "編集",
"common.loadMore": "さらに読み込む",
"sidebar.nav.projectsAndSessions": "プロジェクトとセッション",
"sidebar.settings": "設定",
"sidebar.help": "ヘルプ",
"sidebar.workspaces.enable": "ワークスペースを有効化",
"sidebar.workspaces.disable": "ワークスペースを無効化",
"sidebar.gettingStarted.title": "はじめに",
"sidebar.gettingStarted.line1": "OpenCodeには無料モデルが含まれているため、すぐに開始できます。",
"sidebar.gettingStarted.line2": "プロバイダーを接続して、Claude、GPT、Geminiなどのモデルを使用できます。",
"sidebar.project.recentSessions": "最近のセッション",
"sidebar.project.viewAllSessions": "すべてのセッションを表示",
"settings.section.desktop": "デスクトップ",
"settings.tab.general": "一般",
"settings.tab.shortcuts": "ショートカット",
"settings.general.section.appearance": "外観",
"settings.general.section.notifications": "システム通知",
"settings.general.section.sounds": "効果音",
"settings.general.row.language.title": "言語",
"settings.general.row.language.description": "OpenCodeの表示言語を変更します",
"settings.general.row.appearance.title": "外観",
"settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします",
"settings.general.row.theme.title": "テーマ",
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
"settings.general.row.font.title": "フォント",
"settings.general.row.font.description": "コードブロックで使用する等幅フォントをカスタマイズします",
"settings.general.notifications.agent.title": "エージェント",
"settings.general.notifications.agent.description":
"エージェントが完了したか、注意が必要な場合にシステム通知を表示します",
"settings.general.notifications.permissions.title": "権限",
"settings.general.notifications.permissions.description": "権限が必要な場合にシステム通知を表示します",
"settings.general.notifications.errors.title": "エラー",
"settings.general.notifications.errors.description": "エラーが発生した場合にシステム通知を表示します",
"settings.general.sounds.agent.title": "エージェント",
"settings.general.sounds.agent.description": "エージェントが完了したか、注意が必要な場合に音を再生します",
"settings.general.sounds.permissions.title": "権限",
"settings.general.sounds.permissions.description": "権限が必要な場合に音を再生します",
"settings.general.sounds.errors.title": "エラー",
"settings.general.sounds.errors.description": "エラーが発生した場合に音を再生します",
"settings.shortcuts.title": "キーボードショートカット",
"settings.shortcuts.reset.button": "デフォルトにリセット",
"settings.shortcuts.reset.toast.title": "ショートカットをリセットしました",
"settings.shortcuts.reset.toast.description": "キーボードショートカットがデフォルトにリセットされました。",
"settings.shortcuts.conflict.title": "ショートカットは既に使用されています",
"settings.shortcuts.conflict.description": "{{keybind}} は既に {{titles}} に割り当てられています。",
"settings.shortcuts.unassigned": "未割り当て",
"settings.shortcuts.pressKeys": "キーを押してください",
"settings.shortcuts.search.placeholder": "ショートカットを検索",
"settings.shortcuts.search.empty": "ショートカットが見つかりません",
"settings.shortcuts.group.general": "一般",
"settings.shortcuts.group.session": "セッション",
"settings.shortcuts.group.navigation": "ナビゲーション",
"settings.shortcuts.group.modelAndAgent": "モデルとエージェント",
"settings.shortcuts.group.terminal": "ターミナル",
"settings.shortcuts.group.prompt": "プロンプト",
"settings.providers.title": "プロバイダー",
"settings.providers.description": "プロバイダー設定はここで構成できます。",
"settings.models.title": "モデル",
"settings.models.description": "モデル設定はここで構成できます。",
"settings.agents.title": "エージェント",
"settings.agents.description": "エージェント設定はここで構成できます。",
"settings.commands.title": "コマンド",
"settings.commands.description": "コマンド設定はここで構成できます。",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP設定はここで構成できます。",
"settings.permissions.title": "権限",
"settings.permissions.description": "サーバーがデフォルトで使用できるツールを制御します。",
"settings.permissions.section.tools": "ツール",
"settings.permissions.toast.updateFailed.title": "権限の更新に失敗しました",
"settings.permissions.action.allow": "許可",
"settings.permissions.action.ask": "確認",
"settings.permissions.action.deny": "拒否",
"settings.permissions.tool.read.title": "読み込み",
"settings.permissions.tool.read.description": "ファイルの読み込み (ファイルパスに一致)",
"settings.permissions.tool.edit.title": "編集",
"settings.permissions.tool.edit.description": "ファイルの変更(編集、書き込み、パッチ、複数編集を含む)",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Globパターンを使用したファイルの一致",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "正規表現を使用したファイル内容の検索",
"settings.permissions.tool.list.title": "リスト",
"settings.permissions.tool.list.description": "ディレクトリ内のファイル一覧表示",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "シェルコマンドの実行",
"settings.permissions.tool.task.title": "タスク",
"settings.permissions.tool.task.description": "サブエージェントの起動",
"settings.permissions.tool.skill.title": "スキル",
"settings.permissions.tool.skill.description": "名前によるスキルの読み込み",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "言語サーバークエリの実行",
"settings.permissions.tool.todoread.title": "Todo読み込み",
"settings.permissions.tool.todoread.description": "Todoリストの読み込み",
"settings.permissions.tool.todowrite.title": "Todo書き込み",
"settings.permissions.tool.todowrite.description": "Todoリストの更新",
"settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "URLからコンテンツを取得",
"settings.permissions.tool.websearch.title": "Web Search",
"settings.permissions.tool.websearch.description": "ウェブを検索",
"settings.permissions.tool.codesearch.title": "Code Search",
"settings.permissions.tool.codesearch.description": "ウェブ上のコードを検索",
"settings.permissions.tool.external_directory.title": "外部ディレクトリ",
"settings.permissions.tool.external_directory.description": "プロジェクトディレクトリ外のファイルへのアクセス",
"settings.permissions.tool.doom_loop.title": "Doom Loop",
"settings.permissions.tool.doom_loop.description": "同一入力による繰り返しのツール呼び出しを検出",
"session.delete.failed.title": "セッションの削除に失敗しました",
"session.delete.title": "セッションの削除",
"session.delete.confirm": 'セッション "{{name}}" を削除しますか?',
"session.delete.button": "セッションを削除",
"workspace.new": "新しいワークスペース",
"workspace.type.local": "ローカル",
"workspace.type.sandbox": "サンドボックス",
"workspace.create.failed.title": "ワークスペースの作成に失敗しました",
"workspace.delete.failed.title": "ワークスペースの削除に失敗しました",
"workspace.resetting.title": "ワークスペースをリセット中",
"workspace.resetting.description": "これには少し時間がかかる場合があります。",
"workspace.reset.failed.title": "ワークスペースのリセットに失敗しました",
"workspace.reset.success.title": "ワークスペースをリセットしました",
"workspace.reset.success.description": "ワークスペースはデフォルトブランチと一致しています。",
"workspace.status.checking": "未マージの変更を確認中...",
"workspace.status.error": "gitステータスを確認できません。",
"workspace.status.clean": "未マージの変更は検出されませんでした。",
"workspace.status.dirty": "このワークスペースで未マージの変更が検出されました。",
"workspace.delete.title": "ワークスペースの削除",
"workspace.delete.confirm": 'ワークスペース "{{name}}" を削除しますか?',
"workspace.delete.button": "ワークスペースを削除",
"workspace.reset.title": "ワークスペースのリセット",
"workspace.reset.confirm": 'ワークスペース "{{name}}" をリセットしますか?',
"workspace.reset.button": "ワークスペースをリセット",
"workspace.reset.archived.none": "アクティブなセッションはアーカイブされません。",
"workspace.reset.archived.one": "1つのセッションがアーカイブされます。",
"workspace.reset.archived.many": "{{count}}個のセッションがアーカイブされます。",
"workspace.reset.note": "これにより、ワークスペースはデフォルトブランチと一致するようにリセットされます。",
}

580
packages/app/src/i18n/ko.ts Normal file
View File

@@ -0,0 +1,580 @@
import { dict as en } from "./en"
type Keys = keyof typeof en
export const dict = {
"command.category.suggested": "추천",
"command.category.view": "보기",
"command.category.project": "프로젝트",
"command.category.provider": "공급자",
"command.category.server": "서버",
"command.category.session": "세션",
"command.category.theme": "테마",
"command.category.language": "언어",
"command.category.file": "파일",
"command.category.terminal": "터미널",
"command.category.model": "모델",
"command.category.mcp": "MCP",
"command.category.agent": "에이전트",
"command.category.permissions": "권한",
"command.category.workspace": "작업 공간",
"theme.scheme.system": "시스템",
"theme.scheme.light": "라이트",
"theme.scheme.dark": "다크",
"command.sidebar.toggle": "사이드바 토글",
"command.project.open": "프로젝트 열기",
"command.provider.connect": "공급자 연결",
"command.server.switch": "서버 전환",
"command.session.previous": "이전 세션",
"command.session.next": "다음 세션",
"command.session.archive": "세션 보관",
"command.palette": "명령 팔레트",
"command.theme.cycle": "테마 순환",
"command.theme.set": "테마 사용: {{theme}}",
"command.theme.scheme.cycle": "색상 테마 순환",
"command.theme.scheme.set": "색상 테마 사용: {{scheme}}",
"command.language.cycle": "언어 순환",
"command.language.set": "언어 사용: {{language}}",
"command.session.new": "새 세션",
"command.file.open": "파일 열기",
"command.file.open.description": "파일 및 명령어 검색",
"command.terminal.toggle": "터미널 토글",
"command.review.toggle": "검토 토글",
"command.terminal.new": "새 터미널",
"command.terminal.new.description": "새 터미널 탭 생성",
"command.steps.toggle": "단계 토글",
"command.steps.toggle.description": "현재 메시지의 단계 표시/숨기기",
"command.message.previous": "이전 메시지",
"command.message.previous.description": "이전 사용자 메시지로 이동",
"command.message.next": "다음 메시지",
"command.message.next.description": "다음 사용자 메시지로 이동",
"command.model.choose": "모델 선택",
"command.model.choose.description": "다른 모델 선택",
"command.mcp.toggle": "MCP 토글",
"command.mcp.toggle.description": "MCP 토글",
"command.agent.cycle": "에이전트 순환",
"command.agent.cycle.description": "다음 에이전트로 전환",
"command.agent.cycle.reverse": "에이전트 역순환",
"command.agent.cycle.reverse.description": "이전 에이전트로 전환",
"command.model.variant.cycle": "생각 수준 순환",
"command.model.variant.cycle.description": "다음 생각 수준으로 전환",
"command.permissions.autoaccept.enable": "편집 자동 수락",
"command.permissions.autoaccept.disable": "편집 자동 수락 중지",
"command.session.undo": "실행 취소",
"command.session.undo.description": "마지막 메시지 실행 취소",
"command.session.redo": "다시 실행",
"command.session.redo.description": "마지막 실행 취소된 메시지 다시 실행",
"command.session.compact": "세션 압축",
"command.session.compact.description": "컨텍스트 크기를 줄이기 위해 세션 요약",
"command.session.fork": "메시지에서 분기",
"command.session.fork.description": "이전 메시지에서 새 세션 생성",
"command.session.share": "세션 공유",
"command.session.share.description": "이 세션을 공유하고 URL을 클립보드에 복사",
"command.session.unshare": "세션 공유 중지",
"command.session.unshare.description": "이 세션 공유 중지",
"palette.search.placeholder": "파일 및 명령어 검색",
"palette.empty": "결과 없음",
"palette.group.commands": "명령어",
"palette.group.files": "파일",
"dialog.provider.search.placeholder": "공급자 검색",
"dialog.provider.empty": "공급자 없음",
"dialog.provider.group.popular": "인기",
"dialog.provider.group.other": "기타",
"dialog.provider.tag.recommended": "추천",
"dialog.provider.anthropic.note": "Claude Pro/Max 또는 API 키로 연결",
"dialog.provider.openai.note": "ChatGPT Pro/Plus 또는 API 키로 연결",
"dialog.provider.copilot.note": "Copilot 또는 API 키로 연결",
"dialog.model.select.title": "모델 선택",
"dialog.model.search.placeholder": "모델 검색",
"dialog.model.empty": "모델 결과 없음",
"dialog.model.manage": "모델 관리",
"dialog.model.manage.description": "모델 선택기에 표시할 모델 사용자 지정",
"dialog.model.unpaid.freeModels.title": "OpenCode에서 제공하는 무료 모델",
"dialog.model.unpaid.addMore.title": "인기 공급자의 모델 추가",
"dialog.provider.viewAll": "모든 공급자 보기",
"provider.connect.title": "{{provider}} 연결",
"provider.connect.title.anthropicProMax": "Claude Pro/Max로 로그인",
"provider.connect.selectMethod": "{{provider}} 로그인 방법 선택",
"provider.connect.method.apiKey": "API 키",
"provider.connect.status.inProgress": "인증 진행 중...",
"provider.connect.status.waiting": "인증 대기 중...",
"provider.connect.status.failed": "인증 실패: {{error}}",
"provider.connect.apiKey.description":
"{{provider}} API 키를 입력하여 계정을 연결하고 OpenCode에서 {{provider}} 모델을 사용하세요.",
"provider.connect.apiKey.label": "{{provider}} API 키",
"provider.connect.apiKey.placeholder": "API 키",
"provider.connect.apiKey.required": "API 키가 필요합니다",
"provider.connect.opencodeZen.line1":
"OpenCode Zen은 코딩 에이전트를 위해 최적화된 신뢰할 수 있는 엄선된 모델에 대한 액세스를 제공합니다.",
"provider.connect.opencodeZen.line2": "단일 API 키로 Claude, GPT, Gemini, GLM 등 다양한 모델에 액세스할 수 있습니다.",
"provider.connect.opencodeZen.visit.prefix": "",
"provider.connect.opencodeZen.visit.suffix": "를 방문하여 API 키를 받으세요.",
"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}} 인증 코드",
"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}} 모델을 사용하세요.",
"provider.connect.oauth.auto.confirmationCode": "확인 코드",
"provider.connect.toast.connected.title": "{{provider}} 연결됨",
"provider.connect.toast.connected.description": "이제 {{provider}} 모델을 사용할 수 있습니다.",
"model.tag.free": "무료",
"model.tag.latest": "최신",
"common.search.placeholder": "검색",
"common.goBack": "뒤로 가기",
"common.loading": "로딩 중",
"common.cancel": "취소",
"common.submit": "제출",
"common.save": "저장",
"common.saving": "저장 중...",
"common.default": "기본값",
"common.attachment": "첨부 파일",
"prompt.placeholder.shell": "셸 명령어 입력...",
"prompt.placeholder.normal": '무엇이든 물어보세요... "{{example}}"',
"prompt.mode.shell": "셸",
"prompt.mode.shell.exit": "종료하려면 esc",
"prompt.example.1": "코드베이스의 TODO 수정",
"prompt.example.2": "이 프로젝트의 기술 스택이 무엇인가요?",
"prompt.example.3": "고장 난 테스트 수정",
"prompt.example.4": "인증 작동 방식 설명",
"prompt.example.5": "보안 취약점 찾기 및 수정",
"prompt.example.6": "사용자 서비스에 단위 테스트 추가",
"prompt.example.7": "이 함수를 더 읽기 쉽게 리팩터링",
"prompt.example.8": "이 오류는 무엇을 의미하나요?",
"prompt.example.9": "이 문제 디버깅 도와줘",
"prompt.example.10": "API 문서 생성",
"prompt.example.11": "데이터베이스 쿼리 최적화",
"prompt.example.12": "입력 유효성 검사 추가",
"prompt.example.13": "...를 위한 새 컴포넌트 생성",
"prompt.example.14": "이 프로젝트를 어떻게 배포하나요?",
"prompt.example.15": "모범 사례를 기준으로 내 코드 검토",
"prompt.example.16": "이 함수에 오류 처리 추가",
"prompt.example.17": "이 정규식 패턴 설명",
"prompt.example.18": "이것을 TypeScript로 변환",
"prompt.example.19": "코드베이스 전체에 로깅 추가",
"prompt.example.20": "오래된 종속성은 무엇인가요?",
"prompt.example.21": "마이그레이션 스크립트 작성 도와줘",
"prompt.example.22": "이 엔드포인트에 캐싱 구현",
"prompt.example.23": "이 목록에 페이지네이션 추가",
"prompt.example.24": "...를 위한 CLI 명령어 생성",
"prompt.example.25": "여기서 환경 변수는 어떻게 작동하나요?",
"prompt.popover.emptyResults": "일치하는 결과 없음",
"prompt.popover.emptyCommands": "일치하는 명령어 없음",
"prompt.dropzone.label": "이미지나 PDF를 여기에 드롭하세요",
"prompt.slash.badge.custom": "사용자 지정",
"prompt.context.active": "활성",
"prompt.context.includeActiveFile": "활성 파일 포함",
"prompt.context.removeActiveFile": "컨텍스트에서 활성 파일 제거",
"prompt.context.removeFile": "컨텍스트에서 파일 제거",
"prompt.action.attachFile": "파일 첨부",
"prompt.attachment.remove": "첨부 파일 제거",
"prompt.action.send": "전송",
"prompt.action.stop": "중지",
"prompt.toast.pasteUnsupported.title": "지원되지 않는 붙여넣기",
"prompt.toast.pasteUnsupported.description": "이미지나 PDF만 붙여넣을 수 있습니다.",
"prompt.toast.modelAgentRequired.title": "에이전트 및 모델 선택",
"prompt.toast.modelAgentRequired.description": "프롬프트를 보내기 전에 에이전트와 모델을 선택하세요.",
"prompt.toast.worktreeCreateFailed.title": "작업 트리 생성 실패",
"prompt.toast.sessionCreateFailed.title": "세션 생성 실패",
"prompt.toast.shellSendFailed.title": "셸 명령 전송 실패",
"prompt.toast.commandSendFailed.title": "명령 전송 실패",
"prompt.toast.promptSendFailed.title": "프롬프트 전송 실패",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{total}}개 중 {{enabled}}개 활성화됨",
"dialog.mcp.empty": "구성된 MCP 없음",
"mcp.status.connected": "연결됨",
"mcp.status.failed": "실패",
"mcp.status.needs_auth": "인증 필요",
"mcp.status.disabled": "비활성화됨",
"dialog.fork.empty": "분기할 메시지 없음",
"dialog.directory.search.placeholder": "폴더 검색",
"dialog.directory.empty": "폴더 없음",
"dialog.server.title": "서버",
"dialog.server.description": "이 앱이 연결할 OpenCode 서버를 전환합니다.",
"dialog.server.search.placeholder": "서버 검색",
"dialog.server.empty": "서버 없음",
"dialog.server.add.title": "서버 추가",
"dialog.server.add.url": "서버 URL",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "서버에 연결할 수 없습니다",
"dialog.server.add.checking": "확인 중...",
"dialog.server.add.button": "추가",
"dialog.server.default.title": "기본 서버",
"dialog.server.default.description":
"로컬 서버를 시작하는 대신 앱 실행 시 이 서버에 연결합니다. 다시 시작해야 합니다.",
"dialog.server.default.none": "선택된 서버 없음",
"dialog.server.default.set": "현재 서버를 기본값으로 설정",
"dialog.server.default.clear": "지우기",
"dialog.server.action.remove": "서버 제거",
"dialog.project.edit.title": "프로젝트 편집",
"dialog.project.edit.name": "이름",
"dialog.project.edit.icon": "아이콘",
"dialog.project.edit.icon.alt": "프로젝트 아이콘",
"dialog.project.edit.icon.hint": "이미지를 클릭하거나 드래그하세요",
"dialog.project.edit.icon.recommended": "권장: 128x128px",
"dialog.project.edit.color": "색상",
"dialog.project.edit.color.select": "{{color}} 색상 선택",
"context.breakdown.title": "컨텍스트 분석",
"context.breakdown.note": '입력 토큰의 대략적인 분석입니다. "기타"에는 도구 정의 및 오버헤드가 포함됩니다.',
"context.breakdown.system": "시스템",
"context.breakdown.user": "사용자",
"context.breakdown.assistant": "어시스턴트",
"context.breakdown.tool": "도구 호출",
"context.breakdown.other": "기타",
"context.systemPrompt.title": "시스템 프롬프트",
"context.rawMessages.title": "원시 메시지",
"context.stats.session": "세션",
"context.stats.messages": "메시지",
"context.stats.provider": "공급자",
"context.stats.model": "모델",
"context.stats.limit": "컨텍스트 제한",
"context.stats.totalTokens": "총 토큰",
"context.stats.usage": "사용량",
"context.stats.inputTokens": "입력 토큰",
"context.stats.outputTokens": "출력 토큰",
"context.stats.reasoningTokens": "추론 토큰",
"context.stats.cacheTokens": "캐시 토큰 (읽기/쓰기)",
"context.stats.userMessages": "사용자 메시지",
"context.stats.assistantMessages": "어시스턴트 메시지",
"context.stats.totalCost": "총 비용",
"context.stats.sessionCreated": "세션 생성됨",
"context.stats.lastActivity": "최근 활동",
"context.usage.tokens": "토큰",
"context.usage.usage": "사용량",
"context.usage.cost": "비용",
"context.usage.clickToView": "컨텍스트를 보려면 클릭",
"context.usage.view": "컨텍스트 사용량 보기",
"language.en": "영어",
"language.zh": "중국어 (간체)",
"language.zht": "중국어 (번체)",
"language.ko": "한국어",
"language.de": "독일어",
"language.es": "스페인어",
"language.fr": "프랑스어",
"language.ja": "일본어",
"language.da": "덴마크어",
"language.ru": "러시아어",
"language.pl": "폴란드어",
"language.ar": "아랍어",
"language.no": "노르웨이어",
"language.br": "포르투갈어 (브라질)",
"toast.language.title": "언어",
"toast.language.description": "{{language}}(으)로 전환됨",
"toast.theme.title": "테마 전환됨",
"toast.scheme.title": "색상 테마",
"toast.permissions.autoaccept.on.title": "편집 자동 수락 중",
"toast.permissions.autoaccept.on.description": "편집 및 쓰기 권한이 자동으로 승인됩니다",
"toast.permissions.autoaccept.off.title": "편집 자동 수락 중지됨",
"toast.permissions.autoaccept.off.description": "편집 및 쓰기 권한 승인이 필요합니다",
"toast.model.none.title": "선택된 모델 없음",
"toast.model.none.description": "이 세션을 요약하려면 공급자를 연결하세요",
"toast.file.loadFailed.title": "파일 로드 실패",
"toast.session.share.copyFailed.title": "URL 클립보드 복사 실패",
"toast.session.share.success.title": "세션 공유됨",
"toast.session.share.success.description": "공유 URL이 클립보드에 복사되었습니다!",
"toast.session.share.failed.title": "세션 공유 실패",
"toast.session.share.failed.description": "세션을 공유하는 동안 오류가 발생했습니다",
"toast.session.unshare.success.title": "세션 공유 해제됨",
"toast.session.unshare.success.description": "세션 공유가 성공적으로 해제되었습니다!",
"toast.session.unshare.failed.title": "세션 공유 해제 실패",
"toast.session.unshare.failed.description": "세션 공유를 해제하는 동안 오류가 발생했습니다",
"toast.session.listFailed.title": "{{project}}에 대한 세션을 로드하지 못했습니다",
"toast.update.title": "업데이트 가능",
"toast.update.description": "OpenCode의 새 버전({{version}})을 설치할 수 있습니다.",
"toast.update.action.installRestart": "설치 및 다시 시작",
"toast.update.action.notYet": "나중에",
"error.page.title": "문제가 발생했습니다",
"error.page.description": "애플리케이션을 로드하는 동안 오류가 발생했습니다.",
"error.page.details.label": "오류 세부 정보",
"error.page.action.restart": "다시 시작",
"error.page.action.checking": "확인 중...",
"error.page.action.checkUpdates": "업데이트 확인",
"error.page.action.updateTo": "{{version}} 버전으로 업데이트",
"error.page.report.prefix": "이 오류를 OpenCode 팀에 제보해 주세요: ",
"error.page.report.discord": "Discord",
"error.page.version": "버전: {{version}}",
"error.dev.rootNotFound":
"루트 요소를 찾을 수 없습니다. index.html에 추가하는 것을 잊으셨나요? 또는 id 속성의 철자가 틀렸을 수 있습니다.",
"error.globalSync.connectFailed": "서버에 연결할 수 없습니다. `{{url}}`에서 서버가 실행 중인가요?",
"error.chain.unknown": "알 수 없는 오류",
"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.checkConfig": "구성(opencode.json)의 공급자/모델 이름을 확인하세요",
"error.chain.mcpFailed": 'MCP 서버 "{{name}}" 실패. 참고: OpenCode는 아직 MCP 인증을 지원하지 않습니다.',
"error.chain.providerAuthFailed": "공급자 인증 실패 ({{provider}}): {{message}}",
"error.chain.providerInitFailed": '공급자 "{{provider}}" 초기화 실패. 자격 증명과 구성을 확인하세요.',
"error.chain.configJsonInvalid": "{{path}}의 구성 파일이 유효한 JSON(C)가 아닙니다",
"error.chain.configJsonInvalidWithMessage": "{{path}}의 구성 파일이 유효한 JSON(C)가 아닙니다: {{message}}",
"error.chain.configDirectoryTypo":
'{{path}}의 "{{dir}}" 디렉터리가 유효하지 않습니다. 디렉터리 이름을 "{{suggestion}}"으로 변경하거나 제거하세요. 이는 흔한 오타입니다.',
"error.chain.configFrontmatterError": "{{path}}의 frontmatter 파싱 실패:\n{{message}}",
"error.chain.configInvalid": "{{path}}의 구성 파일이 유효하지 않습니다",
"error.chain.configInvalidWithMessage": "{{path}}의 구성 파일이 유효하지 않습니다: {{message}}",
"notification.permission.title": "권한 필요",
"notification.permission.description": "{{projectName}}의 {{sessionTitle}}에서 권한이 필요합니다",
"notification.question.title": "질문",
"notification.question.description": "{{projectName}}의 {{sessionTitle}}에서 질문이 있습니다",
"notification.action.goToSession": "세션으로 이동",
"notification.session.responseReady.title": "응답 준비됨",
"notification.session.error.title": "세션 오류",
"notification.session.error.fallbackDescription": "오류가 발생했습니다",
"home.recentProjects": "최근 프로젝트",
"home.empty.title": "최근 프로젝트 없음",
"home.empty.description": "로컬 프로젝트를 열어 시작하세요",
"session.tab.session": "세션",
"session.tab.review": "검토",
"session.tab.context": "컨텍스트",
"session.panel.reviewAndFiles": "검토 및 파일",
"session.review.filesChanged": "{{count}}개 파일 변경됨",
"session.review.loadingChanges": "변경 사항 로드 중...",
"session.review.empty": "이 세션에 변경 사항이 아직 없습니다",
"session.messages.renderEarlier": "이전 메시지 렌더링",
"session.messages.loadingEarlier": "이전 메시지 로드 중...",
"session.messages.loadEarlier": "이전 메시지 로드",
"session.messages.loading": "메시지 로드 중...",
"session.context.addToContext": "컨텍스트에 {{selection}} 추가",
"session.new.worktree.main": "메인 브랜치",
"session.new.worktree.mainWithBranch": "메인 브랜치 ({{branch}})",
"session.new.worktree.create": "새 작업 트리 생성",
"session.new.lastModified": "최근 수정",
"session.header.search.placeholder": "{{project}} 검색",
"session.header.searchFiles": "파일 검색",
"session.share.popover.title": "웹에 게시",
"session.share.popover.description.shared": "이 세션은 웹에 공개되었습니다. 링크가 있는 누구나 액세스할 수 있습니다.",
"session.share.popover.description.unshared":
"세션을 웹에 공개적으로 공유합니다. 링크가 있는 누구나 액세스할 수 있습니다.",
"session.share.action.share": "공유",
"session.share.action.publish": "게시",
"session.share.action.publishing": "게시 중...",
"session.share.action.unpublish": "게시 취소",
"session.share.action.unpublishing": "게시 취소 중...",
"session.share.action.view": "보기",
"session.share.copy.copied": "복사됨",
"session.share.copy.copyLink": "링크 복사",
"lsp.tooltip.none": "LSP 서버 없음",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "프롬프트 로드 중...",
"terminal.loading": "터미널 로드 중...",
"terminal.title": "터미널",
"terminal.title.numbered": "터미널 {{number}}",
"terminal.close": "터미널 닫기",
"common.closeTab": "탭 닫기",
"common.dismiss": "닫기",
"common.requestFailed": "요청 실패",
"common.moreOptions": "더 많은 옵션",
"common.learnMore": "더 알아보기",
"common.rename": "이름 바꾸기",
"common.reset": "초기화",
"common.archive": "보관",
"common.delete": "삭제",
"common.close": "닫기",
"common.edit": "편집",
"common.loadMore": "더 불러오기",
"sidebar.nav.projectsAndSessions": "프로젝트 및 세션",
"sidebar.settings": "설정",
"sidebar.help": "도움말",
"sidebar.workspaces.enable": "작업 공간 활성화",
"sidebar.workspaces.disable": "작업 공간 비활성화",
"sidebar.gettingStarted.title": "시작하기",
"sidebar.gettingStarted.line1": "OpenCode에는 무료 모델이 포함되어 있어 즉시 시작할 수 있습니다.",
"sidebar.gettingStarted.line2": "Claude, GPT, Gemini 등을 포함한 모델을 사용하려면 공급자를 연결하세요.",
"sidebar.project.recentSessions": "최근 세션",
"sidebar.project.viewAllSessions": "모든 세션 보기",
"settings.section.desktop": "데스크톱",
"settings.tab.general": "일반",
"settings.tab.shortcuts": "단축키",
"settings.general.section.appearance": "모양",
"settings.general.section.notifications": "시스템 알림",
"settings.general.section.sounds": "효과음",
"settings.general.row.language.title": "언어",
"settings.general.row.language.description": "OpenCode 표시 언어 변경",
"settings.general.row.appearance.title": "모양",
"settings.general.row.appearance.description": "기기에서 OpenCode가 보이는 방식 사용자 지정",
"settings.general.row.theme.title": "테마",
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
"settings.general.row.font.title": "글꼴",
"settings.general.row.font.description": "코드 블록에 사용되는 고정폭 글꼴 사용자 지정",
"settings.general.notifications.agent.title": "에이전트",
"settings.general.notifications.agent.description": "에이전트가 완료되거나 주의가 필요할 때 시스템 알림 표시",
"settings.general.notifications.permissions.title": "권한",
"settings.general.notifications.permissions.description": "권한이 필요할 때 시스템 알림 표시",
"settings.general.notifications.errors.title": "오류",
"settings.general.notifications.errors.description": "오류가 발생했을 때 시스템 알림 표시",
"settings.general.sounds.agent.title": "에이전트",
"settings.general.sounds.agent.description": "에이전트가 완료되거나 주의가 필요할 때 소리 재생",
"settings.general.sounds.permissions.title": "권한",
"settings.general.sounds.permissions.description": "권한이 필요할 때 소리 재생",
"settings.general.sounds.errors.title": "오류",
"settings.general.sounds.errors.description": "오류가 발생했을 때 소리 재생",
"settings.shortcuts.title": "키보드 단축키",
"settings.shortcuts.reset.button": "기본값으로 초기화",
"settings.shortcuts.reset.toast.title": "단축키 초기화됨",
"settings.shortcuts.reset.toast.description": "키보드 단축키가 기본값으로 초기화되었습니다.",
"settings.shortcuts.conflict.title": "단축키가 이미 사용 중임",
"settings.shortcuts.conflict.description": "{{keybind}}은(는) 이미 {{titles}}에 할당되어 있습니다.",
"settings.shortcuts.unassigned": "할당되지 않음",
"settings.shortcuts.pressKeys": "키 누르기",
"settings.shortcuts.search.placeholder": "단축키 검색",
"settings.shortcuts.search.empty": "단축키를 찾을 수 없습니다",
"settings.shortcuts.group.general": "일반",
"settings.shortcuts.group.session": "세션",
"settings.shortcuts.group.navigation": "탐색",
"settings.shortcuts.group.modelAndAgent": "모델 및 에이전트",
"settings.shortcuts.group.terminal": "터미널",
"settings.shortcuts.group.prompt": "프롬프트",
"settings.providers.title": "공급자",
"settings.providers.description": "공급자 설정은 여기서 구성할 수 있습니다.",
"settings.models.title": "모델",
"settings.models.description": "모델 설정은 여기서 구성할 수 있습니다.",
"settings.agents.title": "에이전트",
"settings.agents.description": "에이전트 설정은 여기서 구성할 수 있습니다.",
"settings.commands.title": "명령어",
"settings.commands.description": "명령어 설정은 여기서 구성할 수 있습니다.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP 설정은 여기서 구성할 수 있습니다.",
"settings.permissions.title": "권한",
"settings.permissions.description": "서버가 기본적으로 사용할 수 있는 도구를 제어합니다.",
"settings.permissions.section.tools": "도구",
"settings.permissions.toast.updateFailed.title": "권한 업데이트 실패",
"settings.permissions.action.allow": "허용",
"settings.permissions.action.ask": "묻기",
"settings.permissions.action.deny": "거부",
"settings.permissions.tool.read.title": "읽기",
"settings.permissions.tool.read.description": "파일 읽기 (파일 경로와 일치)",
"settings.permissions.tool.edit.title": "편집",
"settings.permissions.tool.edit.description": "파일 수정 (편집, 쓰기, 패치 및 다중 편집 포함)",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "glob 패턴을 사용하여 파일 일치",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "정규식을 사용하여 파일 내용 검색",
"settings.permissions.tool.list.title": "목록",
"settings.permissions.tool.list.description": "디렉터리 내 파일 나열",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "셸 명령어 실행",
"settings.permissions.tool.task.title": "작업",
"settings.permissions.tool.task.description": "하위 에이전트 실행",
"settings.permissions.tool.skill.title": "기술",
"settings.permissions.tool.skill.description": "이름으로 기술 로드",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "언어 서버 쿼리 실행",
"settings.permissions.tool.todoread.title": "할 일 읽기",
"settings.permissions.tool.todoread.description": "할 일 목록 읽기",
"settings.permissions.tool.todowrite.title": "할 일 쓰기",
"settings.permissions.tool.todowrite.description": "할 일 목록 업데이트",
"settings.permissions.tool.webfetch.title": "웹 가져오기",
"settings.permissions.tool.webfetch.description": "URL에서 콘텐츠 가져오기",
"settings.permissions.tool.websearch.title": "웹 검색",
"settings.permissions.tool.websearch.description": "웹 검색",
"settings.permissions.tool.codesearch.title": "코드 검색",
"settings.permissions.tool.codesearch.description": "웹에서 코드 검색",
"settings.permissions.tool.external_directory.title": "외부 디렉터리",
"settings.permissions.tool.external_directory.description": "프로젝트 디렉터리 외부의 파일에 액세스",
"settings.permissions.tool.doom_loop.title": "무한 반복",
"settings.permissions.tool.doom_loop.description": "동일한 입력으로 반복되는 도구 호출 감지",
"session.delete.failed.title": "세션 삭제 실패",
"session.delete.title": "세션 삭제",
"session.delete.confirm": '"{{name}}" 세션을 삭제하시겠습니까?',
"session.delete.button": "세션 삭제",
"workspace.new": "새 작업 공간",
"workspace.type.local": "로컬",
"workspace.type.sandbox": "샌드박스",
"workspace.create.failed.title": "작업 공간 생성 실패",
"workspace.delete.failed.title": "작업 공간 삭제 실패",
"workspace.resetting.title": "작업 공간 재설정 중",
"workspace.resetting.description": "잠시 시간이 걸릴 수 있습니다.",
"workspace.reset.failed.title": "작업 공간 재설정 실패",
"workspace.reset.success.title": "작업 공간 재설정됨",
"workspace.reset.success.description": "작업 공간이 이제 기본 브랜치와 일치합니다.",
"workspace.status.checking": "병합되지 않은 변경 사항 확인 중...",
"workspace.status.error": "Git 상태를 확인할 수 없습니다.",
"workspace.status.clean": "병합되지 않은 변경 사항이 감지되지 않았습니다.",
"workspace.status.dirty": "이 작업 공간에서 병합되지 않은 변경 사항이 감지되었습니다.",
"workspace.delete.title": "작업 공간 삭제",
"workspace.delete.confirm": '"{{name}}" 작업 공간을 삭제하시겠습니까?',
"workspace.delete.button": "작업 공간 삭제",
"workspace.reset.title": "작업 공간 재설정",
"workspace.reset.confirm": '"{{name}}" 작업 공간을 재설정하시겠습니까?',
"workspace.reset.button": "작업 공간 재설정",
"workspace.reset.archived.none": "활성 세션이 보관되지 않습니다.",
"workspace.reset.archived.one": "1개의 세션이 보관됩니다.",
"workspace.reset.archived.many": "{{count}}개의 세션이 보관됩니다.",
"workspace.reset.note": "이 작업은 작업 공간을 기본 브랜치와 일치하도록 재설정합니다.",
}

602
packages/app/src/i18n/no.ts Normal file
View File

@@ -0,0 +1,602 @@
import { dict as en } from "./en"
type Keys = keyof typeof en
export const dict = {
"command.category.suggested": "Foreslått",
"command.category.view": "Visning",
"command.category.project": "Prosjekt",
"command.category.provider": "Leverandør",
"command.category.server": "Server",
"command.category.session": "Sesjon",
"command.category.theme": "Tema",
"command.category.language": "Språk",
"command.category.file": "Fil",
"command.category.terminal": "Terminal",
"command.category.model": "Modell",
"command.category.mcp": "MCP",
"command.category.agent": "Agent",
"command.category.permissions": "Tillatelser",
"command.category.workspace": "Arbeidsområde",
"command.category.settings": "Innstillinger",
"theme.scheme.system": "System",
"theme.scheme.light": "Lys",
"theme.scheme.dark": "Mørk",
"command.sidebar.toggle": "Veksle sidepanel",
"command.project.open": "Åpne prosjekt",
"command.provider.connect": "Koble til leverandør",
"command.server.switch": "Bytt server",
"command.settings.open": "Åpne innstillinger",
"command.session.previous": "Forrige sesjon",
"command.session.next": "Neste sesjon",
"command.session.archive": "Arkiver sesjon",
"command.palette": "Kommandopalett",
"command.theme.cycle": "Bytt tema",
"command.theme.set": "Bruk tema: {{theme}}",
"command.theme.scheme.cycle": "Bytt fargevalg",
"command.theme.scheme.set": "Bruk fargevalg: {{scheme}}",
"command.language.cycle": "Bytt språk",
"command.language.set": "Bruk språk: {{language}}",
"command.session.new": "Ny sesjon",
"command.file.open": "Åpne fil",
"command.file.open.description": "Søk i filer og kommandoer",
"command.terminal.toggle": "Veksle terminal",
"command.review.toggle": "Veksle gjennomgang",
"command.terminal.new": "Ny terminal",
"command.terminal.new.description": "Opprett en ny terminalfane",
"command.steps.toggle": "Veksle trinn",
"command.steps.toggle.description": "Vis eller skjul trinn for gjeldende melding",
"command.message.previous": "Forrige melding",
"command.message.previous.description": "Gå til forrige brukermelding",
"command.message.next": "Neste melding",
"command.message.next.description": "Gå til neste brukermelding",
"command.model.choose": "Velg modell",
"command.model.choose.description": "Velg en annen modell",
"command.mcp.toggle": "Veksle MCP-er",
"command.mcp.toggle.description": "Veksle MCP-er",
"command.agent.cycle": "Bytt agent",
"command.agent.cycle.description": "Bytt til neste agent",
"command.agent.cycle.reverse": "Bytt agent bakover",
"command.agent.cycle.reverse.description": "Bytt til forrige agent",
"command.model.variant.cycle": "Bytt tenkeinnsats",
"command.model.variant.cycle.description": "Bytt til neste innsatsnivå",
"command.permissions.autoaccept.enable": "Godta endringer automatisk",
"command.permissions.autoaccept.disable": "Slutt å godta endringer automatisk",
"command.session.undo": "Angre",
"command.session.undo.description": "Angre siste melding",
"command.session.redo": "Gjør om",
"command.session.redo.description": "Gjør om siste angrede melding",
"command.session.compact": "Komprimer sesjon",
"command.session.compact.description": "Oppsummer sesjonen for å redusere kontekststørrelsen",
"command.session.fork": "Forgren fra melding",
"command.session.fork.description": "Opprett en ny sesjon fra en tidligere melding",
"command.session.share": "Del sesjon",
"command.session.share.description": "Del denne sesjonen og kopier URL-en til utklippstavlen",
"command.session.unshare": "Slutt å dele sesjon",
"command.session.unshare.description": "Slutt å dele denne sesjonen",
"palette.search.placeholder": "Søk i filer og kommandoer",
"palette.empty": "Ingen resultater funnet",
"palette.group.commands": "Kommandoer",
"palette.group.files": "Filer",
"dialog.provider.search.placeholder": "Søk etter leverandører",
"dialog.provider.empty": "Ingen leverandører funnet",
"dialog.provider.group.popular": "Populære",
"dialog.provider.group.other": "Andre",
"dialog.provider.tag.recommended": "Anbefalt",
"dialog.provider.anthropic.note": "Koble til med Claude Pro/Max eller API-nøkkel",
"dialog.provider.openai.note": "Koble til med ChatGPT Pro/Plus eller API-nøkkel",
"dialog.provider.copilot.note": "Koble til med Copilot eller API-nøkkel",
"dialog.model.select.title": "Velg modell",
"dialog.model.search.placeholder": "Søk etter modeller",
"dialog.model.empty": "Ingen modellresultater",
"dialog.model.manage": "Administrer modeller",
"dialog.model.manage.description": "Tilpass hvilke modeller som vises i modellvelgeren.",
"dialog.model.unpaid.freeModels.title": "Gratis modeller levert av OpenCode",
"dialog.model.unpaid.addMore.title": "Legg til flere modeller fra populære leverandører",
"dialog.provider.viewAll": "Vis alle leverandører",
"provider.connect.title": "Koble til {{provider}}",
"provider.connect.title.anthropicProMax": "Logg inn med Claude Pro/Max",
"provider.connect.selectMethod": "Velg innloggingsmetode for {{provider}}.",
"provider.connect.method.apiKey": "API-nøkkel",
"provider.connect.status.inProgress": "Autorisering pågår...",
"provider.connect.status.waiting": "Venter på autorisering...",
"provider.connect.status.failed": "Autorisering mislyktes: {{error}}",
"provider.connect.apiKey.description":
"Skriv inn din {{provider}} API-nøkkel for å koble til kontoen din og bruke {{provider}}-modeller i OpenCode.",
"provider.connect.apiKey.label": "{{provider}} API-nøkkel",
"provider.connect.apiKey.placeholder": "API-nøkkel",
"provider.connect.apiKey.required": "API-nøkkel er påkrevd",
"provider.connect.opencodeZen.line1":
"OpenCode Zen gir deg tilgang til et utvalg av pålitelige optimaliserte modeller for kodeagenter.",
"provider.connect.opencodeZen.line2":
"Med én enkelt API-nøkkel får du tilgang til modeller som Claude, GPT, Gemini, GLM og flere.",
"provider.connect.opencodeZen.visit.prefix": "Besøk ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " for å hente API-nøkkelen din.",
"provider.connect.oauth.code.visit.prefix": "Besøk ",
"provider.connect.oauth.code.visit.link": "denne lenken",
"provider.connect.oauth.code.visit.suffix":
" for å hente autorisasjonskoden din for å koble til kontoen din og bruke {{provider}}-modeller i OpenCode.",
"provider.connect.oauth.code.label": "{{method}} autorisasjonskode",
"provider.connect.oauth.code.placeholder": "Autorisasjonskode",
"provider.connect.oauth.code.required": "Autorisasjonskode er påkrevd",
"provider.connect.oauth.code.invalid": "Ugyldig autorisasjonskode",
"provider.connect.oauth.auto.visit.prefix": "Besøk ",
"provider.connect.oauth.auto.visit.link": "denne lenken",
"provider.connect.oauth.auto.visit.suffix":
" og skriv inn koden nedenfor for å koble til kontoen din og bruke {{provider}}-modeller i OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Bekreftelseskode",
"provider.connect.toast.connected.title": "{{provider}} tilkoblet",
"provider.connect.toast.connected.description": "{{provider}}-modeller er nå tilgjengelige.",
"model.tag.free": "Gratis",
"model.tag.latest": "Nyeste",
"model.provider.anthropic": "Anthropic",
"model.provider.openai": "OpenAI",
"model.provider.google": "Google",
"model.provider.xai": "xAI",
"model.provider.meta": "Meta",
"model.input.text": "tekst",
"model.input.image": "bilde",
"model.input.audio": "lyd",
"model.input.video": "video",
"model.input.pdf": "pdf",
"model.tooltip.allows": "Tillater: {{inputs}}",
"model.tooltip.reasoning.allowed": "Tillater resonnering",
"model.tooltip.reasoning.none": "Ingen resonnering",
"model.tooltip.context": "Kontekstgrense {{limit}}",
"common.search.placeholder": "Søk",
"common.goBack": "Gå tilbake",
"common.loading": "Laster",
"common.loading.ellipsis": "...",
"common.cancel": "Avbryt",
"common.submit": "Send inn",
"common.save": "Lagre",
"common.saving": "Lagrer...",
"common.default": "Standard",
"common.attachment": "vedlegg",
"prompt.placeholder.shell": "Skriv inn shell-kommando...",
"prompt.placeholder.normal": 'Spør om hva som helst... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "ESC for å avslutte",
"prompt.example.1": "Fiks en TODO i kodebasen",
"prompt.example.2": "Hva er teknologistabelen i dette prosjektet?",
"prompt.example.3": "Fiks ødelagte tester",
"prompt.example.4": "Forklar hvordan autentisering fungerer",
"prompt.example.5": "Finn og fiks sikkerhetssårbarheter",
"prompt.example.6": "Legg til enhetstester for brukerservicen",
"prompt.example.7": "Refaktorer denne funksjonen for bedre lesbarhet",
"prompt.example.8": "Hva betyr denne feilen?",
"prompt.example.9": "Hjelp meg med å feilsøke dette problemet",
"prompt.example.10": "Generer API-dokumentasjon",
"prompt.example.11": "Optimaliser databasespørringer",
"prompt.example.12": "Legg til inputvalidering",
"prompt.example.13": "Lag en ny komponent for...",
"prompt.example.14": "Hvordan deployer jeg dette prosjektet?",
"prompt.example.15": "Gjennomgå koden min for beste praksis",
"prompt.example.16": "Legg til feilhåndtering i denne funksjonen",
"prompt.example.17": "Forklar dette regex-mønsteret",
"prompt.example.18": "Konverter dette til TypeScript",
"prompt.example.19": "Legg til logging i hele kodebasen",
"prompt.example.20": "Hvilke avhengigheter er utdaterte?",
"prompt.example.21": "Hjelp meg med å skrive et migreringsskript",
"prompt.example.22": "Implementer caching for dette endepunktet",
"prompt.example.23": "Legg til paginering i denne listen",
"prompt.example.24": "Lag en CLI-kommando for...",
"prompt.example.25": "Hvordan fungerer miljøvariabler her?",
"prompt.popover.emptyResults": "Ingen matchende resultater",
"prompt.popover.emptyCommands": "Ingen matchende kommandoer",
"prompt.dropzone.label": "Slipp bilder eller PDF-er her",
"prompt.slash.badge.custom": "egendefinert",
"prompt.context.active": "aktiv",
"prompt.context.includeActiveFile": "Inkluder aktiv fil",
"prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
"prompt.context.removeFile": "Fjern fil fra kontekst",
"prompt.action.attachFile": "Legg ved fil",
"prompt.attachment.remove": "Fjern vedlegg",
"prompt.action.send": "Send",
"prompt.action.stop": "Stopp",
"prompt.toast.pasteUnsupported.title": "Liming ikke støttet",
"prompt.toast.pasteUnsupported.description": "Kun bilder eller PDF-er kan limes inn her.",
"prompt.toast.modelAgentRequired.title": "Velg en agent og modell",
"prompt.toast.modelAgentRequired.description": "Velg en agent og modell før du sender en forespørsel.",
"prompt.toast.worktreeCreateFailed.title": "Kunne ikke opprette worktree",
"prompt.toast.sessionCreateFailed.title": "Kunne ikke opprette sesjon",
"prompt.toast.shellSendFailed.title": "Kunne ikke sende shell-kommando",
"prompt.toast.commandSendFailed.title": "Kunne ikke sende kommando",
"prompt.toast.promptSendFailed.title": "Kunne ikke sende forespørsel",
"dialog.mcp.title": "MCP-er",
"dialog.mcp.description": "{{enabled}} av {{total}} aktivert",
"dialog.mcp.empty": "Ingen MCP-er konfigurert",
"mcp.status.connected": "tilkoblet",
"mcp.status.failed": "mislyktes",
"mcp.status.needs_auth": "trenger autentisering",
"mcp.status.disabled": "deaktivert",
"dialog.fork.empty": "Ingen meldinger å forgrene fra",
"dialog.directory.search.placeholder": "Søk etter mapper",
"dialog.directory.empty": "Ingen mapper funnet",
"dialog.server.title": "Servere",
"dialog.server.description": "Bytt hvilken OpenCode-server denne appen kobler til.",
"dialog.server.search.placeholder": "Søk etter servere",
"dialog.server.empty": "Ingen servere ennå",
"dialog.server.add.title": "Legg til en server",
"dialog.server.add.url": "Server-URL",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Kunne ikke koble til server",
"dialog.server.add.checking": "Sjekker...",
"dialog.server.add.button": "Legg til",
"dialog.server.default.title": "Standardserver",
"dialog.server.default.description":
"Koble til denne serveren ved oppstart i stedet for å starte en lokal server. Krever omstart.",
"dialog.server.default.none": "Ingen server valgt",
"dialog.server.default.set": "Sett gjeldende server som standard",
"dialog.server.default.clear": "Tøm",
"dialog.server.action.remove": "Fjern server",
"dialog.project.edit.title": "Rediger prosjekt",
"dialog.project.edit.name": "Navn",
"dialog.project.edit.icon": "Ikon",
"dialog.project.edit.icon.alt": "Prosjektikon",
"dialog.project.edit.icon.hint": "Klikk eller dra et bilde",
"dialog.project.edit.icon.recommended": "Anbefalt: 128x128px",
"dialog.project.edit.color": "Farge",
"dialog.project.edit.color.select": "Velg fargen {{color}}",
"context.breakdown.title": "Kontekstfordeling",
"context.breakdown.note": 'Omtrentlig fordeling av input-tokens. "Annet" inkluderer verktøydefinisjoner og overhead.',
"context.breakdown.system": "System",
"context.breakdown.user": "Bruker",
"context.breakdown.assistant": "Assistent",
"context.breakdown.tool": "Verktøykall",
"context.breakdown.other": "Annet",
"context.systemPrompt.title": "Systemprompt",
"context.rawMessages.title": "Rå meldinger",
"context.stats.session": "Sesjon",
"context.stats.messages": "Meldinger",
"context.stats.provider": "Leverandør",
"context.stats.model": "Modell",
"context.stats.limit": "Kontekstgrense",
"context.stats.totalTokens": "Totalt antall tokens",
"context.stats.usage": "Forbruk",
"context.stats.inputTokens": "Input-tokens",
"context.stats.outputTokens": "Output-tokens",
"context.stats.reasoningTokens": "Resonnerings-tokens",
"context.stats.cacheTokens": "Cache-tokens (les/skriv)",
"context.stats.userMessages": "Brukermeldinger",
"context.stats.assistantMessages": "Assistentmeldinger",
"context.stats.totalCost": "Total kostnad",
"context.stats.sessionCreated": "Sesjon opprettet",
"context.stats.lastActivity": "Siste aktivitet",
"context.usage.tokens": "Tokens",
"context.usage.usage": "Forbruk",
"context.usage.cost": "Kostnad",
"context.usage.clickToView": "Klikk for å se kontekst",
"context.usage.view": "Se kontekstforbruk",
"language.en": "Engelsk",
"language.zh": "Kinesisk (forenklet)",
"language.zht": "Kinesisk (tradisjonell)",
"language.ko": "Koreansk",
"language.de": "Tysk",
"language.es": "Spansk",
"language.fr": "Fransk",
"language.ja": "Japansk",
"language.da": "Dansk",
"language.ru": "Russisk",
"language.pl": "Polsk",
"language.ar": "Arabisk",
"language.no": "Norsk",
"language.br": "Portugisisk (Brasil)",
"toast.language.title": "Språk",
"toast.language.description": "Byttet til {{language}}",
"toast.theme.title": "Tema byttet",
"toast.scheme.title": "Fargevalg",
"toast.permissions.autoaccept.on.title": "Godtar endringer automatisk",
"toast.permissions.autoaccept.on.description": "Redigerings- og skrivetillatelser vil bli godkjent automatisk",
"toast.permissions.autoaccept.off.title": "Sluttet å godta endringer automatisk",
"toast.permissions.autoaccept.off.description": "Redigerings- og skrivetillatelser vil kreve godkjenning",
"toast.model.none.title": "Ingen modell valgt",
"toast.model.none.description": "Koble til en leverandør for å oppsummere denne sesjonen",
"toast.file.loadFailed.title": "Kunne ikke laste fil",
"toast.session.share.copyFailed.title": "Kunne ikke kopiere URL til utklippstavlen",
"toast.session.share.success.title": "Sesjon delt",
"toast.session.share.success.description": "Delings-URL kopiert til utklippstavlen!",
"toast.session.share.failed.title": "Kunne ikke dele sesjon",
"toast.session.share.failed.description": "Det oppstod en feil under deling av sesjonen",
"toast.session.unshare.success.title": "Deling av sesjon stoppet",
"toast.session.unshare.success.description": "Sesjonen deles ikke lenger!",
"toast.session.unshare.failed.title": "Kunne ikke stoppe deling av sesjon",
"toast.session.unshare.failed.description": "Det oppstod en feil da delingen av sesjonen skulle stoppes",
"toast.session.listFailed.title": "Kunne ikke laste sesjoner for {{project}}",
"toast.update.title": "Oppdatering tilgjengelig",
"toast.update.description": "En ny versjon av OpenCode ({{version}}) er nå tilgjengelig for installasjon.",
"toast.update.action.installRestart": "Installer og start på nytt",
"toast.update.action.notYet": "Ikke nå",
"error.page.title": "Noe gikk galt",
"error.page.description": "Det oppstod en feil under lasting av applikasjonen.",
"error.page.details.label": "Feildetaljer",
"error.page.action.restart": "Start på nytt",
"error.page.action.checking": "Sjekker...",
"error.page.action.checkUpdates": "Se etter oppdateringer",
"error.page.action.updateTo": "Oppdater til {{version}}",
"error.page.report.prefix": "Vennligst rapporter denne feilen til OpenCode-teamet",
"error.page.report.discord": "på Discord",
"error.page.version": "Versjon: {{version}}",
"error.dev.rootNotFound":
"Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributten er feilstavet?",
"error.globalSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?",
"error.chain.unknown": "Ukjent feil",
"error.chain.causedBy": "Forårsaket av:",
"error.chain.apiError": "API-feil",
"error.chain.status": "Status: {{status}}",
"error.chain.retryable": "Kan prøves på nytt: {{retryable}}",
"error.chain.responseBody": "Responsinnhold:\n{{body}}",
"error.chain.didYouMean": "Mente du: {{suggestions}}",
"error.chain.modelNotFound": "Modell ikke funnet: {{provider}}/{{model}}",
"error.chain.checkConfig": "Sjekk leverandør-/modellnavnene i konfigurasjonen din (opencode.json)",
"error.chain.mcpFailed": 'MCP-server "{{name}}" mislyktes. Merk at OpenCode ikke støtter MCP-autentisering ennå.',
"error.chain.providerAuthFailed": "Leverandørautentisering mislyktes ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'Kunne ikke initialisere leverandør "{{provider}}". Sjekk legitimasjon og konfigurasjon.',
"error.chain.configJsonInvalid": "Konfigurasjonsfilen på {{path}} er ikke gyldig JSON(C)",
"error.chain.configJsonInvalidWithMessage": "Konfigurasjonsfilen på {{path}} er ikke gyldig JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'Mappen "{{dir}}" i {{path}} er ikke gyldig. Gi mappen nytt navn til "{{suggestion}}" eller fjern den. Dette er en vanlig skrivefeil.',
"error.chain.configFrontmatterError": "Kunne ikke analysere frontmatter i {{path}}:\n{{message}}",
"error.chain.configInvalid": "Konfigurasjonsfilen på {{path}} er ugyldig",
"error.chain.configInvalidWithMessage": "Konfigurasjonsfilen på {{path}} er ugyldig: {{message}}",
"notification.permission.title": "Tillatelse påkrevd",
"notification.permission.description": "{{sessionTitle}} i {{projectName}} trenger tillatelse",
"notification.question.title": "Spørsmål",
"notification.question.description": "{{sessionTitle}} i {{projectName}} har et spørsmål",
"notification.action.goToSession": "Gå til sesjon",
"notification.session.responseReady.title": "Svar klart",
"notification.session.error.title": "Sesjonsfeil",
"notification.session.error.fallbackDescription": "Det oppstod en feil",
"home.recentProjects": "Nylige prosjekter",
"home.empty.title": "Ingen nylige prosjekter",
"home.empty.description": "Kom i gang ved å åpne et lokalt prosjekt",
"session.tab.session": "Sesjon",
"session.tab.review": "Gjennomgang",
"session.tab.context": "Kontekst",
"session.panel.reviewAndFiles": "Gjennomgang og filer",
"session.review.filesChanged": "{{count}} filer endret",
"session.review.loadingChanges": "Laster endringer...",
"session.review.empty": "Ingen endringer i denne sesjonen ennå",
"session.messages.renderEarlier": "Vis tidligere meldinger",
"session.messages.loadingEarlier": "Laster inn tidligere meldinger...",
"session.messages.loadEarlier": "Last inn tidligere meldinger",
"session.messages.loading": "Laster meldinger...",
"session.messages.jumpToLatest": "Hopp til nyeste",
"session.context.addToContext": "Legg til {{selection}} i kontekst",
"session.new.worktree.main": "Hovedgren",
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
"session.new.worktree.create": "Opprett nytt worktree",
"session.new.lastModified": "Sist endret",
"session.header.search.placeholder": "Søk i {{project}}",
"session.header.searchFiles": "Søk etter filer",
"session.share.popover.title": "Publiser på nett",
"session.share.popover.description.shared":
"Denne sesjonen er offentlig på nettet. Den er tilgjengelig for alle med lenken.",
"session.share.popover.description.unshared":
"Del sesjonen offentlig på nettet. Den vil være tilgjengelig for alle med lenken.",
"session.share.action.share": "Del",
"session.share.action.publish": "Publiser",
"session.share.action.publishing": "Publiserer...",
"session.share.action.unpublish": "Avpubliser",
"session.share.action.unpublishing": "Avpubliserer...",
"session.share.action.view": "Vis",
"session.share.copy.copied": "Kopiert",
"session.share.copy.copyLink": "Kopier lenke",
"lsp.tooltip.none": "Ingen LSP-servere",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Laster prompt...",
"terminal.loading": "Laster terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Lukk terminal",
"terminal.connectionLost.title": "Tilkobling mistet",
"terminal.connectionLost.description":
"Terminalforbindelsen ble avbrutt. Dette kan skje når serveren starter på nytt.",
"common.closeTab": "Lukk fane",
"common.dismiss": "Avvis",
"common.requestFailed": "Forespørsel mislyktes",
"common.moreOptions": "Flere alternativer",
"common.learnMore": "Lær mer",
"common.rename": "Gi nytt navn",
"common.reset": "Tilbakestill",
"common.delete": "Slett",
"common.close": "Lukk",
"common.edit": "Rediger",
"common.loadMore": "Last flere",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Veksle meny",
"sidebar.nav.projectsAndSessions": "Prosjekter og sesjoner",
"sidebar.settings": "Innstillinger",
"sidebar.help": "Hjelp",
"sidebar.workspaces.enable": "Aktiver arbeidsområder",
"sidebar.workspaces.disable": "Deaktiver arbeidsområder",
"sidebar.gettingStarted.title": "Kom i gang",
"sidebar.gettingStarted.line1": "OpenCode inkluderer gratis modeller så du kan starte umiddelbart.",
"sidebar.gettingStarted.line2": "Koble til en leverandør for å bruke modeller, inkl. Claude, GPT, Gemini osv.",
"sidebar.project.recentSessions": "Nylige sesjoner",
"sidebar.project.viewAllSessions": "Vis alle sesjoner",
"settings.section.desktop": "Skrivebord",
"settings.tab.general": "Generelt",
"settings.tab.shortcuts": "Snarveier",
"settings.general.section.appearance": "Utseende",
"settings.general.section.notifications": "Systemvarsler",
"settings.general.section.sounds": "Lydeffekter",
"settings.general.row.language.title": "Språk",
"settings.general.row.language.description": "Endre visningsspråket for OpenCode",
"settings.general.row.appearance.title": "Utseende",
"settings.general.row.appearance.description": "Tilpass hvordan OpenCode ser ut på enheten din",
"settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
"settings.general.row.font.title": "Skrift",
"settings.general.row.font.description": "Tilpass mono-skriften som brukes i kodeblokker",
"settings.general.notifications.agent.title": "Agent",
"settings.general.notifications.agent.description":
"Vis systemvarsel når agenten er ferdig eller trenger oppmerksomhet",
"settings.general.notifications.permissions.title": "Tillatelser",
"settings.general.notifications.permissions.description": "Vis systemvarsel når en tillatelse er påkrevd",
"settings.general.notifications.errors.title": "Feil",
"settings.general.notifications.errors.description": "Vis systemvarsel når det oppstår en feil",
"settings.general.sounds.agent.title": "Agent",
"settings.general.sounds.agent.description": "Spill av lyd når agenten er ferdig eller trenger oppmerksomhet",
"settings.general.sounds.permissions.title": "Tillatelser",
"settings.general.sounds.permissions.description": "Spill av lyd når en tillatelse er påkrevd",
"settings.general.sounds.errors.title": "Feil",
"settings.general.sounds.errors.description": "Spill av lyd når det oppstår en feil",
"settings.shortcuts.title": "Tastatursnarveier",
"settings.shortcuts.reset.button": "Tilbakestill til standard",
"settings.shortcuts.reset.toast.title": "Snarveier tilbakestilt",
"settings.shortcuts.reset.toast.description": "Tastatursnarveier er tilbakestilt til standard.",
"settings.shortcuts.conflict.title": "Snarvei allerede i bruk",
"settings.shortcuts.conflict.description": "{{keybind}} er allerede tilordnet til {{titles}}.",
"settings.shortcuts.unassigned": "Ikke tilordnet",
"settings.shortcuts.pressKeys": "Trykk taster",
"settings.shortcuts.search.placeholder": "Søk etter snarveier",
"settings.shortcuts.search.empty": "Ingen snarveier funnet",
"settings.shortcuts.group.general": "Generelt",
"settings.shortcuts.group.session": "Sesjon",
"settings.shortcuts.group.navigation": "Navigasjon",
"settings.shortcuts.group.modelAndAgent": "Modell og agent",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Prompt",
"settings.providers.title": "Leverandører",
"settings.providers.description": "Leverandørinnstillinger vil kunne konfigureres her.",
"settings.models.title": "Modeller",
"settings.models.description": "Modellinnstillinger vil kunne konfigureres her.",
"settings.agents.title": "Agenter",
"settings.agents.description": "Agentinnstillinger vil kunne konfigureres her.",
"settings.commands.title": "Kommandoer",
"settings.commands.description": "Kommandoinnstillinger vil kunne konfigureres her.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP-innstillinger vil kunne konfigureres her.",
"settings.permissions.title": "Tillatelser",
"settings.permissions.description": "Kontroller hvilke verktøy serveren kan bruke som standard.",
"settings.permissions.section.tools": "Verktøy",
"settings.permissions.toast.updateFailed.title": "Kunne ikke oppdatere tillatelser",
"settings.permissions.action.allow": "Tillat",
"settings.permissions.action.ask": "Spør",
"settings.permissions.action.deny": "Avslå",
"settings.permissions.tool.read.title": "Les",
"settings.permissions.tool.read.description": "Lesing av en fil (matcher filbanen)",
"settings.permissions.tool.edit.title": "Rediger",
"settings.permissions.tool.edit.description":
"Endre filer, inkludert redigeringer, skriving, patcher og multi-redigeringer",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Match filer ved hjelp av glob-mønstre",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Søk i filinnhold ved hjelp av regulære uttrykk",
"settings.permissions.tool.list.title": "Liste",
"settings.permissions.tool.list.description": "List filer i en mappe",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Kjør shell-kommandoer",
"settings.permissions.tool.task.title": "Oppgave",
"settings.permissions.tool.task.description": "Start underagenter",
"settings.permissions.tool.skill.title": "Ferdighet",
"settings.permissions.tool.skill.description": "Last en ferdighet etter navn",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Kjør språkserverforespørsler",
"settings.permissions.tool.todoread.title": "Les gjøremål",
"settings.permissions.tool.todoread.description": "Les gjøremålslisten",
"settings.permissions.tool.todowrite.title": "Skriv gjøremål",
"settings.permissions.tool.todowrite.description": "Oppdater gjøremålslisten",
"settings.permissions.tool.webfetch.title": "Webhenting",
"settings.permissions.tool.webfetch.description": "Hent innhold fra en URL",
"settings.permissions.tool.websearch.title": "Websøk",
"settings.permissions.tool.websearch.description": "Søk på nettet",
"settings.permissions.tool.codesearch.title": "Kodesøk",
"settings.permissions.tool.codesearch.description": "Søk etter kode på nettet",
"settings.permissions.tool.external_directory.title": "Ekstern mappe",
"settings.permissions.tool.external_directory.description": "Få tilgang til filer utenfor prosjektmappen",
"settings.permissions.tool.doom_loop.title": "Doom Loop",
"settings.permissions.tool.doom_loop.description": "Oppdager gjentatte verktøykall med identisk input",
"workspace.new": "Nytt arbeidsområde",
"workspace.type.local": "lokal",
"workspace.type.sandbox": "sandkasse",
"workspace.create.failed.title": "Kunne ikke opprette arbeidsområde",
"workspace.delete.failed.title": "Kunne ikke slette arbeidsområde",
"workspace.resetting.title": "Tilbakestiller arbeidsområde",
"workspace.resetting.description": "Dette kan ta et minutt.",
"workspace.reset.failed.title": "Kunne ikke tilbakestille arbeidsområde",
"workspace.reset.success.title": "Arbeidsområde tilbakestilt",
"workspace.reset.success.description": "Arbeidsområdet samsvarer nå med standardgrenen.",
"workspace.status.checking": "Sjekker for ikke-sammenslåtte endringer...",
"workspace.status.error": "Kunne ikke bekrefte git-status.",
"workspace.status.clean": "Ingen ikke-sammenslåtte endringer oppdaget.",
"workspace.status.dirty": "Ikke-sammenslåtte endringer oppdaget i dette arbeidsområdet.",
"workspace.delete.title": "Slett arbeidsområde",
"workspace.delete.confirm": 'Slette arbeidsområdet "{{name}}"?',
"workspace.delete.button": "Slett arbeidsområde",
"workspace.reset.title": "Tilbakestill arbeidsområde",
"workspace.reset.confirm": 'Tilbakestille arbeidsområdet "{{name}}"?',
"workspace.reset.button": "Tilbakestill arbeidsområde",
"workspace.reset.archived.none": "Ingen aktive sesjoner vil bli arkivert.",
"workspace.reset.archived.one": "1 sesjon vil bli arkivert.",
"workspace.reset.archived.many": "{{count}} sesjoner vil bli arkivert.",
"workspace.reset.note": "Dette vil tilbakestille arbeidsområdet til å samsvare med standardgrenen.",
} satisfies Partial<Record<Keys, string>>

661
packages/app/src/i18n/pl.ts Normal file
View File

@@ -0,0 +1,661 @@
export const dict = {
"command.category.suggested": "Sugerowane",
"command.category.view": "Widok",
"command.category.project": "Projekt",
"command.category.provider": "Dostawca",
"command.category.server": "Serwer",
"command.category.session": "Sesja",
"command.category.theme": "Motyw",
"command.category.language": "Język",
"command.category.file": "Plik",
"command.category.terminal": "Terminal",
"command.category.model": "Model",
"command.category.mcp": "MCP",
"command.category.agent": "Agent",
"command.category.permissions": "Uprawnienia",
"command.category.workspace": "Przestrzeń robocza",
"command.category.settings": "Ustawienia",
"theme.scheme.system": "Systemowy",
"theme.scheme.light": "Jasny",
"theme.scheme.dark": "Ciemny",
"command.sidebar.toggle": "Przełącz pasek boczny",
"command.project.open": "Otwórz projekt",
"command.provider.connect": "Połącz dostawcę",
"command.server.switch": "Przełącz serwer",
"command.settings.open": "Otwórz ustawienia",
"command.session.previous": "Poprzednia sesja",
"command.session.next": "Następna sesja",
"command.session.archive": "Zarchiwizuj sesję",
"command.palette": "Paleta poleceń",
"command.theme.cycle": "Przełącz motyw",
"command.theme.set": "Użyj motywu: {{theme}}",
"command.theme.scheme.cycle": "Przełącz schemat kolorów",
"command.theme.scheme.set": "Użyj schematu kolorów: {{scheme}}",
"command.language.cycle": "Przełącz język",
"command.language.set": "Użyj języka: {{language}}",
"command.session.new": "Nowa sesja",
"command.file.open": "Otwórz plik",
"command.file.open.description": "Szukaj plików i poleceń",
"command.terminal.toggle": "Przełącz terminal",
"command.review.toggle": "Przełącz przegląd",
"command.terminal.new": "Nowy terminal",
"command.terminal.new.description": "Utwórz nową kartę terminala",
"command.steps.toggle": "Przełącz kroki",
"command.steps.toggle.description": "Pokaż lub ukryj kroki dla bieżącej wiadomości",
"command.message.previous": "Poprzednia wiadomość",
"command.message.previous.description": "Przejdź do poprzedniej wiadomości użytkownika",
"command.message.next": "Następna wiadomość",
"command.message.next.description": "Przejdź do następnej wiadomości użytkownika",
"command.model.choose": "Wybierz model",
"command.model.choose.description": "Wybierz inny model",
"command.mcp.toggle": "Przełącz MCP",
"command.mcp.toggle.description": "Przełącz MCP",
"command.agent.cycle": "Przełącz agenta",
"command.agent.cycle.description": "Przełącz na następnego agenta",
"command.agent.cycle.reverse": "Przełącz agenta wstecz",
"command.agent.cycle.reverse.description": "Przełącz na poprzedniego agenta",
"command.model.variant.cycle": "Przełącz wysiłek myślowy",
"command.model.variant.cycle.description": "Przełącz na następny poziom wysiłku",
"command.permissions.autoaccept.enable": "Automatyczne akceptowanie edycji",
"command.permissions.autoaccept.disable": "Zatrzymaj automatyczne akceptowanie edycji",
"command.session.undo": "Cofnij",
"command.session.undo.description": "Cofnij ostatnią wiadomość",
"command.session.redo": "Ponów",
"command.session.redo.description": "Ponów ostatnią cofniętą wiadomość",
"command.session.compact": "Kompaktuj sesję",
"command.session.compact.description": "Podsumuj sesję, aby zmniejszyć rozmiar kontekstu",
"command.session.fork": "Rozwidlij od wiadomości",
"command.session.fork.description": "Utwórz nową sesję od poprzedniej wiadomości",
"command.session.share": "Udostępnij sesję",
"command.session.share.description": "Udostępnij tę sesję i skopiuj URL do schowka",
"command.session.unshare": "Przestań udostępniać sesję",
"command.session.unshare.description": "Zatrzymaj udostępnianie tej sesji",
"palette.search.placeholder": "Szukaj plików i poleceń",
"palette.empty": "Brak wyników",
"palette.group.commands": "Polecenia",
"palette.group.files": "Pliki",
"dialog.provider.search.placeholder": "Szukaj dostawców",
"dialog.provider.empty": "Nie znaleziono dostawców",
"dialog.provider.group.popular": "Popularne",
"dialog.provider.group.other": "Inne",
"dialog.provider.tag.recommended": "Zalecane",
"dialog.provider.anthropic.note": "Połącz z Claude Pro/Max lub kluczem API",
"dialog.provider.openai.note": "Połącz z ChatGPT Pro/Plus lub kluczem API",
"dialog.provider.copilot.note": "Połącz z Copilot lub kluczem API",
"dialog.model.select.title": "Wybierz model",
"dialog.model.search.placeholder": "Szukaj modeli",
"dialog.model.empty": "Brak wyników modelu",
"dialog.model.manage": "Zarządzaj modelami",
"dialog.model.manage.description": "Dostosuj, które modele pojawiają się w wyborze modelu.",
"dialog.model.unpaid.freeModels.title": "Darmowe modele dostarczane przez OpenCode",
"dialog.model.unpaid.addMore.title": "Dodaj więcej modeli od popularnych dostawców",
"dialog.provider.viewAll": "Zobacz wszystkich dostawców",
"provider.connect.title": "Połącz {{provider}}",
"provider.connect.title.anthropicProMax": "Zaloguj się z Claude Pro/Max",
"provider.connect.selectMethod": "Wybierz metodę logowania dla {{provider}}.",
"provider.connect.method.apiKey": "Klucz API",
"provider.connect.status.inProgress": "Autoryzacja w toku...",
"provider.connect.status.waiting": "Oczekiwanie na autoryzację...",
"provider.connect.status.failed": "Autoryzacja nie powiodła się: {{error}}",
"provider.connect.apiKey.description":
"Wprowadź swój klucz API {{provider}}, aby połączyć konto i używać modeli {{provider}} w OpenCode.",
"provider.connect.apiKey.label": "Klucz API {{provider}}",
"provider.connect.apiKey.placeholder": "Klucz API",
"provider.connect.apiKey.required": "Klucz API jest wymagany",
"provider.connect.opencodeZen.line1":
"OpenCode Zen daje dostęp do wybranego zestawu niezawodnych, zoptymalizowanych modeli dla agentów kodujących.",
"provider.connect.opencodeZen.line2":
"Z jednym kluczem API uzyskasz dostęp do modeli takich jak Claude, GPT, Gemini, GLM i więcej.",
"provider.connect.opencodeZen.visit.prefix": "Odwiedź ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": ", aby odebrać swój klucz API.",
"provider.connect.oauth.code.visit.prefix": "Odwiedź ",
"provider.connect.oauth.code.visit.link": "ten link",
"provider.connect.oauth.code.visit.suffix":
", aby odebrać kod autoryzacyjny, połączyć konto i używać modeli {{provider}} w OpenCode.",
"provider.connect.oauth.code.label": "Kod autoryzacyjny {{method}}",
"provider.connect.oauth.code.placeholder": "Kod autoryzacyjny",
"provider.connect.oauth.code.required": "Kod autoryzacyjny jest wymagany",
"provider.connect.oauth.code.invalid": "Nieprawidłowy kod autoryzacyjny",
"provider.connect.oauth.auto.visit.prefix": "Odwiedź ",
"provider.connect.oauth.auto.visit.link": "ten link",
"provider.connect.oauth.auto.visit.suffix":
" i wprowadź poniższy kod, aby połączyć konto i używać modeli {{provider}} w OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Kod potwierdzający",
"provider.connect.toast.connected.title": "Połączono {{provider}}",
"provider.connect.toast.connected.description": "Modele {{provider}} są teraz dostępne do użycia.",
"model.tag.free": "Darmowy",
"model.tag.latest": "Najnowszy",
"model.provider.anthropic": "Anthropic",
"model.provider.openai": "OpenAI",
"model.provider.google": "Google",
"model.provider.xai": "xAI",
"model.provider.meta": "Meta",
"model.input.text": "tekst",
"model.input.image": "obraz",
"model.input.audio": "audio",
"model.input.video": "wideo",
"model.input.pdf": "pdf",
"model.tooltip.allows": "Obsługuje: {{inputs}}",
"model.tooltip.reasoning.allowed": "Obsługuje wnioskowanie",
"model.tooltip.reasoning.none": "Brak wnioskowania",
"model.tooltip.context": "Limit kontekstu {{limit}}",
"common.search.placeholder": "Szukaj",
"common.goBack": "Wstecz",
"common.loading": "Ładowanie",
"common.loading.ellipsis": "...",
"common.cancel": "Anuluj",
"common.submit": "Prześlij",
"common.save": "Zapisz",
"common.saving": "Zapisywanie...",
"common.default": "Domyślny",
"common.attachment": "załącznik",
"prompt.placeholder.shell": "Wpisz polecenie terminala...",
"prompt.placeholder.normal": 'Zapytaj o cokolwiek... "{{example}}"',
"prompt.mode.shell": "Terminal",
"prompt.mode.shell.exit": "esc aby wyjść",
"prompt.example.1": "Napraw TODO w bazie kodu",
"prompt.example.2": "Jaki jest stos technologiczny tego projektu?",
"prompt.example.3": "Napraw zepsute testy",
"prompt.example.4": "Wyjaśnij jak działa uwierzytelnianie",
"prompt.example.5": "Znajdź i napraw luki w zabezpieczeniach",
"prompt.example.6": "Dodaj testy jednostkowe dla serwisu użytkownika",
"prompt.example.7": "Zrefaktoryzuj tę funkcję, aby była bardziej czytelna",
"prompt.example.8": "Co oznacza ten błąd?",
"prompt.example.9": "Pomóż mi zdebugować ten problem",
"prompt.example.10": "Wygeneruj dokumentację API",
"prompt.example.11": "Zoptymalizuj zapytania do bazy danych",
"prompt.example.12": "Dodaj walidację danych wejściowych",
"prompt.example.13": "Utwórz nowy komponent dla...",
"prompt.example.14": "Jak wdrożyć ten projekt?",
"prompt.example.15": "Sprawdź mój kod pod kątem najlepszych praktyk",
"prompt.example.16": "Dodaj obsługę błędów do tej funkcję",
"prompt.example.17": "Wyjaśnij ten wzorzec regex",
"prompt.example.18": "Przekonwertuj to na TypeScript",
"prompt.example.19": "Dodaj logowanie w całej bazie kodu",
"prompt.example.20": "Które zależności są przestarzałe?",
"prompt.example.21": "Pomóż mi napisać skrypt migracyjny",
"prompt.example.22": "Zaimplementuj cachowanie dla tego punktu końcowego",
"prompt.example.23": "Dodaj stronicowanie do tej listy",
"prompt.example.24": "Utwórz polecenie CLI dla...",
"prompt.example.25": "Jak działają tutaj zmienne środowiskowe?",
"prompt.popover.emptyResults": "Brak pasujących wyników",
"prompt.popover.emptyCommands": "Brak pasujących poleceń",
"prompt.dropzone.label": "Upuść obrazy lub pliki PDF tutaj",
"prompt.slash.badge.custom": "własne",
"prompt.context.active": "aktywny",
"prompt.context.includeActiveFile": "Dołącz aktywny plik",
"prompt.context.removeActiveFile": "Usuń aktywny plik z kontekstu",
"prompt.context.removeFile": "Usuń plik z kontekstu",
"prompt.action.attachFile": "Załącz plik",
"prompt.attachment.remove": "Usuń załącznik",
"prompt.action.send": "Wyślij",
"prompt.action.stop": "Zatrzymaj",
"prompt.toast.pasteUnsupported.title": "Nieobsługiwane wklejanie",
"prompt.toast.pasteUnsupported.description": "Tylko obrazy lub pliki PDF mogą być tutaj wklejane.",
"prompt.toast.modelAgentRequired.title": "Wybierz agenta i model",
"prompt.toast.modelAgentRequired.description": "Wybierz agenta i model przed wysłaniem zapytania.",
"prompt.toast.worktreeCreateFailed.title": "Nie udało się utworzyć drzewa roboczego",
"prompt.toast.sessionCreateFailed.title": "Nie udało się utworzyć sesji",
"prompt.toast.shellSendFailed.title": "Nie udało się wysłać polecenia powłoki",
"prompt.toast.commandSendFailed.title": "Nie udało się wysłać polecenia",
"prompt.toast.promptSendFailed.title": "Nie udało się wysłać zapytania",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} z {{total}} włączone",
"dialog.mcp.empty": "Brak skonfigurowanych MCP",
"mcp.status.connected": "połączono",
"mcp.status.failed": "niepowodzenie",
"mcp.status.needs_auth": "wymaga autoryzacji",
"mcp.status.disabled": "wyłączone",
"dialog.fork.empty": "Brak wiadomości do rozwidlenia",
"dialog.directory.search.placeholder": "Szukaj folderów",
"dialog.directory.empty": "Nie znaleziono folderów",
"dialog.server.title": "Serwery",
"dialog.server.description": "Przełącz serwer OpenCode, z którym łączy się ta aplikacja.",
"dialog.server.search.placeholder": "Szukaj serwerów",
"dialog.server.empty": "Brak serwerów",
"dialog.server.add.title": "Dodaj serwer",
"dialog.server.add.url": "URL serwera",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Nie można połączyć się z serwerem",
"dialog.server.add.checking": "Sprawdzanie...",
"dialog.server.add.button": "Dodaj",
"dialog.server.default.title": "Domyślny serwer",
"dialog.server.default.description":
"Połącz z tym serwerem przy uruchomieniu aplikacji zamiast uruchamiać lokalny serwer. Wymaga restartu.",
"dialog.server.default.none": "Nie wybrano serwera",
"dialog.server.default.set": "Ustaw bieżący serwer jako domyślny",
"dialog.server.default.clear": "Wyczyść",
"dialog.server.action.remove": "Usuń serwer",
"dialog.project.edit.title": "Edytuj projekt",
"dialog.project.edit.name": "Nazwa",
"dialog.project.edit.icon": "Ikona",
"dialog.project.edit.icon.alt": "Ikona projektu",
"dialog.project.edit.icon.hint": "Kliknij lub przeciągnij obraz",
"dialog.project.edit.icon.recommended": "Zalecane: 128x128px",
"dialog.project.edit.color": "Kolor",
"dialog.project.edit.color.select": "Wybierz kolor {{color}}",
"context.breakdown.title": "Podział kontekstu",
"context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.',
"context.breakdown.system": "System",
"context.breakdown.user": "Użytkownik",
"context.breakdown.assistant": "Asystent",
"context.breakdown.tool": "Wywołania narzędzi",
"context.breakdown.other": "Inne",
"context.systemPrompt.title": "Prompt systemowy",
"context.rawMessages.title": "Surowe wiadomości",
"context.stats.session": "Sesja",
"context.stats.messages": "Wiadomości",
"context.stats.provider": "Dostawca",
"context.stats.model": "Model",
"context.stats.limit": "Limit kontekstu",
"context.stats.totalTokens": "Całkowita liczba tokenów",
"context.stats.usage": "Użycie",
"context.stats.inputTokens": "Tokeny wejściowe",
"context.stats.outputTokens": "Tokeny wyjściowe",
"context.stats.reasoningTokens": "Tokeny wnioskowania",
"context.stats.cacheTokens": "Tokeny pamięci podręcznej (odczyt/zapis)",
"context.stats.userMessages": "Wiadomości użytkownika",
"context.stats.assistantMessages": "Wiadomości asystenta",
"context.stats.totalCost": "Całkowity koszt",
"context.stats.sessionCreated": "Utworzono sesję",
"context.stats.lastActivity": "Ostatnia aktywność",
"context.usage.tokens": "Tokeny",
"context.usage.usage": "Użycie",
"context.usage.cost": "Koszt",
"context.usage.clickToView": "Kliknij, aby zobaczyć kontekst",
"context.usage.view": "Pokaż użycie kontekstu",
"language.en": "Angielski",
"language.zh": "Chiński",
"language.ko": "Koreański",
"language.de": "Niemiecki",
"language.es": "Hiszpański",
"language.fr": "Francuski",
"language.ja": "Japoński",
"language.da": "Duński",
"language.pl": "Polski",
"language.ru": "Rosyjski",
"language.ar": "Arabski",
"language.no": "Norweski",
"language.br": "Portugalski (Brazylia)",
"toast.language.title": "Język",
"toast.language.description": "Przełączono na {{language}}",
"toast.theme.title": "Przełączono motyw",
"toast.scheme.title": "Schemat kolorów",
"toast.permissions.autoaccept.on.title": "Automatyczne akceptowanie edycji",
"toast.permissions.autoaccept.on.description": "Uprawnienia do edycji i zapisu będą automatycznie zatwierdzane",
"toast.permissions.autoaccept.off.title": "Zatrzymano automatyczne akceptowanie edycji",
"toast.permissions.autoaccept.off.description": "Uprawnienia do edycji i zapisu będą wymagały zatwierdzenia",
"toast.model.none.title": "Nie wybrano modelu",
"toast.model.none.description": "Połącz dostawcę, aby podsumować tę sesję",
"toast.file.loadFailed.title": "Nie udało się załadować pliku",
"toast.session.share.copyFailed.title": "Nie udało się skopiować URL do schowka",
"toast.session.share.success.title": "Sesja udostępniona",
"toast.session.share.success.description": "Link udostępniania skopiowany do schowka!",
"toast.session.share.failed.title": "Nie udało się udostępnić sesji",
"toast.session.share.failed.description": "Wystąpił błąd podczas udostępniania sesji",
"toast.session.unshare.success.title": "Zatrzymano udostępnianie sesji",
"toast.session.unshare.success.description": "Udostępnianie sesji zostało pomyślnie zatrzymane!",
"toast.session.unshare.failed.title": "Nie udało się zatrzymać udostępniania sesji",
"toast.session.unshare.failed.description": "Wystąpił błąd podczas zatrzymywania udostępniania sesji",
"toast.session.listFailed.title": "Nie udało się załadować sesji dla {{project}}",
"toast.update.title": "Dostępna aktualizacja",
"toast.update.description": "Nowa wersja OpenCode ({{version}}) jest teraz dostępna do instalacji.",
"toast.update.action.installRestart": "Zainstaluj i zrestartuj",
"toast.update.action.notYet": "Jeszcze nie",
"error.page.title": "Coś poszło nie tak",
"error.page.description": "Wystąpił błąd podczas ładowania aplikacji.",
"error.page.details.label": "Szczegóły błędu",
"error.page.action.restart": "Restartuj",
"error.page.action.checking": "Sprawdzanie...",
"error.page.action.checkUpdates": "Sprawdź aktualizacje",
"error.page.action.updateTo": "Zaktualizuj do {{version}}",
"error.page.report.prefix": "Proszę zgłosić ten błąd do zespołu OpenCode",
"error.page.report.discord": "na Discordzie",
"error.page.version": "Wersja: {{version}}",
"error.dev.rootNotFound":
"Nie znaleziono elementu głównego. Czy zapomniałeś dodać go do swojego index.html? A może atrybut id został błędnie wpisany?",
"error.globalSync.connectFailed": "Nie można połączyć się z serwerem. Czy serwer działa pod adresem `{{url}}`?",
"error.chain.unknown": "Nieznany błąd",
"error.chain.causedBy": "Spowodowany przez:",
"error.chain.apiError": "Błąd API",
"error.chain.status": "Status: {{status}}",
"error.chain.retryable": "Można ponowić: {{retryable}}",
"error.chain.responseBody": "Treść odpowiedzi:\n{{body}}",
"error.chain.didYouMean": "Czy miałeś na myśli: {{suggestions}}",
"error.chain.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}",
"error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli",
"error.chain.mcpFailed":
'Serwer MCP "{{name}}" nie powiódł się. Uwaga, OpenCode nie obsługuje jeszcze uwierzytelniania MCP.',
"error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.',
"error.chain.configJsonInvalid": "Plik konfiguracyjny w {{path}} nie jest poprawnym JSON(C)",
"error.chain.configJsonInvalidWithMessage": "Plik konfiguracyjny w {{path}} nie jest poprawnym JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'Katalog "{{dir}}" w {{path}} jest nieprawidłowy. Zmień nazwę katalogu na "{{suggestion}}" lub usuń go. To częsta literówka.',
"error.chain.configFrontmatterError": "Nie udało się przetworzyć frontmatter w {{path}}:\n{{message}}",
"error.chain.configInvalid": "Plik konfiguracyjny w {{path}} jest nieprawidłowy",
"error.chain.configInvalidWithMessage": "Plik konfiguracyjny w {{path}} jest nieprawidłowy: {{message}}",
"notification.permission.title": "Wymagane uprawnienie",
"notification.permission.description": "{{sessionTitle}} w {{projectName}} potrzebuje uprawnienia",
"notification.question.title": "Pytanie",
"notification.question.description": "{{sessionTitle}} w {{projectName}} ma pytanie",
"notification.action.goToSession": "Przejdź do sesji",
"notification.session.responseReady.title": "Odpowiedź gotowa",
"notification.session.error.title": "Błąd sesji",
"notification.session.error.fallbackDescription": "Wystąpił błąd",
"home.recentProjects": "Ostatnie projekty",
"home.empty.title": "Brak ostatnich projektów",
"home.empty.description": "Zacznij od otwarcia lokalnego projektu",
"session.tab.session": "Sesja",
"session.tab.review": "Przegląd",
"session.tab.context": "Kontekst",
"session.panel.reviewAndFiles": "Przegląd i pliki",
"session.review.filesChanged": "Zmieniono {{count}} plików",
"session.review.loadingChanges": "Ładowanie zmian...",
"session.review.empty": "Brak zmian w tej sesji",
"session.messages.renderEarlier": "Renderuj wcześniejsze wiadomości",
"session.messages.loadingEarlier": "Ładowanie wcześniejszych wiadomości...",
"session.messages.loadEarlier": "Załaduj wcześniejsze wiadomości",
"session.messages.loading": "Ładowanie wiadomości...",
"session.messages.jumpToLatest": "Przejdź do najnowszych",
"session.context.addToContext": "Dodaj {{selection}} do kontekstu",
"session.new.worktree.main": "Główna gałąź",
"session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
"session.new.worktree.create": "Utwórz nowe drzewo robocze",
"session.new.lastModified": "Ostatnio zmodyfikowano",
"session.header.search.placeholder": "Szukaj {{project}}",
"session.header.searchFiles": "Szukaj plików",
"session.share.popover.title": "Opublikuj w sieci",
"session.share.popover.description.shared":
"Ta sesja jest publiczna w sieci. Jest dostępna dla każdego, kto posiada link.",
"session.share.popover.description.unshared":
"Udostępnij sesję publicznie w sieci. Będzie dostępna dla każdego, kto posiada link.",
"session.share.action.share": "Udostępnij",
"session.share.action.publish": "Opublikuj",
"session.share.action.publishing": "Publikowanie...",
"session.share.action.unpublish": "Cofnij publikację",
"session.share.action.unpublishing": "Cofanie publikacji...",
"session.share.action.view": "Widok",
"session.share.copy.copied": "Skopiowano",
"session.share.copy.copyLink": "Kopiuj link",
"lsp.tooltip.none": "Brak serwerów LSP",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Ładowanie promptu...",
"terminal.loading": "Ładowanie terminala...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Zamknij terminal",
"terminal.connectionLost.title": "Utracono połączenie",
"terminal.connectionLost.description":
"Połączenie z terminalem zostało przerwane. Może się to zdarzyć przy restarcie serwera.",
"common.closeTab": "Zamknij kartę",
"common.dismiss": "Odrzuć",
"common.requestFailed": "Żądanie nie powiodło się",
"common.moreOptions": "Więcej opcji",
"common.learnMore": "Dowiedz się więcej",
"common.rename": "Zmień nazwę",
"common.reset": "Resetuj",
"common.archive": "Archiwizuj",
"common.delete": "Usuń",
"common.close": "Zamknij",
"common.edit": "Edytuj",
"common.loadMore": "Załaduj więcej",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Przełącz menu",
"sidebar.nav.projectsAndSessions": "Projekty i sesje",
"sidebar.settings": "Ustawienia",
"sidebar.help": "Pomoc",
"sidebar.workspaces.enable": "Włącz przestrzenie robocze",
"sidebar.workspaces.disable": "Wyłącz przestrzenie robocze",
"sidebar.gettingStarted.title": "Pierwsze kroki",
"sidebar.gettingStarted.line1": "OpenCode zawiera darmowe modele, więc możesz zacząć od razu.",
"sidebar.gettingStarted.line2": "Połącz dowolnego dostawcę, aby używać modeli, w tym Claude, GPT, Gemini itp.",
"sidebar.project.recentSessions": "Ostatnie sesje",
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
"settings.section.desktop": "Pulpit",
"settings.tab.general": "Ogólne",
"settings.tab.shortcuts": "Skróty",
"settings.general.section.appearance": "Wygląd",
"settings.general.section.notifications": "Powiadomienia systemowe",
"settings.general.section.sounds": "Efekty dźwiękowe",
"settings.general.row.language.title": "Język",
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
"settings.general.row.appearance.title": "Wygląd",
"settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu",
"settings.general.row.theme.title": "Motyw",
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
"settings.general.row.font.title": "Czcionka",
"settings.general.row.font.description": "Dostosuj czcionkę mono używaną w blokach kodu",
"font.option.ibmPlexMono": "IBM Plex Mono",
"font.option.cascadiaCode": "Cascadia Code",
"font.option.firaCode": "Fira Code",
"font.option.hack": "Hack",
"font.option.inconsolata": "Inconsolata",
"font.option.intelOneMono": "Intel One Mono",
"font.option.jetbrainsMono": "JetBrains Mono",
"font.option.mesloLgs": "Meslo LGS",
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"sound.option.alert01": "Alert 01",
"sound.option.alert02": "Alert 02",
"sound.option.alert03": "Alert 03",
"sound.option.alert04": "Alert 04",
"sound.option.alert05": "Alert 05",
"sound.option.alert06": "Alert 06",
"sound.option.alert07": "Alert 07",
"sound.option.alert08": "Alert 08",
"sound.option.alert09": "Alert 09",
"sound.option.alert10": "Alert 10",
"sound.option.bipbop01": "Bip-bop 01",
"sound.option.bipbop02": "Bip-bop 02",
"sound.option.bipbop03": "Bip-bop 03",
"sound.option.bipbop04": "Bip-bop 04",
"sound.option.bipbop05": "Bip-bop 05",
"sound.option.bipbop06": "Bip-bop 06",
"sound.option.bipbop07": "Bip-bop 07",
"sound.option.bipbop08": "Bip-bop 08",
"sound.option.bipbop09": "Bip-bop 09",
"sound.option.bipbop10": "Bip-bop 10",
"sound.option.staplebops01": "Staplebops 01",
"sound.option.staplebops02": "Staplebops 02",
"sound.option.staplebops03": "Staplebops 03",
"sound.option.staplebops04": "Staplebops 04",
"sound.option.staplebops05": "Staplebops 05",
"sound.option.staplebops06": "Staplebops 06",
"sound.option.staplebops07": "Staplebops 07",
"sound.option.nope01": "Nope 01",
"sound.option.nope02": "Nope 02",
"sound.option.nope03": "Nope 03",
"sound.option.nope04": "Nope 04",
"sound.option.nope05": "Nope 05",
"sound.option.nope06": "Nope 06",
"sound.option.nope07": "Nope 07",
"sound.option.nope08": "Nope 08",
"sound.option.nope09": "Nope 09",
"sound.option.nope10": "Nope 10",
"sound.option.nope11": "Nope 11",
"sound.option.nope12": "Nope 12",
"sound.option.yup01": "Yup 01",
"sound.option.yup02": "Yup 02",
"sound.option.yup03": "Yup 03",
"sound.option.yup04": "Yup 04",
"sound.option.yup05": "Yup 05",
"sound.option.yup06": "Yup 06",
"settings.general.notifications.agent.title": "Agent",
"settings.general.notifications.agent.description":
"Pokaż powiadomienie systemowe, gdy agent zakończy pracę lub wymaga uwagi",
"settings.general.notifications.permissions.title": "Uprawnienia",
"settings.general.notifications.permissions.description":
"Pokaż powiadomienie systemowe, gdy wymagane jest uprawnienie",
"settings.general.notifications.errors.title": "Błędy",
"settings.general.notifications.errors.description": "Pokaż powiadomienie systemowe, gdy wystąpi błąd",
"settings.general.sounds.agent.title": "Agent",
"settings.general.sounds.agent.description": "Odtwórz dźwięk, gdy agent zakończy pracę lub wymaga uwagi",
"settings.general.sounds.permissions.title": "Uprawnienia",
"settings.general.sounds.permissions.description": "Odtwórz dźwięk, gdy wymagane jest uprawnienie",
"settings.general.sounds.errors.title": "Błędy",
"settings.general.sounds.errors.description": "Odtwórz dźwięk, gdy wystąpi błąd",
"settings.shortcuts.title": "Skróty klawiszowe",
"settings.shortcuts.reset.button": "Przywróć domyślne",
"settings.shortcuts.reset.toast.title": "Zresetowano skróty",
"settings.shortcuts.reset.toast.description": "Skróty klawiszowe zostały przywrócone do ustawień domyślnych.",
"settings.shortcuts.conflict.title": "Skrót już w użyciu",
"settings.shortcuts.conflict.description": "{{keybind}} jest już przypisany do {{titles}}.",
"settings.shortcuts.unassigned": "Nieprzypisany",
"settings.shortcuts.pressKeys": "Naciśnij klawisze",
"settings.shortcuts.search.placeholder": "Szukaj skrótów",
"settings.shortcuts.search.empty": "Nie znaleziono skrótów",
"settings.shortcuts.group.general": "Ogólne",
"settings.shortcuts.group.session": "Sesja",
"settings.shortcuts.group.navigation": "Nawigacja",
"settings.shortcuts.group.modelAndAgent": "Model i agent",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Prompt",
"settings.providers.title": "Dostawcy",
"settings.providers.description": "Ustawienia dostawców będą tutaj konfigurowalne.",
"settings.models.title": "Modele",
"settings.models.description": "Ustawienia modeli będą tutaj konfigurowalne.",
"settings.agents.title": "Agenci",
"settings.agents.description": "Ustawienia agentów będą tutaj konfigurowalne.",
"settings.commands.title": "Polecenia",
"settings.commands.description": "Ustawienia poleceń będą tutaj konfigurowalne.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "Ustawienia MCP będą tutaj konfigurowalne.",
"settings.permissions.title": "Uprawnienia",
"settings.permissions.description": "Kontroluj, jakich narzędzi serwer może używać domyślnie.",
"settings.permissions.section.tools": "Narzędzia",
"settings.permissions.toast.updateFailed.title": "Nie udało się zaktualizować uprawnień",
"settings.permissions.action.allow": "Zezwól",
"settings.permissions.action.ask": "Pytaj",
"settings.permissions.action.deny": "Odmów",
"settings.permissions.tool.read.title": "Odczyt",
"settings.permissions.tool.read.description": "Odczyt pliku (pasuje do ścieżki pliku)",
"settings.permissions.tool.edit.title": "Edycja",
"settings.permissions.tool.edit.description": "Modyfikacja plików, w tym edycje, zapisy, łatki i multi-edycje",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Dopasowywanie plików za pomocą wzorców glob",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Przeszukiwanie zawartości plików za pomocą wyrażeń regularnych",
"settings.permissions.tool.list.title": "Lista",
"settings.permissions.tool.list.description": "Wyświetlanie listy plików w katalogu",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Uruchamianie poleceń powłoki",
"settings.permissions.tool.task.title": "Zadanie",
"settings.permissions.tool.task.description": "Uruchamianie pod-agentów",
"settings.permissions.tool.skill.title": "Umiejętność",
"settings.permissions.tool.skill.description": "Ładowanie umiejętności według nazwy",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Uruchamianie zapytań serwera językowego",
"settings.permissions.tool.todoread.title": "Odczyt Todo",
"settings.permissions.tool.todoread.description": "Odczyt listy zadań",
"settings.permissions.tool.todowrite.title": "Zapis Todo",
"settings.permissions.tool.todowrite.description": "Aktualizacja listy zadań",
"settings.permissions.tool.webfetch.title": "Pobieranie z sieci",
"settings.permissions.tool.webfetch.description": "Pobieranie zawartości z adresu URL",
"settings.permissions.tool.websearch.title": "Wyszukiwanie w sieci",
"settings.permissions.tool.websearch.description": "Przeszukiwanie sieci",
"settings.permissions.tool.codesearch.title": "Wyszukiwanie kodu",
"settings.permissions.tool.codesearch.description": "Przeszukiwanie kodu w sieci",
"settings.permissions.tool.external_directory.title": "Katalog zewnętrzny",
"settings.permissions.tool.external_directory.description": "Dostęp do plików poza katalogiem projektu",
"settings.permissions.tool.doom_loop.title": "Zapętlenie",
"settings.permissions.tool.doom_loop.description": "Wykrywanie powtarzających się wywołań narzędzi (doom loop)",
"session.delete.failed.title": "Nie udało się usunąć sesji",
"session.delete.title": "Usuń sesję",
"session.delete.confirm": 'Usunąć sesję "{{name}}"?',
"session.delete.button": "Usuń sesję",
"workspace.new": "Nowa przestrzeń robocza",
"workspace.type.local": "lokalna",
"workspace.type.sandbox": "piaskownica",
"workspace.create.failed.title": "Nie udało się utworzyć przestrzeni roboczej",
"workspace.delete.failed.title": "Nie udało się usunąć przestrzeni roboczej",
"workspace.resetting.title": "Resetowanie przestrzeni roboczej",
"workspace.resetting.description": "To może potrwać minutę.",
"workspace.reset.failed.title": "Nie udało się zresetować przestrzeni roboczej",
"workspace.reset.success.title": "Przestrzeń robocza zresetowana",
"workspace.reset.success.description": "Przestrzeń robocza odpowiada teraz domyślnej gałęzi.",
"workspace.status.checking": "Sprawdzanie niezscalonych zmian...",
"workspace.status.error": "Nie można zweryfikować statusu git.",
"workspace.status.clean": "Nie wykryto niezscalonych zmian.",
"workspace.status.dirty": "Wykryto niezscalone zmiany w tej przestrzeni roboczej.",
"workspace.delete.title": "Usuń przestrzeń roboczą",
"workspace.delete.confirm": 'Usunąć przestrzeń roboczą "{{name}}"?',
"workspace.delete.button": "Usuń przestrzeń roboczą",
"workspace.reset.title": "Resetuj przestrzeń roboczą",
"workspace.reset.confirm": 'Zresetować przestrzeń roboczą "{{name}}"?',
"workspace.reset.button": "Resetuj przestrzeń roboczą",
"workspace.reset.archived.none": "Żadne aktywne sesje nie zostaną zarchiwizowane.",
"workspace.reset.archived.one": "1 sesja zostanie zarchiwizowana.",
"workspace.reset.archived.many": "{{count}} sesji zostanie zarchiwizowanych.",
"workspace.reset.note": "To zresetuje przestrzeń roboczą, aby odpowiadała domyślnej gałęzi.",
}

664
packages/app/src/i18n/ru.ts Normal file
View File

@@ -0,0 +1,664 @@
export const dict = {
"command.category.suggested": "Предложено",
"command.category.view": "Просмотр",
"command.category.project": "Проект",
"command.category.provider": "Провайдер",
"command.category.server": "Сервер",
"command.category.session": "Сессия",
"command.category.theme": "Тема",
"command.category.language": "Язык",
"command.category.file": "Файл",
"command.category.terminal": "Терминал",
"command.category.model": "Модель",
"command.category.mcp": "MCP",
"command.category.agent": "Агент",
"command.category.permissions": "Разрешения",
"command.category.workspace": "Рабочее пространство",
"command.category.settings": "Настройки",
"theme.scheme.system": "Системная",
"theme.scheme.light": "Светлая",
"theme.scheme.dark": "Тёмная",
"command.sidebar.toggle": "Переключить боковую панель",
"command.project.open": "Открыть проект",
"command.provider.connect": "Подключить провайдера",
"command.server.switch": "Переключить сервер",
"command.settings.open": "Открыть настройки",
"command.session.previous": "Предыдущая сессия",
"command.session.next": "Следующая сессия",
"command.session.archive": "Архивировать сессию",
"command.palette": "Палитра команд",
"command.theme.cycle": "Цикл тем",
"command.theme.set": "Использовать тему: {{theme}}",
"command.theme.scheme.cycle": "Цикл цветовой схемы",
"command.theme.scheme.set": "Использовать цветовую схему: {{scheme}}",
"command.language.cycle": "Цикл языков",
"command.language.set": "Использовать язык: {{language}}",
"command.session.new": "Новая сессия",
"command.file.open": "Открыть файл",
"command.file.open.description": "Поиск файлов и команд",
"command.terminal.toggle": "Переключить терминал",
"command.review.toggle": "Переключить обзор",
"command.terminal.new": "Новый терминал",
"command.terminal.new.description": "Создать новую вкладку терминала",
"command.steps.toggle": "Переключить шаги",
"command.steps.toggle.description": "Показать или скрыть шаги для текущего сообщения",
"command.message.previous": "Предыдущее сообщение",
"command.message.previous.description": "Перейти к предыдущему сообщению пользователя",
"command.message.next": "Следующее сообщение",
"command.message.next.description": "Перейти к следующему сообщению пользователя",
"command.model.choose": "Выбрать модель",
"command.model.choose.description": "Выбрать другую модель",
"command.mcp.toggle": "Переключить MCP",
"command.mcp.toggle.description": "Переключить MCP",
"command.agent.cycle": "Цикл агентов",
"command.agent.cycle.description": "Переключиться к следующему агенту",
"command.agent.cycle.reverse": "Цикл агентов назад",
"command.agent.cycle.reverse.description": "Переключиться к предыдущему агенту",
"command.model.variant.cycle": "Цикл режимов мышления",
"command.model.variant.cycle.description": "Переключиться к следующему уровню усилий",
"command.permissions.autoaccept.enable": "Авто-принятие изменений",
"command.permissions.autoaccept.disable": "Прекратить авто-принятие изменений",
"command.session.undo": "Отменить",
"command.session.undo.description": "Отменить последнее сообщение",
"command.session.redo": "Повторить",
"command.session.redo.description": "Повторить отменённое сообщение",
"command.session.compact": "Сжать сессию",
"command.session.compact.description": "Сократить сессию для уменьшения размера контекста",
"command.session.fork": "Создать ответвление",
"command.session.fork.description": "Создать новую сессию из сообщения",
"command.session.share": "Поделиться сессией",
"command.session.share.description": "Поделиться сессией и скопировать URL в буфер обмена",
"command.session.unshare": "Отменить публикацию",
"command.session.unshare.description": "Прекратить публикацию сессии",
"palette.search.placeholder": "Поиск файлов и команд",
"palette.empty": "Ничего не найдено",
"palette.group.commands": "Команды",
"palette.group.files": "Файлы",
"dialog.provider.search.placeholder": "Поиск провайдеров",
"dialog.provider.empty": "Провайдеры не найдены",
"dialog.provider.group.popular": "Популярные",
"dialog.provider.group.other": "Другие",
"dialog.provider.tag.recommended": "Рекомендуемые",
"dialog.provider.anthropic.note": "Подключитесь с помощью Claude Pro/Max или API ключа",
"dialog.provider.openai.note": "Подключитесь с помощью ChatGPT Pro/Plus или API ключа",
"dialog.provider.copilot.note": "Подключитесь с помощью Copilot или API ключа",
"dialog.model.select.title": "Выбрать модель",
"dialog.model.search.placeholder": "Поиск моделей",
"dialog.model.empty": "Модели не найдены",
"dialog.model.manage": "Управление моделями",
"dialog.model.manage.description": "Настройте какие модели появляются в выборе модели",
"dialog.model.unpaid.freeModels.title": "Бесплатные модели от OpenCode",
"dialog.model.unpaid.addMore.title": "Добавьте больше моделей от популярных провайдеров",
"dialog.provider.viewAll": "Посмотреть всех провайдеров",
"provider.connect.title": "Подключить {{provider}}",
"provider.connect.title.anthropicProMax": "Войти с помощью Claude Pro/Max",
"provider.connect.selectMethod": "Выберите способ входа для {{provider}}.",
"provider.connect.method.apiKey": "API ключ",
"provider.connect.status.inProgress": "Авторизация...",
"provider.connect.status.waiting": "Ожидание авторизации...",
"provider.connect.status.failed": "Ошибка авторизации: {{error}}",
"provider.connect.apiKey.description":
"Введите ваш API ключ {{provider}} для подключения аккаунта и использования моделей {{provider}} в OpenCode.",
"provider.connect.apiKey.label": "{{provider}} API ключ",
"provider.connect.apiKey.placeholder": "API ключ",
"provider.connect.apiKey.required": "API ключ обязателен",
"provider.connect.opencodeZen.line1":
"OpenCode Zen даёт вам доступ к отобранным надёжным оптимизированным моделям для агентов программирования.",
"provider.connect.opencodeZen.line2":
"С одним API ключом вы получите доступ к таким моделям как Claude, GPT, Gemini, GLM и другим.",
"provider.connect.opencodeZen.visit.prefix": "Посетите ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " чтобы получить ваш API ключ.",
"provider.connect.oauth.code.visit.prefix": "Посетите ",
"provider.connect.oauth.code.visit.link": "эту ссылку",
"provider.connect.oauth.code.visit.suffix":
" чтобы получить код авторизации для подключения аккаунта и использования моделей {{provider}} в OpenCode.",
"provider.connect.oauth.code.label": "{{method}} код авторизации",
"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":
" и введите код ниже для подключения аккаунта и использования моделей {{provider}} в OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Код подтверждения",
"provider.connect.toast.connected.title": "{{provider}} подключён",
"provider.connect.toast.connected.description": "Модели {{provider}} теперь доступны.",
"model.tag.free": "Бесплатно",
"model.tag.latest": "Последняя",
"model.provider.anthropic": "Anthropic",
"model.provider.openai": "OpenAI",
"model.provider.google": "Google",
"model.provider.xai": "xAI",
"model.provider.meta": "Meta",
"model.input.text": "текст",
"model.input.image": "изображение",
"model.input.audio": "аудио",
"model.input.video": "видео",
"model.input.pdf": "pdf",
"model.tooltip.allows": "Разрешено: {{inputs}}",
"model.tooltip.reasoning.allowed": "Разрешает рассуждение",
"model.tooltip.reasoning.none": "Без рассуждения",
"model.tooltip.context": "Лимит контекста {{limit}}",
"common.search.placeholder": "Поиск",
"common.goBack": "Назад",
"common.loading": "Загрузка",
"common.loading.ellipsis": "...",
"common.cancel": "Отмена",
"common.submit": "Отправить",
"common.save": "Сохранить",
"common.saving": "Сохранение...",
"common.default": "По умолчанию",
"common.attachment": "вложение",
"prompt.placeholder.shell": "Введите команду оболочки...",
"prompt.placeholder.normal": 'Спросите что угодно... "{{example}}"',
"prompt.mode.shell": "Оболочка",
"prompt.mode.shell.exit": "esc для выхода",
"prompt.example.1": "Исправить TODO в коде",
"prompt.example.2": "Какой технологический стек этого проекта?",
"prompt.example.3": "Исправить сломанные тесты",
"prompt.example.4": "Объясни как работает аутентификация",
"prompt.example.5": "Найти и исправить уязвимости безопасности",
"prompt.example.6": "Добавить юнит-тесты для сервиса пользователя",
"prompt.example.7": "Рефакторить эту функцию для лучшей читаемости",
"prompt.example.8": "Что означает эта ошибка?",
"prompt.example.9": "Помоги мне отладить эту проблему",
"prompt.example.10": "Сгенерировать документацию API",
"prompt.example.11": "Оптимизировать запросы к базе данных",
"prompt.example.12": "Добавить валидацию ввода",
"prompt.example.13": "Создать новый компонент для...",
"prompt.example.14": "Как развернуть этот проект?",
"prompt.example.15": "Проверь мой код на лучшие практики",
"prompt.example.16": "Добавить обработку ошибок в эту функцию",
"prompt.example.17": "Объясни этот паттерн regex",
"prompt.example.18": "Конвертировать это в TypeScript",
"prompt.example.19": "Добавить логирование по всему проекту",
"prompt.example.20": "Какие зависимости устарели?",
"prompt.example.21": "Помоги написать скрипт миграции",
"prompt.example.22": "Реализовать кэширование для этой конечной точки",
"prompt.example.23": "Добавить пагинацию в этот список",
"prompt.example.24": "Создать CLI команду для...",
"prompt.example.25": "Как работают переменные окружения здесь?",
"prompt.popover.emptyResults": "Нет совпадений",
"prompt.popover.emptyCommands": "Нет совпадающих команд",
"prompt.dropzone.label": "Перетащите изображения или PDF сюда",
"prompt.slash.badge.custom": "своё",
"prompt.context.active": "активно",
"prompt.context.includeActiveFile": "Включить активный файл",
"prompt.context.removeActiveFile": "Удалить активный файл из контекста",
"prompt.context.removeFile": "Удалить файл из контекста",
"prompt.action.attachFile": "Прикрепить файл",
"prompt.attachment.remove": "Удалить вложение",
"prompt.action.send": "Отправить",
"prompt.action.stop": "Остановить",
"prompt.toast.pasteUnsupported.title": "Неподдерживаемая вставка",
"prompt.toast.pasteUnsupported.description": "Сюда можно вставлять только изображения или PDF.",
"prompt.toast.modelAgentRequired.title": "Выберите агента и модель",
"prompt.toast.modelAgentRequired.description": "Выберите агента и модель перед отправкой запроса.",
"prompt.toast.worktreeCreateFailed.title": "Не удалось создать worktree",
"prompt.toast.sessionCreateFailed.title": "Не удалось создать сессию",
"prompt.toast.shellSendFailed.title": "Не удалось отправить команду оболочки",
"prompt.toast.commandSendFailed.title": "Не удалось отправить команду",
"prompt.toast.promptSendFailed.title": "Не удалось отправить запрос",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} из {{total}} включено",
"dialog.mcp.empty": "MCP не настроены",
"mcp.status.connected": "подключено",
"mcp.status.failed": "ошибка",
"mcp.status.needs_auth": "требуется авторизация",
"mcp.status.disabled": "отключено",
"dialog.fork.empty": "Нет сообщений для ответвления",
"dialog.directory.search.placeholder": "Поиск папок",
"dialog.directory.empty": "Папки не найдены",
"dialog.server.title": "Серверы",
"dialog.server.description": "Переключите сервер OpenCode к которому подключается приложение.",
"dialog.server.search.placeholder": "Поиск серверов",
"dialog.server.empty": "Серверов пока нет",
"dialog.server.add.title": "Добавить сервер",
"dialog.server.add.url": "URL сервера",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Не удалось подключиться к серверу",
"dialog.server.add.checking": "Проверка...",
"dialog.server.add.button": "Добавить",
"dialog.server.default.title": "Сервер по умолчанию",
"dialog.server.default.description":
"Подключаться к этому серверу при запуске приложения вместо запуска локального сервера. Требуется перезапуск.",
"dialog.server.default.none": "Сервер не выбран",
"dialog.server.default.set": "Установить текущий сервер по умолчанию",
"dialog.server.default.clear": "Очистить",
"dialog.server.action.remove": "Удалить сервер",
"dialog.project.edit.title": "Редактировать проект",
"dialog.project.edit.name": "Название",
"dialog.project.edit.icon": "Иконка",
"dialog.project.edit.icon.alt": "Иконка проекта",
"dialog.project.edit.icon.hint": "Нажмите или перетащите изображение",
"dialog.project.edit.icon.recommended": "Рекомендуется: 128x128px",
"dialog.project.edit.color": "Цвет",
"dialog.project.edit.color.select": "Выбрать цвет {{color}}",
"context.breakdown.title": "Разбивка контекста",
"context.breakdown.note":
'Приблизительная разбивка входных токенов. "Другое" включает определения инструментов и накладные расходы.',
"context.breakdown.system": "Система",
"context.breakdown.user": "Пользователь",
"context.breakdown.assistant": "Ассистент",
"context.breakdown.tool": "Вызовы инструментов",
"context.breakdown.other": "Другое",
"context.systemPrompt.title": "Системный промпт",
"context.rawMessages.title": "Исходные сообщения",
"context.stats.session": "Сессия",
"context.stats.messages": "Сообщения",
"context.stats.provider": "Провайдер",
"context.stats.model": "Модель",
"context.stats.limit": "Лимит контекста",
"context.stats.totalTokens": "Всего токенов",
"context.stats.usage": "Использование",
"context.stats.inputTokens": "Входные токены",
"context.stats.outputTokens": "Выходные токены",
"context.stats.reasoningTokens": "Токены рассуждения",
"context.stats.cacheTokens": "Токены кэша (чтение/запись)",
"context.stats.userMessages": "Сообщения пользователя",
"context.stats.assistantMessages": "Сообщения ассистента",
"context.stats.totalCost": "Общая стоимость",
"context.stats.sessionCreated": "Сессия создана",
"context.stats.lastActivity": "Последняя активность",
"context.usage.tokens": "Токены",
"context.usage.usage": "Использование",
"context.usage.cost": "Стоимость",
"context.usage.clickToView": "Нажмите для просмотра контекста",
"context.usage.view": "Показать использование контекста",
"language.en": "Английский",
"language.zh": "Китайский",
"language.ko": "Корейский",
"language.de": "Немецкий",
"language.es": "Испанский",
"language.fr": "Французский",
"language.ja": "Японский",
"language.da": "Датский",
"language.ru": "Русский",
"language.ar": "Арабский",
"language.no": "Норвежский",
"language.br": "Португальский (Бразилия)",
"toast.language.title": "Язык",
"toast.language.description": "Переключено на {{language}}",
"toast.theme.title": "Тема переключена",
"toast.scheme.title": "Цветовая схема",
"toast.permissions.autoaccept.on.title": "Авто-принятие изменений",
"toast.permissions.autoaccept.on.description": "Разрешения на редактирование и запись будут автоматически одобрены",
"toast.permissions.autoaccept.off.title": "Авто-принятие остановлено",
"toast.permissions.autoaccept.off.description": "Редактирование и запись потребуют подтверждения",
"toast.model.none.title": "Модель не выбрана",
"toast.model.none.description": "Подключите провайдера для суммаризации сессии",
"toast.file.loadFailed.title": "Не удалось загрузить файл",
"toast.session.share.copyFailed.title": "Не удалось скопировать URL в буфер обмена",
"toast.session.share.success.title": "Сессия опубликована",
"toast.session.share.success.description": "URL скопирован в буфер обмена!",
"toast.session.share.failed.title": "Не удалось опубликовать сессию",
"toast.session.share.failed.description": "Произошла ошибка при публикации сессии",
"toast.session.unshare.success.title": "Публикация отменена",
"toast.session.unshare.success.description": "Публикация успешно отменена!",
"toast.session.unshare.failed.title": "Не удалось отменить публикацию",
"toast.session.unshare.failed.description": "Произошла ошибка при отмене публикации",
"toast.session.listFailed.title": "Не удалось загрузить сессии для {{project}}",
"toast.update.title": "Доступно обновление",
"toast.update.description": "Новая версия OpenCode ({{version}}) доступна для установки.",
"toast.update.action.installRestart": "Установить и перезапустить",
"toast.update.action.notYet": "Пока нет",
"error.page.title": "Что-то пошло не так",
"error.page.description": "Произошла ошибка при загрузке приложения.",
"error.page.details.label": "Детали ошибки",
"error.page.action.restart": "Перезапустить",
"error.page.action.checking": "Проверка...",
"error.page.action.checkUpdates": "Проверить обновления",
"error.page.action.updateTo": "Обновить до {{version}}",
"error.page.report.prefix": "Пожалуйста, сообщите об этой ошибке команде OpenCode",
"error.page.report.discord": "в Discord",
"error.page.version": "Версия: {{version}}",
"error.dev.rootNotFound":
"Корневой элемент не найден. Вы забыли добавить его в index.html? Или, может быть, атрибут id был написан неправильно?",
"error.globalSync.connectFailed": "Не удалось подключиться к серверу. Запущен ли сервер по адресу `{{url}}`?",
"error.chain.unknown": "Неизвестная ошибка",
"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.checkConfig": "Проверьте названия провайдера/модели в конфиге (opencode.json)",
"error.chain.mcpFailed":
'MCP сервер "{{name}}" завершился с ошибкой. Обратите внимание, что OpenCode пока не поддерживает MCP авторизацию.',
"error.chain.providerAuthFailed": "Ошибка аутентификации провайдера ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'Не удалось инициализировать провайдера "{{provider}}". Проверьте учётные данные и конфигурацию.',
"error.chain.configJsonInvalid": "Конфигурационный файл по адресу {{path}} не является валидным JSON(C)",
"error.chain.configJsonInvalidWithMessage":
"Конфигурационный файл по адресу {{path}} не является валидным JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'Папка "{{dir}}" в {{path}} невалидна. Переименуйте папку в "{{suggestion}}" или удалите её. Это распространённая опечатка.',
"error.chain.configFrontmatterError": "Не удалось разобрать frontmatter в {{path}}:\n{{message}}",
"error.chain.configInvalid": "Конфигурационный файл по адресу {{path}} невалиден",
"error.chain.configInvalidWithMessage": "Конфигурационный файл по адресу {{path}} невалиден: {{message}}",
"notification.permission.title": "Требуется разрешение",
"notification.permission.description": "{{sessionTitle}} в {{projectName}} требуется разрешение",
"notification.question.title": "Вопрос",
"notification.question.description": "У {{sessionTitle}} в {{projectName}} есть вопрос",
"notification.action.goToSession": "Перейти к сессии",
"notification.session.responseReady.title": "Ответ готов",
"notification.session.error.title": "Ошибка сессии",
"notification.session.error.fallbackDescription": "Произошла ошибка",
"home.recentProjects": "Недавние проекты",
"home.empty.title": "Нет недавних проектов",
"home.empty.description": "Начните с открытия локального проекта",
"session.tab.session": "Сессия",
"session.tab.review": "Обзор",
"session.tab.context": "Контекст",
"session.panel.reviewAndFiles": "Обзор и файлы",
"session.review.filesChanged": "{{count}} файлов изменено",
"session.review.loadingChanges": "Загрузка изменений...",
"session.review.empty": "Изменений в этой сессии пока нет",
"session.messages.renderEarlier": "Показать предыдущие сообщения",
"session.messages.loadingEarlier": "Загрузка предыдущих сообщений...",
"session.messages.loadEarlier": "Загрузить предыдущие сообщения",
"session.messages.loading": "Загрузка сообщений...",
"session.messages.jumpToLatest": "Перейти к последнему",
"session.context.addToContext": "Добавить {{selection}} в контекст",
"session.new.worktree.main": "Основная ветка",
"session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})",
"session.new.worktree.create": "Создать новый worktree",
"session.new.lastModified": "Последнее изменение",
"session.header.search.placeholder": "Поиск {{project}}",
"session.header.searchFiles": "Поиск файлов",
"session.share.popover.title": "Опубликовать в интернете",
"session.share.popover.description.shared":
"Эта сессия общедоступна. Доступ к ней может получить любой, у кого есть ссылка.",
"session.share.popover.description.unshared":
"Опубликуйте сессию в интернете. Доступ к ней сможет получить любой, у кого есть ссылка.",
"session.share.action.share": "Поделиться",
"session.share.action.publish": "Опубликовать",
"session.share.action.publishing": "Публикация...",
"session.share.action.unpublish": "Отменить публикацию",
"session.share.action.unpublishing": "Отмена публикации...",
"session.share.action.view": "Посмотреть",
"session.share.copy.copied": "Скопировано",
"session.share.copy.copyLink": "Копировать ссылку",
"lsp.tooltip.none": "Нет LSP серверов",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Загрузка запроса...",
"terminal.loading": "Загрузка терминала...",
"terminal.title": "Терминал",
"terminal.title.numbered": "Терминал {{number}}",
"terminal.close": "Закрыть терминал",
"terminal.connectionLost.title": "Соединение потеряно",
"terminal.connectionLost.description":
"Соединение с терминалом прервано. Это может произойти при перезапуске сервера.",
"common.closeTab": "Закрыть вкладку",
"common.dismiss": "Закрыть",
"common.requestFailed": "Запрос не выполнен",
"common.moreOptions": "Дополнительные опции",
"common.learnMore": "Подробнее",
"common.rename": "Переименовать",
"common.reset": "Сбросить",
"common.archive": "Архивировать",
"common.delete": "Удалить",
"common.close": "Закрыть",
"common.edit": "Редактировать",
"common.loadMore": "Загрузить ещё",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Переключить меню",
"sidebar.nav.projectsAndSessions": "Проекты и сессии",
"sidebar.settings": "Настройки",
"sidebar.help": "Помощь",
"sidebar.workspaces.enable": "Включить рабочие пространства",
"sidebar.workspaces.disable": "Отключить рабочие пространства",
"sidebar.gettingStarted.title": "Начало работы",
"sidebar.gettingStarted.line1": "OpenCode включает бесплатные модели, чтобы вы могли начать сразу.",
"sidebar.gettingStarted.line2":
"Подключите любого провайдера для использования моделей, включая Claude, GPT, Gemini и др.",
"sidebar.project.recentSessions": "Недавние сессии",
"sidebar.project.viewAllSessions": "Посмотреть все сессии",
"settings.section.desktop": "Приложение",
"settings.tab.general": "Основные",
"settings.tab.shortcuts": "Горячие клавиши",
"settings.general.section.appearance": "Внешний вид",
"settings.general.section.notifications": "Системные уведомления",
"settings.general.section.sounds": "Звуковые эффекты",
"settings.general.row.language.title": "Язык",
"settings.general.row.language.description": "Изменить язык отображения OpenCode",
"settings.general.row.appearance.title": "Внешний вид",
"settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве",
"settings.general.row.theme.title": "Тема",
"settings.general.row.theme.description": "Настройте оформление OpenCode.",
"settings.general.row.font.title": "Шрифт",
"settings.general.row.font.description": "Настройте моноширинный шрифт для блоков кода",
"font.option.ibmPlexMono": "IBM Plex Mono",
"font.option.cascadiaCode": "Cascadia Code",
"font.option.firaCode": "Fira Code",
"font.option.hack": "Hack",
"font.option.inconsolata": "Inconsolata",
"font.option.intelOneMono": "Intel One Mono",
"font.option.jetbrainsMono": "JetBrains Mono",
"font.option.mesloLgs": "Meslo LGS",
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"sound.option.alert01": "Alert 01",
"sound.option.alert02": "Alert 02",
"sound.option.alert03": "Alert 03",
"sound.option.alert04": "Alert 04",
"sound.option.alert05": "Alert 05",
"sound.option.alert06": "Alert 06",
"sound.option.alert07": "Alert 07",
"sound.option.alert08": "Alert 08",
"sound.option.alert09": "Alert 09",
"sound.option.alert10": "Alert 10",
"sound.option.bipbop01": "Bip-bop 01",
"sound.option.bipbop02": "Bip-bop 02",
"sound.option.bipbop03": "Bip-bop 03",
"sound.option.bipbop04": "Bip-bop 04",
"sound.option.bipbop05": "Bip-bop 05",
"sound.option.bipbop06": "Bip-bop 06",
"sound.option.bipbop07": "Bip-bop 07",
"sound.option.bipbop08": "Bip-bop 08",
"sound.option.bipbop09": "Bip-bop 09",
"sound.option.bipbop10": "Bip-bop 10",
"sound.option.staplebops01": "Staplebops 01",
"sound.option.staplebops02": "Staplebops 02",
"sound.option.staplebops03": "Staplebops 03",
"sound.option.staplebops04": "Staplebops 04",
"sound.option.staplebops05": "Staplebops 05",
"sound.option.staplebops06": "Staplebops 06",
"sound.option.staplebops07": "Staplebops 07",
"sound.option.nope01": "Nope 01",
"sound.option.nope02": "Nope 02",
"sound.option.nope03": "Nope 03",
"sound.option.nope04": "Nope 04",
"sound.option.nope05": "Nope 05",
"sound.option.nope06": "Nope 06",
"sound.option.nope07": "Nope 07",
"sound.option.nope08": "Nope 08",
"sound.option.nope09": "Nope 09",
"sound.option.nope10": "Nope 10",
"sound.option.nope11": "Nope 11",
"sound.option.nope12": "Nope 12",
"sound.option.yup01": "Yup 01",
"sound.option.yup02": "Yup 02",
"sound.option.yup03": "Yup 03",
"sound.option.yup04": "Yup 04",
"sound.option.yup05": "Yup 05",
"sound.option.yup06": "Yup 06",
"settings.general.notifications.agent.title": "Агент",
"settings.general.notifications.agent.description":
"Показывать системное уведомление когда агент завершён или требует внимания",
"settings.general.notifications.permissions.title": "Разрешения",
"settings.general.notifications.permissions.description":
"Показывать системное уведомление когда требуется разрешение",
"settings.general.notifications.errors.title": "Ошибки",
"settings.general.notifications.errors.description": "Показывать системное уведомление когда происходит ошибка",
"settings.general.sounds.agent.title": "Агент",
"settings.general.sounds.agent.description": "Воспроизводить звук когда агент завершён или требует внимания",
"settings.general.sounds.permissions.title": "Разрешения",
"settings.general.sounds.permissions.description": "Воспроизводить звук когда требуется разрешение",
"settings.general.sounds.errors.title": "Ошибки",
"settings.general.sounds.errors.description": "Воспроизводить звук когда происходит ошибка",
"settings.shortcuts.title": "Горячие клавиши",
"settings.shortcuts.reset.button": "Сбросить к умолчаниям",
"settings.shortcuts.reset.toast.title": "Горячие клавиши сброшены",
"settings.shortcuts.reset.toast.description": "Горячие клавиши были сброшены к значениям по умолчанию.",
"settings.shortcuts.conflict.title": "Сочетание уже используется",
"settings.shortcuts.conflict.description": "{{keybind}} уже назначено для {{titles}}.",
"settings.shortcuts.unassigned": "Не назначено",
"settings.shortcuts.pressKeys": "Нажмите клавиши",
"settings.shortcuts.search.placeholder": "Поиск горячих клавиш",
"settings.shortcuts.search.empty": "Горячие клавиши не найдены",
"settings.shortcuts.group.general": "Основные",
"settings.shortcuts.group.session": "Сессия",
"settings.shortcuts.group.navigation": "Навигация",
"settings.shortcuts.group.modelAndAgent": "Модель и агент",
"settings.shortcuts.group.terminal": "Терминал",
"settings.shortcuts.group.prompt": "Запрос",
"settings.providers.title": "Провайдеры",
"settings.providers.description": "Настройки провайдеров будут доступны здесь.",
"settings.models.title": "Модели",
"settings.models.description": "Настройки моделей будут доступны здесь.",
"settings.agents.title": "Агенты",
"settings.agents.description": "Настройки агентов будут доступны здесь.",
"settings.commands.title": "Команды",
"settings.commands.description": "Настройки команд будут доступны здесь.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "Настройки MCP будут доступны здесь.",
"settings.permissions.title": "Разрешения",
"settings.permissions.description": "Контролируйте какие инструменты сервер может использовать по умолчанию.",
"settings.permissions.section.tools": "Инструменты",
"settings.permissions.toast.updateFailed.title": "Не удалось обновить разрешения",
"settings.permissions.action.allow": "Разрешить",
"settings.permissions.action.ask": "Спрашивать",
"settings.permissions.action.deny": "Запретить",
"settings.permissions.tool.read.title": "Чтение",
"settings.permissions.tool.read.description": "Чтение файла (по совпадению пути)",
"settings.permissions.tool.edit.title": "Редактирование",
"settings.permissions.tool.edit.description":
"Изменение файлов, включая редактирование, запись, патчи и мульти-редактирование",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Сопоставление файлов по паттернам glob",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Поиск по содержимому файлов с использованием регулярных выражений",
"settings.permissions.tool.list.title": "Список",
"settings.permissions.tool.list.description": "Список файлов в директории",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Выполнение команд оболочки",
"settings.permissions.tool.task.title": "Task",
"settings.permissions.tool.task.description": "Запуск под-агентов",
"settings.permissions.tool.skill.title": "Skill",
"settings.permissions.tool.skill.description": "Загрузить навык по имени",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Выполнение запросов к языковому серверу",
"settings.permissions.tool.todoread.title": "Чтение списка задач",
"settings.permissions.tool.todoread.description": "Чтение списка задач",
"settings.permissions.tool.todowrite.title": "Запись списка задач",
"settings.permissions.tool.todowrite.description": "Обновление списка задач",
"settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "Получить содержимое по URL",
"settings.permissions.tool.websearch.title": "Web Search",
"settings.permissions.tool.websearch.description": "Поиск в интернете",
"settings.permissions.tool.codesearch.title": "Поиск кода",
"settings.permissions.tool.codesearch.description": "Поиск кода в интернете",
"settings.permissions.tool.external_directory.title": "Внешняя директория",
"settings.permissions.tool.external_directory.description": "Доступ к файлам вне директории проекта",
"settings.permissions.tool.doom_loop.title": "Doom Loop",
"settings.permissions.tool.doom_loop.description": "Обнаружение повторных вызовов инструментов с одинаковым вводом",
"session.delete.failed.title": "Не удалось удалить сессию",
"session.delete.title": "Удалить сессию",
"session.delete.confirm": 'Удалить сессию "{{name}}"?',
"session.delete.button": "Удалить сессию",
"workspace.new": "Новое рабочее пространство",
"workspace.type.local": "локальное",
"workspace.type.sandbox": "песочница",
"workspace.create.failed.title": "Не удалось создать рабочее пространство",
"workspace.delete.failed.title": "Не удалось удалить рабочее пространство",
"workspace.resetting.title": "Сброс рабочего пространства",
"workspace.resetting.description": "Это может занять минуту.",
"workspace.reset.failed.title": "Не удалось сбросить рабочее пространство",
"workspace.reset.success.title": "Рабочее пространство сброшено",
"workspace.reset.success.description": "Рабочее пространство теперь соответствует ветке по умолчанию.",
"workspace.status.checking": "Проверка наличия неслитых изменений...",
"workspace.status.error": "Не удалось проверить статус git.",
"workspace.status.clean": "Неслитые изменения не обнаружены.",
"workspace.status.dirty": "Обнаружены неслитые изменения в этом рабочем пространстве.",
"workspace.delete.title": "Удалить рабочее пространство",
"workspace.delete.confirm": 'Удалить рабочее пространство "{{name}}"?',
"workspace.delete.button": "Удалить рабочее пространство",
"workspace.reset.title": "Сбросить рабочее пространство",
"workspace.reset.confirm": 'Сбросить рабочее пространство "{{name}}"?',
"workspace.reset.button": "Сбросить рабочее пространство",
"workspace.reset.archived.none": "Никакие активные сессии не будут архивированы.",
"workspace.reset.archived.one": "1 сессия будет архивирована.",
"workspace.reset.archived.many": "{{count}} сессий будет архивировано.",
"workspace.reset.note": "Рабочее пространство будет сброшено в соответствие с веткой по умолчанию.",
}

574
packages/app/src/i18n/zh.ts Normal file
View File

@@ -0,0 +1,574 @@
import { dict as en } from "./en"
type Keys = keyof typeof en
export const dict = {
"command.category.suggested": "建议",
"command.category.view": "视图",
"command.category.project": "项目",
"command.category.provider": "提供商",
"command.category.server": "服务器",
"command.category.session": "会话",
"command.category.theme": "主题",
"command.category.language": "语言",
"command.category.file": "文件",
"command.category.terminal": "终端",
"command.category.model": "模型",
"command.category.mcp": "MCP",
"command.category.agent": "智能体",
"command.category.permissions": "权限",
"command.category.workspace": "工作区",
"theme.scheme.system": "系统",
"theme.scheme.light": "浅色",
"theme.scheme.dark": "深色",
"command.sidebar.toggle": "切换侧边栏",
"command.project.open": "打开项目",
"command.provider.connect": "连接提供商",
"command.server.switch": "切换服务器",
"command.session.previous": "上一个会话",
"command.session.next": "下一个会话",
"command.session.archive": "归档会话",
"command.palette": "命令面板",
"command.theme.cycle": "切换主题",
"command.theme.set": "使用主题: {{theme}}",
"command.theme.scheme.cycle": "切换配色方案",
"command.theme.scheme.set": "使用配色方案: {{scheme}}",
"command.language.cycle": "切换语言",
"command.language.set": "使用语言: {{language}}",
"command.session.new": "新建会话",
"command.file.open": "打开文件",
"command.file.open.description": "搜索文件和命令",
"command.terminal.toggle": "切换终端",
"command.review.toggle": "切换审查",
"command.terminal.new": "新建终端",
"command.terminal.new.description": "创建新的终端标签页",
"command.steps.toggle": "切换步骤",
"command.steps.toggle.description": "显示或隐藏当前消息的步骤",
"command.message.previous": "上一条消息",
"command.message.previous.description": "跳转到上一条用户消息",
"command.message.next": "下一条消息",
"command.message.next.description": "跳转到下一条用户消息",
"command.model.choose": "选择模型",
"command.model.choose.description": "选择不同的模型",
"command.mcp.toggle": "切换 MCPs",
"command.mcp.toggle.description": "切换 MCPs",
"command.agent.cycle": "切换智能体",
"command.agent.cycle.description": "切换到下一个智能体",
"command.agent.cycle.reverse": "反向切换智能体",
"command.agent.cycle.reverse.description": "切换到上一个智能体",
"command.model.variant.cycle": "切换思考强度",
"command.model.variant.cycle.description": "切换到下一个强度等级",
"command.permissions.autoaccept.enable": "自动接受编辑",
"command.permissions.autoaccept.disable": "停止自动接受编辑",
"command.session.undo": "撤销",
"command.session.undo.description": "撤销上一条消息",
"command.session.redo": "重做",
"command.session.redo.description": "重做上一条撤销的消息",
"command.session.compact": "精简会话",
"command.session.compact.description": "总结会话以减少上下文大小",
"command.session.fork": "从消息分叉",
"command.session.fork.description": "从之前的消息创建新会话",
"command.session.share": "分享会话",
"command.session.share.description": "分享此会话并将链接复制到剪贴板",
"command.session.unshare": "取消分享会话",
"command.session.unshare.description": "停止分享此会话",
"palette.search.placeholder": "搜索文件和命令",
"palette.empty": "未找到结果",
"palette.group.commands": "命令",
"palette.group.files": "文件",
"dialog.provider.search.placeholder": "搜索提供商",
"dialog.provider.empty": "未找到提供商",
"dialog.provider.group.popular": "热门",
"dialog.provider.group.other": "其他",
"dialog.provider.tag.recommended": "推荐",
"dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 密钥连接",
"dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 密钥连接",
"dialog.provider.copilot.note": "使用 Copilot 或 API 密钥连接",
"dialog.model.select.title": "选择模型",
"dialog.model.search.placeholder": "搜索模型",
"dialog.model.empty": "未找到模型",
"dialog.model.manage": "管理模型",
"dialog.model.manage.description": "自定义模型选择器中显示的模型。",
"dialog.model.unpaid.freeModels.title": "OpenCode 提供的免费模型",
"dialog.model.unpaid.addMore.title": "从热门提供商添加更多模型",
"dialog.provider.viewAll": "查看全部提供商",
"provider.connect.title": "连接 {{provider}}",
"provider.connect.title.anthropicProMax": "使用 Claude Pro/Max 登录",
"provider.connect.selectMethod": "选择 {{provider}} 的登录方式。",
"provider.connect.method.apiKey": "API 密钥",
"provider.connect.status.inProgress": "正在授权...",
"provider.connect.status.waiting": "等待授权...",
"provider.connect.status.failed": "授权失败: {{error}}",
"provider.connect.apiKey.description":
"输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。",
"provider.connect.apiKey.label": "{{provider}} API 密钥",
"provider.connect.apiKey.placeholder": "API 密钥",
"provider.connect.apiKey.required": "API 密钥为必填项",
"provider.connect.opencodeZen.line1": "OpenCode Zen 为你提供一组精选的可靠优化模型,用于代码智能体。",
"provider.connect.opencodeZen.line2": "只需一个 API 密钥,你就能使用 Claude、GPT、Gemini、GLM 等模型。",
"provider.connect.opencodeZen.visit.prefix": "访问 ",
"provider.connect.opencodeZen.visit.suffix": " 获取你的 API 密钥。",
"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}} 授权码",
"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}} 模型。",
"provider.connect.oauth.auto.confirmationCode": "确认码",
"provider.connect.toast.connected.title": "{{provider}} 已连接",
"provider.connect.toast.connected.description": "现在可以使用 {{provider}} 模型了。",
"model.tag.free": "免费",
"model.tag.latest": "最新",
"common.search.placeholder": "搜索",
"common.goBack": "返回",
"common.loading": "加载中",
"common.cancel": "取消",
"common.submit": "提交",
"common.save": "保存",
"common.saving": "保存中...",
"common.default": "默认",
"common.attachment": "附件",
"prompt.placeholder.shell": "输入 shell 命令...",
"prompt.placeholder.normal": '随便问点什么... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "按 esc 退出",
"prompt.example.1": "修复代码库中的一个 TODO",
"prompt.example.2": "这个项目的技术栈是什么?",
"prompt.example.3": "修复失败的测试",
"prompt.example.4": "解释认证是如何工作的",
"prompt.example.5": "查找并修复安全漏洞",
"prompt.example.6": "为用户服务添加单元测试",
"prompt.example.7": "重构这个函数,让它更易读",
"prompt.example.8": "这个错误是什么意思?",
"prompt.example.9": "帮我调试这个问题",
"prompt.example.10": "生成 API 文档",
"prompt.example.11": "优化数据库查询",
"prompt.example.12": "添加输入校验",
"prompt.example.13": "创建一个新的组件用于...",
"prompt.example.14": "我该如何部署这个项目?",
"prompt.example.15": "审查我的代码并给出最佳实践建议",
"prompt.example.16": "为这个函数添加错误处理",
"prompt.example.17": "解释这个正则表达式",
"prompt.example.18": "把它转换成 TypeScript",
"prompt.example.19": "在整个代码库中添加日志",
"prompt.example.20": "哪些依赖已经过期?",
"prompt.example.21": "帮我写一个迁移脚本",
"prompt.example.22": "为这个接口实现缓存",
"prompt.example.23": "给这个列表添加分页",
"prompt.example.24": "创建一个 CLI 命令用于...",
"prompt.example.25": "这里的环境变量是怎么工作的?",
"prompt.popover.emptyResults": "没有匹配的结果",
"prompt.popover.emptyCommands": "没有匹配的命令",
"prompt.dropzone.label": "将图片或 PDF 拖到这里",
"prompt.slash.badge.custom": "自定义",
"prompt.context.active": "当前",
"prompt.context.includeActiveFile": "包含当前文件",
"prompt.context.removeActiveFile": "从上下文移除活动文件",
"prompt.context.removeFile": "从上下文移除文件",
"prompt.action.attachFile": "附加文件",
"prompt.attachment.remove": "移除附件",
"prompt.action.send": "发送",
"prompt.action.stop": "停止",
"prompt.toast.pasteUnsupported.title": "不支持的粘贴",
"prompt.toast.pasteUnsupported.description": "这里只能粘贴图片或 PDF 文件。",
"prompt.toast.modelAgentRequired.title": "请选择智能体和模型",
"prompt.toast.modelAgentRequired.description": "发送提示前请先选择智能体和模型。",
"prompt.toast.worktreeCreateFailed.title": "创建工作树失败",
"prompt.toast.sessionCreateFailed.title": "创建会话失败",
"prompt.toast.shellSendFailed.title": "发送 shell 命令失败",
"prompt.toast.commandSendFailed.title": "发送命令失败",
"prompt.toast.promptSendFailed.title": "发送提示失败",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "已启用 {{enabled}} / {{total}}",
"dialog.mcp.empty": "未配置 MCPs",
"mcp.status.connected": "已连接",
"mcp.status.failed": "失败",
"mcp.status.needs_auth": "需要授权",
"mcp.status.disabled": "已禁用",
"dialog.fork.empty": "没有可用于分叉的消息",
"dialog.directory.search.placeholder": "搜索文件夹",
"dialog.directory.empty": "未找到文件夹",
"dialog.server.title": "服务器",
"dialog.server.description": "切换此应用连接的 OpenCode 服务器。",
"dialog.server.search.placeholder": "搜索服务器",
"dialog.server.empty": "暂无服务器",
"dialog.server.add.title": "添加服务器",
"dialog.server.add.url": "服务器 URL",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "无法连接到服务器",
"dialog.server.add.checking": "检查中...",
"dialog.server.add.button": "添加",
"dialog.server.default.title": "默认服务器",
"dialog.server.default.description": "应用启动时连接此服务器,而不是启动本地服务器。需要重启。",
"dialog.server.default.none": "未选择服务器",
"dialog.server.default.set": "将当前服务器设为默认",
"dialog.server.default.clear": "清除",
"dialog.server.action.remove": "移除服务器",
"dialog.project.edit.title": "编辑项目",
"dialog.project.edit.name": "名称",
"dialog.project.edit.icon": "图标",
"dialog.project.edit.icon.alt": "项目图标",
"dialog.project.edit.icon.hint": "点击或拖拽图片",
"dialog.project.edit.icon.recommended": "建议128x128px",
"dialog.project.edit.color": "颜色",
"dialog.project.edit.color.select": "选择{{color}}颜色",
"context.breakdown.title": "上下文拆分",
"context.breakdown.note": "输入 token 的大致拆分。“其他”包含工具定义和开销。",
"context.breakdown.system": "系统",
"context.breakdown.user": "用户",
"context.breakdown.assistant": "助手",
"context.breakdown.tool": "工具调用",
"context.breakdown.other": "其他",
"context.systemPrompt.title": "系统提示词",
"context.rawMessages.title": "原始消息",
"context.stats.session": "会话",
"context.stats.messages": "消息数",
"context.stats.provider": "提供商",
"context.stats.model": "模型",
"context.stats.limit": "上下文限制",
"context.stats.totalTokens": "总 token",
"context.stats.usage": "使用率",
"context.stats.inputTokens": "输入 token",
"context.stats.outputTokens": "输出 token",
"context.stats.reasoningTokens": "推理 token",
"context.stats.cacheTokens": "缓存 token读/写)",
"context.stats.userMessages": "用户消息",
"context.stats.assistantMessages": "助手消息",
"context.stats.totalCost": "总成本",
"context.stats.sessionCreated": "创建时间",
"context.stats.lastActivity": "最后活动",
"context.usage.tokens": "Token",
"context.usage.usage": "使用率",
"context.usage.cost": "成本",
"context.usage.clickToView": "点击查看上下文",
"context.usage.view": "查看上下文用量",
"language.en": "英语",
"language.zh": "简体中文",
"language.zht": "繁体中文",
"language.ko": "韩语",
"language.de": "德语",
"language.es": "西班牙语",
"language.fr": "法语",
"language.ja": "日语",
"language.da": "丹麦语",
"language.ru": "俄语",
"language.pl": "波兰语",
"language.ar": "阿拉伯语",
"language.no": "挪威语",
"language.br": "葡萄牙语(巴西)",
"toast.language.title": "语言",
"toast.language.description": "已切换到{{language}}",
"toast.theme.title": "主题已切换",
"toast.scheme.title": "配色方案",
"toast.permissions.autoaccept.on.title": "自动接受编辑",
"toast.permissions.autoaccept.on.description": "编辑和写入权限将自动获批",
"toast.permissions.autoaccept.off.title": "已停止自动接受编辑",
"toast.permissions.autoaccept.off.description": "编辑和写入权限将需要手动批准",
"toast.model.none.title": "未选择模型",
"toast.model.none.description": "请先连接提供商以总结此会话",
"toast.file.loadFailed.title": "加载文件失败",
"toast.session.share.copyFailed.title": "无法复制链接到剪贴板",
"toast.session.share.success.title": "会话已分享",
"toast.session.share.success.description": "分享链接已复制到剪贴板",
"toast.session.share.failed.title": "分享会话失败",
"toast.session.share.failed.description": "分享会话时发生错误",
"toast.session.unshare.success.title": "已取消分享会话",
"toast.session.unshare.success.description": "会话已成功取消分享",
"toast.session.unshare.failed.title": "取消分享失败",
"toast.session.unshare.failed.description": "取消分享会话时发生错误",
"toast.session.listFailed.title": "无法加载 {{project}} 的会话",
"toast.update.title": "有可用更新",
"toast.update.description": "OpenCode 有新版本 ({{version}}) 可安装。",
"toast.update.action.installRestart": "安装并重启",
"toast.update.action.notYet": "稍后",
"error.page.title": "出了点问题",
"error.page.description": "加载应用程序时发生错误。",
"error.page.details.label": "错误详情",
"error.page.action.restart": "重启",
"error.page.action.checking": "检查中...",
"error.page.action.checkUpdates": "检查更新",
"error.page.action.updateTo": "更新到 {{version}}",
"error.page.report.prefix": "请将此错误报告给 OpenCode 团队",
"error.page.report.discord": "在 Discord 上",
"error.page.version": "版本: {{version}}",
"error.dev.rootNotFound": "未找到根元素。你是不是忘了把它添加到 index.html? 或者 id 属性拼写错了?",
"error.globalSync.connectFailed": "无法连接到服务器。是否有服务器正在 `{{url}}` 运行?",
"error.chain.unknown": "未知错误",
"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.checkConfig": "请检查你的配置 (opencode.json) 中的 provider/model 名称",
"error.chain.mcpFailed": 'MCP 服务器 "{{name}}" 启动失败。注意: OpenCode 暂不支持 MCP 认证。',
"error.chain.providerAuthFailed": "提供商认证失败 ({{provider}}): {{message}}",
"error.chain.providerInitFailed": '无法初始化提供商 "{{provider}}"。请检查凭据和配置。',
"error.chain.configJsonInvalid": "配置文件 {{path}} 不是有效的 JSON(C)",
"error.chain.configJsonInvalidWithMessage": "配置文件 {{path}} 不是有效的 JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'{{path}} 中的目录 "{{dir}}" 无效。请将目录重命名为 "{{suggestion}}" 或移除它。这是一个常见拼写错误。',
"error.chain.configFrontmatterError": "无法解析 {{path}} 中的 frontmatter:\n{{message}}",
"error.chain.configInvalid": "配置文件 {{path}} 无效",
"error.chain.configInvalidWithMessage": "配置文件 {{path}} 无效: {{message}}",
"notification.permission.title": "需要权限",
"notification.permission.description": "{{sessionTitle}}{{projectName}})需要权限",
"notification.question.title": "问题",
"notification.question.description": "{{sessionTitle}}{{projectName}})有一个问题",
"notification.action.goToSession": "前往会话",
"notification.session.responseReady.title": "回复已就绪",
"notification.session.error.title": "会话错误",
"notification.session.error.fallbackDescription": "发生错误",
"home.recentProjects": "最近项目",
"home.empty.title": "没有最近项目",
"home.empty.description": "通过打开本地项目开始使用",
"session.tab.session": "会话",
"session.tab.review": "审查",
"session.tab.context": "上下文",
"session.panel.reviewAndFiles": "审查和文件",
"session.review.filesChanged": "{{count}} 个文件变更",
"session.review.loadingChanges": "正在加载更改...",
"session.review.empty": "此会话暂无更改",
"session.messages.renderEarlier": "显示更早的消息",
"session.messages.loadingEarlier": "正在加载更早的消息...",
"session.messages.loadEarlier": "加载更早的消息",
"session.messages.loading": "正在加载消息...",
"session.context.addToContext": "将 {{selection}} 添加到上下文",
"session.new.worktree.main": "主分支",
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
"session.new.worktree.create": "创建新的 worktree",
"session.new.lastModified": "最后修改",
"session.header.search.placeholder": "搜索 {{project}}",
"session.header.searchFiles": "搜索文件",
"session.share.popover.title": "发布到网页",
"session.share.popover.description.shared": "此会话已在网页上公开。任何拥有链接的人都可以访问。",
"session.share.popover.description.unshared": "在网页上公开分享此会话。任何拥有链接的人都可以访问。",
"session.share.action.share": "分享",
"session.share.action.publish": "发布",
"session.share.action.publishing": "正在发布...",
"session.share.action.unpublish": "取消发布",
"session.share.action.unpublishing": "正在取消发布...",
"session.share.action.view": "查看",
"session.share.copy.copied": "已复制",
"session.share.copy.copyLink": "复制链接",
"lsp.tooltip.none": "没有 LSP 服务器",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "正在加载提示...",
"terminal.loading": "正在加载终端...",
"terminal.title": "终端",
"terminal.title.numbered": "终端 {{number}}",
"terminal.close": "关闭终端",
"common.closeTab": "关闭标签页",
"common.dismiss": "忽略",
"common.requestFailed": "请求失败",
"common.moreOptions": "更多选项",
"common.learnMore": "了解更多",
"common.rename": "重命名",
"common.reset": "重置",
"common.archive": "归档",
"common.delete": "删除",
"common.close": "关闭",
"common.edit": "编辑",
"common.loadMore": "加载更多",
"sidebar.nav.projectsAndSessions": "项目和会话",
"sidebar.settings": "设置",
"sidebar.help": "帮助",
"sidebar.workspaces.enable": "启用工作区",
"sidebar.workspaces.disable": "禁用工作区",
"sidebar.gettingStarted.title": "入门",
"sidebar.gettingStarted.line1": "OpenCode 提供免费模型,你可以立即开始使用。",
"sidebar.gettingStarted.line2": "连接任意提供商即可使用更多模型,如 Claude、GPT、Gemini 等。",
"sidebar.project.recentSessions": "最近会话",
"sidebar.project.viewAllSessions": "查看全部会话",
"settings.section.desktop": "桌面",
"settings.tab.general": "通用",
"settings.tab.shortcuts": "快捷键",
"settings.general.section.appearance": "外观",
"settings.general.section.notifications": "系统通知",
"settings.general.section.sounds": "音效",
"settings.general.row.language.title": "语言",
"settings.general.row.language.description": "更改 OpenCode 的显示语言",
"settings.general.row.appearance.title": "外观",
"settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观",
"settings.general.row.theme.title": "主题",
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
"settings.general.row.font.title": "字体",
"settings.general.row.font.description": "自定义代码块使用的等宽字体",
"settings.general.notifications.agent.title": "智能体",
"settings.general.notifications.agent.description": "当智能体完成或需要注意时显示系统通知",
"settings.general.notifications.permissions.title": "权限",
"settings.general.notifications.permissions.description": "当需要权限时显示系统通知",
"settings.general.notifications.errors.title": "错误",
"settings.general.notifications.errors.description": "发生错误时显示系统通知",
"settings.general.sounds.agent.title": "智能体",
"settings.general.sounds.agent.description": "当智能体完成或需要注意时播放声音",
"settings.general.sounds.permissions.title": "权限",
"settings.general.sounds.permissions.description": "当需要权限时播放声音",
"settings.general.sounds.errors.title": "错误",
"settings.general.sounds.errors.description": "发生错误时播放声音",
"settings.shortcuts.title": "键盘快捷键",
"settings.shortcuts.reset.button": "重置为默认值",
"settings.shortcuts.reset.toast.title": "快捷键已重置",
"settings.shortcuts.reset.toast.description": "键盘快捷键已重置为默认设置。",
"settings.shortcuts.conflict.title": "快捷键已被占用",
"settings.shortcuts.conflict.description": "{{keybind}} 已分配给 {{titles}}。",
"settings.shortcuts.unassigned": "未设置",
"settings.shortcuts.pressKeys": "按下按键",
"settings.shortcuts.search.placeholder": "搜索快捷键",
"settings.shortcuts.search.empty": "未找到快捷键",
"settings.shortcuts.group.general": "通用",
"settings.shortcuts.group.session": "会话",
"settings.shortcuts.group.navigation": "导航",
"settings.shortcuts.group.modelAndAgent": "模型与智能体",
"settings.shortcuts.group.terminal": "终端",
"settings.shortcuts.group.prompt": "提示",
"settings.providers.title": "提供商",
"settings.providers.description": "提供商设置将在此处可配置。",
"settings.models.title": "模型",
"settings.models.description": "模型设置将在此处可配置。",
"settings.agents.title": "智能体",
"settings.agents.description": "智能体设置将在此处可配置。",
"settings.commands.title": "命令",
"settings.commands.description": "命令设置将在此处可配置。",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP 设置将在此处可配置。",
"settings.permissions.title": "权限",
"settings.permissions.description": "控制服务器默认可以使用哪些工具。",
"settings.permissions.section.tools": "工具",
"settings.permissions.toast.updateFailed.title": "更新权限失败",
"settings.permissions.action.allow": "允许",
"settings.permissions.action.ask": "询问",
"settings.permissions.action.deny": "拒绝",
"settings.permissions.tool.read.title": "读取",
"settings.permissions.tool.read.description": "读取文件(匹配文件路径)",
"settings.permissions.tool.edit.title": "编辑",
"settings.permissions.tool.edit.description": "修改文件,包括编辑、写入、补丁和多重编辑",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "使用 glob 模式匹配文件",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "使用正则表达式搜索文件内容",
"settings.permissions.tool.list.title": "列表",
"settings.permissions.tool.list.description": "列出目录中的文件",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "运行 shell 命令",
"settings.permissions.tool.task.title": "Task",
"settings.permissions.tool.task.description": "启动子智能体",
"settings.permissions.tool.skill.title": "Skill",
"settings.permissions.tool.skill.description": "按名称加载技能",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "运行语言服务器查询",
"settings.permissions.tool.todoread.title": "读取待办",
"settings.permissions.tool.todoread.description": "读取待办列表",
"settings.permissions.tool.todowrite.title": "更新待办",
"settings.permissions.tool.todowrite.description": "更新待办列表",
"settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "从 URL 获取内容",
"settings.permissions.tool.websearch.title": "Web Search",
"settings.permissions.tool.websearch.description": "搜索网页",
"settings.permissions.tool.codesearch.title": "Code Search",
"settings.permissions.tool.codesearch.description": "在网上搜索代码",
"settings.permissions.tool.external_directory.title": "外部目录",
"settings.permissions.tool.external_directory.description": "访问项目目录之外的文件",
"settings.permissions.tool.doom_loop.title": "Doom Loop",
"settings.permissions.tool.doom_loop.description": "检测具有相同输入的重复工具调用",
"session.delete.failed.title": "删除会话失败",
"session.delete.title": "删除会话",
"session.delete.confirm": '删除会话 "{{name}}"?',
"session.delete.button": "删除会话",
"workspace.new": "新建工作区",
"workspace.type.local": "本地",
"workspace.type.sandbox": "沙盒",
"workspace.create.failed.title": "创建工作区失败",
"workspace.delete.failed.title": "删除工作区失败",
"workspace.resetting.title": "正在重置工作区",
"workspace.resetting.description": "这可能需要一点时间。",
"workspace.reset.failed.title": "重置工作区失败",
"workspace.reset.success.title": "工作区已重置",
"workspace.reset.success.description": "工作区已与默认分支保持一致。",
"workspace.status.checking": "正在检查未合并的更改...",
"workspace.status.error": "无法验证 git 状态。",
"workspace.status.clean": "未检测到未合并的更改。",
"workspace.status.dirty": "检测到未合并的更改。",
"workspace.delete.title": "删除工作区",
"workspace.delete.confirm": '删除工作区 "{{name}}"?',
"workspace.delete.button": "删除工作区",
"workspace.reset.title": "重置工作区",
"workspace.reset.confirm": '重置工作区 "{{name}}"?',
"workspace.reset.button": "重置工作区",
"workspace.reset.archived.none": "不会归档任何活跃会话。",
"workspace.reset.archived.one": "将归档 1 个会话。",
"workspace.reset.archived.many": "将归档 {{count}} 个会话。",
"workspace.reset.note": "这将把工作区重置为与默认分支一致。",
} satisfies Partial<Record<Keys, string>>

View File

@@ -0,0 +1,570 @@
import { dict as en } from "./en"
type Keys = keyof typeof en
export const dict = {
"command.category.suggested": "建議",
"command.category.view": "檢視",
"command.category.project": "專案",
"command.category.provider": "提供者",
"command.category.server": "伺服器",
"command.category.session": "工作階段",
"command.category.theme": "主題",
"command.category.language": "語言",
"command.category.file": "檔案",
"command.category.terminal": "終端機",
"command.category.model": "模型",
"command.category.mcp": "MCP",
"command.category.agent": "代理程式",
"command.category.permissions": "權限",
"command.category.workspace": "工作區",
"theme.scheme.system": "系統",
"theme.scheme.light": "淺色",
"theme.scheme.dark": "深色",
"command.sidebar.toggle": "切換側邊欄",
"command.project.open": "開啟專案",
"command.provider.connect": "連接提供者",
"command.server.switch": "切換伺服器",
"command.session.previous": "上一個工作階段",
"command.session.next": "下一個工作階段",
"command.session.archive": "封存工作階段",
"command.palette": "命令面板",
"command.theme.cycle": "循環主題",
"command.theme.set": "使用主題: {{theme}}",
"command.theme.scheme.cycle": "循環配色方案",
"command.theme.scheme.set": "使用配色方案: {{scheme}}",
"command.language.cycle": "循環語言",
"command.language.set": "使用語言: {{language}}",
"command.session.new": "新增工作階段",
"command.file.open": "開啟檔案",
"command.file.open.description": "搜尋檔案和命令",
"command.terminal.toggle": "切換終端機",
"command.review.toggle": "切換審查",
"command.terminal.new": "新增終端機",
"command.terminal.new.description": "建立新的終端機標籤頁",
"command.steps.toggle": "切換步驟",
"command.steps.toggle.description": "顯示或隱藏目前訊息的步驟",
"command.message.previous": "上一則訊息",
"command.message.previous.description": "跳到上一則使用者訊息",
"command.message.next": "下一則訊息",
"command.message.next.description": "跳到下一則使用者訊息",
"command.model.choose": "選擇模型",
"command.model.choose.description": "選擇不同的模型",
"command.mcp.toggle": "切換 MCP",
"command.mcp.toggle.description": "切換 MCP",
"command.agent.cycle": "循環代理程式",
"command.agent.cycle.description": "切換到下一個代理程式",
"command.agent.cycle.reverse": "反向循環代理程式",
"command.agent.cycle.reverse.description": "切換到上一個代理程式",
"command.model.variant.cycle": "循環思考強度",
"command.model.variant.cycle.description": "切換到下一個強度等級",
"command.permissions.autoaccept.enable": "自動接受編輯",
"command.permissions.autoaccept.disable": "停止自動接受編輯",
"command.session.undo": "復原",
"command.session.undo.description": "復原上一則訊息",
"command.session.redo": "重做",
"command.session.redo.description": "重做上一則復原的訊息",
"command.session.compact": "精簡工作階段",
"command.session.compact.description": "總結工作階段以減少上下文大小",
"command.session.fork": "從訊息分支",
"command.session.fork.description": "從先前的訊息建立新工作階段",
"command.session.share": "分享工作階段",
"command.session.share.description": "分享此工作階段並將連結複製到剪貼簿",
"command.session.unshare": "取消分享工作階段",
"command.session.unshare.description": "停止分享此工作階段",
"palette.search.placeholder": "搜尋檔案和命令",
"palette.empty": "找不到結果",
"palette.group.commands": "命令",
"palette.group.files": "檔案",
"dialog.provider.search.placeholder": "搜尋提供者",
"dialog.provider.empty": "找不到提供者",
"dialog.provider.group.popular": "熱門",
"dialog.provider.group.other": "其他",
"dialog.provider.tag.recommended": "推薦",
"dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 金鑰連線",
"dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 金鑰連線",
"dialog.provider.copilot.note": "使用 Copilot 或 API 金鑰連線",
"dialog.model.select.title": "選擇模型",
"dialog.model.search.placeholder": "搜尋模型",
"dialog.model.empty": "找不到模型",
"dialog.model.manage": "管理模型",
"dialog.model.manage.description": "自訂模型選擇器中顯示的模型。",
"dialog.model.unpaid.freeModels.title": "OpenCode 提供的免費模型",
"dialog.model.unpaid.addMore.title": "從熱門提供者新增更多模型",
"dialog.provider.viewAll": "查看全部提供者",
"provider.connect.title": "連線 {{provider}}",
"provider.connect.title.anthropicProMax": "使用 Claude Pro/Max 登入",
"provider.connect.selectMethod": "選擇 {{provider}} 的登入方式。",
"provider.connect.method.apiKey": "API 金鑰",
"provider.connect.status.inProgress": "正在授權...",
"provider.connect.status.waiting": "等待授權...",
"provider.connect.status.failed": "授權失敗: {{error}}",
"provider.connect.apiKey.description":
"輸入你的 {{provider}} API 金鑰以連線帳戶,並在 OpenCode 中使用 {{provider}} 模型。",
"provider.connect.apiKey.label": "{{provider}} API 金鑰",
"provider.connect.apiKey.placeholder": "API 金鑰",
"provider.connect.apiKey.required": "API 金鑰為必填",
"provider.connect.opencodeZen.line1": "OpenCode Zen 為你提供一組精選的可靠最佳化模型,用於程式碼代理程式。",
"provider.connect.opencodeZen.line2": "只需一個 API 金鑰,你就能使用 Claude、GPT、Gemini、GLM 等模型。",
"provider.connect.opencodeZen.visit.prefix": "造訪 ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " 取得你的 API 金鑰。",
"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}} 授權碼",
"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}} 模型。",
"provider.connect.oauth.auto.confirmationCode": "確認碼",
"provider.connect.toast.connected.title": "{{provider}} 已連線",
"provider.connect.toast.connected.description": "現在可以使用 {{provider}} 模型了。",
"model.tag.free": "免費",
"model.tag.latest": "最新",
"common.search.placeholder": "搜尋",
"common.goBack": "返回",
"common.loading": "載入中",
"common.cancel": "取消",
"common.submit": "提交",
"common.save": "儲存",
"common.saving": "儲存中...",
"common.default": "預設",
"common.attachment": "附件",
"prompt.placeholder.shell": "輸入 shell 命令...",
"prompt.placeholder.normal": '隨便問點什麼... "{{example}}"',
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "按 esc 退出",
"prompt.example.1": "修復程式碼庫中的一個 TODO",
"prompt.example.2": "這個專案的技術堆疊是什麼?",
"prompt.example.3": "修復失敗的測試",
"prompt.example.4": "解釋驗證是如何運作的",
"prompt.example.5": "尋找並修復安全漏洞",
"prompt.example.6": "為使用者服務新增單元測試",
"prompt.example.7": "重構這個函式,讓它更易讀",
"prompt.example.8": "這個錯誤是什麼意思?",
"prompt.example.9": "幫我偵錯這個問題",
"prompt.example.10": "產生 API 文件",
"prompt.example.11": "最佳化資料庫查詢",
"prompt.example.12": "新增輸入驗證",
"prompt.example.13": "建立一個新的元件用於...",
"prompt.example.14": "我該如何部署這個專案?",
"prompt.example.15": "審查我的程式碼並給出最佳實務建議",
"prompt.example.16": "為這個函式新增錯誤處理",
"prompt.example.17": "解釋這個正規表示式",
"prompt.example.18": "把它轉換成 TypeScript",
"prompt.example.19": "在整個程式碼庫中新增日誌",
"prompt.example.20": "哪些相依性已經過期?",
"prompt.example.21": "幫我寫一個遷移腳本",
"prompt.example.22": "為這個端點實作快取",
"prompt.example.23": "給這個清單新增分頁",
"prompt.example.24": "建立一個 CLI 命令用於...",
"prompt.example.25": "這裡的環境變數是怎麼運作的?",
"prompt.popover.emptyResults": "沒有符合的結果",
"prompt.popover.emptyCommands": "沒有符合的命令",
"prompt.dropzone.label": "將圖片或 PDF 拖到這裡",
"prompt.slash.badge.custom": "自訂",
"prompt.context.active": "作用中",
"prompt.context.includeActiveFile": "包含作用中檔案",
"prompt.context.removeActiveFile": "從上下文移除目前檔案",
"prompt.context.removeFile": "從上下文移除檔案",
"prompt.action.attachFile": "附加檔案",
"prompt.attachment.remove": "移除附件",
"prompt.action.send": "傳送",
"prompt.action.stop": "停止",
"prompt.toast.pasteUnsupported.title": "不支援的貼上",
"prompt.toast.pasteUnsupported.description": "這裡只能貼上圖片或 PDF 檔案。",
"prompt.toast.modelAgentRequired.title": "請選擇代理程式和模型",
"prompt.toast.modelAgentRequired.description": "傳送提示前請先選擇代理程式和模型。",
"prompt.toast.worktreeCreateFailed.title": "建立工作樹失敗",
"prompt.toast.sessionCreateFailed.title": "建立工作階段失敗",
"prompt.toast.shellSendFailed.title": "傳送 shell 命令失敗",
"prompt.toast.commandSendFailed.title": "傳送命令失敗",
"prompt.toast.promptSendFailed.title": "傳送提示失敗",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "已啟用 {{enabled}} / {{total}}",
"dialog.mcp.empty": "未設定 MCP",
"mcp.status.connected": "已連線",
"mcp.status.failed": "失敗",
"mcp.status.needs_auth": "需要授權",
"mcp.status.disabled": "已停用",
"dialog.fork.empty": "沒有可用於分支的訊息",
"dialog.directory.search.placeholder": "搜尋資料夾",
"dialog.directory.empty": "找不到資料夾",
"dialog.server.title": "伺服器",
"dialog.server.description": "切換此應用程式連線的 OpenCode 伺服器。",
"dialog.server.search.placeholder": "搜尋伺服器",
"dialog.server.empty": "暫無伺服器",
"dialog.server.add.title": "新增伺服器",
"dialog.server.add.url": "伺服器 URL",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "無法連線到伺服器",
"dialog.server.add.checking": "檢查中...",
"dialog.server.add.button": "新增",
"dialog.server.default.title": "預設伺服器",
"dialog.server.default.description": "應用程式啟動時連線此伺服器,而不是啟動本地伺服器。需要重新啟動。",
"dialog.server.default.none": "未選擇伺服器",
"dialog.server.default.set": "將目前伺服器設為預設",
"dialog.server.default.clear": "清除",
"dialog.server.action.remove": "移除伺服器",
"dialog.project.edit.title": "編輯專案",
"dialog.project.edit.name": "名稱",
"dialog.project.edit.icon": "圖示",
"dialog.project.edit.icon.alt": "專案圖示",
"dialog.project.edit.icon.hint": "點擊或拖曳圖片",
"dialog.project.edit.icon.recommended": "建議128x128px",
"dialog.project.edit.color": "顏色",
"dialog.project.edit.color.select": "選擇{{color}}顏色",
"context.breakdown.title": "上下文拆分",
"context.breakdown.note": "輸入 token 的大致拆分。「其他」包含工具定義和額外開銷。",
"context.breakdown.system": "系統",
"context.breakdown.user": "使用者",
"context.breakdown.assistant": "助手",
"context.breakdown.tool": "工具呼叫",
"context.breakdown.other": "其他",
"context.systemPrompt.title": "系統提示詞",
"context.rawMessages.title": "原始訊息",
"context.stats.session": "工作階段",
"context.stats.messages": "訊息數",
"context.stats.provider": "提供者",
"context.stats.model": "模型",
"context.stats.limit": "上下文限制",
"context.stats.totalTokens": "總 token",
"context.stats.usage": "使用量",
"context.stats.inputTokens": "輸入 token",
"context.stats.outputTokens": "輸出 token",
"context.stats.reasoningTokens": "推理 token",
"context.stats.cacheTokens": "快取 token讀/寫)",
"context.stats.userMessages": "使用者訊息",
"context.stats.assistantMessages": "助手訊息",
"context.stats.totalCost": "總成本",
"context.stats.sessionCreated": "建立時間",
"context.stats.lastActivity": "最後活動",
"context.usage.tokens": "Token",
"context.usage.usage": "使用量",
"context.usage.cost": "成本",
"context.usage.clickToView": "點擊查看上下文",
"context.usage.view": "檢視上下文用量",
"language.en": "英語",
"language.zh": "簡體中文",
"language.zht": "繁體中文",
"language.ko": "韓語",
"language.ru": "俄語",
"language.ar": "阿拉伯語",
"language.no": "挪威語",
"language.br": "葡萄牙語(巴西)",
"toast.language.title": "語言",
"toast.language.description": "已切換到 {{language}}",
"toast.theme.title": "主題已切換",
"toast.scheme.title": "配色方案",
"toast.permissions.autoaccept.on.title": "自動接受編輯",
"toast.permissions.autoaccept.on.description": "編輯和寫入權限將自動獲准",
"toast.permissions.autoaccept.off.title": "已停止自動接受編輯",
"toast.permissions.autoaccept.off.description": "編輯和寫入權限將需要手動批准",
"toast.model.none.title": "未選擇模型",
"toast.model.none.description": "請先連線提供者以總結此工作階段",
"toast.file.loadFailed.title": "載入檔案失敗",
"toast.session.share.copyFailed.title": "無法複製連結到剪貼簿",
"toast.session.share.success.title": "工作階段已分享",
"toast.session.share.success.description": "分享連結已複製到剪貼簿",
"toast.session.share.failed.title": "分享工作階段失敗",
"toast.session.share.failed.description": "分享工作階段時發生錯誤",
"toast.session.unshare.success.title": "已取消分享工作階段",
"toast.session.unshare.success.description": "工作階段已成功取消分享",
"toast.session.unshare.failed.title": "取消分享失敗",
"toast.session.unshare.failed.description": "取消分享工作階段時發生錯誤",
"toast.session.listFailed.title": "無法載入 {{project}} 的工作階段",
"toast.update.title": "有可用更新",
"toast.update.description": "OpenCode 有新版本 ({{version}}) 可安裝。",
"toast.update.action.installRestart": "安裝並重新啟動",
"toast.update.action.notYet": "稍後",
"error.page.title": "出了點問題",
"error.page.description": "載入應用程式時發生錯誤。",
"error.page.details.label": "錯誤詳情",
"error.page.action.restart": "重新啟動",
"error.page.action.checking": "檢查中...",
"error.page.action.checkUpdates": "檢查更新",
"error.page.action.updateTo": "更新到 {{version}}",
"error.page.report.prefix": "請將此錯誤回報給 OpenCode 團隊",
"error.page.report.discord": "在 Discord 上",
"error.page.version": "版本: {{version}}",
"error.dev.rootNotFound": "找不到根元素。你是不是忘了把它新增到 index.html? 或者 id 屬性拼錯了?",
"error.globalSync.connectFailed": "無法連線到伺服器。是否有伺服器正在 `{{url}}` 執行?",
"error.chain.unknown": "未知錯誤",
"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.checkConfig": "請檢查你的設定 (opencode.json) 中的 provider/model 名稱",
"error.chain.mcpFailed": 'MCP 伺服器 "{{name}}" 啟動失敗。注意: OpenCode 暫不支援 MCP 認證。',
"error.chain.providerAuthFailed": "提供者認證失敗 ({{provider}}): {{message}}",
"error.chain.providerInitFailed": '無法初始化提供者 "{{provider}}"。請檢查憑證和設定。',
"error.chain.configJsonInvalid": "設定檔 {{path}} 不是有效的 JSON(C)",
"error.chain.configJsonInvalidWithMessage": "設定檔 {{path}} 不是有效的 JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'{{path}} 中的目錄 "{{dir}}" 無效。請將目錄重新命名為 "{{suggestion}}" 或移除它。這是一個常見拼寫錯誤。',
"error.chain.configFrontmatterError": "無法解析 {{path}} 中的 frontmatter:\n{{message}}",
"error.chain.configInvalid": "設定檔 {{path}} 無效",
"error.chain.configInvalidWithMessage": "設定檔 {{path}} 無效: {{message}}",
"notification.permission.title": "需要權限",
"notification.permission.description": "{{sessionTitle}}{{projectName}})需要權限",
"notification.question.title": "問題",
"notification.question.description": "{{sessionTitle}}{{projectName}})有一個問題",
"notification.action.goToSession": "前往工作階段",
"notification.session.responseReady.title": "回覆已就緒",
"notification.session.error.title": "工作階段錯誤",
"notification.session.error.fallbackDescription": "發生錯誤",
"home.recentProjects": "最近專案",
"home.empty.title": "沒有最近專案",
"home.empty.description": "透過開啟本地專案開始使用",
"session.tab.session": "工作階段",
"session.tab.review": "審查",
"session.tab.context": "上下文",
"session.panel.reviewAndFiles": "審查與檔案",
"session.review.filesChanged": "{{count}} 個檔案變更",
"session.review.loadingChanges": "正在載入變更...",
"session.review.empty": "此工作階段暫無變更",
"session.messages.renderEarlier": "顯示更早的訊息",
"session.messages.loadingEarlier": "正在載入更早的訊息...",
"session.messages.loadEarlier": "載入更早的訊息",
"session.messages.loading": "正在載入訊息...",
"session.context.addToContext": "將 {{selection}} 新增到上下文",
"session.new.worktree.main": "主分支",
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
"session.new.worktree.create": "建立新的 worktree",
"session.new.lastModified": "最後修改",
"session.header.search.placeholder": "搜尋 {{project}}",
"session.header.searchFiles": "搜尋檔案",
"session.share.popover.title": "發佈到網頁",
"session.share.popover.description.shared": "此工作階段已在網頁上公開。任何擁有連結的人都可以存取。",
"session.share.popover.description.unshared": "在網頁上公開分享此工作階段。任何擁有連結的人都可以存取。",
"session.share.action.share": "分享",
"session.share.action.publish": "發佈",
"session.share.action.publishing": "正在發佈...",
"session.share.action.unpublish": "取消發佈",
"session.share.action.unpublishing": "正在取消發佈...",
"session.share.action.view": "檢視",
"session.share.copy.copied": "已複製",
"session.share.copy.copyLink": "複製連結",
"lsp.tooltip.none": "沒有 LSP 伺服器",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "正在載入提示...",
"terminal.loading": "正在載入終端機...",
"terminal.title": "終端機",
"terminal.title.numbered": "終端機 {{number}}",
"terminal.close": "關閉終端機",
"common.closeTab": "關閉標籤頁",
"common.dismiss": "忽略",
"common.requestFailed": "要求失敗",
"common.moreOptions": "更多選項",
"common.learnMore": "深入了解",
"common.rename": "重新命名",
"common.reset": "重設",
"common.archive": "封存",
"common.delete": "刪除",
"common.close": "關閉",
"common.edit": "編輯",
"common.loadMore": "載入更多",
"sidebar.nav.projectsAndSessions": "專案與工作階段",
"sidebar.settings": "設定",
"sidebar.help": "說明",
"sidebar.workspaces.enable": "啟用工作區",
"sidebar.workspaces.disable": "停用工作區",
"sidebar.gettingStarted.title": "開始使用",
"sidebar.gettingStarted.line1": "OpenCode 提供免費模型,你可以立即開始使用。",
"sidebar.gettingStarted.line2": "連線任意提供者即可使用更多模型,如 Claude、GPT、Gemini 等。",
"sidebar.project.recentSessions": "最近工作階段",
"sidebar.project.viewAllSessions": "查看全部工作階段",
"settings.section.desktop": "桌面",
"settings.tab.general": "一般",
"settings.tab.shortcuts": "快速鍵",
"settings.general.section.appearance": "外觀",
"settings.general.section.notifications": "系統通知",
"settings.general.section.sounds": "音效",
"settings.general.row.language.title": "語言",
"settings.general.row.language.description": "變更 OpenCode 的顯示語言",
"settings.general.row.appearance.title": "外觀",
"settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀",
"settings.general.row.theme.title": "主題",
"settings.general.row.theme.description": "自訂 OpenCode 的主題。",
"settings.general.row.font.title": "字型",
"settings.general.row.font.description": "自訂程式碼區塊使用的等寬字型",
"settings.general.notifications.agent.title": "代理程式",
"settings.general.notifications.agent.description": "當代理程式完成或需要注意時顯示系統通知",
"settings.general.notifications.permissions.title": "權限",
"settings.general.notifications.permissions.description": "當需要權限時顯示系統通知",
"settings.general.notifications.errors.title": "錯誤",
"settings.general.notifications.errors.description": "發生錯誤時顯示系統通知",
"settings.general.sounds.agent.title": "代理程式",
"settings.general.sounds.agent.description": "當代理程式完成或需要注意時播放聲音",
"settings.general.sounds.permissions.title": "權限",
"settings.general.sounds.permissions.description": "當需要權限時播放聲音",
"settings.general.sounds.errors.title": "錯誤",
"settings.general.sounds.errors.description": "發生錯誤時播放聲音",
"settings.shortcuts.title": "鍵盤快速鍵",
"settings.shortcuts.reset.button": "重設為預設值",
"settings.shortcuts.reset.toast.title": "快速鍵已重設",
"settings.shortcuts.reset.toast.description": "鍵盤快速鍵已重設為預設設定。",
"settings.shortcuts.conflict.title": "快速鍵已被占用",
"settings.shortcuts.conflict.description": "{{keybind}} 已分配給 {{titles}}。",
"settings.shortcuts.unassigned": "未設定",
"settings.shortcuts.pressKeys": "按下按鍵",
"settings.shortcuts.search.placeholder": "搜尋快速鍵",
"settings.shortcuts.search.empty": "找不到快速鍵",
"settings.shortcuts.group.general": "一般",
"settings.shortcuts.group.session": "工作階段",
"settings.shortcuts.group.navigation": "導覽",
"settings.shortcuts.group.modelAndAgent": "模型與代理程式",
"settings.shortcuts.group.terminal": "終端機",
"settings.shortcuts.group.prompt": "提示",
"settings.providers.title": "提供者",
"settings.providers.description": "提供者設定將在此處可設定。",
"settings.models.title": "模型",
"settings.models.description": "模型設定將在此處可設定。",
"settings.agents.title": "代理程式",
"settings.agents.description": "代理程式設定將在此處可設定。",
"settings.commands.title": "命令",
"settings.commands.description": "命令設定將在此處可設定。",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP 設定將在此處可設定。",
"settings.permissions.title": "權限",
"settings.permissions.description": "控制伺服器預設可以使用哪些工具。",
"settings.permissions.section.tools": "工具",
"settings.permissions.toast.updateFailed.title": "更新權限失敗",
"settings.permissions.action.allow": "允許",
"settings.permissions.action.ask": "詢問",
"settings.permissions.action.deny": "拒絕",
"settings.permissions.tool.read.title": "讀取",
"settings.permissions.tool.read.description": "讀取檔案(符合檔案路徑)",
"settings.permissions.tool.edit.title": "編輯",
"settings.permissions.tool.edit.description": "修改檔案,包括編輯、寫入、修補和多重編輯",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "使用 glob 模式符合檔案",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "使用正規表示式搜尋檔案內容",
"settings.permissions.tool.list.title": "清單",
"settings.permissions.tool.list.description": "列出目錄中的檔案",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "執行 shell 命令",
"settings.permissions.tool.task.title": "Task",
"settings.permissions.tool.task.description": "啟動子代理程式",
"settings.permissions.tool.skill.title": "Skill",
"settings.permissions.tool.skill.description": "按名稱載入技能",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "執行語言伺服器查詢",
"settings.permissions.tool.todoread.title": "讀取待辦",
"settings.permissions.tool.todoread.description": "讀取待辦清單",
"settings.permissions.tool.todowrite.title": "更新待辦",
"settings.permissions.tool.todowrite.description": "更新待辦清單",
"settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "從 URL 取得內容",
"settings.permissions.tool.websearch.title": "Web Search",
"settings.permissions.tool.websearch.description": "搜尋網頁",
"settings.permissions.tool.codesearch.title": "Code Search",
"settings.permissions.tool.codesearch.description": "在網路上搜尋程式碼",
"settings.permissions.tool.external_directory.title": "外部目錄",
"settings.permissions.tool.external_directory.description": "存取專案目錄之外的檔案",
"settings.permissions.tool.doom_loop.title": "Doom Loop",
"settings.permissions.tool.doom_loop.description": "偵測具有相同輸入的重複工具呼叫",
"session.delete.failed.title": "刪除工作階段失敗",
"session.delete.title": "刪除工作階段",
"session.delete.confirm": '刪除工作階段 "{{name}}"?',
"session.delete.button": "刪除工作階段",
"workspace.new": "新增工作區",
"workspace.type.local": "本地",
"workspace.type.sandbox": "沙盒",
"workspace.create.failed.title": "建立工作區失敗",
"workspace.delete.failed.title": "刪除工作區失敗",
"workspace.resetting.title": "正在重設工作區",
"workspace.resetting.description": "這可能需要一點時間。",
"workspace.reset.failed.title": "重設工作區失敗",
"workspace.reset.success.title": "工作區已重設",
"workspace.reset.success.description": "工作區已與預設分支保持一致。",
"workspace.status.checking": "正在檢查未合併的變更...",
"workspace.status.error": "無法驗證 git 狀態。",
"workspace.status.clean": "未偵測到未合併的變更。",
"workspace.status.dirty": "偵測到未合併的變更。",
"workspace.delete.title": "刪除工作區",
"workspace.delete.confirm": '刪除工作區 "{{name}}"?',
"workspace.delete.button": "刪除工作區",
"workspace.reset.title": "重設工作區",
"workspace.reset.confirm": '重設工作區 "{{name}}"?',
"workspace.reset.button": "重設工作區",
"workspace.reset.archived.none": "不會封存任何作用中工作階段。",
"workspace.reset.archived.one": "將封存 1 個工作階段。",
"workspace.reset.archived.many": "將封存 {{count}} 個工作階段。",
"workspace.reset.note": "這將把工作區重設為與預設分支一致。",
} satisfies Partial<Record<Keys, string>>

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