Explorar o código

Merge branch 'dev' of https://github.com/anomalyco/opencode into dev

Frank hai 1 mes
pai
achega
3a95d56144
Modificáronse 100 ficheiros con 4275 adicións e 658 borrados
  1. 28 28
      bun.lock
  2. 1 0
      package.json
  3. 1 1
      packages/app/package.json
  4. 61 16
      packages/app/src/app.tsx
  5. 325 0
      packages/app/src/components/command-palette.ts
  6. 220 0
      packages/app/src/components/dialog-command-palette-v2.css
  7. 221 0
      packages/app/src/components/dialog-command-palette-v2.tsx
  8. 67 367
      packages/app/src/components/dialog-select-file.tsx
  9. 36 19
      packages/app/src/components/dialog-select-model.tsx
  10. 35 2
      packages/app/src/components/model-tooltip.tsx
  11. 0 10
      packages/app/src/components/settings-general.tsx
  12. 0 10
      packages/app/src/components/settings-v2/general.tsx
  13. 11 6
      packages/app/src/components/titlebar-tab-nav.tsx
  14. 6 3
      packages/app/src/components/titlebar-tab-strip.tsx
  15. 17 1
      packages/app/src/context/command.test.ts
  16. 16 5
      packages/app/src/context/command.tsx
  17. 35 2
      packages/app/src/context/tabs.tsx
  18. 148 2
      packages/app/src/i18n/ar.ts
  19. 152 3
      packages/app/src/i18n/br.ts
  20. 151 2
      packages/app/src/i18n/bs.ts
  21. 149 2
      packages/app/src/i18n/da.ts
  22. 152 2
      packages/app/src/i18n/de.ts
  23. 5 0
      packages/app/src/i18n/en.ts
  24. 152 2
      packages/app/src/i18n/es.ts
  25. 153 2
      packages/app/src/i18n/fr.ts
  26. 151 3
      packages/app/src/i18n/ja.ts
  27. 153 2
      packages/app/src/i18n/ko.ts
  28. 163 4
      packages/app/src/i18n/no.ts
  29. 111 26
      packages/app/src/i18n/parity.test.ts
  30. 157 6
      packages/app/src/i18n/pl.ts
  31. 151 2
      packages/app/src/i18n/ru.ts
  32. 154 7
      packages/app/src/i18n/th.ts
  33. 154 6
      packages/app/src/i18n/tr.ts
  34. 122 0
      packages/app/src/i18n/uk.ts
  35. 132 2
      packages/app/src/i18n/zh.ts
  36. 144 2
      packages/app/src/i18n/zht.ts
  37. 40 2
      packages/app/src/index.css
  38. 3 3
      packages/app/src/pages/home.tsx
  39. 2 0
      packages/app/src/pages/layout-new.tsx
  40. 11 6
      packages/app/src/pages/layout/project-avatar-state.ts
  41. 0 2
      packages/app/src/pages/new-session.tsx
  42. 7 15
      packages/app/src/pages/session.tsx
  43. 4 2
      packages/app/src/pages/session/timeline/message-timeline.tsx
  44. 19 6
      packages/app/src/pages/session/use-session-commands.tsx
  45. 1 1
      packages/cli/package.json
  46. 1 1
      packages/codemode/package.json
  47. 1 1
      packages/console/app/package.json
  48. 1 1
      packages/console/core/package.json
  49. 1 1
      packages/console/function/package.json
  50. 1 1
      packages/console/mail/package.json
  51. 1 1
      packages/console/support/package.json
  52. 1 1
      packages/core/package.json
  53. 41 4
      packages/core/src/config/plugin/agent.ts
  54. 4 3
      packages/core/src/fs-util.ts
  55. 11 4
      packages/core/src/session/compaction.ts
  56. 76 1
      packages/core/test/config/agent.test.ts
  57. 9 0
      packages/core/test/session-compaction.test.ts
  58. 1 1
      packages/desktop/package.json
  59. 12 2
      packages/desktop/src/main/windows.ts
  60. 1 0
      packages/desktop/src/preload/types.ts
  61. 1 1
      packages/effect-drizzle-sqlite/package.json
  62. 1 1
      packages/effect-sqlite-node/package.json
  63. 1 1
      packages/enterprise/package.json
  64. 1 1
      packages/function/package.json
  65. 1 1
      packages/http-recorder/package.json
  66. 1 1
      packages/llm/package.json
  67. 1 0
      packages/llm/src/provider-error.ts
  68. 8 0
      packages/llm/test/provider-error.test.ts
  69. 1 1
      packages/opencode/package.json
  70. 1 0
      packages/opencode/src/config/config.ts
  71. 25 0
      packages/opencode/test/config/config.test.ts
  72. 1 0
      packages/opencode/test/session/message-v2.test.ts
  73. 1 1
      packages/plugin/package.json
  74. 1 1
      packages/sdk/js/package.json
  75. 1 1
      packages/server/package.json
  76. 1 1
      packages/session-ui/package.json
  77. 14 0
      packages/session-ui/src/components/markdown.css
  78. 4 2
      packages/session-ui/src/components/session-turn.tsx
  79. 1 1
      packages/slack/package.json
  80. 1 1
      packages/stats/app/package.json
  81. 6 1
      packages/stats/app/src/i18n.ts
  82. 5 0
      packages/stats/app/src/i18n/ar.ts
  83. 5 0
      packages/stats/app/src/i18n/br.ts
  84. 5 0
      packages/stats/app/src/i18n/da.ts
  85. 5 0
      packages/stats/app/src/i18n/de.ts
  86. 5 0
      packages/stats/app/src/i18n/es.ts
  87. 5 0
      packages/stats/app/src/i18n/fr.ts
  88. 5 0
      packages/stats/app/src/i18n/it.ts
  89. 5 0
      packages/stats/app/src/i18n/ja.ts
  90. 5 0
      packages/stats/app/src/i18n/ko.ts
  91. 5 0
      packages/stats/app/src/i18n/no.ts
  92. 5 0
      packages/stats/app/src/i18n/pl.ts
  93. 5 0
      packages/stats/app/src/i18n/ru.ts
  94. 5 0
      packages/stats/app/src/i18n/th.ts
  95. 5 0
      packages/stats/app/src/i18n/tr.ts
  96. 5 0
      packages/stats/app/src/i18n/uk.ts
  97. 5 0
      packages/stats/app/src/i18n/zh.ts
  98. 5 0
      packages/stats/app/src/i18n/zht.ts
  99. 102 38
      packages/stats/app/src/routes/[lab]/[model].tsx
  100. 32 0
      packages/stats/app/src/routes/[lab]/index.tsx

+ 28 - 28
bun.lock

@@ -29,7 +29,7 @@
     },
     "packages/app": {
       "name": "@opencode-ai/app",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@dnd-kit/abstract": "0.5.0",
         "@dnd-kit/dom": "0.5.0",
@@ -93,7 +93,7 @@
     },
     "packages/cli": {
       "name": "@opencode-ai/cli",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "bin": {
         "lildax": "./bin/lildax.cjs",
       },
@@ -141,7 +141,7 @@
     },
     "packages/codemode": {
       "name": "@opencode-ai/codemode",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "acorn": "8.15.0",
         "effect": "catalog:",
@@ -155,7 +155,7 @@
     },
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
@@ -191,7 +191,7 @@
     },
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
@@ -218,7 +218,7 @@
     },
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@ai-sdk/anthropic": "3.0.82",
         "@ai-sdk/openai": "3.0.48",
@@ -240,7 +240,7 @@
     },
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
@@ -264,7 +264,7 @@
     },
     "packages/console/support": {
       "name": "@opencode-ai/console-support",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@opencode-ai/console-core": "workspace:*",
@@ -284,7 +284,7 @@
     },
     "packages/core": {
       "name": "@opencode-ai/core",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -378,7 +378,7 @@
     },
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@zip.js/zip.js": "2.7.62",
         "effect": "catalog:",
@@ -432,7 +432,7 @@
     },
     "packages/effect-drizzle-sqlite": {
       "name": "@opencode-ai/effect-drizzle-sqlite",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "drizzle-orm": "catalog:",
         "effect": "catalog:",
@@ -446,7 +446,7 @@
     },
     "packages/effect-sqlite-node": {
       "name": "@opencode-ai/effect-sqlite-node",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "effect": "catalog:",
       },
@@ -458,7 +458,7 @@
     },
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@hono/standard-validator": "catalog:",
         "@opencode-ai/core": "workspace:*",
@@ -490,7 +490,7 @@
     },
     "packages/function": {
       "name": "@opencode-ai/function",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "catalog:",
@@ -506,7 +506,7 @@
     },
     "packages/http-recorder": {
       "name": "@opencode-ai/http-recorder",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@effect/platform-node": "4.0.0-beta.83",
         "@effect/platform-node-shared": "4.0.0-beta.83",
@@ -537,7 +537,7 @@
     },
     "packages/llm": {
       "name": "@opencode-ai/llm",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@opencode-ai/schema": "workspace:*",
         "@smithy/eventstream-codec": "4.2.14",
@@ -556,7 +556,7 @@
     },
     "packages/opencode": {
       "name": "opencode",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -687,7 +687,7 @@
     },
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@ai-sdk/provider": "3.0.8",
         "@opencode-ai/sdk": "workspace:*",
@@ -763,7 +763,7 @@
     },
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "cross-spawn": "catalog:",
       },
