mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-10 10:54:28 +00:00
Compare commits
275 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcd5ff7ebe | ||
|
|
c2e234ec4d | ||
|
|
38f735bfc6 | ||
|
|
a4183c3b2c | ||
|
|
2c234b8d62 | ||
|
|
9f96d8aa78 | ||
|
|
4007e57c52 | ||
|
|
d472512eba | ||
|
|
f6b28b61c7 | ||
|
|
0bf9d66da5 | ||
|
|
eabd78cab6 | ||
|
|
7bc8851fc4 | ||
|
|
af5e405391 | ||
|
|
8a216a6ad5 | ||
|
|
225b72ca36 | ||
|
|
8105f186dc | ||
|
|
4f1bdf1c59 | ||
|
|
472695caca | ||
|
|
469fd43c71 | ||
|
|
24d942349f | ||
|
|
65c236c071 | ||
|
|
d6c5ddd6dc | ||
|
|
e5fe50f7da | ||
|
|
b6beda1569 | ||
|
|
f34b509fe7 | ||
|
|
2a2d800ac4 | ||
|
|
4afb46f571 | ||
|
|
c4d223eb99 | ||
|
|
3fbda54045 | ||
|
|
41ede06b20 | ||
|
|
82ec84982e | ||
|
|
df7b6792cd | ||
|
|
c72d9a473c | ||
|
|
d3688b150a | ||
|
|
e376e1de16 | ||
|
|
c130dd425a | ||
|
|
b298982268 | ||
|
|
47a2b9e8df | ||
|
|
213b823c69 | ||
|
|
c0dc8ea39e | ||
|
|
077ebdbfda | ||
|
|
1780bab1ce | ||
|
|
d35fabf5db | ||
|
|
82f718b3cf | ||
|
|
0eb523631d | ||
|
|
99e15caaf6 | ||
|
|
1e1872aada | ||
|
|
cb481d9ac8 | ||
|
|
0ce0cacb28 | ||
|
|
640d1f1ecc | ||
|
|
2e53697da0 | ||
|
|
71cd59932e | ||
|
|
14db336e3a | ||
|
|
2b9b98e9c2 | ||
|
|
07015aae07 | ||
|
|
972cb01d5c | ||
|
|
a8018dcc43 | ||
|
|
31094cd5a4 | ||
|
|
bcf7a65e36 | ||
|
|
7c80ac072b | ||
|
|
515391e9c7 | ||
|
|
510f595e25 | ||
|
|
1b244bf850 | ||
|
|
c128579cfc | ||
|
|
5f3ab9395f | ||
|
|
fdac21688c | ||
|
|
dd5a601eda | ||
|
|
3eaf6f3baf | ||
|
|
71ef43f9a0 | ||
|
|
8ebb766470 | ||
|
|
46de1ed3b6 | ||
|
|
3c7d5174b3 | ||
|
|
32f72f49a8 | ||
|
|
923e3da973 | ||
|
|
c96c25a72c | ||
|
|
cda7d3dd78 | ||
|
|
9802ceb94f | ||
|
|
62115832f5 | ||
|
|
496bbd70f4 | ||
|
|
93044cc7d1 | ||
|
|
5a4eec5b08 | ||
|
|
e17b875641 | ||
|
|
a890d51bbc | ||
|
|
bb582416f2 | ||
|
|
b8526eca67 | ||
|
|
9c45746bd2 | ||
|
|
c4971e48c4 | ||
|
|
de6582b38b | ||
|
|
fc53abe589 | ||
|
|
7b23bf7c1b | ||
|
|
c0d3dd51b1 | ||
|
|
a96f3d153b | ||
|
|
31f3a508dc | ||
|
|
3b7c347b2e | ||
|
|
2e09d7d835 | ||
|
|
29cebd73e5 | ||
|
|
e4286ae7a3 | ||
|
|
c3f393bcc1 | ||
|
|
9aa54fd71b | ||
|
|
e85b953087 | ||
|
|
b776ba6b76 | ||
|
|
224b2c37d7 | ||
|
|
16a8f5a9c3 | ||
|
|
16fad51b5e | ||
|
|
287511c9b1 | ||
|
|
0a678eeacc | ||
|
|
c031139b89 | ||
|
|
710dc4fa94 | ||
|
|
ec53a7962e | ||
|
|
6f7d710129 | ||
|
|
513a8a3d26 | ||
|
|
c41c9a366f | ||
|
|
4385f03053 | ||
|
|
8e3b459d77 | ||
|
|
3807523f49 | ||
|
|
09997bb6c8 | ||
|
|
aa17729008 | ||
|
|
b59f3e6811 | ||
|
|
8427f40e8d | ||
|
|
e9c6a4a2d4 | ||
|
|
fb007d6bab | ||
|
|
4ca088ed12 | ||
|
|
ae2693425e | ||
|
|
d9b9485019 | ||
|
|
366da595af | ||
|
|
fb3d8e83c5 | ||
|
|
d14735ef4b | ||
|
|
3435327bc0 | ||
|
|
8a043edfd5 | ||
|
|
de07cf26e8 | ||
|
|
c737776958 | ||
|
|
7b0ad87781 | ||
|
|
3b92d5c1c6 | ||
|
|
cf1fc02d27 | ||
|
|
ba2e35e29c | ||
|
|
9afc067152 | ||
|
|
9fc182baf2 | ||
|
|
c2844697f3 | ||
|
|
fc0210c2fd | ||
|
|
f1df6f2d18 | ||
|
|
c3415b79fe | ||
|
|
af1e2887bd | ||
|
|
65e267ed3a | ||
|
|
6d574549bc | ||
|
|
f7c5b62ba3 | ||
|
|
8c230fee62 | ||
|
|
59ceca3e51 | ||
|
|
877b0412c9 | ||
|
|
a0d71bf8ef | ||
|
|
19fe3e265a | ||
|
|
20b6cc279f | ||
|
|
80c808d186 | ||
|
|
a132b2a138 | ||
|
|
936f3ebe95 | ||
|
|
23daac2170 | ||
|
|
383c2787f9 | ||
|
|
c89f6e7ac6 | ||
|
|
17a5f75b54 | ||
|
|
5ca28b6454 | ||
|
|
09d2fd57ff | ||
|
|
bcdec15fb4 | ||
|
|
0b63cae1ae | ||
|
|
b7b2eae20c | ||
|
|
1b98f26794 | ||
|
|
fa91337723 | ||
|
|
6d656e4827 | ||
|
|
ae8cff22e5 | ||
|
|
52535654e7 | ||
|
|
7e609cc612 | ||
|
|
416aaff488 | ||
|
|
aa599b4a7d | ||
|
|
b33cec485a | ||
|
|
3ba1111ed0 | ||
|
|
6f7a1c69a5 | ||
|
|
13405aedea | ||
|
|
df2ed99231 | ||
|
|
c69e3bbde7 | ||
|
|
2a370f8038 | ||
|
|
d9f0287d74 | ||
|
|
301e74d953 | ||
|
|
51126f081d | ||
|
|
d03cac2351 | ||
|
|
1820569818 | ||
|
|
8df09abb1b | ||
|
|
95b17bcf5e | ||
|
|
c4c489a5bc | ||
|
|
cd34f5e07c | ||
|
|
621550ac77 | ||
|
|
b746c006cf | ||
|
|
850d50eb64 | ||
|
|
ab3d412a81 | ||
|
|
0e1a8a1839 | ||
|
|
178767af70 | ||
|
|
b8a0e420f8 | ||
|
|
bfbcbc8863 | ||
|
|
fd77d31b49 | ||
|
|
9f02ffe02d | ||
|
|
b10f423743 | ||
|
|
0059fdc1f5 | ||
|
|
f7f2d9700a | ||
|
|
97e0e79f1a | ||
|
|
4fc7bcf09e | ||
|
|
63da3a338a | ||
|
|
f736751a8c | ||
|
|
6ac8c85b34 | ||
|
|
19f68382fd | ||
|
|
368cd2af4c | ||
|
|
d00b8df770 | ||
|
|
7ed448a7e8 | ||
|
|
87d91c29e2 | ||
|
|
259b2a3c2d | ||
|
|
ab705bbc31 | ||
|
|
e237f06c96 | ||
|
|
bb710e9ea1 | ||
|
|
34e4d077cd | ||
|
|
4b8335160b | ||
|
|
8b0353cb2a | ||
|
|
4a386906dd | ||
|
|
efff52714d | ||
|
|
09a9556c70 | ||
|
|
118b4f65da | ||
|
|
e6438aa3f6 | ||
|
|
64c80f1b51 | ||
|
|
7b8fad6202 | ||
|
|
996eeb1f68 | ||
|
|
2e5fe6d5c8 | ||
|
|
8e8fb6a54b | ||
|
|
79aa931a05 | ||
|
|
cf6ad4c407 | ||
|
|
d6caaee816 | ||
|
|
65938baf00 | ||
|
|
a639961973 | ||
|
|
0f979bb87c | ||
|
|
96e9c89cc6 | ||
|
|
a18ae2c8b7 | ||
|
|
c9ea966805 | ||
|
|
2049af4d6f | ||
|
|
74bd52e8a7 | ||
|
|
9dc95c4c69 | ||
|
|
b93f33eaa4 | ||
|
|
34d473c0f5 | ||
|
|
217e4850db | ||
|
|
be9a0bfee7 | ||
|
|
dac73572e0 | ||
|
|
cbe20d22d3 | ||
|
|
3723e1b8d2 | ||
|
|
65d9e829e7 | ||
|
|
6793b4a6fd | ||
|
|
a71c40c717 | ||
|
|
489f2d3709 | ||
|
|
20c8624bb7 | ||
|
|
bb8bf32abe | ||
|
|
233d003b49 | ||
|
|
6037e88ddf | ||
|
|
b13c269162 | ||
|
|
ef36af0e55 | ||
|
|
f86c37f579 | ||
|
|
9b7d9c8173 | ||
|
|
0f2e8ea2b4 | ||
|
|
be493e8be0 | ||
|
|
7e8e4d9938 | ||
|
|
7a359ff67c | ||
|
|
835fea6bb1 | ||
|
|
7138bd021c | ||
|
|
a68e5a1c17 | ||
|
|
92beae1410 | ||
|
|
0470717c7f | ||
|
|
7f50b27996 | ||
|
|
021e42c0bb | ||
|
|
0c4ffec857 | ||
|
|
5c3e9cfa2c | ||
|
|
85ef23a098 | ||
|
|
3b46f90124 | ||
|
|
80dc74a0ec | ||
|
|
a0636fcd50 |
166
.github/workflows/daily-issues-recap.yml
vendored
Normal file
166
.github/workflows/daily-issues-recap.yml
vendored
Normal 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
169
.github/workflows/daily-pr-recap.yml
vendored
Normal 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"
|
||||
26
.github/workflows/test.yml
vendored
26
.github/workflows/test.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
host: windows-latest
|
||||
playwright: bunx playwright install
|
||||
workdir: packages/app
|
||||
command: bun test:e2e
|
||||
command: bun test:e2e:local
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
defaults:
|
||||
run:
|
||||
@@ -65,6 +65,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Seed opencode data
|
||||
if: matrix.settings.name != 'windows'
|
||||
working-directory: packages/opencode
|
||||
run: bun script/seed-e2e.ts
|
||||
env:
|
||||
@@ -85,8 +86,9 @@ jobs:
|
||||
OPENCODE_E2E_MODEL: "opencode/gpt-5-nano"
|
||||
|
||||
- name: Run opencode server
|
||||
if: matrix.settings.name != 'windows'
|
||||
working-directory: packages/opencode
|
||||
run: bun dev -- --print-logs --log-level WARN serve --port 4096 --hostname 0.0.0.0 &
|
||||
run: bun dev -- --print-logs --log-level WARN serve --port 4096 --hostname 127.0.0.1 &
|
||||
env:
|
||||
MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }}
|
||||
OPENCODE_DISABLE_MODELS_FETCH: "true"
|
||||
@@ -102,9 +104,10 @@ jobs:
|
||||
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
|
||||
@@ -125,9 +128,20 @@ jobs:
|
||||
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
|
||||
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
|
||||
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
|
||||
PLAYWRIGHT_SERVER_HOST: "localhost"
|
||||
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
|
||||
|
||||
@@ -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
3
.opencode/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
plans/
|
||||
bun.lock
|
||||
package.json
|
||||
@@ -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:
|
||||
|
||||
|
||||
3
STATS.md
3
STATS.md
@@ -206,3 +206,6 @@
|
||||
| 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) |
|
||||
|
||||
165
bun.lock
165
bun.lock
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -33,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",
|
||||
@@ -72,7 +73,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -106,7 +107,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -133,7 +134,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -157,7 +158,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -181,7 +182,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -210,7 +211,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -239,7 +240,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -255,14 +256,14 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.1.28",
|
||||
"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",
|
||||
@@ -283,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",
|
||||
@@ -310,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:",
|
||||
@@ -351,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",
|
||||
@@ -359,7 +363,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -375,12 +379,11 @@
|
||||
"name": "@opencode-ai/script",
|
||||
"devDependencies": {
|
||||
"@types/bun": "catalog:",
|
||||
"@types/semver": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.90.4",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -391,7 +394,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -404,7 +407,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -445,7 +448,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -456,7 +459,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -554,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=="],
|
||||
|
||||
@@ -922,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=="],
|
||||
|
||||
@@ -1634,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=="],
|
||||
@@ -1852,8 +1857,6 @@
|
||||
|
||||
"@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="],
|
||||
|
||||
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
|
||||
|
||||
"@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
|
||||
|
||||
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
||||
@@ -2042,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=="],
|
||||
@@ -2254,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=="],
|
||||
@@ -2346,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=="],
|
||||
@@ -2430,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=="],
|
||||
@@ -2464,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=="],
|
||||
@@ -2502,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=="],
|
||||
@@ -2552,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=="],
|
||||
@@ -3090,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=="],
|
||||
@@ -3102,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=="],
|
||||
@@ -3120,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=="],
|
||||
@@ -3132,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=="],
|
||||
@@ -3328,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=="],
|
||||
@@ -3350,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=="],
|
||||
@@ -3366,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=="],
|
||||
@@ -3552,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=="],
|
||||
@@ -3656,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=="],
|
||||
@@ -3682,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=="],
|
||||
@@ -3746,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=="],
|
||||
@@ -3978,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=="],
|
||||
@@ -4082,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=="],
|
||||
@@ -4310,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=="],
|
||||
@@ -4414,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=="],
|
||||
@@ -4444,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=="],
|
||||
@@ -4492,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=="],
|
||||
@@ -4946,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=="],
|
||||
@@ -5020,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=="],
|
||||
@@ -5046,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=="],
|
||||
@@ -5192,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
6
flake.lock
generated
@@ -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": {
|
||||
|
||||
15
infra/app.ts
15
infra/app.ts
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-xVA4r7Qugw0TSx5wiTI5al93FI4D5LlvQo2ab3cUlmE=",
|
||||
"aarch64-linux": "sha256-EV0U/mXlrnEyCryL9rLlOZvMn6U0+BSgPhTIudVeqTo=",
|
||||
"aarch64-darwin": "sha256-zQvdRyNEHrpJsQMj8PZH0Ub21EREmDetVaJ0yBCgDlE=",
|
||||
"x86_64-darwin": "sha256-Tt5k5KBnrsNVIqPET7OFzClerjdR68XYstyCj3KpvdI="
|
||||
<<<<<<< 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
- 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
|
||||
|
||||
35
packages/app/e2e/file-viewer.spec.ts
Normal file
35
packages/app/e2e/file-viewer.spec.ts
Normal 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()
|
||||
})
|
||||
43
packages/app/e2e/model-picker.spec.ts
Normal file
43
packages/app/e2e/model-picker.spec.ts
Normal 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()
|
||||
})
|
||||
26
packages/app/e2e/prompt-mention.spec.ts
Normal file
26
packages/app/e2e/prompt-mention.spec.ts
Normal 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")
|
||||
})
|
||||
22
packages/app/e2e/prompt-slash-open.spec.ts
Normal file
22
packages/app/e2e/prompt-slash-open.spec.ts
Normal 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)
|
||||
})
|
||||
44
packages/app/e2e/settings.spec.ts
Normal file
44
packages/app/e2e/settings.spec.ts
Normal 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)
|
||||
})
|
||||
25
packages/app/e2e/terminal-init.spec.ts
Normal file
25
packages/app/e2e/terminal-init.spec.ts
Normal 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)
|
||||
})
|
||||
8
packages/app/e2e/tsconfig.json
Normal file
8
packages/app/e2e/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
||||
@@ -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)" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.28",
|
||||
"version": "1.1.34",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -42,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",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../ui/src/assets/favicon/apple-touch-icon-v2.png
|
||||
1
packages/app/public/apple-touch-icon-v3.png
Symbolic link
1
packages/app/public/apple-touch-icon-v3.png
Symbolic link
@@ -0,0 +1 @@
|
||||
../../ui/src/assets/favicon/apple-touch-icon-v3.png
|
||||
@@ -1 +0,0 @@
|
||||
../../ui/src/assets/favicon/favicon-96x96-v2.png
|
||||
1
packages/app/public/favicon-96x96-v3.png
Symbolic link
1
packages/app/public/favicon-96x96-v3.png
Symbolic link
@@ -0,0 +1 @@
|
||||
../../ui/src/assets/favicon/favicon-96x96-v3.png
|
||||
@@ -1 +0,0 @@
|
||||
../../ui/src/assets/favicon/favicon-v2.ico
|
||||
@@ -1 +0,0 @@
|
||||
../../ui/src/assets/favicon/favicon-v2.svg
|
||||
1
packages/app/public/favicon-v3.ico
Symbolic link
1
packages/app/public/favicon-v3.ico
Symbolic link
@@ -0,0 +1 @@
|
||||
../../ui/src/assets/favicon/favicon-v3.ico
|
||||
1
packages/app/public/favicon-v3.svg
Symbolic link
1
packages/app/public/favicon-v3.svg
Symbolic link
@@ -0,0 +1 @@
|
||||
../../ui/src/assets/favicon/favicon-v3.svg
|
||||
@@ -25,15 +25,21 @@ async function freePort() {
|
||||
}
|
||||
|
||||
async function waitForHealth(url: string) {
|
||||
const timeout = Date.now() + 60_000
|
||||
const timeout = Date.now() + 120_000
|
||||
const errors: string[] = []
|
||||
while (Date.now() < timeout) {
|
||||
const ok = await fetch(url)
|
||||
.then((r) => r.ok)
|
||||
.catch(() => false)
|
||||
if (ok) return
|
||||
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))
|
||||
}
|
||||
throw new Error(`Timed out waiting for server health: ${url}`)
|
||||
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()
|
||||
@@ -72,10 +78,10 @@ const serverEnv = {
|
||||
} satisfies Record<string, string>
|
||||
|
||||
const runnerEnv = {
|
||||
...process.env,
|
||||
PLAYWRIGHT_SERVER_HOST: "localhost",
|
||||
...serverEnv,
|
||||
PLAYWRIGHT_SERVER_HOST: "127.0.0.1",
|
||||
PLAYWRIGHT_SERVER_PORT: String(serverPort),
|
||||
VITE_OPENCODE_SERVER_HOST: "localhost",
|
||||
VITE_OPENCODE_SERVER_HOST: "127.0.0.1",
|
||||
VITE_OPENCODE_SERVER_PORT: String(serverPort),
|
||||
PLAYWRIGHT_PORT: String(webPort),
|
||||
} satisfies Record<string, string>
|
||||
@@ -92,39 +98,46 @@ if (seedExit !== 0) {
|
||||
process.exit(seedExit)
|
||||
}
|
||||
|
||||
const server = Bun.spawn(
|
||||
[
|
||||
"bun",
|
||||
"dev",
|
||||
"--",
|
||||
"--print-logs",
|
||||
"--log-level",
|
||||
"WARN",
|
||||
"serve",
|
||||
"--port",
|
||||
String(serverPort),
|
||||
"--hostname",
|
||||
"127.0.0.1",
|
||||
],
|
||||
{
|
||||
cwd: opencodeDir,
|
||||
env: serverEnv,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
},
|
||||
)
|
||||
Object.assign(process.env, serverEnv)
|
||||
process.env.AGENT = "1"
|
||||
process.env.OPENCODE = "1"
|
||||
|
||||
try {
|
||||
await waitForHealth(`http://localhost:${serverPort}/global/health`)
|
||||
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 runner = Bun.spawn(["bun", "test:e2e", ...extraArgs], {
|
||||
cwd: appDir,
|
||||
env: runnerEnv,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
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}`)
|
||||
|
||||
process.exitCode = await runner.exited
|
||||
} finally {
|
||||
server.kill()
|
||||
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)
|
||||
|
||||
@@ -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"
|
||||
@@ -18,9 +19,12 @@ 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"
|
||||
@@ -32,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>
|
||||
)
|
||||
@@ -109,13 +127,15 @@ export function AppInterface(props: { defaultUrl?: string }) {
|
||||
<Route
|
||||
path="/session/:id?"
|
||||
component={(p) => (
|
||||
<Show when={p.params.id ?? "new"} keyed>
|
||||
<Show when={p.params.id ?? "new"}>
|
||||
<TerminalProvider>
|
||||
<FileProvider>
|
||||
<PromptProvider>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Session />
|
||||
</Suspense>
|
||||
<CommentsProvider>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Session />
|
||||
</Suspense>
|
||||
</CommentsProvider>
|
||||
</PromptProvider>
|
||||
</FileProvider>
|
||||
</TerminalProvider>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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">
|
||||
<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
|
||||
@@ -128,7 +148,11 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
||||
</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
|
||||
@@ -172,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":
|
||||
@@ -205,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>
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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,20 +19,21 @@ 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 = [
|
||||
@@ -56,7 +58,7 @@ export function DialogSelectFile() {
|
||||
title: option.title,
|
||||
description: option.description,
|
||||
keybind: option.keybind,
|
||||
category: "Commands",
|
||||
category: language.t("palette.group.commands"),
|
||||
option,
|
||||
})
|
||||
|
||||
@@ -64,7 +66,7 @@ export function DialogSelectFile() {
|
||||
id: "file:" + path,
|
||||
type: "file",
|
||||
title: path,
|
||||
category: "Files",
|
||||
category: language.t("palette.group.files"),
|
||||
path,
|
||||
})
|
||||
|
||||
@@ -143,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"]}
|
||||
|
||||
@@ -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()}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -2,6 +2,8 @@ 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"
|
||||
@@ -12,6 +14,9 @@ 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">
|
||||
@@ -20,22 +25,35 @@ export const DialogSettings: Component = () => {
|
||||
style={{
|
||||
display: "flex",
|
||||
"flex-direction": "column",
|
||||
gap: "12px",
|
||||
"justify-content": "space-between",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
"padding-top": "12px",
|
||||
"padding-bottom": "12px",
|
||||
}}
|
||||
>
|
||||
<Tabs.SectionTitle>Desktop</Tabs.SectionTitle>
|
||||
<div style={{ display: "flex", "flex-direction": "column", gap: "6px", width: "100%" }}>
|
||||
<Tabs.Trigger value="general">
|
||||
<Icon name="sliders" />
|
||||
General
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="shortcuts">
|
||||
<Icon name="keyboard" />
|
||||
Shortcuts
|
||||
</Tabs.Trigger>
|
||||
<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> */}
|
||||
|
||||
91
packages/app/src/components/model-tooltip.tsx
Normal file
91
packages/app/src/components/model-tooltip.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
@@ -257,7 +284,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
params.id
|
||||
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))
|
||||
})
|
||||
@@ -312,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
|
||||
}
|
||||
@@ -379,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
|
||||
@@ -410,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,
|
||||
})
|
||||
|
||||
@@ -548,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(),
|
||||
@@ -788,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
|
||||
@@ -908,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
|
||||
@@ -997,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
|
||||
}
|
||||
@@ -1009,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)
|
||||
@@ -1030,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
|
||||
@@ -1038,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
|
||||
}
|
||||
|
||||
@@ -1070,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
|
||||
@@ -1079,6 +1166,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
if (!session) return
|
||||
|
||||
props.onSubmit?.()
|
||||
|
||||
const model = {
|
||||
modelID: currentModel.id,
|
||||
providerID: currentModel.provider.id,
|
||||
@@ -1114,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()
|
||||
@@ -1146,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()
|
||||
@@ -1197,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) => ({
|
||||
@@ -1247,7 +1368,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const requestParts = [
|
||||
textPart,
|
||||
...fileAttachmentParts,
|
||||
...contextFileParts,
|
||||
...contextParts,
|
||||
...agentAttachmentParts,
|
||||
...imageAttachmentParts,
|
||||
]
|
||||
@@ -1267,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) {
|
||||
@@ -1288,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) {
|
||||
@@ -1300,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,
|
||||
@@ -1312,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 (
|
||||
@@ -1338,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) => (
|
||||
@@ -1360,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">
|
||||
@@ -1384,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) => (
|
||||
@@ -1406,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)}>
|
||||
@@ -1435,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>
|
||||
@@ -1525,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>
|
||||
@@ -1543,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}
|
||||
@@ -1560,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>
|
||||
@@ -1571,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 ?? ""}
|
||||
@@ -1588,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
|
||||
@@ -1622,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
|
||||
@@ -1640,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"
|
||||
@@ -1667,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>
|
||||
@@ -1681,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>
|
||||
@@ -1700,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,10 +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 showReview = createMemo(() => !!currentSession()?.summary?.files)
|
||||
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,
|
||||
@@ -134,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>
|
||||
|
||||
@@ -174,33 +177,34 @@ export function SessionHeader() {
|
||||
{/* <SessionMcpIndicator /> */}
|
||||
{/* </div> */}
|
||||
<div class="flex items-center gap-1">
|
||||
<div
|
||||
class="hidden md:block shrink-0"
|
||||
classList={{
|
||||
"opacity-0 pointer-events-none": !showReview(),
|
||||
}}
|
||||
aria-hidden={!showReview()}
|
||||
>
|
||||
<TooltipKeybind title="Toggle review" keybind={command.keybind("review.toggle")}>
|
||||
<div class="hidden md:block shrink-0">
|
||||
<TooltipKeybind
|
||||
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>
|
||||
@@ -209,13 +213,16 @@ export function SessionHeader() {
|
||||
</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
|
||||
@@ -237,87 +244,96 @@ export function SessionHeader() {
|
||||
</Button>
|
||||
</TooltipKeybind>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center"
|
||||
classList={{
|
||||
"opacity-0 pointer-events-none": !showShare(),
|
||||
}}
|
||||
aria-hidden={!showShare()}
|
||||
>
|
||||
<Popover
|
||||
title="Publish on web"
|
||||
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 }}
|
||||
<Show when={showShare()}>
|
||||
<div class="flex items-center">
|
||||
<Popover
|
||||
title={language.t("session.share.popover.title")}
|
||||
description={
|
||||
shareUrl()
|
||||
? 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
|
||||
when={shareUrl()}
|
||||
fallback={
|
||||
<div class="flex">
|
||||
<Button
|
||||
size="large"
|
||||
variant="primary"
|
||||
class="w-1/2"
|
||||
onClick={shareSession}
|
||||
disabled={state.share}
|
||||
>
|
||||
{state.share
|
||||
? language.t("session.share.action.publishing")
|
||||
: language.t("session.share.action.publish")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Show
|
||||
when={shareUrl()}
|
||||
fallback={
|
||||
<div class="flex">
|
||||
<Button
|
||||
size="large"
|
||||
variant="primary"
|
||||
class="w-1/2"
|
||||
onClick={shareSession}
|
||||
disabled={state.share}
|
||||
>
|
||||
{state.share ? "Publishing..." : "Publish"}
|
||||
</Button>
|
||||
<div class="flex flex-col gap-2 w-72">
|
||||
<TextField value={shareUrl() ?? ""} readOnly copyable class="w-full" />
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
size="large"
|
||||
variant="secondary"
|
||||
class="w-full shadow-none border border-border-weak-base"
|
||||
onClick={unshareSession}
|
||||
disabled={state.unshare}
|
||||
>
|
||||
{state.unshare
|
||||
? language.t("session.share.action.unpublishing")
|
||||
: language.t("session.share.action.unpublish")}
|
||||
</Button>
|
||||
<Button
|
||||
size="large"
|
||||
variant="primary"
|
||||
class="w-full"
|
||||
onClick={viewShare}
|
||||
disabled={state.unshare}
|
||||
>
|
||||
{language.t("session.share.action.view")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Popover>
|
||||
<Show when={shareUrl()} fallback={<div class="size-6" aria-hidden="true" />}>
|
||||
<Tooltip
|
||||
value={
|
||||
state.copied
|
||||
? language.t("session.share.copy.copied")
|
||||
: language.t("session.share.copy.copyLink")
|
||||
}
|
||||
placement="top"
|
||||
gutter={8}
|
||||
>
|
||||
<div class="flex flex-col gap-2 w-72">
|
||||
<TextField value={shareUrl() ?? ""} readOnly copyable class="w-full" />
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
size="large"
|
||||
variant="secondary"
|
||||
class="w-full shadow-none border border-border-weak-base"
|
||||
onClick={unshareSession}
|
||||
disabled={state.unshare}
|
||||
>
|
||||
{state.unshare ? "Unpublishing..." : "Unpublish"}
|
||||
</Button>
|
||||
<Button
|
||||
size="large"
|
||||
variant="primary"
|
||||
class="w-full"
|
||||
onClick={viewShare}
|
||||
disabled={state.unshare}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Popover>
|
||||
<Show when={shareUrl()} fallback={<div class="size-6" aria-hidden="true" />}>
|
||||
<Tooltip value={state.copied ? "Copied" : "Copy link"} placement="top" gutter={8}>
|
||||
<IconButton
|
||||
icon={state.copied ? "check" : "copy"}
|
||||
variant="secondary"
|
||||
class="rounded-l-none"
|
||||
onClick={copyLink}
|
||||
disabled={state.unshare}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Portal>
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
{language.t("session.new.lastModified")}
|
||||
<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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
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">Agents</h2>
|
||||
<p class="text-14-regular text-text-weak">Agent settings will be configurable here.</p>
|
||||
<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>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
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">Commands</h2>
|
||||
<p class="text-14-regular text-text-weak">Command settings will be configurable here.</p>
|
||||
<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>
|
||||
)
|
||||
|
||||
@@ -2,36 +2,47 @@ 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: { value: ColorScheme; label: string }[] = [
|
||||
{ value: "system", label: "System setting" },
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
]
|
||||
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: "IBM Plex Mono" },
|
||||
{ value: "cascadia-code", label: "Cascadia Code" },
|
||||
{ value: "fira-code", label: "Fira Code" },
|
||||
{ value: "hack", label: "Hack" },
|
||||
{ value: "inconsolata", label: "Inconsolata" },
|
||||
{ value: "intel-one-mono", label: "Intel One Mono" },
|
||||
{ value: "jetbrains-mono", label: "JetBrains Mono" },
|
||||
{ value: "meslo-lgs", label: "Meslo LGS" },
|
||||
{ value: "roboto-mono", label: "Roboto Mono" },
|
||||
{ value: "source-code-pro", label: "Source Code Pro" },
|
||||
{ value: "ubuntu-mono", label: "Ubuntu Mono" },
|
||||
]
|
||||
{ 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]
|
||||
|
||||
@@ -45,20 +56,39 @@ export const SettingsGeneral: Component = () => {
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col gap-1 pt-6 pb-8">
|
||||
<h2 class="text-16-medium text-text-strong">General</h2>
|
||||
<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">Appearance</h3>
|
||||
<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="Appearance" description="Customise how OpenCode looks on your device">
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.language.title")}
|
||||
description={language.t("settings.general.row.language.description")}
|
||||
>
|
||||
<Select
|
||||
options={colorSchemeOptions}
|
||||
current={colorSchemeOptions.find((o) => o.value === theme.colorScheme())}
|
||||
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)}
|
||||
@@ -74,13 +104,11 @@ export const SettingsGeneral: Component = () => {
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title="Theme"
|
||||
title={language.t("settings.general.row.theme.title")}
|
||||
description={
|
||||
<>
|
||||
Customise how OpenCode is themed.{" "}
|
||||
<a href="#" class="text-text-interactive-base">
|
||||
Learn more
|
||||
</a>
|
||||
{language.t("settings.general.row.theme.description")}{" "}
|
||||
<Link href="https://opencode.ai/docs/themes/">{language.t("common.learnMore")}</Link>
|
||||
</>
|
||||
}
|
||||
>
|
||||
@@ -104,19 +132,26 @@ export const SettingsGeneral: Component = () => {
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow title="Font" description="Customise the mono font used in code blocks">
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.font.title")}
|
||||
description={language.t("settings.general.row.font.description")}
|
||||
>
|
||||
<Select
|
||||
options={fontOptions}
|
||||
current={fontOptions.find((o) => o.value === settings.appearance.font())}
|
||||
options={fontOptionsList}
|
||||
current={fontOptionsList.find((o) => o.value === settings.appearance.font())}
|
||||
value={(o) => o.value}
|
||||
label={(o) => o.label}
|
||||
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?.label}</span>}
|
||||
{(option) => (
|
||||
<span style={{ "font-family": monoFontFamily(option?.value) }}>
|
||||
{option ? language.t(option.label) : ""}
|
||||
</span>
|
||||
)}
|
||||
</Select>
|
||||
</SettingsRow>
|
||||
</div>
|
||||
@@ -124,12 +159,12 @@ export const SettingsGeneral: Component = () => {
|
||||
|
||||
{/* System notifications Section */}
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-14-medium text-text-strong pb-2">System notifications</h3>
|
||||
<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="Agent"
|
||||
description="Show system notification when the agent is complete or needs attention"
|
||||
title={language.t("settings.general.notifications.agent.title")}
|
||||
description={language.t("settings.general.notifications.agent.description")}
|
||||
>
|
||||
<Switch
|
||||
checked={settings.notifications.agent()}
|
||||
@@ -137,14 +172,20 @@ export const SettingsGeneral: Component = () => {
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow title="Permissions" description="Show system notification when a permission is required">
|
||||
<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="Errors" description="Show system notification when an error occurs">
|
||||
<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)}
|
||||
@@ -155,15 +196,18 @@ export const SettingsGeneral: Component = () => {
|
||||
|
||||
{/* Sound effects Section */}
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-14-medium text-text-strong pb-2">Sound effects</h3>
|
||||
<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="Agent" description="Play sound when the agent is complete or needs attention">
|
||||
<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) => o.label}
|
||||
label={(o) => language.t(o.label)}
|
||||
onHighlight={(option) => {
|
||||
if (!option) return
|
||||
playSound(option.src)
|
||||
@@ -179,12 +223,15 @@ export const SettingsGeneral: Component = () => {
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow title="Permissions" description="Play sound when a permission is required">
|
||||
<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) => o.label}
|
||||
label={(o) => language.t(o.label)}
|
||||
onHighlight={(option) => {
|
||||
if (!option) return
|
||||
playSound(option.src)
|
||||
@@ -200,12 +247,15 @@ export const SettingsGeneral: Component = () => {
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow title="Errors" description="Play sound when an error occurs">
|
||||
<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) => o.label}
|
||||
label={(o) => language.t(o.label)}
|
||||
onHighlight={(option) => {
|
||||
if (!option) return
|
||||
playSound(option.src)
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
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)
|
||||
@@ -17,6 +22,23 @@ type KeybindMeta = {
|
||||
|
||||
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"
|
||||
@@ -86,9 +108,11 @@ function signatures(config: string | undefined) {
|
||||
|
||||
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
|
||||
@@ -117,12 +141,16 @@ export const SettingsKeybinds: Component = () => {
|
||||
const resetAll = () => {
|
||||
stop()
|
||||
settings.keybinds.resetAll()
|
||||
showToast({ title: "Shortcuts reset", description: "Keyboard shortcuts have been reset to defaults." })
|
||||
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: "Command palette", group: "General" })
|
||||
out.set(PALETTE_ID, { title: language.t("command.palette"), group: "General" })
|
||||
|
||||
for (const opt of command.catalog) {
|
||||
if (opt.id.startsWith("suggested.")) continue
|
||||
@@ -174,6 +202,45 @@ export const SettingsKeybinds: Component = () => {
|
||||
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 }[]>()
|
||||
|
||||
@@ -188,7 +255,7 @@ export const SettingsKeybinds: Component = () => {
|
||||
|
||||
const palette = settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND
|
||||
for (const sig of signatures(palette)) {
|
||||
add(sig, { id: PALETTE_ID, title: "Command palette" })
|
||||
add(sig, { id: PALETTE_ID, title: title(PALETTE_ID) })
|
||||
}
|
||||
|
||||
const valueFor = (id: string) => {
|
||||
@@ -258,8 +325,11 @@ export const SettingsKeybinds: Component = () => {
|
||||
|
||||
if (conflicts.size > 0) {
|
||||
showToast({
|
||||
title: "Shortcut already in use",
|
||||
description: `${formatKeybind(next)} is already assigned to ${[...conflicts.values()].join(", ")}.`,
|
||||
title: language.t("settings.shortcuts.conflict.title"),
|
||||
description: language.t("settings.shortcuts.conflict.description", {
|
||||
keybind: formatKeybind(next),
|
||||
titles: [...conflicts.values()].join(", "),
|
||||
}),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -287,22 +357,43 @@ export const SettingsKeybinds: Component = () => {
|
||||
"linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center justify-between gap-4 pt-6 pb-8 max-w-[720px]">
|
||||
<h2 class="text-16-medium text-text-strong">Keyboard shortcuts</h2>
|
||||
<Button size="small" variant="secondary" onClick={resetAll} disabled={!hasOverrides()}>
|
||||
Reset to defaults
|
||||
</Button>
|
||||
<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={(grouped().get(group) ?? []).length > 0}>
|
||||
<Show when={(filtered().get(group) ?? []).length > 0}>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-14-medium text-text-strong pb-2">{group}</h3>
|
||||
<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={grouped().get(group) ?? []}>
|
||||
<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>
|
||||
@@ -316,8 +407,11 @@ export const SettingsKeybinds: Component = () => {
|
||||
}}
|
||||
onClick={() => start(id)}
|
||||
>
|
||||
<Show when={active() === id} fallback={command.keybind(id) || "Unassigned"}>
|
||||
Press keys
|
||||
<Show
|
||||
when={active() === id}
|
||||
fallback={command.keybind(id) || language.t("settings.shortcuts.unassigned")}
|
||||
>
|
||||
{language.t("settings.shortcuts.pressKeys")}
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
@@ -328,6 +422,15 @@ export const SettingsKeybinds: Component = () => {
|
||||
</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>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
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">MCP</h2>
|
||||
<p class="text-14-regular text-text-weak">MCP settings will be configurable here.</p>
|
||||
<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>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
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">Models</h2>
|
||||
<p class="text-14-regular text-text-weak">Model settings will be configurable here.</p>
|
||||
<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>
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ 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"
|
||||
|
||||
@@ -15,30 +16,94 @@ type PermissionItem = {
|
||||
description: string
|
||||
}
|
||||
|
||||
const ACTIONS: Array<{ value: PermissionAction; label: string }> = [
|
||||
{ value: "allow", label: "Allow" },
|
||||
{ value: "ask", label: "Ask" },
|
||||
{ value: "deny", label: "Deny" },
|
||||
]
|
||||
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: PermissionItem[] = [
|
||||
{ id: "read", title: "Read", description: "Reading a file (matches the file path)" },
|
||||
{ id: "edit", title: "Edit", description: "Modify files, including edits, writes, patches, and multi-edits" },
|
||||
{ id: "glob", title: "Glob", description: "Match files using glob patterns" },
|
||||
{ id: "grep", title: "Grep", description: "Search file contents using regular expressions" },
|
||||
{ id: "list", title: "List", description: "List files within a directory" },
|
||||
{ id: "bash", title: "Bash", description: "Run shell commands" },
|
||||
{ id: "task", title: "Task", description: "Launch sub-agents" },
|
||||
{ id: "skill", title: "Skill", description: "Load a skill by name" },
|
||||
{ id: "lsp", title: "LSP", description: "Run language server queries" },
|
||||
{ id: "todoread", title: "Todo Read", description: "Read the todo list" },
|
||||
{ id: "todowrite", title: "Todo Write", description: "Update the todo list" },
|
||||
{ id: "webfetch", title: "Web Fetch", description: "Fetch content from a URL" },
|
||||
{ id: "websearch", title: "Web Search", description: "Search the web" },
|
||||
{ id: "codesearch", title: "Code Search", description: "Search code on the web" },
|
||||
{ id: "external_directory", title: "External Directory", description: "Access files outside the project directory" },
|
||||
{ id: "doom_loop", title: "Doom Loop", description: "Detect repeated tool calls with identical input" },
|
||||
]
|
||||
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"])
|
||||
|
||||
@@ -67,6 +132,15 @@ function getRuleDefault(value: unknown): PermissionAction | undefined {
|
||||
|
||||
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)
|
||||
@@ -95,7 +169,7 @@ export const SettingsPermissions: Component = () => {
|
||||
globalSync.updateConfig({ permission: { [id]: nextValue } }).catch((err: unknown) => {
|
||||
globalSync.set("config", "permission", before)
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
showToast({ title: "Failed to update permissions", description: message })
|
||||
showToast({ title: language.t("settings.permissions.toast.updateFailed.title"), description: message })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -109,21 +183,21 @@ export const SettingsPermissions: Component = () => {
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col gap-1 p-8 max-w-[720px]">
|
||||
<h2 class="text-16-medium text-text-strong">Permissions</h2>
|
||||
<p class="text-14-regular text-text-weak">Control what tools the server can use by default.</p>
|
||||
<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">Appearance</h3>
|
||||
<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={item.title} description={item.description}>
|
||||
<SettingsRow title={language.t(item.title)} description={language.t(item.description)}>
|
||||
<Select
|
||||
options={ACTIONS}
|
||||
current={ACTIONS.find((o) => o.value === actionFor(item.id))}
|
||||
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)}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
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">Providers</h2>
|
||||
<p class="text-14-regular text-text-weak">Provider settings will be configurable here.</p>
|
||||
<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>
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface TerminalProps extends ComponentProps<"div"> {
|
||||
pty: LocalPTY
|
||||
onSubmit?: () => void
|
||||
onCleanup?: (pty: LocalPTY) => void
|
||||
onConnect?: () => void
|
||||
onConnectError?: (error: unknown) => void
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
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
|
||||
@@ -241,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,
|
||||
@@ -256,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)
|
||||
}
|
||||
@@ -293,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 ?? {}),
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createEffect, createMemo, createSignal, onCleanup, onMount, type Access
|
||||
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"
|
||||
|
||||
@@ -154,6 +155,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
||||
init: () => {
|
||||
const dialog = useDialog()
|
||||
const settings = useSettings()
|
||||
const language = useLanguage()
|
||||
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
|
||||
const [suspendCount, setSuspendCount] = createSignal(0)
|
||||
|
||||
@@ -213,7 +215,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
||||
...suggested.map((x) => ({
|
||||
...x,
|
||||
id: SUGGESTED_PREFIX + x.id,
|
||||
category: "Suggested",
|
||||
category: language.t("command.category.suggested"),
|
||||
})),
|
||||
...resolved,
|
||||
]
|
||||
|
||||
140
packages/app/src/context/comments.tsx
Normal file
140
packages/app/src/context/comments.tsx
Normal 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(),
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,6 +103,18 @@ 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
|
||||
}
|
||||
@@ -95,9 +122,29 @@ type ChildOptions = {
|
||||
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
|
||||
@@ -110,7 +157,7 @@ function createGlobalSync() {
|
||||
}>({
|
||||
ready: false,
|
||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||
project: [],
|
||||
project: projectCache.value,
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
provider_auth: {},
|
||||
config: {},
|
||||
@@ -118,6 +165,24 @@ function createGlobalSync() {
|
||||
})
|
||||
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) {
|
||||
@@ -147,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: "" },
|
||||
@@ -170,6 +255,16 @@ function createGlobalSync() {
|
||||
message: {},
|
||||
part: {},
|
||||
})
|
||||
|
||||
children[directory] = child
|
||||
|
||||
createEffect(() => {
|
||||
child[1]("projectMeta", meta[0].value)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
child[1]("icon", icon[0].value)
|
||||
})
|
||||
}
|
||||
|
||||
runWithOwner(owner, init)
|
||||
@@ -232,7 +327,7 @@ function createGlobalSync() {
|
||||
.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)
|
||||
@@ -251,6 +346,8 @@ function createGlobalSync() {
|
||||
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,
|
||||
@@ -267,6 +364,8 @@ function createGlobalSync() {
|
||||
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: () =>
|
||||
@@ -473,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
|
||||
@@ -656,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
|
||||
}
|
||||
|
||||
@@ -712,6 +822,32 @@ 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,
|
||||
@@ -733,6 +869,8 @@ function createGlobalSync() {
|
||||
},
|
||||
project: {
|
||||
loadSessions,
|
||||
meta: projectMeta,
|
||||
icon: projectIcon,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
161
packages/app/src/context/language.tsx
Normal file
161
packages/app/src/context/language.tsx
Normal 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)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -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])
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
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 { Persist, persisted } from "@/utils/persist"
|
||||
import { playSound, soundSrc } from "@/utils/sound"
|
||||
@@ -43,10 +45,12 @@ function pruneNotifications(list: Notification[]) {
|
||||
export const { use: useNotification, provider: NotificationProvider } = createSimpleContext({
|
||||
name: "Notification",
|
||||
init: () => {
|
||||
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"]),
|
||||
@@ -71,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": {
|
||||
@@ -87,16 +96,21 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||
playSound(soundSrc(settings.sounds.agent()))
|
||||
|
||||
append({
|
||||
...base,
|
||||
directory,
|
||||
time,
|
||||
viewed: viewed(sessionID),
|
||||
type: "turn-complete",
|
||||
session: sessionID,
|
||||
})
|
||||
|
||||
const href = `/${base64Encode(directory)}/session/${sessionID}`
|
||||
if (settings.notifications.agent()) {
|
||||
void platform.notify("Response ready", session?.title ?? sessionID, href)
|
||||
void platform.notify(
|
||||
language.t("notification.session.responseReady.title"),
|
||||
session?.title ?? sessionID,
|
||||
href,
|
||||
)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
case "session.error": {
|
||||
@@ -110,18 +124,20 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||
|
||||
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)}`
|
||||
if (settings.notifications.errors()) {
|
||||
void platform.notify("Session error", description, href)
|
||||
void platform.notify(language.t("notification.session.error.title"), description, href)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -60,16 +60,16 @@ const monoFallback =
|
||||
|
||||
const monoFonts: Record<string, string> = {
|
||||
"ibm-plex-mono": `"IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
"cascadia-code": `"Cascadia Code Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
"fira-code": `"Fira Code Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
hack: `"Hack Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
inconsolata: `"Inconsolata Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
"intel-one-mono": `"Intel One Mono Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
"jetbrains-mono": `"JetBrains Mono Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
"meslo-lgs": `"Meslo LGS Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
"roboto-mono": `"Roboto Mono Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
"source-code-pro": `"Source Code Pro Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||
"ubuntu-mono": `"Ubuntu Mono Nerd Font", "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) {
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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<{
|
||||
@@ -54,24 +63,36 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, sess
|
||||
})
|
||||
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]
|
||||
}),
|
||||
@@ -87,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) => {
|
||||
@@ -102,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,
|
||||
@@ -137,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)
|
||||
})
|
||||
@@ -224,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(),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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
656
packages/app/src/i18n/ar.ts
Normal 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
667
packages/app/src/i18n/br.ts
Normal 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
582
packages/app/src/i18n/da.ts
Normal 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
591
packages/app/src/i18n/de.ts
Normal 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
663
packages/app/src/i18n/en.ts
Normal 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
585
packages/app/src/i18n/es.ts
Normal 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
592
packages/app/src/i18n/fr.ts
Normal 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
579
packages/app/src/i18n/ja.ts
Normal 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
580
packages/app/src/i18n/ko.ts
Normal 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
602
packages/app/src/i18n/no.ts
Normal 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
661
packages/app/src/i18n/pl.ts
Normal 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
664
packages/app/src/i18n/ru.ts
Normal 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
574
packages/app/src/i18n/zh.ts
Normal 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>>
|
||||
570
packages/app/src/i18n/zht.ts
Normal file
570
packages/app/src/i18n/zht.ts
Normal 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>>
|
||||
@@ -6,6 +6,52 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="markdown"] ul {
|
||||
list-style: disc outside;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
[data-component="markdown"] ol {
|
||||
list-style: decimal outside;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
[data-component="markdown"] li > p:first-child {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[data-component="markdown"] li > p + p {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
*[data-tauri-drag-region] {
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
.session-scroller::-webkit-scrollbar {
|
||||
width: 10px !important;
|
||||
height: 10px !important;
|
||||
}
|
||||
|
||||
.session-scroller::-webkit-scrollbar-track {
|
||||
background: transparent !important;
|
||||
border-radius: 5px !important;
|
||||
}
|
||||
|
||||
.session-scroller::-webkit-scrollbar-thumb {
|
||||
background: var(--border-weak-base) !important;
|
||||
border-radius: 5px !important;
|
||||
border: 3px solid transparent !important;
|
||||
background-clip: padding-box !important;
|
||||
}
|
||||
|
||||
.session-scroller::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--border-weak-base) !important;
|
||||
}
|
||||
|
||||
.session-scroller {
|
||||
scrollbar-width: thin !important;
|
||||
scrollbar-color: var(--border-weak-base) transparent !important;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function Layout(props: ParentProps) {
|
||||
return base64Decode(params.dir!)
|
||||
})
|
||||
return (
|
||||
<Show when={params.dir} keyed>
|
||||
<Show when={params.dir}>
|
||||
<SDKProvider directory={directory()}>
|
||||
<SyncProvider>
|
||||
{iife(() => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button } from "@opencode-ai/ui/button"
|
||||
import { Component, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
|
||||
export type InitError = {
|
||||
@@ -11,6 +12,8 @@ export type InitError = {
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
type Translator = ReturnType<typeof useLanguage>["t"]
|
||||
|
||||
function isInitError(error: unknown): error is InitError {
|
||||
return (
|
||||
typeof error === "object" &&
|
||||
@@ -38,30 +41,32 @@ function safeJson(value: unknown): string {
|
||||
return json ?? String(value)
|
||||
}
|
||||
|
||||
function formatInitError(error: InitError): string {
|
||||
function formatInitError(error: InitError, t: Translator): string {
|
||||
const data = error.data
|
||||
switch (error.name) {
|
||||
case "MCPFailed":
|
||||
return `MCP server "${data.name}" failed. Note, opencode does not support MCP authentication yet.`
|
||||
case "MCPFailed": {
|
||||
const name = typeof data.name === "string" ? data.name : ""
|
||||
return t("error.chain.mcpFailed", { name })
|
||||
}
|
||||
case "ProviderAuthError": {
|
||||
const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
|
||||
const message = typeof data.message === "string" ? data.message : safeJson(data.message)
|
||||
return `Provider authentication failed (${providerID}): ${message}`
|
||||
return t("error.chain.providerAuthFailed", { provider: providerID, message })
|
||||
}
|
||||
case "APIError": {
|
||||
const message = typeof data.message === "string" ? data.message : "API error"
|
||||
const message = typeof data.message === "string" ? data.message : t("error.chain.apiError")
|
||||
const lines: string[] = [message]
|
||||
|
||||
if (typeof data.statusCode === "number") {
|
||||
lines.push(`Status: ${data.statusCode}`)
|
||||
lines.push(t("error.chain.status", { status: data.statusCode }))
|
||||
}
|
||||
|
||||
if (typeof data.isRetryable === "boolean") {
|
||||
lines.push(`Retryable: ${data.isRetryable}`)
|
||||
lines.push(t("error.chain.retryable", { retryable: data.isRetryable }))
|
||||
}
|
||||
|
||||
if (typeof data.responseBody === "string" && data.responseBody) {
|
||||
lines.push(`Response body:\n${data.responseBody}`)
|
||||
lines.push(t("error.chain.responseBody", { body: data.responseBody }))
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
@@ -72,24 +77,39 @@ function formatInitError(error: InitError): string {
|
||||
modelID: string
|
||||
suggestions?: string[]
|
||||
}
|
||||
|
||||
const suggestionsLine =
|
||||
Array.isArray(suggestions) && suggestions.length
|
||||
? [t("error.chain.didYouMean", { suggestions: suggestions.join(", ") })]
|
||||
: []
|
||||
|
||||
return [
|
||||
`Model not found: ${providerID}/${modelID}`,
|
||||
...(Array.isArray(suggestions) && suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
|
||||
`Check your config (opencode.json) provider/model names`,
|
||||
t("error.chain.modelNotFound", { provider: providerID, model: modelID }),
|
||||
...suggestionsLine,
|
||||
t("error.chain.checkConfig"),
|
||||
].join("\n")
|
||||
}
|
||||
case "ProviderInitError": {
|
||||
const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
|
||||
return `Failed to initialize provider "${providerID}". Check credentials and configuration.`
|
||||
return t("error.chain.providerInitFailed", { provider: providerID })
|
||||
}
|
||||
case "ConfigJsonError": {
|
||||
const path = typeof data.path === "string" ? data.path : safeJson(data.path)
|
||||
const message = typeof data.message === "string" ? data.message : ""
|
||||
return `Config file at ${data.path} is not valid JSON(C)` + (message ? `: ${message}` : "")
|
||||
if (message) return t("error.chain.configJsonInvalidWithMessage", { path, message })
|
||||
return t("error.chain.configJsonInvalid", { path })
|
||||
}
|
||||
case "ConfigDirectoryTypoError": {
|
||||
const path = typeof data.path === "string" ? data.path : safeJson(data.path)
|
||||
const dir = typeof data.dir === "string" ? data.dir : safeJson(data.dir)
|
||||
const suggestion = typeof data.suggestion === "string" ? data.suggestion : safeJson(data.suggestion)
|
||||
return t("error.chain.configDirectoryTypo", { dir, path, suggestion })
|
||||
}
|
||||
case "ConfigFrontmatterError": {
|
||||
const path = typeof data.path === "string" ? data.path : safeJson(data.path)
|
||||
const message = typeof data.message === "string" ? data.message : safeJson(data.message)
|
||||
return t("error.chain.configFrontmatterError", { path, message })
|
||||
}
|
||||
case "ConfigDirectoryTypoError":
|
||||
return `Directory "${data.dir}" in ${data.path} is not valid. Rename the directory to "${data.suggestion}" or remove it. This is a common typo.`
|
||||
case "ConfigFrontmatterError":
|
||||
return `Failed to parse frontmatter in ${data.path}:\n${data.message}`
|
||||
case "ConfigInvalidError": {
|
||||
const issues = Array.isArray(data.issues)
|
||||
? data.issues.map(
|
||||
@@ -97,7 +117,13 @@ function formatInitError(error: InitError): string {
|
||||
)
|
||||
: []
|
||||
const message = typeof data.message === "string" ? data.message : ""
|
||||
return [`Config file at ${data.path} is invalid` + (message ? `: ${message}` : ""), ...issues].join("\n")
|
||||
const path = typeof data.path === "string" ? data.path : safeJson(data.path)
|
||||
|
||||
const line = message
|
||||
? t("error.chain.configInvalidWithMessage", { path, message })
|
||||
: t("error.chain.configInvalid", { path })
|
||||
|
||||
return [line, ...issues].join("\n")
|
||||
}
|
||||
case "UnknownError":
|
||||
return typeof data.message === "string" ? data.message : safeJson(data)
|
||||
@@ -107,20 +133,20 @@ function formatInitError(error: InitError): string {
|
||||
}
|
||||
}
|
||||
|
||||
function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): string {
|
||||
if (!error) return "Unknown error"
|
||||
function formatErrorChain(error: unknown, t: Translator, depth = 0, parentMessage?: string): string {
|
||||
if (!error) return t("error.chain.unknown")
|
||||
|
||||
if (isInitError(error)) {
|
||||
const message = formatInitError(error)
|
||||
const message = formatInitError(error, t)
|
||||
if (depth > 0 && parentMessage === message) return ""
|
||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\n${t("error.chain.causedBy")}\n` : ""
|
||||
return indent + `${error.name}\n${message}`
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
const isDuplicate = depth > 0 && parentMessage === error.message
|
||||
const parts: string[] = []
|
||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\n${t("error.chain.causedBy")}\n` : ""
|
||||
|
||||
const header = `${error.name}${error.message ? `: ${error.message}` : ""}`
|
||||
const stack = error.stack?.trim()
|
||||
@@ -153,7 +179,7 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
|
||||
}
|
||||
|
||||
if (error.cause) {
|
||||
const causeResult = formatErrorChain(error.cause, depth + 1, error.message)
|
||||
const causeResult = formatErrorChain(error.cause, t, depth + 1, error.message)
|
||||
if (causeResult) {
|
||||
parts.push(causeResult)
|
||||
}
|
||||
@@ -164,16 +190,16 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
|
||||
|
||||
if (typeof error === "string") {
|
||||
if (depth > 0 && parentMessage === error) return ""
|
||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\n${t("error.chain.causedBy")}\n` : ""
|
||||
return indent + error
|
||||
}
|
||||
|
||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\n${t("error.chain.causedBy")}\n` : ""
|
||||
return indent + safeJson(error)
|
||||
}
|
||||
|
||||
function formatError(error: unknown): string {
|
||||
return formatErrorChain(error, 0)
|
||||
function formatError(error: unknown, t: Translator): string {
|
||||
return formatErrorChain(error, t, 0)
|
||||
}
|
||||
|
||||
interface ErrorPageProps {
|
||||
@@ -182,6 +208,7 @@ interface ErrorPageProps {
|
||||
|
||||
export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||
const platform = usePlatform()
|
||||
const language = useLanguage()
|
||||
const [store, setStore] = createStore({
|
||||
checking: false,
|
||||
version: undefined as string | undefined,
|
||||
@@ -206,51 +233,55 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||
<div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
|
||||
<Logo class="w-58.5 opacity-12 shrink-0" />
|
||||
<div class="flex flex-col items-center gap-2 text-center">
|
||||
<h1 class="text-lg font-medium text-text-strong">Something went wrong</h1>
|
||||
<p class="text-sm text-text-weak">An error occurred while loading the application.</p>
|
||||
<h1 class="text-lg font-medium text-text-strong">{language.t("error.page.title")}</h1>
|
||||
<p class="text-sm text-text-weak">{language.t("error.page.description")}</p>
|
||||
</div>
|
||||
<TextField
|
||||
value={formatError(props.error)}
|
||||
value={formatError(props.error, language.t)}
|
||||
readOnly
|
||||
copyable
|
||||
multiline
|
||||
class="max-h-96 w-full font-mono text-xs no-scrollbar"
|
||||
label="Error Details"
|
||||
label={language.t("error.page.details.label")}
|
||||
hideLabel
|
||||
/>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button size="large" onClick={platform.restart}>
|
||||
Restart
|
||||
{language.t("error.page.action.restart")}
|
||||
</Button>
|
||||
<Show when={platform.checkUpdate}>
|
||||
<Show
|
||||
when={store.version}
|
||||
fallback={
|
||||
<Button size="large" variant="ghost" onClick={checkForUpdates} disabled={store.checking}>
|
||||
{store.checking ? "Checking..." : "Check for updates"}
|
||||
{store.checking
|
||||
? language.t("error.page.action.checking")
|
||||
: language.t("error.page.action.checkUpdates")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button size="large" onClick={installUpdate}>
|
||||
Update to {store.version}
|
||||
{language.t("error.page.action.updateTo", { version: store.version ?? "" })}
|
||||
</Button>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
Please report this error to the OpenCode team
|
||||
{language.t("error.page.report.prefix")}
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center text-text-interactive-base gap-1"
|
||||
onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
|
||||
>
|
||||
<div>on Discord</div>
|
||||
<div>{language.t("error.page.report.discord")}</div>
|
||||
<Icon name="discord" class="text-text-interactive-base" />
|
||||
</button>
|
||||
</div>
|
||||
<Show when={platform.version}>
|
||||
<p class="text-xs text-text-weak">Version: {platform.version}</p>
|
||||
{(version) => (
|
||||
<p class="text-xs text-text-weak">{language.t("error.page.version", { version: version() })}</p>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
||||
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||
import { useServer } from "@/context/server"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { useLanguage } from "@/context/language"
|
||||
|
||||
export default function Home() {
|
||||
const sync = useGlobalSync()
|
||||
@@ -20,6 +21,7 @@ export default function Home() {
|
||||
const dialog = useDialog()
|
||||
const navigate = useNavigate()
|
||||
const server = useServer()
|
||||
const language = useLanguage()
|
||||
const homedir = createMemo(() => sync.data.path.home)
|
||||
|
||||
function openProject(directory: string) {
|
||||
@@ -41,7 +43,7 @@ export default function Home() {
|
||||
|
||||
if (platform.openDirectoryPickerDialog && server.isLocal()) {
|
||||
const result = await platform.openDirectoryPickerDialog?.({
|
||||
title: "Open project",
|
||||
title: language.t("command.project.open"),
|
||||
multiple: true,
|
||||
})
|
||||
resolve(result)
|
||||
@@ -76,9 +78,9 @@ export default function Home() {
|
||||
<Match when={sync.data.project.length > 0}>
|
||||
<div class="mt-20 w-full flex flex-col gap-4">
|
||||
<div class="flex gap-2 items-center justify-between pl-3">
|
||||
<div class="text-14-medium text-text-strong">Recent projects</div>
|
||||
<div class="text-14-medium text-text-strong">{language.t("home.recentProjects")}</div>
|
||||
<Button icon="folder-add-left" size="normal" class="pl-2 pr-3" onClick={chooseProject}>
|
||||
Open project
|
||||
{language.t("command.project.open")}
|
||||
</Button>
|
||||
</div>
|
||||
<ul class="flex flex-col gap-2">
|
||||
@@ -108,12 +110,12 @@ export default function Home() {
|
||||
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
||||
<Icon name="folder-add-left" size="large" />
|
||||
<div class="flex flex-col gap-1 items-center justify-center">
|
||||
<div class="text-14-medium text-text-strong">No recent projects</div>
|
||||
<div class="text-12-regular text-text-weak">Get started by opening a local project</div>
|
||||
<div class="text-14-medium text-text-strong">{language.t("home.empty.title")}</div>
|
||||
<div class="text-12-regular text-text-weak">{language.t("home.empty.description")}</div>
|
||||
</div>
|
||||
<div />
|
||||
<Button class="px-3" onClick={chooseProject}>
|
||||
Open project
|
||||
{language.t("command.project.open")}
|
||||
</Button>
|
||||
</div>
|
||||
</Match>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -53,10 +53,11 @@ function textPartValue(parts: Part[]) {
|
||||
* Extract prompt content from message parts for restoring into the prompt input.
|
||||
* This is used by undo to restore the original user prompt.
|
||||
*/
|
||||
export function extractPromptFromParts(parts: Part[], opts?: { directory?: string }): Prompt {
|
||||
export function extractPromptFromParts(parts: Part[], opts?: { directory?: string; attachmentName?: string }): Prompt {
|
||||
const textPart = textPartValue(parts)
|
||||
const text = textPart?.text ?? ""
|
||||
const directory = opts?.directory
|
||||
const attachmentName = opts?.attachmentName ?? "attachment"
|
||||
|
||||
const toRelative = (path: string) => {
|
||||
if (!directory) return path
|
||||
@@ -104,7 +105,7 @@ export function extractPromptFromParts(parts: Part[], opts?: { directory?: strin
|
||||
images.push({
|
||||
type: "image",
|
||||
id: filePart.id,
|
||||
filename: filePart.filename ?? "attachment",
|
||||
filename: filePart.filename ?? attachmentName,
|
||||
mime: filePart.mime,
|
||||
dataUrl: filePart.url,
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user