diff --git a/.github/TEAM_MEMBERS b/.github/TEAM_MEMBERS index f892eb95dbc4..5eb465e0dd41 100644 --- a/.github/TEAM_MEMBERS +++ b/.github/TEAM_MEMBERS @@ -1,4 +1,5 @@ adamdotdevin +arvsrn Brendonovich fwang Hona @@ -7,11 +8,14 @@ jayair jlongster kitlangton kommander +ludvigrask MrMushrooooom nexxeln R44VC0RP rekram1-node thdxr simonklee +Slickstef11 +usrnk1 vimtor starptech diff --git a/.termux-release-automation b/.termux-release-automation index 849f64528208..a2a3ca24003d 160000 --- a/.termux-release-automation +++ b/.termux-release-automation @@ -1 +1 @@ -Subproject commit 849f64528208566f42e39f46ecf725d50663c40b +Subproject commit a2a3ca24003d1163479eaf9c1220aa05d9f4a0f6 diff --git a/bun.lock b/bun.lock index c3457f10924e..92a89cc20689 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@dnd-kit/abstract": "0.5.0", "@dnd-kit/dom": "0.5.0", @@ -72,6 +72,7 @@ "shiki": "catalog:", "solid-js": "catalog:", "solid-list": "catalog:", + "solid-presence": "0.2.0", "tailwindcss": "catalog:", }, "devDependencies": { @@ -93,7 +94,7 @@ }, "packages/cli": { "name": "@opencode-ai/cli", - "version": "1.17.15", + "version": "1.17.16", "bin": { "lildax": "./bin/lildax.cjs", }, @@ -141,7 +142,7 @@ }, "packages/codemode": { "name": "@opencode-ai/codemode", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "acorn": "8.15.0", "effect": "catalog:", @@ -155,7 +156,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -191,7 +192,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -218,7 +219,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.48", @@ -240,7 +241,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -264,7 +265,7 @@ }, "packages/console/support": { "name": "@opencode-ai/console-support", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@opencode-ai/console-core": "workspace:*", @@ -284,7 +285,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "1.17.15", + "version": "1.17.16", "bin": { "opencode": "./bin/opencode", }, @@ -308,7 +309,7 @@ "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.102", "@aws-sdk/credential-providers": "3.1057.0", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", @@ -379,7 +380,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@zip.js/zip.js": "2.7.62", "effect": "catalog:", @@ -433,7 +434,7 @@ }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -447,7 +448,7 @@ }, "packages/effect-sqlite-node": { "name": "@opencode-ai/effect-sqlite-node", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "effect": "catalog:", }, @@ -459,7 +460,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@hono/standard-validator": "catalog:", "@opencode-ai/core": "workspace:*", @@ -491,7 +492,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -507,7 +508,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@effect/platform-node": "4.0.0-beta.83", "@effect/platform-node-shared": "4.0.0-beta.83", @@ -538,7 +539,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@opencode-ai/schema": "workspace:*", "@smithy/eventstream-codec": "4.2.14", @@ -557,7 +558,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.17.15", + "version": "1.17.16", "bin": { "opencode": "./bin/opencode", }, @@ -583,7 +584,7 @@ "@ai-sdk/provider": "3.0.8", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.102", "@aws-sdk/credential-providers": "3.1057.0", "@clack/prompts": "1.0.0-alpha.1", "@effect/opentelemetry": "catalog:", @@ -688,7 +689,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "workspace:*", @@ -764,7 +765,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "cross-spawn": "catalog:", }, @@ -779,7 +780,7 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/protocol": "workspace:*", @@ -794,7 +795,7 @@ }, "packages/session-ui": { "name": "@opencode-ai/session-ui", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -838,7 +839,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -851,7 +852,7 @@ }, "packages/stats/app": { "name": "@opencode-ai/stats-app", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@ibm/plex": "6.4.1", "@opencode-ai/stats-core": "workspace:*", @@ -884,7 +885,7 @@ }, "packages/stats/core": { "name": "@opencode-ai/stats-core", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@aws-sdk/client-athena": "3.933.0", "@planetscale/database": "1.19.0", @@ -903,7 +904,7 @@ }, "packages/stats/server": { "name": "@opencode-ai/stats-server", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@aws-sdk/client-firehose": "3.933.0", "@effect/platform-node": "catalog:", @@ -944,7 +945,7 @@ }, "packages/tui": { "name": "@opencode-ai/tui", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", @@ -971,7 +972,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@kobalte/core": "catalog:", "@pierre/diffs": "catalog:", @@ -1022,7 +1023,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.17.15", + "version": "1.17.16", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -1065,6 +1066,7 @@ ], "patchedDependencies": { "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch", + "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", @@ -1074,7 +1076,6 @@ "@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch", "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", - "@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch", "@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch", "pacote@21.5.0": "patches/pacote@21.5.0.patch", }, @@ -1212,7 +1213,7 @@ "@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="], - "@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], + "@ai-sdk/xai": ["@ai-sdk/xai@3.0.102", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.56", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-NeQyOR7OCqDMgaLS4uNX/ep/HrwUzzFYLzXQSRoqLy2jsnqxAJhsgltRwAwf+ADjyPBIAKEOestWnIQA+LrLrQ=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -5104,7 +5105,7 @@ "solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="], - "solid-presence": ["solid-presence@0.1.8", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA=="], + "solid-presence": ["solid-presence@0.2.0", "", { "dependencies": { "@corvu/utils": "~0.4.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-YM92o+jvpzX3XGaD4rLYmq/Kc2ZVh47GSCLEufHBFQQIurvZTs8SoGJxO8BJGNDxBKdcS8F3dYhW1SDXp4BNjA=="], "solid-prevent-scroll": ["solid-prevent-scroll@0.1.10", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw=="], @@ -5696,7 +5697,11 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cQrN6OUn/jvsY3OdsU6Wn+ss7vp1iwIcakZKSlSRMnYqShBfyT7Qht+eqmgxs7w9ttrw6FAG6o11AiBs+iEsTA=="], + + "@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.13", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-ZPtVYt5QIJzOta1kdUiDuCx4HhFkvNPv/rvmZ2b1iXwybYjJsCnNYR4PAw4kW7rgVfDARvHXcU64efWuqNp6bw=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bjYld/2KGPLt78kpqbya+fD4LYS7BqVQJyUjE3qAHrYB0FR2Q90BaWEVIBZaguTWXf/A8L6uG1zO1v9TxVlGWg=="], "@astrojs/check/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -5920,6 +5925,8 @@ "@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], + "@kobalte/core/solid-presence": ["solid-presence@0.1.8", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA=="], + "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "@mdx-js/mdx/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], @@ -6146,6 +6153,8 @@ "ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], + "ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], + "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="], "ajv-keywords/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], @@ -6580,6 +6589,8 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -6942,6 +6953,8 @@ "ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], + "ai-gateway-provider/@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], diff --git a/nix/hashes.json b/nix/hashes.json index da7fa46acc1f..a7bc1549abff 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-KTyd2ISQ4n1E3tm1LMEHtz+rKRZga+SONn5J6H0pheQ=", - "aarch64-linux": "sha256-ZIeaaqRr4JorEmmwFYuitxfYEAtrP8C6IlD+pmFRko0=", - "aarch64-darwin": "sha256-Po8FISoBzUMwJSn6nw243/hLT/gAQCrm5HbwXe2uA0g=", - "x86_64-darwin": "sha256-5TZzrCnvg1z00YP6O9U0SXjL04r+VmF7LVrqQ9BbSn4=" + "x86_64-linux": "sha256-DDHxztBr46Jxrb0uYvLYSezrqMgY+UrozRHvKdEyeXY=", + "aarch64-linux": "sha256-Dp2ohpv1jXcFX2ri4fVmsnC9WEx60bviUSlcOtIJBmE=", + "aarch64-darwin": "sha256-CXFJmkcRAS+liHf9TG5qoUG08VvKe5RusEGZTlnNer4=", + "x86_64-darwin": "sha256-7iuCfAzggMDyJ7tX8UuS7T7DCbr9h+lskE+HVFUaD0Q=" } } diff --git a/package.json b/package.json index fd86ad3decd2..5c888e358d43 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "prepare": "husky", "random": "echo 'Random script'", "sso": "aws sso login --sso-session=opencode --no-browser", + "translate:app": "bun run script/translate-app.ts", "test": "echo 'do not run tests from root' && exit 1" }, "workspaces": { @@ -146,7 +147,7 @@ "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", - "@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch", + "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", "pacote@21.5.0": "patches/pacote@21.5.0.patch", "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", diff --git a/packages/app/e2e/regression/legacy-new-session.spec.ts b/packages/app/e2e/regression/legacy-new-session.spec.ts new file mode 100644 index 000000000000..30233a5aae10 --- /dev/null +++ b/packages/app/e2e/regression/legacy-new-session.spec.ts @@ -0,0 +1,40 @@ +import { expect, test } from "@playwright/test" +import { base64Encode } from "@opencode-ai/core/util/encode" +import { mockOpenCodeServer } from "../utils/mock-server" + +const draftID = "draft_legacy_new_session" +const directory = "C:/OpenCode/LegacyNewSession" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` + +test("redirects a draft to the legacy new-session route", async ({ page }) => { + await mockOpenCodeServer(page, { + directory, + project: { + id: "proj_legacy_new_session", + worktree: directory, + vcs: "git", + name: "legacy-new-session", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { all: [], connected: [], default: {} }, + sessions: [], + pageMessages: () => ({ items: [] }), + }) + await page.addInitScript( + ({ directory, draftID, server }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: false } })) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify([{ type: "draft", draftID, server, directory }]), + ) + }, + { directory, draftID, server }, + ) + + await page.goto(`/new-session?draftId=${draftID}`) + + await expect(page).toHaveURL(`/${base64Encode(directory)}/session`) + await expect(page.locator("header[data-tauri-drag-region]")).toBeVisible() + await expect(page.locator('[data-component="prompt-input"]')).toBeVisible() +}) diff --git a/packages/app/e2e/regression/review-open-file.spec.ts b/packages/app/e2e/regression/review-open-file.spec.ts new file mode 100644 index 000000000000..f69591f608a6 --- /dev/null +++ b/packages/app/e2e/regression/review-open-file.spec.ts @@ -0,0 +1,146 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/ReviewOpenFile" +const projectID = "proj_review_open_file" +const sessionID = "ses_review_open_file" +const title = "Review open file" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` + +test.use({ viewport: { width: 1440, height: 900 } }) + +test("opens and searches project files inline", async ({ page }) => { + const searches: { query: string; dirs?: string; limit?: number }[] = [] + await mockOpenCodeServer(page, { + directory, + project: { + id: projectID, + worktree: directory, + vcs: "git", + name: "open-file-project", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "test" }, + }, + sessions: [ + { + id: sessionID, + slug: sessionID, + projectID, + directory, + title, + version: "dev", + time: { created: 1700000000000, updated: 1700000000000 }, + }, + ], + vcsDiff: [fileDiff("src/changed.ts")], + fileList: (path) => { + if (path) return [] + return [ + fileNode("README.md"), + { name: "src", path: "src", absolute: `${directory}/src`, type: "directory", ignored: false }, + ] + }, + fileContent: (path) => ({ type: "text", content: `contents:${path}` }), + findFiles: (input) => { + searches.push(input) + return input.query === "nested" ? ["src/nested.ts"] : [] + }, + pageMessages: () => ({ items: [] }), + }) + await page.addInitScript( + ({ directory, server, sessionID }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem( + "opencode.global.dat:server", + JSON.stringify({ + projects: { local: [{ worktree: directory, expanded: true }] }, + lastProject: { local: directory }, + }), + ) + localStorage.setItem( + "opencode.global.dat:layout", + JSON.stringify({ review: { diffStyle: "split", panelOpened: true } }), + ) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify([{ type: "session", server, sessionId: sessionID }]), + ) + }, + { directory, server, sessionID }, + ) + + await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`) + await expectSessionTitle(page, title) + + const panel = page.locator("#review-panel") + const contextButton = page.getByRole("button", { name: "View context usage" }) + await contextButton.click() + await expect(panel.getByRole("tab", { name: "Context" })).toHaveAttribute("data-selected", "") + await panel.getByRole("button", { name: "Open file" }).click() + await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "") + await contextButton.click() + await expect(panel.getByRole("tab", { name: "Context" })).toHaveAttribute("data-selected", "") + await panel.getByRole("button", { name: "Open file" }).click() + const filter = panel.getByRole("combobox", { name: "Filter files" }) + await expect(filter).toBeFocused() + await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "") + await expect(panel.getByText("open-file-project", { exact: true })).toBeVisible() + + await panel.getByRole("button", { name: "README.md" }).click() + await expect(panel.getByRole("tab", { name: "README.md" })).toHaveAttribute("data-selected", "") + await expect(panel.getByText("contents:README.md", { exact: true })).toBeVisible() + + await panel.getByRole("button", { name: "Open file" }).click() + await expect(panel.getByRole("tab", { name: "README.md" })).toHaveCount(0) + await filter.fill("nested") + const result = panel.getByRole("option", { name: /nested\.ts/ }) + await expect(result).toBeVisible() + const resultID = await result.getAttribute("id") + expect(resultID).toBeTruthy() + await expect(filter).toHaveAttribute("aria-activedescendant", resultID!) + await filter.press("Enter") + await expect(panel.getByRole("tab", { name: "nested.ts" })).toHaveAttribute("data-selected", "") + await expect(panel.getByText("contents:src/nested.ts", { exact: true })).toBeVisible() + expect(searches).toContainEqual({ query: "nested", dirs: "false", limit: 200 }) + + await panel.getByRole("button", { name: "Open file" }).click() + await expect(panel.getByRole("tab", { name: "nested.ts" })).toHaveCount(1) + await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "") + await page.keyboard.press("Control+w") + await expect(panel.getByRole("tab", { name: "Open file" })).toHaveCount(0) + await expect(panel.getByRole("tab", { name: "nested.ts" })).toHaveAttribute("data-selected", "") +}) + +function fileNode(path: string) { + return { + name: path, + path, + absolute: `${directory}/${path}`, + type: "file", + ignored: false, + } +} + +function fileDiff(file: string) { + return { + file, + before: "before\n", + after: "after\n", + additions: 1, + deletions: 1, + status: "modified", + } +} diff --git a/packages/app/e2e/regression/review-state-persistence.spec.ts b/packages/app/e2e/regression/review-state-persistence.spec.ts new file mode 100644 index 000000000000..aa42f1bb516d --- /dev/null +++ b/packages/app/e2e/regression/review-state-persistence.spec.ts @@ -0,0 +1,152 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test, type Page } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/ReviewStatePersistence" +const projectID = "proj_review_state_persistence" +const sessionA = "ses_review_state_a" +const sessionB = "ses_review_state_b" +const titleA = "Alpha review state" +const titleB = "Beta review state" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` + +test.use({ viewport: { width: 1440, height: 900 } }) + +test("restores review mode and selected file per session", async ({ page }) => { + await setup(page) + await page.goto(sessionHref(sessionA)) + await expectSessionTitle(page, titleA) + await page.getByRole("button", { name: "Toggle review" }).click() + + await selectMode(page, "Git changes", "Branch changes") + await selectFile(page, "beta.ts") + + await switchSession(page, titleB) + await expect(page.getByRole("button", { name: "Git changes" })).toBeVisible() + await selectFile(page, "gamma.ts") + + await switchSession(page, titleA) + await expect(page.getByRole("button", { name: "Branch changes" })).toBeVisible() + await expectSelectedFile(page, "beta.ts") + await selectMode(page, "Branch changes", "Git changes") + await expectSelectedFile(page, "alpha.ts") + await selectMode(page, "Git changes", "Branch changes") + await expectSelectedFile(page, "beta.ts") + + await page.reload() + await expectSessionTitle(page, titleA) + await expect(page.getByRole("button", { name: "Branch changes" })).toBeVisible() + await expectSelectedFile(page, "beta.ts") + + await switchSession(page, titleB) + await expect(page.getByRole("button", { name: "Git changes" })).toBeVisible() + await expectSelectedFile(page, "gamma.ts") +}) + +async function selectMode(page: Page, current: string, next: string) { + await page.getByRole("button", { name: current }).click() + await page.getByRole("option", { name: next }).click() +} + +async function selectFile(page: Page, file: string) { + await page.getByRole("button", { name: file }).click() + await expectSelectedFile(page, file) +} + +async function expectSelectedFile(page: Page, file: string) { + await expect(page.locator('[data-slot="session-review-v2-file-name"]')).toHaveText(file) +} + +async function switchSession(page: Page, title: string) { + await page.locator("[data-titlebar-tab-slot]", { hasText: title }).click() + await expectSessionTitle(page, title) +} + +async function setup(page: Page) { + await mockOpenCodeServer(page, { + directory, + project: { + id: projectID, + worktree: directory, + vcs: "git", + name: "review-state-persistence", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "test" }, + }, + sessions: [session(sessionA, titleA, 1700000000000), session(sessionB, titleB, 1700000001000)], + pageMessages: () => ({ items: [] }), + }) + await page.route(/\/vcs(?:\?.*)?$/, (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ branch: "feature", default_branch: "dev" }), + }), + ) + await page.route("**/vcs/diff**", (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify( + new URL(route.request().url()).searchParams.get("mode") === "branch" + ? [diff("src/alpha.ts"), diff("src/beta.ts")] + : [diff("src/alpha.ts"), diff("src/gamma.ts")], + ), + }), + ) + await page.addInitScript( + ({ directory, server, sessions }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem( + "opencode.global.dat:server", + JSON.stringify({ + projects: { local: [{ worktree: directory, expanded: true }] }, + lastProject: { local: directory }, + }), + ) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify(sessions.map((sessionId: string) => ({ type: "session", server, sessionId }))), + ) + }, + { directory, server, sessions: [sessionA, sessionB] }, + ) +} + +function session(id: string, title: string, created: number) { + return { + id, + slug: id, + projectID, + directory, + title, + version: "dev", + time: { created, updated: created }, + } +} + +function diff(file: string) { + return { + file, + additions: 1, + deletions: 1, + status: "modified", + patch: `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after'\n`, + } +} + +function sessionHref(sessionID: string) { + return `/server/${base64Encode(server)}/session/${sessionID}` +} diff --git a/packages/app/e2e/regression/review-terminal-stacked.spec.ts b/packages/app/e2e/regression/review-terminal-stacked.spec.ts index 1ba8ed9474cf..e3ba607b3d84 100644 --- a/packages/app/e2e/regression/review-terminal-stacked.spec.ts +++ b/packages/app/e2e/regression/review-terminal-stacked.spec.ts @@ -9,12 +9,19 @@ const title = "Review terminal stacked" const branchDiffs = [ fileDiff(".github/actions/setup-bun/action.yml", 7), ...Array.from({ length: 2_739 }, (_, index) => - fileDiff(`src/branch/generated-${String(index).padStart(4, "0")}.ts`, 100), + fileDiff( + `src/branch/d${String(Math.floor(index / 100)).padStart(5, "0")}/generated-${String(index).padStart(4, "0")}.ts`, + 100, + false, + ), ), ] test("keeps the review tree and terminal sized when both panels are open", async ({ page }) => { test.setTimeout(120_000) + const events: Array<{ directory: string; payload: Record }> = [] + let detailVersion = 1 + let detailFailures = 1 await page.setViewportSize({ width: 1400, height: 900 }) await mockOpenCodeServer(page, { directory, @@ -48,7 +55,10 @@ test("keeps the review tree and terminal sized when both panels are open", async time: { created: 1700000000000, updated: 1700000000000 }, }, ], + sessionStatus: { [sessionID]: { type: "idle" } }, pageMessages: () => ({ items: [] }), + events: () => events.splice(0, 1), + eventRetry: 16, }) await page.route(/\/vcs(?:\?.*)?$/, (route) => route.fulfill({ @@ -57,17 +67,25 @@ test("keeps the review tree and terminal sized when both panels are open", async body: JSON.stringify({ branch: "review-pane-performance", default_branch: "dev" }), }), ) - await page.route("**/vcs/diff**", (route) => - route.fulfill({ + await page.route("**/vcs/diff**", (route) => { + const url = new URL(route.request().url()) + const scope = url.searchParams.get("directory")?.replaceAll("\\", "/") + const detail = scope?.endsWith("/src/branch/d00027") + if (detail && detailFailures-- > 0) return route.fulfill({ status: 500, body: "retry detail" }) + return route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify( - new URL(route.request().url()).searchParams.get("mode") === "branch" - ? branchDiffs + url.searchParams.get("mode") === "branch" + ? detail + ? branchDiffs + .filter((diff) => diff.file.startsWith("src/branch/d00027/")) + .map((diff) => fileDiff(diff.file, diff.additions, true, detailVersion)) + : branchDiffs : Array.from({ length: 7 }, (_, index) => fileDiff(`src/git-${index}.ts`, 1)), ), - }), - ) + }) + }) await page.route("**/pty", (route) => route.fulfill({ status: 200, @@ -96,7 +114,7 @@ test("keeps the review tree and terminal sized when both panels are open", async await expect(page.getByRole("tab", { name: "Review 2740" })).toBeVisible() await page.keyboard.press("Control+Backquote") await expect(page.locator("#terminal-panel")).toBeVisible() - await expectTree(page, 2_745, "action.yml") + await expectTree(page, 2_773, "action.yml") await expectStackGeometry(page) const treeViewport = page.locator('#review-panel [data-slot="session-review-v2-sidebar-tree"] .scroll-view__viewport') @@ -113,41 +131,65 @@ test("keeps the review tree and terminal sized when both panels are open", async }) expect(bottomGap).toBeGreaterThanOrEqual(0) expect(bottomGap).toBeLessThanOrEqual(16) + const lazyDiff = page.waitForRequest((request) => { + const url = new URL(request.url()) + return ( + url.pathname === "/vcs/diff" && + url.searchParams.get("directory")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true + ) + }) + await lastFile.click() + await lazyDiff + const preview = page.locator('[data-slot="session-review-v2-diff-scroll"]') + await expect(preview).toContainText("after-1") + detailVersion = 2 + events.push(statusEvent("busy")) + await expect(page.getByRole("button", { name: "Stop" })).toBeVisible() + const refreshedDiff = page.waitForRequest((request) => { + const url = new URL(request.url()) + return ( + url.pathname === "/vcs/diff" && + url.searchParams.get("directory")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true + ) + }) + events.push(statusEvent("idle")) + await refreshedDiff + await expect(preview).toContainText("after-2") await selectMode(page, "Branch changes", "Git changes") await expectTree(page, 8, "git-0.ts") + await page.getByRole("button", { name: "git-0.ts" }).click() await selectMode(page, "Git changes", "Branch changes") - await expectTree(page, 2_745, "action.yml") + await expectTree(page, 2_773, "action.yml") const filter = page.getByRole("searchbox", { name: "Filter files" }) await filter.fill("generated-2738") await expectTree(page, 1, "generated-2738.ts") await filter.fill("") - await expectTree(page, 2_745, "action.yml") + await expectTree(page, 2_773, "action.yml") await page.getByRole("button", { name: "Toggle file tree" }).click() - await expect(page.locator('[data-slot="session-review-v2-sidebar"]')).toHaveAttribute("aria-hidden", "true") - await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(1) + await expect(page.locator('[data-slot="session-review-v2-sidebar"]')).toHaveCount(0) + await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(0) await page.getByRole("button", { name: "Toggle file tree" }).click() - await expectTree(page, 2_745, "action.yml") + await expectTree(page, 2_773, "action.yml") await page.keyboard.press("Control+Backquote") await expect(page.locator("#terminal-panel")).toHaveCount(0) - await expectTree(page, 2_745, "action.yml") + await expectTree(page, 2_773, "action.yml") await page.keyboard.press("Control+Backquote") await expect(page.locator("#terminal-panel")).toBeVisible() - await expectTree(page, 2_745, "action.yml") + await expectTree(page, 2_773, "action.yml") await page.getByRole("button", { name: "Toggle review" }).click() - await expect(page.locator("#review-panel")).toHaveAttribute("aria-hidden", "true") - await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(1) + await expect(page.locator("#review-panel")).toHaveCount(0) await page.getByRole("button", { name: "Toggle review" }).click() - await expectTree(page, 2_745, "action.yml") + await expectTree(page, 2_773, "action.yml") await page.setViewportSize({ width: 1_000, height: 700 }) - await expectTree(page, 2_745, "action.yml") + await expectTree(page, 2_773, "action.yml") await expectStackGeometry(page) await page.setViewportSize({ width: 1_000, height: 120 }) await page.setViewportSize({ width: 1_400, height: 900 }) - await expectTree(page, 2_745, "action.yml") + await expectTree(page, 2_773, "action.yml") await expectStackGeometry(page) }) @@ -201,12 +243,21 @@ function base64Encode(value: string) { return Buffer.from(value, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") } -function fileDiff(file: string, additions: number) { +function statusEvent(type: "busy" | "idle") { + return { + directory, + payload: { type: "session.status", properties: { sessionID, status: { type } } }, + } +} + +function fileDiff(file: string, additions: number, loaded = true, version = 1) { return { file, additions, deletions: 0, status: "modified", - patch: `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after'\n`, + patch: loaded + ? `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after-${version}'\n` + : `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}`, } } diff --git a/packages/app/e2e/utils/mock-server.ts b/packages/app/e2e/utils/mock-server.ts index e5df40c7d2de..e1946b8fba0b 100644 --- a/packages/app/e2e/utils/mock-server.ts +++ b/packages/app/e2e/utils/mock-server.ts @@ -22,6 +22,7 @@ export interface MockServerConfig { questions?: unknown[] | (() => unknown[]) fileList?: (path: string) => unknown | Promise fileContent?: (path: string) => unknown | Promise + findFiles?: (input: { query: string; dirs?: string; limit?: number }) => unknown sessionStatus?: unknown } @@ -66,6 +67,15 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { return json(route, await config.fileList(url.searchParams.get("path") ?? "")) if (path === "/file/content" && config.fileContent) return json(route, await config.fileContent(url.searchParams.get("path") ?? "")) + if (path === "/find/file" && config.findFiles) + return json( + route, + await config.findFiles({ + query: url.searchParams.get("query") ?? "", + dirs: url.searchParams.get("dirs") ?? undefined, + limit: url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined, + }), + ) if (path === "/api/reference") return json(route, { location: { diff --git a/packages/app/package.json b/packages/app/package.json index 7430a614bfaa..cc6f580a5b7c 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.17.15", + "version": "1.17.16", "description": "", "type": "module", "exports": { @@ -88,6 +88,7 @@ "shiki": "catalog:", "solid-js": "catalog:", "solid-list": "catalog:", + "solid-presence": "0.2.0", "tailwindcss": "catalog:" } } diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index f4cf14518c4c..86263d172d0a 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -9,9 +9,10 @@ import { Font } from "@opencode-ai/ui/font" import { Splash } from "@opencode-ai/ui/logo" import { ThemeProvider } from "@opencode-ai/ui/theme/context" import { MetaProvider } from "@solidjs/meta" -import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router" +import { type BaseRouterProps, Navigate, Route, Router, useNavigate, useParams, useSearchParams } from "@solidjs/router" import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" import { Effect } from "effect" +import { base64Encode } from "@opencode-ai/core/util/encode" import { type Component, createEffect, @@ -32,7 +33,7 @@ import { CommandProvider, useCommand, type CommandOption } from "@/context/comma import { CommentsProvider } from "@/context/comments" import { FileProvider } from "@/context/file" import { ServerSDKProvider } from "@/context/server-sdk" -import { ServerSyncProvider } from "@/context/server-sync" +import { ServerSyncProvider, useServerSync } from "@/context/server-sync" import { GlobalProvider, useGlobal } from "@/context/global" import { HighlightsProvider } from "@/context/highlights" import { LanguageProvider, type Locale, useLanguage } from "@/context/language" @@ -52,9 +53,10 @@ import LegacyLayout from "@/pages/layout" import NewLayout from "@/pages/layout-new" import { ErrorPage } from "./pages/error" import { useCheckServerHealth } from "./utils/server-health" -import { legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route" +import { legacySessionHref, legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route" +import { createSessionLineage } from "@/pages/session/session-lineage" -import { SessionPage, TargetSessionRouteContent } from "@/pages/session" +import { SessionPage, SessionRouteErrorBoundary, TargetSessionRouteContent } from "@/pages/session" import { NewHome, LegacyHome } from "@/pages/home" const NewSession = lazy(() => import("@/pages/new-session")) @@ -88,10 +90,14 @@ const SessionRoute = () => { tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt) }) - return + return ( + + + + ) } -const TargetSessionRoute = () => { +function TargetServerRoute(props: ParentProps) { const params = useParams<{ serverKey: string; id: string }>() const global = useGlobal() const conn = createMemo(() => { @@ -105,14 +111,47 @@ const TargetSessionRoute = () => { // re-resolves reactively instead); both rely on this key for server changes. - - - + {props.children} ) } +const TargetSessionRoute = () => ( + + + +) + +function LegacyTargetSessionRoute() { + const params = useParams<{ serverKey: string; id: string }>() + return ( + + + + + + ) +} + +function LegacyTargetSessionRedirect() { + const params = useParams<{ id: string }>() + const navigate = useNavigate() + const sync = useServerSync() + const current = createSessionLineage( + () => params.id, + () => sync().session.lineage, + ) + + createEffect(() => { + const directory = current()?.session.directory + if (!directory) return + navigate(legacySessionHref(directory, params.id), { replace: true }) + }) + + return null +} + // Wraps the non-draft routes. They are gated on (and keyed to) the globally selected // server via ServerKey, then provide the server-scoped shell (Permission/Layout/ // Notification/Models + the visual Layout) for that server. @@ -136,6 +175,7 @@ function LegacyServerLayout(props: ParentProps<{ serverScoped?: JSX.Element }>) function DraftRoute() { const [search] = useSearchParams<{ draftId?: string }>() + const settings = useSettings() const tabs = useTabs() return ( @@ -144,7 +184,14 @@ function DraftRoute() { keyed fallback={} > - {(draft) => } + {(draft) => ( + } + > + + + )} ) @@ -542,7 +589,14 @@ function Routes(props: { serverScoped?: JSX.Element }) { {routeProps.children} )} > - {} + + { + <> + + + + } + } /> @@ -550,15 +604,15 @@ function Routes(props: { serverScoped?: JSX.Element }) { - + + - ) } -function LegacyTargetSessionRoute() { +function NewLayoutLegacySessionRedirect() { const server = useServer() const tabs = useTabs() const params = useParams<{ id: string }>() diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index d7c6c2a84f47..905abe1e2dd3 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -20,14 +20,11 @@ import { uniqueCommandPaletteEntries, type CommandPaletteEntry, } from "./command-palette" +import { DialogCommandPaletteV2 } from "./dialog-command-palette-v2" const DialogSelectFileV2 = lazy(() => import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })), ) -const DialogCommandPaletteV2 = lazy(() => - import("./dialog-command-palette-v2").then((module) => ({ default: module.DialogCommandPaletteV2 })), -) - type DialogSelectFileMode = "all" | "files" export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) { diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx index 93ff46d5c121..718e2f3493de 100644 --- a/packages/app/src/components/file-tree.tsx +++ b/packages/app/src/components/file-tree.tsx @@ -201,6 +201,7 @@ export default function FileTree(props: { kinds?: ReadonlyMap draggable?: boolean onFileClick?: (file: FileNode) => void + onFileDoubleClick?: (file: FileNode) => void _filter?: Filter _marks?: Set @@ -440,6 +441,7 @@ export default function FileTree(props: { active={props.active} draggable={props.draggable} onFileClick={props.onFileClick} + onFileDoubleClick={props.onFileDoubleClick} _filter={filter()} _marks={marks()} _deeps={deeps()} @@ -462,6 +464,7 @@ export default function FileTree(props: { as="button" type="button" onClick={() => props.onFileClick?.(node)} + onDblClick={() => props.onFileDoubleClick?.(node)} >
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 75ec3b7be52b..92aefd97175f 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -520,7 +520,7 @@ export const PromptInput: Component = (props) => { const setMode = (mode: "normal" | "shell") => { setStore("mode", mode) - setStore("popover", null) + setStore({ popover: null, slashMenu: false, slashMenuQuery: "" }) requestAnimationFrame(() => editorRef?.focus()) } @@ -554,7 +554,7 @@ export const PromptInput: Component = (props) => { }, ]) - const closePopover = () => setStore("popover", null) + const closePopover = () => setStore({ popover: null, slashMenu: false, slashMenuQuery: "" }) const resetHistoryNavigation = (force = false) => { if (!force && (store.historyIndex < 0 || store.applyingHistory)) return @@ -800,17 +800,30 @@ export const PromptInput: Component = (props) => { const handleSlashSelect = (cmd: SlashCommand | undefined) => { if (!cmd) return + const menu = store.slashMenu closePopover() const images = imageAttachments() if (cmd.type === "custom") { const text = `/${cmd.trigger} ` + if (menu) { + editorRef.focus() + setCursorPosition(editorRef, 0) + addPart({ type: "text", content: text, start: 0, end: text.length }) + focusEditorEnd() + return + } setEditorText(text) prompt.set([{ type: "text", content: text, start: 0, end: text.length }, ...images], text.length) focusEditorEnd() return } + if (menu) { + command.trigger(cmd.id, "slash") + return + } + clearEditor() prompt.set([...DEFAULT_PROMPT, ...images], 0) command.trigger(cmd.id, "slash") @@ -1072,10 +1085,10 @@ export const PromptInput: Component = (props) => { if (atMatch) { atOnInput(atMatch[1]) - setStore("popover", "at") + setStore({ popover: "at", slashMenu: false, slashMenuQuery: "" }) } else if (slashMatch) { slashOnInput(slashMatch[1]) - setStore("popover", "slash") + setStore({ popover: "slash", slashMenu: false, slashMenuQuery: "" }) } else { closePopover() } @@ -1171,6 +1184,28 @@ export const PromptInput: Component = (props) => { return true } + const openCommands = () => { + const populated = prompt.dirty() || commentCount() > 0 + requestAnimationFrame(() => { + if (!populated) { + if (!addPart({ type: "text", content: "/", start: 0, end: 0 })) return + slashOnInput("") + setStore({ popover: "slash", slashMenu: false, slashMenuQuery: "" }) + return + } + slashOnInput("") + setStore({ popover: "slash", slashMenu: true, slashMenuQuery: "" }) + }) + } + + const openContext = () => { + requestAnimationFrame(() => { + if (!addPart({ type: "text", content: "@", start: 0, end: 0 })) return + atOnInput("") + setStore({ popover: "at", slashMenu: false, slashMenuQuery: "" }) + }) + } + const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => { history.add(prompt, mode, mode === "shell" ? [] : historyComments()) } @@ -1199,7 +1234,7 @@ export const PromptInput: Component = (props) => { } setStore("mode", "normal") - setStore("popover", null) + closePopover() setStore("historyIndex", -1) setStore("savedPrompt", null) prompt.set(edit.prompt, promptLength(edit.prompt)) @@ -1286,7 +1321,10 @@ export const PromptInput: Component = (props) => { resetHistoryNavigation(true) }, setMode: (mode) => setStore("mode", mode), - setPopover: (popover) => setStore("popover", popover), + setPopover: (popover) => { + if (!popover) return closePopover() + setStore({ popover, slashMenu: false, slashMenuQuery: "" }) + }, newSessionWorktree: () => props.newSessionWorktree, onNewSessionWorktreeReset: props.onNewSessionWorktreeReset, shouldQueue: props.shouldQueue, @@ -1325,7 +1363,7 @@ export const PromptInput: Component = (props) => { const cursorPosition = getCursorPosition(editorRef) if (cursorPosition === 0) { setStore("mode", "shell") - setStore("popover", null) + closePopover() event.preventDefault() return } @@ -1460,6 +1498,29 @@ export const PromptInput: Component = (props) => { } } + const handleSlashMenuKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + closePopover() + requestAnimationFrame(() => editorRef.focus()) + event.preventDefault() + return + } + + if (event.key === "Tab") { + selectPopoverActive() + event.preventDefault() + return + } + + const ctrl = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey + const nav = event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter" + const ctrlNav = ctrl && (event.key === "n" || event.key === "p") + if (!nav && !ctrlNav) return + slashOnKeyDown(event) + if (event.key === "ArrowUp" || event.key === "ArrowDown" || ctrlNav) scrollSlashActiveIntoView() + event.preventDefault() + } + const agentsLoading = () => props.controls.agents.loading const agentsShouldFadeIn = createMemo((prev) => prev ?? agentsLoading()) const providersLoading = () => props.controls.model.loading @@ -1527,6 +1588,13 @@ export const PromptInput: Component = (props) => { slashActive={slashActive() ?? undefined} setSlashActive={setSlashActive} onSlashSelect={handleSlashSelect} + slashMenu={store.slashMenu} + slashMenuQuery={store.slashMenuQuery} + onSlashMenuInput={(value) => { + setStore("slashMenuQuery", value) + slashOnInput(value) + }} + onSlashMenuKeyDown={handleSlashMenuKeyDown} commandKeybind={command.keybind} commandKeybindParts={command.keybindParts} newLayoutDesigns={props.controls.newLayoutDesigns} @@ -1625,23 +1693,45 @@ export const PromptInput: Component = (props) => { placement="top" value={ <> - {language.t("prompt.action.attachFile")} + {language.t("prompt.menu.addImagesAndFiles")} } > - + + + + + + {language.t("prompt.menu.imagesAndFiles")} + + + + {language.t("prompt.menu.commands")} + + + {language.t("prompt.menu.context")} + + setMode("shell")} shortcut="!"> + {language.t("prompt.menu.shellCommand")} + + + + diff --git a/packages/app/src/components/prompt-input/slash-popover.tsx b/packages/app/src/components/prompt-input/slash-popover.tsx index a84cbcca0894..b9a2dc7afaf3 100644 --- a/packages/app/src/components/prompt-input/slash-popover.tsx +++ b/packages/app/src/components/prompt-input/slash-popover.tsx @@ -41,6 +41,10 @@ type PromptPopoverProps = { slashActive?: string setSlashActive: (id: string) => void onSlashSelect: (item: SlashCommand) => void + slashMenu: boolean + slashMenuQuery: string + onSlashMenuInput: (value: string) => void + onSlashMenuKeyDown: (event: KeyboardEvent) => void commandKeybind: (id: string) => string | undefined commandKeybindParts: (id: string) => string[] newLayoutDesigns: boolean @@ -254,6 +258,20 @@ export const PromptPopover: Component = (props) => { + +
+ requestAnimationFrame(() => el.focus())} + value={props.slashMenuQuery} + onInput={(event) => props.onSlashMenuInput(event.currentTarget.value)} + onKeyDown={props.onSlashMenuKeyDown} + onMouseDown={(event) => event.stopPropagation()} + aria-label={props.t("prompt.menu.commands")} + placeholder="/" + class="w-full bg-transparent outline-none text-[13px] leading-5 text-v2-text-text-base placeholder:text-v2-text-text-faint" + /> +
+
0} fallback={ diff --git a/packages/app/src/components/prompt-input/transient-state.ts b/packages/app/src/components/prompt-input/transient-state.ts index c8abf9a76683..eaf35476d1ad 100644 --- a/packages/app/src/components/prompt-input/transient-state.ts +++ b/packages/app/src/components/prompt-input/transient-state.ts @@ -4,6 +4,8 @@ import type { PromptHistoryEntry } from "./history" export type PromptInputTransientState = { popover: "at" | "slash" | null + slashMenu: boolean + slashMenuQuery: string historyIndex: number savedPrompt: PromptHistoryEntry | null placeholder: number @@ -16,6 +18,8 @@ export type PromptInputTransientState = { function resetPromptInputTransientState(setStore: SetStoreFunction) { setStore({ popover: null, + slashMenu: false, + slashMenuQuery: "", historyIndex: -1, savedPrompt: null, draggingType: null, @@ -28,6 +32,8 @@ function resetPromptInputTransientState(setStore: SetStoreFunction, placeholder: number) { const [store, setStore] = createStore({ popover: null, + slashMenu: false, + slashMenuQuery: "", historyIndex: -1, savedPrompt: null, placeholder, diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index 7ac1aa9965d6..fd2d1dd1a30e 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -4,6 +4,7 @@ import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2" import { Button } from "@opencode-ai/ui/button" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" +import { createMediaQuery } from "@solid-primitives/media" import { useFile } from "@/context/file" import { useLayout } from "@/context/layout" @@ -14,6 +15,7 @@ import { useSDK } from "@/context/sdk" import { getSessionContext, getSessionTokenTotal } from "@/components/session/session-context-metrics" import { useSessionLayout } from "@/pages/session/session-layout" import { createSessionTabs } from "@/pages/session/helpers" +import { useSettings } from "@/context/settings" interface SessionContextUsageProps { variant?: "button" | "indicator" @@ -47,8 +49,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) { const layout = useLayout() const language = useLanguage() const sdk = useSDK() + const settings = useSettings() const providers = useProviders(() => sdk().directory) const { params, tabs, view } = useSessionLayout() + const isDesktop = createMediaQuery("(min-width: 768px)") const variant = createMemo(() => props.variant ?? "button") const buttonAppearance = createMemo(() => props.buttonAppearance ?? "default") @@ -56,6 +60,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { tabs, pathFromTab: file.pathFromTab, normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab), + fileBrowser: () => settings.general.newLayoutDesigns() && isDesktop() && !!params.id, }) const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : [])) const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined)) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 500cce7def13..da1770cd130e 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -24,6 +24,7 @@ import { focusTerminalById } from "@/pages/session/helpers" import { useSessionLayout } from "@/pages/session/session-layout" import { messageAgentColor } from "@/utils/agent" import { decode64 } from "@/utils/base64" +import { fileManagerApp } from "@/utils/file-manager" import { Persist, persisted } from "@/utils/persist" import { StatusPopover, StatusPopoverV2 } from "../status-popover" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" @@ -175,11 +176,7 @@ export function SessionHeader() { return LINUX_APPS }) - const fileManager = createMemo(() => { - if (os() === "macos") return { label: "session.header.open.finder", icon: "finder" as const } - if (os() === "windows") return { label: "session.header.open.fileExplorer", icon: "file-explorer" as const } - return { label: "session.header.open.fileManager", icon: "finder" as const } - }) + const fileManager = createMemo(() => fileManagerApp(os())) createEffect(() => { if (platform.platform !== "desktop") return diff --git a/packages/app/src/components/session/session-sortable-tab.tsx b/packages/app/src/components/session/session-sortable-tab.tsx index f04228ca66c7..d78f392941ac 100644 --- a/packages/app/src/components/session/session-sortable-tab.tsx +++ b/packages/app/src/components/session/session-sortable-tab.tsx @@ -10,7 +10,7 @@ import { useFile } from "@/context/file" import { useLanguage } from "@/context/language" import { useCommand } from "@/context/command" -export function FileVisual(props: { path: string; active?: boolean }): JSX.Element { +export function FileVisual(props: { path: string; active?: boolean; temporary?: boolean }): JSX.Element { return (
- {getFilename(props.path)} + + {getFilename(props.path)} +
) } -export function SortableTab(props: { tab: string; onTabClose: (tab: string) => void }): JSX.Element { +export function SortableTab(props: { + tab: string + temporary?: boolean + onTabClose: (tab: string) => void + onTabDoubleClick?: (tab: string) => void +}): JSX.Element { const file = useFile() const language = useLanguage() const command = useCommand() @@ -36,7 +43,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v const content = createMemo(() => { const value = path() if (!value) return - return + return }) return (
@@ -61,6 +68,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v } hideCloseButton onMiddleClick={() => props.onTabClose(props.tab)} + onDblClick={() => props.onTabDoubleClick?.(props.tab)} > {(value) => value()} diff --git a/packages/app/src/components/settings-dialog.tsx b/packages/app/src/components/settings-dialog.tsx index d2cc85c04e57..46dd89ade817 100644 --- a/packages/app/src/components/settings-dialog.tsx +++ b/packages/app/src/components/settings-dialog.tsx @@ -4,7 +4,7 @@ import { useCommand } from "@/context/command" import { useLanguage } from "@/context/language" import { useDialog } from "@opencode-ai/ui/context/dialog" -export function useSettingsDialog() { +export function useSettingsDialog(defaultValue?: string) { const dialog = useDialog() const params = useParams<{ id?: string }>() let run = 0 @@ -19,7 +19,7 @@ export function useSettingsDialog() { const sessionID = params.id void import("@/components/settings-v2").then((module) => { if (dead || run !== current) return - void dialog.show(() => ) + void dialog.show(() => ) }) } } diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx index f7668c19492b..6032b81dde70 100644 --- a/packages/app/src/context/file.tsx +++ b/packages/app/src/context/file.tsx @@ -203,12 +203,15 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({ return promise } - const search = (query: string, dirs: "true" | "false") => + const search = (query: string, dirs: "true" | "false", options?: { limit?: number; signal?: AbortSignal }) => sdk() - .client.find.files({ query, dirs }) + .client.find.files({ query, dirs, limit: options?.limit }, { signal: options?.signal }) .then( (x) => (x.data ?? []).map(path.normalize), - () => [], + (error) => { + if (options?.signal?.aborted) throw error + return [] + }, ) const stop = sdk().event.listen((e) => { @@ -284,7 +287,8 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({ setScrollLeft, selectedLines, setSelectedLines, - searchFiles: (query: string) => search(query, "false"), + searchFiles: (query: string, options?: { limit?: number; signal?: AbortSignal }) => + search(query, "false", options), searchFilesAndDirectories: (query: string) => search(query, "true"), } }, diff --git a/packages/app/src/context/layout-tabs.test.ts b/packages/app/src/context/layout-tabs.test.ts new file mode 100644 index 000000000000..646ed086cef9 --- /dev/null +++ b/packages/app/src/context/layout-tabs.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, test } from "bun:test" +import { + SESSION_OPEN_FILE_TAB, + closeSessionTab, + openSessionTab, + previewSessionTab, + type SessionTabState, +} from "./layout-tabs" + +const state = (all: string[], active?: string, preview?: string): SessionTabState => ({ + tabs: { all, active }, + preview, +}) + +describe("previewSessionTab", () => { + test("appends the Open File placeholder", () => { + expect(previewSessionTab(state(["file://a.ts"], "file://a.ts"), SESSION_OPEN_FILE_TAB)).toEqual( + state(["file://a.ts", SESSION_OPEN_FILE_TAB], SESSION_OPEN_FILE_TAB, SESSION_OPEN_FILE_TAB), + ) + }) + + test("replaces the current preview in place", () => { + expect( + previewSessionTab( + state(["context", SESSION_OPEN_FILE_TAB, "file://b.ts"], SESSION_OPEN_FILE_TAB, SESSION_OPEN_FILE_TAB), + "file://a.ts", + ), + ).toEqual(state(["context", "file://a.ts", "file://b.ts"], "file://a.ts", "file://a.ts")) + }) + + test("activates a durable tab without duplicating it", () => { + expect( + previewSessionTab( + state(["file://a.ts", SESSION_OPEN_FILE_TAB, "file://b.ts"], SESSION_OPEN_FILE_TAB, SESSION_OPEN_FILE_TAB), + "file://b.ts", + ), + ).toEqual(state(["file://a.ts", "file://b.ts"], "file://b.ts")) + }) + + test("replaces a restored Open File placeholder", () => { + expect( + previewSessionTab(state(["file://a.ts", SESSION_OPEN_FILE_TAB], SESSION_OPEN_FILE_TAB), "file://b.ts"), + ).toEqual(state(["file://a.ts", "file://b.ts"], "file://b.ts", "file://b.ts")) + }) +}) + +describe("openSessionTab", () => { + test("pins the current preview", () => { + expect(openSessionTab(state(["file://a.ts"], "file://a.ts", "file://a.ts"), "file://a.ts")).toEqual( + state(["file://a.ts"], "file://a.ts"), + ) + }) + + test("replaces a preview with a directly opened file", () => { + expect(openSessionTab(state(["file://a.ts"], "file://a.ts", "file://a.ts"), "file://b.ts")).toEqual( + state(["file://b.ts"], "file://b.ts"), + ) + }) + + test("keeps the preview when switching to Review", () => { + expect(openSessionTab(state(["file://a.ts"], "file://a.ts", "file://a.ts"), "review")).toEqual( + state(["file://a.ts"], "review", "file://a.ts"), + ) + }) + + test("replaces a restored Open File placeholder with a direct open", () => { + expect(openSessionTab(state(["file://a.ts", SESSION_OPEN_FILE_TAB], SESSION_OPEN_FILE_TAB), "file://b.ts")).toEqual( + state(["file://a.ts", "file://b.ts"], "file://b.ts"), + ) + }) +}) + +describe("closeSessionTab", () => { + test("clears preview metadata and selects the left neighbor", () => { + expect( + closeSessionTab( + state(["file://a.ts", "file://b.ts", "file://c.ts"], "file://b.ts", "file://b.ts"), + "file://b.ts", + ), + ).toEqual(state(["file://a.ts", "file://c.ts"], "file://a.ts")) + }) +}) diff --git a/packages/app/src/context/layout-tabs.ts b/packages/app/src/context/layout-tabs.ts new file mode 100644 index 000000000000..ea4c675ff4ec --- /dev/null +++ b/packages/app/src/context/layout-tabs.ts @@ -0,0 +1,103 @@ +export const SESSION_OPEN_FILE_TAB = "open-file" + +export type SessionTabs = { + active?: string + all: string[] +} + +export type SessionTabState = { + tabs: SessionTabs + preview?: string +} + +const sessionTabPreview = (current: SessionTabState) => + current.preview ?? (current.tabs.all.includes(SESSION_OPEN_FILE_TAB) ? SESSION_OPEN_FILE_TAB : undefined) + +export function previewSessionTab(current: SessionTabState, tab: string): SessionTabState { + const preview = sessionTabPreview(current) + const previewIndex = preview ? current.tabs.all.indexOf(preview) : -1 + const existingIndex = current.tabs.all.indexOf(tab) + + if (existingIndex !== -1) { + if (previewIndex === -1 || preview === tab) { + return { tabs: { all: current.tabs.all, active: tab }, preview: preview === tab ? tab : undefined } + } + return { + tabs: { all: current.tabs.all.filter((item) => item !== preview), active: tab }, + } + } + + if (previewIndex === -1) { + return { tabs: { all: [...current.tabs.all, tab], active: tab }, preview: tab } + } + + return { + tabs: { + all: current.tabs.all.map((item, index) => (index === previewIndex ? tab : item)), + active: tab, + }, + preview: tab, + } +} + +export function openSessionTab(current: SessionTabState, tab: string): SessionTabState { + const preview = sessionTabPreview(current) + if (tab === "review") { + return { + tabs: { all: current.tabs.all.filter((item) => item !== tab), active: tab }, + preview, + } + } + + if (tab === "context") { + return { + tabs: { all: [tab, ...current.tabs.all.filter((item) => item !== tab)], active: tab }, + preview, + } + } + + const previewIndex = preview ? current.tabs.all.indexOf(preview) : -1 + const existingIndex = current.tabs.all.indexOf(tab) + if (existingIndex !== -1) { + if (previewIndex === -1 || preview === tab) { + return { tabs: { all: current.tabs.all, active: tab } } + } + return { + tabs: { all: current.tabs.all.filter((item) => item !== preview), active: tab }, + } + } + + if (previewIndex === -1) { + return { tabs: { all: [...current.tabs.all, tab], active: tab } } + } + + return { + tabs: { + all: current.tabs.all.map((item, index) => (index === previewIndex ? tab : item)), + active: tab, + }, + } +} + +export function closeSessionTab(current: SessionTabState, tab: string): SessionTabState { + if (tab === "review") { + if (current.tabs.active !== tab) return current + return { + tabs: { all: current.tabs.all, active: current.tabs.all[0] }, + preview: current.preview, + } + } + + const all = current.tabs.all.filter((item) => item !== tab) + const preview = current.preview === tab ? undefined : current.preview + if (current.tabs.active !== tab) return { tabs: { ...current.tabs, all }, preview } + + const index = current.tabs.all.indexOf(tab) + return { + tabs: { + all, + active: current.tabs.all[index - 1] ?? current.tabs.all[index + 1] ?? all[0], + }, + preview, + } +} diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index b09a77eb339a..dcac3e778104 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -19,6 +19,7 @@ import { migrateLegacySessionStateKeys, ServerScope, SessionStateKey } from "@/u import { createSessionKeyReader, ensureSessionKey, pruneSessionKeys } from "./layout-helpers" import { requireServerKey } from "@/utils/session-route" import { type DraftTab, useTabs } from "./tabs" +import { closeSessionTab, openSessionTab, previewSessionTab, type SessionTabs } from "./layout-tabs" export { createSessionKeyReader, ensureSessionKey, pruneSessionKeys } @@ -55,14 +56,11 @@ export function getProjectAvatarVariant(key?: string): ProjectAvatarVariant { return "gray" } -type SessionTabs = { - active?: string - all: string[] -} - type SessionView = { scroll: Record reviewOpen?: string[] + reviewMode?: ReviewChangeMode + reviewFile?: string pendingMessage?: string pendingMessageAt?: number todoCollapsed?: boolean @@ -79,6 +77,7 @@ export type LocalProject = Partial & { worktree: string; expanded: bool export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string } export type ReviewDiffStyle = "unified" | "split" +export type ReviewChangeMode = "git" | "branch" | "turn" export type ReviewPanelSource = "context-button" | "other" export type LayoutRoute = @@ -87,14 +86,6 @@ export type LayoutRoute = | { type: "dir-new-sesssion"; dir: string; dirBase64: string; server?: ServerConnection.Key } | { type: "session"; sessionId: string; server?: ServerConnection.Key } -function nextSessionTabsForOpen(current: SessionTabs | undefined, tab: string): SessionTabs { - const all = current?.all ?? [] - if (tab === "review") return { all: all.filter((x) => x !== "review"), active: tab } - if (tab === "context") return { all: [tab, ...all.filter((x) => x !== tab)], active: tab } - if (!all.includes(tab)) return { all: [...all, tab], active: tab } - return { all, active: tab } -} - const sessionPath = (key: string) => { const dir = SessionStateKey.route(key).split("/")[0] if (!dir) return @@ -308,6 +299,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( ) const [ephemeral, setEphemeral] = createStore({ reviewPanelSource: "other" as ReviewPanelSource, + sessionTabPreview: {} as Record, }) const MAX_SESSION_KEYS = 50 @@ -366,6 +358,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( scroll.drop(drop) dropSessionState(drop) + setEphemeral( + "sessionTabPreview", + produce((draft) => { + for (const key of drop) delete draft[key] + }), + ) for (const key of drop) { usage.used.delete(key) @@ -791,6 +789,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( view(sessionKey: string | Accessor) { const key = createSessionKeyReader(sessionKey, ensureKey) const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} }) + const reviewMode = createMemo(() => { + const mode = s().reviewMode + if (mode === "git" || mode === "branch" || mode === "turn") return mode + }) + const reviewFile = createMemo(() => { + const file = s().reviewFile + if (typeof file === "string") return file + }) const terminalOpened = createMemo(() => store.terminal?.opened ?? false) const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED) const reviewPanelSource = createMemo(() => (reviewPanelOpened() ? ephemeral.reviewPanelSource : "other")) @@ -874,6 +880,32 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, }, review: { + mode: reviewMode, + setMode(mode: ReviewChangeMode) { + const session = key() + const current = store.sessionView[session] + if (!current) { + setStore("sessionView", session, { scroll: {}, reviewMode: mode }) + prune(session) + return + } + if (current.reviewMode === mode) return + setStore("sessionView", session, "reviewMode", mode) + prune(session) + }, + file: reviewFile, + setFile(file: string) { + const session = key() + const current = store.sessionView[session] + if (!current) { + setStore("sessionView", session, { scroll: {}, reviewFile: file }) + prune(session) + return + } + if (current.reviewFile === file) return + setStore("sessionView", session, "reviewFile", file) + prune(session) + }, open: createMemo(() => s().reviewOpen ?? []), setOpen(open: string[]) { const session = key() @@ -945,10 +977,17 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] }) const normalize = (tab: string) => normalizeSessionTab(path(), tab) const normalizeAll = (all: string[]) => normalizeSessionTabList(path(), all) + const apply = (session: string, next: ReturnType) => { + batch(() => { + setStore("sessionTabs", session, next.tabs) + setEphemeral("sessionTabPreview", session, next.preview) + }) + } return { tabs, active: createMemo(() => tabs().active), all: createMemo(() => tabs().all.filter((tab) => tab !== "review")), + preview: createMemo(() => ephemeral.sessionTabPreview[key()]), setActive(tab: string | undefined) { const session = key() const next = tab ? normalize(tab) : tab @@ -961,40 +1000,44 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( setAll(all: string[]) { const session = key() const next = normalizeAll(all).filter((tab) => tab !== "review") - if (!store.sessionTabs[session]) { - setStore("sessionTabs", session, { all: next, active: undefined }) - } else { - setStore("sessionTabs", session, "all", next) - } + batch(() => { + if (!store.sessionTabs[session]) { + setStore("sessionTabs", session, { all: next, active: undefined }) + } else { + setStore("sessionTabs", session, "all", next) + } + const preview = ephemeral.sessionTabPreview[session] + if (preview && !next.includes(preview)) setEphemeral("sessionTabPreview", session, undefined) + }) }, async open(tab: string) { const session = key() - const next = nextSessionTabsForOpen(store.sessionTabs[session], normalize(tab)) - setStore("sessionTabs", session, next) + apply( + session, + openSessionTab( + { tabs: store.sessionTabs[session] ?? { all: [] }, preview: ephemeral.sessionTabPreview[session] }, + normalize(tab), + ), + ) + }, + previewTab(tab: string) { + const session = key() + apply( + session, + previewSessionTab( + { tabs: store.sessionTabs[session] ?? { all: [] }, preview: ephemeral.sessionTabPreview[session] }, + normalize(tab), + ), + ) }, close(tab: string) { const session = key() const current = store.sessionTabs[session] if (!current) return - - if (tab === "review") { - if (current.active !== tab) return - setStore("sessionTabs", session, "active", current.all[0]) - return - } - - const all = current.all.filter((x) => x !== tab) - if (current.active !== tab) { - setStore("sessionTabs", session, "all", all) - return - } - - const index = current.all.findIndex((f) => f === tab) - const next = current.all[index - 1] ?? current.all[index + 1] ?? all[0] - batch(() => { - setStore("sessionTabs", session, "all", all) - setStore("sessionTabs", session, "active", next) - }) + apply( + session, + closeSessionTab({ tabs: current, preview: ephemeral.sessionTabPreview[session] }, normalize(tab)), + ) }, move(tab: string, to: number) { const session = key() diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index a5174a59000e..2b639eded7d1 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -21,6 +21,9 @@ export const dict = { "theme.scheme.dark": "داكن", "command.sidebar.toggle": "تبديل الشريط الجانبي", "command.project.open": "فتح مشروع", + "command.project.previous": "المشروع السابق", + "command.project.next": "المشروع التالي", + "command.project.index": "التبديل إلى المشروع {{index}}", "command.provider.connect": "اتصال بموفر", "command.server.switch": "تبديل الخادم", "command.settings.open": "فتح الإعدادات", @@ -39,6 +42,7 @@ export const dict = { "command.session.new": "جلسة جديدة", "command.file.open": "فتح ملف", "command.tab.close": "إغلاق علامة التبويب", + "command.tab.reopenClosed": "إعادة فتح علامة التبويب المغلقة", "command.context.addSelection": "إضافة التحديد إلى السياق", "command.context.addSelection.description": "إضافة الأسطر المحددة من الملف الحالي", "command.input.focus": "التركيز على حقل الإدخال", @@ -193,6 +197,11 @@ export const dict = { "model.input.video": "فيديو", "model.input.pdf": "pdf", "model.tooltip.allows": "يسمح: {{inputs}}", + "model.tooltip.context.label": "السياق", + "model.tooltip.inputs": "المدخلات", + "model.tooltip.model": "النموذج", + "model.tooltip.provider": "الموفر", + "model.tooltip.reasoning": "الاستنتاج", "model.tooltip.reasoning.allowed": "يسمح بالاستنتاج", "model.tooltip.reasoning.none": "بدون استنتاج", "model.tooltip.context": "حد السياق {{limit}}", @@ -202,6 +211,7 @@ export const dict = { "common.loading": "جارٍ التحميل", "common.loading.ellipsis": "...", "common.cancel": "إلغاء", + "common.clear": "مسح", "common.connect": "اتصال", "common.disconnect": "قطع الاتصال", "common.continue": "إرسال", @@ -218,6 +228,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc للخروج", + "session.child.promptDisabled": "لا يمكن إرسال موجّهات إلى جلسات الوكلاء الفرعيين.", + "session.child.backToParent": "العودة إلى الجلسة الرئيسية.", "prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية", "prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟", "prompt.example.3": "إصلاح الاختبارات المعطلة", @@ -255,6 +267,11 @@ export const dict = { "prompt.context.removeActiveFile": "إزالة الملف النشط من السياق", "prompt.context.removeFile": "إزالة الملف من السياق", "prompt.action.attachFile": "إرفاق ملف", + "prompt.menu.addImagesAndFiles": "إضافة ملفات والمزيد", + "prompt.menu.imagesAndFiles": "الصور والملفات", + "prompt.menu.commands": "الأوامر", + "prompt.menu.context": "السياق", + "prompt.menu.shellCommand": "أمر shell", "prompt.attachment.remove": "إزالة المرفق", "prompt.action.send": "إرسال", "prompt.action.stop": "توقف", @@ -314,6 +331,74 @@ export const dict = { "dialog.server.menu.delete": "حذف", "dialog.server.current": "الخادم الحالي", "dialog.server.status.default": "افتراضي", + "wsl.server.add": "إضافة خادم WSL", + "wsl.server.addShort": "إضافة WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "خادم WSL", + "wsl.server.retryStart": "إعادة محاولة التشغيل", + "wsl.server.updating": "جارٍ التحديث...", + "wsl.onboarding.step.distro": "اختر توزيعة", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "جارٍ التحقق من WSL...", + "wsl.onboarding.restartRequired": "يحتاج Windows إلى إعادة التشغيل لإكمال تثبيت WSL.", + "wsl.onboarding.ready": "WSL جاهز.", + "wsl.onboarding.required": "يلزم WSL للمتابعة.", + "wsl.onboarding.checkingDistros": "جارٍ التحقق من التوزيعات...", + "wsl.onboarding.installingDistro": "جارٍ تثبيت {{distro}}...", + "wsl.onboarding.checkingDistro": "جارٍ التحقق من {{distro}}...", + "wsl.onboarding.listingDistros": "جارٍ سرد التوزيعات...", + "wsl.onboarding.distroReady": "{{distro}} جاهزة.", + "wsl.onboarding.distroNotInstalled": "لم يتم تثبيت {{distro}} بعد.", + "wsl.onboarding.openDistroOnce": "افتح {{distro}} مرة واحدة لإكمال الإعداد.", + "wsl.onboarding.finishingDistro": "جارٍ إكمال إعداد {{distro}}.", + "wsl.onboarding.pickDistro": "اختر توزيعة أو ثبّت واحدة أدناه.", + "wsl.onboarding.checkingOpencode": "جارٍ التحقق من OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "جارٍ التحقق من OpenCode في {{distro}}...", + "wsl.onboarding.updatingOpencode": "جارٍ تحديث OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "جارٍ تحديث OpenCode في {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "حدّث OpenCode في {{distro}}.", + "wsl.onboarding.updateOpencode": "تحديث OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode جاهز في {{distro}}.", + "wsl.onboarding.opencodeReady": "OpenCode جاهز.", + "wsl.onboarding.installOpencodeIn": "ثبّت OpenCode في {{distro}}.", + "wsl.onboarding.installOpencode": "تثبيت OpenCode", + "wsl.onboarding.chooseDistroFirst": "اختر توزيعة أولاً.", + "wsl.onboarding.loadFailed": "فشل تحميل حالة WSL.", + "wsl.onboarding.loading": "جارٍ التحميل...", + "wsl.onboarding.installedDistros": "التوزيعات المثبتة", + "wsl.onboarding.checkAgain": "تحقق مجددًا", + "wsl.onboarding.distroStatus.ready": "جاهزة", + "wsl.onboarding.distroStatus.checking": "جارٍ التحقق...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode غير مثبت", + "wsl.onboarding.distroStatus.missingTools": "bash و curl مفقودان", + "wsl.onboarding.distroStatus.unsupported": "غير مدعومة · استخدم WSL 2", + "wsl.onboarding.needAnotherDistro": "هل تحتاج إلى توزيعة أخرى؟", + "wsl.onboarding.needAnotherDistroHint": "ثبّت توزيعة Linux من كتالوج WSL", + "wsl.onboarding.wslNotInstalled.title": "WSL غير مثبت", + "wsl.onboarding.wslNotInstalled.description": + "يلزم WSL (نظام Windows الفرعي لـ Linux) قبل أن يتمكن OpenCode من إضافة خادم WSL", + "wsl.onboarding.wslUnavailable.title": "WSL غير متاح", + "wsl.onboarding.wslUnavailable.description": "تعذر على OpenCode التحقق من WSL على هذا الجهاز.", + "wsl.onboarding.installWsl": "تثبيت WSL", + "wsl.onboarding.windowsRestartRequired": "أعد تشغيل Windows لإكمال تثبيت WSL، ثم أعد فتح OpenCode.", + "wsl.onboarding.next": "التالي", + "wsl.onboarding.refresh": "تحديث", + "wsl.onboarding.allDistrosAdded": "تمت إضافة جميع التوزيعات المثبتة بالفعل.", + "wsl.onboarding.noDistros": "لم يتم اكتشاف أي توزيعات بعد.", + "wsl.onboarding.install": "تثبيت", + "wsl.onboarding.installing": "جارٍ التثبيت...", + "wsl.onboarding.installDistro": "تثبيت توزيعة", + "wsl.onboarding.searchDistros": "البحث عن توزيعات", + "wsl.onboarding.wsl2Required": "يلزم WSL 2.", + "wsl.onboarding.toolsRequired": "تحتاج هذه التوزيعة إلى bash و curl.", + "wsl.onboarding.openTerminal": "فتح المحطة الطرفية", + "wsl.onboarding.path": "المسار: {{path}}", + "wsl.onboarding.notFound": "غير موجود", + "wsl.onboarding.version": "الإصدار: {{version}}", + "wsl.onboarding.unknown": "غير معروف", + "wsl.onboarding.desktopVersion": "سطح المكتب {{version}}", + "wsl.onboarding.versionMismatch": "لا يتطابق الإصدار المثبت مع إصدار تطبيق سطح المكتب.", + "wsl.onboarding.adding": "جارٍ الإضافة...", "dialog.project.edit.title": "تحرير المشروع", "dialog.project.edit.name": "الاسم", "dialog.project.edit.icon": "أيقونة", @@ -370,6 +455,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", "toast.language.title": "لغة", @@ -406,10 +492,12 @@ export const dict = { "toast.update.action.notYet": "ليس الآن", "error.page.title": "حدث خطأ ما", "error.page.description": "حدث خطأ أثناء تحميل التطبيق.", + "error.page.description.localServerStartup": "حدث خطأ أثناء بدء تشغيل الخادم المحلي.", "error.page.details.label": "تفاصيل الخطأ", "error.page.action.restart": "إعادة تشغيل", "error.page.action.report": "الإبلاغ عن الخطأ", "error.page.action.reported": "تم الإبلاغ عن الخطأ", + "error.page.action.exportLogs": "تصدير السجلات", "error.page.action.checking": "جارٍ التحقق...", "error.page.action.checkUpdates": "التحقق من وجود تحديثات", "error.page.action.updateTo": "تحديث إلى {{version}}", @@ -450,16 +538,39 @@ export const dict = { "home.recentProjects": "المشاريع الحديثة", "home.empty.title": "لا توجد مشاريع حديثة", "home.empty.description": "ابدأ بفتح مشروع محلي", + "home.title": "الرئيسية", + "home.projects": "المشاريع", + "home.project.add": "إضافة مشروع", + "home.recentlyClosed": "المغلقة مؤخرًا", + "home.server.collapse": "طي مشاريع الخادم", + "home.server.expand": "توسيع مشاريع الخادم", + "home.sessions.search.placeholder": "البحث في الجلسات", + "home.sessions.search.placeholder.scoped": "البحث في جلسات {{scope}}", + "home.sessions.search.sessions": "الجلسات", + "home.sessions.search.noResults": "لم يتم العثور على جلسات لـ {{query}}", + "home.sessions.empty": "لا يوجد شيء هنا بعد", + "home.sessions.empty.description": "أنشئ جلسة للبدء", + "home.sessions.group.today": "اليوم", + "home.sessions.group.yesterday": "أمس", + "home.sessions.group.older": "الأقدم", + "home.providerTip": "اتصل بأكثر من 75 مزودًا لاستخدام نماذج أخرى، بما فيها Claude وGPT وGemini وغيرها", "session.tab.session": "جلسة", "session.tab.review": "مراجعة", "session.tab.context": "سياق", + "session.tab.unknown": "جلسة غير معروفة", "session.panel.reviewAndFiles": "المراجعة والملفات", + "session.error.notFound": "لا يمكن العثور على هذه الجلسة", + "session.error.notFound.description": "يشير هذا التبويب إلى جلسة لم تعد موجودة على هذا الخادم.", + "session.error.notFound.closeTab": "إغلاق علامة التبويب", + "session.error.serverConnection": "تعذر الاتصال بهذا الخادم", "session.review.filesChanged": "تم تغيير {{count}} ملفات", "session.review.change.one": "تغيير", "session.review.change.other": "تغييرات", "session.review.loadingChanges": "جارٍ تحميل التغييرات...", "session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد", "session.review.noChanges": "لا توجد تغييرات", + "session.review.noUncommittedChanges": "لا توجد تغييرات غير مُودَعة بعد", + "session.review.noBranchChanges": "لا توجد تغييرات في الفرع بعد", "session.review.noVcs": "لم يتم اكتشاف نظام التحكم في الإصدار Git، لن يتم عرض التغييرات", "session.review.noSnapshot": "تم تعطيل تتبع اللقطات في التكوين، لذا فإن تغييرات الجلسة غير متوفرة", "session.files.selectToOpen": "اختر ملفًا لفتحه", @@ -475,6 +586,10 @@ export const dict = { "session.todo.title": "المهام", "session.todo.collapse": "طي", "session.todo.expand": "توسيع", + "session.question.minimize": "تصغير السؤال", + "session.question.restore": "استعادة السؤال", + "session.question.pending.one": "{{count}} سؤال معلق", + "session.question.pending.other": "الأسئلة المعلقة: {{count}}", "session.followupDock.summary.one": "{{count}} رسالة في الانتظار", "session.followupDock.summary.other": "{{count}} رسائل في الانتظار", "session.followupDock.sendNow": "إرسال الآن", @@ -487,9 +602,16 @@ export const dict = { "session.revertDock.expand": "توسيع الرسائل التي تم التراجع عنها", "session.revertDock.restore": "استعادة الرسالة", "session.new.title": "ابنِ أي شيء", + "session.new.project.new": "مشروع جديد", + "session.new.project.search": "البحث عن المشاريع", + "session.new.project.add": "إضافة مشروع", "session.new.worktree.main": "الفرع الرئيسي", "session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})", "session.new.worktree.create": "إنشاء شجرة عمل جديدة", + "session.new.workspace.runIn": "تشغيل الجلسة في", + "session.new.workspace.triggerLocal": "محلي", + "session.new.workspace.local": "المستودع المحلي", + "session.new.workspace.existing": "مساحة عمل…", "session.new.lastModified": "آخر تعديل", "session.header.search.placeholder": "بحث {{project}}", "session.header.searchFiles": "بحث عن الملفات", @@ -550,6 +672,8 @@ export const dict = { "sidebar.project.recentSessions": "الجلسات الحديثة", "sidebar.project.viewAllSessions": "عرض جميع الجلسات", "sidebar.project.clearNotifications": "مسح الإشعارات", + "sidebar.empty.title": "لا توجد مشاريع مفتوحة", + "sidebar.empty.description": "افتح مشروعًا للبدء", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "سطح المكتب", "settings.section.server": "الخادم", @@ -559,6 +683,7 @@ export const dict = { "settings.desktop.wsl.title": "تكامل WSL", "settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.", "settings.general.section.appearance": "المظهر", + "settings.general.section.advanced": "متقدم", "settings.general.section.notifications": "إشعارات النظام", "settings.general.section.updates": "التحديثات", "settings.general.section.sounds": "المؤثرات الصوتية", @@ -566,6 +691,11 @@ export const dict = { "settings.general.section.display": "شاشة العرض", "settings.general.row.language.title": "اللغة", "settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode", + "settings.general.row.shell.title": "Shell المحطة الطرفية", + "settings.general.row.shell.description": + "اختر shell المستخدم في المحطة الطرفية. تُستخدم واجهات shell المتوافقة أيضًا في استدعاءات أدوات الوكيل.", + "settings.general.row.shell.autoDefault": "تلقائي (افتراضي)", + "settings.general.row.shell.terminalOnly": "المحطة الطرفية فقط", "settings.general.row.appearance.title": "المظهر", "settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك", "settings.general.row.colorScheme.title": "مخطط الألوان", @@ -574,14 +704,29 @@ export const dict = { "settings.general.row.theme.description": "تخصيص سمة OpenCode.", "settings.general.row.font.title": "خط الكود", "settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "خط الطرفية", + "settings.general.row.terminalFont.description": "خصّص الخط المستخدم في الطرفية", "settings.general.row.uiFont.title": "خط الواجهة", "settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها", "settings.general.row.followup.title": "سلوك المتابعة", "settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار", "settings.general.row.followup.option.queue": "قائمة انتظار", "settings.general.row.followup.option.steer": "توجيه", + "settings.general.row.showFileTree.title": "شجرة الملفات", + "settings.general.row.showFileTree.description": "إظهار لوحة شجرة الملفات في الجلسات", + "settings.general.row.showNavigation.title": "عناصر التحكم في التنقل", + "settings.general.row.showNavigation.description": "إظهار زري الرجوع والتقدم في شريط عنوان سطح المكتب", + "settings.general.row.showSearch.title": "لوحة الأوامر", + "settings.general.row.showSearch.description": "إظهار زر البحث ولوحة الأوامر في شريط العنوان", + "settings.general.row.showTerminal.title": "المحطة الطرفية", + "settings.general.row.showTerminal.description": "إظهار زر المحطة الطرفية في شريط عنوان سطح المكتب", + "settings.general.row.showStatus.title": "حالة الخادم", + "settings.general.row.showStatus.description": "إظهار زر حالة الخادم في شريط العنوان", + "settings.general.row.mobileTitlebarBottom.title": "التنقل السفلي", + "settings.general.row.mobileTitlebarBottom.description": + "وضع شريط العنوان وعلامات تبويب الجلسات أسفل الشاشة على الأجهزة المحمولة", + "settings.general.row.showCustomAgents.title": "الوكلاء المخصصون", + "settings.general.row.showCustomAgents.description": "إظهار محدد الوكلاء في محرر الرسائل", "settings.general.row.reasoningSummaries.title": "إظهار ملخصات الاستنتاج", "settings.general.row.reasoningSummaries.description": "عرض ملخصات استنتاج النموذج في الشريط الزمني", "settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell", @@ -590,6 +735,11 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit", "settings.general.row.editToolPartsExpanded.description": "إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني", + "settings.general.row.newLayoutDesigns.title": "التخطيط والتصاميم الجديدة", + "settings.general.row.newLayoutDesigns.description": + "تمكين التخطيط والصفحة الرئيسية ومحرر الرسائل وواجهة الجلسة المعاد تصميمها", + "settings.general.row.pinchZoom.title": "التكبير بإيماءة القرص", + "settings.general.row.pinchZoom.description": "السماح بإيماءة القرص على لوحة اللمس وإيماءة Ctrl-scroll للتكبير", "settings.general.row.wayland.title": "استخدام Wayland الأصلي", "settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.", "settings.general.row.wayland.tooltip": @@ -602,6 +752,8 @@ export const dict = { "settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة", "settings.updates.action.checkNow": "تحقق الآن", "settings.updates.action.checking": "جارٍ التحقق...", + "settings.updates.action.downloading": "جارٍ التنزيل...", + "settings.updates.action.installing": "جارٍ التثبيت...", "settings.updates.toast.latest.title": "أنت على آخر إصدار", "settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.", "sound.option.none": "بلا", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 2e53bb1af0ec..688b05fee1de 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -21,6 +21,9 @@ export const dict = { "theme.scheme.dark": "Escuro", "command.sidebar.toggle": "Alternar barra lateral", "command.project.open": "Abrir projeto", + "command.project.previous": "Projeto anterior", + "command.project.next": "Próximo projeto", + "command.project.index": "Alternar para o projeto {{index}}", "command.provider.connect": "Conectar provedor", "command.server.switch": "Trocar servidor", "command.settings.open": "Abrir configurações", @@ -39,6 +42,7 @@ export const dict = { "command.session.new": "Nova sessão", "command.file.open": "Abrir arquivo", "command.tab.close": "Fechar aba", + "command.tab.reopenClosed": "Reabrir aba fechada", "command.context.addSelection": "Adicionar seleção ao contexto", "command.context.addSelection.description": "Adicionar as linhas selecionadas do arquivo atual", "command.input.focus": "Focar entrada", @@ -193,6 +197,11 @@ export const dict = { "model.input.video": "vídeo", "model.input.pdf": "pdf", "model.tooltip.allows": "Permite: {{inputs}}", + "model.tooltip.context.label": "Contexto", + "model.tooltip.inputs": "Entradas", + "model.tooltip.model": "Modelo", + "model.tooltip.provider": "Provedor", + "model.tooltip.reasoning": "Raciocínio", "model.tooltip.reasoning.allowed": "Permite raciocínio", "model.tooltip.reasoning.none": "Sem raciocínio", "model.tooltip.context": "Limite de contexto {{limit}}", @@ -202,6 +211,7 @@ export const dict = { "common.loading": "Carregando", "common.loading.ellipsis": "...", "common.cancel": "Cancelar", + "common.clear": "Limpar", "common.connect": "Conectar", "common.disconnect": "Desconectar", "common.continue": "Enviar", @@ -218,6 +228,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc para sair", + "session.child.promptDisabled": "Não é possível enviar prompts em sessões de subagentes.", + "session.child.backToParent": "Voltar à sessão principal.", "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", @@ -255,6 +267,11 @@ export const dict = { "prompt.context.removeActiveFile": "Remover arquivo ativo do contexto", "prompt.context.removeFile": "Remover arquivo do contexto", "prompt.action.attachFile": "Anexar arquivo", + "prompt.menu.addImagesAndFiles": "Adicionar arquivos e mais", + "prompt.menu.imagesAndFiles": "Imagens e arquivos", + "prompt.menu.commands": "Comandos", + "prompt.menu.context": "Contexto", + "prompt.menu.shellCommand": "Comando shell", "prompt.attachment.remove": "Remover anexo", "prompt.action.send": "Enviar", "prompt.action.stop": "Parar", @@ -314,6 +331,75 @@ export const dict = { "dialog.server.menu.delete": "Excluir", "dialog.server.current": "Servidor atual", "dialog.server.status.default": "Padrão", + "wsl.server.add": "Adicionar servidor WSL", + "wsl.server.addShort": "Adicionar WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "Servidor WSL", + "wsl.server.retryStart": "Tentar iniciar novamente", + "wsl.server.updating": "Atualizando...", + "wsl.onboarding.step.distro": "Escolher distribuição", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "Verificando o WSL...", + "wsl.onboarding.restartRequired": "O Windows precisa ser reiniciado para concluir a instalação do WSL.", + "wsl.onboarding.ready": "O WSL está pronto.", + "wsl.onboarding.required": "O WSL é necessário para continuar.", + "wsl.onboarding.checkingDistros": "Verificando distribuições...", + "wsl.onboarding.installingDistro": "Instalando {{distro}}...", + "wsl.onboarding.checkingDistro": "Verificando {{distro}}...", + "wsl.onboarding.listingDistros": "Listando distribuições...", + "wsl.onboarding.distroReady": "{{distro}} está pronta.", + "wsl.onboarding.distroNotInstalled": "{{distro}} ainda não está instalada.", + "wsl.onboarding.openDistroOnce": "Abra {{distro}} uma vez para concluir a configuração.", + "wsl.onboarding.finishingDistro": "Concluindo a configuração de {{distro}}.", + "wsl.onboarding.pickDistro": "Escolha uma distribuição ou instale uma abaixo.", + "wsl.onboarding.checkingOpencode": "Verificando o OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "Verificando o OpenCode em {{distro}}...", + "wsl.onboarding.updatingOpencode": "Atualizando o OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "Atualizando o OpenCode em {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "Atualize o OpenCode em {{distro}}.", + "wsl.onboarding.updateOpencode": "Atualizar o OpenCode", + "wsl.onboarding.opencodeReadyIn": "O OpenCode está pronto em {{distro}}.", + "wsl.onboarding.opencodeReady": "O OpenCode está pronto.", + "wsl.onboarding.installOpencodeIn": "Instale o OpenCode em {{distro}}.", + "wsl.onboarding.installOpencode": "Instalar o OpenCode", + "wsl.onboarding.chooseDistroFirst": "Escolha primeiro uma distribuição.", + "wsl.onboarding.loadFailed": "Falha ao carregar o estado do WSL.", + "wsl.onboarding.loading": "Carregando...", + "wsl.onboarding.installedDistros": "Distribuições instaladas", + "wsl.onboarding.checkAgain": "Verificar novamente", + "wsl.onboarding.distroStatus.ready": "Pronta", + "wsl.onboarding.distroStatus.checking": "Verificando...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode não instalado", + "wsl.onboarding.distroStatus.missingTools": "bash e curl ausentes", + "wsl.onboarding.distroStatus.unsupported": "Sem suporte · Use o WSL 2", + "wsl.onboarding.needAnotherDistro": "Precisa de outra distribuição?", + "wsl.onboarding.needAnotherDistroHint": "Instale uma distribuição Linux pelo catálogo do WSL", + "wsl.onboarding.wslNotInstalled.title": "WSL não instalado", + "wsl.onboarding.wslNotInstalled.description": + "O WSL (Windows Subsystem for Linux) é necessário para que o OpenCode possa adicionar um servidor WSL", + "wsl.onboarding.wslUnavailable.title": "WSL indisponível", + "wsl.onboarding.wslUnavailable.description": "O OpenCode não conseguiu verificar o WSL neste computador.", + "wsl.onboarding.installWsl": "Instalar o WSL", + "wsl.onboarding.windowsRestartRequired": + "Reinicie o Windows para concluir a instalação do WSL e abra o OpenCode novamente.", + "wsl.onboarding.next": "Próximo", + "wsl.onboarding.refresh": "Atualizar", + "wsl.onboarding.allDistrosAdded": "Todas as distribuições instaladas já foram adicionadas.", + "wsl.onboarding.noDistros": "Nenhuma distribuição detectada ainda.", + "wsl.onboarding.install": "Instalar", + "wsl.onboarding.installing": "Instalando...", + "wsl.onboarding.installDistro": "Instalar distribuição", + "wsl.onboarding.searchDistros": "Buscar distribuições", + "wsl.onboarding.wsl2Required": "O WSL 2 é necessário.", + "wsl.onboarding.toolsRequired": "Esta distribuição precisa do bash e do curl.", + "wsl.onboarding.openTerminal": "Abrir terminal", + "wsl.onboarding.path": "Caminho: {{path}}", + "wsl.onboarding.notFound": "não encontrado", + "wsl.onboarding.version": "Versão: {{version}}", + "wsl.onboarding.unknown": "desconhecido", + "wsl.onboarding.desktopVersion": "desktop {{version}}", + "wsl.onboarding.versionMismatch": "A versão instalada não corresponde à versão do aplicativo para desktop.", + "wsl.onboarding.adding": "Adicionando...", "dialog.project.edit.title": "Editar projeto", "dialog.project.edit.name": "Nome", "dialog.project.edit.icon": "Ícone", @@ -371,6 +457,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", "toast.language.title": "Idioma", @@ -407,10 +494,12 @@ export const dict = { "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.description.localServerStartup": "Ocorreu um erro ao iniciar o servidor local.", "error.page.details.label": "Detalhes do Erro", "error.page.action.restart": "Reiniciar", "error.page.action.report": "Reportar erro", "error.page.action.reported": "Erro reportado", + "error.page.action.exportLogs": "Exportar logs", "error.page.action.checking": "Verificando...", "error.page.action.checkUpdates": "Verificar atualizações", "error.page.action.updateTo": "Atualizar para {{version}}", @@ -453,10 +542,32 @@ export const dict = { "home.recentProjects": "Projetos recentes", "home.empty.title": "Nenhum projeto recente", "home.empty.description": "Comece abrindo um projeto local", + "home.title": "Início", + "home.projects": "Projetos", + "home.project.add": "Adicionar projeto", + "home.recentlyClosed": "Projetos fechados recentemente", + "home.server.collapse": "Recolher projetos do servidor", + "home.server.expand": "Expandir projetos do servidor", + "home.sessions.search.placeholder": "Buscar sessões", + "home.sessions.search.placeholder.scoped": "Buscar sessões em {{scope}}", + "home.sessions.search.sessions": "Sessões", + "home.sessions.search.noResults": "Nenhuma sessão encontrada para {{query}}", + "home.sessions.empty": "Nada por aqui ainda", + "home.sessions.empty.description": "Crie uma sessão para começar", + "home.sessions.group.today": "Hoje", + "home.sessions.group.yesterday": "Ontem", + "home.sessions.group.older": "Mais antigas", + "home.providerTip": + "Conecte-se a mais de 75 provedores para usar outros modelos, incluindo Claude, GPT, Gemini e muito mais", "session.tab.session": "Sessão", "session.tab.review": "Revisão", "session.tab.context": "Contexto", + "session.tab.unknown": "Sessão desconhecida", "session.panel.reviewAndFiles": "Revisão e arquivos", + "session.error.notFound": "Esta sessão não foi encontrada", + "session.error.notFound.description": "Esta aba aponta para uma sessão que não existe mais neste servidor.", + "session.error.notFound.closeTab": "Fechar aba", + "session.error.serverConnection": "Não é possível conectar a este servidor", "session.review.filesChanged": "{{count}} Arquivos Alterados", "session.review.change.one": "Alteração", "session.review.change.other": "Alterações", @@ -466,6 +577,8 @@ export const dict = { "session.review.noSnapshot": "O rastreamento de snapshot está desabilitado na configuração, então as alterações da sessão estão indisponíveis", "session.review.noChanges": "Sem alterações", + "session.review.noUncommittedChanges": "Ainda não há alterações sem commit", + "session.review.noBranchChanges": "Ainda não há alterações na branch", "session.files.selectToOpen": "Selecione um arquivo para abrir", "session.files.all": "Todos os arquivos", "session.files.empty": "Nenhum arquivo", @@ -479,6 +592,10 @@ export const dict = { "session.todo.title": "Tarefas", "session.todo.collapse": "Recolher", "session.todo.expand": "Expandir", + "session.question.minimize": "Minimizar pergunta", + "session.question.restore": "Restaurar pergunta", + "session.question.pending.one": "{{count}} pergunta pendente", + "session.question.pending.other": "{{count}} perguntas pendentes", "session.followupDock.summary.one": "{{count}} mensagem na fila", "session.followupDock.summary.other": "{{count}} mensagens na fila", "session.followupDock.sendNow": "Enviar agora", @@ -491,9 +608,16 @@ export const dict = { "session.revertDock.expand": "Expandir mensagens revertidas", "session.revertDock.restore": "Restaurar mensagem", "session.new.title": "Crie qualquer coisa", + "session.new.project.new": "Novo projeto", + "session.new.project.search": "Buscar projetos", + "session.new.project.add": "Adicionar projeto", "session.new.worktree.main": "Branch principal", "session.new.worktree.mainWithBranch": "Branch principal ({{branch}})", "session.new.worktree.create": "Criar novo worktree", + "session.new.workspace.runIn": "Executar sessão em", + "session.new.workspace.triggerLocal": "Local", + "session.new.workspace.local": "Repositório local", + "session.new.workspace.existing": "Espaço de trabalho…", "session.new.lastModified": "Última modificação", "session.header.search.placeholder": "Buscar {{project}}", "session.header.searchFiles": "Buscar arquivos", @@ -557,15 +681,18 @@ export const dict = { "sidebar.project.recentSessions": "Sessões recentes", "sidebar.project.viewAllSessions": "Ver todas as sessões", "sidebar.project.clearNotifications": "Limpar notificações", + "sidebar.empty.title": "Nenhum projeto aberto", + "sidebar.empty.description": "Abra um projeto para começar", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Servidor", "settings.tab.general": "Geral", "settings.tab.shortcuts": "Atalhos", "settings.desktop.section.wsl": "WSL", - "settings.desktop.wsl.title": "WSL integration", + "settings.desktop.wsl.title": "Integração com WSL", "settings.desktop.wsl.description": "Executar o servidor OpenCode dentro do WSL no Windows.", "settings.general.section.appearance": "Aparência", + "settings.general.section.advanced": "Avançado", "settings.general.section.notifications": "Notificações do sistema", "settings.general.section.updates": "Atualizações", "settings.general.section.sounds": "Efeitos sonoros", @@ -573,6 +700,11 @@ export const dict = { "settings.general.section.display": "Tela", "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode", + "settings.general.row.shell.title": "Shell do terminal", + "settings.general.row.shell.description": + "Escolha o shell usado no terminal. Os shells compatíveis também são usados nas chamadas de ferramentas do agente.", + "settings.general.row.shell.autoDefault": "Automático (Padrão)", + "settings.general.row.shell.terminalOnly": "somente no terminal", "settings.general.row.appearance.title": "Aparência", "settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo", "settings.general.row.colorScheme.title": "Esquema de cores", @@ -581,8 +713,8 @@ export const dict = { "settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.", "settings.general.row.font.title": "Fonte de código", "settings.general.row.font.description": "Personalize a fonte usada em blocos de código", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Fonte do terminal", + "settings.general.row.terminalFont.description": "Personalize a fonte usada no terminal", "settings.general.row.uiFont.title": "Fonte da interface", "settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface", "settings.general.row.followup.title": "Comportamento de acompanhamento", @@ -590,6 +722,22 @@ export const dict = { "Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila", "settings.general.row.followup.option.queue": "Fila", "settings.general.row.followup.option.steer": "Orientar", + "settings.general.row.showFileTree.title": "Árvore de arquivos", + "settings.general.row.showFileTree.description": "Mostrar o painel da árvore de arquivos nas sessões", + "settings.general.row.showNavigation.title": "Controles de navegação", + "settings.general.row.showNavigation.description": + "Mostrar os botões de voltar e avançar na barra de título do desktop", + "settings.general.row.showSearch.title": "Paleta de comandos", + "settings.general.row.showSearch.description": "Mostrar o botão de busca e paleta de comandos na barra de título", + "settings.general.row.showTerminal.title": "Terminal", + "settings.general.row.showTerminal.description": "Mostrar o botão do terminal na barra de título do desktop", + "settings.general.row.showStatus.title": "Status do servidor", + "settings.general.row.showStatus.description": "Mostrar o botão de status do servidor na barra de título", + "settings.general.row.mobileTitlebarBottom.title": "Navegação inferior", + "settings.general.row.mobileTitlebarBottom.description": + "Posicionar a barra de título e as abas da sessão na parte inferior da tela em dispositivos móveis", + "settings.general.row.showCustomAgents.title": "Agentes personalizados", + "settings.general.row.showCustomAgents.description": "Mostrar o seletor de agentes na área de composição", "settings.general.row.reasoningSummaries.title": "Mostrar resumos de raciocínio", "settings.general.row.reasoningSummaries.description": "Exibir resumos de raciocínio do modelo na linha do tempo", "settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell", @@ -598,6 +746,12 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição", "settings.general.row.editToolPartsExpanded.description": "Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo", + "settings.general.row.newLayoutDesigns.title": "Novo layout e design", + "settings.general.row.newLayoutDesigns.description": + "Ativar o layout, a página inicial, a área de composição e a interface de sessão reformulados", + "settings.general.row.pinchZoom.title": "Zoom com gesto de pinça", + "settings.general.row.pinchZoom.description": + "Permitir gestos de pinça no trackpad e de Ctrl+rolagem para aplicar zoom", "settings.general.row.wayland.title": "Usar Wayland nativo", "settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.", "settings.general.row.wayland.tooltip": @@ -610,6 +764,8 @@ export const dict = { "settings.updates.row.check.description": "Verificar atualizações manualmente e instalar se houver", "settings.updates.action.checkNow": "Verificar agora", "settings.updates.action.checking": "Verificando...", + "settings.updates.action.downloading": "Baixando...", + "settings.updates.action.installing": "Instalando...", "settings.updates.toast.latest.title": "Você está atualizado", "settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.", "sound.option.none": "Nenhum", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index c5b9e1a6d823..e0d4e2aa03e5 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -23,6 +23,9 @@ export const dict = { "command.sidebar.toggle": "Prikaži/sakrij bočnu traku", "command.project.open": "Otvori projekat", + "command.project.previous": "Prethodni projekat", + "command.project.next": "Sljedeći projekat", + "command.project.index": "Prebaci na projekat {{index}}", "command.provider.connect": "Poveži provajdera", "command.server.switch": "Promijeni server", "command.settings.open": "Otvori postavke", @@ -45,6 +48,7 @@ export const dict = { "command.session.new": "Nova sesija", "command.file.open": "Otvori datoteku", "command.tab.close": "Zatvori karticu", + "command.tab.reopenClosed": "Ponovo otvori zatvorenu karticu", "command.context.addSelection": "Dodaj odabir u kontekst", "command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke", "command.input.focus": "Fokusiraj polje za unos", @@ -209,6 +213,11 @@ export const dict = { "model.input.video": "video", "model.input.pdf": "pdf", "model.tooltip.allows": "Podržava: {{inputs}}", + "model.tooltip.context.label": "Kontekst", + "model.tooltip.inputs": "Ulazi", + "model.tooltip.model": "Model", + "model.tooltip.provider": "Provajder", + "model.tooltip.reasoning": "Rasuđivanje", "model.tooltip.reasoning.allowed": "Podržava rasuđivanje", "model.tooltip.reasoning.none": "Bez rasuđivanja", "model.tooltip.context": "Limit konteksta {{limit}}", @@ -219,6 +228,7 @@ export const dict = { "common.loading": "Učitavanje", "common.loading.ellipsis": "...", "common.cancel": "Otkaži", + "common.clear": "Očisti", "common.connect": "Poveži", "common.disconnect": "Prekini vezu", "common.continue": "Pošalji", @@ -236,6 +246,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc za izlaz", + "session.child.promptDisabled": "Sesijama podagenta nije moguće slati upite.", + "session.child.backToParent": "Nazad na glavnu sesiju.", "prompt.example.1": "Popravi TODO u bazi koda", "prompt.example.2": "Koji je tehnološki stack ovog projekta?", @@ -275,6 +287,11 @@ export const dict = { "prompt.context.removeActiveFile": "Ukloni aktivnu datoteku iz konteksta", "prompt.context.removeFile": "Ukloni datoteku iz konteksta", "prompt.action.attachFile": "Priloži datoteku", + "prompt.menu.addImagesAndFiles": "Dodaj datoteke i više", + "prompt.menu.imagesAndFiles": "Slike i datoteke", + "prompt.menu.commands": "Komande", + "prompt.menu.context": "Kontekst", + "prompt.menu.shellCommand": "Shell naredba", "prompt.attachment.remove": "Ukloni prilog", "prompt.action.send": "Pošalji", "prompt.action.stop": "Zaustavi", @@ -342,6 +359,75 @@ export const dict = { "dialog.server.menu.delete": "Izbriši", "dialog.server.current": "Trenutni server", "dialog.server.status.default": "Podrazumijevano", + "wsl.server.add": "Dodaj WSL server", + "wsl.server.addShort": "Dodaj WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "WSL server", + "wsl.server.retryStart": "Ponovi pokretanje", + "wsl.server.updating": "Ažuriranje...", + "wsl.onboarding.step.distro": "Odaberi distribuciju", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "Provjera WSL-a...", + "wsl.onboarding.restartRequired": "Potrebno je ponovo pokrenuti Windows da bi se završila instalacija WSL-a.", + "wsl.onboarding.ready": "WSL je spreman.", + "wsl.onboarding.required": "WSL je potreban za nastavak.", + "wsl.onboarding.checkingDistros": "Provjera distribucija...", + "wsl.onboarding.installingDistro": "Instaliranje {{distro}}...", + "wsl.onboarding.checkingDistro": "Provjera {{distro}}...", + "wsl.onboarding.listingDistros": "Učitavanje liste distribucija...", + "wsl.onboarding.distroReady": "Distribucija {{distro}} je spremna.", + "wsl.onboarding.distroNotInstalled": "Distribucija {{distro}} još nije instalirana.", + "wsl.onboarding.openDistroOnce": "Otvori {{distro}} jednom da završiš postavljanje.", + "wsl.onboarding.finishingDistro": "Završavanje postavljanja za {{distro}}.", + "wsl.onboarding.pickDistro": "Odaberi distribuciju ili instaliraj jednu ispod.", + "wsl.onboarding.checkingOpencode": "Provjera OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "Provjera OpenCode u distribuciji {{distro}}...", + "wsl.onboarding.updatingOpencode": "Ažuriranje OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "Ažuriranje OpenCode u distribuciji {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "Ažuriraj OpenCode u distribuciji {{distro}}.", + "wsl.onboarding.updateOpencode": "Ažuriraj OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode je spreman u distribuciji {{distro}}.", + "wsl.onboarding.opencodeReady": "OpenCode je spreman.", + "wsl.onboarding.installOpencodeIn": "Instaliraj OpenCode u distribuciji {{distro}}.", + "wsl.onboarding.installOpencode": "Instaliraj OpenCode", + "wsl.onboarding.chooseDistroFirst": "Prvo odaberi distribuciju.", + "wsl.onboarding.loadFailed": "Nije uspjelo učitavanje WSL stanja.", + "wsl.onboarding.loading": "Učitavanje...", + "wsl.onboarding.installedDistros": "Instalirane distribucije", + "wsl.onboarding.checkAgain": "Provjeri ponovo", + "wsl.onboarding.distroStatus.ready": "Spremno", + "wsl.onboarding.distroStatus.checking": "Provjera...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nije instaliran", + "wsl.onboarding.distroStatus.missingTools": "Nedostaju bash i curl", + "wsl.onboarding.distroStatus.unsupported": "Nije podržano · Koristi WSL 2", + "wsl.onboarding.needAnotherDistro": "Treba ti druga distribucija?", + "wsl.onboarding.needAnotherDistroHint": "Instaliraj Linux distribuciju iz WSL kataloga", + "wsl.onboarding.wslNotInstalled.title": "WSL nije instaliran", + "wsl.onboarding.wslNotInstalled.description": + "WSL (Windows Subsystem for Linux) je potreban da bi OpenCode mogao dodati WSL server", + "wsl.onboarding.wslUnavailable.title": "WSL nije dostupan", + "wsl.onboarding.wslUnavailable.description": "OpenCode nije mogao provjeriti WSL na ovom uređaju.", + "wsl.onboarding.installWsl": "Instaliraj WSL", + "wsl.onboarding.windowsRestartRequired": + "Ponovo pokreni Windows da završiš instalaciju WSL-a, zatim ponovo otvori OpenCode.", + "wsl.onboarding.next": "Dalje", + "wsl.onboarding.refresh": "Osvježi", + "wsl.onboarding.allDistrosAdded": "Sve instalirane distribucije su već dodane.", + "wsl.onboarding.noDistros": "Još nema otkrivenih distribucija.", + "wsl.onboarding.install": "Instaliraj", + "wsl.onboarding.installing": "Instaliranje...", + "wsl.onboarding.installDistro": "Instaliraj distribuciju", + "wsl.onboarding.searchDistros": "Pretraži distribucije", + "wsl.onboarding.wsl2Required": "Potreban je WSL 2.", + "wsl.onboarding.toolsRequired": "Ovoj distribuciji su potrebni bash i curl.", + "wsl.onboarding.openTerminal": "Otvori terminal", + "wsl.onboarding.path": "Putanja: {{path}}", + "wsl.onboarding.notFound": "nije pronađeno", + "wsl.onboarding.version": "Verzija: {{version}}", + "wsl.onboarding.unknown": "nepoznato", + "wsl.onboarding.desktopVersion": "desktop {{version}}", + "wsl.onboarding.versionMismatch": "Instalirana verzija ne odgovara verziji desktop aplikacije.", + "wsl.onboarding.adding": "Dodavanje...", "dialog.project.edit.title": "Uredi projekat", "dialog.project.edit.name": "Naziv", @@ -405,6 +491,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", @@ -453,10 +540,12 @@ export const dict = { "error.page.title": "Nešto je pošlo po zlu", "error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.", + "error.page.description.localServerStartup": "Došlo je do greške pri pokretanju lokalnog servera.", "error.page.details.label": "Detalji greške", "error.page.action.restart": "Restartuj", "error.page.action.report": "Prijavi grešku", "error.page.action.reported": "Greška prijavljena", + "error.page.action.exportLogs": "Izvezi logove", "error.page.action.checking": "Provjera...", "error.page.action.checkUpdates": "Provjeri ažuriranja", "error.page.action.updateTo": "Ažuriraj na {{version}}", @@ -504,11 +593,33 @@ export const dict = { "home.recentProjects": "Nedavni projekti", "home.empty.title": "Nema nedavnih projekata", "home.empty.description": "Kreni tako što ćeš otvoriti lokalni projekat", + "home.title": "Početna", + "home.projects": "Projekti", + "home.project.add": "Dodaj projekat", + "home.recentlyClosed": "Nedavno zatvoreni projekti", + "home.server.collapse": "Sažmi projekte servera", + "home.server.expand": "Proširi projekte servera", + "home.sessions.search.placeholder": "Pretraži sesije", + "home.sessions.search.placeholder.scoped": "Pretraži sesije u {{scope}}", + "home.sessions.search.sessions": "Sesije", + "home.sessions.search.noResults": "Nema pronađenih sesija za {{query}}", + "home.sessions.empty": "Ovdje još nema ničega", + "home.sessions.empty.description": "Kreiraj sesiju za početak", + "home.sessions.group.today": "Danas", + "home.sessions.group.yesterday": "Jučer", + "home.sessions.group.older": "Starije", + "home.providerTip": + "Povežite se s više od 75 pružalaca usluga kako biste koristili druge modele, uključujući Claude, GPT, Gemini i druge", "session.tab.session": "Sesija", "session.tab.review": "Pregled", "session.tab.context": "Kontekst", + "session.tab.unknown": "Nepoznata sesija", "session.panel.reviewAndFiles": "Pregled i datoteke", + "session.error.notFound": "Ova sesija nije pronađena", + "session.error.notFound.description": "Ova kartica upućuje na sesiju koja više ne postoji na ovom serveru.", + "session.error.notFound.closeTab": "Zatvori karticu", + "session.error.serverConnection": "Nije moguće povezati se s ovim serverom", "session.review.filesChanged": "Izmijenjeno {{count}} datoteka", "session.review.change.one": "Izmjena", "session.review.change.other": "Izmjene", @@ -518,6 +629,8 @@ export const dict = { "session.review.noSnapshot": "Praćenje snimaka (snapshot) je onemogućeno u konfiguraciji, pa promjene sesije nisu dostupne", "session.review.noChanges": "Nema izmjena", + "session.review.noUncommittedChanges": "Još nema nepotvrđenih promjena", + "session.review.noBranchChanges": "Još nema promjena na grani", "session.files.selectToOpen": "Odaberi datoteku za otvaranje", "session.files.all": "Sve datoteke", @@ -534,6 +647,10 @@ export const dict = { "session.todo.title": "Zadaci", "session.todo.collapse": "Sažmi", "session.todo.expand": "Proširi", + "session.question.minimize": "Minimiziraj pitanje", + "session.question.restore": "Vrati pitanje", + "session.question.pending.one": "{{count}} pitanje na čekanju", + "session.question.pending.other": "{{count}} pitanja na čekanju", "session.followupDock.summary.one": "{{count}} poruka na čekanju", "session.followupDock.summary.other": "{{count}} poruka na čekanju", "session.followupDock.sendNow": "Pošalji sada", @@ -547,9 +664,16 @@ export const dict = { "session.revertDock.restore": "Vrati poruku", "session.new.title": "Napravi bilo šta", + "session.new.project.new": "Novi projekat", + "session.new.project.search": "Pretraži projekte", + "session.new.project.add": "Dodaj projekat", "session.new.worktree.main": "Glavna grana", "session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})", "session.new.worktree.create": "Kreiraj novi worktree", + "session.new.workspace.runIn": "Pokreni sesiju u", + "session.new.workspace.triggerLocal": "Lokalno", + "session.new.workspace.local": "Lokalni repozitorij", + "session.new.workspace.existing": "Radni prostor…", "session.new.lastModified": "Posljednja izmjena", "session.header.search.placeholder": "Pretraži {{project}}", @@ -618,6 +742,8 @@ export const dict = { "sidebar.project.recentSessions": "Nedavne sesije", "sidebar.project.viewAllSessions": "Prikaži sve sesije", "sidebar.project.clearNotifications": "Očisti obavijesti", + "sidebar.empty.title": "Nema otvorenih projekata", + "sidebar.empty.description": "Otvori projekat za početak", "app.name.desktop": "OpenCode Desktop", @@ -630,6 +756,7 @@ export const dict = { "settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.", "settings.general.section.appearance": "Izgled", + "settings.general.section.advanced": "Napredno", "settings.general.section.notifications": "Sistemske obavijesti", "settings.general.section.updates": "Ažuriranja", "settings.general.section.sounds": "Zvučni efekti", @@ -638,6 +765,11 @@ export const dict = { "settings.general.row.language.title": "Jezik", "settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u", + "settings.general.row.shell.title": "Shell terminala", + "settings.general.row.shell.description": + "Odaberi shell koji se koristi za terminal. Kompatibilni shellovi koriste se i za pozive alata agenta.", + "settings.general.row.shell.autoDefault": "Automatski (podrazumijevano)", + "settings.general.row.shell.terminalOnly": "samo terminal", "settings.general.row.appearance.title": "Izgled", "settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju", "settings.general.row.colorScheme.title": "Šema boja", @@ -646,14 +778,30 @@ export const dict = { "settings.general.row.theme.description": "Prilagodi temu OpenCode-a.", "settings.general.row.font.title": "Font za kod", "settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Font terminala", + "settings.general.row.terminalFont.description": "Prilagodite font koji se koristi u terminalu", "settings.general.row.uiFont.title": "UI font", "settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu", "settings.general.row.followup.title": "Ponašanje nadovezivanja", "settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu", "settings.general.row.followup.option.queue": "Red čekanja", "settings.general.row.followup.option.steer": "Usmjeri", + "settings.general.row.showFileTree.title": "Stablo datoteka", + "settings.general.row.showFileTree.description": "Prikaži stablo datoteka u sesijama", + "settings.general.row.showNavigation.title": "Kontrole navigacije", + "settings.general.row.showNavigation.description": + "Prikaži dugmad za nazad i naprijed u naslovnoj traci desktop aplikacije", + "settings.general.row.showSearch.title": "Paleta komandi", + "settings.general.row.showSearch.description": "Prikaži dugme za pretragu i paletu komandi u naslovnoj traci", + "settings.general.row.showTerminal.title": "Terminal", + "settings.general.row.showTerminal.description": "Prikaži dugme terminala u naslovnoj traci desktop aplikacije", + "settings.general.row.showStatus.title": "Status servera", + "settings.general.row.showStatus.description": "Prikaži dugme statusa servera u naslovnoj traci", + "settings.general.row.mobileTitlebarBottom.title": "Donja navigacija", + "settings.general.row.mobileTitlebarBottom.description": + "Postavi naslovnu traku i kartice sesije na dno ekrana na mobilnim uređajima", + "settings.general.row.showCustomAgents.title": "Prilagođeni agenti", + "settings.general.row.showCustomAgents.description": "Prikaži izbor agenta u uređivaču poruke", "settings.general.row.reasoningSummaries.title": "Prikaži sažetke rasuđivanja", "settings.general.row.reasoningSummaries.description": "Prikaži sažetke rasuđivanja modela na vremenskoj traci", @@ -663,6 +811,12 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje", "settings.general.row.editToolPartsExpanded.description": "Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci", + "settings.general.row.newLayoutDesigns.title": "Novi raspored i dizajn", + "settings.general.row.newLayoutDesigns.description": + "Omogući redizajnirani raspored, početnu stranicu, uređivač poruke i interfejs sesije", + "settings.general.row.pinchZoom.title": "Zumiranje štipanjem", + "settings.general.row.pinchZoom.description": + "Dozvoli zumiranje gestom štipanja na dodirnoj ploči i pomoću Ctrl-pomjeranja", "settings.general.row.wayland.title": "Koristi nativni Wayland", "settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.", "settings.general.row.wayland.tooltip": @@ -677,6 +831,8 @@ export const dict = { "settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna", "settings.updates.action.checkNow": "Provjeri sada", "settings.updates.action.checking": "Provjera...", + "settings.updates.action.downloading": "Preuzimanje...", + "settings.updates.action.installing": "Instaliranje...", "settings.updates.toast.latest.title": "Sve je ažurno", "settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.", "sound.option.none": "Nijedan", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index ad43c1d11145..3b02b6a02612 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -23,6 +23,9 @@ export const dict = { "command.sidebar.toggle": "Skift sidebjælke", "command.project.open": "Åbn projekt", + "command.project.previous": "Forrige projekt", + "command.project.next": "Næste projekt", + "command.project.index": "Skift til projekt {{index}}", "command.provider.connect": "Tilslut udbyder", "command.server.switch": "Skift server", "command.settings.open": "Åbn indstillinger", @@ -45,6 +48,7 @@ export const dict = { "command.session.new": "Ny session", "command.file.open": "Åbn fil", "command.tab.close": "Luk fane", + "command.tab.reopenClosed": "Åbn lukket fane igen", "command.context.addSelection": "Tilføj markering til kontekst", "command.context.addSelection.description": "Tilføj markerede linjer fra den aktuelle fil", "command.input.focus": "Fokuser inputfelt", @@ -208,6 +212,11 @@ export const dict = { "model.input.video": "video", "model.input.pdf": "pdf", "model.tooltip.allows": "Tillader: {{inputs}}", + "model.tooltip.context.label": "Kontekst", + "model.tooltip.inputs": "Input", + "model.tooltip.model": "Model", + "model.tooltip.provider": "Udbyder", + "model.tooltip.reasoning": "Tænkning", "model.tooltip.reasoning.allowed": "Tillader tænkning", "model.tooltip.reasoning.none": "Ingen tænkning", "model.tooltip.context": "Kontekstgrænse {{limit}}", @@ -217,6 +226,7 @@ export const dict = { "common.loading": "Indlæser", "common.loading.ellipsis": "...", "common.cancel": "Annuller", + "common.clear": "Ryd", "common.connect": "Forbind", "common.disconnect": "Frakobl", "common.continue": "Indsend", @@ -234,6 +244,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc for at afslutte", + "session.child.promptDisabled": "Der kan ikke sendes prompts til underagentsessioner.", + "session.child.backToParent": "Tilbage til hovedsessionen.", "prompt.example.1": "Ret en TODO i koden", "prompt.example.2": "Hvad er teknologistakken for dette projekt?", @@ -273,6 +285,11 @@ export const dict = { "prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst", "prompt.context.removeFile": "Fjern fil fra kontekst", "prompt.action.attachFile": "Vedhæft fil", + "prompt.menu.addImagesAndFiles": "Tilføj filer og mere", + "prompt.menu.imagesAndFiles": "Billeder og filer", + "prompt.menu.commands": "Kommandoer", + "prompt.menu.context": "Kontekst", + "prompt.menu.shellCommand": "Shell-kommando", "prompt.attachment.remove": "Fjern vedhæftning", "prompt.action.send": "Send", "prompt.action.stop": "Stop", @@ -340,6 +357,75 @@ export const dict = { "dialog.server.menu.delete": "Slet", "dialog.server.current": "Nuværende server", "dialog.server.status.default": "Standard", + "wsl.server.add": "Tilføj WSL-server", + "wsl.server.addShort": "Tilføj WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "WSL-server", + "wsl.server.retryStart": "Prøv at starte igen", + "wsl.server.updating": "Opdaterer...", + "wsl.onboarding.step.distro": "Vælg distribution", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "Tjekker WSL...", + "wsl.onboarding.restartRequired": "Windows skal genstartes for at fuldføre installationen af WSL.", + "wsl.onboarding.ready": "WSL er klar.", + "wsl.onboarding.required": "WSL er påkrævet for at fortsætte.", + "wsl.onboarding.checkingDistros": "Tjekker distributioner...", + "wsl.onboarding.installingDistro": "Installerer {{distro}}...", + "wsl.onboarding.checkingDistro": "Tjekker {{distro}}...", + "wsl.onboarding.listingDistros": "Henter liste over distributioner...", + "wsl.onboarding.distroReady": "{{distro}} er klar.", + "wsl.onboarding.distroNotInstalled": "{{distro}} er ikke installeret endnu.", + "wsl.onboarding.openDistroOnce": "Åbn {{distro}} én gang for at fuldføre opsætningen.", + "wsl.onboarding.finishingDistro": "Fuldfører opsætningen af {{distro}}.", + "wsl.onboarding.pickDistro": "Vælg en distribution, eller installer en nedenfor.", + "wsl.onboarding.checkingOpencode": "Tjekker OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "Tjekker OpenCode i {{distro}}...", + "wsl.onboarding.updatingOpencode": "Opdaterer OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "Opdaterer OpenCode i {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "Opdater OpenCode i {{distro}}.", + "wsl.onboarding.updateOpencode": "Opdater OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode er klar i {{distro}}.", + "wsl.onboarding.opencodeReady": "OpenCode er klar.", + "wsl.onboarding.installOpencodeIn": "Installer OpenCode i {{distro}}.", + "wsl.onboarding.installOpencode": "Installer OpenCode", + "wsl.onboarding.chooseDistroFirst": "Vælg først en distribution.", + "wsl.onboarding.loadFailed": "Kunne ikke indlæse WSL-status.", + "wsl.onboarding.loading": "Indlæser...", + "wsl.onboarding.installedDistros": "Installerede distributioner", + "wsl.onboarding.checkAgain": "Tjek igen", + "wsl.onboarding.distroStatus.ready": "Klar", + "wsl.onboarding.distroStatus.checking": "Tjekker...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode er ikke installeret", + "wsl.onboarding.distroStatus.missingTools": "Mangler bash, curl", + "wsl.onboarding.distroStatus.unsupported": "Ikke understøttet · Brug WSL 2", + "wsl.onboarding.needAnotherDistro": "Har du brug for en anden distribution?", + "wsl.onboarding.needAnotherDistroHint": "Installer en Linux-distribution fra WSL-kataloget", + "wsl.onboarding.wslNotInstalled.title": "WSL er ikke installeret", + "wsl.onboarding.wslNotInstalled.description": + "WSL (Windows Subsystem for Linux) er påkrævet, før OpenCode kan tilføje en WSL-server", + "wsl.onboarding.wslUnavailable.title": "WSL er ikke tilgængelig", + "wsl.onboarding.wslUnavailable.description": "OpenCode kunne ikke bekræfte WSL på denne maskine.", + "wsl.onboarding.installWsl": "Installer WSL", + "wsl.onboarding.windowsRestartRequired": + "Genstart Windows for at fuldføre installationen af WSL, og åbn derefter OpenCode igen.", + "wsl.onboarding.next": "Næste", + "wsl.onboarding.refresh": "Opdater", + "wsl.onboarding.allDistrosAdded": "Alle installerede distributioner er allerede tilføjet.", + "wsl.onboarding.noDistros": "Ingen distributioner fundet endnu.", + "wsl.onboarding.install": "Installer", + "wsl.onboarding.installing": "Installerer...", + "wsl.onboarding.installDistro": "Installer distribution", + "wsl.onboarding.searchDistros": "Søg efter distributioner", + "wsl.onboarding.wsl2Required": "WSL 2 er påkrævet.", + "wsl.onboarding.toolsRequired": "Denne distribution kræver bash og curl.", + "wsl.onboarding.openTerminal": "Åbn terminal", + "wsl.onboarding.path": "Sti: {{path}}", + "wsl.onboarding.notFound": "ikke fundet", + "wsl.onboarding.version": "Version: {{version}}", + "wsl.onboarding.unknown": "ukendt", + "wsl.onboarding.desktopVersion": "desktop {{version}}", + "wsl.onboarding.versionMismatch": "Den installerede version matcher ikke desktopappens version.", + "wsl.onboarding.adding": "Tilføjer...", "dialog.project.edit.title": "Rediger projekt", "dialog.project.edit.name": "Navn", @@ -403,6 +489,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", @@ -450,10 +537,12 @@ export const dict = { "error.page.title": "Noget gik galt", "error.page.description": "Der opstod en fejl under indlæsning af applikationen.", + "error.page.description.localServerStartup": "Der opstod en fejl under start af den lokale server.", "error.page.details.label": "Fejldetaljer", "error.page.action.restart": "Genstart", "error.page.action.report": "Rapportér fejl", "error.page.action.reported": "Fejl rapporteret", + "error.page.action.exportLogs": "Eksportér logfiler", "error.page.action.checking": "Tjekker...", "error.page.action.checkUpdates": "Tjek for opdateringer", "error.page.action.updateTo": "Opdater til {{version}}", @@ -501,11 +590,33 @@ export const dict = { "home.recentProjects": "Seneste projekter", "home.empty.title": "Ingen seneste projekter", "home.empty.description": "Kom i gang ved at åbne et lokalt projekt", + "home.title": "Hjem", + "home.projects": "Projekter", + "home.project.add": "Tilføj projekt", + "home.recentlyClosed": "Lukket for nylig", + "home.server.collapse": "Skjul serverprojekter", + "home.server.expand": "Udvid serverprojekter", + "home.sessions.search.placeholder": "Søg i sessioner", + "home.sessions.search.placeholder.scoped": "Søg i sessioner i {{scope}}", + "home.sessions.search.sessions": "Sessioner", + "home.sessions.search.noResults": "Ingen sessioner fundet for {{query}}", + "home.sessions.empty": "Intet her endnu", + "home.sessions.empty.description": "Opret en session for at komme i gang", + "home.sessions.group.today": "I dag", + "home.sessions.group.yesterday": "I går", + "home.sessions.group.older": "Ældre", + "home.providerTip": + "Opret forbindelse til mere end 75 udbydere for at bruge andre modeller, herunder Claude, GPT, Gemini og flere", "session.tab.session": "Session", "session.tab.review": "Gennemgang", "session.tab.context": "Kontekst", + "session.tab.unknown": "Ukendt session", "session.panel.reviewAndFiles": "Gennemgang og filer", + "session.error.notFound": "Denne session kan ikke findes", + "session.error.notFound.description": "Denne fane henviser til en session, der ikke længere findes på denne server.", + "session.error.notFound.closeTab": "Luk fane", + "session.error.serverConnection": "Kan ikke oprette forbindelse til denne server", "session.review.filesChanged": "{{count}} Filer ændret", "session.review.change.one": "Ændring", "session.review.change.other": "Ændringer", @@ -515,6 +626,8 @@ export const dict = { "session.review.noSnapshot": "Snapshot-sporing er deaktiveret i konfigurationen, så sessionsændringer er ikke tilgængelige", "session.review.noChanges": "Ingen ændringer", + "session.review.noUncommittedChanges": "Ingen ændringer uden commit endnu", + "session.review.noBranchChanges": "Ingen grenændringer endnu", "session.files.selectToOpen": "Vælg en fil at åbne", "session.files.all": "Alle filer", "session.files.empty": "Ingen filer", @@ -529,6 +642,10 @@ export const dict = { "session.todo.title": "Opgaver", "session.todo.collapse": "Skjul", "session.todo.expand": "Udvid", + "session.question.minimize": "Minimer spørgsmål", + "session.question.restore": "Gendan spørgsmål", + "session.question.pending.one": "{{count}} afventende spørgsmål", + "session.question.pending.other": "{{count}} afventende spørgsmål", "session.followupDock.summary.one": "{{count}} besked i kø", "session.followupDock.summary.other": "{{count}} beskeder i kø", "session.followupDock.sendNow": "Send nu", @@ -542,9 +659,16 @@ export const dict = { "session.revertDock.restore": "Gendan besked", "session.new.title": "Byg hvad som helst", + "session.new.project.new": "Nyt projekt", + "session.new.project.search": "Søg efter projekter", + "session.new.project.add": "Tilføj projekt", "session.new.worktree.main": "Hovedgren", "session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})", "session.new.worktree.create": "Opret nyt worktree", + "session.new.workspace.runIn": "Kør session i", + "session.new.workspace.triggerLocal": "Lokal", + "session.new.workspace.local": "Lokalt repository", + "session.new.workspace.existing": "Arbejdsområde…", "session.new.lastModified": "Sidst ændret", "session.header.search.placeholder": "Søg {{project}}", @@ -614,6 +738,8 @@ export const dict = { "sidebar.project.recentSessions": "Seneste sessioner", "sidebar.project.viewAllSessions": "Vis alle sessioner", "sidebar.project.clearNotifications": "Ryd notifikationer", + "sidebar.empty.title": "Ingen åbne projekter", + "sidebar.empty.description": "Åbn et projekt for at komme i gang", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", @@ -625,6 +751,7 @@ export const dict = { "settings.desktop.wsl.description": "Kør OpenCode-serveren inde i WSL på Windows.", "settings.general.section.appearance": "Udseende", + "settings.general.section.advanced": "Avanceret", "settings.general.section.notifications": "Systemmeddelelser", "settings.general.section.updates": "Opdateringer", "settings.general.section.sounds": "Lydeffekter", @@ -633,6 +760,11 @@ export const dict = { "settings.general.row.language.title": "Sprog", "settings.general.row.language.description": "Ændr visningssproget for OpenCode", + "settings.general.row.shell.title": "Terminalshell", + "settings.general.row.shell.description": + "Vælg den shell, der bruges i din terminal. Kompatible shells bruges også til agentens værktøjskald.", + "settings.general.row.shell.autoDefault": "Automatisk (standard)", + "settings.general.row.shell.terminalOnly": "kun terminal", "settings.general.row.appearance.title": "Udseende", "settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed", "settings.general.row.colorScheme.title": "Farveskema", @@ -641,14 +773,29 @@ export const dict = { "settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.", "settings.general.row.font.title": "Kode-skrifttype", "settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Terminalskrifttype", + "settings.general.row.terminalFont.description": "Tilpas den skrifttype, der bruges i terminalen", "settings.general.row.uiFont.title": "UI-skrifttype", "settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen", "settings.general.row.followup.title": "Opfølgningsadfærd", "settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø", "settings.general.row.followup.option.queue": "Kø", "settings.general.row.followup.option.steer": "Styr", + "settings.general.row.showFileTree.title": "Filtræ", + "settings.general.row.showFileTree.description": "Vis filtræspanelet i sessioner", + "settings.general.row.showNavigation.title": "Navigationsknapper", + "settings.general.row.showNavigation.description": "Vis tilbage- og frem-knapperne i desktopappens titellinje", + "settings.general.row.showSearch.title": "Kommandopalette", + "settings.general.row.showSearch.description": "Vis knappen til søgning og kommandopaletten i titellinjen", + "settings.general.row.showTerminal.title": "Terminal", + "settings.general.row.showTerminal.description": "Vis terminalknappen i desktopappens titellinje", + "settings.general.row.showStatus.title": "Serverstatus", + "settings.general.row.showStatus.description": "Vis serverstatusknappen i titellinjen", + "settings.general.row.mobileTitlebarBottom.title": "Navigation nederst", + "settings.general.row.mobileTitlebarBottom.description": + "Placer titellinjen og sessionsfanerne nederst på skærmen på mobilenheder", + "settings.general.row.showCustomAgents.title": "Brugerdefinerede agenter", + "settings.general.row.showCustomAgents.description": "Vis agentvælgeren i promptfeltet", "settings.general.row.reasoningSummaries.title": "Vis tænkeoversigter", "settings.general.row.reasoningSummaries.description": "Vis model tænkeoversigter i tidslinjen", @@ -657,6 +804,11 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele", "settings.general.row.editToolPartsExpanded.description": "Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen", + "settings.general.row.newLayoutDesigns.title": "Nyt layout og design", + "settings.general.row.newLayoutDesigns.description": + "Aktivér det nydesignede layout, startsiden, promptfeltet og sessionsbrugerfladen", + "settings.general.row.pinchZoom.title": "Knib for at zoome", + "settings.general.row.pinchZoom.description": "Tillad knibebevægelser på pegefeltet og Ctrl-rulning for at zoome", "settings.general.row.wayland.title": "Brug native Wayland", "settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.", "settings.general.row.wayland.tooltip": @@ -671,6 +823,8 @@ export const dict = { "settings.updates.row.check.description": "Tjek manuelt for opdateringer og installer, hvis tilgængelig", "settings.updates.action.checkNow": "Tjek nu", "settings.updates.action.checking": "Tjekker...", + "settings.updates.action.downloading": "Downloader...", + "settings.updates.action.installing": "Installerer...", "settings.updates.toast.latest.title": "Du er opdateret", "settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index d0ec9507e060..cf530f64884d 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -25,6 +25,9 @@ export const dict = { "theme.scheme.dark": "Dunkel", "command.sidebar.toggle": "Seitenleiste umschalten", "command.project.open": "Projekt öffnen", + "command.project.previous": "Vorheriges Projekt", + "command.project.next": "Nächstes Projekt", + "command.project.index": "Zu Projekt {{index}} wechseln", "command.provider.connect": "Anbieter verbinden", "command.server.switch": "Server wechseln", "command.settings.open": "Einstellungen öffnen", @@ -43,6 +46,7 @@ export const dict = { "command.session.new": "Neue Sitzung", "command.file.open": "Datei öffnen", "command.tab.close": "Tab schließen", + "command.tab.reopenClosed": "Geschlossenen Tab wieder öffnen", "command.context.addSelection": "Auswahl zum Kontext hinzufügen", "command.context.addSelection.description": "Ausgewählte Zeilen aus der aktuellen Datei hinzufügen", "command.input.focus": "Eingabefeld fokussieren", @@ -198,6 +202,11 @@ export const dict = { "model.input.video": "Video", "model.input.pdf": "PDF", "model.tooltip.allows": "Erlaubt: {{inputs}}", + "model.tooltip.context.label": "Kontext", + "model.tooltip.inputs": "Eingaben", + "model.tooltip.model": "Modell", + "model.tooltip.provider": "Anbieter", + "model.tooltip.reasoning": "Reasoning", "model.tooltip.reasoning.allowed": "Erlaubt Reasoning", "model.tooltip.reasoning.none": "Kein Reasoning", "model.tooltip.context": "Kontextlimit {{limit}}", @@ -207,6 +216,7 @@ export const dict = { "common.loading": "Laden", "common.loading.ellipsis": "...", "common.cancel": "Abbrechen", + "common.clear": "Leeren", "common.connect": "Verbinden", "common.disconnect": "Trennen", "common.continue": "Absenden", @@ -223,6 +233,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc zum Verlassen", + "session.child.promptDisabled": "Unteragenten-Sitzungen können keine Prompts erhalten.", + "session.child.backToParent": "Zurück zur Hauptsitzung.", "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", @@ -260,6 +272,11 @@ export const dict = { "prompt.context.removeActiveFile": "Aktive Datei aus dem Kontext entfernen", "prompt.context.removeFile": "Datei aus dem Kontext entfernen", "prompt.action.attachFile": "Datei anhängen", + "prompt.menu.addImagesAndFiles": "Dateien und mehr hinzufügen", + "prompt.menu.imagesAndFiles": "Bilder und Dateien", + "prompt.menu.commands": "Befehle", + "prompt.menu.context": "Kontext", + "prompt.menu.shellCommand": "Shell-Befehl", "prompt.attachment.remove": "Anhang entfernen", "prompt.action.send": "Senden", "prompt.action.stop": "Stopp", @@ -320,6 +337,75 @@ export const dict = { "dialog.server.menu.delete": "Löschen", "dialog.server.current": "Aktueller Server", "dialog.server.status.default": "Standard", + "wsl.server.add": "WSL-Server hinzufügen", + "wsl.server.addShort": "WSL hinzufügen", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "WSL-Server", + "wsl.server.retryStart": "Start erneut versuchen", + "wsl.server.updating": "Wird aktualisiert...", + "wsl.onboarding.step.distro": "Distribution auswählen", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "WSL wird geprüft...", + "wsl.onboarding.restartRequired": "Windows muss neu gestartet werden, um die WSL-Installation abzuschließen.", + "wsl.onboarding.ready": "WSL ist bereit.", + "wsl.onboarding.required": "WSL ist erforderlich, um fortzufahren.", + "wsl.onboarding.checkingDistros": "Distributionen werden geprüft...", + "wsl.onboarding.installingDistro": "{{distro}} wird installiert...", + "wsl.onboarding.checkingDistro": "{{distro}} wird geprüft...", + "wsl.onboarding.listingDistros": "Distributionen werden aufgelistet...", + "wsl.onboarding.distroReady": "{{distro}} ist bereit.", + "wsl.onboarding.distroNotInstalled": "{{distro}} ist noch nicht installiert.", + "wsl.onboarding.openDistroOnce": "Öffnen Sie {{distro}} einmal, um die Einrichtung abzuschließen.", + "wsl.onboarding.finishingDistro": "Einrichtung von {{distro}} wird abgeschlossen.", + "wsl.onboarding.pickDistro": "Wählen Sie eine Distribution aus oder installieren Sie unten eine.", + "wsl.onboarding.checkingOpencode": "OpenCode wird geprüft...", + "wsl.onboarding.checkingOpencodeIn": "OpenCode in {{distro}} wird geprüft...", + "wsl.onboarding.updatingOpencode": "OpenCode wird aktualisiert...", + "wsl.onboarding.updatingOpencodeIn": "OpenCode in {{distro}} wird aktualisiert...", + "wsl.onboarding.updateOpencodeIn": "Aktualisieren Sie OpenCode in {{distro}}.", + "wsl.onboarding.updateOpencode": "OpenCode aktualisieren", + "wsl.onboarding.opencodeReadyIn": "OpenCode ist in {{distro}} bereit.", + "wsl.onboarding.opencodeReady": "OpenCode ist bereit.", + "wsl.onboarding.installOpencodeIn": "Installieren Sie OpenCode in {{distro}}.", + "wsl.onboarding.installOpencode": "OpenCode installieren", + "wsl.onboarding.chooseDistroFirst": "Wählen Sie zuerst eine Distribution aus.", + "wsl.onboarding.loadFailed": "WSL-Status konnte nicht geladen werden.", + "wsl.onboarding.loading": "Wird geladen...", + "wsl.onboarding.installedDistros": "Installierte Distributionen", + "wsl.onboarding.checkAgain": "Erneut prüfen", + "wsl.onboarding.distroStatus.ready": "Bereit", + "wsl.onboarding.distroStatus.checking": "Wird geprüft...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nicht installiert", + "wsl.onboarding.distroStatus.missingTools": "Fehlend: bash, curl", + "wsl.onboarding.distroStatus.unsupported": "Nicht unterstützt · WSL 2 verwenden", + "wsl.onboarding.needAnotherDistro": "Andere Distribution benötigt?", + "wsl.onboarding.needAnotherDistroHint": "Installieren Sie eine Linux-Distribution aus dem WSL-Katalog", + "wsl.onboarding.wslNotInstalled.title": "WSL nicht installiert", + "wsl.onboarding.wslNotInstalled.description": + "WSL (Windows Subsystem for Linux) ist erforderlich, bevor OpenCode einen WSL-Server hinzufügen kann", + "wsl.onboarding.wslUnavailable.title": "WSL nicht verfügbar", + "wsl.onboarding.wslUnavailable.description": "OpenCode konnte WSL auf diesem Computer nicht überprüfen.", + "wsl.onboarding.installWsl": "WSL installieren", + "wsl.onboarding.windowsRestartRequired": + "Starten Sie Windows neu, um die WSL-Installation abzuschließen, und öffnen Sie dann OpenCode erneut.", + "wsl.onboarding.next": "Weiter", + "wsl.onboarding.refresh": "Aktualisieren", + "wsl.onboarding.allDistrosAdded": "Alle installierten Distributionen wurden bereits hinzugefügt.", + "wsl.onboarding.noDistros": "Noch keine Distributionen erkannt.", + "wsl.onboarding.install": "Installieren", + "wsl.onboarding.installing": "Wird installiert...", + "wsl.onboarding.installDistro": "Distribution installieren", + "wsl.onboarding.searchDistros": "Distributionen durchsuchen", + "wsl.onboarding.wsl2Required": "WSL 2 ist erforderlich.", + "wsl.onboarding.toolsRequired": "Diese Distribution benötigt bash und curl.", + "wsl.onboarding.openTerminal": "Terminal öffnen", + "wsl.onboarding.path": "Pfad: {{path}}", + "wsl.onboarding.notFound": "nicht gefunden", + "wsl.onboarding.version": "Version: {{version}}", + "wsl.onboarding.unknown": "unbekannt", + "wsl.onboarding.desktopVersion": "Desktop {{version}}", + "wsl.onboarding.versionMismatch": "Die installierte Version entspricht nicht der Version der Desktop-App.", + "wsl.onboarding.adding": "Wird hinzugefügt...", "dialog.project.edit.title": "Projekt bearbeiten", "dialog.project.edit.name": "Name", "dialog.project.edit.icon": "Icon", @@ -378,6 +464,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", "toast.language.title": "Sprache", @@ -414,10 +501,12 @@ export const dict = { "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.description.localServerStartup": "Beim Starten des lokalen Servers ist ein Fehler aufgetreten.", "error.page.details.label": "Fehlerdetails", "error.page.action.restart": "Neustart", "error.page.action.report": "Fehler melden", "error.page.action.reported": "Fehler gemeldet", + "error.page.action.exportLogs": "Protokolle exportieren", "error.page.action.checking": "Prüfen...", "error.page.action.checkUpdates": "Nach Updates suchen", "error.page.action.updateTo": "Auf {{version}} aktualisieren", @@ -461,10 +550,33 @@ export const dict = { "home.recentProjects": "Letzte Projekte", "home.empty.title": "Keine letzten Projekte", "home.empty.description": "Starten Sie, indem Sie ein lokales Projekt öffnen", + "home.title": "Startseite", + "home.projects": "Projekte", + "home.project.add": "Projekt hinzufügen", + "home.recentlyClosed": "Kürzlich geschlossen", + "home.server.collapse": "Serverprojekte einklappen", + "home.server.expand": "Serverprojekte ausklappen", + "home.sessions.search.placeholder": "Sitzungen durchsuchen", + "home.sessions.search.placeholder.scoped": "Sitzungen in {{scope}} durchsuchen", + "home.sessions.search.sessions": "Sitzungen", + "home.sessions.search.noResults": "Keine Sitzungen für {{query}} gefunden", + "home.sessions.empty": "Hier ist noch nichts", + "home.sessions.empty.description": "Erstellen Sie eine Sitzung, um loszulegen", + "home.sessions.group.today": "Heute", + "home.sessions.group.yesterday": "Gestern", + "home.sessions.group.older": "Älter", + "home.providerTip": + "Verbinde dich mit über 75 Anbietern, um weitere Modelle wie Claude, GPT, Gemini und andere zu nutzen", "session.tab.session": "Sitzung", "session.tab.review": "Überprüfung", "session.tab.context": "Kontext", + "session.tab.unknown": "Unbekannte Sitzung", "session.panel.reviewAndFiles": "Überprüfung und Dateien", + "session.error.notFound": "Diese Sitzung wurde nicht gefunden", + "session.error.notFound.description": + "Dieser Tab verweist auf eine Sitzung, die auf diesem Server nicht mehr existiert.", + "session.error.notFound.closeTab": "Tab schließen", + "session.error.serverConnection": "Verbindung zu diesem Server nicht möglich", "session.review.filesChanged": "{{count}} Dateien geändert", "session.review.change.one": "Änderung", "session.review.change.other": "Änderungen", @@ -474,6 +586,8 @@ export const dict = { "session.review.noSnapshot": "Snapshot-Tracking ist in der Konfiguration deaktiviert, daher sind Sitzungsänderungen nicht verfügbar", "session.review.noChanges": "Keine Änderungen", + "session.review.noUncommittedChanges": "Noch keine nicht committeten Änderungen", + "session.review.noBranchChanges": "Noch keine Branch-Änderungen", "session.files.selectToOpen": "Datei zum Öffnen auswählen", "session.files.all": "Alle Dateien", "session.files.empty": "Keine Dateien", @@ -487,6 +601,10 @@ export const dict = { "session.todo.title": "Aufgaben", "session.todo.collapse": "Einklappen", "session.todo.expand": "Ausklappen", + "session.question.minimize": "Frage minimieren", + "session.question.restore": "Frage wiederherstellen", + "session.question.pending.one": "{{count}} ausstehende Frage", + "session.question.pending.other": "{{count}} ausstehende Fragen", "session.followupDock.summary.one": "{{count}} Nachricht in der Warteschlange", "session.followupDock.summary.other": "{{count}} Nachrichten in der Warteschlange", "session.followupDock.sendNow": "Jetzt senden", @@ -499,9 +617,16 @@ export const dict = { "session.revertDock.expand": "Zurückgesetzte Nachrichten ausklappen", "session.revertDock.restore": "Nachricht wiederherstellen", "session.new.title": "Baue, was du willst", + "session.new.project.new": "Neues Projekt", + "session.new.project.search": "Projekte durchsuchen", + "session.new.project.add": "Projekt hinzufügen", "session.new.worktree.main": "Haupt-Branch", "session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})", "session.new.worktree.create": "Neuen Worktree erstellen", + "session.new.workspace.runIn": "Sitzung ausführen in", + "session.new.workspace.triggerLocal": "Lokal", + "session.new.workspace.local": "Lokales Repository", + "session.new.workspace.existing": "Arbeitsbereich…", "session.new.lastModified": "Zuletzt geändert", "session.header.search.placeholder": "{{project}} durchsuchen", "session.header.searchFiles": "Dateien suchen", @@ -566,6 +691,8 @@ export const dict = { "sidebar.project.recentSessions": "Letzte Sitzungen", "sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen", "sidebar.project.clearNotifications": "Benachrichtigungen löschen", + "sidebar.empty.title": "Keine Projekte geöffnet", + "sidebar.empty.description": "Öffnen Sie ein Projekt, um loszulegen", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Server", @@ -575,6 +702,7 @@ export const dict = { "settings.desktop.wsl.title": "WSL-Integration", "settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.", "settings.general.section.appearance": "Erscheinungsbild", + "settings.general.section.advanced": "Erweitert", "settings.general.section.notifications": "Systembenachrichtigungen", "settings.general.section.updates": "Updates", "settings.general.section.sounds": "Soundeffekte", @@ -582,6 +710,11 @@ export const dict = { "settings.general.section.display": "Anzeige", "settings.general.row.language.title": "Sprache", "settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern", + "settings.general.row.shell.title": "Terminal-Shell", + "settings.general.row.shell.description": + "Wählen Sie die Shell für Ihr Terminal aus. Kompatible Shells werden auch für Tool-Aufrufe von Agenten verwendet.", + "settings.general.row.shell.autoDefault": "Automatisch (Standard)", + "settings.general.row.shell.terminalOnly": "nur Terminal", "settings.general.row.appearance.title": "Erscheinungsbild", "settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht", "settings.general.row.colorScheme.title": "Farbschema", @@ -591,8 +724,8 @@ export const dict = { "settings.general.row.theme.description": "Das Thema von OpenCode anpassen.", "settings.general.row.font.title": "Code-Schriftart", "settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Terminalschriftart", + "settings.general.row.terminalFont.description": "Passe die im Terminal verwendete Schriftart an", "settings.general.row.uiFont.title": "UI-Schriftart", "settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen", "settings.general.row.followup.title": "Verhalten bei Folgefragen", @@ -600,6 +733,23 @@ export const dict = { "Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten", "settings.general.row.followup.option.queue": "Warteschlange", "settings.general.row.followup.option.steer": "Steuern", + "settings.general.row.showFileTree.title": "Dateibaum", + "settings.general.row.showFileTree.description": "Dateibaum in Sitzungen anzeigen", + "settings.general.row.showNavigation.title": "Navigationssteuerung", + "settings.general.row.showNavigation.description": + "Schaltflächen zum Vor- und Zurücknavigieren in der Desktop-Titelleiste anzeigen", + "settings.general.row.showSearch.title": "Befehlspalette", + "settings.general.row.showSearch.description": + "Schaltfläche für Suche und Befehlspalette in der Titelleiste anzeigen", + "settings.general.row.showTerminal.title": "Terminal", + "settings.general.row.showTerminal.description": "Terminal-Schaltfläche in der Desktop-Titelleiste anzeigen", + "settings.general.row.showStatus.title": "Serverstatus", + "settings.general.row.showStatus.description": "Serverstatus-Schaltfläche in der Titelleiste anzeigen", + "settings.general.row.mobileTitlebarBottom.title": "Navigation unten", + "settings.general.row.mobileTitlebarBottom.description": + "Titelleiste und Sitzungs-Tabs auf Mobilgeräten am unteren Bildschirmrand platzieren", + "settings.general.row.showCustomAgents.title": "Benutzerdefinierte Agenten", + "settings.general.row.showCustomAgents.description": "Agentenauswahl im Eingabebereich anzeigen", "settings.general.row.reasoningSummaries.title": "Reasoning-Zusammenfassungen anzeigen", "settings.general.row.reasoningSummaries.description": "Zusammenfassungen des Modell-Reasonings in der Timeline anzeigen", @@ -609,6 +759,11 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen", "settings.general.row.editToolPartsExpanded.description": "Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen", + "settings.general.row.newLayoutDesigns.title": "Neues Layout und Design", + "settings.general.row.newLayoutDesigns.description": + "Neu gestaltetes Layout, Startseite, Eingabebereich und Sitzungsoberfläche aktivieren", + "settings.general.row.pinchZoom.title": "Zoom per Fingergeste", + "settings.general.row.pinchZoom.description": "Zoomen per Zwei-Finger- und Ctrl-Scroll-Geste erlauben", "settings.general.row.wayland.title": "Natives Wayland verwenden", "settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.", "settings.general.row.wayland.tooltip": @@ -621,6 +776,8 @@ export const dict = { "settings.updates.row.check.description": "Manuell nach Updates suchen und installieren, wenn verfügbar", "settings.updates.action.checkNow": "Jetzt prüfen", "settings.updates.action.checking": "Wird geprüft...", + "settings.updates.action.downloading": "Wird heruntergeladen...", + "settings.updates.action.installing": "Wird installiert...", "settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand", "settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.", "sound.option.none": "Keine", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 3da8183bb7e7..9d122447a8d0 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -287,6 +287,11 @@ export const dict = { "prompt.context.removeActiveFile": "Remove active file from context", "prompt.context.removeFile": "Remove file from context", "prompt.action.attachFile": "Add files", + "prompt.menu.addImagesAndFiles": "Add files and more", + "prompt.menu.imagesAndFiles": "Images and files", + "prompt.menu.commands": "Commands", + "prompt.menu.context": "Context", + "prompt.menu.shellCommand": "Shell command", "prompt.attachment.remove": "Remove attachment", "prompt.action.send": "Send", "prompt.action.stop": "Stop", @@ -622,6 +627,7 @@ export const dict = { "home.sessions.group.today": "Today", "home.sessions.group.yesterday": "Yesterday", "home.sessions.group.older": "Older", + "home.providerTip": "Connect to 75+ providers to use other models, including Claude, GPT, Gemini, etc", "session.tab.session": "Session", "session.tab.review": "Review", @@ -703,6 +709,9 @@ export const dict = { "session.header.open.finder": "Finder", "session.header.open.fileExplorer": "File Explorer", "session.header.open.fileManager": "File Manager", + "session.header.reveal.finder": "Reveal in Finder", + "session.header.reveal.fileExplorer": "Reveal in File Explorer", + "session.header.reveal.containingFolder": "Open containing folder", "session.header.open.app.vscode": "VS Code", "session.header.open.app.cursor": "Cursor", "session.header.open.app.zed": "Zed", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index a6371d7312a0..0610f2de00cf 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -23,6 +23,9 @@ export const dict = { "command.sidebar.toggle": "Alternar barra lateral", "command.project.open": "Abrir proyecto", + "command.project.previous": "Proyecto anterior", + "command.project.next": "Siguiente proyecto", + "command.project.index": "Cambiar al proyecto {{index}}", "command.provider.connect": "Conectar proveedor", "command.server.switch": "Cambiar servidor", "command.settings.open": "Abrir ajustes", @@ -45,6 +48,7 @@ export const dict = { "command.session.new": "Nueva sesión", "command.file.open": "Abrir archivo", "command.tab.close": "Cerrar pestaña", + "command.tab.reopenClosed": "Reabrir pestaña cerrada", "command.context.addSelection": "Añadir selección al contexto", "command.context.addSelection.description": "Añadir las líneas seleccionadas del archivo actual", "command.input.focus": "Enfocar entrada", @@ -208,6 +212,11 @@ export const dict = { "model.input.video": "video", "model.input.pdf": "pdf", "model.tooltip.allows": "Permite: {{inputs}}", + "model.tooltip.context.label": "Contexto", + "model.tooltip.inputs": "Entradas", + "model.tooltip.model": "Modelo", + "model.tooltip.provider": "Proveedor", + "model.tooltip.reasoning": "Razonamiento", "model.tooltip.reasoning.allowed": "Permite razonamiento", "model.tooltip.reasoning.none": "Sin razonamiento", "model.tooltip.context": "Límite de contexto {{limit}}", @@ -218,6 +227,7 @@ export const dict = { "common.loading": "Cargando", "common.loading.ellipsis": "...", "common.cancel": "Cancelar", + "common.clear": "Limpiar", "common.connect": "Conectar", "common.disconnect": "Desconectar", "common.continue": "Enviar", @@ -235,6 +245,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc para salir", + "session.child.promptDisabled": "No se pueden enviar prompts a las sesiones de subagentes.", + "session.child.backToParent": "Volver a la sesión principal.", "prompt.example.1": "Arreglar un TODO en el código", "prompt.example.2": "¿Cuál es el stack tecnológico de este proyecto?", @@ -274,6 +286,11 @@ export const dict = { "prompt.context.removeActiveFile": "Eliminar archivo activo del contexto", "prompt.context.removeFile": "Eliminar archivo del contexto", "prompt.action.attachFile": "Adjuntar archivo", + "prompt.menu.addImagesAndFiles": "Añadir archivos y más", + "prompt.menu.imagesAndFiles": "Imágenes y archivos", + "prompt.menu.commands": "Comandos", + "prompt.menu.context": "Contexto", + "prompt.menu.shellCommand": "Comando de shell", "prompt.attachment.remove": "Eliminar adjunto", "prompt.action.send": "Enviar", "prompt.action.stop": "Detener", @@ -341,6 +358,75 @@ export const dict = { "dialog.server.menu.delete": "Eliminar", "dialog.server.current": "Servidor actual", "dialog.server.status.default": "Predeterminado", + "wsl.server.add": "Añadir servidor WSL", + "wsl.server.addShort": "Añadir WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "Servidor WSL", + "wsl.server.retryStart": "Reintentar el inicio", + "wsl.server.updating": "Actualizando...", + "wsl.onboarding.step.distro": "Elegir distribución", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "Comprobando WSL...", + "wsl.onboarding.restartRequired": "Es necesario reiniciar Windows para terminar de instalar WSL.", + "wsl.onboarding.ready": "WSL está listo.", + "wsl.onboarding.required": "Se necesita WSL para continuar.", + "wsl.onboarding.checkingDistros": "Comprobando distribuciones...", + "wsl.onboarding.installingDistro": "Instalando {{distro}}...", + "wsl.onboarding.checkingDistro": "Comprobando {{distro}}...", + "wsl.onboarding.listingDistros": "Listando distribuciones...", + "wsl.onboarding.distroReady": "La distribución {{distro}} está lista.", + "wsl.onboarding.distroNotInstalled": "La distribución {{distro}} aún no está instalada.", + "wsl.onboarding.openDistroOnce": "Abre {{distro}} una vez para completar la configuración.", + "wsl.onboarding.finishingDistro": "Completando la configuración de {{distro}}.", + "wsl.onboarding.pickDistro": "Elige una distribución o instala una abajo.", + "wsl.onboarding.checkingOpencode": "Comprobando OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "Comprobando OpenCode en {{distro}}...", + "wsl.onboarding.updatingOpencode": "Actualizando OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "Actualizando OpenCode en {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "Actualiza OpenCode en {{distro}}.", + "wsl.onboarding.updateOpencode": "Actualizar OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode está listo en {{distro}}.", + "wsl.onboarding.opencodeReady": "OpenCode está listo.", + "wsl.onboarding.installOpencodeIn": "Instala OpenCode en {{distro}}.", + "wsl.onboarding.installOpencode": "Instalar OpenCode", + "wsl.onboarding.chooseDistroFirst": "Elige primero una distribución.", + "wsl.onboarding.loadFailed": "No se pudo cargar el estado de WSL.", + "wsl.onboarding.loading": "Cargando...", + "wsl.onboarding.installedDistros": "Distribuciones instaladas", + "wsl.onboarding.checkAgain": "Comprobar de nuevo", + "wsl.onboarding.distroStatus.ready": "Lista", + "wsl.onboarding.distroStatus.checking": "Comprobando...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode no está instalado", + "wsl.onboarding.distroStatus.missingTools": "Faltan bash y curl", + "wsl.onboarding.distroStatus.unsupported": "No compatible · Usa WSL 2", + "wsl.onboarding.needAnotherDistro": "¿Necesitas otra distribución?", + "wsl.onboarding.needAnotherDistroHint": "Instala una distribución de Linux desde el catálogo de WSL", + "wsl.onboarding.wslNotInstalled.title": "WSL no está instalado", + "wsl.onboarding.wslNotInstalled.description": + "Se necesita WSL (Subsistema de Windows para Linux) para que OpenCode pueda añadir un servidor WSL", + "wsl.onboarding.wslUnavailable.title": "WSL no disponible", + "wsl.onboarding.wslUnavailable.description": "OpenCode no pudo comprobar WSL en este equipo.", + "wsl.onboarding.installWsl": "Instalar WSL", + "wsl.onboarding.windowsRestartRequired": + "Reinicia Windows para terminar de instalar WSL y, después, vuelve a abrir OpenCode.", + "wsl.onboarding.next": "Siguiente", + "wsl.onboarding.refresh": "Actualizar", + "wsl.onboarding.allDistrosAdded": "Ya se han añadido todas las distribuciones instaladas.", + "wsl.onboarding.noDistros": "Aún no se han detectado distribuciones.", + "wsl.onboarding.install": "Instalar", + "wsl.onboarding.installing": "Instalando...", + "wsl.onboarding.installDistro": "Instalar distribución", + "wsl.onboarding.searchDistros": "Buscar distribuciones", + "wsl.onboarding.wsl2Required": "Se necesita WSL 2.", + "wsl.onboarding.toolsRequired": "Esta distribución necesita bash y curl.", + "wsl.onboarding.openTerminal": "Abrir terminal", + "wsl.onboarding.path": "Ruta: {{path}}", + "wsl.onboarding.notFound": "no encontrado", + "wsl.onboarding.version": "Versión: {{version}}", + "wsl.onboarding.unknown": "desconocida", + "wsl.onboarding.desktopVersion": "escritorio {{version}}", + "wsl.onboarding.versionMismatch": "La versión instalada no coincide con la versión de la aplicación de escritorio.", + "wsl.onboarding.adding": "Añadiendo...", "dialog.project.edit.title": "Editar proyecto", "dialog.project.edit.name": "Nombre", @@ -405,6 +491,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", @@ -453,10 +540,12 @@ export const dict = { "error.page.title": "Algo salió mal", "error.page.description": "Ocurrió un error al cargar la aplicación.", + "error.page.description.localServerStartup": "Ocurrió un error al iniciar el servidor local.", "error.page.details.label": "Detalles del error", "error.page.action.restart": "Reiniciar", "error.page.action.report": "Informar error", "error.page.action.reported": "Error informado", + "error.page.action.exportLogs": "Exportar registros", "error.page.action.checking": "Comprobando...", "error.page.action.checkUpdates": "Buscar actualizaciones", "error.page.action.updateTo": "Actualizar a {{version}}", @@ -505,11 +594,33 @@ export const dict = { "home.recentProjects": "Proyectos recientes", "home.empty.title": "Sin proyectos recientes", "home.empty.description": "Empieza abriendo un proyecto local", + "home.title": "Inicio", + "home.projects": "Proyectos", + "home.project.add": "Añadir proyecto", + "home.recentlyClosed": "Proyectos cerrados recientemente", + "home.server.collapse": "Contraer proyectos del servidor", + "home.server.expand": "Expandir proyectos del servidor", + "home.sessions.search.placeholder": "Buscar sesiones", + "home.sessions.search.placeholder.scoped": "Buscar sesiones en {{scope}}", + "home.sessions.search.sessions": "Sesiones", + "home.sessions.search.noResults": "No se encontraron sesiones para {{query}}", + "home.sessions.empty": "Aún no hay nada", + "home.sessions.empty.description": "Crea una sesión para empezar", + "home.sessions.group.today": "Hoy", + "home.sessions.group.yesterday": "Ayer", + "home.sessions.group.older": "Anteriores", + "home.providerTip": + "Conéctate a más de 75 proveedores para usar otros modelos, como Claude, GPT, Gemini y muchos más", "session.tab.session": "Sesión", "session.tab.review": "Revisión", "session.tab.context": "Contexto", + "session.tab.unknown": "Sesión desconocida", "session.panel.reviewAndFiles": "Revisión y archivos", + "session.error.notFound": "No se encuentra esta sesión", + "session.error.notFound.description": "Esta pestaña apunta a una sesión que ya no existe en este servidor.", + "session.error.notFound.closeTab": "Cerrar pestaña", + "session.error.serverConnection": "No se puede conectar a este servidor", "session.review.filesChanged": "{{count}} Archivos Cambiados", "session.review.change.one": "Cambio", "session.review.change.other": "Cambios", @@ -519,6 +630,8 @@ export const dict = { "session.review.noSnapshot": "El seguimiento de instantáneas está deshabilitado en la configuración, por lo que los cambios de sesión no están disponibles", "session.review.noChanges": "Sin cambios", + "session.review.noUncommittedChanges": "Aún no hay cambios sin confirmar", + "session.review.noBranchChanges": "Aún no hay cambios en la rama", "session.files.selectToOpen": "Selecciona un archivo para abrir", "session.files.all": "Todos los archivos", @@ -535,6 +648,10 @@ export const dict = { "session.todo.title": "Tareas", "session.todo.collapse": "Contraer", "session.todo.expand": "Expandir", + "session.question.minimize": "Minimizar pregunta", + "session.question.restore": "Restaurar pregunta", + "session.question.pending.one": "{{count}} pregunta pendiente", + "session.question.pending.other": "{{count}} preguntas pendientes", "session.followupDock.summary.one": "{{count}} mensaje en cola", "session.followupDock.summary.other": "{{count}} mensajes en cola", "session.followupDock.sendNow": "Enviar ahora", @@ -548,9 +665,16 @@ export const dict = { "session.revertDock.restore": "Restaurar mensaje", "session.new.title": "Construye lo que quieras", + "session.new.project.new": "Nuevo proyecto", + "session.new.project.search": "Buscar proyectos", + "session.new.project.add": "Añadir proyecto", "session.new.worktree.main": "Rama principal", "session.new.worktree.mainWithBranch": "Rama principal ({{branch}})", "session.new.worktree.create": "Crear nuevo árbol de trabajo", + "session.new.workspace.runIn": "Ejecutar sesión en", + "session.new.workspace.triggerLocal": "Local", + "session.new.workspace.local": "Repositorio local", + "session.new.workspace.existing": "Espacio de trabajo…", "session.new.lastModified": "Última modificación", "session.header.search.placeholder": "Buscar {{project}}", @@ -621,6 +745,8 @@ export const dict = { "sidebar.project.recentSessions": "Sesiones recientes", "sidebar.project.viewAllSessions": "Ver todas las sesiones", "sidebar.project.clearNotifications": "Borrar notificaciones", + "sidebar.empty.title": "No hay proyectos abiertos", + "sidebar.empty.description": "Abre un proyecto para empezar", "app.name.desktop": "OpenCode Desktop", @@ -633,6 +759,7 @@ export const dict = { "settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.", "settings.general.section.appearance": "Apariencia", + "settings.general.section.advanced": "Avanzado", "settings.general.section.notifications": "Notificaciones del sistema", "settings.general.section.updates": "Actualizaciones", "settings.general.section.sounds": "Efectos de sonido", @@ -641,6 +768,11 @@ export const dict = { "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode", + "settings.general.row.shell.title": "Shell del terminal", + "settings.general.row.shell.description": + "Elige el shell que se usará en el terminal. Los shells compatibles también se usarán para las llamadas a herramientas del agente.", + "settings.general.row.shell.autoDefault": "Automático (Predeterminado)", + "settings.general.row.shell.terminalOnly": "solo en el terminal", "settings.general.row.appearance.title": "Apariencia", "settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo", "settings.general.row.colorScheme.title": "Esquema de color", @@ -649,8 +781,8 @@ export const dict = { "settings.general.row.theme.description": "Personaliza el tema de OpenCode.", "settings.general.row.font.title": "Fuente de código", "settings.general.row.font.description": "Personaliza la fuente usada en bloques de código", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Fuente del terminal", + "settings.general.row.terminalFont.description": "Personaliza la fuente utilizada en el terminal", "settings.general.row.uiFont.title": "Fuente de la interfaz", "settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz", "settings.general.row.followup.title": "Comportamiento de seguimiento", @@ -658,6 +790,23 @@ export const dict = { "Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola", "settings.general.row.followup.option.queue": "Cola", "settings.general.row.followup.option.steer": "Dirigir", + "settings.general.row.showFileTree.title": "Árbol de archivos", + "settings.general.row.showFileTree.description": "Mostrar el panel del árbol de archivos en las sesiones", + "settings.general.row.showNavigation.title": "Controles de navegación", + "settings.general.row.showNavigation.description": + "Mostrar los botones para retroceder y avanzar en la barra de título de la aplicación de escritorio", + "settings.general.row.showSearch.title": "Paleta de comandos", + "settings.general.row.showSearch.description": + "Mostrar el botón de búsqueda y paleta de comandos en la barra de título", + "settings.general.row.showTerminal.title": "Terminal", + "settings.general.row.showTerminal.description": "Mostrar el botón del terminal en la barra de título", + "settings.general.row.showStatus.title": "Estado del servidor", + "settings.general.row.showStatus.description": "Mostrar el botón de estado del servidor en la barra de título", + "settings.general.row.mobileTitlebarBottom.title": "Navegación inferior", + "settings.general.row.mobileTitlebarBottom.description": + "Colocar la barra de título y las pestañas de sesión en la parte inferior de la pantalla en dispositivos móviles", + "settings.general.row.showCustomAgents.title": "Agentes personalizados", + "settings.general.row.showCustomAgents.description": "Mostrar el selector de agentes en el editor", "settings.general.row.reasoningSummaries.title": "Mostrar resúmenes de razonamiento", "settings.general.row.reasoningSummaries.description": "Mostrar resúmenes del razonamiento del modelo en la línea de tiempo", @@ -667,6 +816,12 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición", "settings.general.row.editToolPartsExpanded.description": "Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo", + "settings.general.row.newLayoutDesigns.title": "Nuevo diseño y estilos", + "settings.general.row.newLayoutDesigns.description": + "Activar el nuevo diseño de la interfaz, la página de inicio, el editor y las sesiones", + "settings.general.row.pinchZoom.title": "Pellizcar para ampliar", + "settings.general.row.pinchZoom.description": + "Permitir ampliar con el gesto de pellizco del panel táctil y con Ctrl-scroll", "settings.general.row.wayland.title": "Usar Wayland nativo", "settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.", "settings.general.row.wayland.tooltip": @@ -682,6 +837,8 @@ export const dict = { "settings.updates.row.check.description": "Buscar actualizaciones manualmente e instalarlas si hay alguna", "settings.updates.action.checkNow": "Buscar ahora", "settings.updates.action.checking": "Buscando...", + "settings.updates.action.downloading": "Descargando...", + "settings.updates.action.installing": "Instalando...", "settings.updates.toast.latest.title": "Estás al día", "settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.", "sound.option.none": "Ninguno", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 7ebd34e66ba5..3ed47d5dba6e 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -21,6 +21,9 @@ export const dict = { "theme.scheme.dark": "Sombre", "command.sidebar.toggle": "Basculer la barre latérale", "command.project.open": "Ouvrir un projet", + "command.project.previous": "Projet précédent", + "command.project.next": "Projet suivant", + "command.project.index": "Passer au projet {{index}}", "command.provider.connect": "Connecter un fournisseur", "command.server.switch": "Changer de serveur", "command.settings.open": "Ouvrir les paramètres", @@ -39,6 +42,7 @@ export const dict = { "command.session.new": "Nouvelle session", "command.file.open": "Ouvrir un fichier", "command.tab.close": "Fermer l'onglet", + "command.tab.reopenClosed": "Rouvrir l'onglet fermé", "command.context.addSelection": "Ajouter la sélection au contexte", "command.context.addSelection.description": "Ajouter les lignes sélectionnées du fichier actuel", "command.input.focus": "Focus sur l'entrée", @@ -193,6 +197,11 @@ export const dict = { "model.input.video": "vidéo", "model.input.pdf": "pdf", "model.tooltip.allows": "Autorise : {{inputs}}", + "model.tooltip.context.label": "Contexte", + "model.tooltip.inputs": "Entrées", + "model.tooltip.model": "Modèle", + "model.tooltip.provider": "Fournisseur", + "model.tooltip.reasoning": "Raisonnement", "model.tooltip.reasoning.allowed": "Autorise le raisonnement", "model.tooltip.reasoning.none": "Sans raisonnement", "model.tooltip.context": "Limite de contexte {{limit}}", @@ -202,6 +211,7 @@ export const dict = { "common.loading": "Chargement", "common.loading.ellipsis": "...", "common.cancel": "Annuler", + "common.clear": "Effacer", "common.connect": "Connecter", "common.disconnect": "Déconnecter", "common.continue": "Soumettre", @@ -218,6 +228,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc pour quitter", + "session.child.promptDisabled": "Les sessions de sous-agents ne peuvent pas recevoir de messages.", + "session.child.backToParent": "Revenir à la session principale.", "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", @@ -255,6 +267,11 @@ export const dict = { "prompt.context.removeActiveFile": "Retirer le fichier actif du contexte", "prompt.context.removeFile": "Retirer le fichier du contexte", "prompt.action.attachFile": "Joindre un fichier", + "prompt.menu.addImagesAndFiles": "Ajouter des fichiers et plus encore", + "prompt.menu.imagesAndFiles": "Images et fichiers", + "prompt.menu.commands": "Commandes", + "prompt.menu.context": "Contexte", + "prompt.menu.shellCommand": "Commande shell", "prompt.attachment.remove": "Supprimer la pièce jointe", "prompt.action.send": "Envoyer", "prompt.action.stop": "Arrêter", @@ -315,6 +332,75 @@ export const dict = { "dialog.server.menu.delete": "Supprimer", "dialog.server.current": "Serveur actuel", "dialog.server.status.default": "Défaut", + "wsl.server.add": "Ajouter un serveur WSL", + "wsl.server.addShort": "Ajouter WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "Serveur WSL", + "wsl.server.retryStart": "Réessayer de démarrer", + "wsl.server.updating": "Mise à jour...", + "wsl.onboarding.step.distro": "Choisir une distribution", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "Vérification de WSL...", + "wsl.onboarding.restartRequired": "Windows doit redémarrer pour terminer l'installation de WSL.", + "wsl.onboarding.ready": "WSL est prêt.", + "wsl.onboarding.required": "WSL est requis pour continuer.", + "wsl.onboarding.checkingDistros": "Vérification des distributions...", + "wsl.onboarding.installingDistro": "Installation de {{distro}}...", + "wsl.onboarding.checkingDistro": "Vérification de {{distro}}...", + "wsl.onboarding.listingDistros": "Récupération de la liste des distributions...", + "wsl.onboarding.distroReady": "{{distro}} est prête.", + "wsl.onboarding.distroNotInstalled": "{{distro}} n'est pas encore installée.", + "wsl.onboarding.openDistroOnce": "Ouvrez {{distro}} une fois pour terminer la configuration.", + "wsl.onboarding.finishingDistro": "Finalisation de la configuration de {{distro}}.", + "wsl.onboarding.pickDistro": "Choisissez une distribution ou installez-en une ci-dessous.", + "wsl.onboarding.checkingOpencode": "Vérification d'OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "Vérification d'OpenCode dans {{distro}}...", + "wsl.onboarding.updatingOpencode": "Mise à jour d'OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "Mise à jour d'OpenCode dans {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "Mettez à jour OpenCode dans {{distro}}.", + "wsl.onboarding.updateOpencode": "Mettre à jour OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode est prêt dans {{distro}}.", + "wsl.onboarding.opencodeReady": "OpenCode est prêt.", + "wsl.onboarding.installOpencodeIn": "Installez OpenCode dans {{distro}}.", + "wsl.onboarding.installOpencode": "Installer OpenCode", + "wsl.onboarding.chooseDistroFirst": "Choisissez d'abord une distribution.", + "wsl.onboarding.loadFailed": "Impossible de charger l'état de WSL.", + "wsl.onboarding.loading": "Chargement...", + "wsl.onboarding.installedDistros": "Distributions installées", + "wsl.onboarding.checkAgain": "Vérifier à nouveau", + "wsl.onboarding.distroStatus.ready": "Prête", + "wsl.onboarding.distroStatus.checking": "Vérification...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode non installé", + "wsl.onboarding.distroStatus.missingTools": "bash et curl manquants", + "wsl.onboarding.distroStatus.unsupported": "Non prise en charge · Utilisez WSL 2", + "wsl.onboarding.needAnotherDistro": "Besoin d'une autre distribution ?", + "wsl.onboarding.needAnotherDistroHint": "Installez une distribution Linux depuis le catalogue WSL", + "wsl.onboarding.wslNotInstalled.title": "WSL n'est pas installé", + "wsl.onboarding.wslNotInstalled.description": + "WSL (Windows Subsystem for Linux) est requis avant qu'OpenCode puisse ajouter un serveur WSL", + "wsl.onboarding.wslUnavailable.title": "WSL indisponible", + "wsl.onboarding.wslUnavailable.description": "OpenCode n'a pas pu vérifier WSL sur cet appareil.", + "wsl.onboarding.installWsl": "Installer WSL", + "wsl.onboarding.windowsRestartRequired": + "Redémarrez Windows pour terminer l'installation de WSL, puis rouvrez OpenCode.", + "wsl.onboarding.next": "Suivant", + "wsl.onboarding.refresh": "Actualiser", + "wsl.onboarding.allDistrosAdded": "Toutes les distributions installées ont déjà été ajoutées.", + "wsl.onboarding.noDistros": "Aucune distribution détectée pour l'instant.", + "wsl.onboarding.install": "Installer", + "wsl.onboarding.installing": "Installation...", + "wsl.onboarding.installDistro": "Installer une distribution", + "wsl.onboarding.searchDistros": "Rechercher des distributions", + "wsl.onboarding.wsl2Required": "WSL 2 est requis.", + "wsl.onboarding.toolsRequired": "Cette distribution nécessite bash et curl.", + "wsl.onboarding.openTerminal": "Ouvrir le terminal", + "wsl.onboarding.path": "Chemin : {{path}}", + "wsl.onboarding.notFound": "introuvable", + "wsl.onboarding.version": "Version : {{version}}", + "wsl.onboarding.unknown": "inconnue", + "wsl.onboarding.desktopVersion": "desktop {{version}}", + "wsl.onboarding.versionMismatch": "La version installée ne correspond pas à celle de l'application desktop.", + "wsl.onboarding.adding": "Ajout...", "dialog.project.edit.title": "Modifier le projet", "dialog.project.edit.name": "Nom", "dialog.project.edit.icon": "Icône", @@ -373,6 +459,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", "toast.language.title": "Langue", @@ -410,10 +497,12 @@ export const dict = { "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.description.localServerStartup": "Une erreur s'est produite lors du démarrage du serveur local.", "error.page.details.label": "Détails de l'erreur", "error.page.action.restart": "Redémarrer", "error.page.action.report": "Signaler l'erreur", "error.page.action.reported": "Erreur signalée", + "error.page.action.exportLogs": "Exporter les journaux", "error.page.action.checking": "Vérification...", "error.page.action.checkUpdates": "Vérifier les mises à jour", "error.page.action.updateTo": "Mettre à jour vers {{version}}", @@ -458,16 +547,40 @@ export const dict = { "home.recentProjects": "Projets récents", "home.empty.title": "Aucun projet récent", "home.empty.description": "Commencez par ouvrir un projet local", + "home.title": "Accueil", + "home.projects": "Projets", + "home.project.add": "Ajouter un projet", + "home.recentlyClosed": "Récemment fermés", + "home.server.collapse": "Réduire les projets du serveur", + "home.server.expand": "Développer les projets du serveur", + "home.sessions.search.placeholder": "Rechercher des sessions", + "home.sessions.search.placeholder.scoped": "Rechercher les sessions dans {{scope}}", + "home.sessions.search.sessions": "Sessions", + "home.sessions.search.noResults": "Aucune session trouvée pour {{query}}", + "home.sessions.empty": "Rien ici pour l'instant", + "home.sessions.empty.description": "Créez une session pour commencer", + "home.sessions.group.today": "Aujourd'hui", + "home.sessions.group.yesterday": "Hier", + "home.sessions.group.older": "Plus anciennes", + "home.providerTip": + "Connectez-vous à plus de 75 fournisseurs pour utiliser d’autres modèles, notamment Claude, GPT, Gemini, etc.", "session.tab.session": "Session", "session.tab.review": "Revue", "session.tab.context": "Contexte", + "session.tab.unknown": "Session inconnue", "session.panel.reviewAndFiles": "Revue et fichiers", + "session.error.notFound": "Cette session est introuvable", + "session.error.notFound.description": "Cet onglet pointe vers une session qui n'existe plus sur ce serveur.", + "session.error.notFound.closeTab": "Fermer l'onglet", + "session.error.serverConnection": "Impossible de se connecter à ce serveur", "session.review.filesChanged": "{{count}} fichiers modifiés", "session.review.change.one": "Modification", "session.review.change.other": "Modifications", "session.review.loadingChanges": "Chargement des modifications...", "session.review.empty": "Aucune modification dans cette session pour l'instant", "session.review.noChanges": "Aucune modification", + "session.review.noUncommittedChanges": "Aucune modification non validée pour l'instant", + "session.review.noBranchChanges": "Aucune modification de branche pour l'instant", "session.review.noVcs": "Aucun système de contrôle de version Git détecté, modifications non affichées", "session.review.noSnapshot": "Le suivi des instantanés est désactivé dans la configuration, les modifications de session sont donc indisponibles", @@ -484,6 +597,10 @@ export const dict = { "session.todo.title": "Tâches", "session.todo.collapse": "Réduire", "session.todo.expand": "Développer", + "session.question.minimize": "Réduire la question", + "session.question.restore": "Restaurer la question", + "session.question.pending.one": "{{count}} question en attente", + "session.question.pending.other": "{{count}} questions en attente", "session.followupDock.summary.one": "{{count}} message en file d'attente", "session.followupDock.summary.other": "{{count}} messages en file d'attente", "session.followupDock.sendNow": "Envoyer maintenant", @@ -496,9 +613,16 @@ export const dict = { "session.revertDock.expand": "Développer les messages annulés", "session.revertDock.restore": "Restaurer le message", "session.new.title": "Créez ce que vous voulez", + "session.new.project.new": "Nouveau projet", + "session.new.project.search": "Rechercher des projets", + "session.new.project.add": "Ajouter un projet", "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.workspace.runIn": "Exécuter la session dans", + "session.new.workspace.triggerLocal": "Local", + "session.new.workspace.local": "Dépôt local", + "session.new.workspace.existing": "Espace de travail…", "session.new.lastModified": "Dernière modification", "session.header.search.placeholder": "Rechercher {{project}}", "session.header.searchFiles": "Rechercher des fichiers", @@ -564,6 +688,8 @@ export const dict = { "sidebar.project.recentSessions": "Sessions récentes", "sidebar.project.viewAllSessions": "Voir toutes les sessions", "sidebar.project.clearNotifications": "Effacer les notifications", + "sidebar.empty.title": "Aucun projet ouvert", + "sidebar.empty.description": "Ouvrez un projet pour commencer", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Bureau", "settings.section.server": "Serveur", @@ -573,6 +699,7 @@ export const dict = { "settings.desktop.wsl.title": "Intégration WSL", "settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.", "settings.general.section.appearance": "Apparence", + "settings.general.section.advanced": "Avancé", "settings.general.section.notifications": "Notifications système", "settings.general.section.updates": "Mises à jour", "settings.general.section.sounds": "Effets sonores", @@ -580,6 +707,11 @@ export const dict = { "settings.general.section.display": "Affichage", "settings.general.row.language.title": "Langue", "settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode", + "settings.general.row.shell.title": "Shell du terminal", + "settings.general.row.shell.description": + "Choisissez le shell utilisé par votre terminal. Les shells compatibles sont également utilisés pour les appels d'outils de l'agent.", + "settings.general.row.shell.autoDefault": "Auto (par défaut)", + "settings.general.row.shell.terminalOnly": "terminal uniquement", "settings.general.row.appearance.title": "Apparence", "settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil", "settings.general.row.colorScheme.title": "Schéma de couleurs", @@ -588,8 +720,8 @@ export const dict = { "settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.", "settings.general.row.font.title": "Police de code", "settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Police du terminal", + "settings.general.row.terminalFont.description": "Personnalisez la police utilisée dans le terminal", "settings.general.row.uiFont.title": "Police de l'interface", "settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface", "settings.general.row.followup.title": "Comportement de suivi", @@ -597,6 +729,24 @@ export const dict = { "Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente", "settings.general.row.followup.option.queue": "File d'attente", "settings.general.row.followup.option.steer": "Diriger", + "settings.general.row.showFileTree.title": "Arborescence des fichiers", + "settings.general.row.showFileTree.description": "Afficher le panneau d'arborescence des fichiers dans les sessions", + "settings.general.row.showNavigation.title": "Commandes de navigation", + "settings.general.row.showNavigation.description": + "Afficher les boutons précédent et suivant dans la barre de titre de l'application desktop", + "settings.general.row.showSearch.title": "Palette de commandes", + "settings.general.row.showSearch.description": + "Afficher le bouton de recherche et de palette de commandes dans la barre de titre", + "settings.general.row.showTerminal.title": "Terminal", + "settings.general.row.showTerminal.description": + "Afficher le bouton du terminal dans la barre de titre de l'application desktop", + "settings.general.row.showStatus.title": "État du serveur", + "settings.general.row.showStatus.description": "Afficher le bouton d'état du serveur dans la barre de titre", + "settings.general.row.mobileTitlebarBottom.title": "Navigation en bas", + "settings.general.row.mobileTitlebarBottom.description": + "Placer la barre de titre et les onglets de session en bas de l'écran sur mobile", + "settings.general.row.showCustomAgents.title": "Agents personnalisés", + "settings.general.row.showCustomAgents.description": "Afficher le sélecteur d'agent dans la zone de saisie", "settings.general.row.reasoningSummaries.title": "Afficher les résumés de raisonnement", "settings.general.row.reasoningSummaries.description": "Afficher les résumés de raisonnement du modèle dans la chronologie", @@ -606,6 +756,12 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit", "settings.general.row.editToolPartsExpanded.description": "Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie", + "settings.general.row.newLayoutDesigns.title": "Nouvelle mise en page et nouveaux designs", + "settings.general.row.newLayoutDesigns.description": + "Activer la nouvelle mise en page et les nouvelles interfaces d'accueil, de saisie et de session", + "settings.general.row.pinchZoom.title": "Pincer pour zoomer", + "settings.general.row.pinchZoom.description": + "Autoriser les gestes de pincement du pavé tactile et Ctrl-défilement pour zoomer", "settings.general.row.wayland.title": "Utiliser Wayland natif", "settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.", "settings.general.row.wayland.tooltip": @@ -618,6 +774,8 @@ export const dict = { "settings.updates.row.check.description": "Vérifier manuellement les mises à jour et installer si disponible", "settings.updates.action.checkNow": "Vérifier maintenant", "settings.updates.action.checking": "Vérification...", + "settings.updates.action.downloading": "Téléchargement...", + "settings.updates.action.installing": "Installation...", "settings.updates.toast.latest.title": "Vous êtes à jour", "settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.", "sound.option.none": "Aucun", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index ad5dcf5387ae..52ec83b3c4ad 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -21,6 +21,9 @@ export const dict = { "theme.scheme.dark": "ダーク", "command.sidebar.toggle": "サイドバーの切り替え", "command.project.open": "プロジェクトを開く", + "command.project.previous": "前のプロジェクト", + "command.project.next": "次のプロジェクト", + "command.project.index": "プロジェクト{{index}}に切り替え", "command.provider.connect": "プロバイダーに接続", "command.server.switch": "サーバーの切り替え", "command.settings.open": "設定を開く", @@ -39,6 +42,7 @@ export const dict = { "command.session.new": "新しいセッション", "command.file.open": "ファイルを開く", "command.tab.close": "タブを閉じる", + "command.tab.reopenClosed": "閉じたタブを再度開く", "command.context.addSelection": "選択範囲をコンテキストに追加", "command.context.addSelection.description": "現在のファイルから選択した行を追加", "command.input.focus": "入力欄にフォーカス", @@ -149,7 +153,7 @@ export const dict = { "provider.custom.field.providerID.placeholder": "myprovider", "provider.custom.field.providerID.description": "小文字、数字、ハイフン、アンダースコア", "provider.custom.field.name.label": "表示名", - "provider.custom.field.name.placeholder": "My AI Provider", + "provider.custom.field.name.placeholder": "マイAIプロバイダー", "provider.custom.field.baseURL.label": "ベースURL", "provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1", "provider.custom.field.apiKey.label": "APIキー", @@ -192,6 +196,11 @@ export const dict = { "model.input.video": "動画", "model.input.pdf": "pdf", "model.tooltip.allows": "対応: {{inputs}}", + "model.tooltip.context.label": "コンテキスト", + "model.tooltip.inputs": "入力", + "model.tooltip.model": "モデル", + "model.tooltip.provider": "プロバイダー", + "model.tooltip.reasoning": "推論", "model.tooltip.reasoning.allowed": "推論を許可", "model.tooltip.reasoning.none": "推論なし", "model.tooltip.context": "コンテキスト上限 {{limit}}", @@ -201,6 +210,7 @@ export const dict = { "common.loading": "読み込み中", "common.loading.ellipsis": "...", "common.cancel": "キャンセル", + "common.clear": "クリア", "common.connect": "接続", "common.disconnect": "切断", "common.continue": "送信", @@ -217,6 +227,8 @@ export const dict = { "prompt.mode.shell": "シェル", "prompt.mode.normal": "プロンプト", "prompt.mode.shell.exit": "escで終了", + "session.child.promptDisabled": "サブエージェントのセッションにはプロンプトを送信できません。", + "session.child.backToParent": "メインセッションに戻る。", "prompt.example.1": "コードベースのTODOを修正", "prompt.example.2": "このプロジェクトの技術スタックは何ですか?", "prompt.example.3": "壊れたテストを修正", @@ -254,6 +266,11 @@ export const dict = { "prompt.context.removeActiveFile": "コンテキストからアクティブなファイルを削除", "prompt.context.removeFile": "コンテキストからファイルを削除", "prompt.action.attachFile": "ファイルを添付", + "prompt.menu.addImagesAndFiles": "ファイルなどを追加", + "prompt.menu.imagesAndFiles": "画像とファイル", + "prompt.menu.commands": "コマンド", + "prompt.menu.context": "コンテキスト", + "prompt.menu.shellCommand": "シェルコマンド", "prompt.attachment.remove": "添付ファイルを削除", "prompt.action.send": "送信", "prompt.action.stop": "停止", @@ -313,6 +330,75 @@ export const dict = { "dialog.server.menu.delete": "削除", "dialog.server.current": "現在のサーバー", "dialog.server.status.default": "デフォルト", + "wsl.server.add": "WSLサーバーを追加", + "wsl.server.addShort": "WSLを追加", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "WSLサーバー", + "wsl.server.retryStart": "起動を再試行", + "wsl.server.updating": "更新中...", + "wsl.onboarding.step.distro": "ディストリビューションを選択", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "WSLを確認中...", + "wsl.onboarding.restartRequired": "WSLのインストールを完了するにはWindowsの再起動が必要です。", + "wsl.onboarding.ready": "WSLの準備ができました。", + "wsl.onboarding.required": "続行するにはWSLが必要です。", + "wsl.onboarding.checkingDistros": "ディストリビューションを確認中...", + "wsl.onboarding.installingDistro": "{{distro}}をインストール中...", + "wsl.onboarding.checkingDistro": "{{distro}}を確認中...", + "wsl.onboarding.listingDistros": "ディストリビューションの一覧を取得中...", + "wsl.onboarding.distroReady": "{{distro}}の準備ができました。", + "wsl.onboarding.distroNotInstalled": "{{distro}}はまだインストールされていません。", + "wsl.onboarding.openDistroOnce": "セットアップを完了するには、{{distro}}を一度開いてください。", + "wsl.onboarding.finishingDistro": "{{distro}}のセットアップを完了しています。", + "wsl.onboarding.pickDistro": "ディストリビューションを選択するか、以下からインストールしてください。", + "wsl.onboarding.checkingOpencode": "OpenCodeを確認中...", + "wsl.onboarding.checkingOpencodeIn": "{{distro}}内のOpenCodeを確認中...", + "wsl.onboarding.updatingOpencode": "OpenCodeを更新中...", + "wsl.onboarding.updatingOpencodeIn": "{{distro}}内のOpenCodeを更新中...", + "wsl.onboarding.updateOpencodeIn": "{{distro}}内のOpenCodeを更新してください。", + "wsl.onboarding.updateOpencode": "OpenCodeを更新", + "wsl.onboarding.opencodeReadyIn": "{{distro}}内のOpenCodeの準備ができました。", + "wsl.onboarding.opencodeReady": "OpenCodeの準備ができました。", + "wsl.onboarding.installOpencodeIn": "{{distro}}にOpenCodeをインストールしてください。", + "wsl.onboarding.installOpencode": "OpenCodeをインストール", + "wsl.onboarding.chooseDistroFirst": "先にディストリビューションを選択してください。", + "wsl.onboarding.loadFailed": "WSLの状態を読み込めませんでした。", + "wsl.onboarding.loading": "読み込み中...", + "wsl.onboarding.installedDistros": "インストール済みのディストリビューション", + "wsl.onboarding.checkAgain": "再確認", + "wsl.onboarding.distroStatus.ready": "準備完了", + "wsl.onboarding.distroStatus.checking": "確認中...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCodeがインストールされていません", + "wsl.onboarding.distroStatus.missingTools": "bash、curlがありません", + "wsl.onboarding.distroStatus.unsupported": "非対応 · WSL 2を使用してください", + "wsl.onboarding.needAnotherDistro": "別のディストリビューションが必要ですか?", + "wsl.onboarding.needAnotherDistroHint": "WSLカタログからLinuxディストリビューションをインストール", + "wsl.onboarding.wslNotInstalled.title": "WSLがインストールされていません", + "wsl.onboarding.wslNotInstalled.description": + "OpenCodeでWSLサーバーを追加するには、WSL (Windows Subsystem for Linux) が必要です", + "wsl.onboarding.wslUnavailable.title": "WSLを利用できません", + "wsl.onboarding.wslUnavailable.description": "このマシンのWSLを確認できませんでした。", + "wsl.onboarding.installWsl": "WSLをインストール", + "wsl.onboarding.windowsRestartRequired": + "WSLのインストールを完了するにはWindowsを再起動し、OpenCodeをもう一度開いてください。", + "wsl.onboarding.next": "次へ", + "wsl.onboarding.refresh": "更新", + "wsl.onboarding.allDistrosAdded": "インストール済みのディストリビューションはすべて追加済みです。", + "wsl.onboarding.noDistros": "ディストリビューションはまだ検出されていません。", + "wsl.onboarding.install": "インストール", + "wsl.onboarding.installing": "インストール中...", + "wsl.onboarding.installDistro": "ディストリビューションをインストール", + "wsl.onboarding.searchDistros": "ディストリビューションを検索", + "wsl.onboarding.wsl2Required": "WSL 2が必要です。", + "wsl.onboarding.toolsRequired": "このディストリビューションにはbashとcurlが必要です。", + "wsl.onboarding.openTerminal": "ターミナルを開く", + "wsl.onboarding.path": "パス: {{path}}", + "wsl.onboarding.notFound": "見つかりません", + "wsl.onboarding.version": "バージョン: {{version}}", + "wsl.onboarding.unknown": "不明", + "wsl.onboarding.desktopVersion": "デスクトップ {{version}}", + "wsl.onboarding.versionMismatch": "インストールされているバージョンがデスクトップアプリのバージョンと一致しません。", + "wsl.onboarding.adding": "追加中...", "dialog.project.edit.title": "プロジェクトを編集", "dialog.project.edit.name": "名前", "dialog.project.edit.icon": "アイコン", @@ -370,6 +456,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", "toast.language.title": "言語", @@ -406,10 +493,12 @@ export const dict = { "toast.update.action.notYet": "今はしない", "error.page.title": "問題が発生しました", "error.page.description": "アプリケーションの読み込み中にエラーが発生しました。", + "error.page.description.localServerStartup": "ローカルサーバーの起動中にエラーが発生しました。", "error.page.details.label": "エラー詳細", "error.page.action.restart": "再起動", "error.page.action.report": "エラーを報告", "error.page.action.reported": "エラーを報告しました", + "error.page.action.exportLogs": "ログをエクスポート", "error.page.action.checking": "確認中...", "error.page.action.checkUpdates": "アップデートを確認", "error.page.action.updateTo": "{{version}}にアップデート", @@ -451,10 +540,31 @@ export const dict = { "home.recentProjects": "最近のプロジェクト", "home.empty.title": "最近のプロジェクトはありません", "home.empty.description": "ローカルプロジェクトを開いて始めましょう", + "home.title": "ホーム", + "home.projects": "プロジェクト", + "home.project.add": "プロジェクトを追加", + "home.recentlyClosed": "最近閉じたプロジェクト", + "home.server.collapse": "サーバーのプロジェクトを折りたたむ", + "home.server.expand": "サーバーのプロジェクトを展開", + "home.sessions.search.placeholder": "セッションを検索", + "home.sessions.search.placeholder.scoped": "{{scope}}のセッションを検索", + "home.sessions.search.sessions": "セッション", + "home.sessions.search.noResults": "{{query}}に一致するセッションが見つかりません", + "home.sessions.empty": "まだ何もありません", + "home.sessions.empty.description": "セッションを作成して始めましょう", + "home.sessions.group.today": "今日", + "home.sessions.group.yesterday": "昨日", + "home.sessions.group.older": "それ以前", + "home.providerTip": "75以上のプロバイダーに接続して、Claude、GPT、Geminiなどの他のモデルを利用できます", "session.tab.session": "セッション", "session.tab.review": "レビュー", "session.tab.context": "コンテキスト", + "session.tab.unknown": "不明なセッション", "session.panel.reviewAndFiles": "レビューとファイル", + "session.error.notFound": "このセッションが見つかりません", + "session.error.notFound.description": "このタブが参照するセッションは、このサーバーには存在しません。", + "session.error.notFound.closeTab": "タブを閉じる", + "session.error.serverConnection": "このサーバーに接続できません", "session.review.filesChanged": "{{count}} ファイル変更", "session.review.change.one": "変更", "session.review.change.other": "変更", @@ -463,6 +573,8 @@ export const dict = { "session.review.noVcs": "Gitバージョン管理システムが検出されないため、変更は表示されません", "session.review.noSnapshot": "設定でスナップショット追跡が無効になっているため、セッションの変更は利用できません", "session.review.noChanges": "変更なし", + "session.review.noUncommittedChanges": "コミットされていない変更はまだありません", + "session.review.noBranchChanges": "ブランチの変更はまだありません", "session.files.selectToOpen": "開くファイルを選択", "session.files.all": "すべてのファイル", "session.files.empty": "ファイルなし", @@ -476,6 +588,10 @@ export const dict = { "session.todo.title": "ToDo", "session.todo.collapse": "折りたたむ", "session.todo.expand": "展開", + "session.question.minimize": "質問を最小化", + "session.question.restore": "質問を復元", + "session.question.pending.one": "{{count}}件の保留中の質問", + "session.question.pending.other": "{{count}}件の保留中の質問", "session.followupDock.summary.one": "{{count}} 件のメッセージが待機中", "session.followupDock.summary.other": "{{count}} 件のメッセージが待機中", "session.followupDock.sendNow": "今すぐ送信", @@ -488,9 +604,16 @@ export const dict = { "session.revertDock.expand": "ロールバックされたメッセージを展開", "session.revertDock.restore": "メッセージを復元", "session.new.title": "何でも作る", + "session.new.project.new": "新しいプロジェクト", + "session.new.project.search": "プロジェクトを検索", + "session.new.project.add": "プロジェクトを追加", "session.new.worktree.main": "メインブランチ", "session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})", "session.new.worktree.create": "新しいワークツリーを作成", + "session.new.workspace.runIn": "セッションの実行先", + "session.new.workspace.triggerLocal": "ローカル", + "session.new.workspace.local": "ローカルリポジトリ", + "session.new.workspace.existing": "ワークスペース…", "session.new.lastModified": "最終更新", "session.header.search.placeholder": "{{project}}を検索", "session.header.searchFiles": "ファイルを検索", @@ -554,6 +677,8 @@ export const dict = { "sidebar.project.recentSessions": "最近のセッション", "sidebar.project.viewAllSessions": "すべてのセッションを表示", "sidebar.project.clearNotifications": "通知をクリア", + "sidebar.empty.title": "開いているプロジェクトはありません", + "sidebar.empty.description": "プロジェクトを開いて始めましょう", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "デスクトップ", "settings.section.server": "サーバー", @@ -563,6 +688,7 @@ export const dict = { "settings.desktop.wsl.title": "WSL連携", "settings.desktop.wsl.description": "WindowsのWSL環境でOpenCodeサーバーを実行します。", "settings.general.section.appearance": "外観", + "settings.general.section.advanced": "詳細設定", "settings.general.section.notifications": "システム通知", "settings.general.section.updates": "アップデート", "settings.general.section.sounds": "効果音", @@ -570,6 +696,11 @@ export const dict = { "settings.general.section.display": "ディスプレイ", "settings.general.row.language.title": "言語", "settings.general.row.language.description": "OpenCodeの表示言語を変更します", + "settings.general.row.shell.title": "ターミナルシェル", + "settings.general.row.shell.description": + "ターミナルで使用するシェルを選択します。互換性のあるシェルはエージェントのツール呼び出しにも使用されます。", + "settings.general.row.shell.autoDefault": "自動 (デフォルト)", + "settings.general.row.shell.terminalOnly": "ターミナルのみ", "settings.general.row.appearance.title": "外観", "settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします", "settings.general.row.colorScheme.title": "配色", @@ -578,8 +709,8 @@ export const dict = { "settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。", "settings.general.row.font.title": "コードフォント", "settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "ターミナルのフォント", + "settings.general.row.terminalFont.description": "ターミナルで使用するフォントをカスタマイズ", "settings.general.row.uiFont.title": "UIフォント", "settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします", "settings.general.row.followup.title": "フォローアップの動作", @@ -587,6 +718,21 @@ export const dict = { "フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します", "settings.general.row.followup.option.queue": "キューに追加", "settings.general.row.followup.option.steer": "即座に実行 (Steer)", + "settings.general.row.showFileTree.title": "ファイルツリー", + "settings.general.row.showFileTree.description": "セッションにファイルツリーパネルを表示します", + "settings.general.row.showNavigation.title": "ナビゲーションコントロール", + "settings.general.row.showNavigation.description": "デスクトップのタイトルバーに戻るボタンと進むボタンを表示します", + "settings.general.row.showSearch.title": "コマンドパレット", + "settings.general.row.showSearch.description": "タイトルバーに検索とコマンドパレットのボタンを表示します", + "settings.general.row.showTerminal.title": "ターミナル", + "settings.general.row.showTerminal.description": "デスクトップのタイトルバーにターミナルボタンを表示します", + "settings.general.row.showStatus.title": "サーバーステータス", + "settings.general.row.showStatus.description": "タイトルバーにサーバーステータスボタンを表示します", + "settings.general.row.mobileTitlebarBottom.title": "下部ナビゲーション", + "settings.general.row.mobileTitlebarBottom.description": + "モバイルではタイトルバーとセッションタブを画面下部に配置します", + "settings.general.row.showCustomAgents.title": "カスタムエージェント", + "settings.general.row.showCustomAgents.description": "コンポーザーにエージェント選択を表示します", "settings.general.row.reasoningSummaries.title": "推論の要約を表示", "settings.general.row.reasoningSummaries.description": "タイムラインにモデルの推論の要約を表示します", "settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開", @@ -595,6 +741,12 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開", "settings.general.row.editToolPartsExpanded.description": "タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します", + "settings.general.row.newLayoutDesigns.title": "新しいレイアウトとデザイン", + "settings.general.row.newLayoutDesigns.description": + "刷新されたレイアウト、ホーム、コンポーザー、セッションUIを有効にします", + "settings.general.row.pinchZoom.title": "ピンチでズーム", + "settings.general.row.pinchZoom.description": + "トラックパッドのピンチ操作とCtrl+スクロール操作によるズームを許可します", "settings.general.row.wayland.title": "ネイティブWaylandを使用", "settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。", "settings.general.row.wayland.tooltip": @@ -607,6 +759,8 @@ export const dict = { "settings.updates.row.check.description": "手動でアップデートを確認し、利用可能ならインストールします", "settings.updates.action.checkNow": "今すぐ確認", "settings.updates.action.checking": "確認中...", + "settings.updates.action.downloading": "ダウンロード中...", + "settings.updates.action.installing": "インストール中...", "settings.updates.toast.latest.title": "最新です", "settings.updates.toast.latest.description": "OpenCode は最新バージョンです。", "sound.option.none": "なし", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 15f1f28013ef..738123fe03ca 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -254,6 +254,11 @@ export const dict = { "prompt.context.removeActiveFile": "컨텍스트에서 활성 파일 제거", "prompt.context.removeFile": "컨텍스트에서 파일 제거", "prompt.action.attachFile": "파일 첨부", + "prompt.menu.addImagesAndFiles": "파일 및 기타 항목 추가", + "prompt.menu.imagesAndFiles": "이미지 및 파일", + "prompt.menu.commands": "명령어", + "prompt.menu.context": "컨텍스트", + "prompt.menu.shellCommand": "셸 명령", "prompt.attachment.remove": "첨부 파일 제거", "prompt.action.send": "전송", "prompt.action.stop": "중지", @@ -575,8 +580,8 @@ export const dict = { "settings.general.row.theme.description": "OpenCode 테마 사용자 지정", "settings.general.row.font.title": "코드 글꼴", "settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "터미널 글꼴", + "settings.general.row.terminalFont.description": "터미널에서 사용할 글꼴을 설정합니다", "settings.general.row.uiFont.title": "UI 글꼴", "settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정", "settings.general.row.followup.title": "후속 조치 동작", @@ -848,4 +853,156 @@ export const dict = { "error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패", "error.childStore.storeCreateFailed": "저장소 생성 실패", "terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}", + + "command.project.previous": "이전 프로젝트", + "command.project.next": "다음 프로젝트", + "command.project.index": "{{index}}번 프로젝트로 전환", + "command.tab.reopenClosed": "닫은 탭 다시 열기", + "model.tooltip.context.label": "컨텍스트", + "model.tooltip.inputs": "입력", + "model.tooltip.model": "모델", + "model.tooltip.provider": "공급자", + "model.tooltip.reasoning": "추론", + "common.clear": "지우기", + "session.child.promptDisabled": "하위 에이전트 세션에는 프롬프트를 입력할 수 없습니다.", + "session.child.backToParent": "메인 세션으로 돌아가기.", + + "wsl.server.add": "WSL 서버 추가", + "wsl.server.addShort": "WSL 추가", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "WSL 서버", + "wsl.server.retryStart": "시작 재시도", + "wsl.server.updating": "업데이트 중...", + "wsl.onboarding.step.distro": "배포판 선택", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "WSL 확인 중...", + "wsl.onboarding.restartRequired": "WSL 설치를 완료하려면 Windows를 다시 시작해야 합니다.", + "wsl.onboarding.ready": "WSL이 준비되었습니다.", + "wsl.onboarding.required": "계속하려면 WSL이 필요합니다.", + "wsl.onboarding.checkingDistros": "배포판 확인 중...", + "wsl.onboarding.installingDistro": "{{distro}} 배포판 설치 중...", + "wsl.onboarding.checkingDistro": "{{distro}} 배포판 확인 중...", + "wsl.onboarding.listingDistros": "배포판 목록 확인 중...", + "wsl.onboarding.distroReady": "{{distro}} 배포판이 준비되었습니다.", + "wsl.onboarding.distroNotInstalled": "{{distro}} 배포판이 아직 설치되지 않았습니다.", + "wsl.onboarding.openDistroOnce": "설정을 완료하려면 {{distro}} 배포판을 한 번 여세요.", + "wsl.onboarding.finishingDistro": "{{distro}} 배포판 설정을 마무리하는 중입니다.", + "wsl.onboarding.pickDistro": "배포판을 선택하거나 아래에서 설치하세요.", + "wsl.onboarding.checkingOpencode": "OpenCode 확인 중...", + "wsl.onboarding.checkingOpencodeIn": "{{distro}}에서 OpenCode 확인 중...", + "wsl.onboarding.updatingOpencode": "OpenCode 업데이트 중...", + "wsl.onboarding.updatingOpencodeIn": "{{distro}}에서 OpenCode 업데이트 중...", + "wsl.onboarding.updateOpencodeIn": "{{distro}}에서 OpenCode를 업데이트하세요.", + "wsl.onboarding.updateOpencode": "OpenCode 업데이트", + "wsl.onboarding.opencodeReadyIn": "{{distro}}에서 OpenCode를 사용할 준비가 되었습니다.", + "wsl.onboarding.opencodeReady": "OpenCode가 준비되었습니다.", + "wsl.onboarding.installOpencodeIn": "{{distro}}에 OpenCode를 설치하세요.", + "wsl.onboarding.installOpencode": "OpenCode 설치", + "wsl.onboarding.chooseDistroFirst": "먼저 배포판을 선택하세요.", + "wsl.onboarding.loadFailed": "WSL 상태를 불러오지 못했습니다.", + "wsl.onboarding.loading": "로딩 중...", + "wsl.onboarding.installedDistros": "설치된 배포판", + "wsl.onboarding.checkAgain": "다시 확인", + "wsl.onboarding.distroStatus.ready": "준비됨", + "wsl.onboarding.distroStatus.checking": "확인 중...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode가 설치되지 않음", + "wsl.onboarding.distroStatus.missingTools": "bash, curl 없음", + "wsl.onboarding.distroStatus.unsupported": "지원되지 않음 · WSL 2 사용", + "wsl.onboarding.needAnotherDistro": "다른 배포판이 필요하신가요?", + "wsl.onboarding.needAnotherDistroHint": "WSL 카탈로그에서 Linux 배포판을 설치하세요", + "wsl.onboarding.wslNotInstalled.title": "WSL이 설치되지 않음", + "wsl.onboarding.wslNotInstalled.description": + "OpenCode에서 WSL 서버를 추가하려면 WSL (Windows Subsystem for Linux)이 필요합니다", + "wsl.onboarding.wslUnavailable.title": "WSL을 사용할 수 없음", + "wsl.onboarding.wslUnavailable.description": "OpenCode에서 이 컴퓨터의 WSL을 확인할 수 없습니다.", + "wsl.onboarding.installWsl": "WSL 설치", + "wsl.onboarding.windowsRestartRequired": "WSL 설치를 완료하려면 Windows를 다시 시작한 다음 OpenCode를 다시 여세요.", + "wsl.onboarding.next": "다음", + "wsl.onboarding.refresh": "새로 고침", + "wsl.onboarding.allDistrosAdded": "설치된 모든 배포판이 이미 추가되었습니다.", + "wsl.onboarding.noDistros": "아직 감지된 배포판이 없습니다.", + "wsl.onboarding.install": "설치", + "wsl.onboarding.installing": "설치 중...", + "wsl.onboarding.installDistro": "배포판 설치", + "wsl.onboarding.searchDistros": "배포판 검색", + "wsl.onboarding.wsl2Required": "WSL 2가 필요합니다.", + "wsl.onboarding.toolsRequired": "이 배포판에는 bash와 curl이 필요합니다.", + "wsl.onboarding.openTerminal": "터미널 열기", + "wsl.onboarding.path": "경로: {{path}}", + "wsl.onboarding.notFound": "찾을 수 없음", + "wsl.onboarding.version": "버전: {{version}}", + "wsl.onboarding.unknown": "알 수 없음", + "wsl.onboarding.desktopVersion": "데스크톱 {{version}}", + "wsl.onboarding.versionMismatch": "설치된 버전이 데스크톱 앱 버전과 일치하지 않습니다.", + "wsl.onboarding.adding": "추가 중...", + + "language.uk": "Українська", + "error.page.description.localServerStartup": "로컬 서버를 시작하는 동안 오류가 발생했습니다.", + "error.page.action.exportLogs": "로그 내보내기", + + "home.title": "홈", + "home.projects": "프로젝트", + "home.project.add": "프로젝트 추가", + "home.recentlyClosed": "최근에 닫은 항목", + "home.server.collapse": "서버 프로젝트 접기", + "home.server.expand": "서버 프로젝트 펼치기", + "home.sessions.search.placeholder": "세션 검색", + "home.sessions.search.placeholder.scoped": "{{scope}}에서 세션 검색", + "home.sessions.search.sessions": "세션", + "home.sessions.search.noResults": "{{query}}에 해당하는 세션을 찾을 수 없습니다", + "home.sessions.empty": "아직 아무것도 없습니다", + "home.sessions.empty.description": "세션을 생성하여 시작하세요", + "home.sessions.group.today": "오늘", + "home.sessions.group.yesterday": "어제", + "home.sessions.group.older": "이전", + "home.providerTip": "75개 이상의 제공업체에 연결하여 Claude, GPT, Gemini 등의 다른 모델을 사용하세요", + + "session.tab.unknown": "알 수 없는 세션", + "session.error.notFound": "이 세션을 찾을 수 없습니다", + "session.error.notFound.description": "이 탭은 이 서버에 더 이상 존재하지 않는 세션을 가리킵니다.", + "session.error.notFound.closeTab": "탭 닫기", + "session.error.serverConnection": "이 서버에 연결할 수 없습니다", + "session.review.noUncommittedChanges": "아직 커밋되지 않은 변경 사항이 없습니다", + "session.review.noBranchChanges": "아직 브랜치 변경 사항이 없습니다", + "session.question.minimize": "질문 최소화", + "session.question.restore": "질문 복원", + "session.question.pending.one": "{{count}}개의 대기 중인 질문", + "session.question.pending.other": "{{count}}개의 대기 중인 질문", + "session.new.project.new": "새 프로젝트", + "session.new.project.search": "프로젝트 검색", + "session.new.project.add": "프로젝트 추가", + "session.new.workspace.runIn": "세션 실행 위치", + "session.new.workspace.triggerLocal": "로컬", + "session.new.workspace.local": "로컬 저장소", + "session.new.workspace.existing": "작업 공간…", + + "sidebar.empty.title": "열린 프로젝트 없음", + "sidebar.empty.description": "프로젝트를 열어 시작하세요", + + "settings.general.section.advanced": "고급", + "settings.general.row.shell.title": "터미널 셸", + "settings.general.row.shell.description": + "터미널에서 사용할 셸을 선택하세요. 호환되는 셸은 에이전트 도구 호출에도 사용됩니다.", + "settings.general.row.shell.autoDefault": "자동 (기본값)", + "settings.general.row.shell.terminalOnly": "터미널 전용", + "settings.general.row.showFileTree.title": "파일 트리", + "settings.general.row.showFileTree.description": "세션에 파일 트리 패널 표시", + "settings.general.row.showNavigation.title": "탐색 컨트롤", + "settings.general.row.showNavigation.description": "데스크톱 제목 표시줄에 뒤로 및 앞으로 버튼 표시", + "settings.general.row.showSearch.title": "명령 팔레트", + "settings.general.row.showSearch.description": "제목 표시줄에 검색 및 명령 팔레트 버튼 표시", + "settings.general.row.showTerminal.title": "터미널", + "settings.general.row.showTerminal.description": "데스크톱 제목 표시줄에 터미널 버튼 표시", + "settings.general.row.showStatus.title": "서버 상태", + "settings.general.row.showStatus.description": "제목 표시줄에 서버 상태 버튼 표시", + "settings.general.row.mobileTitlebarBottom.title": "하단 탐색", + "settings.general.row.mobileTitlebarBottom.description": "모바일에서 제목 표시줄과 세션 탭을 화면 하단에 배치", + "settings.general.row.showCustomAgents.title": "사용자 지정 에이전트", + "settings.general.row.showCustomAgents.description": "입력창에 에이전트 선택기 표시", + "settings.general.row.newLayoutDesigns.title": "새 레이아웃 및 디자인", + "settings.general.row.newLayoutDesigns.description": "새롭게 디자인된 레이아웃, 홈, 입력창 및 세션 UI 활성화", + "settings.general.row.pinchZoom.title": "핀치 줌", + "settings.general.row.pinchZoom.description": "트랙패드 핀치 및 Ctrl-스크롤 제스처로 확대/축소 허용", + "settings.updates.action.downloading": "다운로드 중...", + "settings.updates.action.installing": "설치 중...", } diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 8ec11de192ab..0a37f8bf3237 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -77,7 +77,7 @@ export const dict = { "command.permissions.autoaccept.enable": "Aksepter tillatelser automatisk", "command.permissions.autoaccept.disable": "Stopp automatisk akseptering av tillatelser", "command.workspace.toggle": "Veksle arbeidsområder", - "command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar", + "command.workspace.toggle.description": "Aktiver eller deaktiver flere arbeidsområder i sidefeltet", "command.session.undo": "Angre", "command.session.undo.description": "Angre siste melding", "command.session.redo": "Gjør om", @@ -277,6 +277,11 @@ export const dict = { "prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst", "prompt.context.removeFile": "Fjern fil fra kontekst", "prompt.action.attachFile": "Legg ved fil", + "prompt.menu.addImagesAndFiles": "Legg til filer og mer", + "prompt.menu.imagesAndFiles": "Bilder og filer", + "prompt.menu.commands": "Kommandoer", + "prompt.menu.context": "Kontekst", + "prompt.menu.shellCommand": "Shell-kommando", "prompt.attachment.remove": "Fjern vedlegg", "prompt.action.send": "Send", "prompt.action.stop": "Stopp", @@ -469,7 +474,7 @@ export const dict = { "Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributten er feilstavet?", "error.serverSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?", - "directory.error.invalidUrl": "Invalid directory in URL.", + "directory.error.invalidUrl": "Ugyldig mappe i URL-en.", "error.chain.unknown": "Ukjent feil", "error.chain.causedBy": "Forårsaket av:", @@ -649,8 +654,8 @@ export const dict = { "settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.", "settings.general.row.font.title": "Kodefont", "settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Terminalskrift", + "settings.general.row.terminalFont.description": "Tilpass skrifttypen som brukes i terminalen", "settings.general.row.uiFont.title": "UI-skrift", "settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet", "settings.general.row.followup.title": "Oppfølgingsadferd", @@ -936,4 +941,165 @@ export const dict = { "error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon", "error.childStore.storeCreateFailed": "Kunne ikke opprette lager", "terminal.connectionLost.abnormalClose": "WebSocket lukket unormalt: {{code}}", + + "command.project.previous": "Forrige prosjekt", + "command.project.next": "Neste prosjekt", + "command.project.index": "Bytt til prosjekt {{index}}", + "command.tab.reopenClosed": "Åpne lukket fane på nytt", + + "model.tooltip.context.label": "Kontekst", + "model.tooltip.inputs": "Inndata", + "model.tooltip.model": "Modell", + "model.tooltip.provider": "Leverandør", + "model.tooltip.reasoning": "Resonnering", + + "common.clear": "Tøm", + + "session.child.promptDisabled": "Underagentsesjoner kan ikke motta forespørsler.", + "session.child.backToParent": "Tilbake til hovedsesjonen.", + + "wsl.server.add": "Legg til WSL-server", + "wsl.server.addShort": "Legg til WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "WSL-server", + "wsl.server.retryStart": "Prøv å starte på nytt", + "wsl.server.updating": "Oppdaterer...", + "wsl.onboarding.step.distro": "Velg distribusjon", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "Sjekker WSL...", + "wsl.onboarding.restartRequired": "Windows må startes på nytt for å fullføre installasjonen av WSL.", + "wsl.onboarding.ready": "WSL er klart.", + "wsl.onboarding.required": "WSL kreves for å fortsette.", + "wsl.onboarding.checkingDistros": "Sjekker distribusjoner...", + "wsl.onboarding.installingDistro": "Installerer {{distro}}...", + "wsl.onboarding.checkingDistro": "Sjekker {{distro}}...", + "wsl.onboarding.listingDistros": "Lister distribusjoner...", + "wsl.onboarding.distroReady": "{{distro}} er klar.", + "wsl.onboarding.distroNotInstalled": "{{distro}} er ikke installert ennå.", + "wsl.onboarding.openDistroOnce": "Åpne {{distro}} én gang for å fullføre oppsettet.", + "wsl.onboarding.finishingDistro": "Fullfører oppsettet for {{distro}}.", + "wsl.onboarding.pickDistro": "Velg en distribusjon eller installer en nedenfor.", + "wsl.onboarding.checkingOpencode": "Sjekker OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "Sjekker OpenCode i {{distro}}...", + "wsl.onboarding.updatingOpencode": "Oppdaterer OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "Oppdaterer OpenCode i {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "Oppdater OpenCode i {{distro}}.", + "wsl.onboarding.updateOpencode": "Oppdater OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode er klar i {{distro}}.", + "wsl.onboarding.opencodeReady": "OpenCode er klar.", + "wsl.onboarding.installOpencodeIn": "Installer OpenCode i {{distro}}.", + "wsl.onboarding.installOpencode": "Installer OpenCode", + "wsl.onboarding.chooseDistroFirst": "Velg en distribusjon først.", + "wsl.onboarding.loadFailed": "Kunne ikke laste inn WSL-status.", + "wsl.onboarding.loading": "Laster...", + "wsl.onboarding.installedDistros": "Installerte distribusjoner", + "wsl.onboarding.checkAgain": "Sjekk på nytt", + "wsl.onboarding.distroStatus.ready": "Klar", + "wsl.onboarding.distroStatus.checking": "Sjekker...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode er ikke installert", + "wsl.onboarding.distroStatus.missingTools": "Mangler bash, curl", + "wsl.onboarding.distroStatus.unsupported": "Ikke støttet · Bruk WSL 2", + "wsl.onboarding.needAnotherDistro": "Trenger du en annen distribusjon?", + "wsl.onboarding.needAnotherDistroHint": "Installer en Linux-distribusjon fra WSL-katalogen", + "wsl.onboarding.wslNotInstalled.title": "WSL er ikke installert", + "wsl.onboarding.wslNotInstalled.description": + "WSL (Windows Subsystem for Linux) kreves før OpenCode kan legge til en WSL-server", + "wsl.onboarding.wslUnavailable.title": "WSL er utilgjengelig", + "wsl.onboarding.wslUnavailable.description": "OpenCode kunne ikke bekrefte WSL på denne maskinen.", + "wsl.onboarding.installWsl": "Installer WSL", + "wsl.onboarding.windowsRestartRequired": + "Start Windows på nytt for å fullføre installasjonen av WSL, og åpne deretter OpenCode på nytt.", + "wsl.onboarding.next": "Neste", + "wsl.onboarding.refresh": "Oppdater", + "wsl.onboarding.allDistrosAdded": "Alle installerte distribusjoner er allerede lagt til.", + "wsl.onboarding.noDistros": "Ingen distribusjoner oppdaget ennå.", + "wsl.onboarding.install": "Installer", + "wsl.onboarding.installing": "Installerer...", + "wsl.onboarding.installDistro": "Installer distribusjon", + "wsl.onboarding.searchDistros": "Søk etter distribusjoner", + "wsl.onboarding.wsl2Required": "WSL 2 er påkrevd.", + "wsl.onboarding.toolsRequired": "Denne distribusjonen trenger bash og curl.", + "wsl.onboarding.openTerminal": "Åpne terminal", + "wsl.onboarding.path": "Bane: {{path}}", + "wsl.onboarding.notFound": "ikke funnet", + "wsl.onboarding.version": "Versjon: {{version}}", + "wsl.onboarding.unknown": "ukjent", + "wsl.onboarding.desktopVersion": "skrivebordsapp {{version}}", + "wsl.onboarding.versionMismatch": "Den installerte versjonen samsvarer ikke med versjonen av skrivebordsappen.", + "wsl.onboarding.adding": "Legger til...", + + "language.uk": "Українська", + + "error.page.description.localServerStartup": "Det oppstod en feil under oppstart av den lokale serveren.", + "error.page.action.exportLogs": "Eksporter logger", + + "home.title": "Hjem", + "home.projects": "Prosjekter", + "home.project.add": "Legg til prosjekt", + "home.recentlyClosed": "Nylig lukket", + "home.server.collapse": "Fold sammen serverprosjekter", + "home.server.expand": "Utvid serverprosjekter", + "home.sessions.search.placeholder": "Søk etter sesjoner", + "home.sessions.search.placeholder.scoped": "Søk etter sesjoner i {{scope}}", + "home.sessions.search.sessions": "Sesjoner", + "home.sessions.search.noResults": "Ingen sesjoner funnet for {{query}}", + "home.sessions.empty": "Ingenting her ennå", + "home.sessions.empty.description": "Opprett en sesjon for å komme i gang", + "home.sessions.group.today": "I dag", + "home.sessions.group.yesterday": "I går", + "home.sessions.group.older": "Eldre", + "home.providerTip": + "Koble til over 75 leverandører for å bruke andre modeller, inkludert Claude, GPT, Gemini og flere", + + "session.tab.unknown": "Ukjent sesjon", + "session.error.notFound": "Denne sesjonen finnes ikke", + "session.error.notFound.description": "Denne fanen peker til en sesjon som ikke lenger finnes på denne serveren.", + "session.error.notFound.closeTab": "Lukk fane", + "session.error.serverConnection": "Kan ikke koble til denne serveren", + "session.review.noUncommittedChanges": "Ingen ucommittede endringer ennå", + "session.review.noBranchChanges": "Ingen grenendringer ennå", + "session.question.minimize": "Minimer spørsmål", + "session.question.restore": "Gjenopprett spørsmål", + "session.question.pending.one": "{{count}} ventende spørsmål", + "session.question.pending.other": "{{count}} ventende spørsmål", + "session.new.project.new": "Nytt prosjekt", + "session.new.project.search": "Søk etter prosjekter", + "session.new.project.add": "Legg til prosjekt", + "session.new.workspace.runIn": "Kjør sesjonen i", + "session.new.workspace.triggerLocal": "Lokalt", + "session.new.workspace.local": "Lokalt depot", + "session.new.workspace.existing": "Arbeidsområde…", + + "sidebar.empty.title": "Ingen åpne prosjekter", + "sidebar.empty.description": "Åpne et prosjekt for å komme i gang", + + "settings.general.section.advanced": "Avansert", + "settings.general.row.shell.title": "Terminalskall", + "settings.general.row.shell.description": + "Velg skallet som skal brukes i terminalen. Kompatible skall brukes også til agentens verktøykall.", + "settings.general.row.shell.autoDefault": "Automatisk (standard)", + "settings.general.row.shell.terminalOnly": "kun terminal", + "settings.general.row.showFileTree.title": "Filtre", + "settings.general.row.showFileTree.description": "Vis filtrepanelet i sesjoner", + "settings.general.row.showNavigation.title": "Navigasjonskontroller", + "settings.general.row.showNavigation.description": + "Vis tilbake- og fremoverknappene på tittellinjen i skrivebordsappen", + "settings.general.row.showSearch.title": "Kommandopalett", + "settings.general.row.showSearch.description": "Vis knappen for søk og kommandopalett på tittellinjen", + "settings.general.row.showTerminal.title": "Terminal", + "settings.general.row.showTerminal.description": "Vis terminalknappen på tittellinjen i skrivebordsappen", + "settings.general.row.showStatus.title": "Serverstatus", + "settings.general.row.showStatus.description": "Vis serverstatusknappen på tittellinjen", + "settings.general.row.mobileTitlebarBottom.title": "Navigasjon nederst", + "settings.general.row.mobileTitlebarBottom.description": + "Plasser tittellinjen og sesjonsfanene nederst på mobilskjermen", + "settings.general.row.showCustomAgents.title": "Egendefinerte agenter", + "settings.general.row.showCustomAgents.description": "Vis agentvelgeren i skrivefeltet", + "settings.general.row.newLayoutDesigns.title": "Ny layout og utforming", + "settings.general.row.newLayoutDesigns.description": + "Aktiver ny layout og utforming for startsiden, skrivefeltet og sesjonsgrensesnittet", + "settings.general.row.pinchZoom.title": "Knip for å zoome", + "settings.general.row.pinchZoom.description": "Tillat knipebevegelser på styreflaten og Ctrl-rulling for å zoome", + "settings.updates.action.downloading": "Laster ned...", + "settings.updates.action.installing": "Installerer...", } satisfies Partial> diff --git a/packages/app/src/i18n/parity.test.ts b/packages/app/src/i18n/parity.test.ts index 8ad1beb56b0c..725b5f68a0c8 100644 --- a/packages/app/src/i18n/parity.test.ts +++ b/packages/app/src/i18n/parity.test.ts @@ -1,33 +1,118 @@ import { describe, expect, test } from "bun:test" -import { dict as en } from "./en" -import { dict as ar } from "./ar" -import { dict as br } from "./br" -import { dict as bs } from "./bs" -import { dict as da } from "./da" -import { dict as de } from "./de" -import { dict as es } from "./es" -import { dict as fr } from "./fr" -import { dict as ja } from "./ja" -import { dict as ko } from "./ko" -import { dict as no } from "./no" -import { dict as pl } from "./pl" -import { dict as ru } from "./ru" -import { dict as uk } from "./uk" -import { dict as th } from "./th" -import { dict as zh } from "./zh" -import { dict as zht } from "./zht" -import { dict as tr } from "./tr" - -const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, uk, th, tr, zh, zht] -const keys = ["command.session.previous.unseen", "command.session.next.unseen"] as const + +const appLocales = [ + "ar", + "br", + "bs", + "da", + "de", + "es", + "fr", + "ja", + "ko", + "no", + "pl", + "ru", + "uk", + "th", + "tr", + "zh", + "zht", +] as const +const desktopLocales = appLocales.filter((locale) => locale !== "th" && locale !== "tr") + +const domains = [ + { + name: "app", + source: "./en.ts", + target: (locale: string) => `./${locale}.ts`, + locales: appLocales, + }, + { + name: "ui", + source: "../../../ui/src/i18n/en.ts", + target: (locale: string) => `../../../ui/src/i18n/${locale}.ts`, + locales: appLocales, + }, + { + name: "desktop", + source: "../../../desktop/src/renderer/i18n/en.ts", + target: (locale: string) => `../../../desktop/src/renderer/i18n/${locale}.ts`, + locales: desktopLocales, + }, +] as const describe("i18n parity", () => { - test("non-English locales translate targeted unseen session keys", () => { - for (const locale of locales) { - for (const key of keys) { - expect(locale[key]).toBeDefined() - expect(locale[key]).not.toBe(en[key]) + test("non-English locales have every English key", async () => { + for (const domain of domains) { + const source = await dictionary(domain.source) + for (const locale of domain.locales) { + const target = await dictionary(domain.target(locale)) + const missing = Object.keys(source).filter((key) => !Object.hasOwn(target, key)) + const extra = Object.keys(target).filter((key) => !Object.hasOwn(source, key)) + expect({ domain: domain.name, locale, missing, extra }).toEqual({ + domain: domain.name, + locale, + missing: [], + extra: [], + }) + } + } + }) + + test("non-English locales preserve English placeholders", async () => { + for (const domain of domains) { + const source = await dictionary(domain.source) + for (const locale of domain.locales) { + const target = await dictionary(domain.target(locale)) + const mismatched = Object.keys(source).filter( + (key) => Object.hasOwn(target, key) && placeholders(source[key]).join() !== placeholders(target[key]).join(), + ) + expect({ domain: domain.name, locale, mismatched }).toEqual({ domain: domain.name, locale, mismatched: [] }) + } + } + }) + + test("non-English locales translate targeted unseen session keys", async () => { + const source = await dictionary("./en.ts") + for (const locale of appLocales) { + const target = await dictionary(`./${locale}.ts`) + for (const key of ["command.session.previous.unseen", "command.session.next.unseen"]) { + expect(target[key]).toBeDefined() + expect(target[key]).not.toBe(source[key]) + } + } + }) + + test("changed-file summary keys preserve rendered English copy and localize complete phrases", async () => { + const source = await dictionary("../../../ui/src/i18n/en.ts") + expect(source["ui.sessionTurn.diffs.changed.one"].replace("{{count}}", "1")).toBe("1 Changed file") + expect(source["ui.sessionTurn.diffs.changed.other"].replace("{{count}}", "2")).toBe("2 Changed files") + expect(source["ui.sessionTurn.diffs.changed"]).toBeUndefined() + + for (const locale of appLocales) { + const target = await dictionary(`../../../ui/src/i18n/${locale}.ts`) + for (const key of ["ui.sessionTurn.diffs.changed.one", "ui.sessionTurn.diffs.changed.other"]) { + expect(target[key].trim()).not.toBe("") + expect(placeholders(target[key])).toEqual(["count"]) } } }) }) + +async function dictionary(file: string) { + const module: unknown = await import(file) + if (typeof module !== "object" || module === null || !("dict" in module) || !isDictionary(module.dict)) { + throw new Error(`Invalid translation dictionary: ${file}`) + } + return module.dict +} + +function isDictionary(value: unknown): value is Record { + if (typeof value !== "object" || value === null || Array.isArray(value)) return false + return Object.values(value).every((item) => typeof item === "string") +} + +function placeholders(value: string) { + return Array.from(value.matchAll(/{{\s*([^}]+?)\s*}}/g), (match) => match[1]).sort() +} diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index fcf9927da016..f56e023aed07 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -21,6 +21,9 @@ export const dict = { "theme.scheme.dark": "Ciemny", "command.sidebar.toggle": "Przełącz pasek boczny", "command.project.open": "Otwórz projekt", + "command.project.previous": "Poprzedni projekt", + "command.project.next": "Następny projekt", + "command.project.index": "Przełącz na projekt {{index}}", "command.provider.connect": "Połącz dostawcę", "command.server.switch": "Przełącz serwer", "command.settings.open": "Otwórz ustawienia", @@ -39,6 +42,7 @@ export const dict = { "command.session.new": "Nowa sesja", "command.file.open": "Otwórz plik", "command.tab.close": "Zamknij kartę", + "command.tab.reopenClosed": "Otwórz ponownie zamkniętą kartę", "command.context.addSelection": "Dodaj zaznaczenie do kontekstu", "command.context.addSelection.description": "Dodaj zaznaczone linie z bieżącego pliku", "command.input.focus": "Fokus na pole wejściowe", @@ -194,6 +198,11 @@ export const dict = { "model.input.video": "wideo", "model.input.pdf": "pdf", "model.tooltip.allows": "Obsługuje: {{inputs}}", + "model.tooltip.context.label": "Kontekst", + "model.tooltip.inputs": "Dane wejściowe", + "model.tooltip.model": "Model", + "model.tooltip.provider": "Dostawca", + "model.tooltip.reasoning": "Wnioskowanie", "model.tooltip.reasoning.allowed": "Obsługuje wnioskowanie", "model.tooltip.reasoning.none": "Brak wnioskowania", "model.tooltip.context": "Limit kontekstu {{limit}}", @@ -203,6 +212,7 @@ export const dict = { "common.loading": "Ładowanie", "common.loading.ellipsis": "...", "common.cancel": "Anuluj", + "common.clear": "Wyczyść", "common.connect": "Połącz", "common.disconnect": "Rozłącz", "common.continue": "Prześlij", @@ -219,6 +229,8 @@ export const dict = { "prompt.mode.shell": "Terminal", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc aby wyjść", + "session.child.promptDisabled": "Nie można wysyłać promptów w sesjach podagentów.", + "session.child.backToParent": "Wróć do głównej sesji.", "prompt.example.1": "Napraw TODO w bazie kodu", "prompt.example.2": "Jaki jest stos technologiczny tego projektu?", "prompt.example.3": "Napraw zepsute testy", @@ -256,6 +268,11 @@ export const dict = { "prompt.context.removeActiveFile": "Usuń aktywny plik z kontekstu", "prompt.context.removeFile": "Usuń plik z kontekstu", "prompt.action.attachFile": "Załącz plik", + "prompt.menu.addImagesAndFiles": "Dodaj pliki i inne elementy", + "prompt.menu.imagesAndFiles": "Obrazy i pliki", + "prompt.menu.commands": "Polecenia", + "prompt.menu.context": "Kontekst", + "prompt.menu.shellCommand": "Polecenie powłoki", "prompt.attachment.remove": "Usuń załącznik", "prompt.action.send": "Wyślij", "prompt.action.stop": "Zatrzymaj", @@ -315,6 +332,75 @@ export const dict = { "dialog.server.menu.delete": "Usuń", "dialog.server.current": "Obecny serwer", "dialog.server.status.default": "Domyślny", + "wsl.server.add": "Dodaj serwer WSL", + "wsl.server.addShort": "Dodaj WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "Serwer WSL", + "wsl.server.retryStart": "Ponów uruchamianie", + "wsl.server.updating": "Aktualizowanie...", + "wsl.onboarding.step.distro": "Wybierz dystrybucję", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "Sprawdzanie WSL...", + "wsl.onboarding.restartRequired": "Aby dokończyć instalację WSL, uruchom ponownie system Windows.", + "wsl.onboarding.ready": "WSL jest gotowy.", + "wsl.onboarding.required": "Aby kontynuować, wymagany jest WSL.", + "wsl.onboarding.checkingDistros": "Sprawdzanie dystrybucji...", + "wsl.onboarding.installingDistro": "Instalowanie {{distro}}...", + "wsl.onboarding.checkingDistro": "Sprawdzanie {{distro}}...", + "wsl.onboarding.listingDistros": "Pobieranie listy dystrybucji...", + "wsl.onboarding.distroReady": "Dystrybucja {{distro}} jest gotowa.", + "wsl.onboarding.distroNotInstalled": "Dystrybucja {{distro}} nie jest jeszcze zainstalowana.", + "wsl.onboarding.openDistroOnce": "Uruchom raz {{distro}}, aby dokończyć konfigurację.", + "wsl.onboarding.finishingDistro": "Kończenie konfiguracji {{distro}}.", + "wsl.onboarding.pickDistro": "Wybierz dystrybucję lub zainstaluj jedną z poniższych.", + "wsl.onboarding.checkingOpencode": "Sprawdzanie OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "Sprawdzanie OpenCode w dystrybucji {{distro}}...", + "wsl.onboarding.updatingOpencode": "Aktualizowanie OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "Aktualizowanie OpenCode w dystrybucji {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "Zaktualizuj OpenCode w dystrybucji {{distro}}.", + "wsl.onboarding.updateOpencode": "Zaktualizuj OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode jest gotowy w dystrybucji {{distro}}.", + "wsl.onboarding.opencodeReady": "OpenCode jest gotowy.", + "wsl.onboarding.installOpencodeIn": "Zainstaluj OpenCode w dystrybucji {{distro}}.", + "wsl.onboarding.installOpencode": "Zainstaluj OpenCode", + "wsl.onboarding.chooseDistroFirst": "Najpierw wybierz dystrybucję.", + "wsl.onboarding.loadFailed": "Nie udało się załadować stanu WSL.", + "wsl.onboarding.loading": "Ładowanie...", + "wsl.onboarding.installedDistros": "Zainstalowane dystrybucje", + "wsl.onboarding.checkAgain": "Sprawdź ponownie", + "wsl.onboarding.distroStatus.ready": "Gotowa", + "wsl.onboarding.distroStatus.checking": "Sprawdzanie...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nie jest zainstalowany", + "wsl.onboarding.distroStatus.missingTools": "Brak bash i curl", + "wsl.onboarding.distroStatus.unsupported": "Nieobsługiwana · Użyj WSL 2", + "wsl.onboarding.needAnotherDistro": "Potrzebujesz innej dystrybucji?", + "wsl.onboarding.needAnotherDistroHint": "Zainstaluj dystrybucję systemu Linux z katalogu WSL", + "wsl.onboarding.wslNotInstalled.title": "WSL nie jest zainstalowany", + "wsl.onboarding.wslNotInstalled.description": + "WSL (Podsystem Windows dla systemu Linux) jest wymagany, zanim OpenCode będzie mógł dodać serwer WSL", + "wsl.onboarding.wslUnavailable.title": "WSL jest niedostępny", + "wsl.onboarding.wslUnavailable.description": "OpenCode nie mógł zweryfikować WSL na tym komputerze.", + "wsl.onboarding.installWsl": "Zainstaluj WSL", + "wsl.onboarding.windowsRestartRequired": + "Uruchom ponownie system Windows, aby dokończyć instalację WSL, a następnie ponownie otwórz OpenCode.", + "wsl.onboarding.next": "Dalej", + "wsl.onboarding.refresh": "Odśwież", + "wsl.onboarding.allDistrosAdded": "Wszystkie zainstalowane dystrybucje zostały już dodane.", + "wsl.onboarding.noDistros": "Nie wykryto jeszcze żadnych dystrybucji.", + "wsl.onboarding.install": "Zainstaluj", + "wsl.onboarding.installing": "Instalowanie...", + "wsl.onboarding.installDistro": "Zainstaluj dystrybucję", + "wsl.onboarding.searchDistros": "Szukaj dystrybucji", + "wsl.onboarding.wsl2Required": "Wymagany jest WSL 2.", + "wsl.onboarding.toolsRequired": "Ta dystrybucja wymaga bash i curl.", + "wsl.onboarding.openTerminal": "Otwórz terminal", + "wsl.onboarding.path": "Ścieżka: {{path}}", + "wsl.onboarding.notFound": "nie znaleziono", + "wsl.onboarding.version": "Wersja: {{version}}", + "wsl.onboarding.unknown": "nieznana", + "wsl.onboarding.desktopVersion": "aplikacja komputerowa {{version}}", + "wsl.onboarding.versionMismatch": "Zainstalowana wersja nie jest zgodna z wersją aplikacji komputerowej.", + "wsl.onboarding.adding": "Dodawanie...", "dialog.project.edit.title": "Edytuj projekt", "dialog.project.edit.name": "Nazwa", "dialog.project.edit.icon": "Ikona", @@ -324,7 +410,8 @@ export const dict = { "dialog.project.edit.color": "Kolor", "dialog.project.edit.color.select": "Wybierz kolor {{color}}", "dialog.project.edit.worktree.startup": "Skrypt uruchamiania przestrzeni roboczej", - "dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).", + "dialog.project.edit.worktree.startup.description": + "Uruchamiany po utworzeniu nowej przestrzeni roboczej (worktree).", "dialog.project.edit.worktree.startup.placeholder": "np. bun install", "context.breakdown.title": "Podział kontekstu", "context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.', @@ -371,6 +458,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", "toast.language.title": "Język", @@ -407,10 +495,12 @@ export const dict = { "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.description.localServerStartup": "Wystąpił błąd podczas uruchamiania serwera lokalnego.", "error.page.details.label": "Szczegóły błędu", "error.page.action.restart": "Restartuj", "error.page.action.report": "Zgłoś błąd", "error.page.action.reported": "Błąd zgłoszony", + "error.page.action.exportLogs": "Eksportuj dzienniki", "error.page.action.checking": "Sprawdzanie...", "error.page.action.checkUpdates": "Sprawdź aktualizacje", "error.page.action.updateTo": "Zaktualizuj do {{version}}", @@ -430,7 +520,8 @@ export const dict = { "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": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.', + "error.chain.mcpFailed": + "Serwer MCP „{{name}}” nie uruchomił 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ę.', @@ -452,10 +543,32 @@ export const dict = { "home.recentProjects": "Ostatnie projekty", "home.empty.title": "Brak ostatnich projektów", "home.empty.description": "Zacznij od otwarcia lokalnego projektu", + "home.title": "Strona główna", + "home.projects": "Projekty", + "home.project.add": "Dodaj projekt", + "home.recentlyClosed": "Ostatnio zamknięte", + "home.server.collapse": "Zwiń projekty serwera", + "home.server.expand": "Rozwiń projekty serwera", + "home.sessions.search.placeholder": "Szukaj sesji", + "home.sessions.search.placeholder.scoped": "Szukaj sesji w {{scope}}", + "home.sessions.search.sessions": "Sesje", + "home.sessions.search.noResults": "Nie znaleziono sesji dla {{query}}", + "home.sessions.empty": "Jeszcze nic tu nie ma", + "home.sessions.empty.description": "Utwórz sesję, aby rozpocząć", + "home.sessions.group.today": "Dzisiaj", + "home.sessions.group.yesterday": "Wczoraj", + "home.sessions.group.older": "Starsze", + "home.providerTip": + "Połącz się z ponad 75 dostawcami, aby korzystać z innych modeli, w tym Claude, GPT, Gemini i innych", "session.tab.session": "Sesja", "session.tab.review": "Przegląd", "session.tab.context": "Kontekst", + "session.tab.unknown": "Nieznana sesja", "session.panel.reviewAndFiles": "Przegląd i pliki", + "session.error.notFound": "Nie można znaleźć tej sesji", + "session.error.notFound.description": "Ta karta wskazuje sesję, która nie istnieje już na tym serwerze.", + "session.error.notFound.closeTab": "Zamknij kartę", + "session.error.serverConnection": "Nie można połączyć się z tym serwerem", "session.review.filesChanged": "Zmieniono {{count}} plików", "session.review.change.one": "Zmiana", "session.review.change.other": "Zmiany", @@ -464,6 +577,8 @@ export const dict = { "session.review.noVcs": "Nie wykryto systemu kontroli wersji Git, zmiany nie są wyświetlane", "session.review.noSnapshot": "Śledzenie migawek jest wyłączone w konfiguracji, więc zmiany w sesji są niedostępne", "session.review.noChanges": "Brak zmian", + "session.review.noUncommittedChanges": "Brak jeszcze niezatwierdzonych zmian", + "session.review.noBranchChanges": "Brak jeszcze zmian w gałęzi", "session.files.selectToOpen": "Wybierz plik do otwarcia", "session.files.all": "Wszystkie pliki", "session.files.empty": "Brak plików", @@ -477,6 +592,10 @@ export const dict = { "session.todo.title": "Zadania", "session.todo.collapse": "Zwiń", "session.todo.expand": "Rozwiń", + "session.question.minimize": "Zminimalizuj pytanie", + "session.question.restore": "Przywróć pytanie", + "session.question.pending.one": "{{count}} oczekujące pytanie", + "session.question.pending.other": "Pytania oczekujące: {{count}}", "session.followupDock.summary.one": "{{count}} wiadomość w kolejce", "session.followupDock.summary.other": "{{count}} wiadomości w kolejce", "session.followupDock.sendNow": "Wyślij teraz", @@ -489,9 +608,16 @@ export const dict = { "session.revertDock.expand": "Rozwiń cofnięte wiadomości", "session.revertDock.restore": "Przywróć wiadomość", "session.new.title": "Zbuduj cokolwiek", + "session.new.project.new": "Nowy projekt", + "session.new.project.search": "Szukaj projektów", + "session.new.project.add": "Dodaj projekt", "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.workspace.runIn": "Uruchom sesję w", + "session.new.workspace.triggerLocal": "Lokalnie", + "session.new.workspace.local": "Lokalne repozytorium", + "session.new.workspace.existing": "Przestrzeń robocza…", "session.new.lastModified": "Ostatnio zmodyfikowano", "session.header.search.placeholder": "Szukaj {{project}}", "session.header.searchFiles": "Szukaj plików", @@ -555,15 +681,18 @@ export const dict = { "sidebar.project.recentSessions": "Ostatnie sesje", "sidebar.project.viewAllSessions": "Zobacz wszystkie sesje", "sidebar.project.clearNotifications": "Wyczyść powiadomienia", + "sidebar.empty.title": "Brak otwartych projektów", + "sidebar.empty.description": "Otwórz projekt, aby rozpocząć", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Pulpit", "settings.section.server": "Serwer", "settings.tab.general": "Ogólne", "settings.tab.shortcuts": "Skróty", "settings.desktop.section.wsl": "WSL", - "settings.desktop.wsl.title": "WSL integration", - "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.", + "settings.desktop.wsl.title": "Integracja z WSL", + "settings.desktop.wsl.description": "Uruchamiaj serwer OpenCode wewnątrz WSL w systemie Windows.", "settings.general.section.appearance": "Wygląd", + "settings.general.section.advanced": "Zaawansowane", "settings.general.section.notifications": "Powiadomienia systemowe", "settings.general.section.updates": "Aktualizacje", "settings.general.section.sounds": "Efekty dźwiękowe", @@ -571,6 +700,11 @@ export const dict = { "settings.general.section.display": "Ekran", "settings.general.row.language.title": "Język", "settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode", + "settings.general.row.shell.title": "Powłoka terminala", + "settings.general.row.shell.description": + "Wybierz powłokę używaną w terminalu. Zgodne powłoki są również używane przez wywołania narzędzi agenta.", + "settings.general.row.shell.autoDefault": "Automatycznie (domyślna)", + "settings.general.row.shell.terminalOnly": "tylko terminal", "settings.general.row.appearance.title": "Wygląd", "settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu", "settings.general.row.colorScheme.title": "Schemat kolorów", @@ -580,14 +714,30 @@ export const dict = { "settings.general.row.theme.description": "Dostosuj motyw OpenCode.", "settings.general.row.font.title": "Czcionka kodu", "settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Czcionka terminala", + "settings.general.row.terminalFont.description": "Dostosuj czcionkę używaną w terminalu", "settings.general.row.uiFont.title": "Czcionka interfejsu", "settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie", "settings.general.row.followup.title": "Zachowanie kontynuacji", "settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce", "settings.general.row.followup.option.queue": "Kolejka", "settings.general.row.followup.option.steer": "Sterowanie", + "settings.general.row.showFileTree.title": "Drzewo plików", + "settings.general.row.showFileTree.description": "Pokaż panel drzewa plików w sesjach", + "settings.general.row.showNavigation.title": "Elementy nawigacji", + "settings.general.row.showNavigation.description": + "Pokaż przyciski Wstecz i Dalej na pasku tytułu aplikacji komputerowej", + "settings.general.row.showSearch.title": "Paleta poleceń", + "settings.general.row.showSearch.description": "Pokaż przycisk wyszukiwania i palety poleceń na pasku tytułu", + "settings.general.row.showTerminal.title": "Terminal", + "settings.general.row.showTerminal.description": "Pokaż przycisk terminala na pasku tytułu aplikacji komputerowej", + "settings.general.row.showStatus.title": "Status serwera", + "settings.general.row.showStatus.description": "Pokaż przycisk statusu serwera na pasku tytułu", + "settings.general.row.mobileTitlebarBottom.title": "Dolna nawigacja", + "settings.general.row.mobileTitlebarBottom.description": + "Umieść pasek tytułu i karty sesji u dołu ekranu na urządzeniach mobilnych", + "settings.general.row.showCustomAgents.title": "Agenci niestandardowi", + "settings.general.row.showCustomAgents.description": "Pokaż wybór agenta w edytorze wiadomości", "settings.general.row.reasoningSummaries.title": "Pokaż podsumowania wnioskowania", "settings.general.row.reasoningSummaries.description": "Wyświetlaj podsumowania wnioskowania modelu na osi czasu", "settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell", @@ -596,6 +746,12 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit", "settings.general.row.editToolPartsExpanded.description": "Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu", + "settings.general.row.newLayoutDesigns.title": "Nowy układ i wygląd", + "settings.general.row.newLayoutDesigns.description": + "Włącz przeprojektowany układ, stronę główną, edytor wiadomości i interfejs sesji", + "settings.general.row.pinchZoom.title": "Powiększanie gestem szczypania", + "settings.general.row.pinchZoom.description": + "Zezwalaj na powiększanie gestem szczypania na gładziku i przewijaniem z klawiszem Ctrl", "settings.general.row.wayland.title": "Użyj natywnego Wayland", "settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.", "settings.general.row.wayland.tooltip": @@ -608,6 +764,8 @@ export const dict = { "settings.updates.row.check.description": "Ręcznie sprawdź aktualizacje i zainstaluj, jeśli są dostępne", "settings.updates.action.checkNow": "Sprawdź teraz", "settings.updates.action.checking": "Sprawdzanie...", + "settings.updates.action.downloading": "Pobieranie...", + "settings.updates.action.installing": "Instalowanie...", "settings.updates.toast.latest.title": "Masz najnowszą wersję", "settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.", "sound.option.none": "Brak", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index b2f58b101b40..0d68552e6d70 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -23,6 +23,9 @@ export const dict = { "command.sidebar.toggle": "Переключить боковую панель", "command.project.open": "Открыть проект", + "command.project.previous": "Предыдущий проект", + "command.project.next": "Следующий проект", + "command.project.index": "Переключиться на проект {{index}}", "command.provider.connect": "Подключить провайдера", "command.server.switch": "Переключить сервер", "command.settings.open": "Открыть настройки", @@ -45,6 +48,7 @@ export const dict = { "command.session.new": "Новая сессия", "command.file.open": "Открыть файл", "command.tab.close": "Закрыть вкладку", + "command.tab.reopenClosed": "Повторно открыть закрытую вкладку", "command.context.addSelection": "Добавить выделение в контекст", "command.context.addSelection.description": "Добавить выбранные строки из текущего файла", "command.input.focus": "Фокус на поле ввода", @@ -208,6 +212,11 @@ export const dict = { "model.input.video": "видео", "model.input.pdf": "pdf", "model.tooltip.allows": "Разрешено: {{inputs}}", + "model.tooltip.context.label": "Контекст", + "model.tooltip.inputs": "Входные данные", + "model.tooltip.model": "Модель", + "model.tooltip.provider": "Провайдер", + "model.tooltip.reasoning": "Рассуждение", "model.tooltip.reasoning.allowed": "Разрешает рассуждение", "model.tooltip.reasoning.none": "Без рассуждения", "model.tooltip.context": "Лимит контекста {{limit}}", @@ -218,6 +227,7 @@ export const dict = { "common.loading": "Загрузка", "common.loading.ellipsis": "...", "common.cancel": "Отмена", + "common.clear": "Очистить", "common.connect": "Подключить", "common.disconnect": "Отключить", "common.continue": "Отправить", @@ -235,6 +245,8 @@ export const dict = { "prompt.mode.shell": "Оболочка", "prompt.mode.normal": "Промпт", "prompt.mode.shell.exit": "esc для выхода", + "session.child.promptDisabled": "В сессиях субагентов нельзя отправлять запросы.", + "session.child.backToParent": "Вернуться к основной сессии.", "prompt.example.1": "Исправить TODO в коде", "prompt.example.2": "Какой технологический стек этого проекта?", @@ -274,6 +286,11 @@ export const dict = { "prompt.context.removeActiveFile": "Удалить активный файл из контекста", "prompt.context.removeFile": "Удалить файл из контекста", "prompt.action.attachFile": "Прикрепить файл", + "prompt.menu.addImagesAndFiles": "Добавить файлы и другое", + "prompt.menu.imagesAndFiles": "Изображения и файлы", + "prompt.menu.commands": "Команды", + "prompt.menu.context": "Контекст", + "prompt.menu.shellCommand": "Команда оболочки", "prompt.attachment.remove": "Удалить вложение", "prompt.action.send": "Отправить", "prompt.action.stop": "Остановить", @@ -341,6 +358,75 @@ export const dict = { "dialog.server.menu.delete": "Удалить", "dialog.server.current": "Текущий сервер", "dialog.server.status.default": "По умолч.", + "wsl.server.add": "Добавить сервер WSL", + "wsl.server.addShort": "Добавить WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "Сервер WSL", + "wsl.server.retryStart": "Повторить запуск", + "wsl.server.updating": "Обновление...", + "wsl.onboarding.step.distro": "Выбрать дистрибутив", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "Проверка WSL...", + "wsl.onboarding.restartRequired": "Чтобы завершить установку WSL, перезапустите Windows.", + "wsl.onboarding.ready": "WSL готова к работе.", + "wsl.onboarding.required": "Для продолжения требуется WSL.", + "wsl.onboarding.checkingDistros": "Проверка дистрибутивов...", + "wsl.onboarding.installingDistro": "Установка {{distro}}...", + "wsl.onboarding.checkingDistro": "Проверка {{distro}}...", + "wsl.onboarding.listingDistros": "Получение списка дистрибутивов...", + "wsl.onboarding.distroReady": "Дистрибутив {{distro}} готов к работе.", + "wsl.onboarding.distroNotInstalled": "Дистрибутив {{distro}} пока не установлен.", + "wsl.onboarding.openDistroOnce": "Откройте {{distro}} один раз, чтобы завершить настройку.", + "wsl.onboarding.finishingDistro": "Завершение настройки {{distro}}.", + "wsl.onboarding.pickDistro": "Выберите дистрибутив или установите его ниже.", + "wsl.onboarding.checkingOpencode": "Проверка OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "Проверка OpenCode в {{distro}}...", + "wsl.onboarding.updatingOpencode": "Обновление OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "Обновление OpenCode в {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "Обновите OpenCode в {{distro}}.", + "wsl.onboarding.updateOpencode": "Обновить OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode в {{distro}} готов к работе.", + "wsl.onboarding.opencodeReady": "OpenCode готов к работе.", + "wsl.onboarding.installOpencodeIn": "Установите OpenCode в {{distro}}.", + "wsl.onboarding.installOpencode": "Установить OpenCode", + "wsl.onboarding.chooseDistroFirst": "Сначала выберите дистрибутив.", + "wsl.onboarding.loadFailed": "Не удалось загрузить состояние WSL.", + "wsl.onboarding.loading": "Загрузка...", + "wsl.onboarding.installedDistros": "Установленные дистрибутивы", + "wsl.onboarding.checkAgain": "Проверить снова", + "wsl.onboarding.distroStatus.ready": "Готов", + "wsl.onboarding.distroStatus.checking": "Проверка...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode не установлен", + "wsl.onboarding.distroStatus.missingTools": "Отсутствуют bash и curl", + "wsl.onboarding.distroStatus.unsupported": "Не поддерживается · Используйте WSL 2", + "wsl.onboarding.needAnotherDistro": "Нужен другой дистрибутив?", + "wsl.onboarding.needAnotherDistroHint": "Установите дистрибутив Linux из каталога WSL", + "wsl.onboarding.wslNotInstalled.title": "WSL не установлена", + "wsl.onboarding.wslNotInstalled.description": + "Для добавления сервера WSL в OpenCode требуется WSL (Windows Subsystem for Linux)", + "wsl.onboarding.wslUnavailable.title": "WSL недоступна", + "wsl.onboarding.wslUnavailable.description": "OpenCode не смог проверить WSL на этом компьютере.", + "wsl.onboarding.installWsl": "Установить WSL", + "wsl.onboarding.windowsRestartRequired": + "Перезапустите Windows, чтобы завершить установку WSL, затем снова откройте OpenCode.", + "wsl.onboarding.next": "Далее", + "wsl.onboarding.refresh": "Обновить", + "wsl.onboarding.allDistrosAdded": "Все установленные дистрибутивы уже добавлены.", + "wsl.onboarding.noDistros": "Дистрибутивы пока не обнаружены.", + "wsl.onboarding.install": "Установить", + "wsl.onboarding.installing": "Установка...", + "wsl.onboarding.installDistro": "Установить дистрибутив", + "wsl.onboarding.searchDistros": "Поиск дистрибутивов", + "wsl.onboarding.wsl2Required": "Требуется WSL 2.", + "wsl.onboarding.toolsRequired": "Для этого дистрибутива нужны bash и curl.", + "wsl.onboarding.openTerminal": "Открыть терминал", + "wsl.onboarding.path": "Путь: {{path}}", + "wsl.onboarding.notFound": "не найден", + "wsl.onboarding.version": "Версия: {{version}}", + "wsl.onboarding.unknown": "неизвестно", + "wsl.onboarding.desktopVersion": "приложение {{version}}", + "wsl.onboarding.versionMismatch": "Установленная версия не соответствует версии приложения.", + "wsl.onboarding.adding": "Добавление...", "dialog.project.edit.title": "Редактировать проект", "dialog.project.edit.name": "Название", @@ -405,6 +491,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", @@ -452,10 +539,12 @@ export const dict = { "error.page.title": "Что-то пошло не так", "error.page.description": "Произошла ошибка при загрузке приложения.", + "error.page.description.localServerStartup": "Произошла ошибка при запуске локального сервера.", "error.page.details.label": "Детали ошибки", "error.page.action.restart": "Перезапустить", "error.page.action.report": "Сообщить об ошибке", "error.page.action.reported": "Об ошибке сообщено", + "error.page.action.exportLogs": "Экспортировать журналы", "error.page.action.checking": "Проверка...", "error.page.action.checkUpdates": "Проверить обновления", "error.page.action.updateTo": "Обновить до {{version}}", @@ -505,11 +594,33 @@ export const dict = { "home.recentProjects": "Недавние проекты", "home.empty.title": "Нет недавних проектов", "home.empty.description": "Начните с открытия локального проекта", + "home.title": "Главная", + "home.projects": "Проекты", + "home.project.add": "Добавить проект", + "home.recentlyClosed": "Недавно закрытые", + "home.server.collapse": "Свернуть проекты сервера", + "home.server.expand": "Развернуть проекты сервера", + "home.sessions.search.placeholder": "Поиск сессий", + "home.sessions.search.placeholder.scoped": "Поиск сессий в {{scope}}", + "home.sessions.search.sessions": "Сессии", + "home.sessions.search.noResults": "Сессии по запросу {{query}} не найдены", + "home.sessions.empty": "Здесь пока ничего нет", + "home.sessions.empty.description": "Создайте сессию, чтобы начать", + "home.sessions.group.today": "Сегодня", + "home.sessions.group.yesterday": "Вчера", + "home.sessions.group.older": "Ранее", + "home.providerTip": + "Подключитесь к более чем 75 провайдерам, чтобы использовать другие модели, включая Claude, GPT, Gemini и другие", "session.tab.session": "Сессия", "session.tab.review": "Обзор", "session.tab.context": "Контекст", + "session.tab.unknown": "Неизвестная сессия", "session.panel.reviewAndFiles": "Обзор и файлы", + "session.error.notFound": "Эта сессия не найдена", + "session.error.notFound.description": "Эта вкладка указывает на сессию, которой больше нет на этом сервере.", + "session.error.notFound.closeTab": "Закрыть вкладку", + "session.error.serverConnection": "Не удаётся подключиться к этому серверу", "session.review.filesChanged": "{{count}} файлов изменено", "session.review.change.one": "Изменение", "session.review.change.other": "Изменения", @@ -518,6 +629,8 @@ export const dict = { "session.review.noVcs": "Система контроля версий Git не обнаружена, изменения не отображаются", "session.review.noSnapshot": "Отслеживание снимков отключено в настройках, поэтому изменения сессии недоступны", "session.review.noChanges": "Нет изменений", + "session.review.noUncommittedChanges": "Пока нет незафиксированных изменений", + "session.review.noBranchChanges": "Пока нет изменений в ветке", "session.files.selectToOpen": "Выберите файл, чтобы открыть", "session.files.all": "Все файлы", "session.files.empty": "Нет файлов", @@ -532,6 +645,10 @@ export const dict = { "session.todo.title": "Задачи", "session.todo.collapse": "Свернуть", "session.todo.expand": "Развернуть", + "session.question.minimize": "Свернуть вопрос", + "session.question.restore": "Восстановить вопрос", + "session.question.pending.one": "{{count}} вопрос без ответа", + "session.question.pending.other": "Вопросы без ответа: {{count}}", "session.followupDock.summary.one": "{{count}} сообщение в очереди", "session.followupDock.summary.other": "{{count}} сообщений в очереди", "session.followupDock.sendNow": "Отправить сейчас", @@ -545,9 +662,16 @@ export const dict = { "session.revertDock.restore": "Восстановить сообщение", "session.new.title": "Создавайте что угодно", + "session.new.project.new": "Новый проект", + "session.new.project.search": "Поиск проектов", + "session.new.project.add": "Добавить проект", "session.new.worktree.main": "Основная ветка", "session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})", "session.new.worktree.create": "Создать новый worktree", + "session.new.workspace.runIn": "Запустить сессию в", + "session.new.workspace.triggerLocal": "Локально", + "session.new.workspace.local": "Локальный репозиторий", + "session.new.workspace.existing": "Рабочее пространство…", "session.new.lastModified": "Последнее изменение", "session.header.search.placeholder": "Поиск {{project}}", @@ -619,6 +743,8 @@ export const dict = { "sidebar.project.recentSessions": "Недавние сессии", "sidebar.project.viewAllSessions": "Посмотреть все сессии", "sidebar.project.clearNotifications": "Очистить уведомления", + "sidebar.empty.title": "Нет открытых проектов", + "sidebar.empty.description": "Откройте проект, чтобы начать", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Приложение", @@ -630,6 +756,7 @@ export const dict = { "settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.", "settings.general.section.appearance": "Внешний вид", + "settings.general.section.advanced": "Дополнительно", "settings.general.section.notifications": "Системные уведомления", "settings.general.section.updates": "Обновления", "settings.general.section.sounds": "Звуковые эффекты", @@ -638,6 +765,11 @@ export const dict = { "settings.general.row.language.title": "Язык", "settings.general.row.language.description": "Изменить язык отображения OpenCode", + "settings.general.row.shell.title": "Оболочка терминала", + "settings.general.row.shell.description": + "Выберите оболочку для терминала. Совместимые оболочки также используются агентом при вызове инструментов.", + "settings.general.row.shell.autoDefault": "Авто (по умолчанию)", + "settings.general.row.shell.terminalOnly": "только терминал", "settings.general.row.appearance.title": "Внешний вид", "settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве", "settings.general.row.colorScheme.title": "Цветовая схема", @@ -646,8 +778,8 @@ export const dict = { "settings.general.row.theme.description": "Настройте оформление OpenCode.", "settings.general.row.font.title": "Шрифт кода", "settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Шрифт терминала", + "settings.general.row.terminalFont.description": "Настройте шрифт, используемый в терминале", "settings.general.row.uiFont.title": "Шрифт интерфейса", "settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе", "settings.general.row.followup.title": "Поведение уточняющих вопросов", @@ -655,6 +787,22 @@ export const dict = { "Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь", "settings.general.row.followup.option.queue": "Очередь", "settings.general.row.followup.option.steer": "Направлять", + "settings.general.row.showFileTree.title": "Дерево файлов", + "settings.general.row.showFileTree.description": "Показывать панель дерева файлов в сессиях", + "settings.general.row.showNavigation.title": "Элементы навигации", + "settings.general.row.showNavigation.description": + "Показывать кнопки «Назад» и «Вперёд» в строке заголовка приложения", + "settings.general.row.showSearch.title": "Палитра команд", + "settings.general.row.showSearch.description": "Показывать кнопку поиска и палитры команд в строке заголовка", + "settings.general.row.showTerminal.title": "Терминал", + "settings.general.row.showTerminal.description": "Показывать кнопку терминала в строке заголовка приложения", + "settings.general.row.showStatus.title": "Состояние сервера", + "settings.general.row.showStatus.description": "Показывать кнопку состояния сервера в строке заголовка", + "settings.general.row.mobileTitlebarBottom.title": "Нижняя навигация", + "settings.general.row.mobileTitlebarBottom.description": + "На мобильных устройствах размещать строку заголовка и вкладки сессии внизу экрана", + "settings.general.row.showCustomAgents.title": "Пользовательские агенты", + "settings.general.row.showCustomAgents.description": "Показывать выбор агента в редакторе запросов", "settings.general.row.reasoningSummaries.title": "Показывать сводки рассуждений", "settings.general.row.reasoningSummaries.description": "Отображать сводки рассуждений модели в ленте", @@ -664,6 +812,12 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit", "settings.general.row.editToolPartsExpanded.description": "Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию", + "settings.general.row.newLayoutDesigns.title": "Новая компоновка и оформление", + "settings.general.row.newLayoutDesigns.description": + "Включить обновлённую компоновку, главную страницу, редактор запросов и интерфейс сессии", + "settings.general.row.pinchZoom.title": "Масштабирование щипком", + "settings.general.row.pinchZoom.description": + "Разрешить масштабирование жестом щипка на трекпаде и прокруткой с Ctrl", "settings.general.row.wayland.title": "Использовать нативный Wayland", "settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.", "settings.general.row.wayland.tooltip": @@ -678,6 +832,8 @@ export const dict = { "settings.updates.row.check.description": "Проверить обновления вручную и установить, если доступны", "settings.updates.action.checkNow": "Проверить сейчас", "settings.updates.action.checking": "Проверка...", + "settings.updates.action.downloading": "Загрузка...", + "settings.updates.action.installing": "Установка...", "settings.updates.toast.latest.title": "У вас последняя версия", "settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.", "sound.option.none": "Нет", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index d066f3c616b8..91c009be0c54 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -23,6 +23,9 @@ export const dict = { "command.sidebar.toggle": "สลับแถบข้าง", "command.project.open": "เปิดโปรเจกต์", + "command.project.previous": "โปรเจกต์ก่อนหน้า", + "command.project.next": "โปรเจกต์ถัดไป", + "command.project.index": "สลับไปยังโปรเจกต์ {{index}}", "command.provider.connect": "เชื่อมต่อผู้ให้บริการ", "command.server.switch": "สลับเซิร์ฟเวอร์", "command.settings.open": "เปิดการตั้งค่า", @@ -32,7 +35,7 @@ export const dict = { "command.session.next.unseen": "เซสชันที่ยังไม่ได้อ่านถัดไป", "command.session.archive": "จัดเก็บเซสชัน", - "command.palette": "คำสั่งค้นหา", + "command.palette": "ชุดคำสั่ง", "command.theme.cycle": "เปลี่ยนธีม", "command.theme.set": "ใช้ธีม: {{theme}}", @@ -45,6 +48,7 @@ export const dict = { "command.session.new": "เซสชันใหม่", "command.file.open": "เปิดไฟล์", "command.tab.close": "ปิดแท็บ", + "command.tab.reopenClosed": "เปิดแท็บที่ปิดไปอีกครั้ง", "command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท", "command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน", "command.input.focus": "โฟกัสช่องป้อนข้อมูล", @@ -163,7 +167,7 @@ export const dict = { "provider.custom.field.providerID.placeholder": "myprovider", "provider.custom.field.providerID.description": "ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง", "provider.custom.field.name.label": "ชื่อที่แสดง", - "provider.custom.field.name.placeholder": "My AI Provider", + "provider.custom.field.name.placeholder": "ผู้ให้บริการ AI ของฉัน", "provider.custom.field.baseURL.label": "URL พื้นฐาน", "provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1", "provider.custom.field.apiKey.label": "คีย์ API", @@ -208,6 +212,11 @@ export const dict = { "model.input.video": "วิดีโอ", "model.input.pdf": "PDF", "model.tooltip.allows": "อนุญาต: {{inputs}}", + "model.tooltip.context.label": "บริบท", + "model.tooltip.inputs": "ข้อมูลนำเข้า", + "model.tooltip.model": "โมเดล", + "model.tooltip.provider": "ผู้ให้บริการ", + "model.tooltip.reasoning": "การใช้เหตุผล", "model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล", "model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล", "model.tooltip.context": "ขีดจำกัดบริบท {{limit}}", @@ -218,6 +227,7 @@ export const dict = { "common.loading": "กำลังโหลด", "common.loading.ellipsis": "...", "common.cancel": "ยกเลิก", + "common.clear": "ล้าง", "common.connect": "เชื่อมต่อ", "common.disconnect": "ยกเลิกการเชื่อมต่อ", "common.continue": "ส่ง", @@ -235,6 +245,8 @@ export const dict = { "prompt.mode.shell": "เชลล์", "prompt.mode.normal": "พรอมต์", "prompt.mode.shell.exit": "กด esc เพื่อออก", + "session.child.promptDisabled": "ไม่สามารถส่งพรอมต์ในเซสชันเอเจนต์ย่อยได้.", + "session.child.backToParent": "กลับไปยังเซสชันหลัก.", "prompt.example.1": "แก้ไข TODO ในโค้ดเบส", "prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?", @@ -274,6 +286,11 @@ export const dict = { "prompt.context.removeActiveFile": "เอาไฟล์ที่ใช้งานอยู่ออกจากบริบท", "prompt.context.removeFile": "เอาไฟล์ออกจากบริบท", "prompt.action.attachFile": "แนบไฟล์", + "prompt.menu.addImagesAndFiles": "เพิ่มไฟล์และอื่น ๆ", + "prompt.menu.imagesAndFiles": "รูปภาพและไฟล์", + "prompt.menu.commands": "คำสั่ง", + "prompt.menu.context": "บริบท", + "prompt.menu.shellCommand": "คำสั่งเชลล์", "prompt.attachment.remove": "เอาไฟล์แนบออก", "prompt.action.send": "ส่ง", "prompt.action.stop": "หยุด", @@ -341,6 +358,75 @@ export const dict = { "dialog.server.menu.delete": "ลบ", "dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน", "dialog.server.status.default": "ค่าเริ่มต้น", + "wsl.server.add": "เพิ่มเซิร์ฟเวอร์ WSL", + "wsl.server.addShort": "เพิ่ม WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "เซิร์ฟเวอร์ WSL", + "wsl.server.retryStart": "ลองเริ่มอีกครั้ง", + "wsl.server.updating": "กำลังอัปเดต...", + "wsl.onboarding.step.distro": "เลือกดิสโทร", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "กำลังตรวจสอบ WSL...", + "wsl.onboarding.restartRequired": "ต้องรีสตาร์ท Windows เพื่อให้การติดตั้ง WSL เสร็จสมบูรณ์.", + "wsl.onboarding.ready": "WSL พร้อมใช้งานแล้ว.", + "wsl.onboarding.required": "ต้องมี WSL เพื่อดำเนินการต่อ.", + "wsl.onboarding.checkingDistros": "กำลังตรวจสอบดิสโทร...", + "wsl.onboarding.installingDistro": "กำลังติดตั้ง {{distro}}...", + "wsl.onboarding.checkingDistro": "กำลังตรวจสอบ {{distro}}...", + "wsl.onboarding.listingDistros": "กำลังแสดงรายการดิสโทร...", + "wsl.onboarding.distroReady": "{{distro}} พร้อมใช้งานแล้ว.", + "wsl.onboarding.distroNotInstalled": "ยังไม่ได้ติดตั้ง {{distro}}.", + "wsl.onboarding.openDistroOnce": "เปิด {{distro}} หนึ่งครั้งเพื่อให้การตั้งค่าเสร็จสมบูรณ์.", + "wsl.onboarding.finishingDistro": "กำลังตั้งค่า {{distro}} ให้เสร็จสมบูรณ์.", + "wsl.onboarding.pickDistro": "เลือกดิสโทรหรือติดตั้งจากรายการด้านล่าง.", + "wsl.onboarding.checkingOpencode": "กำลังตรวจสอบ OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "กำลังตรวจสอบ OpenCode ใน {{distro}}...", + "wsl.onboarding.updatingOpencode": "กำลังอัปเดต OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "กำลังอัปเดต OpenCode ใน {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "อัปเดต OpenCode ใน {{distro}}.", + "wsl.onboarding.updateOpencode": "อัปเดต OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode พร้อมใช้งานใน {{distro}} แล้ว.", + "wsl.onboarding.opencodeReady": "OpenCode พร้อมใช้งานแล้ว.", + "wsl.onboarding.installOpencodeIn": "ติดตั้ง OpenCode ใน {{distro}}.", + "wsl.onboarding.installOpencode": "ติดตั้ง OpenCode", + "wsl.onboarding.chooseDistroFirst": "เลือกดิสโทรก่อน.", + "wsl.onboarding.loadFailed": "ไม่สามารถโหลดสถานะ WSL ได้.", + "wsl.onboarding.loading": "กำลังโหลด...", + "wsl.onboarding.installedDistros": "ดิสโทรที่ติดตั้งแล้ว", + "wsl.onboarding.checkAgain": "ตรวจสอบอีกครั้ง", + "wsl.onboarding.distroStatus.ready": "พร้อมใช้งาน", + "wsl.onboarding.distroStatus.checking": "กำลังตรวจสอบ...", + "wsl.onboarding.distroStatus.opencodeMissing": "ยังไม่ได้ติดตั้ง OpenCode", + "wsl.onboarding.distroStatus.missingTools": "ไม่มี bash, curl", + "wsl.onboarding.distroStatus.unsupported": "ไม่รองรับ · ใช้ WSL 2", + "wsl.onboarding.needAnotherDistro": "ต้องการดิสโทรอื่น?", + "wsl.onboarding.needAnotherDistroHint": "ติดตั้งดิสทริบิวชัน Linux จากแค็ตตาล็อก WSL", + "wsl.onboarding.wslNotInstalled.title": "ยังไม่ได้ติดตั้ง WSL", + "wsl.onboarding.wslNotInstalled.description": + "ต้องมี WSL (Windows Subsystem for Linux) ก่อนที่ OpenCode จะเพิ่มเซิร์ฟเวอร์ WSL ได้", + "wsl.onboarding.wslUnavailable.title": "WSL ไม่พร้อมใช้งาน", + "wsl.onboarding.wslUnavailable.description": "OpenCode ไม่สามารถตรวจสอบ WSL บนเครื่องนี้ได้.", + "wsl.onboarding.installWsl": "ติดตั้ง WSL", + "wsl.onboarding.windowsRestartRequired": + "รีสตาร์ท Windows เพื่อให้การติดตั้ง WSL เสร็จสมบูรณ์ แล้วเปิด OpenCode อีกครั้ง.", + "wsl.onboarding.next": "ถัดไป", + "wsl.onboarding.refresh": "รีเฟรช", + "wsl.onboarding.allDistrosAdded": "เพิ่มดิสโทรที่ติดตั้งไว้ทั้งหมดแล้ว.", + "wsl.onboarding.noDistros": "ยังไม่พบดิสโทร.", + "wsl.onboarding.install": "ติดตั้ง", + "wsl.onboarding.installing": "กำลังติดตั้ง...", + "wsl.onboarding.installDistro": "ติดตั้งดิสโทร", + "wsl.onboarding.searchDistros": "ค้นหาดิสโทร", + "wsl.onboarding.wsl2Required": "ต้องใช้ WSL 2.", + "wsl.onboarding.toolsRequired": "ดิสโทรนี้ต้องมี bash และ curl.", + "wsl.onboarding.openTerminal": "เปิดเทอร์มินัล", + "wsl.onboarding.path": "เส้นทาง: {{path}}", + "wsl.onboarding.notFound": "ไม่พบ", + "wsl.onboarding.version": "เวอร์ชัน: {{version}}", + "wsl.onboarding.unknown": "ไม่ทราบ", + "wsl.onboarding.desktopVersion": "เดสก์ท็อป {{version}}", + "wsl.onboarding.versionMismatch": "เวอร์ชันที่ติดตั้งไม่ตรงกับเวอร์ชันของแอปเดสก์ท็อป.", + "wsl.onboarding.adding": "กำลังเพิ่ม...", "dialog.project.edit.title": "แก้ไขโปรเจกต์", "dialog.project.edit.name": "ชื่อ", @@ -403,6 +489,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", @@ -451,10 +538,12 @@ export const dict = { "error.page.title": "เกิดข้อผิดพลาด", "error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน", + "error.page.description.localServerStartup": "เกิดข้อผิดพลาดขณะเริ่มเซิร์ฟเวอร์ในเครื่อง.", "error.page.details.label": "รายละเอียดข้อผิดพลาด", "error.page.action.restart": "รีสตาร์ท", "error.page.action.report": "รายงานข้อผิดพลาด", "error.page.action.reported": "รายงานข้อผิดพลาดแล้ว", + "error.page.action.exportLogs": "ส่งออกบันทึก", "error.page.action.checking": "กำลังตรวจสอบ...", "error.page.action.checkUpdates": "ตรวจสอบการอัปเดต", "error.page.action.updateTo": "อัปเดตเป็น {{version}}", @@ -500,11 +589,32 @@ export const dict = { "home.recentProjects": "โปรเจกต์ล่าสุด", "home.empty.title": "ไม่มีโปรเจกต์ล่าสุด", "home.empty.description": "เริ่มต้นโดยเปิดโปรเจกต์ในเครื่อง", + "home.title": "หน้าหลัก", + "home.projects": "โปรเจกต์", + "home.project.add": "เพิ่มโปรเจกต์", + "home.recentlyClosed": "ที่ปิดไปล่าสุด", + "home.server.collapse": "ย่อโปรเจกต์ของเซิร์ฟเวอร์", + "home.server.expand": "ขยายโปรเจกต์ของเซิร์ฟเวอร์", + "home.sessions.search.placeholder": "ค้นหาเซสชัน", + "home.sessions.search.placeholder.scoped": "ค้นหาเซสชันใน {{scope}}", + "home.sessions.search.sessions": "เซสชัน", + "home.sessions.search.noResults": "ไม่พบเซสชันสำหรับ {{query}}", + "home.sessions.empty": "ยังไม่มีอะไรที่นี่", + "home.sessions.empty.description": "สร้างเซสชันเพื่อเริ่มต้น", + "home.sessions.group.today": "วันนี้", + "home.sessions.group.yesterday": "เมื่อวาน", + "home.sessions.group.older": "ก่อนหน้านี้", + "home.providerTip": "เชื่อมต่อกับผู้ให้บริการกว่า 75 รายเพื่อใช้โมเดลอื่นๆ รวมถึง Claude, GPT, Gemini และอีกมากมาย", "session.tab.session": "เซสชัน", "session.tab.review": "ตรวจสอบ", "session.tab.context": "บริบท", + "session.tab.unknown": "เซสชันที่ไม่รู้จัก", "session.panel.reviewAndFiles": "ตรวจสอบและไฟล์", + "session.error.notFound": "ไม่พบเซสชันนี้", + "session.error.notFound.description": "แท็บนี้อ้างถึงเซสชันที่ไม่มีอยู่บนเซิร์ฟเวอร์นี้แล้ว.", + "session.error.notFound.closeTab": "ปิดแท็บ", + "session.error.serverConnection": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์นี้", "session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง", "session.review.change.one": "การเปลี่ยนแปลง", "session.review.change.other": "การเปลี่ยนแปลง", @@ -513,6 +623,8 @@ export const dict = { "session.review.noVcs": "ไม่ตรวจพบระบบควบคุมเวอร์ชัน Git การเปลี่ยนแปลงจะไม่แสดง", "session.review.noSnapshot": "การติดตามสแนปชอตถูกปิดใช้งานในการกำหนดค่า ดังนั้นการเปลี่ยนแปลงเซสชันจึงไม่พร้อมใช้งาน", "session.review.noChanges": "ไม่มีการเปลี่ยนแปลง", + "session.review.noUncommittedChanges": "ยังไม่มีการเปลี่ยนแปลงที่รอคอมมิต", + "session.review.noBranchChanges": "ยังไม่มีการเปลี่ยนแปลงในสาขา", "session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด", "session.files.empty": "ไม่มีไฟล์", @@ -529,6 +641,10 @@ export const dict = { "session.todo.title": "สิ่งที่ต้องทำ", "session.todo.collapse": "ย่อ", "session.todo.expand": "ขยาย", + "session.question.minimize": "ย่อคำถาม", + "session.question.restore": "คืนค่าคำถาม", + "session.question.pending.one": "คำถามที่รอดำเนินการ {{count}} ข้อ", + "session.question.pending.other": "คำถามที่รอดำเนินการ {{count}} ข้อ", "session.followupDock.summary.one": "{{count}} ข้อความในคิว", "session.followupDock.summary.other": "{{count}} ข้อความในคิว", "session.followupDock.sendNow": "ส่งทันที", @@ -542,9 +658,16 @@ export const dict = { "session.revertDock.restore": "กู้คืนข้อความ", "session.new.title": "สร้างอะไรก็ได้", + "session.new.project.new": "โปรเจกต์ใหม่", + "session.new.project.search": "ค้นหาโปรเจกต์", + "session.new.project.add": "เพิ่มโปรเจกต์", "session.new.worktree.main": "สาขาหลัก", "session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})", "session.new.worktree.create": "สร้าง worktree ใหม่", + "session.new.workspace.runIn": "เรียกใช้เซสชันใน", + "session.new.workspace.triggerLocal": "ในเครื่อง", + "session.new.workspace.local": "รีโพซิทอรีในเครื่อง", + "session.new.workspace.existing": "พื้นที่ทำงาน…", "session.new.lastModified": "แก้ไขล่าสุด", "session.header.search.placeholder": "ค้นหา {{project}}", @@ -612,6 +735,8 @@ export const dict = { "sidebar.project.recentSessions": "เซสชันล่าสุด", "sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด", "sidebar.project.clearNotifications": "ล้างการแจ้งเตือน", + "sidebar.empty.title": "ไม่มีโปรเจกต์ที่เปิดอยู่", + "sidebar.empty.description": "เปิดโปรเจกต์เพื่อเริ่มต้น", "app.name.desktop": "OpenCode Desktop", @@ -624,6 +749,7 @@ export const dict = { "settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows", "settings.general.section.appearance": "รูปลักษณ์", + "settings.general.section.advanced": "ขั้นสูง", "settings.general.section.notifications": "การแจ้งเตือนระบบ", "settings.general.section.updates": "การอัปเดต", "settings.general.section.sounds": "เสียงเอฟเฟกต์", @@ -632,6 +758,11 @@ export const dict = { "settings.general.row.language.title": "ภาษา", "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode", + "settings.general.row.shell.title": "เชลล์เทอร์มินัล", + "settings.general.row.shell.description": + "เลือกเชลล์ที่จะใช้กับเทอร์มินัล เชลล์ที่เข้ากันได้จะใช้สำหรับการเรียกเครื่องมือของเอเจนต์ด้วย", + "settings.general.row.shell.autoDefault": "อัตโนมัติ (ค่าเริ่มต้น)", + "settings.general.row.shell.terminalOnly": "เทอร์มินัลเท่านั้น", "settings.general.row.appearance.title": "รูปลักษณ์", "settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ", "settings.general.row.colorScheme.title": "โทนสี", @@ -640,14 +771,29 @@ export const dict = { "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม", "settings.general.row.font.title": "ฟอนต์โค้ด", "settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "ฟอนต์เทอร์มินัล", + "settings.general.row.terminalFont.description": "ปรับแต่งฟอนต์ที่ใช้ในเทอร์มินัล", "settings.general.row.uiFont.title": "ฟอนต์ UI", "settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ", "settings.general.row.followup.title": "พฤติกรรมการติดตามผล", "settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว", "settings.general.row.followup.option.queue": "คิว", "settings.general.row.followup.option.steer": "นำทาง", + "settings.general.row.showFileTree.title": "ต้นไม้ไฟล์", + "settings.general.row.showFileTree.description": "แสดงแผงต้นไม้ไฟล์ในเซสชัน", + "settings.general.row.showNavigation.title": "ตัวควบคุมการนำทาง", + "settings.general.row.showNavigation.description": "แสดงปุ่มย้อนกลับและไปข้างหน้าในแถบชื่อเรื่องเดสก์ท็อป", + "settings.general.row.showSearch.title": "ชุดคำสั่ง", + "settings.general.row.showSearch.description": "แสดงปุ่มค้นหาและชุดคำสั่งในแถบชื่อเรื่อง", + "settings.general.row.showTerminal.title": "เทอร์มินัล", + "settings.general.row.showTerminal.description": "แสดงปุ่มเทอร์มินัลในแถบชื่อเรื่องเดสก์ท็อป", + "settings.general.row.showStatus.title": "สถานะเซิร์ฟเวอร์", + "settings.general.row.showStatus.description": "แสดงปุ่มสถานะเซิร์ฟเวอร์ในแถบชื่อเรื่อง", + "settings.general.row.mobileTitlebarBottom.title": "การนำทางด้านล่าง", + "settings.general.row.mobileTitlebarBottom.description": + "วางแถบชื่อเรื่องและแท็บเซสชันไว้ด้านล่างของหน้าจอบนอุปกรณ์เคลื่อนที่", + "settings.general.row.showCustomAgents.title": "เอเจนต์ที่กำหนดเอง", + "settings.general.row.showCustomAgents.description": "แสดงตัวเลือกเอเจนต์ในช่องเขียนข้อความ", "settings.general.row.reasoningSummaries.title": "แสดงสรุปการใช้เหตุผล", "settings.general.row.reasoningSummaries.description": "แสดงสรุปการใช้เหตุผลของโมเดลในไทม์ไลน์", "settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell", @@ -655,6 +801,11 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit", "settings.general.row.editToolPartsExpanded.description": "แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์", + "settings.general.row.newLayoutDesigns.title": "เลย์เอาต์และดีไซน์ใหม่", + "settings.general.row.newLayoutDesigns.description": + "เปิดใช้เลย์เอาต์ หน้าแรก ช่องเขียนข้อความ และ UI เซสชันที่ออกแบบใหม่", + "settings.general.row.pinchZoom.title": "บีบนิ้วเพื่อซูม", + "settings.general.row.pinchZoom.description": "อนุญาตให้ใช้ท่าบีบนิ้วบนแทร็คแพดและ Ctrl-scroll เพื่อซูม", "settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ", "settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท", "settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า", @@ -668,6 +819,8 @@ export const dict = { "settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี", "settings.updates.action.checkNow": "ตรวจสอบทันที", "settings.updates.action.checking": "กำลังตรวจสอบ...", + "settings.updates.action.downloading": "กำลังดาวน์โหลด...", + "settings.updates.action.installing": "กำลังติดตั้ง...", "settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว", "settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด", @@ -857,10 +1010,10 @@ export const dict = { "dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้", "dialog.server.add.passwordPlaceholder": "รหัสผ่าน", "server.row.noUsername": "ไม่มีชื่อผู้ใช้", - "session.review.noVcs.createGit.title": "สร้าง Git repository", + "session.review.noVcs.createGit.title": "สร้าง Git รีโพซิทอรี", "session.review.noVcs.createGit.description": "ติดตาม ตรวจสอบ และเลิกทำสิ่งเปลี่ยนแปลงในโปรเจกต์นี้", - "session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git repository...", - "session.review.noVcs.createGit.action": "สร้าง Git repository", + "session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git รีโพซิทอรี...", + "session.review.noVcs.createGit.action": "สร้าง Git รีโพซิทอรี", "session.todo.progress": "เสร็จสิ้น {{done}} จาก {{total}} รายการ", "session.question.progress": "{{current}} จาก {{total}} คำถาม", "session.header.open.finder": "Finder", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index 90531735994f..ff17d0001ce0 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -27,6 +27,9 @@ export const dict = { "command.sidebar.toggle": "Kenar çubuğunu aç/kapat", "command.project.open": "Proje aç", + "command.project.previous": "Önceki proje", + "command.project.next": "Sonraki proje", + "command.project.index": "{{index}} numaralı projeye geç", "command.provider.connect": "Sağlayıcı bağla", "command.server.switch": "Sunucu değiştir", "command.settings.open": "Ayarları aç", @@ -49,6 +52,7 @@ export const dict = { "command.session.new": "Yeni oturum", "command.file.open": "Dosya aç", "command.tab.close": "Sekmeyi kapat", + "command.tab.reopenClosed": "Kapatılan sekmeyi yeniden aç", "command.context.addSelection": "Seçimi bağlama ekle", "command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle", "command.input.focus": "Girişi odakla", @@ -132,7 +136,7 @@ export const dict = { "provider.connect.status.waiting": "Yetkilendirme bekleniyor...", "provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}", "provider.connect.apiKey.description": - "{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için {{provider}} API anahtarınızı girin.", + "{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için API anahtarınızı girin.", "provider.connect.apiKey.label": "{{provider}} API anahtarı", "provider.connect.apiKey.placeholder": "API anahtarı", "provider.connect.apiKey.required": "API anahtarı gerekli", @@ -143,10 +147,10 @@ export const dict = { "provider.connect.opencodeZen.visit.prefix": "", "provider.connect.opencodeZen.visit.link": "opencode.ai/zen", "provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.", - "provider.connect.oauth.code.visit.prefix": - "Hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için ", + "provider.connect.oauth.code.visit.prefix": "Yetkilendirme kodunuzu almak için ", "provider.connect.oauth.code.visit.link": "bu bağlantıya", - "provider.connect.oauth.code.visit.suffix": " tıklayarak yetkilendirme kodunuzu alın.", + "provider.connect.oauth.code.visit.suffix": + " gidin; ardından hesabınızı bağlayarak OpenCode'da {{provider}} modellerini kullanın.", "provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu", "provider.connect.oauth.code.placeholder": "Yetkilendirme kodu", "provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli", @@ -213,6 +217,11 @@ export const dict = { "model.input.video": "video", "model.input.pdf": "pdf", "model.tooltip.allows": "Kabul eder: {{inputs}}", + "model.tooltip.context.label": "Bağlam", + "model.tooltip.inputs": "Girdiler", + "model.tooltip.model": "Model", + "model.tooltip.provider": "Sağlayıcı", + "model.tooltip.reasoning": "Akıl yürütme", "model.tooltip.reasoning.allowed": "Akıl yürütme destekler", "model.tooltip.reasoning.none": "Akıl yürütme yok", "model.tooltip.context": "Bağlam limiti {{limit}}", @@ -223,6 +232,7 @@ export const dict = { "common.loading": "Yükleniyor", "common.loading.ellipsis": "...", "common.cancel": "İptal", + "common.clear": "Temizle", "common.connect": "Bağlan", "common.disconnect": "Bağlantı Kes", "common.continue": "Gönder", @@ -240,6 +250,8 @@ export const dict = { "prompt.mode.shell": "Kabuk", "prompt.mode.normal": "Komut", "prompt.mode.shell.exit": "çıkmak için esc", + "session.child.promptDisabled": "Alt ajan oturumlarına istem gönderilemez.", + "session.child.backToParent": "Ana oturuma dön.", "prompt.example.1": "Kod tabanındaki bir TODO'yu düzelt", "prompt.example.2": "Bu projenin teknoloji yığını nedir?", @@ -279,6 +291,11 @@ export const dict = { "prompt.context.removeActiveFile": "Aktif dosyayı bağlamdan çıkar", "prompt.context.removeFile": "Dosyayı bağlamdan çıkar", "prompt.action.attachFile": "Dosya ekle", + "prompt.menu.addImagesAndFiles": "Dosya ve daha fazlasını ekle", + "prompt.menu.imagesAndFiles": "Görseller ve dosyalar", + "prompt.menu.commands": "Komutlar", + "prompt.menu.context": "Bağlam", + "prompt.menu.shellCommand": "Kabuk komutu", "prompt.attachment.remove": "Eki kaldır", "prompt.action.send": "Gönder", "prompt.action.stop": "Durdur", @@ -346,6 +363,75 @@ export const dict = { "dialog.server.menu.delete": "Sil", "dialog.server.current": "Mevcut Sunucu", "dialog.server.status.default": "Varsayılan", + "wsl.server.add": "WSL sunucusu ekle", + "wsl.server.addShort": "WSL ekle", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "WSL sunucusu", + "wsl.server.retryStart": "Başlatmayı yeniden dene", + "wsl.server.updating": "Güncelleniyor...", + "wsl.onboarding.step.distro": "Dağıtım seç", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "WSL kontrol ediliyor...", + "wsl.onboarding.restartRequired": "WSL kurulumunu tamamlamak için Windows'un yeniden başlatılması gerekiyor.", + "wsl.onboarding.ready": "WSL hazır.", + "wsl.onboarding.required": "Devam etmek için WSL gerekli.", + "wsl.onboarding.checkingDistros": "Dağıtımlar kontrol ediliyor...", + "wsl.onboarding.installingDistro": "{{distro}} kuruluyor...", + "wsl.onboarding.checkingDistro": "{{distro}} kontrol ediliyor...", + "wsl.onboarding.listingDistros": "Dağıtımlar listeleniyor...", + "wsl.onboarding.distroReady": "{{distro}} hazır.", + "wsl.onboarding.distroNotInstalled": "{{distro}} henüz kurulu değil.", + "wsl.onboarding.openDistroOnce": "Kurulumu tamamlamak için {{distro}} dağıtımını bir kez açın.", + "wsl.onboarding.finishingDistro": "{{distro}} kurulumu tamamlanıyor.", + "wsl.onboarding.pickDistro": "Bir dağıtım seçin veya aşağıdan bir dağıtım kurun.", + "wsl.onboarding.checkingOpencode": "OpenCode kontrol ediliyor...", + "wsl.onboarding.checkingOpencodeIn": "{{distro}} içinde OpenCode kontrol ediliyor...", + "wsl.onboarding.updatingOpencode": "OpenCode güncelleniyor...", + "wsl.onboarding.updatingOpencodeIn": "{{distro}} içinde OpenCode güncelleniyor...", + "wsl.onboarding.updateOpencodeIn": "{{distro}} içindeki OpenCode'u güncelleyin.", + "wsl.onboarding.updateOpencode": "OpenCode'u güncelle", + "wsl.onboarding.opencodeReadyIn": "{{distro}} içinde OpenCode hazır.", + "wsl.onboarding.opencodeReady": "OpenCode hazır.", + "wsl.onboarding.installOpencodeIn": "OpenCode'u {{distro}} içine kurun.", + "wsl.onboarding.installOpencode": "OpenCode'u kur", + "wsl.onboarding.chooseDistroFirst": "Önce bir dağıtım seçin.", + "wsl.onboarding.loadFailed": "WSL durumu yüklenemedi.", + "wsl.onboarding.loading": "Yükleniyor...", + "wsl.onboarding.installedDistros": "Kurulu dağıtımlar", + "wsl.onboarding.checkAgain": "Tekrar kontrol et", + "wsl.onboarding.distroStatus.ready": "Hazır", + "wsl.onboarding.distroStatus.checking": "Kontrol ediliyor...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode kurulu değil", + "wsl.onboarding.distroStatus.missingTools": "bash ve curl eksik", + "wsl.onboarding.distroStatus.unsupported": "Desteklenmiyor · WSL 2 kullanın", + "wsl.onboarding.needAnotherDistro": "Başka bir dağıtım mı gerekiyor?", + "wsl.onboarding.needAnotherDistroHint": "WSL kataloğundan bir Linux dağıtımı kurun", + "wsl.onboarding.wslNotInstalled.title": "WSL kurulu değil", + "wsl.onboarding.wslNotInstalled.description": + "OpenCode'un WSL sunucusu ekleyebilmesi için önce WSL (Linux için Windows Alt Sistemi) kurulmalıdır", + "wsl.onboarding.wslUnavailable.title": "WSL kullanılamıyor", + "wsl.onboarding.wslUnavailable.description": "OpenCode bu makinede WSL'yi doğrulayamadı.", + "wsl.onboarding.installWsl": "WSL'yi kur", + "wsl.onboarding.windowsRestartRequired": + "WSL kurulumunu tamamlamak için Windows'u yeniden başlatın, ardından OpenCode'u tekrar açın.", + "wsl.onboarding.next": "İleri", + "wsl.onboarding.refresh": "Yenile", + "wsl.onboarding.allDistrosAdded": "Kurulu tüm dağıtımlar zaten eklenmiş.", + "wsl.onboarding.noDistros": "Henüz dağıtım algılanmadı.", + "wsl.onboarding.install": "Kur", + "wsl.onboarding.installing": "Kuruluyor...", + "wsl.onboarding.installDistro": "Dağıtım kur", + "wsl.onboarding.searchDistros": "Dağıtım ara", + "wsl.onboarding.wsl2Required": "WSL 2 gerekli.", + "wsl.onboarding.toolsRequired": "Bu dağıtım için bash ve curl gerekli.", + "wsl.onboarding.openTerminal": "Terminali aç", + "wsl.onboarding.path": "Yol: {{path}}", + "wsl.onboarding.notFound": "bulunamadı", + "wsl.onboarding.version": "Sürüm: {{version}}", + "wsl.onboarding.unknown": "bilinmiyor", + "wsl.onboarding.desktopVersion": "masaüstü {{version}}", + "wsl.onboarding.versionMismatch": "Kurulu sürüm masaüstü uygulamasının sürümüyle eşleşmiyor.", + "wsl.onboarding.adding": "Ekleniyor...", "dialog.project.edit.title": "Projeyi düzenle", "dialog.project.edit.name": "Ad", @@ -408,6 +494,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", @@ -456,10 +543,12 @@ export const dict = { "error.page.title": "Bir şeyler yanlış gitti", "error.page.description": "Uygulama yüklenirken bir hata oluştu.", + "error.page.description.localServerStartup": "Yerel sunucu başlatılırken bir hata oluştu.", "error.page.details.label": "Hata Detayları", "error.page.action.restart": "Yeniden Başlat", "error.page.action.report": "Hatayı Bildir", "error.page.action.reported": "Hata Bildirildi", + "error.page.action.exportLogs": "Günlükleri Dışa Aktar", "error.page.action.checking": "Kontrol ediliyor...", "error.page.action.checkUpdates": "Güncellemeleri kontrol et", "error.page.action.updateTo": "{{version}} sürümüne güncelle", @@ -509,11 +598,32 @@ export const dict = { "home.recentProjects": "Son projeler", "home.empty.title": "Son proje yok", "home.empty.description": "Yerel bir proje açarak başlayın", + "home.title": "Ana Sayfa", + "home.projects": "Projeler", + "home.project.add": "Proje ekle", + "home.recentlyClosed": "Yakın zamanda kapatılanlar", + "home.server.collapse": "Sunucu projelerini daralt", + "home.server.expand": "Sunucu projelerini genişlet", + "home.sessions.search.placeholder": "Oturum ara", + "home.sessions.search.placeholder.scoped": "{{scope}} kapsamında oturum ara", + "home.sessions.search.sessions": "Oturumlar", + "home.sessions.search.noResults": "{{query}} için oturum bulunamadı", + "home.sessions.empty": "Henüz burada bir şey yok", + "home.sessions.empty.description": "Başlamak için bir oturum oluşturun", + "home.sessions.group.today": "Bugün", + "home.sessions.group.yesterday": "Dün", + "home.sessions.group.older": "Daha eski", + "home.providerTip": "Claude, GPT, Gemini ve diğer modelleri kullanmak için 75'ten fazla sağlayıcıya bağlanın", "session.tab.session": "Oturum", "session.tab.review": "İnceleme", "session.tab.context": "Bağlam", + "session.tab.unknown": "Bilinmeyen Oturum", "session.panel.reviewAndFiles": "İnceleme ve dosyalar", + "session.error.notFound": "Bu oturum bulunamıyor", + "session.error.notFound.description": "Bu sekme, artık bu sunucuda bulunmayan bir oturuma yönlendiriyor.", + "session.error.notFound.closeTab": "Sekmeyi Kapat", + "session.error.serverConnection": "Bu sunucuya bağlanılamıyor", "session.review.filesChanged": "{{count}} Dosya Değişti", "session.review.change.one": "Değişiklik", "session.review.change.other": "Değişiklik", @@ -523,6 +633,8 @@ export const dict = { "session.review.noSnapshot": "Yapılandırmada anlık görüntü takibi devre dışı bırakıldı, bu nedenle oturum değişiklikleri kullanılamıyor", "session.review.noChanges": "Değişiklik yok", + "session.review.noUncommittedChanges": "Henüz commit edilmemiş değişiklik yok", + "session.review.noBranchChanges": "Henüz dal değişikliği yok", "session.files.selectToOpen": "Açmak için bir dosya seçin", "session.files.all": "Tüm dosyalar", @@ -539,6 +651,10 @@ export const dict = { "session.todo.title": "Görevler", "session.todo.collapse": "Daralt", "session.todo.expand": "Genişlet", + "session.question.minimize": "Soruyu küçült", + "session.question.restore": "Soruyu geri yükle", + "session.question.pending.one": "{{count}} bekleyen soru", + "session.question.pending.other": "{{count}} bekleyen soru", "session.followupDock.summary.one": "{{count}} sıradaki mesaj", "session.followupDock.summary.other": "{{count}} sıradaki mesaj", "session.followupDock.sendNow": "Şimdi gönder", @@ -552,9 +668,16 @@ export const dict = { "session.revertDock.restore": "Mesajı geri yükle", "session.new.title": "İstediğini yap", + "session.new.project.new": "Yeni proje", + "session.new.project.search": "Proje ara", + "session.new.project.add": "Proje ekle", "session.new.worktree.main": "Ana dal", "session.new.worktree.mainWithBranch": "Ana dal ({{branch}})", "session.new.worktree.create": "Yeni çalışma ağacı oluştur", + "session.new.workspace.runIn": "Oturumu şurada çalıştır", + "session.new.workspace.triggerLocal": "Yerel", + "session.new.workspace.local": "Yerel depo", + "session.new.workspace.existing": "Çalışma alanı…", "session.new.lastModified": "Son değişiklik", "session.header.search.placeholder": "{{project}} ara", @@ -624,6 +747,8 @@ export const dict = { "sidebar.project.recentSessions": "Son oturumlar", "sidebar.project.viewAllSessions": "Tüm oturumları görüntüle", "sidebar.project.clearNotifications": "Bildirimleri temizle", + "sidebar.empty.title": "Açık proje yok", + "sidebar.empty.description": "Başlamak için bir proje açın", "app.name.desktop": "OpenCode Masaüstü", @@ -636,6 +761,7 @@ export const dict = { "settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.", "settings.general.section.appearance": "Görünüm", + "settings.general.section.advanced": "Gelişmiş", "settings.general.section.notifications": "Sistem bildirimleri", "settings.general.section.updates": "Güncellemeler", "settings.general.section.sounds": "Ses efektleri", @@ -644,6 +770,11 @@ export const dict = { "settings.general.row.language.title": "Dil", "settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin", + "settings.general.row.shell.title": "Terminal Kabuğu", + "settings.general.row.shell.description": + "Terminalinizde kullanılacak kabuğu seçin. Uyumlu kabuklar, ajan araç çağrılarında da kullanılır.", + "settings.general.row.shell.autoDefault": "Otomatik (Varsayılan)", + "settings.general.row.shell.terminalOnly": "yalnızca terminal", "settings.general.row.appearance.title": "Görünüm", "settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin", "settings.general.row.colorScheme.title": "Renk şeması", @@ -653,8 +784,8 @@ export const dict = { "settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.", "settings.general.row.font.title": "Kod Yazı Tipi", "settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "Terminal yazı tipi", + "settings.general.row.terminalFont.description": "Terminalde kullanılan yazı tipini özelleştirin", "settings.general.row.uiFont.title": "Arayüz Yazı Tipi", "settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin", "settings.general.row.followup.title": "Takip davranışı", @@ -662,6 +793,21 @@ export const dict = { "Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin", "settings.general.row.followup.option.queue": "Sıra", "settings.general.row.followup.option.steer": "Yönlendir", + "settings.general.row.showFileTree.title": "Dosya ağacı", + "settings.general.row.showFileTree.description": "Oturumlarda dosya ağacı panelini göster", + "settings.general.row.showNavigation.title": "Gezinme denetimleri", + "settings.general.row.showNavigation.description": "Masaüstü başlık çubuğunda geri ve ileri düğmelerini göster", + "settings.general.row.showSearch.title": "Komut paleti", + "settings.general.row.showSearch.description": "Başlık çubuğunda arama ve komut paleti düğmesini göster", + "settings.general.row.showTerminal.title": "Terminal", + "settings.general.row.showTerminal.description": "Masaüstü başlık çubuğunda terminal düğmesini göster", + "settings.general.row.showStatus.title": "Sunucu durumu", + "settings.general.row.showStatus.description": "Başlık çubuğunda sunucu durumu düğmesini göster", + "settings.general.row.mobileTitlebarBottom.title": "Alt gezinme", + "settings.general.row.mobileTitlebarBottom.description": + "Mobil cihazlarda başlık çubuğunu ve oturum sekmelerini ekranın altına yerleştir", + "settings.general.row.showCustomAgents.title": "Özel ajanlar", + "settings.general.row.showCustomAgents.description": "Yazma alanında ajan seçiciyi göster", "settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster", "settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle", "settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet", @@ -670,6 +816,12 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet", "settings.general.row.editToolPartsExpanded.description": "Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster", + "settings.general.row.newLayoutDesigns.title": "Yeni düzen ve tasarımlar", + "settings.general.row.newLayoutDesigns.description": + "Yeniden tasarlanan düzeni, ana sayfayı, yazma alanını ve oturum arayüzünü etkinleştir", + "settings.general.row.pinchZoom.title": "Sıkıştırarak yakınlaştır", + "settings.general.row.pinchZoom.description": + "İzleme dörtgeninde sıkıştırma ve Ctrl-kaydırma hareketleriyle yakınlaştırmaya izin ver", "settings.general.row.wayland.title": "Yerel Wayland kullan", "settings.general.row.wayland.description": @@ -686,6 +838,8 @@ export const dict = { "settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle", "settings.updates.action.checkNow": "Şimdi kontrol et", "settings.updates.action.checking": "Kontrol ediliyor...", + "settings.updates.action.downloading": "İndiriliyor...", + "settings.updates.action.installing": "Kuruluyor...", "settings.updates.toast.latest.title": "Güncelsiniz", "settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.", diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts index d875766e8855..c6a0ae96298c 100644 --- a/packages/app/src/i18n/uk.ts +++ b/packages/app/src/i18n/uk.ts @@ -48,6 +48,7 @@ export const dict = { "command.session.new": "Нова сесія", "command.file.open": "Відкрити файл", "command.tab.close": "Закрити вкладку", + "command.tab.reopenClosed": "Повторно відкрити закриту вкладку", "command.context.addSelection": "Додати виділення до контексту", "command.context.addSelection.description": "Додати вибрані рядки з поточного файлу", "command.input.focus": "Фокус на полі введення", @@ -212,6 +213,11 @@ export const dict = { "model.input.video": "відео", "model.input.pdf": "pdf", "model.tooltip.allows": "Дозволяє: {{inputs}}", + "model.tooltip.context.label": "Контекст", + "model.tooltip.inputs": "Вхідні дані", + "model.tooltip.model": "Модель", + "model.tooltip.provider": "Провайдер", + "model.tooltip.reasoning": "Мислення", "model.tooltip.reasoning.allowed": "Підтримує мислення", "model.tooltip.reasoning.none": "Без мислення", "model.tooltip.context": "Ліміт контексту {{limit}}", @@ -222,6 +228,7 @@ export const dict = { "common.loading": "Завантаження", "common.loading.ellipsis": "...", "common.cancel": "Скасувати", + "common.clear": "Очистити", "common.open": "Відкрити", "common.connect": "Підключити", "common.disconnect": "Відключити", @@ -281,6 +288,11 @@ export const dict = { "prompt.context.removeActiveFile": "Видалити активний файл з контексту", "prompt.context.removeFile": "Видалити файл з контексту", "prompt.action.attachFile": "Додати файли", + "prompt.menu.addImagesAndFiles": "Додати файли та інше", + "prompt.menu.imagesAndFiles": "Зображення та файли", + "prompt.menu.commands": "Команди", + "prompt.menu.context": "Контекст", + "prompt.menu.shellCommand": "Команда оболонки", "prompt.attachment.remove": "Видалити вкладення", "prompt.action.send": "Надіслати", "prompt.action.stop": "Зупинити", @@ -354,6 +366,75 @@ export const dict = { "dialog.server.menu.delete": "Видалити", "dialog.server.current": "Поточний сервер", "dialog.server.status.default": "За замовчуванням", + "wsl.server.add": "Додати сервер WSL", + "wsl.server.addShort": "Додати WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "Сервер WSL", + "wsl.server.retryStart": "Повторити запуск", + "wsl.server.updating": "Оновлення...", + "wsl.onboarding.step.distro": "Вибрати дистрибутив", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "Перевірка WSL...", + "wsl.onboarding.restartRequired": "Щоб завершити встановлення WSL, потрібно перезапустити Windows.", + "wsl.onboarding.ready": "WSL готова до роботи.", + "wsl.onboarding.required": "Для продовження потрібна WSL.", + "wsl.onboarding.checkingDistros": "Перевірка дистрибутивів...", + "wsl.onboarding.installingDistro": "Встановлення {{distro}}...", + "wsl.onboarding.checkingDistro": "Перевірка {{distro}}...", + "wsl.onboarding.listingDistros": "Отримання списку дистрибутивів...", + "wsl.onboarding.distroReady": "{{distro}} готовий до роботи.", + "wsl.onboarding.distroNotInstalled": "{{distro}} ще не встановлено.", + "wsl.onboarding.openDistroOnce": "Відкрийте {{distro}} один раз, щоб завершити налаштування.", + "wsl.onboarding.finishingDistro": "Завершення налаштування {{distro}}.", + "wsl.onboarding.pickDistro": "Виберіть дистрибутив або встановіть один із наведених нижче.", + "wsl.onboarding.checkingOpencode": "Перевірка OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "Перевірка OpenCode у {{distro}}...", + "wsl.onboarding.updatingOpencode": "Оновлення OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "Оновлення OpenCode у {{distro}}...", + "wsl.onboarding.updateOpencodeIn": "Оновіть OpenCode у {{distro}}.", + "wsl.onboarding.updateOpencode": "Оновити OpenCode", + "wsl.onboarding.opencodeReadyIn": "OpenCode готовий до роботи у {{distro}}.", + "wsl.onboarding.opencodeReady": "OpenCode готовий до роботи.", + "wsl.onboarding.installOpencodeIn": "Встановіть OpenCode у {{distro}}.", + "wsl.onboarding.installOpencode": "Встановити OpenCode", + "wsl.onboarding.chooseDistroFirst": "Спочатку виберіть дистрибутив.", + "wsl.onboarding.loadFailed": "Не вдалося завантажити стан WSL.", + "wsl.onboarding.loading": "Завантаження...", + "wsl.onboarding.installedDistros": "Встановлені дистрибутиви", + "wsl.onboarding.checkAgain": "Перевірити ще раз", + "wsl.onboarding.distroStatus.ready": "Готовий", + "wsl.onboarding.distroStatus.checking": "Перевірка...", + "wsl.onboarding.distroStatus.opencodeMissing": "OpenCode не встановлено", + "wsl.onboarding.distroStatus.missingTools": "Немає bash і curl", + "wsl.onboarding.distroStatus.unsupported": "Не підтримується · Використовуйте WSL 2", + "wsl.onboarding.needAnotherDistro": "Потрібен інший дистрибутив?", + "wsl.onboarding.needAnotherDistroHint": "Встановіть дистрибутив Linux із каталогу WSL", + "wsl.onboarding.wslNotInstalled.title": "WSL не встановлено", + "wsl.onboarding.wslNotInstalled.description": + "Для додавання сервера WSL в OpenCode потрібна WSL (Підсистема Windows для Linux)", + "wsl.onboarding.wslUnavailable.title": "WSL недоступна", + "wsl.onboarding.wslUnavailable.description": "OpenCode не вдалося перевірити WSL на цьому комп'ютері.", + "wsl.onboarding.installWsl": "Встановити WSL", + "wsl.onboarding.windowsRestartRequired": + "Перезапустіть Windows, щоб завершити встановлення WSL, а потім знову відкрийте OpenCode.", + "wsl.onboarding.next": "Далі", + "wsl.onboarding.refresh": "Оновити", + "wsl.onboarding.allDistrosAdded": "Усі встановлені дистрибутиви вже додано.", + "wsl.onboarding.noDistros": "Дистрибутивів поки не виявлено.", + "wsl.onboarding.install": "Встановити", + "wsl.onboarding.installing": "Встановлення...", + "wsl.onboarding.installDistro": "Встановити дистрибутив", + "wsl.onboarding.searchDistros": "Пошук дистрибутивів", + "wsl.onboarding.wsl2Required": "Для роботи потрібна WSL 2.", + "wsl.onboarding.toolsRequired": "Для цього дистрибутива потрібні bash і curl.", + "wsl.onboarding.openTerminal": "Відкрити термінал", + "wsl.onboarding.path": "Шлях: {{path}}", + "wsl.onboarding.notFound": "не знайдено", + "wsl.onboarding.version": "Версія: {{version}}", + "wsl.onboarding.unknown": "невідомо", + "wsl.onboarding.desktopVersion": "десктопний застосунок {{version}}", + "wsl.onboarding.versionMismatch": "Встановлена версія не відповідає версії десктопного застосунку.", + "wsl.onboarding.adding": "Додавання...", "server.row.noUsername": "без імені користувача", "dialog.project.edit.title": "Редагувати проєкт", @@ -473,10 +554,12 @@ export const dict = { "error.page.title": "Щось пішло не так", "error.page.description": "Під час завантаження програми сталася помилка.", + "error.page.description.localServerStartup": "Під час запуску локального сервера сталася помилка.", "error.page.details.label": "Деталі помилки", "error.page.action.restart": "Перезапустити", "error.page.action.report": "Повідомити про помилку", "error.page.action.reported": "Помилку повідомлено", + "error.page.action.exportLogs": "Експортувати журнали", "error.page.action.checking": "Перевірка...", "error.page.action.checkUpdates": "Перевірити оновлення", "error.page.action.updateTo": "Оновити до {{version}}", @@ -533,11 +616,33 @@ export const dict = { "home.recentProjects": "Нещодавні проєкти", "home.empty.title": "Немає нещодавніх проєктів", "home.empty.description": "Почніть, відкривши локальний проєкт", + "home.title": "Головна", + "home.projects": "Проєкти", + "home.project.add": "Додати проєкт", + "home.recentlyClosed": "Нещодавно закриті", + "home.server.collapse": "Згорнути проєкти сервера", + "home.server.expand": "Розгорнути проєкти сервера", + "home.sessions.search.placeholder": "Пошук сесій", + "home.sessions.search.placeholder.scoped": "Пошук сесій у {{scope}}", + "home.sessions.search.sessions": "Сесії", + "home.sessions.search.noResults": "Сесій за запитом {{query}} не знайдено", + "home.sessions.empty": "Тут поки нічого немає", + "home.sessions.empty.description": "Створіть сесію, щоб почати", + "home.sessions.group.today": "Сьогодні", + "home.sessions.group.yesterday": "Учора", + "home.sessions.group.older": "Раніше", + "home.providerTip": + "Підключіться до понад 75 провайдерів, щоб використовувати інші моделі, зокрема Claude, GPT, Gemini та інші", "session.tab.session": "Сесія", "session.tab.review": "Огляд", "session.tab.context": "Контекст", + "session.tab.unknown": "Невідома сесія", "session.panel.reviewAndFiles": "Огляд і файли", + "session.error.notFound": "Цю сесію не знайдено", + "session.error.notFound.description": "Ця вкладка посилається на сесію, якої більше немає на цьому сервері.", + "session.error.notFound.closeTab": "Закрити вкладку", + "session.error.serverConnection": "Не вдається підключитися до цього сервера", "session.review.filesChanged": "Змінено файлів: {{count}}", "session.review.change.one": "Зміна", "session.review.change.other": "Зміни", @@ -570,6 +675,10 @@ export const dict = { "session.todo.expand": "Розгорнути", "session.todo.progress": "Виконано {{done}} з {{total}} завдань", "session.question.progress": "{{current}} з {{total}} запитань", + "session.question.minimize": "Згорнути запитання", + "session.question.restore": "Відновити запитання", + "session.question.pending.one": "{{count}} запитання очікує відповіді", + "session.question.pending.other": "Запитання, що очікують відповіді: {{count}}", "session.followupDock.summary.one": "{{count}} повідомлення в черзі", "session.followupDock.summary.other": "{{count}} повідомлень у черзі", "session.followupDock.sendNow": "Надіслати зараз", @@ -583,9 +692,16 @@ export const dict = { "session.revertDock.restore": "Відновити повідомлення", "session.new.title": "Створити що завгодно", + "session.new.project.new": "Новий проєкт", + "session.new.project.search": "Пошук проєктів", + "session.new.project.add": "Додати проєкт", "session.new.worktree.main": "Основна гілка", "session.new.worktree.mainWithBranch": "Основна гілка ({{branch}})", "session.new.worktree.create": "Створити нове робоче дерево", + "session.new.workspace.runIn": "Запустити сесію в", + "session.new.workspace.triggerLocal": "Локально", + "session.new.workspace.local": "Локальний репозиторій", + "session.new.workspace.existing": "Робоча область…", "session.new.lastModified": "Востаннє змінено", "session.header.search.placeholder": "Пошук {{project}}", @@ -773,6 +889,11 @@ export const dict = { "settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу", "settings.general.row.showStatus.title": "Статус сервера", "settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку", + "settings.general.row.mobileTitlebarBottom.title": "Нижня навігація", + "settings.general.row.mobileTitlebarBottom.description": + "Розмістити заголовок і вкладки сесії внизу екрана на мобільних пристроях", + "settings.general.row.showCustomAgents.title": "Користувацькі агенти", + "settings.general.row.showCustomAgents.description": "Показувати вибір агента в редакторі запиту", "settings.general.row.reasoningSummaries.title": "Показувати підсумки мислення", "settings.general.row.reasoningSummaries.description": "Відображати підсумки мислення моделі на часовій шкалі", "settings.general.row.shellToolPartsExpanded.title": "Розгортати частини інструменту оболонки", @@ -781,6 +902,12 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Розгортати частини інструменту редагування", "settings.general.row.editToolPartsExpanded.description": "Показувати частини інструментів редагування, запису та патчів розгорнутими за замовчуванням на часовій шкалі", + "settings.general.row.newLayoutDesigns.title": "Новий макет і дизайн", + "settings.general.row.newLayoutDesigns.description": + "Увімкнути оновлений макет та інтерфейси головної сторінки, редактора запитів і сесії", + "settings.general.row.pinchZoom.title": "Масштабування щипком", + "settings.general.row.pinchZoom.description": + "Дозволити масштабування жестом щипка на трекпаді та прокручуванням із Ctrl", "settings.general.row.wayland.title": "Використовувати нативний Wayland", "settings.general.row.wayland.description": "Вимкнути резервний X11 на Wayland. Потребує перезапуску.", @@ -796,6 +923,8 @@ export const dict = { "settings.updates.row.check.description": "Вручну перевірити наявність оновлень і встановити, якщо доступні", "settings.updates.action.checkNow": "Перевірити зараз", "settings.updates.action.checking": "Перевірка...", + "settings.updates.action.downloading": "Завантаження...", + "settings.updates.action.installing": "Встановлення...", "settings.updates.toast.latest.title": "У вас актуальна версія", "settings.updates.toast.latest.description": "Ви використовуєте останню версію OpenCode.", "sound.option.none": "Немає", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 15d873ee04a9..a44bfc107f4a 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -28,6 +28,9 @@ export const dict = { "command.sidebar.toggle": "切换侧边栏", "command.project.open": "打开项目", + "command.project.previous": "上一个项目", + "command.project.next": "下一个项目", + "command.project.index": "切换到项目 {{index}}", "command.provider.connect": "连接提供商", @@ -56,6 +59,7 @@ export const dict = { "command.file.open": "打开文件", "command.tab.close": "关闭标签页", + "command.tab.reopenClosed": "重新打开已关闭的标签页", "command.context.addSelection": "将所选内容添加到上下文", "command.context.addSelection.description": "添加当前文件中选中的行", @@ -230,6 +234,11 @@ export const dict = { "model.input.video": "视频", "model.input.pdf": "pdf", "model.tooltip.allows": "支持:{{inputs}}", + "model.tooltip.context.label": "上下文", + "model.tooltip.inputs": "输入", + "model.tooltip.model": "模型", + "model.tooltip.provider": "提供商", + "model.tooltip.reasoning": "推理", "model.tooltip.reasoning.allowed": "支持推理", "model.tooltip.reasoning.none": "不支持推理", "model.tooltip.context": "上下文上限 {{limit}}", @@ -240,6 +249,7 @@ export const dict = { "common.loading": "加载中", "common.loading.ellipsis": "...", "common.cancel": "取消", + "common.clear": "清除", "common.connect": "连接", "common.disconnect": "断开连接", "common.continue": "提交", @@ -257,6 +267,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "按 esc 退出", + "session.child.promptDisabled": "无法向子智能体会话发送提示词。", + "session.child.backToParent": "返回主会话。", "prompt.example.1": "修复代码库中的一个 TODO", "prompt.example.2": "这个项目的技术栈是什么?", "prompt.example.3": "修复失败的测试", @@ -294,6 +306,11 @@ export const dict = { "prompt.context.removeActiveFile": "从上下文移除活动文件", "prompt.context.removeFile": "从上下文移除文件", "prompt.action.attachFile": "附加文件", + "prompt.menu.addImagesAndFiles": "添加文件及更多内容", + "prompt.menu.imagesAndFiles": "图片和文件", + "prompt.menu.commands": "命令", + "prompt.menu.context": "上下文", + "prompt.menu.shellCommand": "shell 命令", "prompt.attachment.remove": "移除附件", "prompt.action.send": "发送", "prompt.action.stop": "停止", @@ -359,6 +376,73 @@ export const dict = { "dialog.server.menu.delete": "删除", "dialog.server.current": "当前服务器", "dialog.server.status.default": "默认", + "wsl.server.add": "添加 WSL 服务器", + "wsl.server.addShort": "添加 WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "WSL 服务器", + "wsl.server.retryStart": "重试启动", + "wsl.server.updating": "正在更新...", + "wsl.onboarding.step.distro": "选择发行版", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "正在检查 WSL...", + "wsl.onboarding.restartRequired": "Windows 需要重启才能完成 WSL 安装。", + "wsl.onboarding.ready": "WSL 已就绪。", + "wsl.onboarding.required": "需要 WSL 才能继续。", + "wsl.onboarding.checkingDistros": "正在检查发行版...", + "wsl.onboarding.installingDistro": "正在安装 {{distro}}...", + "wsl.onboarding.checkingDistro": "正在检查 {{distro}}...", + "wsl.onboarding.listingDistros": "正在列出发行版...", + "wsl.onboarding.distroReady": "{{distro}} 已就绪。", + "wsl.onboarding.distroNotInstalled": "{{distro}} 尚未安装。", + "wsl.onboarding.openDistroOnce": "打开一次 {{distro}} 以完成设置。", + "wsl.onboarding.finishingDistro": "正在完成 {{distro}} 的设置。", + "wsl.onboarding.pickDistro": "选择一个发行版,或在下方安装。", + "wsl.onboarding.checkingOpencode": "正在检查 OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "正在检查 {{distro}} 中的 OpenCode...", + "wsl.onboarding.updatingOpencode": "正在更新 OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "正在更新 {{distro}} 中的 OpenCode...", + "wsl.onboarding.updateOpencodeIn": "更新 {{distro}} 中的 OpenCode。", + "wsl.onboarding.updateOpencode": "更新 OpenCode", + "wsl.onboarding.opencodeReadyIn": "{{distro}} 中的 OpenCode 已就绪。", + "wsl.onboarding.opencodeReady": "OpenCode 已就绪。", + "wsl.onboarding.installOpencodeIn": "在 {{distro}} 中安装 OpenCode。", + "wsl.onboarding.installOpencode": "安装 OpenCode", + "wsl.onboarding.chooseDistroFirst": "请先选择发行版。", + "wsl.onboarding.loadFailed": "加载 WSL 状态失败。", + "wsl.onboarding.loading": "加载中...", + "wsl.onboarding.installedDistros": "已安装的发行版", + "wsl.onboarding.checkAgain": "重新检查", + "wsl.onboarding.distroStatus.ready": "已就绪", + "wsl.onboarding.distroStatus.checking": "检查中...", + "wsl.onboarding.distroStatus.opencodeMissing": "未安装 OpenCode", + "wsl.onboarding.distroStatus.missingTools": "缺少 bash、curl", + "wsl.onboarding.distroStatus.unsupported": "不支持 · 请使用 WSL 2", + "wsl.onboarding.needAnotherDistro": "需要其他发行版?", + "wsl.onboarding.needAnotherDistroHint": "从 WSL 目录安装 Linux 发行版", + "wsl.onboarding.wslNotInstalled.title": "未安装 WSL", + "wsl.onboarding.wslNotInstalled.description": "OpenCode 需要 WSL(Windows Subsystem for Linux)才能添加 WSL 服务器", + "wsl.onboarding.wslUnavailable.title": "WSL 不可用", + "wsl.onboarding.wslUnavailable.description": "OpenCode 无法验证此设备上的 WSL。", + "wsl.onboarding.installWsl": "安装 WSL", + "wsl.onboarding.windowsRestartRequired": "重启 Windows 以完成 WSL 安装,然后重新打开 OpenCode。", + "wsl.onboarding.next": "下一步", + "wsl.onboarding.refresh": "刷新", + "wsl.onboarding.allDistrosAdded": "所有已安装的发行版均已添加。", + "wsl.onboarding.noDistros": "尚未检测到发行版。", + "wsl.onboarding.install": "安装", + "wsl.onboarding.installing": "正在安装...", + "wsl.onboarding.installDistro": "安装发行版", + "wsl.onboarding.searchDistros": "搜索发行版", + "wsl.onboarding.wsl2Required": "需要 WSL 2。", + "wsl.onboarding.toolsRequired": "此发行版需要 bash 和 curl。", + "wsl.onboarding.openTerminal": "打开终端", + "wsl.onboarding.path": "路径:{{path}}", + "wsl.onboarding.notFound": "未找到", + "wsl.onboarding.version": "版本:{{version}}", + "wsl.onboarding.unknown": "未知", + "wsl.onboarding.desktopVersion": "桌面版 {{version}}", + "wsl.onboarding.versionMismatch": "已安装版本与桌面应用版本不一致。", + "wsl.onboarding.adding": "正在添加...", "dialog.project.edit.title": "编辑项目", "dialog.project.edit.name": "名称", @@ -418,6 +502,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", @@ -456,10 +541,12 @@ export const dict = { "error.page.title": "出了点问题", "error.page.description": "加载应用程序时发生错误。", + "error.page.description.localServerStartup": "启动本地服务器时发生错误。", "error.page.details.label": "错误详情", "error.page.action.restart": "重启", "error.page.action.report": "上报错误", "error.page.action.reported": "错误已上报", + "error.page.action.exportLogs": "导出日志", "error.page.action.checking": "检查中...", "error.page.action.checkUpdates": "检查更新", "error.page.action.updateTo": "更新到 {{version}}", @@ -506,6 +593,9 @@ export const dict = { "home.title": "主页", "home.projects": "项目", "home.project.add": "添加项目", + "home.recentlyClosed": "最近关闭", + "home.server.collapse": "折叠服务器项目", + "home.server.expand": "展开服务器项目", "home.sessions.search.placeholder": "搜索会话", "home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜索会话", "home.sessions.search.sessions": "会话", @@ -515,11 +605,17 @@ export const dict = { "home.sessions.group.today": "今天", "home.sessions.group.yesterday": "昨天", "home.sessions.group.older": "更早", + "home.providerTip": "连接 75 个以上的提供商,使用 Claude、GPT、Gemini 等其他模型", "session.tab.session": "会话", "session.tab.review": "审查", "session.tab.context": "上下文", + "session.tab.unknown": "未知会话", "session.panel.reviewAndFiles": "审查和文件", + "session.error.notFound": "找不到此会话", + "session.error.notFound.description": "此标签页指向的会话已不存在于此服务器上。", + "session.error.notFound.closeTab": "关闭标签页", + "session.error.serverConnection": "无法连接到此服务器", "session.review.filesChanged": "{{count}} 个文件变更", "session.review.change.one": "更改", "session.review.change.other": "更改", @@ -528,6 +624,8 @@ export const dict = { "session.review.noVcs": "未检测到 Git 版本控制系统,无法显示更改", "session.review.noSnapshot": "配置中已禁用快照跟踪,因此会话更改不可用", "session.review.noChanges": "无更改", + "session.review.noUncommittedChanges": "尚无未提交的更改", + "session.review.noBranchChanges": "尚无分支更改", "session.files.selectToOpen": "选择要打开的文件", "session.files.all": "所有文件", "session.files.empty": "无文件", @@ -541,6 +639,10 @@ export const dict = { "session.todo.title": "待办事项", "session.todo.collapse": "折叠", "session.todo.expand": "展开", + "session.question.minimize": "最小化问题", + "session.question.restore": "恢复问题", + "session.question.pending.one": "{{count}} 个待处理问题", + "session.question.pending.other": "{{count}} 个待处理问题", "session.followupDock.summary.one": "{{count}} 条排队消息", "session.followupDock.summary.other": "{{count}} 条排队消息", "session.followupDock.sendNow": "立即发送", @@ -553,9 +655,16 @@ export const dict = { "session.revertDock.expand": "展开已回滚消息", "session.revertDock.restore": "恢复消息", "session.new.title": "构建任何东西", + "session.new.project.new": "新建项目", + "session.new.project.search": "搜索项目", + "session.new.project.add": "添加项目", "session.new.worktree.main": "主分支", "session.new.worktree.mainWithBranch": "主分支({{branch}})", "session.new.worktree.create": "创建新的 worktree", + "session.new.workspace.runIn": "运行会话的位置", + "session.new.workspace.triggerLocal": "本地", + "session.new.workspace.local": "本地仓库", + "session.new.workspace.existing": "工作区…", "session.new.lastModified": "最后修改", "session.header.search.placeholder": "搜索 {{project}}", "session.header.searchFiles": "搜索文件", @@ -623,6 +732,8 @@ export const dict = { "sidebar.project.recentSessions": "最近会话", "sidebar.project.viewAllSessions": "查看全部会话", "sidebar.project.clearNotifications": "清除通知", + "sidebar.empty.title": "没有打开的项目", + "sidebar.empty.description": "打开一个项目以开始使用", "app.name.desktop": "OpenCode Desktop", @@ -637,6 +748,7 @@ export const dict = { "settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。", "settings.general.section.appearance": "外观", + "settings.general.section.advanced": "高级", "settings.general.section.notifications": "系统通知", "settings.general.section.updates": "更新", "settings.general.section.sounds": "音效", @@ -644,6 +756,10 @@ export const dict = { "settings.general.section.display": "显示", "settings.general.row.language.title": "语言", "settings.general.row.language.description": "更改 OpenCode 的显示语言", + "settings.general.row.shell.title": "终端 Shell", + "settings.general.row.shell.description": "选择终端使用的 shell。兼容的 shell 也会用于智能体工具调用。", + "settings.general.row.shell.autoDefault": "自动(默认)", + "settings.general.row.shell.terminalOnly": "仅终端", "settings.general.row.appearance.title": "外观", "settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观", "settings.general.row.colorScheme.title": "配色方案", @@ -652,20 +768,38 @@ export const dict = { "settings.general.row.theme.description": "自定义 OpenCode 的主题。", "settings.general.row.font.title": "代码字体", "settings.general.row.font.description": "自定义代码块使用的字体", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "终端字体", + "settings.general.row.terminalFont.description": "自定义终端使用的字体", "settings.general.row.uiFont.title": "界面字体", "settings.general.row.uiFont.description": "自定义整个界面使用的字体", "settings.general.row.followup.title": "跟进消息行为", "settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待", "settings.general.row.followup.option.queue": "排队", "settings.general.row.followup.option.steer": "引导", + "settings.general.row.showFileTree.title": "文件树", + "settings.general.row.showFileTree.description": "在会话中显示文件树面板", + "settings.general.row.showNavigation.title": "导航控件", + "settings.general.row.showNavigation.description": "在桌面版标题栏中显示前进和后退按钮", + "settings.general.row.showSearch.title": "命令面板", + "settings.general.row.showSearch.description": "在标题栏中显示搜索和命令面板按钮", + "settings.general.row.showTerminal.title": "终端", + "settings.general.row.showTerminal.description": "在桌面版标题栏中显示终端按钮", + "settings.general.row.showStatus.title": "服务器状态", + "settings.general.row.showStatus.description": "在标题栏中显示服务器状态按钮", + "settings.general.row.mobileTitlebarBottom.title": "底部导航", + "settings.general.row.mobileTitlebarBottom.description": "在移动设备上将标题栏和会话标签页置于屏幕底部", + "settings.general.row.showCustomAgents.title": "自定义智能体", + "settings.general.row.showCustomAgents.description": "在输入框中显示智能体选择器", "settings.general.row.reasoningSummaries.title": "显示推理摘要", "settings.general.row.reasoningSummaries.description": "在时间线中显示模型推理摘要", "settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分", "settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分", "settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分", "settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分", + "settings.general.row.newLayoutDesigns.title": "新版布局和设计", + "settings.general.row.newLayoutDesigns.description": "启用重新设计的布局、主页、输入框和会话界面", + "settings.general.row.pinchZoom.title": "双指缩放", + "settings.general.row.pinchZoom.description": "允许使用触控板双指捏合和 Ctrl-scroll 手势缩放", "settings.general.row.wayland.title": "使用原生 Wayland", "settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。", "settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。", @@ -678,6 +812,8 @@ export const dict = { "settings.updates.row.check.description": "手动检查更新并在有更新时安装", "settings.updates.action.checkNow": "立即检查", "settings.updates.action.checking": "正在检查...", + "settings.updates.action.downloading": "正在下载...", + "settings.updates.action.installing": "正在安装...", "settings.updates.toast.latest.title": "已是最新版本", "settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 7998dde3ef87..e9a1fea29e66 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -27,6 +27,9 @@ export const dict = { "command.sidebar.toggle": "切換側邊欄", "command.project.open": "開啟專案", + "command.project.previous": "上一個專案", + "command.project.next": "下一個專案", + "command.project.index": "切換至專案 {{index}}", "command.provider.connect": "連接提供者", "command.server.switch": "切換伺服器", "command.settings.open": "開啟設定", @@ -49,6 +52,7 @@ export const dict = { "command.session.new": "新增工作階段", "command.file.open": "開啟檔案", "command.tab.close": "關閉分頁", + "command.tab.reopenClosed": "重新開啟已關閉的分頁", "command.context.addSelection": "將選取內容加入上下文", "command.context.addSelection.description": "加入目前檔案中選取的行", "command.input.focus": "聚焦輸入框", @@ -209,6 +213,11 @@ export const dict = { "model.input.video": "影片", "model.input.pdf": "pdf", "model.tooltip.allows": "支援: {{inputs}}", + "model.tooltip.context.label": "上下文", + "model.tooltip.inputs": "輸入", + "model.tooltip.model": "模型", + "model.tooltip.provider": "供應商", + "model.tooltip.reasoning": "推理", "model.tooltip.reasoning.allowed": "支援推理", "model.tooltip.reasoning.none": "不支援推理", "model.tooltip.context": "上下文上限 {{limit}}", @@ -218,6 +227,7 @@ export const dict = { "common.loading": "載入中", "common.loading.ellipsis": "...", "common.cancel": "取消", + "common.clear": "清除", "common.connect": "連線", "common.disconnect": "中斷連線", "common.continue": "提交", @@ -235,6 +245,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "按 esc 退出", + "session.child.promptDisabled": "無法在子代理程式工作階段中輸入提示詞。", + "session.child.backToParent": "返回主要工作階段。", "prompt.example.1": "修復程式碼庫中的一個 TODO", "prompt.example.2": "這個專案的技術堆疊是什麼?", @@ -274,6 +286,11 @@ export const dict = { "prompt.context.removeActiveFile": "從上下文移除目前檔案", "prompt.context.removeFile": "從上下文移除檔案", "prompt.action.attachFile": "附加檔案", + "prompt.menu.addImagesAndFiles": "新增檔案及更多內容", + "prompt.menu.imagesAndFiles": "圖片和檔案", + "prompt.menu.commands": "命令", + "prompt.menu.context": "上下文", + "prompt.menu.shellCommand": "shell 命令", "prompt.attachment.remove": "移除附件", "prompt.action.send": "傳送", "prompt.action.stop": "停止", @@ -340,6 +357,73 @@ export const dict = { "dialog.server.menu.delete": "刪除", "dialog.server.current": "目前伺服器", "dialog.server.status.default": "預設", + "wsl.server.add": "新增 WSL 伺服器", + "wsl.server.addShort": "新增 WSL", + "wsl.server.label": "WSL", + "wsl.server.menu.label": "WSL 伺服器", + "wsl.server.retryStart": "重試啟動", + "wsl.server.updating": "更新中...", + "wsl.onboarding.step.distro": "選擇發行版", + "wsl.onboarding.step.opencode": "OpenCode", + "wsl.onboarding.checkingRuntime": "正在檢查 WSL...", + "wsl.onboarding.restartRequired": "Windows 需要重新啟動,才能完成 WSL 安裝。", + "wsl.onboarding.ready": "WSL 已就緒。", + "wsl.onboarding.required": "需要 WSL 才能繼續。", + "wsl.onboarding.checkingDistros": "正在檢查發行版...", + "wsl.onboarding.installingDistro": "正在安裝 {{distro}}...", + "wsl.onboarding.checkingDistro": "正在檢查 {{distro}}...", + "wsl.onboarding.listingDistros": "正在列出發行版...", + "wsl.onboarding.distroReady": "{{distro}} 已就緒。", + "wsl.onboarding.distroNotInstalled": "尚未安裝 {{distro}}。", + "wsl.onboarding.openDistroOnce": "開啟 {{distro}} 一次以完成設定。", + "wsl.onboarding.finishingDistro": "正在完成 {{distro}} 的設定。", + "wsl.onboarding.pickDistro": "選擇發行版,或在下方安裝。", + "wsl.onboarding.checkingOpencode": "正在檢查 OpenCode...", + "wsl.onboarding.checkingOpencodeIn": "正在檢查 {{distro}} 中的 OpenCode...", + "wsl.onboarding.updatingOpencode": "正在更新 OpenCode...", + "wsl.onboarding.updatingOpencodeIn": "正在更新 {{distro}} 中的 OpenCode...", + "wsl.onboarding.updateOpencodeIn": "更新 {{distro}} 中的 OpenCode。", + "wsl.onboarding.updateOpencode": "更新 OpenCode", + "wsl.onboarding.opencodeReadyIn": "{{distro}} 中的 OpenCode 已就緒。", + "wsl.onboarding.opencodeReady": "OpenCode 已就緒。", + "wsl.onboarding.installOpencodeIn": "在 {{distro}} 中安裝 OpenCode。", + "wsl.onboarding.installOpencode": "安裝 OpenCode", + "wsl.onboarding.chooseDistroFirst": "請先選擇發行版。", + "wsl.onboarding.loadFailed": "無法載入 WSL 狀態。", + "wsl.onboarding.loading": "載入中...", + "wsl.onboarding.installedDistros": "已安裝的發行版", + "wsl.onboarding.checkAgain": "再次檢查", + "wsl.onboarding.distroStatus.ready": "就緒", + "wsl.onboarding.distroStatus.checking": "檢查中...", + "wsl.onboarding.distroStatus.opencodeMissing": "未安裝 OpenCode", + "wsl.onboarding.distroStatus.missingTools": "缺少 bash、curl", + "wsl.onboarding.distroStatus.unsupported": "不支援 · 請使用 WSL 2", + "wsl.onboarding.needAnotherDistro": "需要其他發行版?", + "wsl.onboarding.needAnotherDistroHint": "從 WSL 目錄安裝 Linux 發行版", + "wsl.onboarding.wslNotInstalled.title": "未安裝 WSL", + "wsl.onboarding.wslNotInstalled.description": "新增 WSL 伺服器前,必須先安裝 WSL(Windows Subsystem for Linux)", + "wsl.onboarding.wslUnavailable.title": "WSL 無法使用", + "wsl.onboarding.wslUnavailable.description": "OpenCode 無法確認此電腦上的 WSL。", + "wsl.onboarding.installWsl": "安裝 WSL", + "wsl.onboarding.windowsRestartRequired": "重新啟動 Windows 以完成 WSL 安裝,然後重新開啟 OpenCode。", + "wsl.onboarding.next": "下一步", + "wsl.onboarding.refresh": "重新整理", + "wsl.onboarding.allDistrosAdded": "所有已安裝的發行版都已新增。", + "wsl.onboarding.noDistros": "尚未偵測到發行版。", + "wsl.onboarding.install": "安裝", + "wsl.onboarding.installing": "安裝中...", + "wsl.onboarding.installDistro": "安裝發行版", + "wsl.onboarding.searchDistros": "搜尋發行版", + "wsl.onboarding.wsl2Required": "需要 WSL 2。", + "wsl.onboarding.toolsRequired": "此發行版需要 bash 和 curl。", + "wsl.onboarding.openTerminal": "開啟終端機", + "wsl.onboarding.path": "路徑: {{path}}", + "wsl.onboarding.notFound": "找不到", + "wsl.onboarding.version": "版本: {{version}}", + "wsl.onboarding.unknown": "未知", + "wsl.onboarding.desktopVersion": "桌面版 {{version}}", + "wsl.onboarding.versionMismatch": "已安裝版本與桌面應用程式版本不符。", + "wsl.onboarding.adding": "新增中...", "dialog.project.edit.title": "編輯專案", "dialog.project.edit.name": "名稱", @@ -402,6 +486,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", @@ -449,10 +534,12 @@ export const dict = { "error.page.title": "出了點問題", "error.page.description": "載入應用程式時發生錯誤。", + "error.page.description.localServerStartup": "啟動本機伺服器時發生錯誤。", "error.page.details.label": "錯誤詳情", "error.page.action.restart": "重新啟動", "error.page.action.report": "回報錯誤", "error.page.action.reported": "已回報錯誤", + "error.page.action.exportLogs": "匯出日誌", "error.page.action.checking": "檢查中...", "error.page.action.checkUpdates": "檢查更新", "error.page.action.updateTo": "更新到 {{version}}", @@ -498,17 +585,40 @@ export const dict = { "home.recentProjects": "最近專案", "home.empty.title": "沒有最近專案", "home.empty.description": "透過開啟本地專案開始使用", + "home.title": "首頁", + "home.projects": "專案", + "home.project.add": "新增專案", + "home.recentlyClosed": "最近關閉", + "home.server.collapse": "收合伺服器專案", + "home.server.expand": "展開伺服器專案", + "home.sessions.search.placeholder": "搜尋工作階段", + "home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜尋工作階段", + "home.sessions.search.sessions": "工作階段", + "home.sessions.search.noResults": "找不到符合 {{query}} 的工作階段", + "home.sessions.empty": "這裡還沒有內容", + "home.sessions.empty.description": "建立工作階段以開始使用", + "home.sessions.group.today": "今天", + "home.sessions.group.yesterday": "昨天", + "home.sessions.group.older": "更早", + "home.providerTip": "連接 75 個以上的供應商,使用 Claude、GPT、Gemini 等其他模型", "session.tab.session": "工作階段", "session.tab.review": "審查", "session.tab.context": "上下文", + "session.tab.unknown": "未知的工作階段", "session.panel.reviewAndFiles": "審查與檔案", + "session.error.notFound": "找不到此工作階段", + "session.error.notFound.description": "此分頁指向的工作階段已不存在於此伺服器。", + "session.error.notFound.closeTab": "關閉分頁", + "session.error.serverConnection": "無法連線至此伺服器", "session.review.filesChanged": "{{count}} 個檔案變更", "session.review.change.one": "變更", "session.review.change.other": "變更", "session.review.loadingChanges": "正在載入變更...", "session.review.empty": "此工作階段暫無變更", "session.review.noChanges": "沒有變更", + "session.review.noUncommittedChanges": "尚無未提交的變更", + "session.review.noBranchChanges": "尚無分支變更", "session.review.noVcs": "未偵測到 Git 版本控制系統,無法顯示變更", "session.review.noSnapshot": "設定中已停用快照追蹤,因此無法使用工作階段變更", "session.files.selectToOpen": "選取要開啟的檔案", @@ -525,6 +635,10 @@ export const dict = { "session.todo.title": "待辦事項", "session.todo.collapse": "折疊", "session.todo.expand": "展開", + "session.question.minimize": "最小化問題", + "session.question.restore": "還原問題", + "session.question.pending.one": "{{count}} 個待處理問題", + "session.question.pending.other": "{{count}} 個待處理問題", "session.followupDock.summary.one": "{{count}} 則佇列訊息", "session.followupDock.summary.other": "{{count}} 則佇列訊息", "session.followupDock.sendNow": "立即傳送", @@ -538,9 +652,16 @@ export const dict = { "session.revertDock.restore": "還原訊息", "session.new.title": "建構任何東西", + "session.new.project.new": "新專案", + "session.new.project.search": "搜尋專案", + "session.new.project.add": "新增專案", "session.new.worktree.main": "主分支", "session.new.worktree.mainWithBranch": "主分支 ({{branch}})", "session.new.worktree.create": "建立新的 worktree", + "session.new.workspace.runIn": "工作階段執行位置", + "session.new.workspace.triggerLocal": "本機", + "session.new.workspace.local": "本機儲存庫", + "session.new.workspace.existing": "工作區…", "session.new.lastModified": "最後修改", "session.header.search.placeholder": "搜尋 {{project}}", @@ -608,6 +729,8 @@ export const dict = { "sidebar.project.recentSessions": "最近工作階段", "sidebar.project.viewAllSessions": "查看全部工作階段", "sidebar.project.clearNotifications": "清除通知", + "sidebar.empty.title": "未開啟任何專案", + "sidebar.empty.description": "開啟專案以開始使用", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "桌面", @@ -619,6 +742,7 @@ export const dict = { "settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。", "settings.general.section.appearance": "外觀", + "settings.general.section.advanced": "進階", "settings.general.section.notifications": "系統通知", "settings.general.section.updates": "更新", "settings.general.section.sounds": "音效", @@ -627,6 +751,10 @@ export const dict = { "settings.general.row.language.title": "語言", "settings.general.row.language.description": "變更 OpenCode 的顯示語言", + "settings.general.row.shell.title": "終端機 Shell", + "settings.general.row.shell.description": "選擇終端機使用的 Shell。相容的 Shell 也會用於代理程式工具呼叫。", + "settings.general.row.shell.autoDefault": "自動(預設)", + "settings.general.row.shell.terminalOnly": "僅限終端機", "settings.general.row.appearance.title": "外觀", "settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀", "settings.general.row.colorScheme.title": "配色方案", @@ -635,14 +763,28 @@ export const dict = { "settings.general.row.theme.description": "自訂 OpenCode 的主題。", "settings.general.row.font.title": "程式碼字型", "settings.general.row.font.description": "自訂程式碼區塊使用的字型", - "settings.general.row.terminalFont.title": "Terminal Font", - "settings.general.row.terminalFont.description": "Customise the font used in the terminal", + "settings.general.row.terminalFont.title": "終端機字型", + "settings.general.row.terminalFont.description": "自訂終端機使用的字型", "settings.general.row.uiFont.title": "介面字型", "settings.general.row.uiFont.description": "自訂整個介面使用的字型", "settings.general.row.followup.title": "後續追問行為", "settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待", "settings.general.row.followup.option.queue": "佇列", "settings.general.row.followup.option.steer": "引導", + "settings.general.row.showFileTree.title": "檔案樹", + "settings.general.row.showFileTree.description": "在工作階段中顯示檔案樹面板", + "settings.general.row.showNavigation.title": "導覽控制項", + "settings.general.row.showNavigation.description": "在桌面標題列中顯示上一頁和下一頁按鈕", + "settings.general.row.showSearch.title": "命令面板", + "settings.general.row.showSearch.description": "在標題列中顯示搜尋和命令面板按鈕", + "settings.general.row.showTerminal.title": "終端機", + "settings.general.row.showTerminal.description": "在桌面標題列中顯示終端機按鈕", + "settings.general.row.showStatus.title": "伺服器狀態", + "settings.general.row.showStatus.description": "在標題列中顯示伺服器狀態按鈕", + "settings.general.row.mobileTitlebarBottom.title": "底部導覽", + "settings.general.row.mobileTitlebarBottom.description": "在行動裝置上將標題列和工作階段分頁置於畫面底部", + "settings.general.row.showCustomAgents.title": "自訂代理程式", + "settings.general.row.showCustomAgents.description": "在輸入區顯示代理程式選擇器", "settings.general.row.reasoningSummaries.title": "顯示推理摘要", "settings.general.row.reasoningSummaries.description": "在時間軸中顯示模型推理摘要", @@ -650,6 +792,10 @@ export const dict = { "settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊", "settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊", "settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊", + "settings.general.row.newLayoutDesigns.title": "新版面與設計", + "settings.general.row.newLayoutDesigns.description": "啟用重新設計的版面、首頁、輸入區和工作階段介面", + "settings.general.row.pinchZoom.title": "雙指縮放", + "settings.general.row.pinchZoom.description": "允許使用觸控板雙指開合和 Ctrl-捲動手勢縮放", "settings.general.row.wayland.title": "使用原生 Wayland", "settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。", "settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。", @@ -663,6 +809,8 @@ export const dict = { "settings.updates.row.check.description": "手動檢查更新並在有更新時安裝", "settings.updates.action.checkNow": "立即檢查", "settings.updates.action.checking": "檢查中...", + "settings.updates.action.downloading": "下載中...", + "settings.updates.action.installing": "安裝中...", "settings.updates.toast.latest.title": "已是最新版本", "settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。", diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 762b8b2c8e02..c8a89672dfca 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -35,7 +35,7 @@ import { usePlatform } from "@/context/platform" import { DateTime } from "luxon" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDirectoryPicker } from "@/components/directory-picker" -import { useSettingsCommand } from "@/components/settings-dialog" +import { useSettingsDialog } from "@/components/settings-dialog" import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server" import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2" import { ServerConnection, serverName, useServer } from "@/context/server" @@ -68,6 +68,7 @@ import { preloadMarkdown } from "@opencode-ai/session-ui/markdown-cache" import { archiveHomeSession } from "./home-session-archive" import { shouldOpenSessionInBackground } from "./home-session-open" import { showToast } from "@/utils/toast" +import { fileManagerApp } from "@/utils/file-manager" const HOME_SESSION_LIMIT = 64 const HOME_SESSION_HEADER_STICKY_TOP = 12 @@ -267,7 +268,7 @@ export function NewHome() { const command = useCommand() const notification = useNotification() const marked = useMarked() - const openSettings = useSettingsCommand() + const openSettings = useSettingsDialog() let focusSessionSearch: (() => void) | undefined const [state, setState] = createStore({ search: "", @@ -1030,8 +1031,24 @@ function HomeProjectRow(props: { language: ReturnType }) { const global = useGlobal() + const platform = usePlatform() const serverUnreachable = () => global.servers.health[ServerConnection.key(props.server)]?.healthy === false const [state, setState] = createStore({ menuOpen: false }) + const canRevealInFileManager = () => + platform.platform === "desktop" && !!platform.openPath && ServerConnection.local(props.server) + const fileManagerActionLabel = () => + props.language.t( + fileManagerApp(platform.platform === "desktop" ? (platform.os ?? "unknown") : "unknown").actionLabel, + ) + const revealInFileManager = () => { + if (!platform.openPath) return + platform.openPath(props.project.worktree).catch((err: unknown) => + showToast({ + title: props.language.t("common.requestFailed"), + description: errorMessage(err, props.language.t("common.requestFailed")), + }), + ) + } return (
+ serverSync().child(sdk().directory)[0].provider_ready} + connected={() => providers.paid().length > 0} + openProviders={openProviderSettings} + />
) } + +function ProviderTip(props: { ready: () => boolean; connected: () => boolean; openProviders: () => void }) { + const language = useLanguage() + const [persistedState, setPersistedState, , persistedReady] = persisted( + Persist.global("new-session.provider-tip"), + createStore({ dismissedAt: 0 }), + ) + const visible = createMemo( + () => + props.ready() && + persistedReady() && + !props.connected() && + Date.now() - persistedState.dismissedAt >= providerTipDismissalDuration, + ) + + function dismiss() { + setPersistedState("dismissedAt", Date.now()) + } + + const [ref, setRef] = createSignal() + const presence = createPresence({ + show: () => visible(), + element: () => ref() ?? null, + }) + + return ( + +
+
+ + + + +
+
+
+ ) +} diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 46bd907167a4..cbf47dc84984 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,4 +1,4 @@ -import type { Project, UserMessage } from "@opencode-ai/sdk/v2" +import type { Project, UserMessage, VcsFileDiff } from "@opencode-ai/sdk/v2" import { useDialog } from "@opencode-ai/ui/context/dialog" import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query" import { @@ -11,6 +11,7 @@ import { createMemo, createEffect, createComputed, + createSignal, on, onMount, type ParentProps, @@ -35,7 +36,7 @@ import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-b import { Button } from "@opencode-ai/ui/button" import { showToast } from "@/utils/toast" import { base64Encode, checksum } from "@opencode-ai/core/util/encode" -import { Navigate, useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router" +import { useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router" import { NewSessionView, SessionHeader } from "@/components/session" import { ErrorPage } from "@/pages/error" import { CommentsProvider, useComments } from "@/context/comments" @@ -56,7 +57,6 @@ import { useSync } from "@/context/sync" import { useTabs } from "@/context/tabs" import { TerminalProvider, useTerminal } from "@/context/terminal" import { PromptInput } from "@/components/prompt-input" -import { useSettingsCommand } from "@/components/settings-dialog" import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit" import { createPromptInputController, @@ -70,12 +70,18 @@ import { createTimelineModel } from "@/pages/session/timeline/model" import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab" import { useSessionLayout } from "@/pages/session/session-layout" import { syncSessionModel } from "@/pages/session/session-model-helpers" +import { + clampSessionPanelWidth, + SESSION_PANEL_WIDTH_MIN, + sessionPanelWidthMax, +} from "@/pages/session/session-panel-width" import { SessionSidePanel } from "@/pages/session/session-side-panel" import { sessionPanelLayout } from "@/pages/session/session-panel-layout" import { SessionReviewEmptyChangesV2 } from "@opencode-ai/session-ui/v2/session-review-empty-changes-v2" import { SessionReviewEmptyNoGitV2 } from "@opencode-ai/session-ui/v2/session-review-empty-no-git-v2" import { ReviewPanelV2 } from "@/pages/session/v2/review-panel-v2" import { createReviewPanelV2State } from "@/pages/session/v2/review-panel-v2-state" +import { reviewDiffDirectory, reviewDiffNeedsLoad, reviewRootDirectory } from "@/pages/session/v2/review-diff-kinds" import { TerminalPanel } from "@/pages/session/terminal-panel" import { TerminalPanelV2 } from "@/pages/session/terminal-panel-v2" import { useComposerCommands } from "@/pages/session/use-composer-commands" @@ -101,7 +107,6 @@ type VcsMode = "git" | "branch" const sessionViewState = () => ({ messageId: undefined as string | undefined, mobileTab: "session" as "session" | "changes", - changes: "git" as ChangeMode, }) function isCurrentSessionNotFoundError(error: unknown, sessionID: string | undefined) { @@ -153,7 +158,7 @@ export function TargetSessionRouteContent() { ) } -function SessionRouteErrorBoundary( +export function SessionRouteErrorBoundary( props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>, ) { const settings = useSettings() @@ -221,7 +226,6 @@ function SessionErrorFallback(props: { error: unknown; sessionID?: string; serve function ResolvedTargetSessionRoute() { const params = useParams<{ serverKey: string; id: string }>() - const settings = useSettings() const tabs = useTabs() const sync = useServerSync() const serverKey = createMemo(() => requireServerKey(params.serverKey)) @@ -248,16 +252,11 @@ function ResolvedTargetSessionRoute() { the terminal. Same-workspace tab switches keep it open because warm targets resolve synchronously from the sync cache. */} - } - > - - - - - - + + + + + ) @@ -354,6 +353,8 @@ export default function Page() { const location = useLocation() const navigate = useNavigate() const { params, sessionKey, workspaceKey, tabs, view } = useSessionLayout() + const reviewMode = () => view().review.mode() ?? "git" + const reviewFile = () => view().review.file() const sessionOwnership = createSessionOwnership(sessionKey) const newSessionDesign = createMemo(() => settings.general.newLayoutDesigns()) @@ -447,9 +448,39 @@ export default function Page() { newSessionDesign() ? desktopV2ReviewOpen() || desktopTerminalOpen() : desktopReviewOpen(), ) const desktopSidePanelOpen = createMemo(() => desktopSessionResizeOpen() || desktopFileTreeOpen()) + let panelRow: HTMLDivElement | undefined + const [panelRowWidth, setPanelRowWidth] = createSignal() + createResizeObserver( + () => panelRow, + ({ width }) => setPanelRowWidth(width), + ) + const splitReview = createMemo( + () => (newSessionDesign() ? desktopV2ReviewOpen() : desktopReviewOpen()) && layout.review.diffStyle() === "split", + ) + // The observer reports the content-box width, which already excludes the row + // padding; only the flex gap between the panels remains to subtract. + const sessionPanelAvailable = createMemo(() => { + const width = panelRowWidth() + if (width === undefined) return undefined + return width - (settings.general.newLayoutDesigns() ? 8 : 0) + }) + const sessionPanelMax = createMemo(() => { + const available = sessionPanelAvailable() + if (available === undefined) return 1000 + return sessionPanelWidthMax({ available, split: splitReview() }) + }) + // Clamp at render time so window or sidebar resizes squeeze the chat panel + // instead of the review pane, without overwriting the persisted width. + const sessionPanelResizedWidth = createMemo(() => + clampSessionPanelWidth({ + width: layout.session.width(), + available: sessionPanelAvailable(), + split: splitReview(), + }), + ) const sessionPanelWidth = createMemo(() => { if (!desktopSidePanelOpen()) return "100%" - if (desktopSessionResizeOpen()) return `${layout.session.width()}px` + if (desktopSessionResizeOpen()) return `${sessionPanelResizedWidth()}px` return `calc(100% - ${layout.fileTree.width()}px)` }) const centered = createMemo(() => isDesktop() && !desktopReviewOpen()) @@ -613,7 +644,8 @@ export default function Page() { : store.mobileTab === "changes", ) const vcsMode = createMemo(() => { - if (store.changes === "git" || store.changes === "branch") return store.changes + const mode = reviewMode() + if (mode === "git" || mode === "branch") return mode }) const vcsKey = createMemo( () => @@ -640,17 +672,63 @@ export default function Page() { }) const refreshVcs = debounce(() => void queryClient.invalidateQueries({ queryKey: vcsKey() }), 100) const reviewDiffs = () => { - if (store.changes === "git" || store.changes === "branch") + if (reviewMode() === "git" || reviewMode() === "branch") // avoids suspense return vcsQuery.isFetched ? (vcsQuery.data ?? []) : [] return turnDiffs() } + const activeReviewFile = () => { + const diffs = reviewDiffs() + const selected = reviewFile() + if (selected && diffs.some((diff) => diff.file === selected)) return selected + return diffs[0]?.file + } const reviewCount = () => reviewDiffs().length const hasReview = () => reviewCount() > 0 const reviewReady = () => { - if (store.changes === "git" || store.changes === "branch") return !vcsQuery.isPending + if (reviewMode() === "git" || reviewMode() === "branch") return !vcsQuery.isPending return true } + const loadReviewDiff = async (file: string, version?: number): Promise => { + const mode = vcsMode() + if (!mode) return + const root = reviewRootDirectory(sync().project?.worktree ?? sdk().directory) + const directory = reviewDiffDirectory(root, file) + const source = reviewDiffs().find((diff) => diff.file === file) + const valid = (diff: VcsFileDiff | undefined) => { + if (!diff || !source) return + if (diff.additions !== source.additions || diff.deletions !== source.deletions) return + if (reviewDiffNeedsLoad(diff)) return + return diff + } + const request = (scope: string, context?: number) => + queryClient + .fetchQuery({ + queryKey: [serverSDK().scope, ...vcsKey(), mode, "directory", scope, context, version] as const, + staleTime: Number.POSITIVE_INFINITY, + retry: 2, + queryFn: () => + sdk() + .client.vcs.diff({ mode, directory: scope, context }) + .then((result) => result.data ?? []), + }) + .then((diffs) => diffs.find((diff) => diff.file === file)) + + if (directory !== root) { + try { + const scoped = valid(await request(directory)) + if (scoped) return scoped + } catch (error) { + console.debug("[session-review] failed to load scoped vcs diff", { mode, file, directory, error }) + } + } + try { + const bounded = valid(await request(root, 3)) + if (bounded) return bounded + } catch (error) { + console.debug("[session-review] failed to load bounded vcs diff", { mode, file, root, error }) + } + } const newSessionWorktree = createMemo(() => { if (store.newSessionWorktree === "create") return "create" @@ -968,12 +1046,15 @@ export default function Page() { } createEffect(() => { + if (!layout.ready()) return + if (sync().status !== "complete") return if (!sync().project) return const list = changesOptions() - if (list.includes(store.changes)) return + const mode = reviewMode() + if (list.includes(mode)) return const next = list[0] if (!next) return - setStore("changes", next) + view().review.setMode(next) }) createEffect( @@ -993,7 +1074,6 @@ export default function Page() { const [tree, setTree] = createStore({ reviewScroll: undefined as HTMLDivElement | undefined, pendingDiff: undefined as string | undefined, - activeDiff: undefined as string | undefined, }) createEffect( @@ -1003,7 +1083,6 @@ export default function Page() { setTree({ reviewScroll: undefined, pendingDiff: undefined, - activeDiff: undefined, }) }, { defer: true }, @@ -1021,12 +1100,12 @@ export default function Page() { } useComposerCommands() - useSettingsCommand() useSessionCommands({ navigateMessageByOffset, setActiveMessage, focusInput, review: reviewTab, + fileBrowser: () => newSessionDesign() && isDesktop() && !!params.id, }) const openReviewFile = createOpenReviewFile({ @@ -1051,9 +1130,9 @@ export default function Page() { return ( setFirstId(event.currentTarget.value)} required> + + + {(model) => ( + + )} + + + + + + + ) +} + +function differentModelId(models: ModelCatalogEntry[], firstId: string, preferredId: string | undefined) { + if (preferredId && preferredId !== firstId && models.some((model) => model.id === preferredId)) return preferredId + return models.find((model) => model.id !== firstId)?.id ?? "" +} diff --git a/packages/stats/app/src/routes/compare/[firstLab]/[firstModel]/[secondLab]/[secondModel].tsx b/packages/stats/app/src/routes/compare/[firstLab]/[firstModel]/[secondLab]/[secondModel].tsx new file mode 100644 index 000000000000..88df35bf36d2 --- /dev/null +++ b/packages/stats/app/src/routes/compare/[firstLab]/[firstModel]/[secondLab]/[secondModel].tsx @@ -0,0 +1,611 @@ +import "../../../../index.css" +import { Link, Meta, Title } from "@solidjs/meta" +import { getStatsModelComparisonData, type StatsModelComparisonEntry } from "@opencode-ai/stats-core/domain/home" +import { runtime } from "@opencode-ai/stats-core/runtime" +import { createAsync, query, useParams } from "@solidjs/router" +import { createMemo, createSignal, For, onMount, Show } from "solid-js" +import { getRequestEvent } from "solid-js/web" +import { + ComparisonCardsSection, + modelRefFromCatalog, + uniqueComparisonPairs, + type ComparisonModelRef, + type ComparisonPair, +} from "../../../../compare-cards" +import { ComparisonSelector } from "../../../../compare-selector" +import { + catalogSlug, + findModelCatalogEntry, + formatCatalogLabName, + getModelCatalog, + type ModelCatalog, + type ModelCatalogEntry, +} from "../../../../model-catalog" +import { + applyThemePreference, + Footer, + getGitHubStars, + Header, + isThemePreference, + themeStorageKey, + type HeaderLink, + type ThemePreference, +} from "../../../../stats-shell" + +const compareFallbackUrl = "https://stats.opencode.ai" +const compareHeaderLinks: readonly HeaderLink[] = [ + { href: "#overview", label: "Overview" }, + { href: "#comparison", label: "Comparison" }, + { href: "#compare-tool", label: "Compare" }, + { href: "#model-comparison", label: "Related" }, +] +const compareFooterLinks: readonly HeaderLink[] = [ + { href: import.meta.env.BASE_URL, label: "Data Home" }, + { href: `${import.meta.env.BASE_URL}compare`, label: "Model Compare" }, + { href: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" }, + { href: `${import.meta.env.BASE_URL}#token-cost`, label: "Token Cost" }, +] + +type ComparisonModel = { + name: string + lab: string + labName: string + slug: string + catalog: ModelCatalogEntry | null + stats: StatsModelComparisonEntry | null +} +type ComparisonDirection = "higher" | "lower" +type ComparisonCell = { value: string; detail?: string; score?: number } +type ComparisonRow = { + label: string + description: string + direction: ComparisonDirection + cells: [ComparisonCell, ComparisonCell] +} + +const getComparisonData = query( + async (firstLab: string, firstModel: string, secondLab: string, secondModel: string) => { + "use server" + return runtime.runPromise(getStatsModelComparisonData(firstLab, firstModel, secondLab, secondModel)) + }, + "getStatsModelComparisonData", +) + +export default function ModelComparePair() { + const event = getRequestEvent() + event?.response.headers.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=86400") + const params = useParams() + const firstLabParam = createMemo(() => params.firstLab ?? "") + const firstModelParam = createMemo(() => params.firstModel ?? "") + const secondLabParam = createMemo(() => params.secondLab ?? "") + const secondModelParam = createMemo(() => params.secondModel ?? "") + const catalog = createAsync(() => getModelCatalog()) + const firstCatalog = createMemo(() => resolvedCatalogEntry(catalog(), firstLabParam(), firstModelParam())) + const secondCatalog = createMemo(() => resolvedCatalogEntry(catalog(), secondLabParam(), secondModelParam())) + const stats = createAsync(() => { + if (catalog() === undefined || firstCatalog() === undefined || secondCatalog() === undefined) + return Promise.resolve(undefined) + return getComparisonData( + firstCatalog()?.lab ?? firstLabParam(), + firstCatalog()?.slug ?? firstModelParam(), + secondCatalog()?.lab ?? secondLabParam(), + secondCatalog()?.slug ?? secondModelParam(), + ) + }) + const githubStars = createAsync(() => getGitHubStars()) + const [themePreference, setThemePreference] = createSignal("system") + const models = createMemo( + () => + [ + buildComparisonModel(firstLabParam(), firstModelParam(), firstCatalog() ?? null, stats()?.models[0] ?? null), + buildComparisonModel(secondLabParam(), secondModelParam(), secondCatalog() ?? null, stats()?.models[1] ?? null), + ] as const, + ) + const title = createMemo(() => `${models()[0].name} vs ${models()[1].name} - Model Comparison`) + const description = createMemo( + () => + `Compare ${models()[0].name} and ${models()[1].name} by usage, rank, context window, output limit, cache ratio, and cost across OpenCode data.`, + ) + const canonicalPath = createMemo( + () => + `${import.meta.env.BASE_URL}compare/${catalogSlug(models()[0].lab)}/${catalogSlug(models()[0].slug)}/${catalogSlug( + models()[1].lab, + )}/${catalogSlug(models()[1].slug)}`, + ) + const canonicalUrl = createMemo(() => + new URL( + canonicalPath(), + event?.request.url ?? (typeof window === "undefined" ? compareFallbackUrl : window.location.href), + ).toString(), + ) + const rows = createMemo(() => buildComparisonRows(models()[0], models()[1])) + const relatedPairs = createMemo(() => buildRelatedPairs(catalog(), models()[0], models()[1])) + const selectorModels = createMemo(() => + uniqueCatalogModels([ + comparisonCatalogEntry(models()[0]), + comparisonCatalogEntry(models()[1]), + ...(catalog()?.models ?? []), + ]), + ) + const structuredData = createMemo(() => + JSON.stringify({ + "@context": "https://schema.org", + "@type": "WebPage", + name: title(), + description: description(), + url: canonicalUrl(), + about: models().map((model) => ({ + "@type": "SoftwareApplication", + name: model.name, + applicationCategory: "AI model", + provider: model.labName, + })), + }), + ) + const updateThemePreference = (preference: ThemePreference) => { + applyThemePreference(preference) + setThemePreference(preference) + if (typeof window === "undefined") return + window.localStorage.setItem(themeStorageKey, preference) + } + + onMount(() => { + if (typeof window === "undefined") return + const preference = window.localStorage.getItem(themeStorageKey) + const nextPreference = isThemePreference(preference) ? preference : "system" + applyThemePreference(nextPreference) + setThemePreference(nextPreference) + }) + + return ( +
+ {title()} + + + + + + + + + + + +
+
+
+ +
+

+ Comparison Table. Compare usage, cost, limits, and features. +

+ + Loading comparison +

Loading stats for both models.

+
+ } + > + + + +
+

+ Compare Another Pair. Choose two models to compare. +

+ 1} + fallback={ +
+ No models found +

The model list could not be loaded.

+
+ } + > + +
+
+ +
+
+ +
+ ) +} + +function ComparisonHero(props: { models: readonly [ComparisonModel, ComparisonModel] }) { + return ( +
+ + Compare + +
+

+ {props.models[0].name} vs {props.models[1].name} +

+

Compare usage, cost, limits, and features for these two models.

+
+
+ ) +} + +function ComparisonTable(props: { models: readonly [ComparisonModel, ComparisonModel]; rows: ComparisonRow[] }) { + return ( +
+ + + + + + {(model) => } + + + + + {(row) => { + const best = () => bestCellIndex(row) + return ( + + + + {(cell, index) => ( + + )} + + + ) + }} + + +
+ {props.models[0].name} compared with {props.models[1].name} +
Metric{model.name}
+ {row.label} + {row.description} + + {cell.value} + {(detail) => {detail()}} +
+
+ ) +} + +function resolvedCatalogEntry(catalog: ModelCatalog | undefined, lab: string, model: string) { + if (!catalog) return undefined + return findModelCatalogEntry(catalog, model, lab) ?? null +} + +function buildComparisonModel( + labParam: string, + modelParam: string, + catalog: ModelCatalogEntry | null, + stats: StatsModelComparisonEntry | null, +): ComparisonModel { + return { + name: catalog?.name ?? stats?.model ?? formatParamName(modelParam), + lab: catalog?.lab ?? stats?.provider ?? catalogSlug(labParam), + labName: formatCatalogLabName(catalog?.lab ?? stats?.provider ?? labParam), + slug: catalog?.slug ?? stats?.slug ?? catalogSlug(modelParam), + catalog, + stats, + } +} + +function comparisonCatalogEntry(model: ComparisonModel): ModelCatalogEntry { + if (model.catalog) return model.catalog + return { + id: `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`, + lab: catalogSlug(model.lab), + slug: catalogSlug(model.slug), + name: model.name, + modalities: { input: [], output: [] }, + openWeights: false, + reasoning: false, + toolCall: false, + attachment: false, + temperature: false, + weights: [], + benchmarks: [], + } +} + +function uniqueCatalogModels(models: ModelCatalogEntry[]) { + return Object.values( + models.reduce>((result, model) => { + result[model.id] = result[model.id] ?? model + return result + }, {}), + ) +} + +function buildComparisonRows(first: ComparisonModel, second: ComparisonModel): ComparisonRow[] { + return [ + comparisonRow( + "Recent Rank", + "Lower is better.", + { + value: first.stats?.rank == null ? "No usage" : `#${first.stats.rank}`, + score: first.stats?.rank ?? undefined, + }, + { + value: second.stats?.rank == null ? "No usage" : `#${second.stats.rank}`, + score: second.stats?.rank ?? undefined, + }, + "lower", + ), + comparisonRow( + "Token Share", + "Share of recent OpenCode usage.", + { value: first.stats ? formatPercent(first.stats.tokenShare) : "No usage", score: first.stats?.tokenShare }, + { value: second.stats ? formatPercent(second.stats.tokenShare) : "No usage", score: second.stats?.tokenShare }, + "higher", + ), + comparisonRow( + "Tokens", + "Recent token volume.", + { value: first.stats ? formatTokens(first.stats.totals.tokens) : "No usage", score: first.stats?.totals.tokens }, + { + value: second.stats ? formatTokens(second.stats.totals.tokens) : "No usage", + score: second.stats?.totals.tokens, + }, + "higher", + ), + comparisonRow( + "Sessions", + "Recent session count.", + { + value: first.stats ? formatInteger(first.stats.totals.sessions) : "No usage", + score: first.stats?.totals.sessions, + }, + { + value: second.stats ? formatInteger(second.stats.totals.sessions) : "No usage", + score: second.stats?.totals.sessions, + }, + "higher", + ), + comparisonRow( + "Cost / 1M Tokens", + "Lower is better.", + { + value: first.stats ? formatMoney(first.stats.totals.costPerMillion) : "No usage", + score: positiveScore(first.stats?.totals.costPerMillion), + }, + { + value: second.stats ? formatMoney(second.stats.totals.costPerMillion) : "No usage", + score: positiveScore(second.stats?.totals.costPerMillion), + }, + "lower", + ), + comparisonRow( + "Cost / Session", + "Lower is better.", + { + value: first.stats ? formatSessionCost(first.stats.totals.costPerSession) : "No usage", + score: positiveScore(first.stats?.totals.costPerSession), + }, + { + value: second.stats ? formatSessionCost(second.stats.totals.costPerSession) : "No usage", + score: positiveScore(second.stats?.totals.costPerSession), + }, + "lower", + ), + comparisonRow( + "Cache Ratio", + "Higher is better.", + { + value: first.stats ? formatPercent(first.stats.totals.cacheRatio) : "No usage", + score: first.stats?.totals.cacheRatio, + }, + { + value: second.stats ? formatPercent(second.stats.totals.cacheRatio) : "No usage", + score: second.stats?.totals.cacheRatio, + }, + "higher", + ), + comparisonRow( + "Context Window", + "Higher limit is better.", + { + value: formatCatalogLimit(first.catalog?.limit?.context), + score: first.catalog?.limit?.context, + }, + { + value: formatCatalogLimit(second.catalog?.limit?.context), + score: second.catalog?.limit?.context, + }, + "higher", + ), + comparisonRow( + "Output Limit", + "Higher limit is better.", + { + value: formatCatalogLimit(first.catalog?.limit?.output), + score: first.catalog?.limit?.output, + }, + { + value: formatCatalogLimit(second.catalog?.limit?.output), + score: second.catalog?.limit?.output, + }, + "higher", + ), + comparisonRow( + "Release Date", + "Newer release is highlighted.", + { + value: formatCatalogDate(first.catalog?.releaseDate), + score: catalogDateScore(first.catalog?.releaseDate), + }, + { + value: formatCatalogDate(second.catalog?.releaseDate), + score: catalogDateScore(second.catalog?.releaseDate), + }, + "higher", + ), + comparisonRow( + "Reasoning", + "Supports reasoning.", + booleanCell(first.catalog?.reasoning), + booleanCell(second.catalog?.reasoning), + "higher", + ), + comparisonRow( + "Tool Calling", + "Supports tool calls.", + booleanCell(first.catalog?.toolCall), + booleanCell(second.catalog?.toolCall), + "higher", + ), + comparisonRow( + "Attachments", + "Supports attachments.", + booleanCell(first.catalog?.attachment), + booleanCell(second.catalog?.attachment), + "higher", + ), + comparisonRow( + "Open Weights", + "Open weights available.", + booleanCell(first.catalog?.openWeights), + booleanCell(second.catalog?.openWeights), + "higher", + ), + ] +} + +function comparisonRow( + label: string, + description: string, + first: ComparisonCell, + second: ComparisonCell, + direction: ComparisonDirection, +): ComparisonRow { + return { label, description, direction, cells: [first, second] } +} + +function bestCellIndex(row: ComparisonRow) { + const [first, second] = row.cells.map((cell) => cell.score) + if (first === undefined || second === undefined || first === second) return undefined + if (row.direction === "higher") return first > second ? 0 : 1 + return first < second ? 0 : 1 +} + +function buildRelatedPairs( + catalog: ModelCatalog | undefined, + first: ComparisonModel, + second: ComparisonModel, +): ComparisonPair[] { + const current = [comparisonRef(first), comparisonRef(second)] as const + const alternatives = (catalog?.models ?? []) + .filter((model) => model.id !== first.catalog?.id && model.id !== second.catalog?.id) + .slice(0, 4) + .map(modelRefFromCatalog) + + return uniqueComparisonPairs([ + ...alternatives.slice(0, 3).flatMap((model, index) => [ + { first: current[0], second: model, detail: index === 0 ? "Nearby alternative" : "Related comparison" }, + { first: current[1], second: model, detail: index === 0 ? "Nearby alternative" : "Related comparison" }, + ]), + ]).slice(0, 6) +} + +function comparisonRef(model: ComparisonModel): ComparisonModelRef { + return { + name: model.name, + lab: model.lab, + slug: model.slug, + labName: model.labName, + metric: model.stats ? `#${model.stats.rank}` : "Catalog", + } +} + +function positiveScore(value: number | undefined) { + return value && value > 0 ? value : undefined +} + +function booleanCell(value: boolean | undefined): ComparisonCell { + if (value === undefined) return { value: "Unknown" } + return { value: value ? "Yes" : "No", score: value ? 1 : 0 } +} + +function catalogDateScore(value: string | undefined) { + if (!value) return undefined + const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value) + if (!match) return undefined + return Date.UTC(Number(match[1]), match[2] ? Number(match[2]) - 1 : 0, match[3] ? Number(match[3]) : 1) +} + +function formatParamName(value: string) { + return value + .replace(/[-_]/g, " ") + .replace(/\b\w/g, (letter) => letter.toUpperCase()) + .trim() +} + +function formatCatalogLimit(value: number | undefined) { + return value === undefined ? "Unknown" : formatTokens(value) +} + +function formatCatalogDate(value: string | undefined) { + if (!value) return "Unknown" + const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value) + if (!match) return value + const year = Number(match[1]) + const month = match[2] ? Number(match[2]) - 1 : 0 + const day = match[3] ? Number(match[3]) : 1 + return new Intl.DateTimeFormat("en", { + month: match[2] ? "short" : undefined, + day: match[3] ? "numeric" : undefined, + year: "numeric", + timeZone: "UTC", + }).format(new Date(Date.UTC(year, month, day))) +} + +function formatTokens(value: number) { + if (value >= 1_000_000_000_000) + return `${trimNumber(value / 1_000_000_000_000, value >= 10_000_000_000_000 ? 0 : 1)}T` + if (value >= 1_000_000_000) return `${trimNumber(value / 1_000_000_000, value >= 10_000_000_000 ? 0 : 1)}B` + if (value >= 1_000_000) return `${trimNumber(value / 1_000_000, value >= 10_000_000 ? 0 : 1)}M` + if (value >= 1_000) return `${trimNumber(value / 1_000, value >= 10_000 ? 0 : 1)}K` + return String(Math.round(value)) +} + +function formatInteger(value: number) { + return new Intl.NumberFormat("en").format(value) +} + +function formatPercent(value: number) { + return `${trimNumber(value, value >= 10 ? 1 : 2)}%` +} + +function formatMoney(value: number) { + if (value >= 1) return `$${trimNumber(value, 2)}` + if (value > 0) return `$${value.toFixed(4)}` + return "$0" +} + +function formatSessionCost(value: number) { + if (value >= 1) return `$${trimNumber(value, 2)}` + if (value >= 0.01) return `$${value.toFixed(2)}` + if (value > 0) return `$${value.toFixed(4)}` + return "$0" +} + +function trimNumber(value: number, digits: number) { + return Number(value.toFixed(digits)).toLocaleString("en") +} diff --git a/packages/stats/app/src/routes/compare/index.tsx b/packages/stats/app/src/routes/compare/index.tsx new file mode 100644 index 000000000000..748a0b08b2d2 --- /dev/null +++ b/packages/stats/app/src/routes/compare/index.tsx @@ -0,0 +1,642 @@ +import "../index.css" +import { Meta, Title } from "@solidjs/meta" +import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { createAsync } from "@solidjs/router" +import { createEffect, createMemo, createSignal, For, onMount, Show } from "solid-js" +import { getRequestEvent } from "solid-js/web" +import { LocaleLinks } from "../../component/locale-links" +import { useI18n } from "../../context/i18n" +import { useLanguage } from "../../context/language" +import { localizedUrl } from "../../lib/language" +import { comparisonHref, modelRefFromCatalog, type ComparisonModelRef } from "../compare-cards" +import { formatCatalogLabName, getModelCatalog, type ModelCatalogEntry } from "../model-catalog" +import { setStatsPageCacheHeaders } from "../stats-cache" +import { + applyThemePreference, + Footer, + getGitHubStars, + Header, + isThemePreference, + themeStorageKey, + type HeaderLink, + type ThemePreference, +} from "../stats-shell" + +const comparePath = "/data/compare" +const compareTitle = "AI Model Comparison" +const compareDescription = + "Compare AI models on key metrics including benchmarks, price, context length, and other model features." +const statsUnfurlPath = "banner.png" +const uptimeBars = Array.from({ length: 10 }, (_, index) => index) +const heroLabs = [ + { lab: "deepseek", label: "DeepSeek" }, + { lab: "openai", label: "OpenAI" }, + { lab: "anthropic", label: "Anthropic" }, +] as const +const categoryTemplates = [ + { + title: "Flagship models", + description: "The latest top-tier model from major labs.", + kind: "flagship", + }, + { + title: "Most affordable", + description: "Compact, lower-cost picks for high-throughput use.", + kind: "affordable", + }, + { + title: "Best for code", + description: "Frequently chosen for programming tasks.", + kind: "code", + }, + { + title: "Image generation", + description: "Compare top image models on cost and quality.", + kind: "image", + }, +] as const + +type CompareCategory = { + title: string + description: string + first: ComparisonModelRef + second: ComparisonModelRef + avatars: ComparisonModelRef[] +} +type CompareSlot = "first" | "second" + +export default function ModelCompareIndex() { + const i18n = useI18n() + const language = useLanguage() + const event = getRequestEvent() + setStatsPageCacheHeaders(event?.response.headers) + const catalog = createAsync(() => getModelCatalog()) + const githubStars = createAsync(() => getGitHubStars()) + const [themePreference, setThemePreference] = createSignal("system") + const compareUrl = createMemo(() => localizedUrl(language.locale(), comparePath)) + const statsUnfurlUrl = new URL(statsUnfurlPath, localizedUrl("en", "/data/")).toString() + const featuredModels = createMemo(() => (catalog()?.models ?? []).slice(0, 120)) + const categories = createMemo(() => buildComparisonCategories(featuredModels())) + const compareHeaderLinks = createMemo(() => [ + { href: `${import.meta.env.BASE_URL}#top-models`, label: i18n.t("nav.topModels") }, + { href: `${import.meta.env.BASE_URL}#leaderboard`, label: i18n.t("nav.leaderboard") }, + { href: `${import.meta.env.BASE_URL}#market-share`, label: i18n.t("nav.marketShare") }, + { href: `${import.meta.env.BASE_URL}#token-cost`, label: i18n.t("nav.tokenCost") }, + { href: `${import.meta.env.BASE_URL}#session-cost`, label: i18n.t("nav.sessionCost") }, + ]) + const compareFooterLinks = createMemo(() => [ + { href: `${import.meta.env.BASE_URL}#top-models`, label: i18n.t("nav.topModels") }, + { href: `${import.meta.env.BASE_URL}#leaderboard`, label: i18n.t("nav.leaderboard") }, + { href: `${import.meta.env.BASE_URL}#market-share`, label: i18n.t("nav.marketShare") }, + { href: `${import.meta.env.BASE_URL}#token-cost`, label: i18n.t("nav.tokenCost") }, + { href: `${import.meta.env.BASE_URL}#session-cost`, label: i18n.t("nav.sessionCost") }, + ]) + const updateThemePreference = (preference: ThemePreference) => { + applyThemePreference(preference) + setThemePreference(preference) + if (typeof window === "undefined") return + window.localStorage.setItem(themeStorageKey, preference) + } + + onMount(() => { + if (typeof window === "undefined") return + const preference = window.localStorage.getItem(themeStorageKey) + const nextPreference = isThemePreference(preference) ? preference : "system" + applyThemePreference(nextPreference) + setThemePreference(nextPreference) + }) + + return ( +
+ {compareTitle} + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

+ Compare + + AI models +

+

{compareDescription}

+
+
+
+ 1} + fallback={ +
+ No models found +

The model list could not be loaded.

+
+ } + > + +
+
+ 0}> +
+

+ Related comparisons. Other model pairs to check. +

+
+ {(category) => } +
+
+
+
+
+
+
+ ) +} + +function CompareHomeSelector(props: { models: ModelCatalogEntry[] }) { + const [firstId, setFirstId] = createSignal("") + const [secondId, setSecondId] = createSignal("") + const [activeSlot, setActiveSlot] = createSignal() + const modelById = createMemo(() => new Map(props.models.map((model) => [model.id, model]))) + const first = createMemo(() => modelById().get(firstId())) + const second = createMemo(() => modelById().get(secondId())) + const activeSelected = createMemo(() => (activeSlot() === "first" ? first() : second())) + const activeBlockedId = createMemo(() => (activeSlot() === "first" ? secondId() : firstId())) + const href = createMemo(() => { + const firstModel = first() + const secondModel = second() + if (!firstModel || !secondModel || firstModel.id === secondModel.id) return undefined + return comparisonHref(modelRefFromCatalog(firstModel), modelRefFromCatalog(secondModel)) + }) + + createEffect(() => { + if (!secondId() || secondId() !== firstId()) return + setSecondId("") + }) + + createEffect(() => { + const url = href() + if (!url || typeof window === "undefined") return + window.location.href = url + }) + + return ( +
event.preventDefault()} + > + setActiveSlot("first")} + /> + setActiveSlot("second")} + /> + + {(slot) => ( + setActiveSlot(undefined)} + onSelect={(model) => { + if (slot() === "first") setFirstId(model.id) + if (slot() === "second") setSecondId(model.id) + setActiveSlot(undefined) + }} + /> + )} + + + ) +} + +function CompareHomeSelect(props: { + selected: ModelCatalogEntry | undefined + label: string + expanded: boolean + onOpen: () => void +}) { + return ( + + ) +} + +function CompareModelSelectModal(props: { + models: ModelCatalogEntry[] + selected: ModelCatalogEntry | undefined + blockedId: string + label: string + onClose: () => void + onSelect: (model: ModelCatalogEntry) => void +}) { + let searchInput: HTMLInputElement | undefined + const [search, setSearch] = createSignal("") + const [previewId, setPreviewId] = createSignal(props.selected?.id ?? "") + const availableModels = createMemo(() => props.models.filter((model) => model.id !== props.blockedId)) + const filteredModels = createMemo(() => { + const terms = search().trim().toLowerCase().split(/\s+/).filter(Boolean) + if (terms.length === 0) return availableModels() + return availableModels().filter((model) => terms.every((term) => modelSearchText(model).includes(term))) + }) + const preview = createMemo( + () => filteredModels().find((model) => model.id === previewId()) ?? filteredModels()[0] ?? availableModels()[0], + ) + + createEffect(() => { + const selected = props.selected + const fallback = selected && selected.id !== props.blockedId ? selected.id : filteredModels()[0]?.id + if (!fallback) return + if (availableModels().some((model) => model.id === previewId())) return + setPreviewId(fallback) + }) + + createEffect(() => { + const models = filteredModels() + if (models.some((model) => model.id === previewId())) return + setPreviewId(models[0]?.id ?? "") + }) + + createEffect(() => { + if (typeof window === "undefined") return + window.requestAnimationFrame(() => searchInput?.focus()) + }) + + return ( +
{ + if (event.key !== "Escape") return + props.onClose() + }} + > +
event.stopPropagation()} + > +
+ +
+ 0} + fallback={ +
+ No models found + Try another search. +
+ } + > + + {(model) => ( + + )} + +
+
+
+ +
+ ) +} + +function CompareModelDetail(props: { model: ModelCatalogEntry }) { + return ( + + ) +} + +function CompareModelFact(props: { label: string; value: string }) { + return ( +
+
{props.label}
+
{props.value}
+
+ ) +} + +function HeroModelStack() { + return ( + + ) +} + +function CompareHomeCard(props: { category: CompareCategory }) { + return ( + + + + {props.category.title} + {props.category.description} + +