@@ -778,7 +778,7 @@
     },
     "packages/server": {
       "name": "@opencode-ai/server",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@opencode-ai/core": "workspace:*",
         "@opencode-ai/protocol": "workspace:*",
@@ -793,7 +793,7 @@
     },
     "packages/session-ui": {
       "name": "@opencode-ai/session-ui",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/core": "workspace:*",
@@ -837,7 +837,7 @@
     },
     "packages/slack": {
       "name": "@opencode-ai/slack",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
@@ -850,7 +850,7 @@
     },
     "packages/stats/app": {
       "name": "@opencode-ai/stats-app",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@ibm/plex": "6.4.1",
         "@opencode-ai/stats-core": "workspace:*",
@@ -883,7 +883,7 @@
     },
     "packages/stats/core": {
       "name": "@opencode-ai/stats-core",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@aws-sdk/client-athena": "3.933.0",
         "@planetscale/database": "1.19.0",
@@ -902,7 +902,7 @@
     },
     "packages/stats/server": {
       "name": "@opencode-ai/stats-server",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@aws-sdk/client-firehose": "3.933.0",
         "@effect/platform-node": "catalog:",
@@ -943,7 +943,7 @@
     },
     "packages/tui": {
       "name": "@opencode-ai/tui",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@opencode-ai/core": "workspace:*",
         "@opencode-ai/plugin": "workspace:*",
@@ -970,7 +970,7 @@
     },
     "packages/ui": {
       "name": "@opencode-ai/ui",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@pierre/diffs": "catalog:",
@@ -1021,7 +1021,7 @@
     },
     "packages/web": {
       "name": "@opencode-ai/web",
-      "version": "1.17.14",
+      "version": "1.17.15",
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",

+ 1 - 0
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": {

+ 1 - 1
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/app",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "description": "",
   "type": "module",
   "exports": {

+ 61 - 16
packages/app/src/app.tsx

@@ -9,7 +9,7 @@ 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 {
@@ -32,7 +32,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 +52,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 +89,14 @@ const SessionRoute = () => {
     tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
   })
 
-  return <SessionPage />
+  return (
+    <SessionRouteErrorBoundary sessionID={params.id}>
+      <SessionPage />
+    </SessionRouteErrorBoundary>
+  )
 }
 
-const TargetSessionRoute = () => {
+function TargetServerRoute(props: ParentProps) {
   const params = useParams<{ serverKey: string; id: string }>()
   const global = useGlobal()
   const conn = createMemo(() => {
@@ -105,14 +110,47 @@ const TargetSessionRoute = () => {
     // re-resolves reactively instead); both rely on this key for server changes.
     <Show when={requireServerKey(params.serverKey)} keyed>
       <ServerSDKProvider server={conn}>
-        <ServerSyncProvider server={conn}>
-          <TargetSessionRouteContent />
-        </ServerSyncProvider>
+        <ServerSyncProvider server={conn}>{props.children}</ServerSyncProvider>
       </ServerSDKProvider>
     </Show>
   )
 }
 
+const TargetSessionRoute = () => (
+  <TargetServerRoute>
+    <TargetSessionRouteContent />
+  </TargetServerRoute>
+)
+
+function LegacyTargetSessionRoute() {
+  const params = useParams<{ serverKey: string; id: string }>()
+  return (
+    <TargetServerRoute>
+      <SessionRouteErrorBoundary sessionID={params.id} serverKey={requireServerKey(params.serverKey)}>
+        <LegacyTargetSessionRedirect />
+      </SessionRouteErrorBoundary>
+    </TargetServerRoute>
+  )
+}
+
+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.
@@ -186,7 +224,7 @@ declare global {
       deepLinks?: string[]
     }
     api?: {
-      setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void>
+      setTitlebar?: (theme: { mode: "light" | "dark"; scheme?: "system" | "light" | "dark" }) => Promise<void>
       exportDebugLogs?: () => Promise<string>
     }
   }
@@ -320,8 +358,8 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
     <MetaProvider>
       <Font />
       <ThemeProvider
-        onThemeApplied={(_, mode) => {
-          void window.api?.setTitlebar?.({ mode })
+        onThemeApplied={(_, mode, scheme) => {
+          void window.api?.setTitlebar?.({ mode, scheme })
         }}
       >
         <LanguageProvider locale={props.locale}>
@@ -542,7 +580,14 @@ function Routes(props: { serverScoped?: JSX.Element }) {
           <LegacyServerLayout serverScoped={props.serverScoped}>{routeProps.children}</LegacyServerLayout>
         )}
       >
-        <Show when={!settings.general.newLayoutDesigns()}>{<Route path="/" component={LegacyHome} />}</Show>
+        <Show when={!settings.general.newLayoutDesigns()}>
+          {
+            <>
+              <Route path="/" component={LegacyHome} />
+              <Route path="/server/:serverKey/session/:id" component={LegacyTargetSessionRoute} />
+            </>
+          }
+        </Show>
         <Route path="/:dir" component={DirectoryLayout}>
           <Route path="/" component={() => <Navigate href="session" />} />
           <Route path="/session/:id?" component={SessionRoute} />
@@ -550,15 +595,15 @@ function Routes(props: { serverScoped?: JSX.Element }) {
       </Route>
       <Show when={settings.general.newLayoutDesigns()}>
         <Route path="/" component={NewHome} />
-        <Route path="/:dir/session/:id" component={LegacyTargetSessionRoute} />
+        <Route path="/:dir/session/:id" component={NewLayoutLegacySessionRedirect} />
+        <Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
       </Show>
       <Route path="/new-session" component={DraftRoute} />
-      <Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
     </>
   )
 }
 
-function LegacyTargetSessionRoute() {
+function NewLayoutLegacySessionRedirect() {
   const server = useServer()
   const tabs = useTabs()
   const params = useParams<{ id: string }>()

+ 325 - 0
packages/app/src/components/command-palette.ts

@@ -0,0 +1,325 @@
+import { base64Encode } from "@opencode-ai/core/util/encode"
+import { getFilename } from "@opencode-ai/core/util/path"
+import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { useNavigate } from "@solidjs/router"
+import { createMemo, onCleanup } from "solid-js"
+import { useCommand, type CommandOption } from "@/context/command"
+import { useFile } from "@/context/file"
+import { useLanguage } from "@/context/language"
+import { useLayout } from "@/context/layout"
+import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
+import { useServerSync } from "@/context/server-sync"
+import { createSessionTabs } from "@/pages/session/helpers"
+import { useSessionLayout } from "@/pages/session/session-layout"
+import { decode64 } from "@/utils/base64"
+
+export type CommandPaletteEntry = {
+  id: string
+  type: "command" | "file" | "session"
+  title: string
+  description?: string
+  keybind?: string
+  category: string
+  option?: CommandOption
+  path?: string
+  directory?: string
+  sessionID?: string
+  archived?: number
+  updated?: number
+}
+
+const ENTRY_LIMIT = 5
+const COMMON_COMMAND_IDS = [
+  "session.new",
+  "workspace.new",
+  "session.previous",
+  "session.next",
+  "terminal.toggle",
+  "review.toggle",
+] as const
+
+export function uniqueCommandPaletteEntries(items: CommandPaletteEntry[]) {
+  const seen = new Set<string>()
+  return items.filter((item) => {
+    if (seen.has(item.id)) return false
+    seen.add(item.id)
+    return true
+  })
+}
+
+export function createCommandPaletteFileEntry(path: string, category: string): CommandPaletteEntry {
+  return {
+    id: "file:" + path,
+    type: "file",
+    title: path,
+    category,
+    path,
+  }
+}
+
+export function createCommandPaletteFileOpener(onOpenFile?: (path: string) => void) {
+  const file = useFile()
+  const layout = useLayout()
+  const { tabs, view } = useSessionLayout()
+
+  return (path: string) => {
+    const value = file.tab(path)
+    void tabs().open(value)
+    void file.load(path)
+    if (!view().reviewPanel.opened()) view().reviewPanel.open()
+    layout.fileTree.setTab("all")
+    onOpenFile?.(path)
+    tabs().setActive(value)
+  }
+}
+
+export function createCommandPaletteModel(props: { filesOnly?: () => boolean; onOpenFile?: (path: string) => void }) {
+  const command = useCommand()
+  const language = useLanguage()
+  const layout = useLayout()
+  const file = useFile()
+  const dialog = useDialog()
+  const navigate = useNavigate()
+  const serverSDK = useServerSDK()()
+  const serverSync = useServerSync()
+  const { params, tabs } = useSessionLayout()
+  const openFile = createCommandPaletteFileOpener(props.onOpenFile)
+  const state = { cleanup: undefined as (() => void) | void, committed: false }
+  const filesOnly = () => props.filesOnly?.() ?? false
+
+  const allowedCommands = createMemo(() => {
+    if (filesOnly()) return []
+    return command.options.filter(
+      (option) =>
+        !option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open",
+    )
+  })
+  const commandEntries = createMemo(() => {
+    const category = language.t("palette.group.commands")
+    return allowedCommands().map((option) => createCommandEntry(option, category))
+  })
+  const preferredCommandEntries = createMemo(() => {
+    const all = allowedCommands()
+    const order = new Map<string, number>(COMMON_COMMAND_IDS.map((id, index) => [id, index]))
+    const picked = all.filter((option) => order.has(option.id))
+    const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
+    const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
+    const category = language.t("palette.group.commands")
+    return sorted.map((option) => createCommandEntry(option, category))
+  })
+
+  const tabState = createSessionTabs({
+    tabs,
+    pathFromTab: file.pathFromTab,
+    normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
+  })
+  const recentFileEntries = createMemo(() => {
+    const all = tabState.openedTabs()
+    const active = tabState.activeFileTab()
+    const order = active ? [active, ...all.filter((item) => item !== active)] : all
+    const seen = new Set<string>()
+    const category = language.t("palette.group.files")
+    return order
+      .map((item) => file.pathFromTab(item))
+      .filter((path): path is string => {
+        if (!path || seen.has(path)) return false
+        seen.add(path)
+        return true
+      })
+      .slice(0, ENTRY_LIMIT)
+      .map((path) => createCommandPaletteFileEntry(path, category))
+  })
+  const rootFileEntries = createMemo(() => {
+    const category = language.t("palette.group.files")
+    return file.tree
+      .children("")
+      .filter((node) => node.type === "file")
+      .map((node) => node.path)
+      .sort((a, b) => a.localeCompare(b))
+      .slice(0, ENTRY_LIMIT)
+      .map((path) => createCommandPaletteFileEntry(path, category))
+  })
+
+  const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
+  const project = createMemo(() => {
+    const directory = projectDirectory()
+    if (!directory) return undefined
+    return layout.projects.list().find((item) => item.worktree === directory || item.sandboxes?.includes(directory))
+  })
+  const workspaces = createMemo(() => {
+    const directory = projectDirectory()
+    const current = project()
+    if (!current) return directory ? [directory] : []
+    const dirs = [current.worktree, ...(current.sandboxes ?? [])]
+    if (directory && !dirs.includes(directory)) return [...dirs, directory]
+    return dirs
+  })
+  const homedir = createMemo(() => serverSync().data.path.home)
+  const sessions = createSessionEntries({
+    workspaces,
+    label: (directory) => {
+      const current = project()
+      const kind =
+        current && directory === current.worktree
+          ? language.t("workspace.type.local")
+          : language.t("workspace.type.sandbox")
+      const [store] = serverSync().child(directory, { bootstrap: false })
+      const home = homedir()
+      const path = home ? directory.replace(home, "~") : directory
+      const name = store.vcs?.branch ?? getFilename(directory)
+      return `${kind} : ${name || path}`
+    },
+    load: (directory) => serverSDK.client.session.list({ directory, roots: true }),
+    untitled: () => language.t("command.session.new"),
+    category: () => language.t("command.category.session"),
+  })
+
+  const highlight = (item: CommandPaletteEntry | undefined) => {
+    state.cleanup?.()
+    state.cleanup = undefined
+    if (item?.type !== "command") return
+    state.cleanup = item.option?.onHighlight?.()
+  }
+
+  const select = (item: CommandPaletteEntry | undefined) => {
+    if (!item) return
+    state.committed = true
+    state.cleanup = undefined
+    dialog.close()
+    if (item.type === "command") {
+      item.option?.onSelect?.("palette")
+      return
+    }
+    if (item.type === "session") {
+      if (!item.directory || !item.sessionID) return
+      navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
+      return
+    }
+    if (!item.path) return
+    openFile(item.path)
+  }
+
+  onCleanup(() => {
+    if (state.committed) return
+    state.cleanup?.()
+  })
+
+  return {
+    language,
+    file,
+    commandEntries,
+    preferredCommandEntries,
+    recentFileEntries,
+    rootFileEntries,
+    sessions,
+    highlight,
+    select,
+    close: () => dialog.close(),
+  }
+}
+
+function createCommandEntry(option: CommandOption, category: string): CommandPaletteEntry {
+  return {
+    id: "command:" + option.id,
+    type: "command",
+    title: option.title,
+    description: option.description,
+    keybind: option.keybind,
+    category,
+    option,
+  }
+}
+
+function createSessionEntries(props: {
+  workspaces: () => string[]
+  label: (directory: string) => string
+  load: (directory: string) => ReturnType<ServerSDK["client"]["session"]["list"]>
+  untitled: () => string
+  category: () => string
+}) {
+  const state: {
+    token: number
+    inflight: Promise<CommandPaletteEntry[]> | undefined
+    cached: CommandPaletteEntry[] | undefined
+  } = { token: 0, inflight: undefined, cached: undefined }
+
+  return (text: string) => {
+    if (!text.trim()) {
+      state.token += 1
+      state.inflight = undefined
+      state.cached = undefined
+      return [] as CommandPaletteEntry[]
+    }
+    if (state.cached) return state.cached
+    if (state.inflight) return state.inflight
+
+    const current = state.token
+    const dirs = props.workspaces()
+    if (dirs.length === 0) return [] as CommandPaletteEntry[]
+
+    state.inflight = Promise.all(
+      dirs.map((directory) => {
+        const description = props.label(directory)
+        return props
+          .load(directory)
+          .then((result) =>
+            (result.data ?? [])
+              .filter((session) => !!session?.id)
+              .map((session) => ({
+                id: session.id,
+                title: session.title ?? props.untitled(),
+                description,
+                directory,
+                archived: session.time?.archived,
+                updated: session.time?.updated,
+              })),
+          )
+          .catch(() => [] as SessionEntryInput[])
+      }),
+    )
+      .then((results) => {
+        if (state.token !== current) return [] as CommandPaletteEntry[]
+        const seen = new Set<string>()
+        const next = results
+          .flat()
+          .filter((item) => {
+            const key = `${item.directory}:${item.id}`
+            if (seen.has(key)) return false
+            seen.add(key)
+            return true
+          })
+          .map((item) => createSessionEntry(item, props.category()))
+        state.cached = next
+        return next
+      })
+      .catch(() => [] as CommandPaletteEntry[])
+      .finally(() => {
+        state.inflight = undefined
+      })
+
+    return state.inflight
+  }
+}
+
+type SessionEntryInput = {
+  directory: string
+  id: string
+  title: string
+  description: string
+  archived?: number
+  updated?: number
+}
+
+function createSessionEntry(input: SessionEntryInput, category: string): CommandPaletteEntry {
+  return {
+    id: `session:${input.directory}:${input.id}`,
+    type: "session",
+    title: input.title,
+    description: input.description,
+    category,
+    directory: input.directory,
+    sessionID: input.id,
+    archived: input.archived,
+    updated: input.updated,
+  }
+}

+ 220 - 0
packages/app/src/components/dialog-command-palette-v2.css

@@ -0,0 +1,220 @@
+.command-palette-v2 {
+  overflow: hidden;
+}
+
+/* Anchor to the top edge of where a centered 480px-tall dialog would sit, so the
+   top stays put while the content-driven height grows and shrinks. */
+[data-component="dialog-v2"]:has(.command-palette-v2) {
+  align-items: flex-start;
+}
+
+[data-component="dialog-v2"]:has(.command-palette-v2) [data-slot="dialog-container"] {
+  width: min(calc(100vw - 24px), 640px);
+  height: auto;
+  min-height: 280px;
+  max-height: min(calc(100vh - 96px), 480px);
+  margin-top: max(48px, calc((100vh - 480px) / 2));
+  border-radius: 12px;
+  background: var(--v2-background-bg-base);
+  box-shadow: var(--v2-elevation-floating);
+}
+
+.command-palette-v2-body {
+  display: flex;
+  min-height: 0;
+  flex: 1;
+  flex-direction: column;
+  gap: 0;
+  padding: 0;
+}
+
+.command-palette-v2-search {
+  flex-shrink: 0;
+  padding: 6px;
+}
+
+.command-palette-v2-search [data-component="text-input-v2"] {
+  width: 100%;
+  height: 36px;
+  border-radius: 6px;
+  outline: 0;
+  background: color-mix(in srgb, var(--v2-background-bg-layer-02) 60%, transparent);
+  box-shadow: none;
+  transition:
+    background-color 120ms ease-in-out,
+    box-shadow 120ms ease-in-out;
+}
+
+.command-palette-v2-search [data-component="text-input-v2"]:where(:hover):not([data-disabled], [data-invalid]),
+.command-palette-v2-search [data-component="text-input-v2"]:where(:focus-within):not([data-disabled], [data-invalid]) {
+  background: var(--v2-background-bg-layer-02);
+  box-shadow: none;
+}
+
+.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-value"] {
+  border-radius: 6px;
+  background: transparent;
+  gap: 8px;
+}
+
+.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-leading-icon"] {
+  padding-left: 12px;
+}
+
+.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-input"] {
+  border-radius: 6px;
+  background: transparent;
+}
+
+.command-palette-v2-scroll {
+  min-height: 0;
+  flex: 1;
+}
+
+.command-palette-v2-results {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  padding: 6px 6px 8px;
+}
+
+.command-palette-v2-group {
+  display: flex;
+  flex-direction: column;
+  gap: 1px;
+}
+
+.command-palette-v2-group-title {
+  margin: 6px 0;
+  padding: 0 12px;
+  color: var(--v2-text-text-muted);
+  font-size: 13px;
+  font-weight: 440;
+  line-height: 16px;
+  letter-spacing: -0.04px;
+  user-select: none;
+}
+
+.command-palette-v2-row {
+  display: flex;
+  width: 100%;
+  height: 36px;
+  flex-shrink: 0;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+  padding: 0 12px;
+  border: 0;
+  border-radius: 6px;
+  background: transparent;
+  color: var(--v2-text-text-base);
+  text-align: left;
+  cursor: default;
+  scroll-margin: 6px 0;
+}
+
+.command-palette-v2-row[data-active] {
+  background: var(--v2-overlay-simple-overlay-hover);
+}
+
+.command-palette-v2-row:focus-visible {
+  background: var(--v2-overlay-simple-overlay-hover);
+  outline: none;
+}
+
+.command-palette-v2-row-main {
+  display: flex;
+  min-width: 0;
+  flex: 1;
+  align-items: center;
+  gap: 8px;
+}
+
+.command-palette-v2-row-icon {
+  flex-shrink: 0;
+  color: var(--v2-icon-icon-muted);
+}
+
+.command-palette-v2-row-text {
+  display: flex;
+  min-width: 0;
+  align-items: center;
+  gap: 6px;
+}
+
+.command-palette-v2-title {
+  min-width: 0;
+  overflow: hidden;
+  color: var(--v2-text-text-base);
+  font-size: 13px;
+  font-weight: 530;
+  line-height: 16px;
+  letter-spacing: -0.04px;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.command-palette-v2-description,
+.command-palette-v2-meta {
+  min-width: 0;
+  overflow: hidden;
+  color: var(--v2-text-text-muted);
+  font-size: 13px;
+  font-weight: 440;
+  line-height: 16px;
+  letter-spacing: -0.04px;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.command-palette-v2-meta {
+  flex-shrink: 0;
+}
+
+.command-palette-v2-file-path {
+  display: flex;
+  min-width: 0;
+  align-items: baseline;
+  font-size: 13px;
+  line-height: 16px;
+  letter-spacing: -0.04px;
+}
+
+.command-palette-v2-file-dir {
+  min-width: 0;
+  overflow: hidden;
+  color: var(--v2-text-text-muted);
+  font-weight: 440;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.command-palette-v2-file-name {
+  flex-shrink: 0;
+  color: var(--v2-text-text-base);
+  font-weight: 530;
+  white-space: nowrap;
+}
+
+.command-palette-v2-state {
+  display: grid;
+  min-height: 120px;
+  place-items: center;
+  color: var(--v2-text-text-muted);
+  font-size: 13px;
+  font-weight: 440;
+  line-height: 16px;
+  letter-spacing: -0.04px;
+}
+
+@media (max-width: 640px) {
+  .command-palette-v2-row-text {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 1px;
+  }
+
+  .command-palette-v2-description {
+    max-width: 100%;
+  }
+}

+ 221 - 0
packages/app/src/components/dialog-command-palette-v2.tsx

@@ -0,0 +1,221 @@
+import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
+import { FileIcon } from "@opencode-ai/ui/file-icon"
+import { ScrollView } from "@opencode-ai/ui/scroll-view"
+import { Dialog, DialogBody } from "@opencode-ai/ui/v2/dialog-v2"
+import { Icon } from "@opencode-ai/ui/v2/icon"
+import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
+import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
+import { createEffect, createMemo, createResource, createSignal, For, Match, Show, Switch } from "solid-js"
+import { formatKeybindParts } from "@/context/command"
+import { useLanguage } from "@/context/language"
+import { getRelativeTime } from "@/utils/time"
+import {
+  createCommandPaletteFileEntry,
+  createCommandPaletteModel,
+  uniqueCommandPaletteEntries,
+  type CommandPaletteEntry,
+} from "./command-palette"
+import "./dialog-command-palette-v2.css"
+
+function groups(entries: CommandPaletteEntry[]) {
+  const map = new Map<string, CommandPaletteEntry[]>()
+  for (const entry of entries) map.set(entry.category, [...(map.get(entry.category) ?? []), entry])
+  return Array.from(map.entries()).map(([category, entries]) => ({ category, entries }))
+}
+
+function matchesEntry(entry: CommandPaletteEntry, query: string) {
+  const value = query.toLowerCase()
+  return [entry.title, entry.description, entry.category].some((text) => text?.toLowerCase().includes(value))
+}
+
+export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => void }) {
+  const palette = createCommandPaletteModel(props)
+  const [query, setQuery] = createSignal("")
+  const [active, setActive] = createSignal(0)
+
+  const loadItems = async (text: string) => {
+    const q = text.trim()
+    if (!q) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
+
+    const [files, nextSessions] = await Promise.all([palette.file.searchFiles(q), Promise.resolve(palette.sessions(q))])
+    const category = palette.language.t("palette.group.files")
+    return [
+      ...palette.commandEntries().filter((entry) => matchesEntry(entry, q)),
+      ...nextSessions.filter((entry) => matchesEntry(entry, q)),
+      ...files.map((path) => createCommandPaletteFileEntry(path, category)),
+    ]
+  }
+
+  const [entries] = createResource(query, loadItems, { initialValue: [] as CommandPaletteEntry[] })
+  // Render stale results while a new query loads to avoid flashing "Loading" per keystroke.
+  const visibleEntries = createMemo(() => uniqueCommandPaletteEntries(entries.latest ?? []))
+  const groupedEntries = createMemo(() => groups(visibleEntries()))
+  const activeEntry = createMemo(() => visibleEntries()[active()])
+
+  createEffect(() => {
+    query()
+    visibleEntries()
+    setActive(0)
+  })
+
+  createEffect(() => {
+    palette.highlight(activeEntry())
+  })
+
+  let resultsRef: HTMLDivElement | undefined
+
+  const move = (delta: -1 | 1) => {
+    const count = visibleEntries().length
+    if (count === 0) return
+    setActive((index) => (index + delta + count) % count)
+    requestAnimationFrame(() => {
+      resultsRef?.querySelector("[data-active]")?.scrollIntoView({ block: "nearest" })
+    })
+  }
+
+  const handleKeyDown = (event: KeyboardEvent) => {
+    if (event.key === "ArrowDown") {
+      event.preventDefault()
+      move(1)
+      return
+    }
+    if (event.key === "ArrowUp") {
+      event.preventDefault()
+      move(-1)
+      return
+    }
+    if (event.key === "Enter") {
+      event.preventDefault()
+      palette.select(activeEntry())
+      return
+    }
+    if (event.key === "Escape") {
+      event.preventDefault()
+      palette.close()
+    }
+  }
+
+  return (
+    <Dialog class="command-palette-v2" size="large">
+      <DialogBody class="command-palette-v2-body">
+        <div class="command-palette-v2-search">
+          <TextInputV2
+            value={query()}
+            autofocus
+            autocomplete="off"
+            spellcheck={false}
+            appearance="large"
+            placeholder={palette.language.t("palette.search.placeholder")}
+            leadingIcon={<Icon name="magnifying-glass" />}
+            onInput={(event) => setQuery(event.currentTarget.value)}
+            onKeyDown={handleKeyDown}
+          />
+        </div>
+        <ScrollView class="command-palette-v2-scroll" viewportRef={(el) => (resultsRef = el)}>
+          <div class="command-palette-v2-results" role="listbox">
+            <Show
+              when={visibleEntries().length > 0}
+              fallback={
+                <div class="command-palette-v2-state">
+                  {entries.loading ? palette.language.t("common.loading") : palette.language.t("palette.empty")}
+                </div>
+              }
+            >
+              <For each={groupedEntries()}>
+                {(group) => (
+                  <div class="command-palette-v2-group">
+                    <Show when={group.category}>
+                      <div class="command-palette-v2-group-title">{group.category}</div>
+                    </Show>
+                    <For each={group.entries}>
+                      {(item) => (
+                        <PaletteRow
+                          item={item}
+                          active={activeEntry()?.id === item.id}
+                          language={palette.language}
+                          onActive={() => setActive(visibleEntries().findIndex((entry) => entry.id === item.id))}
+                          onSelect={() => palette.select(item)}
+                        />
+                      )}
+                    </For>
+                  </div>
+                )}
+              </For>
+            </Show>
+          </div>
+        </ScrollView>
+      </DialogBody>
+    </Dialog>
+  )
+}
+
+function PaletteRow(props: {
+  item: CommandPaletteEntry
+  active: boolean
+  language: ReturnType<typeof useLanguage>
+  onActive: () => void
+  onSelect: () => void
+}) {
+  return (
+    <button
+      type="button"
+      class="command-palette-v2-row"
+      role="option"
+      aria-selected={props.active}
+      data-active={props.active ? "" : undefined}
+      onMouseMove={(event) => {
+        // Ignore hover from a static cursor when keyboard scrolling moves rows underneath it.
+        if (event.movementX === 0 && event.movementY === 0) return
+        props.onActive()
+      }}
+      onMouseDown={(event) => event.preventDefault()}
+      onClick={props.onSelect}
+    >
+      <Switch
+        fallback={
+          <div class="command-palette-v2-row-main">
+            <FileIcon node={{ path: props.item.path ?? "", type: "file" }} class="command-palette-v2-row-icon size-4" />
+            <div class="command-palette-v2-file-path">
+              <span class="command-palette-v2-file-dir">{getDirectory(props.item.path ?? "")}</span>
+              <span class="command-palette-v2-file-name">{getFilename(props.item.path ?? "")}</span>
+            </div>
+          </div>
+        }
+      >
+        <Match when={props.item.type === "command"}>
+          <div class="command-palette-v2-row-main">
+            <div class="command-palette-v2-row-text">
+              <span class="command-palette-v2-title">{props.item.title}</span>
+              <Show when={props.item.description}>
+                <span class="command-palette-v2-description">{props.item.description}</span>
+              </Show>
+            </div>
+          </div>
+          <Show when={props.item.keybind}>
+            <KeybindV2 keys={formatKeybindParts(props.item.keybind ?? "", props.language.t)} variant="neutral" />
+          </Show>
+        </Match>
+        <Match when={props.item.type === "session"}>
+          <div class="command-palette-v2-row-main">
+            <Icon name="status" class="command-palette-v2-row-icon" />
+            <div class="command-palette-v2-row-text">
+              <span class="command-palette-v2-title" classList={{ "opacity-70": !!props.item.archived }}>
+                {props.item.title}
+              </span>
+              <Show when={props.item.description}>
+                <span class="command-palette-v2-description" classList={{ "opacity-70": !!props.item.archived }}>
+                  {props.item.description}
+                </span>
+              </Show>
+            </div>
+          </div>
+          <Show when={props.item.updated}>
+            <span class="command-palette-v2-meta">
+              {getRelativeTime(new Date(props.item.updated!).toISOString(), props.language.t)}
+            </span>
+          </Show>
+        </Match>
+      </Switch>
+    </button>
+  )
+}

+ 67 - 367
packages/app/src/components/dialog-select-file.tsx

@@ -1,329 +1,84 @@
-import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { Dialog } from "@opencode-ai/ui/dialog"
 import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { Icon } from "@opencode-ai/ui/icon"
 import { Keybind } from "@opencode-ai/ui/keybind"
 import { List } from "@opencode-ai/ui/list"
-import { base64Encode } from "@opencode-ai/core/util/encode"
 import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
-import { useNavigate } from "@solidjs/router"
-import { createMemo, createSignal, lazy, Match, onCleanup, Show, Switch } from "solid-js"
-import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
-import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
-import { useServerSync } from "@/context/server-sync"
-import { useLayout } from "@/context/layout"
-import { useFile } from "@/context/file"
+import { createMemo, createSignal, lazy, Match, Show, Switch } from "solid-js"
+import { formatKeybind } from "@/context/command"
+import { useServerSDK } from "@/context/server-sdk"
 import { useLanguage } from "@/context/language"
 import { usePlatform } from "@/context/platform"
 import { useSettings } from "@/context/settings"
 import { useSessionLayout } from "@/pages/session/session-layout"
-import { createSessionTabs } from "@/pages/session/helpers"
 import { decode64 } from "@/utils/base64"
 import { getRelativeTime } from "@/utils/time"
+import {
+  createCommandPaletteFileEntry,
+  createCommandPaletteFileOpener,
+  createCommandPaletteModel,
+  uniqueCommandPaletteEntries,
+  type CommandPaletteEntry,
+} from "./command-palette"
 
 const DialogSelectFileV2 = lazy(() =>
   import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })),
 )
