Adam пре 5 месеци
родитељ
комит
05cb3c87ca
65 измењених фајлова са 1776 додато и 156 уклоњено
  1. 11 4
      packages/app/src/app.tsx
  2. 38 32
      packages/app/src/components/debug-bar.tsx
  3. 1 1
      packages/app/src/components/dialog-select-file.tsx
  4. 2 2
      packages/app/src/components/dialog-select-server.tsx
  5. 3 1
      packages/app/src/components/server/server-row.tsx
  6. 28 26
      packages/app/src/components/session/session-header.tsx
  7. 2 5
      packages/app/src/components/session/session-sortable-terminal-tab.tsx
  8. 1 1
      packages/app/src/components/settings-keybinds.tsx
  9. 1 1
      packages/app/src/components/terminal.tsx
  10. 51 10
      packages/app/src/context/command.tsx
  11. 3 3
      packages/app/src/context/file.tsx
  12. 4 2
      packages/app/src/context/global-sdk.tsx
  13. 1 0
      packages/app/src/context/global-sync.tsx
  14. 1 1
      packages/app/src/context/global-sync/bootstrap.ts
  15. 1 0
      packages/app/src/context/global-sync/child-store.test.ts
  16. 5 4
      packages/app/src/context/global-sync/child-store.ts
  17. 51 0
      packages/app/src/context/terminal-title.ts
  18. 4 7
      packages/app/src/context/terminal.tsx
  19. 73 0
      packages/app/src/i18n/ar.ts
  20. 75 0
      packages/app/src/i18n/br.ts
  21. 75 0
      packages/app/src/i18n/bs.ts
  22. 75 0
      packages/app/src/i18n/da.ts
  23. 76 0
      packages/app/src/i18n/de.ts
  24. 76 0
      packages/app/src/i18n/en.ts
  25. 75 0
      packages/app/src/i18n/es.ts
  26. 77 0
      packages/app/src/i18n/fr.ts
  27. 74 0
      packages/app/src/i18n/ja.ts
  28. 74 0
      packages/app/src/i18n/ko.ts
  29. 75 0
      packages/app/src/i18n/no.ts
  30. 76 0
      packages/app/src/i18n/pl.ts
  31. 75 0
      packages/app/src/i18n/ru.ts
  32. 75 0
      packages/app/src/i18n/th.ts
  33. 74 0
      packages/app/src/i18n/tr.ts
  34. 73 0
      packages/app/src/i18n/zh.ts
  35. 73 0
      packages/app/src/i18n/zht.ts
  36. 17 15
      packages/app/src/pages/error.tsx
  37. 1 1
      packages/app/src/pages/layout.tsx
  38. 5 3
      packages/app/src/pages/session.tsx
  39. 0 1
      packages/app/src/pages/session/composer/session-composer-region.tsx
  40. 1 1
      packages/app/src/pages/session/composer/session-question-dock.tsx
  41. 22 6
      packages/app/src/pages/session/composer/session-todo-dock.tsx
  42. 3 3
      packages/app/src/pages/session/terminal-label.ts
  43. 4 1
      packages/ui/src/components/basic-tool.tsx
  44. 7 4
      packages/ui/src/components/file-search.tsx
  45. 5 3
      packages/ui/src/components/line-comment-annotations.tsx
  46. 11 9
      packages/ui/src/components/message-part.tsx
  47. 10 6
      packages/ui/src/components/tool-error-card.tsx
  48. 12 0
      packages/ui/src/i18n/ar.ts
  49. 12 0
      packages/ui/src/i18n/br.ts
  50. 12 0
      packages/ui/src/i18n/bs.ts
  51. 12 0
      packages/ui/src/i18n/da.ts
  52. 12 0
      packages/ui/src/i18n/de.ts
  53. 13 0
      packages/ui/src/i18n/en.ts
  54. 12 0
      packages/ui/src/i18n/es.ts
  55. 12 0
      packages/ui/src/i18n/fr.ts
  56. 12 0
      packages/ui/src/i18n/ja.ts
  57. 12 0
      packages/ui/src/i18n/ko.ts
  58. 12 0
      packages/ui/src/i18n/no.ts
  59. 12 0
      packages/ui/src/i18n/pl.ts
  60. 12 0
      packages/ui/src/i18n/ru.ts
  61. 12 0
      packages/ui/src/i18n/th.ts
  62. 12 0
      packages/ui/src/i18n/tr.ts
  63. 12 0
      packages/ui/src/i18n/zh.ts
  64. 12 0
      packages/ui/src/i18n/zht.ts
  65. 6 3
      packages/ui/src/pierre/selection-bridge.ts

+ 11 - 4
packages/app/src/app.tsx

@@ -12,6 +12,7 @@ import { type BaseRouterProps, Navigate, Route, Router } from "@solidjs/router"
 import { type Duration, Effect } from "effect"
 import { type Duration, Effect } from "effect"
 import {
 import {
   type Component,
   type Component,
+  createMemo,
   createResource,
   createResource,
   createSignal,
   createSignal,
   ErrorBoundary,
   ErrorBoundary,
@@ -67,7 +68,7 @@ const SessionIndexRoute = () => <Navigate href="session" />
 
 
 function UiI18nBridge(props: ParentProps) {
 function UiI18nBridge(props: ParentProps) {
   const language = useLanguage()
   const language = useLanguage()
-  return <I18nProvider value={{ locale: language.locale, t: language.t }}>{props.children}</I18nProvider>
+  return <I18nProvider value={{ locale: language.intl, t: language.t }}>{props.children}</I18nProvider>
 }
 }
 
 
 declare global {
 declare global {
@@ -218,8 +219,12 @@ function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) {
 }
 }
 
 
 function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key: ServerConnection.Key) => void }) {
 function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key: ServerConnection.Key) => void }) {
+  const language = useLanguage()
   const server = useServer()
   const server = useServer()
   const others = () => server.list.filter((s) => ServerConnection.key(s) !== server.key)
   const others = () => server.list.filter((s) => ServerConnection.key(s) !== server.key)
+  const name = createMemo(() => server.name || server.key)
+  const serverToken = "\u0000server\u0000"
+  const unreachable = createMemo(() => language.t("app.server.unreachable", { server: serverToken }).split(serverToken))
 
 
   const timer = setInterval(() => props.onRetry?.(), 1000)
   const timer = setInterval(() => props.onRetry?.(), 1000)
   onCleanup(() => clearInterval(timer))
   onCleanup(() => clearInterval(timer))
@@ -229,13 +234,15 @@ function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key:
       <div class="flex flex-col items-center max-w-md text-center">
       <div class="flex flex-col items-center max-w-md text-center">
         <Splash class="w-12 h-15 mb-4" />
         <Splash class="w-12 h-15 mb-4" />
         <p class="text-14-regular text-text-base">
         <p class="text-14-regular text-text-base">
-          Could not reach <span class="text-text-strong font-medium">{server.name || server.key}</span>
+          {unreachable()[0]}
+          <span class="text-text-strong font-medium">{name()}</span>
+          {unreachable()[1]}
         </p>
         </p>
-        <p class="mt-1 text-12-regular text-text-weak">Retrying automatically...</p>
+        <p class="mt-1 text-12-regular text-text-weak">{language.t("app.server.retrying")}</p>
       </div>
       </div>
       <Show when={others().length > 0}>
       <Show when={others().length > 0}>
         <div class="flex flex-col gap-2 w-full max-w-sm">
         <div class="flex flex-col gap-2 w-full max-w-sm">
-          <span class="text-12-regular text-text-base text-center">Other servers</span>
+          <span class="text-12-regular text-text-base text-center">{language.t("app.server.otherServers")}</span>
           <div class="flex flex-col gap-1 bg-surface-base rounded-lg p-2">
           <div class="flex flex-col gap-1 bg-surface-base rounded-lg p-2">
             <For each={others()}>
             <For each={others()}>
               {(conn) => {
               {(conn) => {

+ 38 - 32
packages/app/src/components/debug-bar.tsx

@@ -2,6 +2,7 @@ import { useIsRouting, useLocation } from "@solidjs/router"
 import { batch, createEffect, onCleanup, onMount } from "solid-js"
 import { batch, createEffect, onCleanup, onMount } from "solid-js"
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
 import { Tooltip } from "@opencode-ai/ui/tooltip"
 import { Tooltip } from "@opencode-ai/ui/tooltip"
+import { useLanguage } from "@/context/language"
 
 
 type Mem = Performance & {
 type Mem = Performance & {
   memory?: {
   memory?: {
@@ -27,17 +28,17 @@ type Obs = PerformanceObserverInit & {
 const span = 5000
 const span = 5000
 
 
 const ms = (n?: number, d = 0) => {
 const ms = (n?: number, d = 0) => {
-  if (n === undefined || Number.isNaN(n)) return "n/a"
+  if (n === undefined || Number.isNaN(n)) return
   return `${n.toFixed(d)}ms`
   return `${n.toFixed(d)}ms`
 }
 }
 
 
 const time = (n?: number) => {
 const time = (n?: number) => {
-  if (n === undefined || Number.isNaN(n)) return "n/a"
+  if (n === undefined || Number.isNaN(n)) return
   return `${Math.round(n)}`
   return `${Math.round(n)}`
 }
 }
 
 
 const mb = (n?: number) => {
 const mb = (n?: number) => {
-  if (n === undefined || Number.isNaN(n)) return "n/a"
+  if (n === undefined || Number.isNaN(n)) return
   const v = n / 1024 / 1024
   const v = n / 1024 / 1024
   return `${v >= 1024 ? v.toFixed(0) : v.toFixed(1)}MB`
   return `${v >= 1024 ? v.toFixed(0) : v.toFixed(1)}MB`
 }
 }
@@ -74,6 +75,7 @@ function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string;
 }
 }
 
 
 export function DebugBar() {
 export function DebugBar() {
+  const language = useLanguage()
   const location = useLocation()
   const location = useLocation()
   const routing = useIsRouting()
   const routing = useIsRouting()
   const [state, setState] = createStore({
   const [state, setState] = createStore({
@@ -98,14 +100,15 @@ export function DebugBar() {
     },
     },
   })
   })
 
 
+  const na = () => language.t("debugBar.na")
   const heap = () => (state.heap.limit ? (state.heap.used ?? 0) / state.heap.limit : undefined)
   const heap = () => (state.heap.limit ? (state.heap.used ?? 0) / state.heap.limit : undefined)
   const heapv = () => {
   const heapv = () => {
     const value = heap()
     const value = heap()
-    if (value === undefined) return "n/a"
+    if (value === undefined) return na()
     return `${Math.round(value * 100)}%`
     return `${Math.round(value * 100)}%`
   }
   }
-  const longv = () => (state.long.count === undefined ? "n/a" : `${time(state.long.block)}/${state.long.count}`)
-  const navv = () => (state.nav.pending ? "..." : time(state.nav.dur))
+  const longv = () => (state.long.count === undefined ? na() : `${time(state.long.block) ?? na()}/${state.long.count}`)
+  const navv = () => (state.nav.pending ? "..." : (time(state.nav.dur) ?? na()))
 
 
   let prev = ""
   let prev = ""
   let start = 0
   let start = 0
@@ -359,7 +362,7 @@ export function DebugBar() {
 
 
   return (
   return (
     <aside
     <aside
-      aria-label="Development performance diagnostics"
+      aria-label={language.t("debugBar.ariaLabel")}
       class="pointer-events-auto fixed bottom-3 right-3 z-50 w-[308px] max-w-[calc(100vw-1.5rem)] overflow-hidden rounded-xl border p-0.5 text-text-on-interactive-base shadow-[var(--shadow-lg-border-base)] sm:bottom-4 sm:right-4 sm:w-[324px]"
       class="pointer-events-auto fixed bottom-3 right-3 z-50 w-[308px] max-w-[calc(100vw-1.5rem)] overflow-hidden rounded-xl border p-0.5 text-text-on-interactive-base shadow-[var(--shadow-lg-border-base)] sm:bottom-4 sm:right-4 sm:w-[324px]"
       style={{
       style={{
         "background-color": "color-mix(in srgb, var(--icon-interactive-base) 42%, black)",
         "background-color": "color-mix(in srgb, var(--icon-interactive-base) 42%, black)",
@@ -368,67 +371,70 @@ export function DebugBar() {
     >
     >
       <div class="grid grid-cols-5 gap-px font-mono">
       <div class="grid grid-cols-5 gap-px font-mono">
         <Cell
         <Cell
-          label="NAV"
-          tip="Last completed route transition touching a session page, measured from router start until the first paint after it settles."
+          label={language.t("debugBar.nav.label")}
+          tip={language.t("debugBar.nav.tip")}
           value={navv()}
           value={navv()}
           bad={bad(state.nav.dur, 400)}
           bad={bad(state.nav.dur, 400)}
           dim={state.nav.dur === undefined && !state.nav.pending}
           dim={state.nav.dur === undefined && !state.nav.pending}
         />
         />
         <Cell
         <Cell
-          label="FPS"
-          tip="Rolling frames per second over the last 5 seconds."
-          value={state.fps === undefined ? "n/a" : `${Math.round(state.fps)}`}
+          label={language.t("debugBar.fps.label")}
+          tip={language.t("debugBar.fps.tip")}
+          value={state.fps === undefined ? na() : `${Math.round(state.fps)}`}
           bad={bad(state.fps, 50, true)}
           bad={bad(state.fps, 50, true)}
           dim={state.fps === undefined}
           dim={state.fps === undefined}
         />
         />
         <Cell
         <Cell
-          label="FRAME"
-          tip="Worst frame time over the last 5 seconds."
-          value={time(state.gap)}
+          label={language.t("debugBar.frame.label")}
+          tip={language.t("debugBar.frame.tip")}
+          value={time(state.gap) ?? na()}
           bad={bad(state.gap, 50)}
           bad={bad(state.gap, 50)}
           dim={state.gap === undefined}
           dim={state.gap === undefined}
         />
         />
         <Cell
         <Cell
-          label="JANK"
-          tip="Frames over 32ms in the last 5 seconds."
-          value={state.jank === undefined ? "n/a" : `${state.jank}`}
+          label={language.t("debugBar.jank.label")}
+          tip={language.t("debugBar.jank.tip")}
+          value={state.jank === undefined ? na() : `${state.jank}`}
           bad={bad(state.jank, 8)}
           bad={bad(state.jank, 8)}
           dim={state.jank === undefined}
           dim={state.jank === undefined}
         />
         />
         <Cell
         <Cell
-          label="LONG"
-          tip={`Blocked time and long-task count in the last 5 seconds. Max task: ${ms(state.long.max)}.`}
+          label={language.t("debugBar.long.label")}
+          tip={language.t("debugBar.long.tip", { max: ms(state.long.max) ?? na() })}
           value={longv()}
           value={longv()}
           bad={bad(state.long.block, 200)}
           bad={bad(state.long.block, 200)}
           dim={state.long.count === undefined}
           dim={state.long.count === undefined}
         />
         />
         <Cell
         <Cell
-          label="DELAY"
-          tip="Worst observed input delay in the last 5 seconds."
-          value={time(state.delay)}
+          label={language.t("debugBar.delay.label")}
+          tip={language.t("debugBar.delay.tip")}
+          value={time(state.delay) ?? na()}
           bad={bad(state.delay, 100)}
           bad={bad(state.delay, 100)}
           dim={state.delay === undefined}
           dim={state.delay === undefined}
         />
         />
         <Cell
         <Cell
-          label="INP"
-          tip="Approximate interaction duration over the last 5 seconds. This is INP-like, not the official Web Vitals INP."
-          value={time(state.inp)}
+          label={language.t("debugBar.inp.label")}
+          tip={language.t("debugBar.inp.tip")}
+          value={time(state.inp) ?? na()}
           bad={bad(state.inp, 200)}
           bad={bad(state.inp, 200)}
           dim={state.inp === undefined}
           dim={state.inp === undefined}
         />
         />
         <Cell
         <Cell
-          label="CLS"
-          tip="Cumulative layout shift for the current app lifetime."
-          value={state.cls === undefined ? "n/a" : state.cls.toFixed(2)}
+          label={language.t("debugBar.cls.label")}
+          tip={language.t("debugBar.cls.tip")}
+          value={state.cls === undefined ? na() : state.cls.toFixed(2)}
           bad={bad(state.cls, 0.1)}
           bad={bad(state.cls, 0.1)}
           dim={state.cls === undefined}
           dim={state.cls === undefined}
         />
         />
         <Cell
         <Cell
-          label="MEM"
+          label={language.t("debugBar.mem.label")}
           tip={
           tip={
             state.heap.used === undefined
             state.heap.used === undefined
-              ? "Used JS heap vs heap limit. Chromium only."
-              : `Used JS heap vs heap limit. ${mb(state.heap.used)} of ${mb(state.heap.limit)}.`
+              ? language.t("debugBar.mem.tipUnavailable")
+              : language.t("debugBar.mem.tip", {
+                  used: mb(state.heap.used) ?? na(),
+                  limit: mb(state.heap.limit) ?? na(),
+                })
           }
           }
           value={heapv()}
           value={heapv()}
           bad={bad(heap(), 0.8)}
           bad={bad(heap(), 0.8)}

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

@@ -426,7 +426,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
                   </Show>
                   </Show>
                 </div>
                 </div>
                 <Show when={item.keybind}>
                 <Show when={item.keybind}>
-                  <Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "")}</Keybind>
+                  <Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", language.t)}</Keybind>
                 </Show>
                 </Show>
               </div>
               </div>
             </Match>
             </Match>

+ 2 - 2
packages/app/src/components/dialog-select-server.tsx

@@ -149,7 +149,7 @@ function ServerForm(props: ServerFormProps) {
           <TextField
           <TextField
             type="text"
             type="text"
             label={language.t("dialog.server.add.username")}
             label={language.t("dialog.server.add.username")}
-            placeholder="username"
+            placeholder={language.t("dialog.server.add.usernamePlaceholder")}
             value={props.username}
             value={props.username}
             disabled={props.busy}
             disabled={props.busy}
             onChange={props.onUsernameChange}
             onChange={props.onUsernameChange}
@@ -158,7 +158,7 @@ function ServerForm(props: ServerFormProps) {
           <TextField
           <TextField
             type="password"
             type="password"
             label={language.t("dialog.server.add.password")}
             label={language.t("dialog.server.add.password")}
-            placeholder="password"
+            placeholder={language.t("dialog.server.add.passwordPlaceholder")}
             value={props.password}
             value={props.password}
             disabled={props.busy}
             disabled={props.busy}
             onChange={props.onPasswordChange}
             onChange={props.onPasswordChange}

+ 3 - 1
packages/app/src/components/server/server-row.tsx

@@ -10,6 +10,7 @@ import {
   type ParentProps,
   type ParentProps,
   Show,
   Show,
 } from "solid-js"
 } from "solid-js"
+import { useLanguage } from "@/context/language"
 import { type ServerConnection, serverName } from "@/context/server"
 import { type ServerConnection, serverName } from "@/context/server"
 import type { ServerHealth } from "@/utils/server-health"
 import type { ServerHealth } from "@/utils/server-health"
 
 
@@ -25,6 +26,7 @@ interface ServerRowProps extends ParentProps {
 }
 }
 
 
 export function ServerRow(props: ServerRowProps) {
 export function ServerRow(props: ServerRowProps) {
+  const language = useLanguage()
   const [truncated, setTruncated] = createSignal(false)
   const [truncated, setTruncated] = createSignal(false)
   let nameRef: HTMLSpanElement | undefined
   let nameRef: HTMLSpanElement | undefined
   let versionRef: HTMLSpanElement | undefined
   let versionRef: HTMLSpanElement | undefined
@@ -100,7 +102,7 @@ export function ServerRow(props: ServerRowProps) {
                   {conn().http.username ? (
                   {conn().http.username ? (
                     <span class="text-text-weak">{conn().http.username}</span>
                     <span class="text-text-weak">{conn().http.username}</span>
                   ) : (
                   ) : (
-                    <span class="text-text-weaker">no username</span>
+                    <span class="text-text-weaker">{language.t("server.row.noUsername")}</span>
                   )}
                   )}
                 </span>
                 </span>
                 {conn().http.password && <span class="text-text-weak">••••••••</span>}
                 {conn().http.password && <span class="text-text-weak">••••••••</span>}

+ 28 - 26
packages/app/src/components/session/session-header.tsx

@@ -46,63 +46,63 @@ type OS = "macos" | "windows" | "linux" | "unknown"
 const MAC_APPS = [
 const MAC_APPS = [
   {
   {
     id: "vscode",
     id: "vscode",
-    label: "VS Code",
+    label: "session.header.open.app.vscode",
     icon: "vscode",
     icon: "vscode",
     openWith: "Visual Studio Code",
     openWith: "Visual Studio Code",
   },
   },
-  { id: "cursor", label: "Cursor", icon: "cursor", openWith: "Cursor" },
-  { id: "zed", label: "Zed", icon: "zed", openWith: "Zed" },
-  { id: "textmate", label: "TextMate", icon: "textmate", openWith: "TextMate" },
+  { id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "Cursor" },
+  { id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "Zed" },
+  { id: "textmate", label: "session.header.open.app.textmate", icon: "textmate", openWith: "TextMate" },
   {
   {
     id: "antigravity",
     id: "antigravity",
-    label: "Antigravity",
+    label: "session.header.open.app.antigravity",
     icon: "antigravity",
     icon: "antigravity",
     openWith: "Antigravity",
     openWith: "Antigravity",
   },
   },
-  { id: "terminal", label: "Terminal", icon: "terminal", openWith: "Terminal" },
-  { id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" },
-  { id: "ghostty", label: "Ghostty", icon: "ghostty", openWith: "Ghostty" },
-  { id: "warp", label: "Warp", icon: "warp", openWith: "Warp" },
-  { id: "xcode", label: "Xcode", icon: "xcode", openWith: "Xcode" },
+  { id: "terminal", label: "session.header.open.app.terminal", icon: "terminal", openWith: "Terminal" },
+  { id: "iterm2", label: "session.header.open.app.iterm2", icon: "iterm2", openWith: "iTerm" },
+  { id: "ghostty", label: "session.header.open.app.ghostty", icon: "ghostty", openWith: "Ghostty" },
+  { id: "warp", label: "session.header.open.app.warp", icon: "warp", openWith: "Warp" },
+  { id: "xcode", label: "session.header.open.app.xcode", icon: "xcode", openWith: "Xcode" },
   {
   {
     id: "android-studio",
     id: "android-studio",
-    label: "Android Studio",
+    label: "session.header.open.app.androidStudio",
     icon: "android-studio",
     icon: "android-studio",
     openWith: "Android Studio",
     openWith: "Android Studio",
   },
   },
   {
   {
     id: "sublime-text",
     id: "sublime-text",
-    label: "Sublime Text",
+    label: "session.header.open.app.sublimeText",
     icon: "sublime-text",
     icon: "sublime-text",
     openWith: "Sublime Text",
     openWith: "Sublime Text",
   },
   },
 ] as const
 ] as const
 
 
 const WINDOWS_APPS = [
 const WINDOWS_APPS = [
-  { id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
-  { id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
-  { id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
+  { id: "vscode", label: "session.header.open.app.vscode", icon: "vscode", openWith: "code" },
+  { id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "cursor" },
+  { id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "zed" },
   {
   {
     id: "powershell",
     id: "powershell",
-    label: "PowerShell",
+    label: "session.header.open.app.powershell",
     icon: "powershell",
     icon: "powershell",
     openWith: "powershell",
     openWith: "powershell",
   },
   },
   {
   {
     id: "sublime-text",
     id: "sublime-text",
-    label: "Sublime Text",
+    label: "session.header.open.app.sublimeText",
     icon: "sublime-text",
     icon: "sublime-text",
     openWith: "Sublime Text",
     openWith: "Sublime Text",
   },
   },
 ] as const
 ] as const
 
 
 const LINUX_APPS = [
 const LINUX_APPS = [
-  { id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
-  { id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
-  { id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
+  { id: "vscode", label: "session.header.open.app.vscode", icon: "vscode", openWith: "code" },
+  { id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "cursor" },
+  { id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "zed" },
   {
   {
     id: "sublime-text",
     id: "sublime-text",
-    label: "Sublime Text",
+    label: "session.header.open.app.sublimeText",
     icon: "sublime-text",
     icon: "sublime-text",
     openWith: "Sublime Text",
     openWith: "Sublime Text",
   },
   },
@@ -160,9 +160,9 @@ export function SessionHeader() {
   })
   })
 
 
   const fileManager = createMemo(() => {
   const fileManager = createMemo(() => {
-    if (os() === "macos") return { label: "Finder", icon: "finder" as const }
-    if (os() === "windows") return { label: "File Explorer", icon: "file-explorer" as const }
-    return { label: "File Manager", icon: "finder" as const }
+    if (os() === "macos") return { label: "session.header.open.finder", icon: "finder" as const }
+    if (os() === "windows") return { label: "session.header.open.fileExplorer", icon: "file-explorer" as const }
+    return { label: "session.header.open.fileManager", icon: "finder" as const }
   })
   })
 
 
   createEffect(() => {
   createEffect(() => {
@@ -187,8 +187,10 @@ export function SessionHeader() {
 
 
   const options = createMemo(() => {
   const options = createMemo(() => {
     return [
     return [
-      { id: "finder", label: fileManager().label, icon: fileManager().icon },
-      ...apps().filter((app) => exists[app.id]),
+      { id: "finder", label: language.t(fileManager().label), icon: fileManager().icon },
+      ...apps()
+        .filter((app) => exists[app.id])
+        .map((app) => ({ ...app, label: language.t(app.label) })),
     ] as const
     ] as const
   })
   })
 
 

+ 2 - 5
packages/app/src/components/session/session-sortable-terminal-tab.tsx

@@ -6,6 +6,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
 import { Tabs } from "@opencode-ai/ui/tabs"
 import { Tabs } from "@opencode-ai/ui/tabs"
 import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
 import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
 import { Icon } from "@opencode-ai/ui/icon"
 import { Icon } from "@opencode-ai/ui/icon"
+import { isDefaultTitle as isDefaultTerminalTitle } from "@/context/terminal-title"
 import { useTerminal, type LocalPTY } from "@/context/terminal"
 import { useTerminal, type LocalPTY } from "@/context/terminal"
 import { useLanguage } from "@/context/language"
 import { useLanguage } from "@/context/language"
 import { focusTerminalById } from "@/pages/session/helpers"
 import { focusTerminalById } from "@/pages/session/helpers"
@@ -27,11 +28,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
   const isDefaultTitle = () => {
   const isDefaultTitle = () => {
     const number = props.terminal.titleNumber
     const number = props.terminal.titleNumber
     if (!Number.isFinite(number) || number <= 0) return false
     if (!Number.isFinite(number) || number <= 0) return false
-    const match = props.terminal.title.match(/^Terminal (\d+)$/)
-    if (!match) return false
-    const parsed = Number(match[1])
-    if (!Number.isFinite(parsed) || parsed <= 0) return false
-    return parsed === number
+    return isDefaultTerminalTitle(props.terminal.title, number)
   }
   }
 
 
   const label = () => {
   const label = () => {

+ 1 - 1
packages/app/src/components/settings-keybinds.tsx

@@ -239,7 +239,7 @@ function useKeyCapture(input: {
         showToast({
         showToast({
           title: input.language.t("settings.shortcuts.conflict.title"),
           title: input.language.t("settings.shortcuts.conflict.title"),
           description: input.language.t("settings.shortcuts.conflict.description", {
           description: input.language.t("settings.shortcuts.conflict.description", {
-            keybind: formatKeybind(next),
+            keybind: formatKeybind(next, input.language.t),
             titles: [...conflicts.values()].join(", "),
             titles: [...conflicts.values()].join(", "),
           }),
           }),
         })
         })

+ 1 - 1
packages/app/src/components/terminal.tsx

@@ -519,7 +519,7 @@ export const Terminal = (props: TerminalProps) => {
         if (event.code !== 1000) {
         if (event.code !== 1000) {
           if (once.value) return
           if (once.value) return
           once.value = true
           once.value = true
-          local.onConnectError?.(new Error(`WebSocket closed abnormally: ${event.code}`))
+          local.onConnectError?.(new Error(language.t("terminal.connectionLost.abnormalClose", { code: event.code })))
         }
         }
       }
       }
       socket.addEventListener("close", handleClose)
       socket.addEventListener("close", handleClose)

+ 51 - 10
packages/app/src/context/command.tsx

@@ -2,6 +2,7 @@ import { createEffect, createMemo, onCleanup, onMount, type Accessor } from "sol
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
 import { createSimpleContext } from "@opencode-ai/ui/context"
 import { createSimpleContext } from "@opencode-ai/ui/context"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { dict as en } from "@/i18n/en"
 import { useLanguage } from "@/context/language"
 import { useLanguage } from "@/context/language"
 import { useSettings } from "@/context/settings"
 import { useSettings } from "@/context/settings"
 import { Persist, persisted } from "@/utils/persist"
 import { Persist, persisted } from "@/utils/persist"
@@ -13,6 +14,27 @@ const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"
 const SUGGESTED_PREFIX = "suggested."
 const SUGGESTED_PREFIX = "suggested."
 const EDITABLE_KEYBIND_IDS = new Set(["terminal.toggle", "terminal.new", "file.attach"])
 const EDITABLE_KEYBIND_IDS = new Set(["terminal.toggle", "terminal.new", "file.attach"])
 
 
+type KeyLabel =
+  | "common.key.ctrl"
+  | "common.key.alt"
+  | "common.key.shift"
+  | "common.key.meta"
+  | "common.key.space"
+  | "common.key.backspace"
+  | "common.key.enter"
+  | "common.key.tab"
+  | "common.key.delete"
+  | "common.key.home"
+  | "common.key.end"
+  | "common.key.pageUp"
+  | "common.key.pageDown"
+  | "common.key.insert"
+  | "common.key.esc"
+
+function keyText(key: KeyLabel, t?: (key: KeyLabel) => string) {
+  return t ? t(key) : en[key]
+}
+
 function actionId(id: string) {
 function actionId(id: string) {
   if (!id.startsWith(SUGGESTED_PREFIX)) return id
   if (!id.startsWith(SUGGESTED_PREFIX)) return id
   return id.slice(SUGGESTED_PREFIX.length)
   return id.slice(SUGGESTED_PREFIX.length)
@@ -145,7 +167,7 @@ export function matchKeybind(keybinds: Keybind[], event: KeyboardEvent): boolean
   return false
   return false
 }
 }
 
 
-export function formatKeybind(config: string): string {
+export function formatKeybind(config: string, t?: (key: KeyLabel) => string): string {
   if (!config || config === "none") return ""
   if (!config || config === "none") return ""
 
 
   const keybinds = parseKeybind(config)
   const keybinds = parseKeybind(config)
@@ -154,10 +176,10 @@ export function formatKeybind(config: string): string {
   const kb = keybinds[0]
   const kb = keybinds[0]
   const parts: string[] = []
   const parts: string[] = []
 
 
-  if (kb.ctrl) parts.push(IS_MAC ? "⌃" : "Ctrl")
-  if (kb.alt) parts.push(IS_MAC ? "⌥" : "Alt")
-  if (kb.shift) parts.push(IS_MAC ? "⇧" : "Shift")
-  if (kb.meta) parts.push(IS_MAC ? "⌘" : "Meta")
+  if (kb.ctrl) parts.push(IS_MAC ? "⌃" : keyText("common.key.ctrl", t))
+  if (kb.alt) parts.push(IS_MAC ? "⌥" : keyText("common.key.alt", t))
+  if (kb.shift) parts.push(IS_MAC ? "⇧" : keyText("common.key.shift", t))
+  if (kb.meta) parts.push(IS_MAC ? "⌘" : keyText("common.key.meta", t))
 
 
   if (kb.key) {
   if (kb.key) {
     const keys: Record<string, string> = {
     const keys: Record<string, string> = {
@@ -167,10 +189,29 @@ export function formatKeybind(config: string): string {
       arrowright: "→",
       arrowright: "→",
       comma: ",",
       comma: ",",
       plus: "+",
       plus: "+",
-      space: "Space",
+    }
+    const named: Record<string, KeyLabel> = {
+      backspace: "common.key.backspace",
+      delete: "common.key.delete",
+      end: "common.key.end",
+      enter: "common.key.enter",
+      esc: "common.key.esc",
+      escape: "common.key.esc",
+      home: "common.key.home",
+      insert: "common.key.insert",
+      pagedown: "common.key.pageDown",
+      pageup: "common.key.pageUp",
+      space: "common.key.space",
+      tab: "common.key.tab",
     }
     }
     const key = kb.key.toLowerCase()
     const key = kb.key.toLowerCase()
-    const displayKey = keys[key] ?? (key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1))
+    const displayKey =
+      keys[key] ??
+      (named[key]
+        ? keyText(named[key], t)
+        : key.length === 1
+          ? key.toUpperCase()
+          : key.charAt(0).toUpperCase() + key.slice(1))
     parts.push(displayKey)
     parts.push(displayKey)
   }
   }
 
 
@@ -364,17 +405,17 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
       },
       },
       keybind(id: string) {
       keybind(id: string) {
         if (id === PALETTE_ID) {
         if (id === PALETTE_ID) {
-          return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND)
+          return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND, language.t)
         }
         }
 
 
         const base = actionId(id)
         const base = actionId(id)
         const option = options().find((x) => actionId(x.id) === base)
         const option = options().find((x) => actionId(x.id) === base)
-        if (option?.keybind) return formatKeybind(option.keybind)
+        if (option?.keybind) return formatKeybind(option.keybind, language.t)
 
 
         const meta = catalog[base]
         const meta = catalog[base]
         const config = bind(base, meta?.keybind)
         const config = bind(base, meta?.keybind)
         if (!config) return ""
         if (!config) return ""
-        return formatKeybind(config)
+        return formatKeybind(config, language.t)
       },
       },
       show: showPalette,
       show: showPalette,
       keybinds(enabled: boolean) {
       keybinds(enabled: boolean) {

+ 3 - 3
packages/app/src/context/file.tsx

@@ -43,10 +43,10 @@ export {
   touchFileContent,
   touchFileContent,
 }
 }
 
 
-function errorMessage(error: unknown) {
+function errorMessage(error: unknown, fallback: string) {
   if (error instanceof Error && error.message) return error.message
   if (error instanceof Error && error.message) return error.message
   if (typeof error === "string" && error) return error
   if (typeof error === "string" && error) return error
-  return "Unknown error"
+  return fallback
 }
 }
 
 
 export const { use: useFile, provider: FileProvider } = createSimpleContext({
 export const { use: useFile, provider: FileProvider } = createSimpleContext({
@@ -184,7 +184,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
         })
         })
         .catch((e) => {
         .catch((e) => {
           if (scope() !== directory) return
           if (scope() !== directory) return
-          setLoadError(file, errorMessage(e))
+          setLoadError(file, errorMessage(e, language.t("error.chain.unknown")))
         })
         })
         .finally(() => {
         .finally(() => {
           inflight.delete(key)
           inflight.delete(key)

+ 4 - 2
packages/app/src/context/global-sdk.tsx

@@ -4,6 +4,7 @@ import { createGlobalEmitter } from "@solid-primitives/event-bus"
 import { batch, onCleanup } from "solid-js"
 import { batch, onCleanup } from "solid-js"
 import z from "zod"
 import z from "zod"
 import { createSdkForServer } from "@/utils/server"
 import { createSdkForServer } from "@/utils/server"
+import { useLanguage } from "./language"
 import { usePlatform } from "./platform"
 import { usePlatform } from "./platform"
 import { useServer } from "./server"
 import { useServer } from "./server"
 
 
@@ -14,6 +15,7 @@ const abortError = z.object({
 export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({
 export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({
   name: "GlobalSDK",
   name: "GlobalSDK",
   init: () => {
   init: () => {
+    const language = useLanguage()
     const server = useServer()
     const server = useServer()
     const platform = usePlatform()
     const platform = usePlatform()
     const abort = new AbortController()
     const abort = new AbortController()
@@ -30,7 +32,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
     })()
     })()
 
 
     const currentServer = server.current
     const currentServer = server.current
-    if (!currentServer) throw new Error("No server available")
+    if (!currentServer) throw new Error(language.t("error.globalSDK.noServerAvailable"))
 
 
     const eventSdk = createSdkForServer({
     const eventSdk = createSdkForServer({
       signal: abort.signal,
       signal: abort.signal,
@@ -218,7 +220,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
       event: emitter,
       event: emitter,
       createClient(opts: Omit<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) {
       createClient(opts: Omit<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) {
         const s = server.current
         const s = server.current
-        if (!s) throw new Error("Server not available")
+        if (!s) throw new Error(language.t("error.globalSDK.serverNotAvailable"))
         return createSdkForServer({
         return createSdkForServer({
           server: s.http,
           server: s.http,
           fetch: platform.fetch,
           fetch: platform.fetch,

+ 1 - 0
packages/app/src/context/global-sync.tsx

@@ -164,6 +164,7 @@ function createGlobalSync() {
       sdkCache.delete(directory)
       sdkCache.delete(directory)
       clearSessionPrefetchDirectory(directory)
       clearSessionPrefetchDirectory(directory)
     },
     },
+    translate: language.t,
   })
   })
 
 
   const sdkFor = (directory: string) => {
   const sdkFor = (directory: string) => {

+ 1 - 1
packages/app/src/context/global-sync/bootstrap.ts

@@ -139,7 +139,7 @@ export async function bootstrapDirectory(input: {
     const project = getFilename(input.directory)
     const project = getFilename(input.directory)
     showToast({
     showToast({
       variant: "error",
       variant: "error",
-      title: `Failed to reload ${project}`,
+      title: input.translate("toast.project.reloadFailed.title", { project }),
       description: formatServerError(err, input.translate),
       description: formatServerError(err, input.translate),
     })
     })
     input.setStore("status", "partial")
     input.setStore("status", "partial")

+ 1 - 0
packages/app/src/context/global-sync/child-store.test.ts

@@ -21,6 +21,7 @@ describe("createChildStoreManager", () => {
       isLoadingSessions: () => false,
       isLoadingSessions: () => false,
       onBootstrap() {},
       onBootstrap() {},
       onDispose() {},
       onDispose() {},
+      translate: (key) => key,
     })
     })
 
 
     Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => {
     Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => {

+ 5 - 4
packages/app/src/context/global-sync/child-store.ts

@@ -21,6 +21,7 @@ export function createChildStoreManager(input: {
   isLoadingSessions: (directory: string) => boolean
   isLoadingSessions: (directory: string) => boolean
   onBootstrap: (directory: string) => void
   onBootstrap: (directory: string) => void
   onDispose: (directory: string) => void
   onDispose: (directory: string) => void
+  translate: (key: string, vars?: Record<string, string | number>) => string
 }) {
 }) {
   const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
   const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
   const vcsCache = new Map<string, VcsCache>()
   const vcsCache = new Map<string, VcsCache>()
@@ -129,7 +130,7 @@ export function createChildStoreManager(input: {
           createStore({ value: undefined as VcsInfo | undefined }),
           createStore({ value: undefined as VcsInfo | undefined }),
         ),
         ),
       )
       )
-      if (!vcs) throw new Error("Failed to create persisted cache")
+      if (!vcs) throw new Error(input.translate("error.childStore.persistedCacheCreateFailed"))
       const vcsStore = vcs[0]
       const vcsStore = vcs[0]
       vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcs[3] })
       vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcs[3] })
 
 
@@ -139,7 +140,7 @@ export function createChildStoreManager(input: {
           createStore({ value: undefined as ProjectMeta | undefined }),
           createStore({ value: undefined as ProjectMeta | undefined }),
         ),
         ),
       )
       )
-      if (!meta) throw new Error("Failed to create persisted project metadata")
+      if (!meta) throw new Error(input.translate("error.childStore.persistedProjectMetadataCreateFailed"))
       metaCache.set(directory, { store: meta[0], setStore: meta[1], ready: meta[3] })
       metaCache.set(directory, { store: meta[0], setStore: meta[1], ready: meta[3] })
 
 
       const icon = runWithOwner(input.owner, () =>
       const icon = runWithOwner(input.owner, () =>
@@ -148,7 +149,7 @@ export function createChildStoreManager(input: {
           createStore({ value: undefined as string | undefined }),
           createStore({ value: undefined as string | undefined }),
         ),
         ),
       )
       )
-      if (!icon) throw new Error("Failed to create persisted project icon")
+      if (!icon) throw new Error(input.translate("error.childStore.persistedProjectIconCreateFailed"))
       iconCache.set(directory, { store: icon[0], setStore: icon[1], ready: icon[3] })
       iconCache.set(directory, { store: icon[0], setStore: icon[1], ready: icon[3] })
 
 
       const init = () =>
       const init = () =>
@@ -211,7 +212,7 @@ export function createChildStoreManager(input: {
     }
     }
     mark(directory)
     mark(directory)
     const childStore = children[directory]
     const childStore = children[directory]
-    if (!childStore) throw new Error("Failed to create store")
+    if (!childStore) throw new Error(input.translate("error.childStore.storeCreateFailed"))
     return childStore
     return childStore
   }
   }
 
 

+ 51 - 0
packages/app/src/context/terminal-title.ts

@@ -0,0 +1,51 @@
+import { dict as ar } from "@/i18n/ar"
+import { dict as br } from "@/i18n/br"
+import { dict as bs } from "@/i18n/bs"
+import { dict as da } from "@/i18n/da"
+import { dict as de } from "@/i18n/de"
+import { dict as en } from "@/i18n/en"
+import { dict as es } from "@/i18n/es"
+import { dict as fr } from "@/i18n/fr"
+import { dict as ja } from "@/i18n/ja"
+import { dict as ko } from "@/i18n/ko"
+import { dict as no } from "@/i18n/no"
+import { dict as pl } from "@/i18n/pl"
+import { dict as ru } from "@/i18n/ru"
+import { dict as th } from "@/i18n/th"
+import { dict as tr } from "@/i18n/tr"
+import { dict as zh } from "@/i18n/zh"
+import { dict as zht } from "@/i18n/zht"
+
+const numbered = Array.from(
+  new Set([
+    en["terminal.title.numbered"],
+    ar["terminal.title.numbered"],
+    br["terminal.title.numbered"],
+    bs["terminal.title.numbered"],
+    da["terminal.title.numbered"],
+    de["terminal.title.numbered"],
+    es["terminal.title.numbered"],
+    fr["terminal.title.numbered"],
+    ja["terminal.title.numbered"],
+    ko["terminal.title.numbered"],
+    no["terminal.title.numbered"],
+    pl["terminal.title.numbered"],
+    ru["terminal.title.numbered"],
+    th["terminal.title.numbered"],
+    tr["terminal.title.numbered"],
+    zh["terminal.title.numbered"],
+    zht["terminal.title.numbered"],
+  ]),
+)
+
+export function defaultTitle(number: number) {
+  return en["terminal.title.numbered"].replace("{{number}}", String(number))
+}
+
+export function isDefaultTitle(title: string, number: number) {
+  return numbered.some((text) => title === text.replace("{{number}}", String(number)))
+}
+
+export function titleNumber(title: string, max: number) {
+  return Array.from({ length: max }, (_, idx) => idx + 1).find((number) => isDefaultTitle(title, number))
+}

+ 4 - 7
packages/app/src/context/terminal.tsx

@@ -4,6 +4,7 @@ import { batch, createEffect, createMemo, createRoot, on, onCleanup } from "soli
 import { useParams } from "@solidjs/router"
 import { useParams } from "@solidjs/router"
 import { useSDK } from "./sdk"
 import { useSDK } from "./sdk"
 import type { Platform } from "./platform"
 import type { Platform } from "./platform"
+import { defaultTitle, titleNumber } from "./terminal-title"
 import { Persist, persisted, removePersisted } from "@/utils/persist"
 import { Persist, persisted, removePersisted } from "@/utils/persist"
 
 
 export type LocalPTY = {
 export type LocalPTY = {
@@ -33,11 +34,7 @@ function num(value: unknown) {
 }
 }
 
 
 function numberFromTitle(title: string) {
 function numberFromTitle(title: string) {
-  const match = title.match(/^Terminal (\d+)$/)
-  if (!match) return
-  const value = Number(match[1])
-  if (!Number.isFinite(value) || value <= 0) return
-  return value
+  return titleNumber(title, MAX_TERMINAL_SESSIONS)
 }
 }
 
 
 function pty(value: unknown): LocalPTY | undefined {
 function pty(value: unknown): LocalPTY | undefined {
@@ -202,13 +199,13 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
       const nextNumber = pickNextTerminalNumber()
       const nextNumber = pickNextTerminalNumber()
 
 
       sdk.client.pty
       sdk.client.pty
-        .create({ title: `Terminal ${nextNumber}` })
+        .create({ title: defaultTitle(nextNumber) })
         .then((pty: { data?: { id?: string; title?: string } }) => {
         .then((pty: { data?: { id?: string; title?: string } }) => {
           const id = pty.data?.id
           const id = pty.data?.id
           if (!id) return
           if (!id) return
           const newTerminal = {
           const newTerminal = {
             id,
             id,
-            title: pty.data?.title ?? "Terminal",
+            title: pty.data?.title ?? defaultTitle(nextNumber),
             titleNumber: nextNumber,
             titleNumber: nextNumber,
           }
           }
           setStore("all", store.all.length, newTerminal)
           setStore("all", store.all.length, newTerminal)

+ 73 - 0
packages/app/src/i18n/ar.ts

@@ -778,4 +778,77 @@ export const dict = {
   "common.time.daysAgo.short": "قبل {{count}} ي",
   "common.time.daysAgo.short": "قبل {{count}} ي",
   "settings.providers.connected.environmentDescription": "متصل من متغيرات البيئة الخاصة بك",
   "settings.providers.connected.environmentDescription": "متصل من متغيرات البيئة الخاصة بك",
   "settings.providers.custom.description": "أضف مزود متوافق مع OpenAI بواسطة عنوان URL الأساسي.",
   "settings.providers.custom.description": "أضف مزود متوافق مع OpenAI بواسطة عنوان URL الأساسي.",
+
+  "app.server.unreachable": "تعذر الوصول إلى {{server}}",
+  "app.server.retrying": "جاري إعادة المحاولة تلقائيًا...",
+  "app.server.otherServers": "خوادم أخرى",
+  "dialog.server.add.usernamePlaceholder": "اسم المستخدم",
+  "dialog.server.add.passwordPlaceholder": "كلمة المرور",
+  "server.row.noUsername": "لا يوجد اسم مستخدم",
+  "session.review.noVcs.createGit.title": "إنشاء مستودع Git",
+  "session.review.noVcs.createGit.description": "تتبع ومراجعة والتراجع عن التغييرات في هذا المشروع",
+  "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",
+  "session.header.open.fileExplorer": "مستكشف الملفات",
+  "session.header.open.fileManager": "مدير الملفات",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "المحطة الطرفية",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "تشخيص أداء التطوير",
+  "debugBar.na": "غير متاح",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip": "آخر انتقال مكتمل للمسار يمس صفحة جلسة، مُقاسًا من بدء التوجيه حتى أول رسم بعد استقراره.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "الإطارات المتجددة في الثانية خلال آخر 5 ثوانٍ.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "أسوأ وقت للإطار خلال آخر 5 ثوانٍ.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "الإطارات التي تزيد عن 32 مللي ثانية في آخر 5 ثوانٍ.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "الوقت المحظور وعدد المهام الطويلة في آخر 5 ثوانٍ. أقصى مهمة: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "أسوأ تأخير إدخال تمت ملاحظته في آخر 5 ثوانٍ.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip": "مدة التفاعل التقريبية خلال آخر 5 ثوانٍ. هذا يشبه INP، وليس Web Vitals INP الرسمي.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "التحول التخطيطي التراكمي لعمر التطبيق الحالي.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "كومة JS المستخدمة مقابل حد الكومة. Chromium فقط.",
+  "debugBar.mem.tip": "كومة JS المستخدمة مقابل حد الكومة. {{used}} من {{limit}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Space",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "غير معروف",
+  "error.page.circular": "[دائري]",
+  "error.globalSDK.noServerAvailable": "لا يوجد خادم متاح",
+  "error.globalSDK.serverNotAvailable": "الخادم غير متاح",
+  "error.childStore.persistedCacheCreateFailed": "فشل إنشاء ذاكرة التخزين المؤقت الدائمة",
+  "error.childStore.persistedProjectMetadataCreateFailed": "فشل إنشاء بيانات تعريف المشروع الدائمة",
+  "error.childStore.persistedProjectIconCreateFailed": "فشل إنشاء أيقونة المشروع الدائمة",
+  "error.childStore.storeCreateFailed": "فشل إنشاء المخزن",
+  "terminal.connectionLost.abnormalClose": "تم إغلاق WebSocket بشكل غير طبيعي: {{code}}",
 }
 }

+ 75 - 0
packages/app/src/i18n/br.ts

@@ -788,4 +788,79 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}}d atrás",
   "common.time.daysAgo.short": "{{count}}d atrás",
   "settings.providers.connected.environmentDescription": "Conectado a partir de suas variáveis de ambiente",
   "settings.providers.connected.environmentDescription": "Conectado a partir de suas variáveis de ambiente",
   "settings.providers.custom.description": "Adicionar um provedor compatível com a OpenAI através do URL base.",
   "settings.providers.custom.description": "Adicionar um provedor compatível com a OpenAI através do URL base.",
+
+  "app.server.unreachable": "Não foi possível conectar a {{server}}",
+  "app.server.retrying": "Tentando novamente automaticamente...",
+  "app.server.otherServers": "Outros servidores",
+  "dialog.server.add.usernamePlaceholder": "nome de usuário",
+  "dialog.server.add.passwordPlaceholder": "senha",
+  "server.row.noUsername": "sem nome de usuário",
+  "session.review.noVcs.createGit.title": "Criar um repositório Git",
+  "session.review.noVcs.createGit.description": "Rastreie, revise e desfaça alterações neste projeto",
+  "session.review.noVcs.createGit.actionLoading": "Criando repositório Git...",
+  "session.review.noVcs.createGit.action": "Criar repositório Git",
+  "session.todo.progress": "{{done}} de {{total}} tarefas concluídas",
+  "session.question.progress": "{{current}} de {{total}} perguntas",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "Explorador de Arquivos",
+  "session.header.open.fileManager": "Gerenciador de Arquivos",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Diagnóstico de desempenho de desenvolvimento",
+  "debugBar.na": "n/a",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Última transição de rota concluída tocando em uma página de sessão, medida desde o início do roteador até a primeira pintura após o estabelecimento.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Quadros por segundo nos últimos 5 segundos.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Pior tempo de quadro nos últimos 5 segundos.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Quadros acima de 32ms nos últimos 5 segundos.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "Tempo bloqueado e contagem de tarefas longas nos últimos 5 segundos. Tarefa máx: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Pior atraso de entrada observado nos últimos 5 segundos.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Duração aproximada da interação nos últimos 5 segundos. Isso é semelhante ao INP, não o INP oficial do Web Vitals.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Mudança cumulativa de layout para o tempo de vida atual do aplicativo.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Heap JS usado vs limite de heap. Apenas Chromium.",
+  "debugBar.mem.tip": "Heap JS usado vs limite de heap. {{used}} de {{limit}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Espaço",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "desconhecido",
+  "error.page.circular": "[Circular]",
+  "error.globalSDK.noServerAvailable": "Nenhum servidor disponível",
+  "error.globalSDK.serverNotAvailable": "Servidor indisponível",
+  "error.childStore.persistedCacheCreateFailed": "Falha ao criar cache persistente",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Falha ao criar metadados de projeto persistentes",
+  "error.childStore.persistedProjectIconCreateFailed": "Falha ao criar ícone de projeto persistente",
+  "error.childStore.storeCreateFailed": "Falha ao criar armazenamento",
+  "terminal.connectionLost.abnormalClose": "WebSocket fechado anormalmente: {{code}}",
 }
 }

+ 75 - 0
packages/app/src/i18n/bs.ts

@@ -864,4 +864,79 @@ export const dict = {
   "common.time.daysAgo.short": "prije {{count}} d",
   "common.time.daysAgo.short": "prije {{count}} d",
   "settings.providers.connected.environmentDescription": "Povezano sa vašim varijablama okruženja",
   "settings.providers.connected.environmentDescription": "Povezano sa vašim varijablama okruženja",
   "settings.providers.custom.description": "Dodajte provajdera kompatibilnog s OpenAI putem osnovnog URL-a.",
   "settings.providers.custom.description": "Dodajte provajdera kompatibilnog s OpenAI putem osnovnog URL-a.",
+
+  "app.server.unreachable": "Nije moguće pristupiti {{server}}",
+  "app.server.retrying": "Automatski ponovni pokušaj...",
+  "app.server.otherServers": "Drugi serveri",
+  "dialog.server.add.usernamePlaceholder": "korisničko ime",
+  "dialog.server.add.passwordPlaceholder": "lozinka",
+  "server.row.noUsername": "nema korisničkog imena",
+  "session.review.noVcs.createGit.title": "Kreiraj Git repozitorij",
+  "session.review.noVcs.createGit.description": "Pratite, pregledajte i poništite promjene u ovom projektu",
+  "session.review.noVcs.createGit.actionLoading": "Kreiranje Git repozitorija...",
+  "session.review.noVcs.createGit.action": "Kreiraj Git repozitorij",
+  "session.todo.progress": "{{done}} od {{total}} zadataka završeno",
+  "session.question.progress": "{{current}} od {{total}} pitanja",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "File Explorer",
+  "session.header.open.fileManager": "File Manager",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Dijagnostika performansi razvoja",
+  "debugBar.na": "n/a",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Posljednji završeni prelazak rute koji dotiče stranicu sesije, mjeren od početka rutera do prvog iscrtavanja nakon smirivanja.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Kadrovi u sekundi tokom posljednjih 5 sekundi.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Najgore vrijeme kadra u posljednjih 5 sekundi.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Kadrovi duži od 32ms u posljednjih 5 sekundi.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "Blokirano vrijeme i broj dugih zadataka u posljednjih 5 sekundi. Maks zadatak: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Najgore zabilježeno kašnjenje unosa u posljednjih 5 sekundi.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Približno trajanje interakcije tokom posljednjih 5 sekundi. Ovo je slično INP-u, nije službeni Web Vitals INP.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Kumulativni pomak rasporeda za trenutni životni vijek aplikacije.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Korišteni JS heap naspram limita heapa. Samo Chromium.",
+  "debugBar.mem.tip": "Korišteni JS heap naspram limita heapa. {{used}} od {{limit}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Space",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "nepoznato",
+  "error.page.circular": "[Kružno]",
+  "error.globalSDK.noServerAvailable": "Nema dostupnog servera",
+  "error.globalSDK.serverNotAvailable": "Server nije dostupan",
+  "error.childStore.persistedCacheCreateFailed": "Nije uspjelo kreiranje trajnog keša",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Nije uspjelo kreiranje trajnih metapodataka projekta",
+  "error.childStore.persistedProjectIconCreateFailed": "Nije uspjelo kreiranje trajne ikone projekta",
+  "error.childStore.storeCreateFailed": "Nije uspjelo kreiranje skladišta",
+  "terminal.connectionLost.abnormalClose": "WebSocket zatvoren nenormalno: {{code}}",
 }
 }

+ 75 - 0
packages/app/src/i18n/da.ts

@@ -858,4 +858,79 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}}d siden",
   "common.time.daysAgo.short": "{{count}}d siden",
   "settings.providers.connected.environmentDescription": "Tilsluttet fra dine miljøvariabler",
   "settings.providers.connected.environmentDescription": "Tilsluttet fra dine miljøvariabler",
   "settings.providers.custom.description": "Tilføj en OpenAI-kompatibel udbyder via basis-URL.",
   "settings.providers.custom.description": "Tilføj en OpenAI-kompatibel udbyder via basis-URL.",
+
+  "app.server.unreachable": "Kunne ikke nå {{server}}",
+  "app.server.retrying": "Prøver igen automatisk...",
+  "app.server.otherServers": "Andre servere",
+  "dialog.server.add.usernamePlaceholder": "brugernavn",
+  "dialog.server.add.passwordPlaceholder": "adgangskode",
+  "server.row.noUsername": "intet brugernavn",
+  "session.review.noVcs.createGit.title": "Opret et Git-repository",
+  "session.review.noVcs.createGit.description": "Spor, gennemgå og fortryd ændringer i dette projekt",
+  "session.review.noVcs.createGit.actionLoading": "Opretter Git-repository...",
+  "session.review.noVcs.createGit.action": "Opret Git-repository",
+  "session.todo.progress": "{{done}} af {{total}} opgaver fuldført",
+  "session.question.progress": "{{current}} af {{total}} spørgsmål",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "Stifinder",
+  "session.header.open.fileManager": "Filhåndtering",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Udviklingsydelsesdiagnostik",
+  "debugBar.na": "n/a",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Sidste gennemførte ruteovergang, der berører en sessionsside, målt fra routerstart til den første optegning efter den falder til ro.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Rullende billeder pr. sekund over de sidste 5 sekunder.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Værste billedtid over de sidste 5 sekunder.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Billeder over 32ms i de sidste 5 sekunder.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "Blokeret tid og antal lange opgaver i de sidste 5 sekunder. Maks opgave: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Værste observerede inputforsinkelse i de sidste 5 sekunder.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Omtrentlig interaktionsvarighed over de sidste 5 sekunder. Dette er INP-lignende, ikke den officielle Web Vitals INP.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Kumulativt layoutskift for den nuværende app-levetid.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Brugt JS-heap vs heap-grænse. Kun Chromium.",
+  "debugBar.mem.tip": "Brugt JS-heap vs heap-grænse. {{used}} af {{limit}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Mellemrum",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "ukendt",
+  "error.page.circular": "[Cirkulær]",
+  "error.globalSDK.noServerAvailable": "Ingen server tilgængelig",
+  "error.globalSDK.serverNotAvailable": "Server ikke tilgængelig",
+  "error.childStore.persistedCacheCreateFailed": "Kunne ikke oprette vedvarende cache",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Kunne ikke oprette vedvarende projektmetadata",
+  "error.childStore.persistedProjectIconCreateFailed": "Kunne ikke oprette vedvarende projektikon",
+  "error.childStore.storeCreateFailed": "Kunne ikke oprette lager",
+  "terminal.connectionLost.abnormalClose": "WebSocket lukkede unormalt: {{code}}",
 }
 }

+ 76 - 0
packages/app/src/i18n/de.ts

@@ -799,4 +799,80 @@ export const dict = {
   "common.time.daysAgo.short": "vor {{count}} Tg",
   "common.time.daysAgo.short": "vor {{count}} Tg",
   "settings.providers.connected.environmentDescription": "Verbunden aus Ihren Umgebungsvariablen",
   "settings.providers.connected.environmentDescription": "Verbunden aus Ihren Umgebungsvariablen",
   "settings.providers.custom.description": "Fügen Sie einen OpenAI-kompatiblen Anbieter per Basis-URL hinzu.",
   "settings.providers.custom.description": "Fügen Sie einen OpenAI-kompatiblen Anbieter per Basis-URL hinzu.",
+
+  "app.server.unreachable": "Konnte {{server}} nicht erreichen",
+  "app.server.retrying": "Automatische erneute Verbindung...",
+  "app.server.otherServers": "Andere Server",
+  "dialog.server.add.usernamePlaceholder": "Benutzername",
+  "dialog.server.add.passwordPlaceholder": "Passwort",
+  "server.row.noUsername": "Kein Benutzername",
+  "session.review.noVcs.createGit.title": "Git-Repository erstellen",
+  "session.review.noVcs.createGit.description":
+    "Änderungen in diesem Projekt verfolgen, überprüfen und rückgängig machen",
+  "session.review.noVcs.createGit.actionLoading": "Git-Repository wird erstellt...",
+  "session.review.noVcs.createGit.action": "Git-Repository erstellen",
+  "session.todo.progress": "{{done}} von {{total}} Aufgaben erledigt",
+  "session.question.progress": "{{current}} von {{total}} Fragen",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "Datei-Explorer",
+  "session.header.open.fileManager": "Dateimanager",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Entwicklungs-Leistungsdiagnose",
+  "debugBar.na": "n.v.",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Letzter abgeschlossener Routenübergang, der eine Sitzungsseite berührt, gemessen vom Start des Routers bis zum ersten Rendern nach dem Einschwingen.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Gleitende Bilder pro Sekunde in den letzten 5 Sekunden.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Schlechteste Frame-Zeit in den letzten 5 Sekunden.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Frames über 32ms in den letzten 5 Sekunden.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "Blockierte Zeit und Anzahl langer Aufgaben in den letzten 5 Sekunden. Max Aufgabe: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Schlechteste beobachtete Eingabeverzögerung in den letzten 5 Sekunden.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Ungefähre Interaktionsdauer in den letzten 5 Sekunden. Dies ist INP-ähnlich, nicht das offizielle Web Vitals INP.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Kumulative Layoutverschiebung für die aktuelle App-Lebensdauer.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Verwendeter JS-Heap vs Heap-Limit. Nur Chromium.",
+  "debugBar.mem.tip": "Verwendeter JS-Heap vs Heap-Limit. {{used}} von {{limit}}.",
+  "common.key.ctrl": "Strg",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Umschalt",
+  "common.key.meta": "Meta",
+  "common.key.space": "Leertaste",
+  "common.key.backspace": "Rücktaste",
+  "common.key.enter": "Eingabe",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Entf",
+  "common.key.home": "Pos1",
+  "common.key.end": "Ende",
+  "common.key.pageUp": "Bild auf",
+  "common.key.pageDown": "Bild ab",
+  "common.key.insert": "Einfg",
+  "common.unknown": "unbekannt",
+  "error.page.circular": "[Zirkulär]",
+  "error.globalSDK.noServerAvailable": "Kein Server verfügbar",
+  "error.globalSDK.serverNotAvailable": "Server nicht verfügbar",
+  "error.childStore.persistedCacheCreateFailed": "Dauerhafter Cache konnte nicht erstellt werden",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Dauerhafte Projektmetadaten konnten nicht erstellt werden",
+  "error.childStore.persistedProjectIconCreateFailed": "Dauerhaftes Projekticon konnte nicht erstellt werden",
+  "error.childStore.storeCreateFailed": "Speicher konnte nicht erstellt werden",
+  "terminal.connectionLost.abnormalClose": "WebSocket abnormal geschlossen: {{code}}",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

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

@@ -306,6 +306,10 @@ export const dict = {
   "dialog.directory.search.placeholder": "Search folders",
   "dialog.directory.search.placeholder": "Search folders",
   "dialog.directory.empty": "No folders found",
   "dialog.directory.empty": "No folders found",
 
 
+  "app.server.unreachable": "Could not reach {{server}}",
+  "app.server.retrying": "Retrying automatically...",
+  "app.server.otherServers": "Other servers",
+
   "dialog.server.title": "Servers",
   "dialog.server.title": "Servers",
   "dialog.server.description": "Switch which OpenCode server this app connects to.",
   "dialog.server.description": "Switch which OpenCode server this app connects to.",
   "dialog.server.search.placeholder": "Search servers",
   "dialog.server.search.placeholder": "Search servers",
@@ -319,7 +323,9 @@ export const dict = {
   "dialog.server.add.name": "Server name (optional)",
   "dialog.server.add.name": "Server name (optional)",
   "dialog.server.add.namePlaceholder": "Localhost",
   "dialog.server.add.namePlaceholder": "Localhost",
   "dialog.server.add.username": "Username (optional)",
   "dialog.server.add.username": "Username (optional)",
+  "dialog.server.add.usernamePlaceholder": "username",
   "dialog.server.add.password": "Password (optional)",
   "dialog.server.add.password": "Password (optional)",
+  "dialog.server.add.passwordPlaceholder": "password",
   "dialog.server.edit.title": "Edit server",
   "dialog.server.edit.title": "Edit server",
   "dialog.server.default.title": "Default server",
   "dialog.server.default.title": "Default server",
   "dialog.server.default.description":
   "dialog.server.default.description":
@@ -335,6 +341,7 @@ export const dict = {
   "dialog.server.menu.delete": "Delete",
   "dialog.server.menu.delete": "Delete",
   "dialog.server.current": "Current Server",
   "dialog.server.current": "Current Server",
   "dialog.server.status.default": "Default",
   "dialog.server.status.default": "Default",
+  "server.row.noUsername": "no username",
 
 
   "dialog.project.edit.title": "Edit project",
   "dialog.project.edit.title": "Edit project",
   "dialog.project.edit.name": "Name",
   "dialog.project.edit.name": "Name",
@@ -456,6 +463,7 @@ export const dict = {
   "error.page.action.checking": "Checking...",
   "error.page.action.checking": "Checking...",
   "error.page.action.checkUpdates": "Check for updates",
   "error.page.action.checkUpdates": "Check for updates",
   "error.page.action.updateTo": "Update to {{version}}",
   "error.page.action.updateTo": "Update to {{version}}",
+  "error.page.circular": "[Circular]",
   "error.page.report.prefix": "Please report this error to the OpenCode team",
   "error.page.report.prefix": "Please report this error to the OpenCode team",
   "error.page.report.discord": "on Discord",
   "error.page.report.discord": "on Discord",
   "error.page.version": "Version: {{version}}",
   "error.page.version": "Version: {{version}}",
@@ -464,6 +472,12 @@ export const dict = {
     "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
     "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
 
 
   "error.globalSync.connectFailed": "Could not connect to server. Is there a server running at `{{url}}`?",
   "error.globalSync.connectFailed": "Could not connect to server. Is there a server running at `{{url}}`?",
+  "error.globalSDK.noServerAvailable": "No server available",
+  "error.globalSDK.serverNotAvailable": "Server not available",
+  "error.childStore.persistedCacheCreateFailed": "Failed to create persisted cache",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Failed to create persisted project metadata",
+  "error.childStore.persistedProjectIconCreateFailed": "Failed to create persisted project icon",
+  "error.childStore.storeCreateFailed": "Failed to create store",
   "directory.error.invalidUrl": "Invalid directory in URL.",
   "directory.error.invalidUrl": "Invalid directory in URL.",
 
 
   "error.chain.unknown": "Unknown error",
   "error.chain.unknown": "Unknown error",
@@ -512,6 +526,10 @@ export const dict = {
   "session.review.loadingChanges": "Loading changes...",
   "session.review.loadingChanges": "Loading changes...",
   "session.review.empty": "No changes in this session yet",
   "session.review.empty": "No changes in this session yet",
   "session.review.noVcs": "No Git Version Control System detected, changes not displayed",
   "session.review.noVcs": "No Git Version Control System detected, changes not displayed",
+  "session.review.noVcs.createGit.title": "Create a Git repository",
+  "session.review.noVcs.createGit.description": "Track, review, and undo changes in this project",
+  "session.review.noVcs.createGit.actionLoading": "Creating Git repository...",
+  "session.review.noVcs.createGit.action": "Create Git repository",
   "session.review.noSnapshot": "Snapshot tracking is disabled in config, so session changes are unavailable",
   "session.review.noSnapshot": "Snapshot tracking is disabled in config, so session changes are unavailable",
   "session.review.noChanges": "No changes",
   "session.review.noChanges": "No changes",
 
 
@@ -530,6 +548,8 @@ export const dict = {
   "session.todo.title": "Todos",
   "session.todo.title": "Todos",
   "session.todo.collapse": "Collapse",
   "session.todo.collapse": "Collapse",
   "session.todo.expand": "Expand",
   "session.todo.expand": "Expand",
+  "session.todo.progress": "{{done}} of {{total}} todos completed",
+  "session.question.progress": "{{current}} of {{total}} questions",
   "session.followupDock.summary.one": "{{count}} queued message",
   "session.followupDock.summary.one": "{{count}} queued message",
   "session.followupDock.summary.other": "{{count}} queued messages",
   "session.followupDock.summary.other": "{{count}} queued messages",
   "session.followupDock.sendNow": "Send now",
   "session.followupDock.sendNow": "Send now",
@@ -555,6 +575,22 @@ export const dict = {
   "session.header.open.ariaLabel": "Open in {{app}}",
   "session.header.open.ariaLabel": "Open in {{app}}",
   "session.header.open.menu": "Open options",
   "session.header.open.menu": "Open options",
   "session.header.open.copyPath": "Copy path",
   "session.header.open.copyPath": "Copy path",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "File Explorer",
+  "session.header.open.fileManager": "File Manager",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
 
 
   "status.popover.trigger": "Status",
   "status.popover.trigger": "Status",
   "status.popover.ariaLabel": "Server configurations",
   "status.popover.ariaLabel": "Server configurations",
@@ -587,6 +623,7 @@ export const dict = {
   "terminal.title.numbered": "Terminal {{number}}",
   "terminal.title.numbered": "Terminal {{number}}",
   "terminal.close": "Close terminal",
   "terminal.close": "Close terminal",
   "terminal.connectionLost.title": "Connection Lost",
   "terminal.connectionLost.title": "Connection Lost",
+  "terminal.connectionLost.abnormalClose": "WebSocket closed abnormally: {{code}}",
   "terminal.connectionLost.description":
   "terminal.connectionLost.description":
     "The terminal connection was interrupted. This can happen when the server restarts.",
     "The terminal connection was interrupted. This can happen when the server restarts.",
 
 
@@ -604,6 +641,21 @@ export const dict = {
   "common.edit": "Edit",
   "common.edit": "Edit",
   "common.loadMore": "Load more",
   "common.loadMore": "Load more",
   "common.key.esc": "ESC",
   "common.key.esc": "ESC",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Space",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "unknown",
 
 
   "common.time.justNow": "Just now",
   "common.time.justNow": "Just now",
   "common.time.minutesAgo.short": "{{count}}m ago",
   "common.time.minutesAgo.short": "{{count}}m ago",
@@ -623,6 +675,30 @@ export const dict = {
   "sidebar.project.viewAllSessions": "View all sessions",
   "sidebar.project.viewAllSessions": "View all sessions",
   "sidebar.project.clearNotifications": "Clear notifications",
   "sidebar.project.clearNotifications": "Clear notifications",
 
 
+  "debugBar.ariaLabel": "Development performance diagnostics",
+  "debugBar.na": "n/a",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Last completed route transition touching a session page, measured from router start until the first paint after it settles.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Rolling frames per second over the last 5 seconds.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Worst frame time over the last 5 seconds.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Frames over 32ms in the last 5 seconds.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "Blocked time and long-task count in the last 5 seconds. Max task: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Worst observed input delay in the last 5 seconds.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Approximate interaction duration over the last 5 seconds. This is INP-like, not the official Web Vitals INP.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Cumulative layout shift for the current app lifetime.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Used JS heap vs heap limit. Chromium only.",
+  "debugBar.mem.tip": "Used JS heap vs heap limit. {{used}} of {{limit}}.",
+
   "app.name.desktop": "OpenCode Desktop",
   "app.name.desktop": "OpenCode Desktop",
 
 
   "settings.section.desktop": "Desktop",
   "settings.section.desktop": "Desktop",

+ 75 - 0
packages/app/src/i18n/es.ts

@@ -871,4 +871,79 @@ export const dict = {
   "common.time.daysAgo.short": "hace {{count}} d",
   "common.time.daysAgo.short": "hace {{count}} d",
   "settings.providers.connected.environmentDescription": "Conectado desde tus variables de entorno",
   "settings.providers.connected.environmentDescription": "Conectado desde tus variables de entorno",
   "settings.providers.custom.description": "Añade un proveedor compatible con OpenAI por su URL base.",
   "settings.providers.custom.description": "Añade un proveedor compatible con OpenAI por su URL base.",
+
+  "app.server.unreachable": "No se pudo conectar con {{server}}",
+  "app.server.retrying": "Reintentando automáticamente...",
+  "app.server.otherServers": "Otros servidores",
+  "dialog.server.add.usernamePlaceholder": "usuario",
+  "dialog.server.add.passwordPlaceholder": "contraseña",
+  "server.row.noUsername": "sin usuario",
+  "session.review.noVcs.createGit.title": "Crear repositorio Git",
+  "session.review.noVcs.createGit.description": "Rastrea, revisa y deshaz cambios en este proyecto",
+  "session.review.noVcs.createGit.actionLoading": "Creando repositorio Git...",
+  "session.review.noVcs.createGit.action": "Crear repositorio Git",
+  "session.todo.progress": "{{done}} de {{total}} tareas completadas",
+  "session.question.progress": "{{current}} de {{total}} preguntas",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "Explorador de archivos",
+  "session.header.open.fileManager": "Gestor de archivos",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Diagnóstico de rendimiento de desarrollo",
+  "debugBar.na": "n/d",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Última transición de ruta completada tocando una página de sesión, medida desde el inicio del router hasta el primer pintado después de asentarse.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Cuadros por segundo en los últimos 5 segundos.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Peor tiempo de cuadro en los últimos 5 segundos.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Cuadros superiores a 32ms en los últimos 5 segundos.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "Tiempo bloqueado y recuento de tareas largas en los últimos 5 segundos. Tarea máx: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Peor retraso de entrada observado en los últimos 5 segundos.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Duración aproximada de la interacción en los últimos 5 segundos. Esto es similar a INP, no el INP oficial de Web Vitals.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Cambio de diseño acumulativo para la vida útil actual de la aplicación.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Heap JS usado vs límite de heap. Solo Chromium.",
+  "debugBar.mem.tip": "Heap JS usado vs límite de heap. {{used}} de {{limit}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Mayús",
+  "common.key.meta": "Meta",
+  "common.key.space": "Espacio",
+  "common.key.backspace": "Retroceso",
+  "common.key.enter": "Intro",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Supr",
+  "common.key.home": "Inicio",
+  "common.key.end": "Fin",
+  "common.key.pageUp": "RePág",
+  "common.key.pageDown": "AvPág",
+  "common.key.insert": "Insert",
+  "common.unknown": "desconocido",
+  "error.page.circular": "[Circular]",
+  "error.globalSDK.noServerAvailable": "Ningún servidor disponible",
+  "error.globalSDK.serverNotAvailable": "Servidor no disponible",
+  "error.childStore.persistedCacheCreateFailed": "Error al crear caché persistente",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Error al crear metadatos de proyecto persistentes",
+  "error.childStore.persistedProjectIconCreateFailed": "Error al crear icono de proyecto persistente",
+  "error.childStore.storeCreateFailed": "Error al crear almacén",
+  "terminal.connectionLost.abnormalClose": "WebSocket cerrado anormalmente: {{code}}",
 }
 }

+ 77 - 0
packages/app/src/i18n/fr.ts

@@ -796,4 +796,81 @@ export const dict = {
   "common.time.daysAgo.short": "il y a {{count}}j",
   "common.time.daysAgo.short": "il y a {{count}}j",
   "settings.providers.connected.environmentDescription": "Connecté à partir de vos variables d'environnement",
   "settings.providers.connected.environmentDescription": "Connecté à partir de vos variables d'environnement",
   "settings.providers.custom.description": "Ajouter un fournisseur compatible avec OpenAI via l'URL de base.",
   "settings.providers.custom.description": "Ajouter un fournisseur compatible avec OpenAI via l'URL de base.",
+
+  "app.server.unreachable": "Impossible de joindre {{server}}",
+  "app.server.retrying": "Nouvelle tentative automatique...",
+  "app.server.otherServers": "Autres serveurs",
+  "dialog.server.add.usernamePlaceholder": "nom d'utilisateur",
+  "dialog.server.add.passwordPlaceholder": "mot de passe",
+  "server.row.noUsername": "aucun nom d'utilisateur",
+  "session.review.noVcs.createGit.title": "Créer un dépôt Git",
+  "session.review.noVcs.createGit.description": "Suivre, examiner et annuler les modifications dans ce projet",
+  "session.review.noVcs.createGit.actionLoading": "Création du dépôt Git...",
+  "session.review.noVcs.createGit.action": "Créer un dépôt Git",
+  "session.todo.progress": "{{done}} tâches sur {{total}} terminées",
+  "session.question.progress": "{{current}} questions sur {{total}}",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "Explorateur de fichiers",
+  "session.header.open.fileManager": "Gestionnaire de fichiers",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Diagnostics de performance de développement",
+  "debugBar.na": "n/a",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Dernière transition de route terminée touchant une page de session, mesurée du début du routeur jusqu'au premier affichage après stabilisation.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Images par seconde glissantes sur les 5 dernières secondes.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Pire temps d'image sur les 5 dernières secondes.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Images de plus de 32ms au cours des 5 dernières secondes.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip":
+    "Temps bloqué et nombre de tâches longues au cours des 5 dernières secondes. Tâche max : {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Pire délai d'entrée observé au cours des 5 dernières secondes.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Durée approximative d'interaction au cours des 5 dernières secondes. Ceci est similaire à INP, pas le INP officiel des Web Vitals.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Décalage cumulatif de la mise en page pour la durée de vie actuelle de l'application.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Tas JS utilisé vs limite de tas. Chromium uniquement.",
+  "debugBar.mem.tip": "Tas JS utilisé vs limite de tas. {{used}} sur {{limit}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Maj",
+  "common.key.meta": "Méta",
+  "common.key.space": "Espace",
+  "common.key.backspace": "Retour arrière",
+  "common.key.enter": "Entrée",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Suppr",
+  "common.key.home": "Début",
+  "common.key.end": "Fin",
+  "common.key.pageUp": "Page précédente",
+  "common.key.pageDown": "Page suivante",
+  "common.key.insert": "Inser",
+  "common.unknown": "inconnu",
+  "error.page.circular": "[Circulaire]",
+  "error.globalSDK.noServerAvailable": "Aucun serveur disponible",
+  "error.globalSDK.serverNotAvailable": "Serveur non disponible",
+  "error.childStore.persistedCacheCreateFailed": "Échec de la création du cache persistant",
+  "error.childStore.persistedProjectMetadataCreateFailed":
+    "Échec de la création des métadonnées de projet persistantes",
+  "error.childStore.persistedProjectIconCreateFailed": "Échec de la création de l'icône de projet persistante",
+  "error.childStore.storeCreateFailed": "Échec de la création du stockage",
+  "terminal.connectionLost.abnormalClose": "WebSocket fermé anormalement : {{code}}",
 }
 }

+ 74 - 0
packages/app/src/i18n/ja.ts

@@ -783,4 +783,78 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}} 日前",
   "common.time.daysAgo.short": "{{count}} 日前",
   "settings.providers.connected.environmentDescription": "環境変数から接続されました",
   "settings.providers.connected.environmentDescription": "環境変数から接続されました",
   "settings.providers.custom.description": "ベース URL を指定して OpenAI 互換のプロバイダーを追加します。",
   "settings.providers.custom.description": "ベース URL を指定して OpenAI 互換のプロバイダーを追加します。",
+
+  "app.server.unreachable": "{{server}} に到達できませんでした",
+  "app.server.retrying": "自動的に再試行中...",
+  "app.server.otherServers": "その他のサーバー",
+  "dialog.server.add.usernamePlaceholder": "ユーザー名",
+  "dialog.server.add.passwordPlaceholder": "パスワード",
+  "server.row.noUsername": "ユーザー名なし",
+  "session.review.noVcs.createGit.title": "Git リポジトリを作成",
+  "session.review.noVcs.createGit.description": "このプロジェクトの変更を追跡、レビュー、元に戻す",
+  "session.review.noVcs.createGit.actionLoading": "Git リポジトリを作成中...",
+  "session.review.noVcs.createGit.action": "Git リポジトリを作成",
+  "session.todo.progress": "{{done}} 個中 {{total}} 個の Todo が完了",
+  "session.question.progress": "{{total}} 問中 {{current}} 問",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "エクスプローラー",
+  "session.header.open.fileManager": "ファイルマネージャー",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "ターミナル",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "開発パフォーマンス診断",
+  "debugBar.na": "n/a",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip": "セッションページに触れる最後に完了したルート遷移。ルーター開始から安定後の最初の描画まで測定。",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "過去5秒間のローリングフレーム/秒。",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "過去5秒間の最悪フレーム時間。",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "過去5秒間で32msを超えたフレーム。",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "過去5秒間のブロック時間と長時間タスク数。最大タスク: {{max}}。",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "過去5秒間で観測された最悪の入力遅延。",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "過去5秒間の概算インタラクション時間。これは INP に似ていますが、公式の Web Vitals INP ではありません。",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "現在のアプリ寿命の累積レイアウトシフト。",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "使用中の JS ヒープ対ヒープ制限。Chromium のみ。",
+  "debugBar.mem.tip": "使用中の JS ヒープ対ヒープ制限。{{limit}} 中 {{used}}。",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Space",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "不明",
+  "error.page.circular": "[循環]",
+  "error.globalSDK.noServerAvailable": "利用可能なサーバーがありません",
+  "error.globalSDK.serverNotAvailable": "サーバーが利用できません",
+  "error.childStore.persistedCacheCreateFailed": "永続キャッシュの作成に失敗しました",
+  "error.childStore.persistedProjectMetadataCreateFailed": "永続プロジェクトメタデータの作成に失敗しました",
+  "error.childStore.persistedProjectIconCreateFailed": "永続プロジェクトアイコンの作成に失敗しました",
+  "error.childStore.storeCreateFailed": "ストアの作成に失敗しました",
+  "terminal.connectionLost.abnormalClose": "WebSocket が異常終了しました: {{code}}",
 }
 }

+ 74 - 0
packages/app/src/i18n/ko.ts

@@ -782,4 +782,78 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}}일 전",
   "common.time.daysAgo.short": "{{count}}일 전",
   "settings.providers.connected.environmentDescription": "환경 변수에서 연결됨",
   "settings.providers.connected.environmentDescription": "환경 변수에서 연결됨",
   "settings.providers.custom.description": "기본 URL로 OpenAI 호환 공급자를 추가합니다.",
   "settings.providers.custom.description": "기본 URL로 OpenAI 호환 공급자를 추가합니다.",
+
+  "app.server.unreachable": "{{server}}에 연결할 수 없습니다",
+  "app.server.retrying": "자동으로 재시도 중...",
+  "app.server.otherServers": "다른 서버",
+  "dialog.server.add.usernamePlaceholder": "사용자 이름",
+  "dialog.server.add.passwordPlaceholder": "비밀번호",
+  "server.row.noUsername": "사용자 이름 없음",
+  "session.review.noVcs.createGit.title": "Git 저장소 생성",
+  "session.review.noVcs.createGit.description": "이 프로젝트의 변경 사항을 추적, 검토 및 실행 취소",
+  "session.review.noVcs.createGit.actionLoading": "Git 저장소 생성 중...",
+  "session.review.noVcs.createGit.action": "Git 저장소 생성",
+  "session.todo.progress": "{{total}}개의 할 일 중 {{done}}개 완료",
+  "session.question.progress": "{{total}}개의 질문 중 {{current}}개",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "파일 탐색기",
+  "session.header.open.fileManager": "파일 관리자",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "터미널",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "개발 성능 진단",
+  "debugBar.na": "해당 없음",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "세션 페이지에 닿은 마지막 완료된 라우트 전환. 라우터 시작부터 정착 후 첫 번째 페인트까지 측정됨.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "지난 5초간의 초당 프레임 수.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "지난 5초간의 최악의 프레임 시간.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "지난 5초간 32ms를 초과한 프레임.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "지난 5초간의 차단된 시간 및 긴 작업 수. 최대 작업: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "지난 5초간 관찰된 최악의 입력 지연.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip": "지난 5초간의 대략적인 상호작용 지속 시간. 이것은 공식 Web Vitals INP가 아닌 INP와 유사합니다.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "현재 앱 수명 동안의 누적 레이아웃 이동.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "사용된 JS 힙 대 힙 제한. Chromium 전용.",
+  "debugBar.mem.tip": "사용된 JS 힙 대 힙 제한. {{limit}} 중 {{used}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Space",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "알 수 없음",
+  "error.page.circular": "[순환]",
+  "error.globalSDK.noServerAvailable": "사용 가능한 서버 없음",
+  "error.globalSDK.serverNotAvailable": "서버를 사용할 수 없음",
+  "error.childStore.persistedCacheCreateFailed": "영구 캐시 생성 실패",
+  "error.childStore.persistedProjectMetadataCreateFailed": "영구 프로젝트 메타데이터 생성 실패",
+  "error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패",
+  "error.childStore.storeCreateFailed": "저장소 생성 실패",
+  "terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}",
 }
 }

+ 75 - 0
packages/app/src/i18n/no.ts

@@ -865,4 +865,79 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}} d siden",
   "common.time.daysAgo.short": "{{count}} d siden",
   "settings.providers.connected.environmentDescription": "Koblet til fra miljøvariablene dine",
   "settings.providers.connected.environmentDescription": "Koblet til fra miljøvariablene dine",
   "settings.providers.custom.description": "Legg til en OpenAI-kompatibel leverandør via basis-URL.",
   "settings.providers.custom.description": "Legg til en OpenAI-kompatibel leverandør via basis-URL.",
+
+  "app.server.unreachable": "Kunne ikke nå {{server}}",
+  "app.server.retrying": "Prøver på nytt automatisk...",
+  "app.server.otherServers": "Andre servere",
+  "dialog.server.add.usernamePlaceholder": "brukernavn",
+  "dialog.server.add.passwordPlaceholder": "passord",
+  "server.row.noUsername": "inget brukernavn",
+  "session.review.noVcs.createGit.title": "Opprett et Git-depot",
+  "session.review.noVcs.createGit.description": "Spor, gjennomgå og angre endringer i dette prosjektet",
+  "session.review.noVcs.createGit.actionLoading": "Oppretter Git-depot...",
+  "session.review.noVcs.createGit.action": "Opprett Git-depot",
+  "session.todo.progress": "{{done}} av {{total}} oppgaver fullført",
+  "session.question.progress": "{{current}} av {{total}} spørsmål",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "Filutforsker",
+  "session.header.open.fileManager": "Filbehandler",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Utviklingsytelsesdiagnostikk",
+  "debugBar.na": "i/t",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Siste fullførte ruteovergang som berører en sesjonsside, målt fra ruterstart til første opptegning etter at den har roet seg.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Rullende bilder per sekund over de siste 5 sekundene.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Verste bildetid over de siste 5 sekundene.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Bilder over 32ms i de siste 5 sekundene.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "Blokkert tid og antall lange oppgaver i de siste 5 sekundene. Maks oppgave: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Verste observerte inndataforsinkelse i de siste 5 sekundene.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Omtrentlig interaksjonsvarighet over de siste 5 sekundene. Dette er INP-lignende, ikke den offisielle Web Vitals INP.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Kumulativ layoutforskyvning for gjeldende app-levetid.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Brukt JS-heap vs heap-grense. Kun Chromium.",
+  "debugBar.mem.tip": "Brukt JS-heap vs heap-grense. {{used}} av {{limit}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Mellomrom",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "ukjent",
+  "error.page.circular": "[Sirkulær]",
+  "error.globalSDK.noServerAvailable": "Ingen server tilgjengelig",
+  "error.globalSDK.serverNotAvailable": "Server ikke tilgjengelig",
+  "error.childStore.persistedCacheCreateFailed": "Kunne ikke opprette vedvarende hurtigbuffer",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Kunne ikke opprette vedvarende prosjektmetadata",
+  "error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon",
+  "error.childStore.storeCreateFailed": "Kunne ikke opprette lager",
+  "terminal.connectionLost.abnormalClose": "WebSocket lukket unormalt: {{code}}",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

+ 76 - 0
packages/app/src/i18n/pl.ts

@@ -785,4 +785,80 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}} dni temu",
   "common.time.daysAgo.short": "{{count}} dni temu",
   "settings.providers.connected.environmentDescription": "Połączono ze zmiennymi środowiskowymi",
   "settings.providers.connected.environmentDescription": "Połączono ze zmiennymi środowiskowymi",
   "settings.providers.custom.description": "Dodaj dostawcę zgodnego z OpenAI poprzez podstawowy URL.",
   "settings.providers.custom.description": "Dodaj dostawcę zgodnego z OpenAI poprzez podstawowy URL.",
+
+  "app.server.unreachable": "Nie można połączyć z {{server}}",
+  "app.server.retrying": "Ponawianie automatycznie...",
+  "app.server.otherServers": "Inne serwery",
+  "dialog.server.add.usernamePlaceholder": "nazwa użytkownika",
+  "dialog.server.add.passwordPlaceholder": "hasło",
+  "server.row.noUsername": "brak nazwy użytkownika",
+  "session.review.noVcs.createGit.title": "Utwórz repozytorium Git",
+  "session.review.noVcs.createGit.description": "Śledź, przeglądaj i cofaj zmiany w tym projekcie",
+  "session.review.noVcs.createGit.actionLoading": "Tworzenie repozytorium Git...",
+  "session.review.noVcs.createGit.action": "Utwórz repozytorium Git",
+  "session.todo.progress": "Ukończono {{done}} z {{total}} zadań",
+  "session.question.progress": "{{current}} z {{total}} pytań",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "Eksplorator plików",
+  "session.header.open.fileManager": "Menedżer plików",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Diagnostyka wydajności deweloperskiej",
+  "debugBar.na": "n.d.",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Ostatnie zakończone przejście trasy dotykające strony sesji, mierzone od startu routera do pierwszego odrysowania po ustaleniu.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Średnia liczba klatek na sekundę w ciągu ostatnich 5 sekund.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Najgorszy czas klatki w ciągu ostatnich 5 sekund.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Klatki powyżej 32ms w ciągu ostatnich 5 sekund.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip":
+    "Zablokowany czas i liczba długich zadań w ciągu ostatnich 5 sekund. Maksymalne zadanie: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Najgorsze zaobserwowane opóźnienie wejścia w ciągu ostatnich 5 sekund.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Przybliżony czas trwania interakcji w ciągu ostatnich 5 sekund. Jest to podobne do INP, a nie oficjalne Web Vitals INP.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Skumulowane przesunięcie układu dla bieżącego czasu życia aplikacji.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Użyta sterta JS vs limit sterty. Tylko Chromium.",
+  "debugBar.mem.tip": "Użyta sterta JS vs limit sterty. {{used}} z {{limit}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Spacja",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "nieznany",
+  "error.page.circular": "[Cykliczne]",
+  "error.globalSDK.noServerAvailable": "Brak dostępnego serwera",
+  "error.globalSDK.serverNotAvailable": "Serwer niedostępny",
+  "error.childStore.persistedCacheCreateFailed": "Nie udało się utworzyć trwałej pamięci podręcznej",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Nie udało się utworzyć trwałych metadanych projektu",
+  "error.childStore.persistedProjectIconCreateFailed": "Nie udało się utworzyć trwałej ikony projektu",
+  "error.childStore.storeCreateFailed": "Nie udało się utworzyć magazynu",
+  "terminal.connectionLost.abnormalClose": "WebSocket zamknięty nieprawidłowo: {{code}}",
 }
 }

+ 75 - 0
packages/app/src/i18n/ru.ts

@@ -867,4 +867,79 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}} д назад",
   "common.time.daysAgo.short": "{{count}} д назад",
   "settings.providers.connected.environmentDescription": "Подключено из ваших переменных окружения",
   "settings.providers.connected.environmentDescription": "Подключено из ваших переменных окружения",
   "settings.providers.custom.description": "Добавить провайдера, совместимого с OpenAI, по базовому URL.",
   "settings.providers.custom.description": "Добавить провайдера, совместимого с OpenAI, по базовому URL.",
+
+  "app.server.unreachable": "Не удалось связаться с {{server}}",
+  "app.server.retrying": "Автоматическая повторная попытка...",
+  "app.server.otherServers": "Другие серверы",
+  "dialog.server.add.usernamePlaceholder": "имя пользователя",
+  "dialog.server.add.passwordPlaceholder": "пароль",
+  "server.row.noUsername": "нет имени пользователя",
+  "session.review.noVcs.createGit.title": "Создать репозиторий Git",
+  "session.review.noVcs.createGit.description": "Отслеживайте, просматривайте и отменяйте изменения в этом проекте",
+  "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",
+  "session.header.open.fileExplorer": "Проводник",
+  "session.header.open.fileManager": "Файловый менеджер",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Терминал",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Диагностика производительности разработки",
+  "debugBar.na": "н/д",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Последний завершенный переход маршрута, затрагивающий страницу сеанса, измеренный от запуска маршрутизатора до первой отрисовки после стабилизации.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Скользящая частота кадров в секунду за последние 5 секунд.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Худшее время кадра за последние 5 секунд.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Кадры более 32 мс за последние 5 секунд.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "Заблокированное время и количество длинных задач за последние 5 секунд. Макс. задача: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Худшая наблюдаемая задержка ввода за последние 5 секунд.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "Приблизительная продолжительность взаимодействия за последние 5 секунд. Это похоже на INP, а не официальный Web Vitals INP.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Кумулятивный сдвиг макета за текущее время жизни приложения.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Используемая куча JS по сравнению с лимитом кучи. Только Chromium.",
+  "debugBar.mem.tip": "Используемая куча JS по сравнению с лимитом кучи. {{used}} из {{limit}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Пробел",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "неизвестно",
+  "error.page.circular": "[Циклично]",
+  "error.globalSDK.noServerAvailable": "Нет доступного сервера",
+  "error.globalSDK.serverNotAvailable": "Сервер недоступен",
+  "error.childStore.persistedCacheCreateFailed": "Не удалось создать постоянный кэш",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Не удалось создать постоянные метаданные проекта",
+  "error.childStore.persistedProjectIconCreateFailed": "Не удалось создать постоянный значок проекта",
+  "error.childStore.storeCreateFailed": "Не удалось создать хранилище",
+  "terminal.connectionLost.abnormalClose": "WebSocket закрыт аварийно: {{code}}",
 }
 }

+ 75 - 0
packages/app/src/i18n/th.ts

@@ -854,4 +854,79 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}} วันที่แล้ว",
   "common.time.daysAgo.short": "{{count}} วันที่แล้ว",
   "settings.providers.connected.environmentDescription": "เชื่อมต่อจากตัวแปรสภาพแวดล้อมของคุณ",
   "settings.providers.connected.environmentDescription": "เชื่อมต่อจากตัวแปรสภาพแวดล้อมของคุณ",
   "settings.providers.custom.description": "เพิ่มผู้ให้บริการที่รองรับ OpenAI ด้วย URL หลัก",
   "settings.providers.custom.description": "เพิ่มผู้ให้บริการที่รองรับ OpenAI ด้วย URL หลัก",
+
+  "app.server.unreachable": "ไม่สามารถติดต่อ {{server}}",
+  "app.server.retrying": "กำลังลองใหม่โดยอัตโนมัติ...",
+  "app.server.otherServers": "เซิร์ฟเวอร์อื่น ๆ",
+  "dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้",
+  "dialog.server.add.passwordPlaceholder": "รหัสผ่าน",
+  "server.row.noUsername": "ไม่มีชื่อผู้ใช้",
+  "session.review.noVcs.createGit.title": "สร้าง Git repository",
+  "session.review.noVcs.createGit.description": "ติดตาม ตรวจสอบ และเลิกทำสิ่งเปลี่ยนแปลงในโปรเจกต์นี้",
+  "session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git repository...",
+  "session.review.noVcs.createGit.action": "สร้าง Git repository",
+  "session.todo.progress": "เสร็จสิ้น {{done}} จาก {{total}} รายการ",
+  "session.question.progress": "{{current}} จาก {{total}} คำถาม",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "File Explorer",
+  "session.header.open.fileManager": "File Manager",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "การวินิจฉัยประสิทธิภาพการพัฒนา",
+  "debugBar.na": "n/a",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "การเปลี่ยนเส้นทางที่เสร็จสมบูรณ์ล่าสุดที่สัมผัสหน้าเซสชัน วัดจากจุดเริ่มต้นเราเตอร์จนถึงการวาดครั้งแรกหลังจากที่นิ่ง",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "เฟรมต่อวินาทีแบบต่อเนื่องในช่วง 5 วินาทีที่ผ่านมา",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "เวลาเฟรมที่แย่ที่สุดในช่วง 5 วินาทีที่ผ่านมา",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "เฟรมที่เกิน 32ms ในช่วง 5 วินาทีที่ผ่านมา",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "เวลาที่ถูกบล็อกและจำนวนงานยาวในช่วง 5 วินาทีที่ผ่านมา งานสูงสุด: {{max}}",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "ความล่าช้าในการป้อนข้อมูลที่แย่ที่สุดที่สังเกตได้ในช่วง 5 วินาทีที่ผ่านมา",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip":
+    "ระยะเวลาการโต้ตอบโดยประมาณในช่วง 5 วินาทีที่ผ่านมา นี่เป็นเหมือน INP ไม่ใช่ Web Vitals INP อย่างเป็นทางการ",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "การเลื่อนเลย์เอาต์สะสมสำหรับอายุการใช้งานของแอปปัจจุบัน",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "JS heap ที่ใช้เทียบกับขีดจำกัด heap เฉพาะ Chromium",
+  "debugBar.mem.tip": "JS heap ที่ใช้เทียบกับขีดจำกัด heap {{used}} จาก {{limit}}",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Space",
+  "common.key.backspace": "Backspace",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "ไม่ทราบ",
+  "error.page.circular": "[วงกลม]",
+  "error.globalSDK.noServerAvailable": "ไม่มีเซิร์ฟเวอร์",
+  "error.globalSDK.serverNotAvailable": "เซิร์ฟเวอร์ไม่พร้อมใช้งาน",
+  "error.childStore.persistedCacheCreateFailed": "ไม่สามารถสร้างแคชถาวร",
+  "error.childStore.persistedProjectMetadataCreateFailed": "ไม่สามารถสร้างเมตาดาต้าโปรเจกต์ถาวร",
+  "error.childStore.persistedProjectIconCreateFailed": "ไม่สามารถสร้างไอคอนโปรเจกต์ถาวร",
+  "error.childStore.storeCreateFailed": "ไม่สามารถสร้างที่เก็บ",
+  "terminal.connectionLost.abnormalClose": "WebSocket ปิดอย่างผิดปกติ: {{code}}",
 }
 }

+ 74 - 0
packages/app/src/i18n/tr.ts

@@ -874,4 +874,78 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}}g önce",
   "common.time.daysAgo.short": "{{count}}g önce",
   "settings.providers.connected.environmentDescription": "Ortam değişkenlerinizden bağlandı",
   "settings.providers.connected.environmentDescription": "Ortam değişkenlerinizden bağlandı",
   "settings.providers.custom.description": "Temel URL üzerinden OpenAI uyumlu bir sağlayıcı ekleyin.",
   "settings.providers.custom.description": "Temel URL üzerinden OpenAI uyumlu bir sağlayıcı ekleyin.",
+
+  "app.server.unreachable": "{{server}} sunucusuna ulaşılamadı",
+  "app.server.retrying": "Otomatik olarak tekrar deneniyor...",
+  "app.server.otherServers": "Diğer sunucular",
+  "dialog.server.add.usernamePlaceholder": "kullanıcı adı",
+  "dialog.server.add.passwordPlaceholder": "parola",
+  "server.row.noUsername": "kullanıcı adı yok",
+  "session.review.noVcs.createGit.title": "Git deposu oluştur",
+  "session.review.noVcs.createGit.description": "Bu projedeki değişiklikleri takip et, incele ve geri al",
+  "session.review.noVcs.createGit.actionLoading": "Git deposu oluşturuluyor...",
+  "session.review.noVcs.createGit.action": "Git deposu oluştur",
+  "session.todo.progress": "{{total}} görevin {{done}} tanesi tamamlandı",
+  "session.question.progress": "{{total}} sorunun {{current}} tanesi",
+  "session.header.open.finder": "Finder",
+  "session.header.open.fileExplorer": "Dosya Gezgini",
+  "session.header.open.fileManager": "Dosya Yöneticisi",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "Terminal",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "Geliştirme performansı teşhisi",
+  "debugBar.na": "yok",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip":
+    "Yönlendirici başlangıcından yerleşme sonrası ilk boyamaya kadar ölçülen, bir oturum sayfasına dokunan son tamamlanmış rota geçişi.",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "Son 5 saniyedeki kayan saniye başına kare sayısı.",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "Son 5 saniyedeki en kötü kare süresi.",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "Son 5 saniyede 32ms üzerindeki kareler.",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "Son 5 saniyedeki engellenen süre ve uzun görev sayısı. Maksimum görev: {{max}}.",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "Son 5 saniyede gözlemlenen en kötü giriş gecikmesi.",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip": "Son 5 saniyedeki yaklaşık etkileşim süresi. Bu INP benzeridir, resmi Web Vitals INP değildir.",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "Mevcut uygulama ömrü için kümülatif düzen kayması.",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "Kullanılan JS yığını vs yığın sınırı. Yalnızca Chromium.",
+  "debugBar.mem.tip": "Kullanılan JS yığını vs yığın sınırı. {{limit}} içinde {{used}}.",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "Boşluk",
+  "common.key.backspace": "Geri",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "bilinmiyor",
+  "error.page.circular": "[Döngüsel]",
+  "error.globalSDK.noServerAvailable": "Sunucu yok",
+  "error.globalSDK.serverNotAvailable": "Sunucu mevcut değil",
+  "error.childStore.persistedCacheCreateFailed": "Kalıcı önbellek oluşturulamadı",
+  "error.childStore.persistedProjectMetadataCreateFailed": "Kalıcı proje meta verileri oluşturulamadı",
+  "error.childStore.persistedProjectIconCreateFailed": "Kalıcı proje simgesi oluşturulamadı",
+  "error.childStore.storeCreateFailed": "Depo oluşturulamadı",
+  "terminal.connectionLost.abnormalClose": "WebSocket anormal şekilde kapandı: {{code}}",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

+ 73 - 0
packages/app/src/i18n/zh.ts

@@ -853,4 +853,77 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}}天前",
   "common.time.daysAgo.short": "{{count}}天前",
   "settings.providers.connected.environmentDescription": "已通过环境变量连接",
   "settings.providers.connected.environmentDescription": "已通过环境变量连接",
   "settings.providers.custom.description": "通过基础 URL 添加与 OpenAI 兼容的提供商。",
   "settings.providers.custom.description": "通过基础 URL 添加与 OpenAI 兼容的提供商。",
+
+  "app.server.unreachable": "无法连接到 {{server}}",
+  "app.server.retrying": "正在自动重试...",
+  "app.server.otherServers": "其他服务器",
+  "dialog.server.add.usernamePlaceholder": "用户名",
+  "dialog.server.add.passwordPlaceholder": "密码",
+  "server.row.noUsername": "无用户名",
+  "session.review.noVcs.createGit.title": "创建 Git 仓库",
+  "session.review.noVcs.createGit.description": "在此项目中跟踪、审查和撤消更改",
+  "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": "访达",
+  "session.header.open.fileExplorer": "文件资源管理器",
+  "session.header.open.fileManager": "文件管理器",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "终端",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "开发性能诊断",
+  "debugBar.na": "不适用",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip": "最后一次完成的涉及会话页面的路由转换,从路由器启动到稳定后的第一次绘制。",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "过去 5 秒内的滚动帧率。",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "过去 5 秒内最差的帧时间。",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "过去 5 秒内超过 32ms 的帧。",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "过去 5 秒内的阻塞时间和长任务计数。最大任务:{{max}}。",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "过去 5 秒内观察到的最差输入延迟。",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip": "过去 5 秒内的近似交互持续时间。这类似于 INP,而非官方的 Web Vitals INP。",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "当前应用生命周期的累积布局偏移。",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "使用的 JS 堆与堆限制。仅限 Chromium。",
+  "debugBar.mem.tip": "使用的 JS 堆与堆限制。{{used}} / {{limit}}。",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "空格",
+  "common.key.backspace": "退格",
+  "common.key.enter": "回车",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "未知",
+  "error.page.circular": "[循环]",
+  "error.globalSDK.noServerAvailable": "无可用服务器",
+  "error.globalSDK.serverNotAvailable": "服务器不可用",
+  "error.childStore.persistedCacheCreateFailed": "创建持久化缓存失败",
+  "error.childStore.persistedProjectMetadataCreateFailed": "创建持久化项目元数据失败",
+  "error.childStore.persistedProjectIconCreateFailed": "创建持久化项目图标失败",
+  "error.childStore.storeCreateFailed": "创建存储失败",
+  "terminal.connectionLost.abnormalClose": "WebSocket 异常关闭:{{code}}",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

+ 73 - 0
packages/app/src/i18n/zht.ts

@@ -848,4 +848,77 @@ export const dict = {
   "common.time.daysAgo.short": "{{count}}天前",
   "common.time.daysAgo.short": "{{count}}天前",
   "settings.providers.connected.environmentDescription": "已從環境變數連線",
   "settings.providers.connected.environmentDescription": "已從環境變數連線",
   "settings.providers.custom.description": "透過基本 URL 新增與 OpenAI 相容的提供者。",
   "settings.providers.custom.description": "透過基本 URL 新增與 OpenAI 相容的提供者。",
+
+  "app.server.unreachable": "無法連線至 {{server}}",
+  "app.server.retrying": "正在自動重試...",
+  "app.server.otherServers": "其他伺服器",
+  "dialog.server.add.usernamePlaceholder": "使用者名稱",
+  "dialog.server.add.passwordPlaceholder": "密碼",
+  "server.row.noUsername": "無使用者名稱",
+  "session.review.noVcs.createGit.title": "建立 Git 儲存庫",
+  "session.review.noVcs.createGit.description": "追蹤、檢閱及復原此專案中的變更",
+  "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",
+  "session.header.open.fileExplorer": "檔案總管",
+  "session.header.open.fileManager": "檔案管理員",
+  "session.header.open.app.vscode": "VS Code",
+  "session.header.open.app.cursor": "Cursor",
+  "session.header.open.app.zed": "Zed",
+  "session.header.open.app.textmate": "TextMate",
+  "session.header.open.app.antigravity": "Antigravity",
+  "session.header.open.app.terminal": "終端機",
+  "session.header.open.app.iterm2": "iTerm2",
+  "session.header.open.app.ghostty": "Ghostty",
+  "session.header.open.app.warp": "Warp",
+  "session.header.open.app.xcode": "Xcode",
+  "session.header.open.app.androidStudio": "Android Studio",
+  "session.header.open.app.powershell": "PowerShell",
+  "session.header.open.app.sublimeText": "Sublime Text",
+  "debugBar.ariaLabel": "開發效能診斷",
+  "debugBar.na": "不適用",
+  "debugBar.nav.label": "NAV",
+  "debugBar.nav.tip": "最後一次完成的涉及工作階段頁面的路由轉換,從路由器啟動到穩定後的第一次繪製。",
+  "debugBar.fps.label": "FPS",
+  "debugBar.fps.tip": "過去 5 秒內的滾動幀率。",
+  "debugBar.frame.label": "FRAME",
+  "debugBar.frame.tip": "過去 5 秒內最差的幀時間。",
+  "debugBar.jank.label": "JANK",
+  "debugBar.jank.tip": "過去 5 秒內超過 32ms 的幀。",
+  "debugBar.long.label": "LONG",
+  "debugBar.long.tip": "過去 5 秒內的阻塞時間和長任務計數。最大任務:{{max}}。",
+  "debugBar.delay.label": "DELAY",
+  "debugBar.delay.tip": "過去 5 秒內觀察到的最差輸入延遲。",
+  "debugBar.inp.label": "INP",
+  "debugBar.inp.tip": "過去 5 秒內的近似互動持續時間。這類似於 INP,而非官方的 Web Vitals INP。",
+  "debugBar.cls.label": "CLS",
+  "debugBar.cls.tip": "目前應用程式生命週期的累積版面配置位移。",
+  "debugBar.mem.label": "MEM",
+  "debugBar.mem.tipUnavailable": "使用的 JS 堆積與堆積限制。僅限 Chromium。",
+  "debugBar.mem.tip": "使用的 JS 堆積與堆積限制。{{used}} / {{limit}}。",
+  "common.key.ctrl": "Ctrl",
+  "common.key.alt": "Alt",
+  "common.key.shift": "Shift",
+  "common.key.meta": "Meta",
+  "common.key.space": "空白鍵",
+  "common.key.backspace": "退格鍵",
+  "common.key.enter": "Enter",
+  "common.key.tab": "Tab",
+  "common.key.delete": "Delete",
+  "common.key.home": "Home",
+  "common.key.end": "End",
+  "common.key.pageUp": "Page Up",
+  "common.key.pageDown": "Page Down",
+  "common.key.insert": "Insert",
+  "common.unknown": "未知",
+  "error.page.circular": "[循環]",
+  "error.globalSDK.noServerAvailable": "無可用的伺服器",
+  "error.globalSDK.serverNotAvailable": "伺服器無法使用",
+  "error.childStore.persistedCacheCreateFailed": "建立持續性快取失敗",
+  "error.childStore.persistedProjectMetadataCreateFailed": "建立持續性專案中繼資料失敗",
+  "error.childStore.persistedProjectIconCreateFailed": "建立持續性專案圖示失敗",
+  "error.childStore.storeCreateFailed": "建立儲存區失敗",
+  "terminal.connectionLost.abnormalClose": "WebSocket 異常關閉:{{code}}",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

+ 17 - 15
packages/app/src/pages/error.tsx

@@ -35,14 +35,14 @@ function isInitError(error: unknown): error is InitError {
   )
   )
 }
 }
 
 
-function safeJson(value: unknown): string {
+function safeJson(value: unknown, circular: string): string {
   const seen = new WeakSet<object>()
   const seen = new WeakSet<object>()
   const json = JSON.stringify(
   const json = JSON.stringify(
     value,
     value,
     (_key, val) => {
     (_key, val) => {
       if (typeof val === "bigint") return val.toString()
       if (typeof val === "bigint") return val.toString()
       if (typeof val === "object" && val) {
       if (typeof val === "object" && val) {
-        if (seen.has(val)) return "[Circular]"
+        if (seen.has(val)) return circular
         seen.add(val)
         seen.add(val)
       }
       }
       return val
       return val
@@ -54,14 +54,15 @@ function safeJson(value: unknown): string {
 
 
 function formatInitError(error: InitError, t: Translator): string {
 function formatInitError(error: InitError, t: Translator): string {
   const data = error.data
   const data = error.data
+  const json = (value: unknown) => safeJson(value, t("error.page.circular"))
   switch (error.name) {
   switch (error.name) {
     case "MCPFailed": {
     case "MCPFailed": {
       const name = typeof data.name === "string" ? data.name : ""
       const name = typeof data.name === "string" ? data.name : ""
       return t("error.chain.mcpFailed", { name })
       return t("error.chain.mcpFailed", { name })
     }
     }
     case "ProviderAuthError": {
     case "ProviderAuthError": {
-      const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
-      const message = typeof data.message === "string" ? data.message : safeJson(data.message)
+      const providerID = typeof data.providerID === "string" ? data.providerID : t("common.unknown")
+      const message = typeof data.message === "string" ? data.message : json(data.message)
       return t("error.chain.providerAuthFailed", { provider: providerID, message })
       return t("error.chain.providerAuthFailed", { provider: providerID, message })
     }
     }
     case "APIError": {
     case "APIError": {
@@ -101,24 +102,24 @@ function formatInitError(error: InitError, t: Translator): string {
       ].join("\n")
       ].join("\n")
     }
     }
     case "ProviderInitError": {
     case "ProviderInitError": {
-      const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
+      const providerID = typeof data.providerID === "string" ? data.providerID : t("common.unknown")
       return t("error.chain.providerInitFailed", { provider: providerID })
       return t("error.chain.providerInitFailed", { provider: providerID })
     }
     }
     case "ConfigJsonError": {
     case "ConfigJsonError": {
-      const path = typeof data.path === "string" ? data.path : safeJson(data.path)
+      const path = typeof data.path === "string" ? data.path : json(data.path)
       const message = typeof data.message === "string" ? data.message : ""
       const message = typeof data.message === "string" ? data.message : ""
       if (message) return t("error.chain.configJsonInvalidWithMessage", { path, message })
       if (message) return t("error.chain.configJsonInvalidWithMessage", { path, message })
       return t("error.chain.configJsonInvalid", { path })
       return t("error.chain.configJsonInvalid", { path })
     }
     }
     case "ConfigDirectoryTypoError": {
     case "ConfigDirectoryTypoError": {
-      const path = typeof data.path === "string" ? data.path : safeJson(data.path)
-      const dir = typeof data.dir === "string" ? data.dir : safeJson(data.dir)
-      const suggestion = typeof data.suggestion === "string" ? data.suggestion : safeJson(data.suggestion)
+      const path = typeof data.path === "string" ? data.path : json(data.path)
+      const dir = typeof data.dir === "string" ? data.dir : json(data.dir)
+      const suggestion = typeof data.suggestion === "string" ? data.suggestion : json(data.suggestion)
       return t("error.chain.configDirectoryTypo", { dir, path, suggestion })
       return t("error.chain.configDirectoryTypo", { dir, path, suggestion })
     }
     }
     case "ConfigFrontmatterError": {
     case "ConfigFrontmatterError": {
-      const path = typeof data.path === "string" ? data.path : safeJson(data.path)
-      const message = typeof data.message === "string" ? data.message : safeJson(data.message)
+      const path = typeof data.path === "string" ? data.path : json(data.path)
+      const message = typeof data.message === "string" ? data.message : json(data.message)
       return t("error.chain.configFrontmatterError", { path, message })
       return t("error.chain.configFrontmatterError", { path, message })
     }
     }
     case "ConfigInvalidError": {
     case "ConfigInvalidError": {
@@ -126,7 +127,7 @@ function formatInitError(error: InitError, t: Translator): string {
         ? data.issues.filter(isIssue).map((issue) => "↳ " + issue.message + " " + issue.path.join("."))
         ? data.issues.filter(isIssue).map((issue) => "↳ " + issue.message + " " + issue.path.join("."))
         : []
         : []
       const message = typeof data.message === "string" ? data.message : ""
       const message = typeof data.message === "string" ? data.message : ""
-      const path = typeof data.path === "string" ? data.path : safeJson(data.path)
+      const path = typeof data.path === "string" ? data.path : json(data.path)
 
 
       const line = message
       const line = message
         ? t("error.chain.configInvalidWithMessage", { path, message })
         ? t("error.chain.configInvalidWithMessage", { path, message })
@@ -135,14 +136,15 @@ function formatInitError(error: InitError, t: Translator): string {
       return [line, ...issues].join("\n")
       return [line, ...issues].join("\n")
     }
     }
     case "UnknownError":
     case "UnknownError":
-      return typeof data.message === "string" ? data.message : safeJson(data)
+      return typeof data.message === "string" ? data.message : json(data)
     default:
     default:
       if (typeof data.message === "string") return data.message
       if (typeof data.message === "string") return data.message
-      return safeJson(data)
+      return json(data)
   }
   }
 }
 }
 
 
 function formatErrorChain(error: unknown, t: Translator, depth = 0, parentMessage?: string): string {
 function formatErrorChain(error: unknown, t: Translator, depth = 0, parentMessage?: string): string {
+  const json = (value: unknown) => safeJson(value, t("error.page.circular"))
   if (!error) return t("error.chain.unknown")
   if (!error) return t("error.chain.unknown")
 
 
   if (isInitError(error)) {
   if (isInitError(error)) {
@@ -204,7 +206,7 @@ function formatErrorChain(error: unknown, t: Translator, depth = 0, parentMessag
   }
   }
 
 
   const indent = depth > 0 ? `\n${CHAIN_SEPARATOR}${t("error.chain.causedBy")}\n` : ""
   const indent = depth > 0 ? `\n${CHAIN_SEPARATOR}${t("error.chain.causedBy")}\n` : ""
-  return indent + safeJson(error)
+  return indent + json(error)
 }
 }
 
 
 function formatError(error: unknown, t: Translator): string {
 function formatError(error: unknown, t: Translator): string {

+ 1 - 1
packages/app/src/pages/layout.tsx

@@ -2159,7 +2159,7 @@ export default function Layout(props: ParentProps) {
                   {language.t("command.provider.connect")}
                   {language.t("command.provider.connect")}
                 </Button>
                 </Button>
                 <Button size="large" variant="ghost" onClick={() => setStore("gettingStartedDismissed", true)}>
                 <Button size="large" variant="ghost" onClick={() => setStore("gettingStartedDismissed", true)}>
-                  Not yet
+                  {language.t("toast.update.action.notYet")}
                 </Button>
                 </Button>
               </div>
               </div>
             </div>
             </div>

+ 5 - 3
packages/app/src/pages/session.tsx

@@ -956,13 +956,15 @@ export default function Page() {
       return (
       return (
         <div class={input.emptyClass}>
         <div class={input.emptyClass}>
           <div class="flex flex-col gap-3">
           <div class="flex flex-col gap-3">
-            <div class="text-14-medium text-text-strong">Create a Git repository</div>
+            <div class="text-14-medium text-text-strong">{language.t("session.review.noVcs.createGit.title")}</div>
             <div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}>
             <div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}>
-              Track, review, and undo changes in this project
+              {language.t("session.review.noVcs.createGit.description")}
             </div>
             </div>
           </div>
           </div>
           <Button size="large" disabled={ui.git} onClick={initGit}>
           <Button size="large" disabled={ui.git} onClick={initGit}>
-            {ui.git ? "Creating Git repository..." : "Create Git repository"}
+            {ui.git
+              ? language.t("session.review.noVcs.createGit.actionLoading")
+              : language.t("session.review.noVcs.createGit.action")}
           </Button>
           </Button>
         </div>
         </div>
       )
       )

+ 0 - 1
packages/app/src/pages/session/composer/session-composer-region.tsx

@@ -196,7 +196,6 @@ export function SessionComposerRegion(props: {
                   <SessionTodoDock
                   <SessionTodoDock
                     sessionID={route.params.id}
                     sessionID={route.params.id}
                     todos={props.state.todos()}
                     todos={props.state.todos()}
-                    title={language.t("session.todo.title")}
                     collapseLabel={language.t("session.todo.collapse")}
                     collapseLabel={language.t("session.todo.collapse")}
                     expandLabel={language.t("session.todo.expand")}
                     expandLabel={language.t("session.todo.expand")}
                     dockProgress={value()}
                     dockProgress={value()}

+ 1 - 1
packages/app/src/pages/session/composer/session-question-dock.tsx

@@ -38,7 +38,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
 
 
   const summary = createMemo(() => {
   const summary = createMemo(() => {
     const n = Math.min(store.tab + 1, total())
     const n = Math.min(store.tab + 1, total())
-    return `${n} of ${total()} questions`
+    return language.t("session.question.progress", { current: n, total: total() })
   })
   })
 
 
   const last = createMemo(() => store.tab >= total() - 1)
   const last = createMemo(() => store.tab >= total() - 1)

+ 22 - 6
packages/app/src/pages/session/composer/session-todo-dock.tsx

@@ -9,6 +9,10 @@ import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough"
 import { Index, createEffect, createMemo, on, onCleanup } from "solid-js"
 import { Index, createEffect, createMemo, on, onCleanup } from "solid-js"
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
 import { composerEnabled, composerProbe } from "@/testing/session-composer"
 import { composerEnabled, composerProbe } from "@/testing/session-composer"
+import { useLanguage } from "@/context/language"
+
+const doneToken = "\u0000done\u0000"
+const totalToken = "\u0000total\u0000"
 
 
 function dot(status: Todo["status"]) {
 function dot(status: Todo["status"]) {
   if (status !== "in_progress") return undefined
   if (status !== "in_progress") return undefined
@@ -38,11 +42,11 @@ function dot(status: Todo["status"]) {
 export function SessionTodoDock(props: {
 export function SessionTodoDock(props: {
   sessionID?: string
   sessionID?: string
   todos: Todo[]
   todos: Todo[]
-  title: string
   collapseLabel: string
   collapseLabel: string
   expandLabel: string
   expandLabel: string
   dockProgress: number
   dockProgress: number
 }) {
 }) {
+  const language = useLanguage()
   const [store, setStore] = createStore({
   const [store, setStore] = createStore({
     collapsed: false,
     collapsed: false,
     height: 320,
     height: 320,
@@ -52,7 +56,12 @@ export function SessionTodoDock(props: {
 
 
   const total = createMemo(() => props.todos.length)
   const total = createMemo(() => props.todos.length)
   const done = createMemo(() => props.todos.filter((todo) => todo.status === "completed").length)
   const done = createMemo(() => props.todos.filter((todo) => todo.status === "completed").length)
-  const label = createMemo(() => `${done()} of ${total()} ${props.title.toLowerCase()} completed`)
+  const label = createMemo(() => language.t("session.todo.progress", { done: done(), total: total() }))
+  const progress = createMemo(() =>
+    language
+      .t("session.todo.progress", { done: doneToken, total: totalToken })
+      .split(/(\u0000done\u0000|\u0000total\u0000)/),
+  )
 
 
   const active = createMemo(
   const active = createMemo(
     () =>
     () =>
@@ -137,10 +146,17 @@ export function SessionTodoDock(props: {
               opacity: `${Math.max(0, Math.min(1, 1 - shut()))}`,
               opacity: `${Math.max(0, Math.min(1, 1 - shut()))}`,
             }}
             }}
           >
           >
-            <AnimatedNumber value={done()} />
-            <span class="mx-1">of</span>
-            <AnimatedNumber value={total()} />
-            <span>&nbsp;{props.title.toLowerCase()} completed</span>
+            <Index each={progress()}>
+              {(item) =>
+                item() === doneToken ? (
+                  <AnimatedNumber value={done()} />
+                ) : item() === totalToken ? (
+                  <AnimatedNumber value={total()} />
+                ) : (
+                  <span>{item()}</span>
+                )
+              }
+            </Index>
           </span>
           </span>
           <div
           <div
             data-slot="session-todo-preview"
             data-slot="session-todo-preview"

+ 3 - 3
packages/app/src/pages/session/terminal-label.ts

@@ -1,3 +1,5 @@
+import { isDefaultTitle as isDefaultTerminalTitle } from "@/context/terminal-title"
+
 export const terminalTabLabel = (input: {
 export const terminalTabLabel = (input: {
   title?: string
   title?: string
   titleNumber?: number
   titleNumber?: number
@@ -5,9 +7,7 @@ export const terminalTabLabel = (input: {
 }) => {
 }) => {
   const title = input.title ?? ""
   const title = input.title ?? ""
   const number = input.titleNumber ?? 0
   const number = input.titleNumber ?? 0
-  const match = title.match(/^Terminal (\d+)$/)
-  const parsed = match ? Number(match[1]) : undefined
-  const isDefaultTitle = Number.isFinite(number) && number > 0 && Number.isFinite(parsed) && parsed === number
+  const isDefaultTitle = Number.isFinite(number) && number > 0 && isDefaultTerminalTitle(title, number)
 
 
   if (title && !isDefaultTitle) return title
   if (title && !isDefaultTitle) return title
   if (number > 0) return input.t("terminal.title.numbered", { number })
   if (number > 0) return input.t("terminal.title.numbered", { number })

+ 4 - 1
packages/ui/src/components/basic-tool.tsx

@@ -1,5 +1,6 @@
 import { createEffect, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js"
 import { createEffect, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js"
 import { animate, type AnimationPlaybackControls } from "motion"
 import { animate, type AnimationPlaybackControls } from "motion"
+import { useI18n } from "../context/i18n"
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
 import { Collapsible } from "./collapsible"
 import { Collapsible } from "./collapsible"
 import type { IconProps } from "./icon"
 import type { IconProps } from "./icon"
@@ -233,12 +234,14 @@ export function GenericTool(props: {
   hideDetails?: boolean
   hideDetails?: boolean
   input?: Record<string, unknown>
   input?: Record<string, unknown>
 }) {
 }) {
+  const i18n = useI18n()
+
   return (
   return (
     <BasicTool
     <BasicTool
       icon="mcp"
       icon="mcp"
       status={props.status}
       status={props.status}
       trigger={{
       trigger={{
-        title: `Called \`${props.tool}\``,
+        title: i18n.t("ui.basicTool.called", { tool: props.tool }),
         subtitle: label(props.input),
         subtitle: label(props.input),
         args: args(props.input),
         args: args(props.input),
       }}
       }}

+ 7 - 4
packages/ui/src/components/file-search.tsx

@@ -1,4 +1,5 @@
 import { Portal } from "solid-js/web"
 import { Portal } from "solid-js/web"
+import { useI18n } from "../context/i18n"
 import { Icon } from "./icon"
 import { Icon } from "./icon"
 
 
 export function FileSearchBar(props: {
 export function FileSearchBar(props: {
@@ -13,6 +14,8 @@ export function FileSearchBar(props: {
   onPrev: () => void
   onPrev: () => void
   onNext: () => void
   onNext: () => void
 }) {
 }) {
+  const i18n = useI18n()
+
   return (
   return (
     <Portal>
     <Portal>
       <div
       <div
@@ -26,7 +29,7 @@ export function FileSearchBar(props: {
         <Icon name="magnifying-glass" size="small" class="text-text-weak shrink-0" />
         <Icon name="magnifying-glass" size="small" class="text-text-weak shrink-0" />
         <input
         <input
           ref={props.setInput}
           ref={props.setInput}
-          placeholder="Find"
+          placeholder={i18n.t("ui.fileSearch.placeholder")}
           value={props.query()}
           value={props.query()}
           class="w-40 bg-transparent outline-none text-14-regular text-text-strong placeholder:text-text-weak"
           class="w-40 bg-transparent outline-none text-14-regular text-text-strong placeholder:text-text-weak"
           onInput={(e) => props.onInput(e.currentTarget.value)}
           onInput={(e) => props.onInput(e.currentTarget.value)}
@@ -40,7 +43,7 @@ export function FileSearchBar(props: {
             type="button"
             type="button"
             class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
             class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
             disabled={props.count() === 0}
             disabled={props.count() === 0}
-            aria-label="Previous match"
+            aria-label={i18n.t("ui.fileSearch.previousMatch")}
             onClick={props.onPrev}
             onClick={props.onPrev}
           >
           >
             <Icon name="chevron-down" size="small" class="rotate-180" />
             <Icon name="chevron-down" size="small" class="rotate-180" />
@@ -49,7 +52,7 @@ export function FileSearchBar(props: {
             type="button"
             type="button"
             class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
             class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
             disabled={props.count() === 0}
             disabled={props.count() === 0}
-            aria-label="Next match"
+            aria-label={i18n.t("ui.fileSearch.nextMatch")}
             onClick={props.onNext}
             onClick={props.onNext}
           >
           >
             <Icon name="chevron-down" size="small" />
             <Icon name="chevron-down" size="small" />
@@ -58,7 +61,7 @@ export function FileSearchBar(props: {
         <button
         <button
           type="button"
           type="button"
           class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong"
           class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong"
-          aria-label="Close search"
+          aria-label={i18n.t("ui.fileSearch.close")}
           onClick={props.onClose}
           onClick={props.onClose}
         >
         >
           <Icon name="close-small" size="small" />
           <Icon name="close-small" size="small" />

+ 5 - 3
packages/ui/src/components/line-comment-annotations.tsx

@@ -2,6 +2,7 @@ import { type DiffLineAnnotation, type SelectedLineRange } from "@pierre/diffs"
 import { createEffect, createMemo, createSignal, onCleanup, Show, type Accessor, type JSX } from "solid-js"
 import { createEffect, createMemo, createSignal, onCleanup, Show, type Accessor, type JSX } from "solid-js"
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
 import { render as renderSolid } from "solid-js/web"
 import { render as renderSolid } from "solid-js/web"
+import { useI18n } from "../context/i18n"
 import { createHoverCommentUtility } from "../pierre/comment-hover"
 import { createHoverCommentUtility } from "../pierre/comment-hover"
 import { cloneSelectedLineRange, formatSelectedLineLabel, lineInSelectedRange } from "../pierre/selection-bridge"
 import { cloneSelectedLineRange, formatSelectedLineLabel, lineInSelectedRange } from "../pierre/selection-bridge"
 import { LineComment, LineCommentEditor } from "./line-comment"
 import { LineComment, LineCommentEditor } from "./line-comment"
@@ -341,6 +342,7 @@ export function createLineCommentController<T extends LineCommentShape>(
 export function createLineCommentController<T extends LineCommentShape>(
 export function createLineCommentController<T extends LineCommentShape>(
   props: LineCommentControllerProps<T> | LineCommentControllerWithSideProps<T>,
   props: LineCommentControllerProps<T> | LineCommentControllerWithSideProps<T>,
 ) {
 ) {
+  const i18n = useI18n()
   const note = createLineCommentState<string>(props.state)
   const note = createLineCommentState<string>(props.state)
 
 
   const annotations =
   const annotations =
@@ -376,7 +378,7 @@ export function createLineCommentController<T extends LineCommentShape>(
           return note.isOpen(comment.id) || note.isEditing(comment.id)
           return note.isOpen(comment.id) || note.isEditing(comment.id)
         },
         },
         comment: comment.comment,
         comment: comment.comment,
-        selection: formatSelectedLineLabel(comment.selection),
+        selection: formatSelectedLineLabel(comment.selection, i18n.t),
         get actions() {
         get actions() {
           return props.renderCommentActions?.(comment, { edit, remove })
           return props.renderCommentActions?.(comment, { edit, remove })
         },
         },
@@ -386,7 +388,7 @@ export function createLineCommentController<T extends LineCommentShape>(
                 get value() {
                 get value() {
                   return note.draft()
                   return note.draft()
                 },
                 },
-                selection: formatSelectedLineLabel(comment.selection),
+                selection: formatSelectedLineLabel(comment.selection, i18n.t),
                 onInput: note.setDraft,
                 onInput: note.setDraft,
                 onCancel: note.cancelDraft,
                 onCancel: note.cancelDraft,
                 onSubmit: (value: string) => {
                 onSubmit: (value: string) => {
@@ -412,7 +414,7 @@ export function createLineCommentController<T extends LineCommentShape>(
       get value() {
       get value() {
         return note.draft()
         return note.draft()
       },
       },
-      selection: formatSelectedLineLabel(range),
+      selection: formatSelectedLineLabel(range, i18n.t),
       onInput: note.setDraft,
       onInput: note.setDraft,
       onCancel: note.cancelDraft,
       onCancel: note.cancelDraft,
       onSubmit: (comment) => {
       onSubmit: (comment) => {

+ 11 - 9
packages/ui/src/components/message-part.tsx

@@ -322,7 +322,7 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
     case "skill":
     case "skill":
       return {
       return {
         icon: "brain",
         icon: "brain",
-        title: input.name || "skill",
+        title: input.name || i18n.t("ui.tool.skill"),
       }
       }
     default:
     default:
       return {
       return {
@@ -924,15 +924,12 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
     const match = data.store.provider?.all?.find((p) => p.id === providerID)
     const match = data.store.provider?.all?.find((p) => p.id === providerID)
     return match?.models?.[modelID]?.name ?? modelID
     return match?.models?.[modelID]?.name ?? modelID
   })
   })
+  const timefmt = createMemo(() => new Intl.DateTimeFormat(i18n.locale(), { timeStyle: "short" }))
 
 
   const stamp = createMemo(() => {
   const stamp = createMemo(() => {
     const created = props.message.time?.created
     const created = props.message.time?.created
     if (typeof created !== "number") return ""
     if (typeof created !== "number") return ""
-    const date = new Date(created)
-    const hours = date.getHours()
-    const hour12 = hours % 12 || 12
-    const minute = String(date.getMinutes()).padStart(2, "0")
-    return `${hour12}:${minute} ${hours < 12 ? "AM" : "PM"}`
+    return timefmt().format(created)
   })
   })
 
 
   const metaHead = createMemo(() => {
   const metaHead = createMemo(() => {
@@ -1318,6 +1315,7 @@ PART_MAPPING["compaction"] = function CompactionPartDisplay() {
 PART_MAPPING["text"] = function TextPartDisplay(props) {
 PART_MAPPING["text"] = function TextPartDisplay(props) {
   const data = useData()
   const data = useData()
   const i18n = useI18n()
   const i18n = useI18n()
+  const numfmt = createMemo(() => new Intl.NumberFormat(i18n.locale()))
   const part = () => props.part as TextPart
   const part = () => props.part as TextPart
   const interrupted = createMemo(
   const interrupted = createMemo(
     () =>
     () =>
@@ -1343,10 +1341,13 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
           : -1
           : -1
     if (!(ms >= 0)) return ""
     if (!(ms >= 0)) return ""
     const total = Math.round(ms / 1000)
     const total = Math.round(ms / 1000)
-    if (total < 60) return `${total}s`
+    if (total < 60) return i18n.t("ui.message.duration.seconds", { count: numfmt().format(total) })
     const minutes = Math.floor(total / 60)
     const minutes = Math.floor(total / 60)
     const seconds = total % 60
     const seconds = total % 60
-    return `${minutes}m ${seconds}s`
+    return i18n.t("ui.message.duration.minutesSeconds", {
+      minutes: numfmt().format(minutes),
+      seconds: numfmt().format(seconds),
+    })
   })
   })
 
 
   const meta = createMemo(() => {
   const meta = createMemo(() => {
@@ -2206,7 +2207,8 @@ ToolRegistry.register({
 ToolRegistry.register({
 ToolRegistry.register({
   name: "skill",
   name: "skill",
   render(props) {
   render(props) {
-    const title = createMemo(() => props.input.name || "skill")
+    const i18n = useI18n()
+    const title = createMemo(() => props.input.name || i18n.t("ui.tool.skill"))
     const running = createMemo(() => props.status === "pending" || props.status === "running")
     const running = createMemo(() => props.status === "pending" || props.status === "running")
 
 
     const titleContent = () => <TextShimmer text={title()} active={running()} />
     const titleContent = () => <TextShimmer text={title()} active={running()} />

+ 10 - 6
packages/ui/src/components/tool-error-card.tsx

@@ -30,7 +30,7 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
       list: "ui.tool.list",
       list: "ui.tool.list",
       glob: "ui.tool.glob",
       glob: "ui.tool.glob",
       grep: "ui.tool.grep",
       grep: "ui.tool.grep",
-      task: "Task",
+      task: "ui.tool.task",
       webfetch: "ui.tool.webfetch",
       webfetch: "ui.tool.webfetch",
       websearch: "ui.tool.websearch",
       websearch: "ui.tool.websearch",
       codesearch: "ui.tool.codesearch",
       codesearch: "ui.tool.codesearch",
@@ -54,10 +54,10 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
   const subtitle = createMemo(() => {
   const subtitle = createMemo(() => {
     if (split.subtitle) return split.subtitle
     if (split.subtitle) return split.subtitle
     const parts = tail().split(": ")
     const parts = tail().split(": ")
-    if (parts.length <= 1) return "Failed"
+    if (parts.length <= 1) return i18n.t("ui.toolErrorCard.failed")
     const head = (parts[0] ?? "").trim()
     const head = (parts[0] ?? "").trim()
-    if (!head) return "Failed"
-    return head[0] ? head[0].toUpperCase() + head.slice(1) : "Failed"
+    if (!head) return i18n.t("ui.toolErrorCard.failed")
+    return head[0] ? head[0].toUpperCase() + head.slice(1) : i18n.t("ui.toolErrorCard.failed")
   })
   })
 
 
   const body = createMemo(() => {
   const body = createMemo(() => {
@@ -116,7 +116,11 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
           <div data-slot="tool-error-card-content">
           <div data-slot="tool-error-card-content">
             <Show when={open()}>
             <Show when={open()}>
               <div data-slot="tool-error-card-copy">
               <div data-slot="tool-error-card-copy">
-                <Tooltip value={copied() ? i18n.t("ui.message.copied") : "Copy error"} placement="top" gutter={4}>
+                <Tooltip
+                  value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.toolErrorCard.copyError")}
+                  placement="top"
+                  gutter={4}
+                >
                   <IconButton
                   <IconButton
                     icon={copied() ? "check" : "copy"}
                     icon={copied() ? "check" : "copy"}
                     size="normal"
                     size="normal"
@@ -126,7 +130,7 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
                       e.stopPropagation()
                       e.stopPropagation()
                       copy()
                       copy()
                     }}
                     }}
-                    aria-label={copied() ? i18n.t("ui.message.copied") : "Copy error"}
+                    aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.toolErrorCard.copyError")}
                   />
                   />
                 </Tooltip>
                 </Tooltip>
               </div>
               </div>

+ 12 - 0
packages/ui/src/i18n/ar.ts

@@ -145,4 +145,16 @@ export const dict = {
   "ui.question.multiHint": "حدد كل ما ينطبق",
   "ui.question.multiHint": "حدد كل ما ينطبق",
   "ui.question.singleHint": "حدد إجابة واحدة",
   "ui.question.singleHint": "حدد إجابة واحدة",
   "ui.question.custom.placeholder": "اكتب إجابتك...",
   "ui.question.custom.placeholder": "اكتب إجابتك...",
+
+  "ui.fileSearch.placeholder": "بحث",
+  "ui.fileSearch.previousMatch": "المطابقة السابقة",
+  "ui.fileSearch.nextMatch": "المطابقة التالية",
+  "ui.fileSearch.close": "إغلاق البحث",
+  "ui.tool.task": "مهمة",
+  "ui.tool.skill": "مهارة",
+  "ui.basicTool.called": "تم استدعاء `{{tool}}`",
+  "ui.toolErrorCard.failed": "فشل",
+  "ui.toolErrorCard.copyError": "نسخ الخطأ",
+  "ui.message.duration.seconds": "{{count}}ث",
+  "ui.message.duration.minutesSeconds": "{{minutes}}د {{seconds}}ث",
 }
 }

+ 12 - 0
packages/ui/src/i18n/br.ts

@@ -145,4 +145,16 @@ export const dict = {
   "ui.question.multiHint": "Selecione todas que se aplicam",
   "ui.question.multiHint": "Selecione todas que se aplicam",
   "ui.question.singleHint": "Selecione uma resposta",
   "ui.question.singleHint": "Selecione uma resposta",
   "ui.question.custom.placeholder": "Digite sua resposta...",
   "ui.question.custom.placeholder": "Digite sua resposta...",
+
+  "ui.fileSearch.placeholder": "Localizar",
+  "ui.fileSearch.previousMatch": "Ocorrência anterior",
+  "ui.fileSearch.nextMatch": "Próxima ocorrência",
+  "ui.fileSearch.close": "Fechar busca",
+  "ui.tool.task": "Tarefa",
+  "ui.tool.skill": "Habilidade",
+  "ui.basicTool.called": "Chamou `{{tool}}`",
+  "ui.toolErrorCard.failed": "Falhou",
+  "ui.toolErrorCard.copyError": "Copiar erro",
+  "ui.message.duration.seconds": "{{count}}s",
+  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
 }
 }

+ 12 - 0
packages/ui/src/i18n/bs.ts

@@ -149,4 +149,16 @@ export const dict = {
   "ui.question.multiHint": "Odaberi sve što važi",
   "ui.question.multiHint": "Odaberi sve što važi",
   "ui.question.singleHint": "Odaberi jedan odgovor",
   "ui.question.singleHint": "Odaberi jedan odgovor",
   "ui.question.custom.placeholder": "Unesi svoj odgovor...",
   "ui.question.custom.placeholder": "Unesi svoj odgovor...",
+
+  "ui.fileSearch.placeholder": "Pronađi",
+  "ui.fileSearch.previousMatch": "Prethodno",
+  "ui.fileSearch.nextMatch": "Sljedeće",
+  "ui.fileSearch.close": "Zatvori pretragu",
+  "ui.tool.task": "Zadatak",
+  "ui.tool.skill": "Vještina",
+  "ui.basicTool.called": "Pozvan `{{tool}}`",
+  "ui.toolErrorCard.failed": "Neuspješno",
+  "ui.toolErrorCard.copyError": "Kopiraj grešku",
+  "ui.message.duration.seconds": "{{count}}s",
+  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

+ 12 - 0
packages/ui/src/i18n/da.ts

@@ -144,4 +144,16 @@ export const dict = {
   "ui.question.multiHint": "Vælg alle der gælder",
   "ui.question.multiHint": "Vælg alle der gælder",
   "ui.question.singleHint": "Vælg ét svar",
   "ui.question.singleHint": "Vælg ét svar",
   "ui.question.custom.placeholder": "Skriv dit svar...",
   "ui.question.custom.placeholder": "Skriv dit svar...",
+
+  "ui.fileSearch.placeholder": "Find",
+  "ui.fileSearch.previousMatch": "Forrige match",
+  "ui.fileSearch.nextMatch": "Næste match",
+  "ui.fileSearch.close": "Luk søgning",
+  "ui.tool.task": "Opgave",
+  "ui.tool.skill": "Færdighed",
+  "ui.basicTool.called": "Kaldte `{{tool}}`",
+  "ui.toolErrorCard.failed": "Fejlede",
+  "ui.toolErrorCard.copyError": "Kopier fejl",
+  "ui.message.duration.seconds": "{{count}}s",
+  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
 }
 }

+ 12 - 0
packages/ui/src/i18n/de.ts

@@ -150,4 +150,16 @@ export const dict = {
   "ui.question.multiHint": "Alle zutreffenden auswählen",
   "ui.question.multiHint": "Alle zutreffenden auswählen",
   "ui.question.singleHint": "Eine Antwort auswählen",
   "ui.question.singleHint": "Eine Antwort auswählen",
   "ui.question.custom.placeholder": "Geben Sie Ihre Antwort ein...",
   "ui.question.custom.placeholder": "Geben Sie Ihre Antwort ein...",
+
+  "ui.fileSearch.placeholder": "Suchen",
+  "ui.fileSearch.previousMatch": "Vorheriges Ergebnis",
+  "ui.fileSearch.nextMatch": "Nächstes Ergebnis",
+  "ui.fileSearch.close": "Suche schließen",
+  "ui.tool.task": "Aufgabe",
+  "ui.tool.skill": "Fähigkeit",
+  "ui.basicTool.called": "`{{tool}}` aufgerufen",
+  "ui.toolErrorCard.failed": "Fehlgeschlagen",
+  "ui.toolErrorCard.copyError": "Fehler kopieren",
+  "ui.message.duration.seconds": "{{count}}s",
+  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

+ 13 - 0
packages/ui/src/i18n/en.ts

@@ -80,6 +80,11 @@ export const dict: Record<string, string> = {
   "ui.list.emptyWithFilter.prefix": "No results for",
   "ui.list.emptyWithFilter.prefix": "No results for",
   "ui.list.emptyWithFilter.suffix": "",
   "ui.list.emptyWithFilter.suffix": "",
 
 
+  "ui.fileSearch.placeholder": "Find",
+  "ui.fileSearch.previousMatch": "Previous match",
+  "ui.fileSearch.nextMatch": "Next match",
+  "ui.fileSearch.close": "Close search",
+
   "ui.messageNav.newMessage": "New message",
   "ui.messageNav.newMessage": "New message",
 
 
   "ui.textField.copyToClipboard": "Copy to clipboard",
   "ui.textField.copyToClipboard": "Copy to clipboard",
@@ -94,6 +99,7 @@ export const dict: Record<string, string> = {
   "ui.tool.list": "List",
   "ui.tool.list": "List",
   "ui.tool.glob": "Glob",
   "ui.tool.glob": "Glob",
   "ui.tool.grep": "Grep",
   "ui.tool.grep": "Grep",
+  "ui.tool.task": "Task",
   "ui.tool.webfetch": "Webfetch",
   "ui.tool.webfetch": "Webfetch",
   "ui.tool.websearch": "Web Search",
   "ui.tool.websearch": "Web Search",
   "ui.tool.codesearch": "Code Search",
   "ui.tool.codesearch": "Code Search",
@@ -104,6 +110,11 @@ export const dict: Record<string, string> = {
   "ui.tool.questions": "Questions",
   "ui.tool.questions": "Questions",
   "ui.tool.agent": "{{type}} Agent",
   "ui.tool.agent": "{{type}} Agent",
   "ui.tool.agent.default": "Agent",
   "ui.tool.agent.default": "Agent",
+  "ui.tool.skill": "Skill",
+
+  "ui.basicTool.called": "Called `{{tool}}`",
+  "ui.toolErrorCard.failed": "Failed",
+  "ui.toolErrorCard.copyError": "Copy error",
 
 
   "ui.common.file.one": "file",
   "ui.common.file.one": "file",
   "ui.common.file.other": "files",
   "ui.common.file.other": "files",
@@ -131,6 +142,8 @@ export const dict: Record<string, string> = {
   "ui.message.revertMessage": "Revert message",
   "ui.message.revertMessage": "Revert message",
   "ui.message.copyResponse": "Copy response",
   "ui.message.copyResponse": "Copy response",
   "ui.message.copied": "Copied",
   "ui.message.copied": "Copied",
+  "ui.message.duration.seconds": "{{count}}s",
+  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
   "ui.message.interrupted": "Interrupted",
   "ui.message.interrupted": "Interrupted",
   "ui.message.queued": "Queued",
   "ui.message.queued": "Queued",
   "ui.message.attachment.alt": "attachment",
   "ui.message.attachment.alt": "attachment",

+ 12 - 0
packages/ui/src/i18n/es.ts

@@ -145,4 +145,16 @@ export const dict = {
   "ui.question.multiHint": "Selecciona todas las que correspondan",
   "ui.question.multiHint": "Selecciona todas las que correspondan",
   "ui.question.singleHint": "Selecciona una respuesta",
   "ui.question.singleHint": "Selecciona una respuesta",
   "ui.question.custom.placeholder": "Escribe tu respuesta...",
   "ui.question.custom.placeholder": "Escribe tu respuesta...",
+
+  "ui.fileSearch.placeholder": "Buscar",
+  "ui.fileSearch.previousMatch": "Anterior",
+  "ui.fileSearch.nextMatch": "Siguiente",
+  "ui.fileSearch.close": "Cerrar búsqueda",
+  "ui.tool.task": "Tarea",
+  "ui.tool.skill": "Habilidad",
+  "ui.basicTool.called": "Llamado `{{tool}}`",
+  "ui.toolErrorCard.failed": "Falló",
+  "ui.toolErrorCard.copyError": "Copiar error",
+  "ui.message.duration.seconds": "{{count}}s",
+  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
 }
 }

+ 12 - 0
packages/ui/src/i18n/fr.ts

@@ -145,4 +145,16 @@ export const dict = {
   "ui.question.multiHint": "Sélectionnez tout ce qui s'applique",
   "ui.question.multiHint": "Sélectionnez tout ce qui s'applique",
   "ui.question.singleHint": "Sélectionnez une réponse",
   "ui.question.singleHint": "Sélectionnez une réponse",
   "ui.question.custom.placeholder": "Tapez votre réponse...",
   "ui.question.custom.placeholder": "Tapez votre réponse...",
+
+  "ui.fileSearch.placeholder": "Rechercher",
+  "ui.fileSearch.previousMatch": "Précédent",
+  "ui.fileSearch.nextMatch": "Suivant",
+  "ui.fileSearch.close": "Fermer la recherche",
+  "ui.tool.task": "Tâche",
+  "ui.tool.skill": "Compétence",
+  "ui.basicTool.called": "Appelé `{{tool}}`",
+  "ui.toolErrorCard.failed": "Échoué",
+  "ui.toolErrorCard.copyError": "Copier l'erreur",
+  "ui.message.duration.seconds": "{{count}}s",
+  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
 }
 }

+ 12 - 0
packages/ui/src/i18n/ja.ts

@@ -144,4 +144,16 @@ export const dict = {
   "ui.question.multiHint": "該当するものをすべて選択",
   "ui.question.multiHint": "該当するものをすべて選択",
   "ui.question.singleHint": "1 つ選択",
   "ui.question.singleHint": "1 つ選択",
   "ui.question.custom.placeholder": "回答を入力...",
   "ui.question.custom.placeholder": "回答を入力...",
+
+  "ui.fileSearch.placeholder": "検索",
+  "ui.fileSearch.previousMatch": "前の一致",
+  "ui.fileSearch.nextMatch": "次の一致",
+  "ui.fileSearch.close": "検索を閉じる",
+  "ui.tool.task": "タスク",
+  "ui.tool.skill": "スキル",
+  "ui.basicTool.called": "`{{tool}}` を呼び出しました",
+  "ui.toolErrorCard.failed": "失敗",
+  "ui.toolErrorCard.copyError": "エラーをコピー",
+  "ui.message.duration.seconds": "{{count}}秒",
+  "ui.message.duration.minutesSeconds": "{{minutes}}分 {{seconds}}秒",
 }
 }

+ 12 - 0
packages/ui/src/i18n/ko.ts

@@ -145,4 +145,16 @@ export const dict = {
   "ui.question.multiHint": "해당하는 항목 모두 선택",
   "ui.question.multiHint": "해당하는 항목 모두 선택",
   "ui.question.singleHint": "하나의 답변을 선택",
   "ui.question.singleHint": "하나의 답변을 선택",
   "ui.question.custom.placeholder": "답변 입력...",
   "ui.question.custom.placeholder": "답변 입력...",
+
+  "ui.fileSearch.placeholder": "찾기",
+  "ui.fileSearch.previousMatch": "이전 항목",
+  "ui.fileSearch.nextMatch": "다음 항목",
+  "ui.fileSearch.close": "검색 닫기",
+  "ui.tool.task": "작업",
+  "ui.tool.skill": "스킬",
+  "ui.basicTool.called": "`{{tool}}` 호출됨",
+  "ui.toolErrorCard.failed": "실패",
+  "ui.toolErrorCard.copyError": "오류 복사",
+  "ui.message.duration.seconds": "{{count}}초",
+  "ui.message.duration.minutesSeconds": "{{minutes}}분 {{seconds}}초",
 }
 }

+ 12 - 0
packages/ui/src/i18n/no.ts

@@ -148,4 +148,16 @@ export const dict: Record<Keys, string> = {
   "ui.question.multiHint": "Velg alle som gjelder",
   "ui.question.multiHint": "Velg alle som gjelder",
   "ui.question.singleHint": "Velg ett svar",
   "ui.question.singleHint": "Velg ett svar",
   "ui.question.custom.placeholder": "Skriv svaret ditt...",
   "ui.question.custom.placeholder": "Skriv svaret ditt...",
+
+  "ui.fileSearch.placeholder": "Finn",
+  "ui.fileSearch.previousMatch": "Forrige treff",
+  "ui.fileSearch.nextMatch": "Neste treff",
+  "ui.fileSearch.close": "Lukk søk",
+  "ui.tool.task": "Oppgave",
+  "ui.tool.skill": "Ferdighet",
+  "ui.basicTool.called": "Kalte `{{tool}}`",
+  "ui.toolErrorCard.failed": "Mislyktes",
+  "ui.toolErrorCard.copyError": "Kopier feil",
+  "ui.message.duration.seconds": "{{count}}s",
+  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
 }
 }

+ 12 - 0
packages/ui/src/i18n/pl.ts

@@ -144,4 +144,16 @@ export const dict = {
   "ui.question.multiHint": "Zaznacz wszystkie pasujące",
   "ui.question.multiHint": "Zaznacz wszystkie pasujące",
   "ui.question.singleHint": "Wybierz jedną odpowiedź",
   "ui.question.singleHint": "Wybierz jedną odpowiedź",
   "ui.question.custom.placeholder": "Wpisz swoją odpowiedź...",
   "ui.question.custom.placeholder": "Wpisz swoją odpowiedź...",
+
+  "ui.fileSearch.placeholder": "Szukaj",
+  "ui.fileSearch.previousMatch": "Poprzednie",
+  "ui.fileSearch.nextMatch": "Następne",
+  "ui.fileSearch.close": "Zamknij wyszukiwanie",
+  "ui.tool.task": "Zadanie",
+  "ui.tool.skill": "Umiejętność",
+  "ui.basicTool.called": "Wywołano `{{tool}}`",
+  "ui.toolErrorCard.failed": "Błąd",
+  "ui.toolErrorCard.copyError": "Kopiuj błąd",
+  "ui.message.duration.seconds": "{{count}}s",
+  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
 }
 }

+ 12 - 0
packages/ui/src/i18n/ru.ts

@@ -144,4 +144,16 @@ export const dict = {
   "ui.question.multiHint": "Выберите все подходящие",
   "ui.question.multiHint": "Выберите все подходящие",
   "ui.question.singleHint": "Выберите один ответ",
   "ui.question.singleHint": "Выберите один ответ",
   "ui.question.custom.placeholder": "Введите ваш ответ...",
   "ui.question.custom.placeholder": "Введите ваш ответ...",
+
+  "ui.fileSearch.placeholder": "Найти",
+  "ui.fileSearch.previousMatch": "Предыдущее",
+  "ui.fileSearch.nextMatch": "Следующее",
+  "ui.fileSearch.close": "Закрыть поиск",
+  "ui.tool.task": "Задача",
+  "ui.tool.skill": "Навык",
+  "ui.basicTool.called": "Вызван `{{tool}}`",
+  "ui.toolErrorCard.failed": "Ошибка",
+  "ui.toolErrorCard.copyError": "Скопировать ошибку",
+  "ui.message.duration.seconds": "{{count}}с",
+  "ui.message.duration.minutesSeconds": "{{minutes}}м {{seconds}}с",
 }
 }

+ 12 - 0
packages/ui/src/i18n/th.ts

@@ -146,4 +146,16 @@ export const dict = {
   "ui.question.multiHint": "เลือกทั้งหมดที่ใช้",
   "ui.question.multiHint": "เลือกทั้งหมดที่ใช้",
   "ui.question.singleHint": "เลือกหนึ่งคำตอบ",
   "ui.question.singleHint": "เลือกหนึ่งคำตอบ",
   "ui.question.custom.placeholder": "พิมพ์คำตอบของคุณ...",
   "ui.question.custom.placeholder": "พิมพ์คำตอบของคุณ...",
+
+  "ui.fileSearch.placeholder": "ค้นหา",
+  "ui.fileSearch.previousMatch": "ก่อนหน้า",
+  "ui.fileSearch.nextMatch": "ถัดไป",
+  "ui.fileSearch.close": "ปิดการค้นหา",
+  "ui.tool.task": "งาน",
+  "ui.tool.skill": "ทักษะ",
+  "ui.basicTool.called": "เรียกใช้ `{{tool}}`",
+  "ui.toolErrorCard.failed": "ล้มเหลว",
+  "ui.toolErrorCard.copyError": "คัดลอกข้อผิดพลาด",
+  "ui.message.duration.seconds": "{{count}}วิ",
+  "ui.message.duration.minutesSeconds": "{{minutes}}นาที {{seconds}}วิ",
 }
 }

+ 12 - 0
packages/ui/src/i18n/tr.ts

@@ -151,4 +151,16 @@ export const dict = {
   "ui.question.multiHint": "Geçerli tüm cevapları seçin",
   "ui.question.multiHint": "Geçerli tüm cevapları seçin",
   "ui.question.singleHint": "Bir cevap seçin",
   "ui.question.singleHint": "Bir cevap seçin",
   "ui.question.custom.placeholder": "Cevabınızı yazın...",
   "ui.question.custom.placeholder": "Cevabınızı yazın...",
+
+  "ui.fileSearch.placeholder": "Bul",
+  "ui.fileSearch.previousMatch": "Önceki",
+  "ui.fileSearch.nextMatch": "Sonraki",
+  "ui.fileSearch.close": "Aramayı kapat",
+  "ui.tool.task": "Görev",
+  "ui.tool.skill": "Yetenek",
+  "ui.basicTool.called": "`{{tool}}` çağrıldı",
+  "ui.toolErrorCard.failed": "Başarısız",
+  "ui.toolErrorCard.copyError": "Hatayı kopyala",
+  "ui.message.duration.seconds": "{{count}}sn",
+  "ui.message.duration.minutesSeconds": "{{minutes}}dk {{seconds}}sn",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

+ 12 - 0
packages/ui/src/i18n/zh.ts

@@ -149,4 +149,16 @@ export const dict = {
   "ui.question.multiHint": "可多选",
   "ui.question.multiHint": "可多选",
   "ui.question.singleHint": "选择一个答案",
   "ui.question.singleHint": "选择一个答案",
   "ui.question.custom.placeholder": "输入你的答案...",
   "ui.question.custom.placeholder": "输入你的答案...",
+
+  "ui.fileSearch.placeholder": "查找",
+  "ui.fileSearch.previousMatch": "上一个",
+  "ui.fileSearch.nextMatch": "下一个",
+  "ui.fileSearch.close": "关闭搜索",
+  "ui.tool.task": "任务",
+  "ui.tool.skill": "技能",
+  "ui.basicTool.called": "调用了 `{{tool}}`",
+  "ui.toolErrorCard.failed": "失败",
+  "ui.toolErrorCard.copyError": "复制错误",
+  "ui.message.duration.seconds": "{{count}}秒",
+  "ui.message.duration.minutesSeconds": "{{minutes}}分 {{seconds}}秒",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

+ 12 - 0
packages/ui/src/i18n/zht.ts

@@ -149,4 +149,16 @@ export const dict = {
   "ui.question.multiHint": "可多選",
   "ui.question.multiHint": "可多選",
   "ui.question.singleHint": "選擇一個答案",
   "ui.question.singleHint": "選擇一個答案",
   "ui.question.custom.placeholder": "輸入你的答案...",
   "ui.question.custom.placeholder": "輸入你的答案...",
+
+  "ui.fileSearch.placeholder": "搜尋",
+  "ui.fileSearch.previousMatch": "上一個",
+  "ui.fileSearch.nextMatch": "下一個",
+  "ui.fileSearch.close": "關閉搜尋",
+  "ui.tool.task": "任務",
+  "ui.tool.skill": "技能",
+  "ui.basicTool.called": "呼叫了 `{{tool}}`",
+  "ui.toolErrorCard.failed": "失敗",
+  "ui.toolErrorCard.copyError": "複製錯誤",
+  "ui.message.duration.seconds": "{{count}}秒",
+  "ui.message.duration.minutesSeconds": "{{minutes}}分 {{seconds}}秒",
 } satisfies Partial<Record<Keys, string>>
 } satisfies Partial<Record<Keys, string>>

+ 6 - 3
packages/ui/src/pierre/selection-bridge.ts

@@ -1,14 +1,17 @@
 import { type SelectedLineRange } from "@pierre/diffs"
 import { type SelectedLineRange } from "@pierre/diffs"
 
 
+type SelectionKey = "ui.sessionReview.selection.line" | "ui.sessionReview.selection.lines"
+type SelectionVars = Record<string, string | number>
+
 type PointerMode = "none" | "text" | "numbers"
 type PointerMode = "none" | "text" | "numbers"
 type Side = SelectedLineRange["side"]
 type Side = SelectedLineRange["side"]
 type LineSpan = Pick<SelectedLineRange, "start" | "end">
 type LineSpan = Pick<SelectedLineRange, "start" | "end">
 
 
-export function formatSelectedLineLabel(range: LineSpan) {
+export function formatSelectedLineLabel(range: LineSpan, t: (key: SelectionKey, params: SelectionVars) => string) {
   const start = Math.min(range.start, range.end)
   const start = Math.min(range.start, range.end)
   const end = Math.max(range.start, range.end)
   const end = Math.max(range.start, range.end)
-  if (start === end) return `line ${start}`
-  return `lines ${start}-${end}`
+  if (start === end) return t("ui.sessionReview.selection.line", { line: start })
+  return t("ui.sessionReview.selection.lines", { start, end })
 }
 }
 
 
 export function previewSelectedLines(source: string, range: LineSpan) {
 export function previewSelectedLines(source: string, range: LineSpan) {