-
-type EntryType = "command" | "file" | "session"
-
-type Entry = {
-  id: string
-  type: EntryType
-  title: string
-  description?: string
-  keybind?: string
-  category: string
-  option?: CommandOption
-  path?: string
-  directory?: string
-  sessionID?: string
-  archived?: number
-  updated?: number
-}
+const DialogCommandPaletteV2 = lazy(() =>
+  import("./dialog-command-palette-v2").then((module) => ({ default: module.DialogCommandPaletteV2 })),
+)
 
 type DialogSelectFileMode = "all" | "files"
 
-const ENTRY_LIMIT = 5
-const COMMON_COMMAND_IDS = [
-  "session.new",
-  "workspace.new",
-  "session.previous",
-  "session.next",
-  "terminal.toggle",
-  "review.toggle",
-] as const
-
-const uniqueEntries = (items: Entry[]) => {
-  const seen = new Set<string>()
-  const out: Entry[] = []
-  for (const item of items) {
-    if (seen.has(item.id)) continue
-    seen.add(item.id)
-    out.push(item)
-  }
-  return out
-}
-
-const createCommandEntry = (option: CommandOption, category: string): Entry => ({
-  id: "command:" + option.id,
-  type: "command",
-  title: option.title,
-  description: option.description,
-  keybind: option.keybind,
-  category,
-  option,
-})
-
-const createFileEntry = (path: string, category: string): Entry => ({
-  id: "file:" + path,
-  type: "file",
-  title: path,
-  category,
-  path,
-})
-
-const createSessionEntry = (
-  input: {
-    directory: string
-    id: string
-    title: string
-    description: string
-    archived?: number
-    updated?: number
-  },
-  category: string,
-): Entry => ({
-  id: `session:${input.directory}:${input.id}`,
-  type: "session",
-  title: input.title,
-  description: input.description,
-  category,
-  directory: input.directory,
-  sessionID: input.id,
-  archived: input.archived,
-  updated: input.updated,
-})
-
-function createCommandEntries(props: {
-  filesOnly: () => boolean
-  command: ReturnType<typeof useCommand>
-  language: ReturnType<typeof useLanguage>
-}) {
-  const allowed = createMemo(() => {
-    if (props.filesOnly()) return []
-    return props.command.options.filter(
-      (option) =>
-        !option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open",
-    )
-  })
-
-  const list = createMemo(() => {
-    const category = props.language.t("palette.group.commands")
-    return allowed().map((option) => createCommandEntry(option, category))
-  })
-
-  const picks = createMemo(() => {
-    const all = allowed()
-    const order = new Map<string, number>(COMMON_COMMAND_IDS.map((id, index) => [id, index]))
-    const picked = all.filter((option) => order.has(option.id))
-    const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
-    const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
-    const category = props.language.t("palette.group.commands")
-    return sorted.map((option) => createCommandEntry(option, category))
-  })
-
-  return { allowed, list, picks }
-}
-
-function createFileEntries(props: {
-  file: ReturnType<typeof useFile>
-  tabs: () => ReturnType<ReturnType<typeof useLayout>["tabs"]>
-  language: ReturnType<typeof useLanguage>
-}) {
-  const tabState = createSessionTabs({
-    tabs: props.tabs,
-    pathFromTab: props.file.pathFromTab,
-    normalizeTab: (tab) => (tab.startsWith("file://") ? props.file.tab(tab) : tab),
-  })
-  const recent = createMemo(() => {
-    const all = tabState.openedTabs()
-    const active = tabState.activeFileTab()
-    const order = active ? [active, ...all.filter((item) => item !== active)] : all
-    const seen = new Set<string>()
-    const category = props.language.t("palette.group.files")
-    const items: Entry[] = []
-
-    for (const item of order) {
-      const path = props.file.pathFromTab(item)
-      if (!path) continue
-      if (seen.has(path)) continue
-      seen.add(path)
-      items.push(createFileEntry(path, category))
-    }
-
-    return items.slice(0, ENTRY_LIMIT)
-  })
-
-  const root = createMemo(() => {
-    const category = props.language.t("palette.group.files")
-    const nodes = props.file.tree.children("")
-    const paths = nodes
-      .filter((node) => node.type === "file")
-      .map((node) => node.path)
-      .sort((a, b) => a.localeCompare(b))
-    return paths.slice(0, ENTRY_LIMIT).map((path) => createFileEntry(path, category))
-  })
-
-  return { recent, root }
-}
+export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) {
+  const platform = usePlatform()
+  const settings = useSettings()
+  const filesOnly = () => props.mode === "files"
 
-function createSessionEntries(props: {
-  workspaces: () => string[]
-  label: (directory: string) => string
-  serverSDK: ServerSDK
-  language: ReturnType<typeof useLanguage>
-}) {
-  const state: {
-    token: number
-    inflight: Promise<Entry[]> | undefined
-    cached: Entry[] | undefined
-  } = {
-    token: 0,
-    inflight: undefined,
-    cached: undefined,
+  if (!filesOnly() && settings.general.newLayoutDesigns()) {
+    return <DialogCommandPaletteV2 onOpenFile={props.onOpenFile} />
   }
 
-  const sessions = (text: string) => {
-    const query = text.trim()
-    if (!query) {
-      state.token += 1
-      state.inflight = undefined
-      state.cached = undefined
-      return [] as Entry[]
-    }
-
-    if (state.cached) return state.cached
-    if (state.inflight) return state.inflight
-
-    const current = state.token
-    const dirs = props.workspaces()
-    if (dirs.length === 0) return [] as Entry[]
-
-    state.inflight = Promise.all(
-      dirs.map((directory) => {
-        const description = props.label(directory)
-        return props.serverSDK.client.session
-          .list({ directory, roots: true })
-          .then((x) =>
-            (x.data ?? [])
-              .filter((s) => !!s?.id)
-              .map((s) => ({
-                id: s.id,
-                title: s.title ?? props.language.t("command.session.new"),
-                description,
-                directory,
-                archived: s.time?.archived,
-                updated: s.time?.updated,
-              })),
-          )
-          .catch(
-            () =>
-              [] as {
-                id: string
-                title: string
-                description: string
-                directory: string
-                archived?: number
-                updated?: number
-              }[],
-          )
-      }),
-    )
-      .then((results) => {
-        if (state.token !== current) return [] as Entry[]
-        const seen = new Set<string>()
-        const category = props.language.t("command.category.session")
-        const next = results
-          .flat()
-          .filter((item) => {
-            const key = `${item.directory}:${item.id}`
-            if (seen.has(key)) return false
-            seen.add(key)
-            return true
-          })
-          .map((item) => createSessionEntry(item, category))
-        state.cached = next
-        return next
-      })
-      .catch(() => [] as Entry[])
-      .finally(() => {
-        state.inflight = undefined
-      })
-
-    return state.inflight
+  if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) {
+    return <DialogSelectFileDesktopV2 onOpenFile={props.onOpenFile} />
   }
 
-  return { sessions }
+  return <DialogSelectFileLegacy filesOnly={filesOnly} onOpenFile={props.onOpenFile} />
 }
 
-export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) {
-  const command = useCommand()
+function DialogSelectFileDesktopV2(props: { onOpenFile?: (path: string) => void }) {
   const language = useLanguage()
-  const platform = usePlatform()
-  const settings = useSettings()
-  const layout = useLayout()
-  const file = useFile()
-  const dialog = useDialog()
-  const navigate = useNavigate()
   const serverSDK = useServerSDK()
-  const serverSync = useServerSync()
-  const { params, tabs, view } = useSessionLayout()
-  const filesOnly = () => props.mode === "files"
-  const state = { cleanup: undefined as (() => void) | void, committed: false }
-  const [grouped, setGrouped] = createSignal(false)
-  const commandEntries = createCommandEntries({ filesOnly, command, language })
-  const fileEntries = createFileEntries({ file, tabs, language })
-
+  const { params } = useSessionLayout()
   const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
-  const project = createMemo(() => {
-    const directory = projectDirectory()
-    if (!directory) return
-    return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
-  })
-  const workspaces = createMemo(() => {
-    const directory = projectDirectory()
-    const current = project()
-    if (!current) return directory ? [directory] : []
+  const openFile = createCommandPaletteFileOpener(props.onOpenFile)
 
-    const dirs = [current.worktree, ...(current.sandboxes ?? [])]
-    if (directory && !dirs.includes(directory)) return [...dirs, directory]
-    return dirs
-  })
-  const homedir = createMemo(() => serverSync().data.path.home)
-  const label = (directory: string) => {
-    const current = project()
-    const kind =
-      current && directory === current.worktree
-        ? language.t("workspace.type.local")
-        : language.t("workspace.type.sandbox")
-    const [store] = serverSync().child(directory, { bootstrap: false })
-    const home = homedir()
-    const path = home ? directory.replace(home, "~") : directory
-    const name = store.vcs?.branch ?? getFilename(directory)
-    return `${kind} : ${name || path}`
-  }
+  return (
+    <DialogSelectFileV2
+      server={serverSDK().server}
+      mode="file"
+      start={projectDirectory()}
+      title={language.t("session.header.searchFiles")}
+      onSelect={(result) => {
+        if (typeof result !== "string") return
+        openFile(result)
+      }}
+    />
+  )
+}
 
-  const { sessions } = createSessionEntries({ workspaces, label, serverSDK: serverSDK(), language })
+function DialogSelectFileLegacy(props: { filesOnly: () => boolean; onOpenFile?: (path: string) => void }) {
+  const palette = createCommandPaletteModel(props)
+  const [grouped, setGrouped] = createSignal(false)
 
   const items = async (text: string) => {
     const query = text.trim()
     setGrouped(query.length > 0)
 
-    if (!query && filesOnly()) {
-      const loaded = file.tree.state("")?.loaded
-      const pending = loaded ? Promise.resolve() : file.tree.list("")
-      const next = uniqueEntries([...fileEntries.recent(), ...fileEntries.root()])
+    if (!query && props.filesOnly()) {
+      const loaded = palette.file.tree.state("")?.loaded
+      const pending = loaded ? Promise.resolve() : palette.file.tree.list("")
+      const next = uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
 
       if (loaded || next.length > 0) {
         void pending
@@ -331,79 +86,24 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
       }
 
       await pending
-      return uniqueEntries([...fileEntries.recent(), ...fileEntries.root()])
-    }
-
-    if (!query) return [...commandEntries.picks(), ...fileEntries.recent()]
-
-    if (filesOnly()) {
-      const files = await file.searchFiles(query)
-      const category = language.t("palette.group.files")
-      return files.map((path) => createFileEntry(path, category))
+      return uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
     }
 
-    const [files, nextSessions] = await Promise.all([file.searchFiles(query), Promise.resolve(sessions(query))])
-    const category = language.t("palette.group.files")
-    const entries = files.map((path) => createFileEntry(path, category))
-    return [...commandEntries.list(), ...nextSessions, ...entries]
-  }
-
-  const handleMove = (item: Entry | undefined) => {
-    state.cleanup?.()
-    if (!item) return
-    if (item.type !== "command") return
-    state.cleanup = item.option?.onHighlight?.()
-  }
-
-  const open = (path: string) => {
-    const value = file.tab(path)
-    void tabs().open(value)
-    void file.load(path)
-    if (!view().reviewPanel.opened()) view().reviewPanel.open()
-    layout.fileTree.setTab("all")
-    props.onOpenFile?.(path)
-    tabs().setActive(value)
-  }
-
-  const handleSelect = (item: Entry | undefined) => {
-    if (!item) return
-    state.committed = true
-    state.cleanup = undefined
-    dialog.close()
+    if (!query) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
 
-    if (item.type === "command") {
-      item.option?.onSelect?.("palette")
-      return
+    if (props.filesOnly()) {
+      const files = await palette.file.searchFiles(query)
+      const category = palette.language.t("palette.group.files")
+      return files.map((path) => createCommandPaletteFileEntry(path, category))
     }
 
-    if (item.type === "session") {
-      if (!item.directory || !item.sessionID) return
-      navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
-      return
-    }
-
-    if (!item.path) return
-    open(item.path)
-  }
-
-  onCleanup(() => {
-    if (state.committed) return
-    state.cleanup?.()
-  })
-
-  if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) {
-    return (
-      <DialogSelectFileV2
-        server={serverSDK().server}
-        mode="file"
-        start={projectDirectory()}
-        title={language.t("session.header.searchFiles")}
-        onSelect={(result) => {
-          if (typeof result !== "string") return
-          open(result)
-        }}
-      />
-    )
+    const [files, nextSessions] = await Promise.all([
+      palette.file.searchFiles(query),
+      Promise.resolve(palette.sessions(query)),
+    ])
+    const category = palette.language.t("palette.group.files")
+    const entries = files.map((path) => createCommandPaletteFileEntry(path, category))
+    return [...palette.commandEntries(), ...nextSessions, ...entries]
   }
 
   return (
@@ -411,21 +111,21 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
       <List
         class="px-3"
         search={{
-          placeholder: filesOnly()
-            ? language.t("session.header.searchFiles")
-            : language.t("palette.search.placeholder"),
+          placeholder: props.filesOnly()
+            ? palette.language.t("session.header.searchFiles")
+            : palette.language.t("palette.search.placeholder"),
           autofocus: true,
           hideIcon: true,
         }}
-        emptyMessage={language.t("palette.empty")}
-        loadingMessage={language.t("common.loading")}
+        emptyMessage={palette.language.t("palette.empty")}
+        loadingMessage={palette.language.t("common.loading")}
         items={items}
         key={(item) => item.id}
         filterKeys={["title", "description", "category"]}
         skipFilter={(item) => item.type === "file"}
         groupBy={grouped() ? (item) => item.category : () => ""}
-        onMove={handleMove}
-        onSelect={handleSelect}
+        onMove={(item: CommandPaletteEntry | undefined) => palette.highlight(item)}
+        onSelect={(item: CommandPaletteEntry | undefined) => palette.select(item)}
       >
         {(item) => (
           <Switch
@@ -452,7 +152,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
                   </Show>
                 </div>
                 <Show when={item.keybind}>
-                  <Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", language.t)}</Keybind>
+                  <Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", palette.language.t)}</Keybind>
                 </Show>
               </div>
             </Match>
@@ -479,7 +179,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
                 </div>
                 <Show when={item.updated}>
                   <span class="text-12-regular text-text-weak whitespace-nowrap ml-2">
-                    {getRelativeTime(new Date(item.updated!).toISOString(), language.t)}
+                    {getRelativeTime(new Date(item.updated!).toISOString(), palette.language.t)}
                   </span>
                 </Show>
               </div>

+ 36 - 19
packages/app/src/components/dialog-select-model.tsx

@@ -24,6 +24,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
 import { Icon } from "@opencode-ai/ui/v2/icon"
 import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2"
 import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
+import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
 import { ModelTooltip } from "./model-tooltip"
 import { useLanguage } from "@/context/language"
 import { decode64 } from "@/utils/base64"
@@ -92,6 +93,7 @@ const ModelList: Component<{
           class="w-full"
           placement="right-start"
           gutter={12}
+          openDelay={0}
           value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
         >
           {node}
@@ -446,26 +448,41 @@ export function ModelSelectorPopoverV2(props: {
                       <MenuV2.RadioGroup value={current()}>
                         <For each={group.items}>
                           {(item) => (
-                            <MenuV2.RadioItem
-                              value={modelKey(item)}
-                              data-option-key={modelKey(item)}
-                              data-selected-model={current() === modelKey(item) ? true : undefined}
-                              class="scroll-my-6"
-                              classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
-                              onMouseEnter={() => {
-                                setStore("active", modelKey(item))
-                                setTimeout(() => searchRef?.focus())
-                              }}
-                              onSelect={() => selectModel(item)}
+                            <TooltipV2
+                              class="w-full"
+                              placement="right-start"
+                              gutter={6}
+                              openDelay={0}
+                              value={
+                                <ModelTooltip
+                                  model={item}
+                                  latest={item.latest}
+                                  free={isFree(item.provider.id, item.cost)}
+                                  v2
+                                />
+                              }
                             >
-                              <span class="min-w-0 truncate">{item.name}</span>
-                              <Show when={isFree(item.provider.id, item.cost)}>
-                                <TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2>
-                              </Show>
-                              <Show when={item.latest}>
-                                <TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
-                              </Show>
-                            </MenuV2.RadioItem>
+                              <MenuV2.RadioItem
+                                value={modelKey(item)}
+                                data-option-key={modelKey(item)}
+                                data-selected-model={current() === modelKey(item) ? true : undefined}
+                                class="scroll-my-6 w-full"
+                                classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
+                                onMouseEnter={() => {
+                                  setStore("active", modelKey(item))
+                                  setTimeout(() => searchRef?.focus())
+                                }}
+                                onSelect={() => selectModel(item)}
+                              >
+                                <span class="min-w-0 truncate">{item.name}</span>
+                                <Show when={isFree(item.provider.id, item.cost)}>
+                                  <TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2>
+                                </Show>
+                                <Show when={item.latest}>
+                                  <TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
+                                </Show>
+                              </MenuV2.RadioItem>
+                            </TooltipV2>
                           )}
                         </For>
                       </MenuV2.RadioGroup>

+ 35 - 2
packages/app/src/components/model-tooltip.tsx

@@ -1,4 +1,4 @@
-import { Show, type Component } from "solid-js"
+import { Show, type Component, type JSX } from "solid-js"
 import { useLanguage } from "@/context/language"
 
 type InputKey = "text" | "image" | "audio" | "video" | "pdf"
@@ -23,7 +23,18 @@ type ModelInfo = {
   }
 }
 
-export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean }> = (props) => {
+function ModelTooltipRow(props: { name: JSX.Element; value: JSX.Element }) {
+  return (
+    <div class="flex min-w-0 items-center gap-4">
+      <span class="shrink-0 text-v2-text-text-muted">{props.name}</span>
+      <span class="ml-auto min-w-0 truncate text-right text-v2-text-text-base">{props.value}</span>
+    </div>
+  )
+}
+
+export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean; v2?: boolean }> = (
+  props,
+) => {
   const language = useLanguage()
   const sourceName = (model: ModelInfo) => {
     const value = `${model.id} ${model.name}`.toLowerCase()
@@ -51,6 +62,13 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
     const suffix = tags.length ? ` (${tags.join(", ")})` : ""
     return `${sourceName(props.model)} ${props.model.name}${suffix}`
   }
+  const name = () => {
+    const tags: Array<string> = []
+    if (props.latest) tags.push(language.t("model.tag.latest"))
+    if (props.free) tags.push(language.t("model.tag.free"))
+    const suffix = tags.length ? ` (${tags.join(", ")})` : ""
+    return `${props.model.name}${suffix}`
+  }
   const inputs = () => {
     if (props.model.capabilities) {
       const input = props.model.capabilities.input
@@ -73,6 +91,21 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
       : language.t("model.tooltip.reasoning.none")
   }
   const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() })
+  const contextLimit = () => props.model.limit.context.toLocaleString(language.intl())
+
+  if (props.v2) {
+    return (
+      <div class="flex w-[180px] flex-col gap-2">
+        <ModelTooltipRow name={language.t("model.tooltip.model")} value={name()} />
+        <ModelTooltipRow name={language.t("model.tooltip.provider")} value={props.model.provider.name} />
+        <Show when={inputs()}>
+          {(value) => <ModelTooltipRow name={language.t("model.tooltip.inputs")} value={value()} />}
+        </Show>
+        <ModelTooltipRow name={language.t("model.tooltip.reasoning")} value={reasoning()} />
+        <ModelTooltipRow name={language.t("model.tooltip.context.label")} value={contextLimit()} />
+      </div>
+    )
+  }
 
   return (
     <div class="flex flex-col gap-1 py-1">

+ 0 - 10
packages/app/src/components/settings-general.tsx

@@ -440,11 +440,6 @@ export const SettingsGeneral: Component = () => {
             value={(o) => o.value}
             label={(o) => o.label}
             onSelect={(option) => option && theme.setColorScheme(option.value)}
-            onHighlight={(option) => {
-              if (!option) return
-              theme.previewColorScheme(option.value)
-              return () => theme.cancelPreview()
-            }}
             variant="secondary"
             size="small"
             triggerVariant="settings"
@@ -471,11 +466,6 @@ export const SettingsGeneral: Component = () => {
               if (!option) return
               theme.setTheme(option.id)
             }}
-            onHighlight={(option) => {
-              if (!option) return
-              theme.previewTheme(option.id)
-              return () => theme.cancelPreview()
-            }}
             variant="secondary"
             size="small"
             triggerVariant="settings"

+ 0 - 10
packages/app/src/components/settings-v2/general.tsx

@@ -422,11 +422,6 @@ export const SettingsGeneralV2: Component<{
             value={(o) => o.value}
             label={(o) => o.label}
             onSelect={(option) => option && theme.setColorScheme(option.value)}
-            onHighlight={(option) => {
-              if (!option) return
-              theme.previewColorScheme(option.value)
-              return () => theme.cancelPreview()
-            }}
           />
         </SettingsRowV2>
 
@@ -454,11 +449,6 @@ export const SettingsGeneralV2: Component<{
               if (!option) return
               theme.setTheme(option.id)
             }}
-            onHighlight={(option) => {
-              if (!option) return
-              theme.previewTheme(option.id)
-              return () => theme.cancelPreview()
-            }}
           />
         </SettingsRowV2>
 

+ 11 - 6
packages/app/src/components/titlebar-tab-nav.tsx

@@ -246,18 +246,23 @@ export function TabNavItem(props: {
           }}
           class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
         >
-          <Show when={props.session()}>
-            {(session) => (
-              <span data-slot="project-avatar-slot">
+          <span data-slot="project-avatar-slot" class="flex size-4 shrink-0 items-center justify-center">
+            <Show
+              when={props.session()}
+              fallback={
+                <span class="block size-4 rounded-[3px] border border-v2-border-border-muted" aria-hidden="true" />
+              }
+            >
+              {(session) => (
                 <SessionTabAvatar
                   project={project()}
                   directory={session().directory}
                   sessionId={session().id}
                   server={props.server}
                 />
-              </span>
-            )}
-          </Show>
+              )}
+            </Show>
+          </span>
           <span
             ref={(el) => {
               titleEl = el

+ 6 - 3
packages/app/src/components/titlebar-tab-strip.tsx

@@ -40,6 +40,7 @@ function SessionTabSlot(props: {
   let ref!: HTMLDivElement
   const sdk = createMemo(() => props.serverCtx()?.sdk ?? null)
   const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId))
+  const persisted = createMemo(() => tabs.info[props.id])
   const [loadedSession] = createResource(
     () => {
       const ctx = props.serverCtx()
@@ -71,8 +72,10 @@ function SessionTabSlot(props: {
 
   createEffect(() => {
     const value = session()
+    if (!value) return
+    tabs.rememberSessionInfo(props.tab, value)
     const current = sdk()
-    if (!value || !current) return
+    if (!current) return
     createTabPromptState(tabs, props.tab, current.scope, {
       dir: base64Encode(value.directory),
       id: value.id,
@@ -86,7 +89,7 @@ function SessionTabSlot(props: {
       data-tab-key={props.id}
       data-active={props.active()}
       class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
-      classList={{ hidden: !session() && !missingSession() }}
+      classList={{ hidden: !session() && !missingSession() && !persisted()?.title }}
     >
       <TabNavItem
         ref={(el) => {
@@ -95,7 +98,7 @@ function SessionTabSlot(props: {
         href={tabHref(props.tab)}
         server={props.tab.server}
         session={session}
-        fallbackTitle={missingSession() ? language.t("session.tab.unknown") : undefined}
+        fallbackTitle={persisted()?.title ?? (missingSession() ? language.t("session.tab.unknown") : undefined)}
         onTitleChange={(title) => {
           const value = session()
           const ctx = props.serverCtx()

+ 17 - 1
packages/app/src/context/command.test.ts

@@ -1,5 +1,5 @@
 import { describe, expect, test } from "bun:test"
-import { upsertCommandRegistration } from "./command"
+import { resolveKeybindOption, upsertCommandRegistration } from "./command"
 
 describe("upsertCommandRegistration", () => {
   test("replaces keyed registrations", () => {
@@ -23,3 +23,19 @@ describe("upsertCommandRegistration", () => {
     expect(next[1]?.options).toBe(one)
   })
 })
+
+describe("resolveKeybindOption", () => {
+  test("prefers a matching contextual command over the global fallback", () => {
+    const fallback = { id: "tab.close", title: "Close tab" }
+    const contextual = { id: "terminal.close", title: "Close terminal", when: () => true }
+
+    expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(contextual)
+  })
+
+  test("uses the global fallback outside the command context", () => {
+    const fallback = { id: "tab.close", title: "Close tab" }
+    const contextual = { id: "terminal.close", title: "Close terminal", when: () => false }
+
+    expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(fallback)
+  })
+})

+ 16 - 5
packages/app/src/context/command.tsx

@@ -82,10 +82,15 @@ export interface CommandOption {
   suggested?: boolean
   disabled?: boolean
   hidden?: boolean
+  when?: (event: KeyboardEvent) => boolean
   onSelect?: (source?: "palette" | "keybind" | "slash") => void
   onHighlight?: () => (() => void) | void
 }
 
+export function resolveKeybindOption(candidates: CommandOption[] | undefined, event: KeyboardEvent) {
+  return candidates?.find((option) => option.when?.(event)) ?? candidates?.find((option) => !option.when)
+}
+
 type CommandSource = "palette" | "keybind" | "slash"
 
 export type CommandCatalogItem = {
@@ -334,7 +339,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
     })
 
     const keymap = createMemo(() => {
-      const map = new Map<string, CommandOption>()
+      const map = new Map<string, CommandOption[]>()
       for (const option of options()) {
         if (option.id.startsWith(SUGGESTED_PREFIX)) continue
         if (option.disabled) continue
@@ -344,8 +349,12 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
         for (const kb of keybinds) {
           if (!kb.key) continue
           const sig = signature(kb.key, kb.ctrl, kb.meta, kb.shift, kb.alt)
-          if (map.has(sig)) continue
-          map.set(sig, option)
+          const existing = map.get(sig)
+          if (existing) {
+            existing.push(option)
+            continue
+          }
+          map.set(sig, [option])
         }
       }
       return map
@@ -374,7 +383,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
 
       const sig = signatureFromEvent(event)
       const isPalette = palette().has(sig)
-      const option = keymap().get(sig)
+      const option = resolveKeybindOption(keymap().get(sig), event)
       const modified = event.ctrlKey || event.metaKey || event.altKey
       const isTab = event.key === "Tab"
 
@@ -383,17 +392,19 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
 
       if (isPalette) {
         event.preventDefault()
+        event.stopPropagation()
         showPalette()
         return
       }
 
       if (!option) return
       event.preventDefault()
+      event.stopPropagation()
       option.onSelect?.("keybind")
     }
 
     onMount(() => {
-      makeEventListener(document, "keydown", handleKeyDown)
+      makeEventListener(document, "keydown", handleKeyDown, { capture: true })
     })
 
     function register(cb: () => CommandOption[]): void

+ 35 - 2
packages/app/src/context/tabs.tsx

@@ -28,6 +28,11 @@ export type DraftTab = {
 
 export type Tab = SessionTab | DraftTab
 
+export type TabInfo = {
+  title?: string
+  directory?: string
+}
+
 type RecentTab = {
   key?: string
 }
@@ -64,6 +69,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
       createStore<Tab[]>([]),
     )
     const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({}))
+    const [info, setInfo] = persisted(Persist.window("tabs.info"), createStore<Record<string, TabInfo>>({}))
     const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([]))
 
     const params = useParams()
@@ -102,6 +108,15 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
       for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform)
     }
 
+    const removeInfo = (key: string) => {
+      if (!info[key]) return
+      setInfo(
+        produce((draft) => {
+          delete draft[key]
+        }),
+      )
+    }
+
     onCleanup(memory.dispose)
 
     createEffect(() => {
@@ -110,11 +125,19 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
       const next = store.filter((tab) => servers.has(tab.server))
       if (next.length !== store.length) {
         for (const tab of store) {
-          if (!servers.has(tab.server)) memory.remove(tabKey(tab))
+          if (!servers.has(tab.server)) {
+            const key = tabKey(tab)
+            memory.remove(key)
+            removeInfo(key)
+          }
         }
         setStore(() => next)
       }
       if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined)
+      const keys = new Set(next.map(tabKey))
+      for (const key of Object.keys(info)) {
+        if (!keys.has(key)) removeInfo(key)
+      }
     })
 
     createEffect(() => {
@@ -150,6 +173,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
         if (nextTab) navigateTab(nextTab)
       }).finally(() => closing.delete(key))
       memory.remove(key)
+      removeInfo(key)
       if (draftID) removeDraftPersisted(draftID)
     }
 
@@ -264,6 +288,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
         const removed = store.filter((tab) => tab.server === key).map(tabKey)
         setStore((tabs) => tabs.filter((tab) => tab.server !== key))
         for (const key of removed) memory.remove(key)
+        for (const key of removed) removeInfo(key)
         if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
         for (const draftID of drafts) removeDraftPersisted(draftID)
         if (server.key === key) navigate("/")
@@ -318,6 +343,14 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
           if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
         })
         for (const key of removed) memory.remove(key)
+        for (const key of removed) removeInfo(key)
+      },
+      rememberSessionInfo(tab: SessionTab, session: Session) {
+        const key = tabKey(tab)
+        const next = { title: session.title, directory: session.directory }
+        const current = info[key]
+        if (current?.title === next.title && current.directory === next.directory) return
+        setInfo(key, next)
       },
       select: navigateTab,
       remember(tab: Tab) {
@@ -342,6 +375,6 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
       },
     }
 
-    return { ...actions, store, ready, recentReady }
+    return { ...actions, store, info, ready, recentReady }
   },
 })

+ 148 - 2
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": "إصلاح الاختبارات المعطلة",
@@ -314,6 +326,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 +450,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 +487,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 +533,38 @@ 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": "الأقدم",
   "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 +580,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 +596,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 +666,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 +677,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 +685,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 +698,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 +729,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 +746,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": "بلا",

+ 152 - 3
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",
@@ -314,6 +326,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 +452,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 +489,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 +537,30 @@ 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",
   "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 +570,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 +585,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 +601,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 +674,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 +693,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 +706,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 +715,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 +739,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 +757,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",

+ 151 - 2
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?",
@@ -342,6 +354,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 +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",
 
@@ -453,10 +535,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 +588,31 @@ 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",
 
   "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 +622,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 +640,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 +657,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 +735,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 +749,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 +758,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 +771,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 +804,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 +824,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",

+ 149 - 2
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?",
@@ -340,6 +352,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 +484,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 +532,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 +585,31 @@ 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",
 
   "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 +619,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 +635,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 +652,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 +731,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 +744,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 +753,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 +766,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 +797,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 +816,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.",
 

+ 152 - 2
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",
@@ -320,6 +332,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 +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": "Sprache",
@@ -414,10 +496,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 +545,31 @@ 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",
   "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 +579,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 +594,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 +610,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 +684,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 +695,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 +703,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 +717,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 +726,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 +752,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 +769,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",

+ 5 - 0
packages/app/src/i18n/en.ts

@@ -212,6 +212,11 @@ export const dict = {
   "model.input.video": "video",
   "model.input.pdf": "pdf",
   "model.tooltip.allows": "Allows: {{inputs}}",
+  "model.tooltip.context.label": "Context",
+  "model.tooltip.inputs": "Inputs",
+  "model.tooltip.model": "Model",
+  "model.tooltip.provider": "Provider",
+  "model.tooltip.reasoning": "Reasoning",
   "model.tooltip.reasoning.allowed": "Allows reasoning",
   "model.tooltip.reasoning.none": "No reasoning",
   "model.tooltip.context": "Context limit {{limit}}",

+ 152 - 2
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?",
@@ -341,6 +353,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 +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",
 
@@ -453,10 +535,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 +589,31 @@ 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",
 
   "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 +623,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 +641,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 +658,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 +738,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 +752,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 +761,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 +774,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 +783,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 +809,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 +830,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",

+ 153 - 2
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",
@@ -315,6 +327,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 +454,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 +492,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 +542,38 @@ 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",
   "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 +590,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 +606,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 +681,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 +692,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 +700,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 +713,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 +722,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 +749,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 +767,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",

+ 151 - 3
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": "壊れたテストを修正",
@@ -313,6 +325,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 +451,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 +488,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 +535,30 @@ 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": "それ以前",
   "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 +567,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 +582,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 +598,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 +671,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 +682,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 +690,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 +703,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 +712,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 +735,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 +753,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": "なし",

+ 153 - 2
packages/app/src/i18n/ko.ts

@@ -575,8 +575,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 +848,155 @@ 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": "이전",
+
+  "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": "설치 중...",
 }

+ 163 - 4
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",
@@ -469,7 +469,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 +649,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 +936,163 @@ 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",
+
+  "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<Record<Keys, string>>

+ 111 - 26
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<string, string> {
+  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()
+}

+ 157 - 6
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",
@@ -315,6 +327,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 +405,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 +453,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 +490,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 +515,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 +538,30 @@ 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",
   "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 +570,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 +585,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 +601,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 +674,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 +693,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 +707,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 +739,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 +757,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",

+ 151 - 2
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": "Какой технологический стек этого проекта?",
@@ -341,6 +353,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 +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",
 
@@ -452,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}}",
@@ -505,11 +589,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": "Ранее",
 
   "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 +622,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 +638,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 +655,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 +736,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 +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": "Звуковые эффекты",
@@ -638,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": "Цветовая схема",
@@ -646,8 +771,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 +780,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 +805,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 +825,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": "Нет",

+ 154 - 7
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": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?",
@@ -341,6 +353,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 +484,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 +533,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 +584,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": "ก่อนหน้านี้",
 
   "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 +617,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 +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": "ส่งทันที",
@@ -542,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}}",
@@ -612,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",
 
@@ -624,6 +743,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 +752,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 +765,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 +795,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 +813,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 +1004,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",

+ 154 - 6
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?",
@@ -346,6 +358,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 +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",
 
@@ -456,10 +538,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 +593,31 @@ 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",
 
   "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 +627,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 +645,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 +662,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 +741,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 +755,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 +764,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 +778,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 +787,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 +810,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 +832,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.",
 

+ 122 - 0
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": "Відключити",
@@ -354,6 +361,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 +549,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 +611,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": "Раніше",
 
   "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 +668,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 +685,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 +882,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 +895,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 +916,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": "Немає",

+ 132 - 2
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": "修复失败的测试",
@@ -359,6 +371,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 +497,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 +536,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 +588,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": "会话",
@@ -519,7 +604,12 @@ export const dict = {
   "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 +618,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 +633,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 +649,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 +726,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 +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": "音效",
@@ -644,6 +750,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 +762,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 +806,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。",
 

+ 144 - 2
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": "這個專案的技術堆疊是什麼?",
@@ -340,6 +352,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 +481,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 +529,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 +580,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": "更早",
 
   "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 +629,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 +646,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 +723,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 +736,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 +745,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 +757,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 +786,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 +803,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。",
 

+ 40 - 2
packages/app/src/index.css

@@ -224,7 +224,26 @@
 
   [data-slot="model-selector-scroll"]::before {
     top: 0;
-    background: linear-gradient(to bottom, var(--v2-background-bg-layer-01), transparent);
+    height: 12px;
+    background: linear-gradient(
+      180deg,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%
+    );
   }
 
   [data-slot="manage-models-scroll"]::before {
@@ -239,7 +258,26 @@
 
   [data-slot="model-selector-scroll"]::after {
     bottom: 0;
-    background: linear-gradient(to top, var(--v2-background-bg-layer-01), transparent);
+    height: 12px;
+    background: linear-gradient(
+      0deg,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%,
+      color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%
+    );
   }
 
   @supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) {

+ 3 - 3
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"
@@ -267,7 +267,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: "",
@@ -1278,7 +1278,7 @@ function HomeSessionSearch(props: {
             </div>
           </div>
         </Show>
-        <label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02">
+        <label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02 focus-within:bg-v2-background-bg-layer-02">
           <IconV2 name="magnifying-glass" />
           <input
             ref={input}

+ 2 - 0
packages/app/src/pages/layout-new.tsx

@@ -6,11 +6,13 @@ import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
 import { usePlatform } from "@/context/platform"
 import { setNavigate } from "@/utils/notification-click"
 import { setV2Toast, ToastRegion } from "@/utils/toast"
+import { useSettingsCommand } from "@/components/settings-dialog"
 
 export default function NewLayout(props: ParentProps) {
   const platform = usePlatform()
   const navigate = useNavigate()
   setNavigate(navigate)
+  useSettingsCommand()
 
   createEffect(() => setV2Toast(true))
 

+ 11 - 6
packages/app/src/pages/layout/project-avatar-state.ts

@@ -2,8 +2,8 @@ import { createMemo, type Accessor } from "solid-js"
 import { useGlobal } from "@/context/global"
 import { useNotification } from "@/context/notification"
 import { usePermission } from "@/context/permission"
+import { sessionPermissionRequest, sessionQuestionRequest } from "@/pages/session/composer/session-request-tree"
 import { ServerConnection } from "@/context/server"
-import { sessionPermissionRequest } from "@/pages/session/composer/session-request-tree"
 
 export function useSessionTabAvatarState(
   server: Accessor<ServerConnection.Key>,
@@ -26,15 +26,20 @@ export function useSessionTabAvatarState(
       return !permission.autoResponds(item, directory())
     })
   })
-  const unread = createMemo(() => {
-    if (hasPermissions()) return true
-    if (!connection()) return false
-    return notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0
+  const hasQuestions = createMemo(() => {
+    const serverSync = sync()
+    if (!serverSync) return false
+    const [store] = serverSync.child(directory(), { bootstrap: false })
+    return !!sessionQuestionRequest(store.session, serverSync.session.data.question, sessionId())
   })
+  const needsAttention = createMemo(() => hasPermissions() || hasQuestions())
+  const unread = createMemo(
+    () => needsAttention() || notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0,
+  )
   const loading = createMemo(() => {
     const serverSync = sync()
     if (!serverSync) return false
-    if (hasPermissions()) return false
+    if (needsAttention()) return false
     return serverSync.session.data.session_working(sessionId())
   })
   return { unread, loading }

+ 0 - 2
packages/app/src/pages/new-session.tsx

@@ -6,7 +6,6 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
 import { NewSessionDesignView } from "@/components/session"
 import { PromptInput } from "@/components/prompt-input"
 import { StatusPopoverV2 } from "@/components/status-popover"
-import { useSettingsCommand } from "@/components/settings-dialog"
 import {
   PromptProjectAddButton,
   PromptProjectSelector,
@@ -45,7 +44,6 @@ export default function NewSessionPage() {
   const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>()
 
   useComposerCommands()
-  useSettingsCommand()
 
   let inputRef: HTMLDivElement | undefined
 

+ 7 - 15
packages/app/src/pages/session.tsx

@@ -35,7 +35,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 +56,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,
@@ -153,7 +152,7 @@ export function TargetSessionRouteContent() {
   )
 }
 
-function SessionRouteErrorBoundary(
+export function SessionRouteErrorBoundary(
   props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
 ) {
   const settings = useSettings()
@@ -221,7 +220,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 +246,11 @@ function ResolvedTargetSessionRoute() {
           the terminal. Same-workspace tab switches keep it open because warm
           targets resolve synchronously from the sync cache. */}
       <Show when={directory()}>
-        <Show
-          when={settings.general.newLayoutDesigns()}
-          fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />}
-        >
-          <SDKProvider directory={targetDirectory}>
-            <DirectoryDataProvider directory={targetDirectory} server={serverKey}>
-              <TargetSessionPage />
-            </DirectoryDataProvider>
-          </SDKProvider>
-        </Show>
+        <SDKProvider directory={targetDirectory}>
+          <DirectoryDataProvider directory={targetDirectory} server={serverKey}>
+            <TargetSessionPage />
+          </DirectoryDataProvider>
+        </SDKProvider>
       </Show>
     </TargetServerScopedProviders>
   )
@@ -1021,7 +1014,6 @@ export default function Page() {
   }
 
   useComposerCommands()
-  useSettingsCommand()
   useSessionCommands({
     navigateMessageByOffset,
     setActiveMessage,

+ 4 - 2
packages/app/src/pages/session/timeline/message-timeline.tsx

@@ -160,8 +160,10 @@ function TimelineDiffSummaryRow(props: { diffs: SummaryDiff[] }) {
     >
       <div data-slot="session-turn-diffs-header">
         <span data-slot="session-turn-diffs-label">
-          {props.diffs.length} {language.t("ui.sessionTurn.diffs.changed")}{" "}
-          {language.t(props.diffs.length === 1 ? "ui.common.file.one" : "ui.common.file.other")}
+          {language.t(
+            props.diffs.length === 1 ? "ui.sessionTurn.diffs.changed.one" : "ui.sessionTurn.diffs.changed.other",
+            { count: String(props.diffs.length) },
+          )}
         </span>
         <DiffChanges changes={props.diffs} />
         <Show when={overflow() > 0}>

+ 19 - 6
packages/app/src/pages/session/use-session-commands.tsx

@@ -18,8 +18,6 @@ import { createSessionTabs } from "@/pages/session/helpers"
 import { extractPromptFromParts } from "@/utils/prompt"
 import { UserMessage } from "@opencode-ai/sdk/v2"
 import { useSessionLayout } from "@/pages/session/session-layout"
-import { useTabs } from "@/context/tabs"
-import { requireServerKey } from "@/utils/session-route"
 import { createSessionOwnership } from "./session-ownership"
 
 export type SessionCommandContext = {
@@ -48,7 +46,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
   const settings = useSettings()
   const sync = useSync()
   const terminal = useTerminal()
-  const sessionTabs = useTabs()
   const layout = useLayout()
   const navigate = useNavigate()
   const { params, sessionKey, tabs, view } = useSessionLayout()
@@ -269,6 +266,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
     view().terminal.open()
   }
 
+  const closeTerminal = () => {
+    const id = terminal.active()
+    if (!id) return
+    const last = terminal.all().length === 1
+    void terminal.close(id)
+    if (last) view().terminal.close()
+  }
+
   const chooseMcp = () => {
     void openDialog(
       () => import("@/components/dialog-select-mcp"),
@@ -411,9 +416,9 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
       title: language.t("command.session.new"),
       keybind: "mod+shift+s",
       slash: "new",
-      onSelect: () => {
-        if (params.serverKey) {
-          sessionTabs.newDraft({ server: requireServerKey(params.serverKey), directory: sdk().directory })
+      onSelect: (source) => {
+        if (settings.general.newLayoutDesigns()) {
+          command.trigger("tab.new", source)
           return
         }
         navigate(`/${params.dir}/session`)
@@ -518,6 +523,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
   ]
 
   const terminalCmds = () => [
+    terminalCommand({
+      id: "terminal.close",
+      title: language.t("terminal.close"),
+      keybind: "mod+w",
+      hidden: true,
+      when: (event) => event.target instanceof Element && !!event.target.closest('[data-component="terminal"]'),
+      onSelect: closeTerminal,
+    }),
     terminalCommand({
       id: "terminal.new",
       title: language.t("command.terminal.new"),

+ 1 - 1
packages/cli/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/cli",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "type": "module",
   "license": "MIT",
   "bin": {

+ 1 - 1
packages/codemode/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/codemode",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "description": "Effect-native confined code execution over schema-described tools",
   "private": true,
   "type": "module",

+ 1 - 1
packages/console/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-app",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/console/core/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/console-core",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 1 - 1
packages/console/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-function",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 1 - 1
packages/console/mail/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-mail",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "dependencies": {
     "@jsx-email/all": "2.2.3",
     "@jsx-email/cli": "1.4.3",

+ 1 - 1
packages/console/support/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-support",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "name": "@opencode-ai/core",
   "type": "module",
   "license": "MIT",

+ 41 - 4
packages/core/src/config/plugin/agent.ts

@@ -11,6 +11,11 @@ import { FSUtil } from "../../fs-util"
 import { ModelV2 } from "../../model"
 import { ConfigAgentV1 } from "../../v1/config/agent"
 import { ConfigMigrateV1 } from "../../v1/config/migrate"
+import { Global } from "../../global"
+import { PermissionV2 } from "../../permission"
+import type { LocationMutation } from "../../location-mutation"
+import type { ReadTool } from "../../tool/read"
+import type { EditTool } from "../../tool/edit"
 
 const legacySources = [
   { pattern: "{agent,agents}/**/*.md", primary: false },
@@ -19,6 +24,11 @@ const legacySources = [
 const decodeAgent = Schema.decodeUnknownOption(ConfigAgent.Info)
 const decodeLegacyAgent = Schema.decodeUnknownOption(ConfigAgentV1.Info)
 const decodeConfig = Schema.decodeUnknownOption(Config.Info)
+type PathAction =
+  | LocationMutation.ExternalDirectoryAuthorization["action"]
+  | typeof ReadTool.name
+  | typeof EditTool.name
+const pathActions = ["external_directory", "read", "edit"] as const satisfies readonly PathAction[]
 const agentKeys = new Set([
   "model",
   "variant",
@@ -38,6 +48,7 @@ export const Plugin = define({
   effect: Effect.fn(function* (ctx) {
     const config = yield* Config.Service
     const fs = yield* FSUtil.Service
+    const global = yield* Global.Service
     yield* ctx.agent.transform(
       Effect.fn(function* (draft) {
         const documents = yield* Effect.forEach(yield* config.entries(), (entry) => {
@@ -56,11 +67,14 @@ export const Plugin = define({
             )
           })
         }).pipe(Effect.map((documents) => documents.flat()))
-        const global = documents.flatMap((document) => document.info.permissions ?? [])
+        const permissions = expandPermissions(
+          documents.flatMap((document) => document.info.permissions ?? []),
+          global.home,
+        )
         const configuredDefault = Config.latest(documents, "default_agent")
         if (configuredDefault !== undefined) draft.default(AgentV2.ID.make(configuredDefault))
         for (const current of draft.list()) {
-          draft.update(current.id, (agent) => agent.permissions.push(...global))
+          draft.update(current.id, (agent) => agent.permissions.push(...permissions))
         }
 
         for (const document of documents) {
@@ -73,7 +87,7 @@ export const Plugin = define({
 
             const exists = draft.get(agentID) !== undefined
             draft.update(agentID, (agent) => {
-              if (!exists) agent.permissions.push(...global)
+              if (!exists) agent.permissions.push(...permissions)
               if (item.model !== undefined) {
                 const model = ModelV2.parse(item.model)
                 agent.model = { id: model.modelID, providerID: model.providerID, variant: agent.model?.variant }
@@ -91,7 +105,9 @@ export const Plugin = define({
               if (item.hidden !== undefined) agent.hidden = item.hidden
               if (item.color !== undefined) agent.color = item.color
               if (item.steps !== undefined) agent.steps = item.steps
-              if (item.permissions !== undefined) agent.permissions.push(...item.permissions)
+              if (item.permissions !== undefined) {
+                agent.permissions.push(...expandPermissions(item.permissions, global.home))
+              }
             })
           }
         }
@@ -100,6 +116,27 @@ export const Plugin = define({
   }),
 })
 
+function expandPermissions(rules: PermissionV2.Ruleset, home: string): PermissionV2.Ruleset {
+  // Expand only resources tools resolve as filesystem paths. Bash resources are raw shell text:
+  // rewriting `$HOME/private/**` would miss `$HOME/private/key`, and safe expansion needs shell-aware parsing.
+  return rules.map((rule) =>
+    isPathAction(rule.action) ? { ...rule, resource: expandHome(rule.resource, home) } : rule,
+  )
+}
+
+function isPathAction(action: string): action is PathAction {
+  return pathActions.some((item) => item === action)
+}
+
+function expandHome(resource: string, home: string) {
+  if (resource.startsWith("~/")) return home + resource.slice(1)
+  if (resource === "~") return home
+  if (resource === "$HOME") return home
+  if (resource.startsWith("$HOME/")) return home + resource.slice(5)
+  if (resource.startsWith("$HOME\\")) return home + resource.slice(5)
+  return resource
+}
+
 function discover(fs: FSUtil.Interface, directory: string) {
   return Effect.forEach(legacySources, (source) =>
     fs

+ 4 - 3
packages/core/src/fs-util.ts

@@ -60,9 +60,10 @@ export namespace FSUtil {
       })
 
       const readFileStringSafe = Effect.fn("FileSystem.readFileStringSafe")(function* (path: string) {
-        return yield* fs
-          .readFileString(path)
-          .pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)))
+        return yield* fs.readFileString(path).pipe(
+          Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)),
+          Effect.catchReason("PlatformError", "PermissionDenied", () => Effect.succeed(undefined)),
+        )
       })
 
       const isDir = Effect.fn("FileSystem.isDir")(function* (path: string) {

+ 11 - 4
packages/core/src/session/compaction.ts

@@ -22,20 +22,27 @@ const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <te
 - [constraints/preferences, decisions and why, important facts/assumptions, exact context needed to continue, or "(none)"]
 
 ## Work State
-- Completed: [finished work, verified facts, or changes made; otherwise "(none)"]
-- Active: [current work, partial changes, or investigation state; otherwise "(none)"]
-- Blocked: [blockers, failing commands, or unknowns; otherwise "(none)"]
+### Completed
+- [finished work, verified facts, or changes made; otherwise "(none)"]
+
+### Active
+- [current work, partial changes, or investigation state; otherwise "(none)"]
+
+### Blocked
+- [blockers, failing commands, or unknowns; otherwise "(none)"]
 
 ## Next Move
 1. [immediate concrete action, or "(none)"]
 2. [next action if known, or "(none)"]
+
+## Relevant Files
+- [file or directory path: why it matters, or "(none)"]
 </template>
 
 Rules:
 - Keep every section, even when empty.
 - Use terse bullets, not prose paragraphs.
 - Preserve exact file paths, symbols, commands, error strings, URLs, and identifiers when known.
-- Put relevant files and symbols inside the section where they matter; do not add extra sections.
 - Do not mention the summary process or that context was compacted.`
 
 type Entry = {

+ 76 - 1
packages/core/test/config/agent.test.ts

@@ -8,16 +8,43 @@ import { ConfigAgentPlugin } from "@opencode-ai/core/config/plugin/agent"
 import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { FSUtil } from "@opencode-ai/core/fs-util"
+import { Global } from "@opencode-ai/core/global"
 import { PermissionV2 } from "@opencode-ai/core/permission"
 import { AbsolutePath } from "@opencode-ai/core/schema"
+import { ConfigMigrateV1 } from "@opencode-ai/core/v1/config/migrate"
 import { tmpdir } from "../fixture/tmpdir"
 import { testEffect } from "../lib/effect"
 import { agentHost, host } from "../plugin/host"
 
-const it = testEffect(AppNodeBuilder.build(LayerNode.group([AgentV2.node, FSUtil.node])))
+const it = testEffect(AppNodeBuilder.build(LayerNode.group([AgentV2.node, FSUtil.node, Global.node])))
 const decode = Schema.decodeUnknownSync(Config.Info)
 
 describe("ConfigAgentPlugin.Plugin", () => {
+  it.effect("matches POSIX paths against home-relative permissions", () =>
+    Effect.gen(function* () {
+      const permissions = yield* loadHomePermissions("/home/test")
+      expect(PermissionV2.evaluate("external_directory", "/home/test/p/opencode/src/*", permissions).effect).toBe(
+        "allow",
+      )
+      expect(PermissionV2.evaluate("external_directory", "/home/test/cache/files/*", permissions).effect).toBe("deny")
+      expect(PermissionV2.evaluate("external_directory", "/some/~/path", permissions).effect).toBe("deny")
+      expect(PermissionV2.evaluate("external_directory", "$HOMELESS/private/*", permissions).effect).toBe("deny")
+      expect(PermissionV2.evaluate("bash", "$HOME/private/key", permissions).effect).toBe("deny")
+    }),
+  )
+
+  it.effect("matches Windows paths against home-relative permissions", () =>
+    Effect.gen(function* () {
+      const permissions = yield* loadHomePermissions("C:\\Users\\test")
+      expect(
+        PermissionV2.evaluate("external_directory", "C:\\Users\\test\\p\\opencode\\src\\*", permissions).effect,
+      ).toBe("allow")
+      expect(PermissionV2.evaluate("external_directory", "C:\\Users\\test\\cache\\files\\*", permissions).effect).toBe(
+        "deny",
+      )
+    }),
+  )
+
   it.effect("applies all global permissions before agent-specific permissions", () =>
     Effect.gen(function* () {
       const agents = yield* AgentV2.Service
@@ -272,3 +299,51 @@ Use native v2 fields.`,
     ),
   )
 })
+
+function loadHomePermissions(home: string) {
+  return Effect.gen(function* () {
+    const agents = yield* AgentV2.Service
+    const build = AgentV2.ID.make("build")
+    yield* agents.transform((editor) => editor.update(build, () => {}))
+    const config = Config.Service.of({
+      entries: () =>
+        Effect.succeed([
+          new Config.Document({
+            type: "document",
+            info: decode(
+              ConfigMigrateV1.migrate({
+                permission: {
+                  external_directory: {
+                    "~/p/**": "allow",
+                    "/some/~/path": "deny",
+                    "$HOMELESS/**": "deny",
+                  },
+                  bash: {
+                    "$HOME/private/**": "deny",
+                  },
+                },
+                agent: {
+                  build: {
+                    permission: {
+                      external_directory: {
+                        "$HOME/cache/**": "deny",
+                      },
+                    },
+                  },
+                },
+              }),
+            ),
+          }),
+        ]),
+    })
+
+    yield* ConfigAgentPlugin.Plugin.effect(host({ agent: agentHost(agents) })).pipe(
+      Effect.provideService(Config.Service, config),
+      Effect.provideService(Global.Service, Global.Service.of({ ...Global.make(), home })),
+    )
+
+    const agent = yield* agents.get(build)
+    if (!agent) throw new Error("expected configured build agent")
+    return agent.permissions
+  })
+}

+ 9 - 0
packages/core/test/session-compaction.test.ts

@@ -1,6 +1,15 @@
 import { expect, test } from "bun:test"
 import { SessionCompaction } from "@opencode-ai/core/session/compaction"
 
+test("compaction prompt preserves detailed work state and relevant files", () => {
+  const prompt = SessionCompaction.buildPrompt({ context: ["conversation history"] })
+
+  expect(prompt).toContain("## Work State\n### Completed")
+  expect(prompt).toContain("### Active")
+  expect(prompt).toContain("### Blocked")
+  expect(prompt).toContain("## Relevant Files")
+})
+
 test("compaction describes tool media without embedding base64", () => {
   const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
   const serialized = SessionCompaction.serializeToolContent([

+ 1 - 1
packages/desktop/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop",
   "private": true,
-  "version": "1.17.14",
+  "version": "1.17.15",
   "type": "module",
   "license": "MIT",
   "homepage": "https://opencode.ai",

+ 12 - 2
packages/desktop/src/main/windows.ts

@@ -71,7 +71,10 @@ export function setAppQuitting(quitting = true) {
 
 export function setBackgroundColor(color: string) {
   backgroundColor = color
-  BrowserWindow.getAllWindows().forEach((win) => win.setBackgroundColor(color))
+  BrowserWindow.getAllWindows().forEach((win) => {
+    win.setBackgroundColor(color)
+    if (process.platform === "darwin") win.invalidateShadow()
+  })
 }
 
 export function getBackgroundColor(): string | undefined {
@@ -106,6 +109,13 @@ function overlay(theme: Partial<TitlebarTheme> = {}, zoom = 1) {
 
 export function setTitlebar(win: BrowserWindow, theme: Partial<TitlebarTheme> = {}) {
   titlebarThemes.set(win, theme)
+  // macOS draws the window frame hairline and shadow using the NSWindow
+  // appearance, which follows nativeTheme rather than the rendered content.
+  // Align it with the app theme so a light app on a dark system does not get
+  // the dark-appearance border and shadow. A "system" scheme must map to
+  // "system" (not the resolved mode) or prefers-color-scheme stops tracking
+  // OS appearance changes in the renderer.
+  if (process.platform === "darwin") nativeTheme.themeSource = theme.scheme ?? theme.mode ?? "system"
   updateTitlebar(win)
 }
 
@@ -172,7 +182,7 @@ export function createMainWindow(id: string = randomUUID()) {
     ...(process.platform === "darwin"
       ? {
           titleBarStyle: "hidden" as const,
-          trafficLightPosition: { x: 12, y: 14 },
+          trafficLightPosition: { x: 14, y: 14 },
         }
       : {}),
     ...(process.platform === "win32"

+ 1 - 0
packages/desktop/src/preload/types.ts

@@ -31,6 +31,7 @@ export type UpdaterAPI = {
 export type LinuxDisplayBackend = "wayland" | "auto"
 export type TitlebarTheme = {
   mode: "light" | "dark"
+  scheme?: "system" | "light" | "dark"
 }
 export type FatalRendererError = {
   error: string

+ 1 - 1
packages/effect-drizzle-sqlite/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "name": "@opencode-ai/effect-drizzle-sqlite",
   "type": "module",
   "license": "MIT",

+ 1 - 1
packages/effect-sqlite-node/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "name": "@opencode-ai/effect-sqlite-node",
   "type": "module",
   "license": "MIT",

+ 1 - 1
packages/enterprise/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/enterprise",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 1 - 1
packages/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/function",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 1 - 1
packages/http-recorder/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "name": "@opencode-ai/http-recorder",
   "description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
   "type": "module",

+ 1 - 1
packages/llm/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "name": "@opencode-ai/llm",
   "type": "module",
   "license": "MIT",

+ 1 - 0
packages/llm/src/provider-error.ts

@@ -6,6 +6,7 @@ const patterns = [
   /input is too long for requested model/i,
   /exceeds the context window/i,
   /input token count.*exceeds the maximum/i,
+  /tokens in request more than max tokens allowed/i,
   /maximum prompt length is \d+/i,
   /reduce the length of the messages/i,
   /maximum context length is \d+ tokens/i,

+ 8 - 0
packages/llm/test/provider-error.test.ts

@@ -0,0 +1,8 @@
+import { describe, expect, test } from "bun:test"
+import { isContextOverflow } from "../src"
+
+describe("provider error classification", () => {
+  test("classifies Z.AI GLM token limit messages as context overflow", () => {
+    expect(isContextOverflow("tokens in request more than max tokens allowed")).toBe(true)
+  })
+})

+ 1 - 1
packages/opencode/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "name": "opencode",
   "type": "module",
   "license": "MIT",

+ 1 - 0
packages/opencode/src/config/config.ts

@@ -293,6 +293,7 @@ const layer = Layer.effect(
     })
 
     const ensureGitignore = Effect.fn("Config.ensureGitignore")(function* (dir: string) {
+      yield* fs.ensureDir(dir)
       const gitignore = path.join(dir, ".gitignore")
       const hasIgnore = yield* fs.existsSafe(gitignore)
       if (!hasIgnore) {

+ 25 - 0
packages/opencode/test/config/config.test.ts

@@ -923,6 +923,31 @@ it.effect("does not try to install dependencies in read-only OPENCODE_CONFIG_DIR
   }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))),
 )
 
+it.effect("ignores an inaccessible OPENCODE_CONFIG_DIR", () =>
+  Effect.gen(function* () {
+    if (process.platform === "win32") return
+
+    const dir = yield* tmpdirScoped()
+    const configDir = path.join(dir, "inaccessible")
+    yield* FSUtil.use.ensureDir(configDir)
+    yield* FSUtil.use.chmod(configDir, 0o000)
+    yield* Effect.addFinalizer(() => FSUtil.use.chmod(configDir, 0o755).pipe(Effect.ignore))
+
+    yield* withProcessEnv("OPENCODE_CONFIG_DIR", configDir, Config.use.get().pipe(provideInstanceEffect(dir)))
+  }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))),
+)
+
+it.effect("creates a missing OPENCODE_CONFIG_DIR", () =>
+  Effect.gen(function* () {
+    const dir = yield* tmpdirScoped()
+    const configDir = path.join(dir, "configdir")
+
+    yield* withProcessEnv("OPENCODE_CONFIG_DIR", configDir, Config.use.get().pipe(provideInstanceEffect(dir)))
+
+    expect(yield* FSUtil.use.readFileString(path.join(configDir, ".gitignore"))).toContain("node_modules")
+  }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))),
+)
+
 it.effect("installs dependencies in writable OPENCODE_CONFIG_DIR", () =>
   Effect.gen(function* () {
     const dir = yield* tmpdirScoped()

+ 1 - 0
packages/opencode/test/session/message-v2.test.ts

@@ -1448,6 +1448,7 @@ describe("session.message-v2.fromError", () => {
       "prompt is too long: 213462 tokens > 200000 maximum",
       "Your input exceeds the context window of this model",
       "The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)",
+      "tokens in request more than max tokens allowed",
       "Please reduce the length of the messages or completion",
       "400 status code (no body)",
       "413 status code (no body)",

+ 1 - 1
packages/plugin/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/plugin",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/sdk/js/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/sdk",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/server/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/server",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 1 - 1
packages/session-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/session-ui",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 14 - 0
packages/session-ui/src/components/markdown.css

@@ -121,6 +121,20 @@
     margin: 0;
   }
 
+  li:has(> input[type="checkbox"]),
+  li:has(> p > input[type="checkbox"]) {
+    position: relative;
+    list-style-type: none;
+  }
+
+  li > input[type="checkbox"],
+  li > p > input[type="checkbox"] {
+    position: absolute;
+    top: 4px;
+    left: -20px;
+    margin: 0;
+  }
+
   li > p + p {
     display: block;
     margin-top: 0.5rem;

+ 4 - 2
packages/session-ui/src/components/session-turn.tsx

@@ -440,8 +440,10 @@ export function SessionTurn(
                 >
                   <div data-slot="session-turn-diffs-header">
                     <span data-slot="session-turn-diffs-label">
-                      {edited()} {i18n.t("ui.sessionTurn.diffs.changed")}{" "}
-                      {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")}
+                      {i18n.t(
+                        edited() === 1 ? "ui.sessionTurn.diffs.changed.one" : "ui.sessionTurn.diffs.changed.other",
+                        { count: String(edited()) },
+                      )}
                     </span>
                     <DiffChanges changes={diffs()} />
                     <Show when={overflow() > 0}>

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/slack",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/stats/app/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/stats-app",
-  "version": "1.17.14",
+  "version": "1.17.15",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 6 - 1
packages/stats/app/src/i18n.ts

@@ -218,17 +218,22 @@ const en = {
   "model.noUsersTitle": "No user data",
   "model.noUsersDescription": "No user-bearing rows landed in the current window.",
   "model.dailyUserChart": "Daily unique user chart",
-  "model.efficiencyDescription": "Cost, cache behavior, and average OpenCode Go session shape.",
+  "model.efficiencyDescription": "Cost, cache behavior, and average session shape.",
   "model.noEfficiencyTitle": "No efficiency data",
   "model.noEfficiencyDescription": "Efficiency data appears after usage lands.",
+  "model.totalSpendLabel": "Total spend",
   "model.cost": "Cost",
   "model.totalSpend": "total spend",
   "model.costPerMillion": "Cost / 1M",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
   "model.inputOutput": "input / output",
   "model.observedTokens": "observed all tokens",
   "model.costSession": "Cost / Session",
+  "model.averageCostSession": "Average cost / Session",
   "model.average": "average",
   "model.tokensSession": "Tokens / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cacheRatio": "Cache Ratio",
   "model.inputTokens": "input tokens",
   "model.geoDescription": "OpenCode Go model tokens used by country.",

+ 5 - 0
packages/stats/app/src/i18n/ar.ts

@@ -202,6 +202,11 @@ export const dict = {
   "model.efficiencyDescription": "التكلفة، وسلوك التخزين المؤقت، ومتوسط شكل جلسة OpenCode Go.",
   "model.noEfficiencyTitle": "لا توجد بيانات كفاءة",
   "model.noEfficiencyDescription": "تظهر بيانات الكفاءة بعد وصول الاستخدام.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "التكلفة",
   "model.totalSpend": "إجمالي الإنفاق",
   "model.costPerMillion": "التكلفة / 1M",

+ 5 - 0
packages/stats/app/src/i18n/br.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Custo, comportamento de cache e formato médio da sessão do OpenCode Go.",
   "model.noEfficiencyTitle": "Sem dados de eficiência",
   "model.noEfficiencyDescription": "Os dados de eficiência aparecem depois que o uso chega.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Custo",
   "model.totalSpend": "gasto total",
   "model.costPerMillion": "Custo / 1M",

+ 5 - 0
packages/stats/app/src/i18n/da.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "Omkostning, cacheadfærd og gennemsnitlig OpenCode Go-sessionsform.",
   "model.noEfficiencyTitle": "Ingen effektivitetsdata",
   "model.noEfficiencyDescription": "Effektivitetsdata vises, når brug lander.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Omkostning",
   "model.totalSpend": "samlet forbrug",
   "model.costPerMillion": "Omkostning / 1 mio.",

+ 5 - 0
packages/stats/app/src/i18n/de.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Kosten, Cache-Verhalten und durchschnittliche OpenCode Go-Sitzungsform.",
   "model.noEfficiencyTitle": "Keine Effizienzdaten",
   "model.noEfficiencyDescription": "Effizienzdaten erscheinen, nachdem Nutzung eingegangen ist.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Kosten",
   "model.totalSpend": "Gesamtausgaben",
   "model.costPerMillion": "Kosten / 1 Mio.",

+ 5 - 0
packages/stats/app/src/i18n/es.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "Coste, comportamiento de caché y forma media de sesión de OpenCode Go.",
   "model.noEfficiencyTitle": "Sin datos de eficiencia",
   "model.noEfficiencyDescription": "Los datos de eficiencia aparecen después de que llegue uso.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Coste",
   "model.totalSpend": "gasto total",
   "model.costPerMillion": "Coste / 1 M",

+ 5 - 0
packages/stats/app/src/i18n/fr.ts

@@ -205,6 +205,11 @@ export const dict = {
   "model.efficiencyDescription": "Coût, comportement du cache et forme moyenne des sessions OpenCode Go.",
   "model.noEfficiencyTitle": "Aucune donnée d'efficacité",
   "model.noEfficiencyDescription": "Les données d'efficacité apparaissent après l'arrivée de l'utilisation.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Coût",
   "model.totalSpend": "dépense totale",
   "model.costPerMillion": "Coût / 1 M",

+ 5 - 0
packages/stats/app/src/i18n/it.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Costi, comportamento della cache e forma media della sessione OpenCode Go.",
   "model.noEfficiencyTitle": "Nessun dato di efficienza",
   "model.noEfficiencyDescription": "I dati di efficienza appaiono dopo l'arrivo dell'utilizzo.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Costo",
   "model.totalSpend": "spesa totale",
   "model.costPerMillion": "Costo / 1 M",

+ 5 - 0
packages/stats/app/src/i18n/ja.ts

@@ -205,6 +205,11 @@ export const dict = {
   "model.efficiencyDescription": "コスト、キャッシュ動作、平均的なOpenCode Goセッションの形。",
   "model.noEfficiencyTitle": "効率データがありません",
   "model.noEfficiencyDescription": "使用量が届くと効率データが表示されます。",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "コスト",
   "model.totalSpend": "総支出",
   "model.costPerMillion": "100万あたりのコスト",

+ 5 - 0
packages/stats/app/src/i18n/ko.ts

@@ -205,6 +205,11 @@ export const dict = {
   "model.efficiencyDescription": "비용, 캐시 동작, 평균 OpenCode Go 세션 형태입니다.",
   "model.noEfficiencyTitle": "효율 데이터 없음",
   "model.noEfficiencyDescription": "사용량이 들어오면 효율 데이터가 표시됩니다.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "비용",
   "model.totalSpend": "총 지출",
   "model.costPerMillion": "100만당 비용",

+ 5 - 0
packages/stats/app/src/i18n/no.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "Kostnad, cacheatferd og gjennomsnittlig OpenCode Go-øktform.",
   "model.noEfficiencyTitle": "Ingen effektivitetsdata",
   "model.noEfficiencyDescription": "Effektivitetsdata vises etter at bruk lander.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Kostnad",
   "model.totalSpend": "totalt forbruk",
   "model.costPerMillion": "Kostnad / 1 mill.",

+ 5 - 0
packages/stats/app/src/i18n/pl.ts

@@ -202,6 +202,11 @@ export const dict = {
   "model.efficiencyDescription": "Koszt, zachowanie cache i średni kształt sesji OpenCode Go.",
   "model.noEfficiencyTitle": "Brak danych wydajności",
   "model.noEfficiencyDescription": "Dane wydajności pojawią się po nadejściu użycia.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Koszt",
   "model.totalSpend": "łączne wydatki",
   "model.costPerMillion": "Koszt / 1 mln",

+ 5 - 0
packages/stats/app/src/i18n/ru.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Стоимость, поведение кэша и средняя форма сеанса OpenCode Go.",
   "model.noEfficiencyTitle": "Нет данных эффективности",
   "model.noEfficiencyDescription": "Данные эффективности появятся после использования.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Стоимость",
   "model.totalSpend": "общие расходы",
   "model.costPerMillion": "Стоимость / 1 млн",

+ 5 - 0
packages/stats/app/src/i18n/th.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "ต้นทุน พฤติกรรมแคช และรูปแบบเซสชัน OpenCode Go โดยเฉลี่ย",
   "model.noEfficiencyTitle": "ไม่มีข้อมูลประสิทธิภาพ",
   "model.noEfficiencyDescription": "ข้อมูลประสิทธิภาพจะแสดงหลังจากมีการใช้งานเข้ามา",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "ต้นทุน",
   "model.totalSpend": "ยอดใช้จ่ายรวม",
   "model.costPerMillion": "ต้นทุน / 1 ล้าน",

+ 5 - 0
packages/stats/app/src/i18n/tr.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Maliyet, önbellek davranışı ve ortalama OpenCode Go oturum yapısı.",
   "model.noEfficiencyTitle": "Verimlilik verisi yok",
   "model.noEfficiencyDescription": "Verimlilik verileri kullanım geldikten sonra görünür.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Maliyet",
   "model.totalSpend": "toplam harcama",
   "model.costPerMillion": "Maliyet / 1M",

+ 5 - 0
packages/stats/app/src/i18n/uk.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Вартість, поведінка кешу та середня форма сеансу OpenCode Go.",
   "model.noEfficiencyTitle": "Немає даних ефективності",
   "model.noEfficiencyDescription": "Дані ефективності з'являться після використання.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Вартість",
   "model.totalSpend": "загальні витрати",
   "model.costPerMillion": "Вартість / 1 млн",

+ 5 - 0
packages/stats/app/src/i18n/zh.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "成本、缓存行为和平均 OpenCode Go 会话形态。",
   "model.noEfficiencyTitle": "无效率数据",
   "model.noEfficiencyDescription": "使用量到达后会显示效率数据。",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "成本",
   "model.totalSpend": "总支出",
   "model.costPerMillion": "每 100 万成本",

+ 5 - 0
packages/stats/app/src/i18n/zht.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "成本、快取行為和平均 OpenCode Go 工作階段形態。",
   "model.noEfficiencyTitle": "無效率數據",
   "model.noEfficiencyDescription": "使用量到達後會顯示效率數據。",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "成本",
   "model.totalSpend": "總支出",
   "model.costPerMillion": "每 100 萬成本",

+ 102 - 38
packages/stats/app/src/routes/[lab]/[model].tsx

@@ -28,12 +28,17 @@ import {
   formatCatalogLabName,
   getModelCatalog,
   type ModelCatalog,
-  type ModelCatalogCost,
   type ModelCatalogEntry,
 } from "../model-catalog"
 import { SectionHeading } from "../section-heading"
 import { runStatsEffect } from "../../stats-runtime"
 import { setStatsPageCacheHeaders } from "../stats-cache"
+import {
+  ComparisonCardsSection,
+  modelRefFromCatalog,
+  uniqueComparisonPairs,
+  type ComparisonModelRef,
+} from "../compare-cards"
 import {
   applyThemePreference,
   Footer,
@@ -190,6 +195,11 @@ export default function StatsModel() {
                 <ModelEfficiencySection data={stats() ?? null} catalog={catalogEntry() ?? null} />
                 <ModelGeoBreakdownSection data={stats()?.country ?? emptyCountryRecord()} />
                 <ModelPeersSection data={stats() ?? null} />
+                <ComparisonCardsSection
+                  pairs={modelComparisonPairs(catalog(), catalogEntry() ?? null, stats() ?? null)}
+                  title="Compare This Model"
+                  description="Other models to compare with this one."
+                />
               </>
             </Show>
           </Show>
@@ -198,6 +208,7 @@ export default function StatsModel() {
           themePreference={themePreference()}
           onThemePreferenceChange={updateThemePreference}
           links={modelFooterLinks()}
+          bridge={{ href: "#model-comparison", label: "MODEL COMPARISONS" }}
         />
       </div>
     </main>
@@ -881,35 +892,32 @@ function ModelEfficiencySection(props: { data: StatsModelData | null; catalog: M
         }
       >
         {(data) => (
-          <div data-component="model-metric-grid" data-variant="dense">
-            <MetricCard
-              label={i18n.t("model.cost")}
-              value={formatMoney(data().totals.cost)}
-              detail={i18n.t("model.totalSpend")}
-            />
-            <MetricCard
-              label={i18n.t("model.costPerMillion")}
-              value={
-                props.catalog?.cost ? formatCatalogPrice(props.catalog.cost) : formatMoney(data().totals.costPerMillion)
-              }
-              detail={props.catalog?.cost ? i18n.t("model.inputOutput") : i18n.t("model.observedTokens")}
-            />
-            <MetricCard
-              label={i18n.t("model.costSession")}
-              value={formatSessionCost(data().totals.costPerSession)}
-              detail={i18n.t("model.average")}
-            />
-            <MetricCard
-              label={i18n.t("model.tokensSession")}
-              value={formatTokens(data().totals.tokensPerSession)}
-              detail={i18n.t("model.average")}
-            />
-            <MetricCard
-              label={i18n.t("model.cacheRatio")}
-              value={formatPercent(data().totals.cacheRatio)}
-              detail={i18n.t("model.inputTokens")}
-            />
-          </div>
+          <>
+            <div data-slot="model-efficiency-pattern" aria-hidden="true" />
+            <div data-component="model-efficiency-grid">
+              <MetricCard label={i18n.t("model.totalSpendLabel")} value={formatMoney(data().totals.cost)} />
+              <MetricCard
+                label={i18n.t("model.costInput")}
+                value={formatCatalogUnitPrice(props.catalog?.cost?.input)}
+              />
+              <MetricCard
+                label={i18n.t("model.costOutput")}
+                value={formatCatalogUnitPrice(props.catalog?.cost?.output)}
+              />
+              <MetricCard
+                label={i18n.t("model.averageCostSession")}
+                value={formatSessionCost(data().totals.costPerSession)}
+              />
+              <MetricCard
+                label={i18n.t("model.averageTokensSession")}
+                value={formatTokens(data().totals.tokensPerSession)}
+              />
+              <MetricCard
+                label={i18n.t("model.cacheRatio")}
+                value={`${formatPercent(data().totals.cacheRatio)} ${i18n.t("model.inputTokens")}`}
+              />
+            </div>
+          </>
         )}
       </Show>
     </section>
@@ -1129,12 +1137,12 @@ function ModelPeersSection(props: { data: StatsModelData | null }) {
   )
 }
 
-function MetricCard(props: { label: string; value: string; detail: string; state?: "positive" | "negative" }) {
+function MetricCard(props: { label: string; value: string; detail?: string; state?: "positive" | "negative" }) {
   return (
     <article data-component="model-metric" data-state={props.state}>
       <span>{props.label}</span>
       <strong>{props.value}</strong>
-      <p>{props.detail}</p>
+      <Show when={props.detail}>{(detail) => <p>{detail()}</p>}</Show>
     </article>
   )
 }
@@ -1147,10 +1155,16 @@ function PeerRow(props: { peer: ModelPeerEntry; active: boolean }) {
         href={language.route(`${import.meta.env.BASE_URL}${providerSlug(props.peer.provider)}/${props.peer.slug}`)}
         data-active={props.active ? "true" : undefined}
       >
-        <span>{String(props.peer.rank).padStart(2, "0")}</span>
-        <ProviderIcon aria-hidden="true" id={getProviderIconId(props.peer.author)} />
-        <strong>{props.peer.model}</strong>
-        <em>{props.peer.author}</em>
+        <span data-slot="model-peer-rank" aria-label={props.active ? `Rank ${props.peer.rank}` : undefined}>
+          <Show when={!props.active}>{String(props.peer.rank).padStart(2, "0")}</Show>
+        </span>
+        <span data-slot="model-peer-avatar">
+          <ProviderIcon aria-hidden="true" id={getProviderIconId(props.peer.author)} />
+        </span>
+        <span data-slot="model-peer-copy">
+          <strong>{props.peer.model}</strong>
+          <em>{props.peer.author}</em>
+        </span>
         <b>{formatTokens(props.peer.tokens)}</b>
       </a>
     </li>
@@ -1170,6 +1184,55 @@ function ModelEmptyState(props: { title: string; description: string; compact?:
   )
 }
 
+function modelComparisonPairs(
+  catalog: ModelCatalog | undefined,
+  catalogEntry: ModelCatalogEntry | null,
+  data: StatsModelData | null,
+) {
+  const current = modelComparisonRef(catalogEntry, data)
+  if (!current) return []
+  const peerPairs = (data?.peers ?? [])
+    .filter((peer) => peer.model !== data?.model)
+    .slice(0, 3)
+    .map((peer) => ({
+      first: current,
+      second: {
+        name: peer.model,
+        lab: peer.provider,
+        slug: peer.slug,
+        labName: peer.author,
+        metric: `#${peer.rank} / ${formatTokens(peer.tokens)}`,
+      },
+      detail: "Usage peer",
+    }))
+  const catalogPairs = (
+    catalogEntry && catalog ? (catalog.labs.find((lab) => lab.id === catalogEntry.lab)?.models ?? []) : []
+  )
+    .filter((model) => model.id !== catalogEntry?.id)
+    .slice(0, 3)
+    .map((model) => ({
+      first: current,
+      second: modelRefFromCatalog(model),
+      detail: "Same lab pair",
+    }))
+  return uniqueComparisonPairs([...peerPairs, ...catalogPairs])
+}
+
+function modelComparisonRef(
+  catalogEntry: ModelCatalogEntry | null,
+  data: StatsModelData | null,
+): ComparisonModelRef | undefined {
+  if (catalogEntry) return modelRefFromCatalog(catalogEntry)
+  if (!data) return undefined
+  return {
+    name: data.model,
+    lab: data.provider,
+    slug: data.slug,
+    labName: data.author,
+    metric: `#${data.rank}`,
+  }
+}
+
 function getProviderIconId(author: string) {
   if (author === "MiniMax") return "minimax"
   if (author === "Moonshot") return "moonshotai"
@@ -1517,8 +1580,9 @@ function formatMoney(value: number) {
   return `$${value.toFixed(value >= 10 ? 0 : 2)}`
 }
 
-function formatCatalogPrice(value: ModelCatalogCost) {
-  return `${formatModelPrice(value.input)} / ${formatModelPrice(value.output)}`
+function formatCatalogUnitPrice(value: number | undefined) {
+  if (value === undefined) return "-"
+  return `${formatModelPrice(value)} / 1M`
 }
 
 function formatModelPrice(value: number) {

+ 32 - 0
packages/stats/app/src/routes/[lab]/index.tsx

@@ -28,6 +28,7 @@ import {
 import { SectionHeading } from "../section-heading"
 import { runStatsEffect } from "../../stats-runtime"
 import { setStatsPageCacheHeaders } from "../stats-cache"
+import { ComparisonCardsSection, modelRefFromCatalog, uniqueComparisonPairs } from "../compare-cards"
 import {
   applyThemePreference,
   Footer,
@@ -149,6 +150,11 @@ export default function StatsLab() {
                     labs={catalog()?.labs ?? []}
                     market={homeStats()?.market["2M"] ?? []}
                   />
+                  <ComparisonCardsSection
+                    pairs={labComparisonPairs(data(), stats()?.models ?? [])}
+                    title={`${data().name} Model Comparisons`}
+                    description="Model pairs from this lab."
+                  />
                 </>
               )}
             </Show>
@@ -158,6 +164,7 @@ export default function StatsLab() {
           themePreference={themePreference()}
           onThemePreferenceChange={updateThemePreference}
           links={labFooterLinks()}
+          bridge={{ href: "#model-comparison", label: "MODEL COMPARISONS" }}
         />
       </div>
     </main>
@@ -732,6 +739,31 @@ function LabEmptyState(props: { title: string; description: string }) {
   )
 }
 
+function labComparisonPairs(lab: ModelCatalogLab, usage: LabUsageModelEntry[]) {
+  const usageRefs = usage.slice(0, 4).map((model) => ({
+    name: model.model,
+    lab: model.provider,
+    slug: model.slug,
+    labName: model.author,
+    metric: formatTokens(model.tokens),
+  }))
+  const refs = usageRefs.length > 1 ? usageRefs : lab.models.slice(0, 4).map(modelRefFromCatalog)
+  return uniqueComparisonPairs(
+    (
+      [
+        [0, 1, "Most-used lab pair"],
+        [0, 2, "Lab alternative"],
+        [1, 2, "Adjacent lab pair"],
+        [2, 3, "Same lab pair"],
+      ] as const
+    ).flatMap(([firstIndex, secondIndex, detail]) => {
+      const first = refs[firstIndex]
+      const second = refs[secondIndex]
+      return first && second ? [{ first, second, detail }] : []
+    }),
+  )
+}
+
 type RelatedLabEntry = { lab: ModelCatalogLab; share: number; tokens: number }
 
 function relatedLabs(current: ModelCatalogLab, labs: ModelCatalogLab[], market: MarketDay[]): RelatedLabEntry[] {

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio