From ceb4890ca3651899dd3e2b1564168ab098ac540d Mon Sep 17 00:00:00 2001 From: Deven Navani <4031146+devennavani@users.noreply.github.com> Date: Thu, 30 Jul 2026 11:29:32 -0700 Subject: [PATCH 001/114] docs: add Modal provider setup (#39710) --- packages/web/src/content/docs/providers.mdx | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index d3c541311ff..7931d4e03f6 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -1505,6 +1505,30 @@ To use Kimi K2 from Moonshot AI: --- +### Modal + +1. Create a [shared Endpoint](https://modal.com/endpoints) for the model you want to use. + +2. Create a [proxy token](https://modal.com/docs/guide/endpoints#proxy-tokens), then join its ID and secret with a period: + + ```txt + wk-.ws- + ``` + +3. Run the `/connect` command, search for **Modal**, and enter the combined proxy token. + + ```txt + /connect + ``` + +4. Run the `/models` command to select one of the endpoints in your Modal workspace. + + ```txt + /models + ``` + +--- + ### NVIDIA NVIDIA provides access to Nemotron models and many other open models through [build.nvidia.com](https://build.nvidia.com) for free. From e024e2ef92293a81a06b6cc418422c52207ce85d Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:46:36 +1000 Subject: [PATCH 002/114] fix(app): align debug gutter text (#39782) --- packages/app/src/components/debug-bar.tsx | 54 +++++++++++++++-------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx index 67c9a899294..adadeda3035 100644 --- a/packages/app/src/components/debug-bar.tsx +++ b/packages/app/src/components/debug-bar.tsx @@ -66,7 +66,7 @@ function Cell(props: {
- {props.label} -
-
- {props.value} +
+ {props.label} +
+
+ {props.value} +
) @@ -116,15 +124,25 @@ function FocusCell(props: { active: boolean; inline?: boolean; onClick: () => vo aria-pressed={props.active} classList={{ "flex min-w-0 items-center font-mono uppercase hover:bg-surface-raised-base focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-border-focus": true, - "min-h-[20px] w-fit flex-row justify-start gap-1.5 rounded px-1.5 py-0.5 text-left": !!props.inline, + "min-h-[20px] w-fit justify-start rounded px-1.5 py-0.5 text-left": !!props.inline, "min-h-[42px] w-full flex-col justify-center rounded-[8px] px-0.5 py-1 text-center": !props.inline, "bg-surface-raised-base text-text-strong": props.active, }} onClick={props.onClick} > - FOCUS - - {props.active ? "ON" : "OFF"} + + FOCUS + + {props.active ? "ON" : "OFF"} + ) From 7ea343cabd057e29d4f34d07d7aeae832293bfae Mon Sep 17 00:00:00 2001 From: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Date: Fri, 31 Jul 2026 06:24:19 +0200 Subject: [PATCH 003/114] fix(app): prevent file tree tab clipping (#39770) --- packages/app/src/pages/session/session-side-panel.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index 22c52e73feb..6f074fa2030 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -59,6 +59,7 @@ import { SessionFileBrowserTab, type SessionFileBrowserState } from "@/pages/ses type ReviewDiff = FileDiffInfo | SnapshotFileDiff | VcsFileDiff type RenderDiff = FileDiffInfo | (SnapshotFileDiff & { file: string }) | VcsFileDiff +const FILE_TREE_WIDTH_MIN = 240 function renderDiff(value: ReviewDiff): value is RenderDiff { return typeof value.file === "string" @@ -104,13 +105,14 @@ export function SessionSidePanel(props: { }), ) const open = createMemo(() => reviewOpen() || fileOpen()) + const fileTreeWidth = createMemo(() => Math.max(FILE_TREE_WIDTH_MIN, layout.fileTree.width())) const reviewTab = createMemo(() => isDesktop()) const panelWidth = createMemo(() => { if (!open()) return "0px" if (reviewOpen()) return "auto" - return `${layout.fileTree.width()}px` + return `${fileTreeWidth()}px` }) - const treeWidth = createMemo(() => (fileOpen() ? `${layout.fileTree.width()}px` : "0px")) + const treeWidth = createMemo(() => (fileOpen() ? `${fileTreeWidth()}px` : "0px")) const diffs = createMemo(() => props.diffs().filter(renderDiff)) const diffFiles = createMemo(() => diffs().map((d) => d.file)) @@ -845,8 +847,8 @@ export function SessionSidePanel(props: { { props.size.touch() From d3f30df1806ea9f95190948c59f6e8b4c97301d0 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 04:38:34 +0000 Subject: [PATCH 004/114] fix(app): open legacy picker at home (#39804) Co-authored-by: Brendan Allan --- packages/app/src/components/dialog-select-directory.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/dialog-select-directory.tsx b/packages/app/src/components/dialog-select-directory.tsx index 2a6c877df1d..96bcedb9db5 100644 --- a/packages/app/src/components/dialog-select-directory.tsx +++ b/packages/app/src/components/dialog-select-directory.tsx @@ -57,9 +57,9 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) { const [filter, setFilter] = createSignal("") let list: ListRef | undefined - const missingBase = createMemo(() => !(sync.data.path.home || sync.data.path.directory)) + const missingHome = createMemo(() => !sync.data.path.home) const [fallbackPath] = createResource( - () => (missingBase() ? true : undefined), + () => (missingHome() ? true : undefined), async (): Promise => { if ((await sdk.protocol) !== "v1") return return sdk.client.path From db4dbaa2894a1a98e90e814416eb9f7c35711819 Mon Sep 17 00:00:00 2001 From: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Date: Fri, 31 Jul 2026 07:14:09 +0200 Subject: [PATCH 005/114] fix(app): prevent stale session tab reads (#39767) --- .../app/src/components/session/session-header.tsx | 12 ++++++------ packages/app/src/components/titlebar-tab-nav.tsx | 5 +++-- .../app/src/pages/new-session/new-session-view.tsx | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index da1770cd130..dc6cdaa6f3f 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -289,9 +289,9 @@ export function SessionHeader() { return ( <> - + {(mount) => ( - + @@ -89,7 +91,7 @@ export function TabsInfoPopup() { type="button" size="small" variant="neutral" - aria-label="Close" + aria-label={language.t("common.close")} icon={} class="absolute top-[10px] left-[-36px]" /> @@ -102,7 +104,7 @@ export function TabsInfoPopup() { }} >

- July 14 + {language.t("help.tabs.date")}

} />

- Introducing Tabs + {language.t("help.tabs.title")}

-

OpenCode Desktop is now built around tabs.

+

{language.t("help.tabs.introduction")}

-

- Start a new session in a tab, or open an existing session from any of your projects. Open a new tab when - you're starting something new, and close it when you're done. -

-

- Keeping a few tabs open makes it easier to organize your active sessions. Rename tabs to something - memorable if you plan to keep them around. -

-

- You'll find all your sessions and projects on the new Home screen. Selecting a session opens it in a tab. -

+

{language.t("help.tabs.sessions")}

+

{language.t("help.tabs.organize")}

+

{language.t("help.tabs.home")}

-

When you reopen the app, your tabs are still open.

-

- The new design does not support Git Worktrees yet, it's coming soon. So if you'd prefer to continue using - the previous layout, you can switch between layouts in Settings. Just keep in mind that the new layout - will become permanent in a few weeks. -

+

{language.t("help.tabs.persistence")}

+

{language.t("help.tabs.worktrees")}

diff --git a/packages/app/src/components/prompt-input-v2.tsx b/packages/app/src/components/prompt-input-v2.tsx index 96b99aaf4a2..481b7922cf3 100644 --- a/packages/app/src/components/prompt-input-v2.tsx +++ b/packages/app/src/components/prompt-input-v2.tsx @@ -137,7 +137,10 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): t: (key, params) => language.t(key as Parameters[0], params as never), }), ) - const designPlaceholder = () => promptDesignPlaceholder(mode(), placeholder()) + const designPlaceholder = () => + promptDesignPlaceholder(mode(), placeholder(), (key, params) => + language.t(key as Parameters[0], params as never), + ) const historyComments = () => { const byID = new Map(comments.all().map((item) => [`${item.file}\n${item.id}`, item] as const)) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 08c1ee34891..41cf7d30ca3 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -77,7 +77,7 @@ import { PromptPopover, type AtOption, type SlashCommand } from "./prompt-input/ import { PromptContextItems } from "./prompt-input/context-items" import { PromptImageAttachments } from "./prompt-input/image-attachments" import { PromptDragOverlay } from "./prompt-input/drag-overlay" -import { promptDesignPlaceholder, promptPlaceholder } from "./prompt-input/placeholder" +import { promptPlaceholder } from "./prompt-input/placeholder" import { createPromptInputTransientState } from "./prompt-input/transient-state" import { showToast } from "@/utils/toast" import { ImagePreview } from "@opencode-ai/ui/image-preview" @@ -1493,6 +1493,7 @@ export const PromptInput: Component = (props) => { } onRemove={removeAttachment} removeLabel={language.t("prompt.attachment.remove")} + fileLabel={language.t("ui.common.file")} newLayoutDesigns={false} />
void onRemove: (id: string) => void removeLabel: string + fileLabel: string newLayoutDesigns: boolean comments?: PromptCommentItem[] commentActive?: (item: PromptCommentItem) => boolean @@ -94,7 +95,7 @@ export const PromptImageAttachments: Component = (p } > - {typeLabel(attachment.filename, attachment.mime)} + {typeLabel(attachment.filename, attachment.mime, props.fileLabel)} } diff --git a/packages/app/src/components/prompt-input/placeholder.test.ts b/packages/app/src/components/prompt-input/placeholder.test.ts index d4caead0d2e..b8f0537d0a0 100644 --- a/packages/app/src/components/prompt-input/placeholder.test.ts +++ b/packages/app/src/components/prompt-input/placeholder.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { promptPlaceholder } from "./placeholder" +import { promptDesignPlaceholder, promptPlaceholder } from "./placeholder" describe("promptPlaceholder", () => { const t = (key: string, params?: Record) => `${key}${params?.example ? `:${params.example}` : ""}` @@ -46,3 +46,20 @@ describe("promptPlaceholder", () => { expect(value).toBe("prompt.placeholder.simple") }) }) + +describe("promptDesignPlaceholder", () => { + const t = (key: string, params?: Record) => { + if (key !== "ui.promptInput.placeholder.normal") return key + return `Ask anything, ${params?.slash} for commands, ${params?.at} for context...` + } + + test("composes the design placeholder from localized fragments", () => { + expect(promptDesignPlaceholder("normal", "fallback", t)).toBe( + "Ask anything, / for commands, @ for context...", + ) + }) + + test("preserves the shell placeholder", () => { + expect(promptDesignPlaceholder("shell", "Enter shell command...", t)).toBe("Enter shell command...") + }) +}) diff --git a/packages/app/src/components/prompt-input/placeholder.ts b/packages/app/src/components/prompt-input/placeholder.ts index cbb4c45b7e3..9bd4417f498 100644 --- a/packages/app/src/components/prompt-input/placeholder.ts +++ b/packages/app/src/components/prompt-input/placeholder.ts @@ -14,7 +14,11 @@ export function promptPlaceholder(input: PromptPlaceholderInput) { return input.t("prompt.placeholder.normal", { example: input.example }) } -export function promptDesignPlaceholder(mode: PromptPlaceholderInput["mode"], placeholder: string) { +export function promptDesignPlaceholder( + mode: PromptPlaceholderInput["mode"], + placeholder: string, + t: PromptPlaceholderInput["t"], +) { if (mode === "shell") return placeholder - return "Ask anything, / for commands, @ for context..." + return t("ui.promptInput.placeholder.normal", { slash: "/", at: "@" }) } diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index d0887470155..0ca42b35bb1 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -573,9 +573,8 @@ export const Terminal = (props: TerminalProps) => { if (!result) return if (result.response.status === 200 && result.data?.ticket) return result.data.ticket if (result.response.status === 404 || result.response.status === 405) return - if (result.response.status === 403) - throw new Error("PTY connect ticket rejected by origin or CSRF checks. Check the server CORS config.") - throw new Error(`PTY connect ticket failed with ${result.response.status}`) + if (result.response.status === 403) throw new Error(language.t("terminal.connectTicket.csrfError")) + throw new Error(language.t("terminal.connectTicket.statusError", { status: result.response.status })) } // return sdk() // .api.pty.connectToken({ diff --git a/packages/app/src/components/titlebar-tab-nav.tsx b/packages/app/src/components/titlebar-tab-nav.tsx index 5003d0ed303..65493c54dff 100644 --- a/packages/app/src/components/titlebar-tab-nav.tsx +++ b/packages/app/src/components/titlebar-tab-nav.tsx @@ -5,6 +5,7 @@ import { createMutation } from "@tanstack/solid-query" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { useGlobal } from "@/context/global" +import { useLanguage } from "@/context/language" import { ServerConnection, serverName } from "@/context/server" import { displayName, projectForSession } from "@/pages/layout/helpers" import { SessionTabAvatar } from "@/pages/layout/session-tab-avatar" @@ -323,6 +324,7 @@ export function DraftTabItem(props: { pressed?: boolean hidden?: boolean }) { + const language = useLanguage() const closeTab = (event: MouseEvent) => { event.preventDefault() event.stopPropagation() @@ -397,7 +399,7 @@ export function DraftTabItem(props: { class="hover-reveal relative z-10 group-hover:opacity-100 group-data-[active=true]:opacity-100 group-data-[editing=true]:opacity-100" onClick={closeTab} icon={} - aria-label="Close tab" + aria-label={language.t("common.closeTab")} />
diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 649a8f57773..8bc9d01eb52 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -113,9 +113,9 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl return { visible: version !== undefined || installing, installing, - label: "Update", + label: language.t("titlebar.update"), ariaLabel: language.t("toast.update.action.installRestart"), - title: version ? `Update ${version}` : undefined, + title: version ? language.t("titlebar.updateVersion", { version }) : undefined, onInstall: () => props.update?.install(), } }) diff --git a/packages/app/src/components/windows-app-menu.tsx b/packages/app/src/components/windows-app-menu.tsx index e41c2a584a7..b6088c57f43 100644 --- a/packages/app/src/components/windows-app-menu.tsx +++ b/packages/app/src/components/windows-app-menu.tsx @@ -8,6 +8,7 @@ import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { useCommand } from "@/context/command" import { DESKTOP_MENU, desktopMenuVisible, type DesktopMenuAction, type DesktopMenuEntry } from "@/desktop-menu" import { usePlatform } from "@/context/platform" +import { useLanguage } from "@/context/language" export function WindowsAppMenu(props: { command: ReturnType @@ -15,6 +16,7 @@ export function WindowsAppMenu(props: { variant?: "legacy" | "v2" }) { let lastFocused: HTMLElement | undefined + const language = useLanguage() const rememberFocus = () => { const active = document.activeElement @@ -58,7 +60,7 @@ export function WindowsAppMenu(props: { variant="ghost-muted" size="large" icon={} - aria-label="OpenCode menu" + aria-label={language.t("desktop.menu.ariaLabel")} onPointerDown={rememberFocus} onKeyDown={rememberFocus} /> @@ -69,7 +71,7 @@ export function WindowsAppMenu(props: { icon="menu" variant="ghost" class="titlebar-icon rounded-md shrink-0" - aria-label="OpenCode menu" + aria-label={language.t("desktop.menu.ariaLabel")} onPointerDown={rememberFocus} onKeyDown={rememberFocus} /> @@ -79,7 +81,7 @@ export function WindowsAppMenu(props: { OpenCode {DESKTOP_MENU.filter((menu) => desktopMenuVisible(menu, "windows")).map((menu) => ( - + {menu.items ?.filter((entry) => desktopMenuVisible(entry, "windows")) .map((entry) => @@ -87,7 +89,7 @@ export function WindowsAppMenu(props: { ) : ( runEntry(entry)} diff --git a/packages/app/src/constants/file-picker.ts b/packages/app/src/constants/file-picker.ts index c661bc8f36b..1e029b551cc 100644 --- a/packages/app/src/constants/file-picker.ts +++ b/packages/app/src/constants/file-picker.ts @@ -83,7 +83,7 @@ export const ACCEPTED_FILE_EXTENSIONS = Array.from( ), ).sort() -export function filePickerFilters(ext?: string[]) { +export function filePickerFilters(name: string, ext?: string[]) { if (!ext || ext.length === 0) return undefined - return [{ name: "Files", extensions: ext }] + return [{ name, extensions: ext }] } diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx index f1c9ddcfd3a..adab9ddb72f 100644 --- a/packages/app/src/context/language.tsx +++ b/packages/app/src/context/language.tsx @@ -1,40 +1,20 @@ import * as i18n from "@solid-primitives/i18n" import { createEffect, createMemo, createResource } from "solid-js" import { createStore } from "solid-js/store" -import { createSimpleContext, pluralCategory, type UiI18nPluralKey } from "@opencode-ai/ui/context" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { pluralCategory, type UiI18nPluralKey } from "@opencode-ai/ui/context/i18n" import { Persist, persisted } from "@/utils/persist" import { dict as en } from "@/i18n/en" import { dict as uiEn } from "@opencode-ai/ui/i18n/en" +import { + createDesktopNativeBundle, + DESKTOP_NATIVE_ENGLISH, + DESKTOP_NATIVE_LOCALES, + type DesktopNativeBundle, + type DesktopNativeLocale, +} from "@/i18n/desktop-native" -export type Locale = - | "en" - | "zh" - | "zht" - | "ko" - | "de" - | "es" - | "fr" - | "da" - | "ja" - | "pl" - | "ru" - | "uk" - | "ar" - | "no" - | "br" - | "th" - | "bs" - | "tr" - | "hi" - | "nl" - | "id" - | "vi" - | "it" - | "ur" - | "pa" - | "az" - | "fi" - | "sv" +export type Locale = DesktopNativeLocale const RTL_LOCALES: ReadonlySet = new Set(["ar", "ur", "pa"]) @@ -51,36 +31,7 @@ function cookie(locale: Locale) { return `oc_locale=${encodeURIComponent(locale)}; Path=/; Max-Age=31536000; SameSite=Lax` } -const LOCALES: readonly Locale[] = [ - "en", - "zh", - "zht", - "ko", - "de", - "es", - "fr", - "da", - "ja", - "pl", - "ru", - "uk", - "bs", - "ar", - "no", - "br", - "th", - "tr", - "hi", - "nl", - "id", - "vi", - "it", - "ur", - "pa", - "az", - "fi", - "sv", -] +const LOCALES: readonly Locale[] = DESKTOP_NATIVE_LOCALES const INTL: Record = { en: "en", @@ -280,7 +231,7 @@ if (warm !== "en") void loadDict(warm) export const { use: useLanguage, provider: LanguageProvider } = createSimpleContext({ name: "Language", gate: false, - init: (props: { locale?: Locale }) => { + init: (props: { locale?: Locale; onNativeTranslations?: (bundle: DesktopNativeBundle) => void }) => { const initial = props.locale ?? readStoredLocale() ?? detectLocale() const [store, setStore, _, ready] = persisted( Persist.global("language", ["language.v1"]), @@ -323,6 +274,15 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont document.cookie = cookie(value) }) + createEffect(() => { + if (!props.onNativeTranslations || dict.loading) return + const current = dict() + if (!current) return + props.onNativeTranslations( + createDesktopNativeBundle(locale(), (key) => current[key] ?? DESKTOP_NATIVE_ENGLISH[key]), + ) + }) + return { ready, locale, diff --git a/packages/app/src/context/prompt-state.ts b/packages/app/src/context/prompt-state.ts index ea639a5bddf..65f47257810 100644 --- a/packages/app/src/context/prompt-state.ts +++ b/packages/app/src/context/prompt-state.ts @@ -6,6 +6,7 @@ import type { FileSelection } from "@/context/file" import { Persist, persisted } from "@/utils/persist" import type { ServerScope } from "@/utils/server-scope" import type { BlobReference } from "@/utils/draft-store" +import type { Platform } from "@/context/platform" interface PartBase { content: string @@ -236,13 +237,18 @@ function createPromptStateValue(store: PromptStore, setStore: SetStoreFunction

, initial?: InitialPrompt) { - const [store, setStore, _, ready] = persisted(target, createStore(promptStore(initial))) +function createPersistedPrompt(target: ReturnType, initial?: InitialPrompt, platform?: Platform) { + const [store, setStore, _, ready] = persisted(target, createStore(promptStore(initial)), platform) return { ready, ...createPromptStateValue(store, setStore) } } -export function createPromptSession(serverScope: ServerScope, scope: PromptScope, initial?: InitialPrompt) { - return createPersistedPrompt(promptTarget(serverScope, scope), initial) +export function createPromptSession( + serverScope: ServerScope, + scope: PromptScope, + initial?: InitialPrompt, + platform?: Platform, +) { + return createPersistedPrompt(promptTarget(serverScope, scope), initial, platform) } export function createDraftPromptSession(draftID: string, initial?: InitialPrompt) { diff --git a/packages/app/src/desktop-menu.test.ts b/packages/app/src/desktop-menu.test.ts index c7e36a7cb73..79dbe89adbd 100644 --- a/packages/app/src/desktop-menu.test.ts +++ b/packages/app/src/desktop-menu.test.ts @@ -4,10 +4,20 @@ import { DESKTOP_MENU } from "./desktop-menu" describe("desktop menu", () => { test("exports logs through the desktop command registry", () => { const items = DESKTOP_MENU.flatMap((menu) => menu.items ?? []).filter( - (item) => item.type === "item" && item.label === "Export Logs...", + (item) => item.type === "item" && item.labelKey === "desktop.menu.exportLogs", ) expect(items).toHaveLength(2) expect(items.every((item) => item.type === "item" && item.command === "logs.export" && !item.action)).toBe(true) }) + + test("provides translated labels for role-backed entries", () => { + const windowMenu = DESKTOP_MENU.find((menu) => menu.role === "windowMenu") + const roleItems = DESKTOP_MENU.flatMap((menu) => menu.items ?? []).filter( + (item) => item.type === "item" && item.role && item.labelKey, + ) + + expect(windowMenu?.labelKey).toBe("desktop.menu.window") + expect(roleItems.length).toBeGreaterThan(0) + }) }) diff --git a/packages/app/src/desktop-menu.ts b/packages/app/src/desktop-menu.ts index d3902cd2b24..d7c421e94de 100644 --- a/packages/app/src/desktop-menu.ts +++ b/packages/app/src/desktop-menu.ts @@ -1,3 +1,5 @@ +import type { DesktopNativeKey } from "./i18n/desktop-native" + export type DesktopMenuPlatform = "macos" | "windows" export type DesktopMenuAction = @@ -44,7 +46,7 @@ export type DesktopMenuRole = export type DesktopMenuItem = { type: "item" - label?: string + labelKey?: DesktopNativeKey command?: string action?: DesktopMenuAction role?: DesktopMenuRole @@ -63,7 +65,7 @@ export type DesktopMenuEntry = DesktopMenuItem | DesktopMenuSeparator export type DesktopMenu = { id: string - label: string + labelKey: DesktopNativeKey role?: DesktopMenuRole items?: DesktopMenuEntry[] platforms?: DesktopMenuPlatform[] @@ -72,15 +74,20 @@ export type DesktopMenu = { export const DESKTOP_MENU: DesktopMenu[] = [ { id: "app", - label: "OpenCode", + labelKey: "desktop.menu.app", platforms: ["macos"], items: [ { type: "item", role: "about" }, - { type: "item", label: "Check for Updates...", action: "app.checkForUpdates", enabled: "updater" }, - { type: "item", label: "Settings", command: "settings.open", accelerator: { macos: "Cmd+," } }, - { type: "item", label: "Reload Webview", action: "view.reload" }, - { type: "item", label: "Restart", action: "app.relaunch" }, - { type: "item", label: "Export Logs...", command: "logs.export" }, + { + type: "item", + labelKey: "desktop.menu.checkForUpdates", + action: "app.checkForUpdates", + enabled: "updater", + }, + { type: "item", labelKey: "desktop.menu.settings", command: "settings.open", accelerator: { macos: "Cmd+," } }, + { type: "item", labelKey: "desktop.menu.reloadWebview", action: "view.reload" }, + { type: "item", labelKey: "desktop.menu.restart", action: "app.relaunch" }, + { type: "item", labelKey: "desktop.menu.exportLogs", command: "logs.export" }, { type: "separator" }, { type: "item", role: "hide" }, { type: "item", role: "hideOthers" }, @@ -91,46 +98,63 @@ export const DESKTOP_MENU: DesktopMenu[] = [ }, { id: "file", - label: "File", + labelKey: "desktop.menu.file", items: [ { type: "item", - label: "New Session", + labelKey: "desktop.menu.newSession", command: "session.new", accelerator: { macos: "Shift+Cmd+S" }, }, - { type: "item", label: "Open Project...", command: "project.open", accelerator: { macos: "Cmd+O" } }, { type: "item", - label: "Settings", + labelKey: "desktop.menu.openProject", + command: "project.open", + accelerator: { macos: "Cmd+O" }, + }, + { + type: "item", + labelKey: "desktop.menu.settings", command: "settings.open", accelerator: { windows: "Ctrl+," }, platforms: ["windows"], }, { type: "item", - label: "New Window", + labelKey: "desktop.menu.newWindow", action: "window.new", accelerator: { macos: "Cmd+Shift+N", windows: "Ctrl+Shift+N" }, }, { type: "separator" }, - { type: "item", label: "Close Window", action: "window.close", role: "close" }, + { type: "item", labelKey: "desktop.menu.closeWindow", action: "window.close", role: "close" }, ], }, { id: "edit", - label: "Edit", + labelKey: "desktop.menu.edit", items: [ - { type: "item", label: "Undo", action: "edit.undo", role: "undo", accelerator: { windows: "Ctrl+Z" } }, - { type: "item", label: "Redo", action: "edit.redo", role: "redo", accelerator: { windows: "Ctrl+Y" } }, - { type: "separator" }, - { type: "item", label: "Cut", action: "edit.cut", role: "cut", accelerator: { windows: "Ctrl+X" } }, - { type: "item", label: "Copy", action: "edit.copy", role: "copy", accelerator: { windows: "Ctrl+C" } }, - { type: "item", label: "Paste", action: "edit.paste", role: "paste", accelerator: { windows: "Ctrl+V" } }, - { type: "item", label: "Delete", action: "edit.delete" }, { type: "item", - label: "Select All", + labelKey: "desktop.menu.undo", + action: "edit.undo", + role: "undo", + accelerator: { windows: "Ctrl+Z" }, + }, + { + type: "item", + labelKey: "desktop.menu.redo", + action: "edit.redo", + role: "redo", + accelerator: { windows: "Ctrl+Y" }, + }, + { type: "separator" }, + { type: "item", labelKey: "desktop.menu.cut", action: "edit.cut", role: "cut", accelerator: { windows: "Ctrl+X" } }, + { type: "item", labelKey: "desktop.menu.copy", action: "edit.copy", role: "copy", accelerator: { windows: "Ctrl+C" } }, + { type: "item", labelKey: "desktop.menu.paste", action: "edit.paste", role: "paste", accelerator: { windows: "Ctrl+V" } }, + { type: "item", labelKey: "desktop.menu.delete", action: "edit.delete" }, + { + type: "item", + labelKey: "desktop.menu.selectAll", action: "edit.selectAll", role: "selectAll", accelerator: { windows: "Ctrl+A" }, @@ -139,47 +163,47 @@ export const DESKTOP_MENU: DesktopMenu[] = [ }, { id: "view", - label: "View", + labelKey: "desktop.menu.view", items: [ - { type: "item", label: "Toggle Sidebar", command: "sidebar.toggle" }, - { type: "item", label: "Toggle Terminal", command: "terminal.toggle", accelerator: { macos: "Ctrl+`" } }, - { type: "item", label: "Toggle File Tree", command: "fileTree.toggle" }, + { type: "item", labelKey: "desktop.menu.toggleSidebar", command: "sidebar.toggle" }, + { type: "item", labelKey: "desktop.menu.toggleTerminal", command: "terminal.toggle", accelerator: { macos: "Ctrl+`" } }, + { type: "item", labelKey: "desktop.menu.toggleFileTree", command: "fileTree.toggle" }, { type: "separator" }, - { type: "item", label: "Reload", action: "view.reload", role: "reload" }, - { type: "item", label: "Toggle Developer Tools", action: "view.toggleDevTools", role: "toggleDevTools" }, + { type: "item", labelKey: "desktop.menu.reload", action: "view.reload", role: "reload" }, + { type: "item", labelKey: "desktop.menu.toggleDeveloperTools", action: "view.toggleDevTools", role: "toggleDevTools" }, { type: "separator" }, { type: "item", - label: "Actual Size", + labelKey: "desktop.menu.actualSize", action: "view.resetZoom", role: "resetZoom", accelerator: { windows: "Ctrl+0" }, }, - { type: "item", label: "Zoom In", action: "view.zoomIn", role: "zoomIn", accelerator: { windows: "Ctrl++" } }, - { type: "item", label: "Zoom Out", action: "view.zoomOut", role: "zoomOut", accelerator: { windows: "Ctrl+-" } }, + { type: "item", labelKey: "desktop.menu.zoomIn", action: "view.zoomIn", role: "zoomIn", accelerator: { windows: "Ctrl++" } }, + { type: "item", labelKey: "desktop.menu.zoomOut", action: "view.zoomOut", role: "zoomOut", accelerator: { windows: "Ctrl+-" } }, { type: "separator" }, - { type: "item", label: "Toggle Full Screen", action: "view.toggleFullscreen", role: "togglefullscreen" }, + { type: "item", labelKey: "desktop.menu.toggleFullScreen", action: "view.toggleFullscreen", role: "togglefullscreen" }, ], }, { id: "go", - label: "Go", + labelKey: "desktop.menu.go", items: [ - { type: "item", label: "Back", command: "common.goBack", accelerator: { macos: "Cmd+[" } }, - { type: "item", label: "Forward", command: "common.goForward", accelerator: { macos: "Cmd+]" } }, + { type: "item", labelKey: "desktop.menu.back", command: "common.goBack", accelerator: { macos: "Cmd+[" } }, + { type: "item", labelKey: "desktop.menu.forward", command: "common.goForward", accelerator: { macos: "Cmd+]" } }, { type: "separator" }, - { type: "item", label: "Previous Session", command: "session.previous", accelerator: { macos: "Option+Up" } }, - { type: "item", label: "Next Session", command: "session.next", accelerator: { macos: "Option+Down" } }, + { type: "item", labelKey: "desktop.menu.previousSession", command: "session.previous", accelerator: { macos: "Option+Up" } }, + { type: "item", labelKey: "desktop.menu.nextSession", command: "session.next", accelerator: { macos: "Option+Down" } }, { type: "separator" }, { type: "item", - label: "Previous Project", + labelKey: "desktop.menu.previousProject", command: "project.previous", accelerator: { macos: "Cmd+Option+Up" }, }, { type: "item", - label: "Next Project", + labelKey: "desktop.menu.nextProject", command: "project.next", accelerator: { macos: "Cmd+Option+Down" }, }, @@ -187,31 +211,31 @@ export const DESKTOP_MENU: DesktopMenu[] = [ }, { id: "window", - label: "Window", + labelKey: "desktop.menu.window", role: "windowMenu", items: [ - { type: "item", label: "Minimize", action: "window.minimize" }, - { type: "item", label: "Maximize", action: "window.toggleMaximize" }, + { type: "item", labelKey: "desktop.menu.minimize", action: "window.minimize" }, + { type: "item", labelKey: "desktop.menu.maximize", action: "window.toggleMaximize" }, { type: "separator" }, - { type: "item", label: "Close Window", action: "window.close" }, + { type: "item", labelKey: "desktop.menu.closeWindow", action: "window.close" }, ], }, { id: "help", - label: "Help", + labelKey: "desktop.menu.help", items: [ - { type: "item", label: "OpenCode Documentation", href: "https://opencode.ai/docs" }, - { type: "item", label: "Support Forum", href: "https://discord.com/invite/opencode" }, - { type: "item", label: "Export Logs...", command: "logs.export" }, + { type: "item", labelKey: "desktop.menu.documentation", href: "https://opencode.ai/docs" }, + { type: "item", labelKey: "desktop.menu.supportForum", href: "https://discord.com/invite/opencode" }, + { type: "item", labelKey: "desktop.menu.exportLogs", command: "logs.export" }, { type: "separator" }, { type: "item", - label: "Share Feedback", + labelKey: "desktop.menu.shareFeedback", href: "https://github.com/anomalyco/opencode/issues/new?template=feature_request.yml", }, { type: "item", - label: "Report a Bug", + labelKey: "desktop.menu.reportBug", href: "https://github.com/anomalyco/opencode/issues/new?template=bug_report.yml", }, ], diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 0bb5ea2ae72..cc3b7764e4c 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -1,4 +1,103 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "ملف", + "desktop.menu.edit": "تحرير", + "desktop.menu.view": "عرض", + "desktop.menu.go": "انتقال", + "desktop.menu.window": "نافذة", + "desktop.menu.help": "مساعدة", + "desktop.menu.checkForUpdates": "التحقق من وجود تحديثات...", + "desktop.menu.settings": "الإعدادات", + "desktop.menu.reloadWebview": "إعادة تحميل Webview", + "desktop.menu.restart": "إعادة التشغيل", + "desktop.menu.exportLogs": "تصدير السجلات...", + "desktop.menu.newSession": "جلسة جديدة", + "desktop.menu.openProject": "فتح مشروع...", + "desktop.menu.newWindow": "نافذة جديدة", + "desktop.menu.closeWindow": "إغلاق النافذة", + "desktop.menu.undo": "تراجع", + "desktop.menu.redo": "إعادة", + "desktop.menu.cut": "قص", + "desktop.menu.copy": "نسخ", + "desktop.menu.paste": "لصق", + "desktop.menu.delete": "حذف", + "desktop.menu.selectAll": "تحديد الكل", + "desktop.menu.toggleSidebar": "إظهار الشريط الجانبي أو إخفاؤه", + "desktop.menu.toggleTerminal": "إظهار المحطة الطرفية أو إخفاؤها", + "desktop.menu.toggleFileTree": "إظهار شجرة الملفات أو إخفاؤها", + "desktop.menu.reload": "إعادة التحميل", + "desktop.menu.toggleDeveloperTools": "إظهار أدوات المطور أو إخفاؤها", + "desktop.menu.actualSize": "الحجم الفعلي", + "desktop.menu.zoomIn": "تكبير", + "desktop.menu.zoomOut": "تصغير", + "desktop.menu.toggleFullScreen": "الدخول إلى ملء الشاشة أو الخروج منه", + "desktop.menu.back": "رجوع", + "desktop.menu.forward": "تقدم", + "desktop.menu.previousSession": "الجلسة السابقة", + "desktop.menu.nextSession": "الجلسة التالية", + "desktop.menu.previousProject": "المشروع السابق", + "desktop.menu.nextProject": "المشروع التالي", + "desktop.menu.minimize": "تصغير النافذة", + "desktop.menu.maximize": "تكبير النافذة", + "desktop.menu.documentation": "وثائق OpenCode", + "desktop.menu.supportForum": "منتدى الدعم", + "desktop.menu.shareFeedback": "إرسال الملاحظات", + "desktop.menu.reportBug": "الإبلاغ عن خلل", + "desktop.menu.ariaLabel": "قائمة OpenCode", + + "desktop.updater.dialog.checkFailed.message": "فشل التحقق من وجود تحديثات.", + "desktop.updater.dialog.checkFailed.title": "خطأ في التحديث", + "desktop.updater.dialog.upToDate.message": "لديك أحدث إصدار.", + "desktop.updater.dialog.upToDate.title": "لا توجد تحديثات", + "desktop.updater.dialog.ready.message": "تم تنزيل التحديث {{version}}. هل تريد إعادة التشغيل الآن؟", + "desktop.updater.dialog.ready.title": "التحديث جاهز", + "desktop.updater.dialog.restart": "إعادة التشغيل", + "desktop.updater.dialog.later": "لاحقًا", + + "desktop.recovery.action.relaunch": "إعادة تشغيل التطبيق", + "desktop.recovery.action.exportLogs": "تصدير السجلات", + "desktop.recovery.action.keepWaiting": "متابعة الانتظار", + "desktop.recovery.action.quit": "إنهاء", + "desktop.recovery.loadFailed": "فشل تحميل OpenCode", + "desktop.recovery.terminated": "توقفت نافذة OpenCode بشكل غير متوقع", + "desktop.recovery.unresponsive": "OpenCode لا يستجيب", + "desktop.recovery.unresponsive.detail": "يمكنك إعادة تشغيل التطبيق أو فتح السجلات أو متابعة الانتظار.", + "desktop.recovery.loadFailed.detail": "النافذة: {{window}}\nعنوان URL: {{url}}\nالخطأ: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "النافذة: {{window}}\nالسبب: {{reason}}\nالرمز: {{code}}", + "desktop.recovery.unknown": "<غير معروف>", + + "desktop.dialog.chooseFolder": "اختيار مجلد", + "desktop.dialog.chooseFile": "اختيار ملف", + "desktop.dialog.saveFile": "حفظ الملف", + "desktop.dialog.files": "الملفات", + "desktop.server.local": "الخادم المحلي", + + "desktop.wsl.error.windowsOnly": "لا يتوفر WSL إلا على Windows", + "desktop.wsl.error.unavailable": "WSL غير متاح", + "desktop.wsl.error.listInstalled": "فشل سرد توزيعات WSL المثبتة", + "desktop.wsl.error.listOnline": "فشل سرد توزيعات WSL المتاحة عبر الإنترنت", + "desktop.wsl.error.executeDistro": "تعذر تنفيذ الأوامر في التوزيعة", + "desktop.wsl.error.installWsl": "فشل تثبيت WSL", + "desktop.wsl.error.installDistro": "فشل تثبيت التوزيعة: {{distro}}", + "desktop.wsl.error.installOpencode": "فشل تثبيت OpenCode", + "desktop.wsl.error.alreadyAdded": "تمت إضافة {{distro}} بالفعل", + "desktop.wsl.error.opencodeMissing": "opencode غير مثبت في هذه التوزيعة", + "desktop.wsl.error.opencodeCannotRun": "opencode مثبت، لكن تعذر تشغيله", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode غير مثبت في {{distro}}", + "desktop.wsl.error.updateVersion": + "اكتمل تحديث OpenCode، لكن {{distro}} ما زالت تعرض الإصدار {{installed}}؛ الإصدار المتوقع هو {{expected}}", + "desktop.wsl.error.noVersion": "لا يوجد إصدار", + "desktop.wsl.error.serverExited": "خرج خادم WSL بعد بدء التشغيل (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "خرج خادم WSL قبل أن يصبح سليمًا (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "انتهت مهلة فحص سلامة العملية المصاحبة للتوزيعة {{distro}} بعد {{timeout}}ms", + "desktop.wsl.error.commandTimeout": "انتهت مهلة {{command}} {{args}} بعد {{timeout}}ms", + "desktop.wsl.error.failedPort": "فشل الحصول على المنفذ", + + "desktop.picker.error.notSelected": "لم يحدد منتقي الملفات ملفًا", + "desktop.picker.error.sizeLimit": "تتجاوز المرفقات المحددة الحد البالغ {{limit}} MB", + "command.category.suggested": "مقترح", "command.category.view": "عرض", "command.category.project": "مشروع", @@ -16,6 +115,7 @@ export const dict = { "command.category.permissions": "أذونات", "command.category.workspace": "مساحة عمل", "command.category.settings": "إعدادات", + "command.logs.export": "تصدير السجلات", "theme.scheme.system": "نظام", "theme.scheme.light": "فاتح", "theme.scheme.dark": "داكن", @@ -119,6 +219,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "تسجيل الدخول باستخدام Claude Pro/Max", "provider.connect.selectMethod": "حدد طريقة تسجيل الدخول لـ {{provider}}.", "provider.connect.method.apiKey": "مفتاح API", + "provider.connect.method.browser": "المتصفح", + "provider.connect.method.headless": "بدون واجهة رسومية", "provider.connect.status.inProgress": "جارٍ التفويض...", "provider.connect.status.waiting": "في انتظار التفويض...", "provider.connect.status.failed": "فشل التفويض: {{error}}", @@ -150,6 +252,7 @@ export const dict = { "provider.connect.toast.connected.title": "تم توصيل {{provider}}", "provider.connect.toast.connected.description": "نماذج {{provider}} متاحة الآن للاستخدام.", "provider.custom.title": "موفر مخصص", + "provider.custom.unavailable": "الموفرون المخصصون غير متاحين على هذا الخادم", "provider.custom.description.prefix": "تكوين موفر متوافق مع OpenAI. راجع ", "provider.custom.description.link": "وثائق تكوين الموفر", "provider.custom.description.suffix": ".", @@ -403,6 +506,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "سطح المكتب {{version}}", "wsl.onboarding.versionMismatch": "لا يتطابق الإصدار المثبت مع إصدار تطبيق سطح المكتب.", "wsl.onboarding.adding": "جارٍ الإضافة...", + "help.tabs.toast.ariaLabel": "نقدّم علامات التبويب. نظّم عملك وجلساتك النشطة باستخدام علامات التبويب", + "help.tabs.toast.dismiss": "إغلاق معلومات علامات التبويب", + "help.tabs.title": "نقدّم علامات التبويب", + "help.tabs.description": "نظّم عملك وجلساتك النشطة باستخدام علامات التبويب", + "help.tabs.date": "14 يوليو", + "help.tabs.introduction": "أصبح OpenCode Desktop الآن قائمًا على علامات التبويب.", + "help.tabs.sessions": + "ابدأ جلسة جديدة في علامة تبويب، أو افتح جلسة حالية من أي من مشاريعك. افتح علامة تبويب جديدة عندما تبدأ عملاً جديدًا، وأغلقها عند الانتهاء.", + "help.tabs.organize": + "يسهّل إبقاء بضع علامات تبويب مفتوحة تنظيم جلساتك النشطة. أعد تسمية علامات التبويب بأسماء يسهل تذكرها إذا كنت تنوي إبقاءها مفتوحة.", + "help.tabs.home": + "ستجد جميع جلساتك ومشاريعك في شاشة الصفحة الرئيسية الجديدة. يؤدي تحديد جلسة إلى فتحها في علامة تبويب.", + "help.tabs.persistence": "عندما تعيد فتح التطبيق، تظل علامات تبويبك مفتوحة.", + "help.tabs.worktrees": + "لا يدعم التصميم الجديد أشجار عمل Git حتى الآن، وستتوفر قريبًا. لذلك، إذا كنت تفضل مواصلة استخدام التخطيط السابق، فيمكنك التبديل بين التخطيطات في الإعدادات. ضع في اعتبارك أن التخطيط الجديد سيصبح دائمًا خلال بضعة أسابيع.", "dialog.project.edit.title": "تحرير المشروع", "dialog.project.edit.name": "الاسم", "dialog.project.edit.icon": "أيقونة", @@ -664,6 +782,12 @@ export const dict = { "terminal.close": "إغلاق المحطة الطرفية", "terminal.connectionLost.title": "فقد الاتصال", "terminal.connectionLost.description": "انقطع اتصال المحطة الطرفية. يمكن أن يحدث هذا عند إعادة تشغيل الخادم.", + "terminal.connectTicket.csrfError": "رُفضت تذكرة اتصال PTY بسبب فحوصات الأصل أو CSRF. تحقق من إعداد CORS للخادم.", + "terminal.connectTicket.statusError": "فشلت تذكرة اتصال PTY بالحالة {{status}}", + + "titlebar.update": "تحديث", + "titlebar.updateVersion": "تحديث {{version}}", + "common.closeTab": "إغلاق علامة التبويب", "common.dismiss": "إهمال", "common.requestFailed": "فشل الطلب", diff --git a/packages/app/src/i18n/az.ts b/packages/app/src/i18n/az.ts index 6a9f48bb048..66f4b72ab00 100644 --- a/packages/app/src/i18n/az.ts +++ b/packages/app/src/i18n/az.ts @@ -1,4 +1,98 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Fayl", + "desktop.menu.edit": "Redaktə", + "desktop.menu.view": "Görünüş", + "desktop.menu.go": "Keçid", + "desktop.menu.window": "Pəncərə", + "desktop.menu.help": "Kömək", + "desktop.menu.checkForUpdates": "Yeniləmələri yoxla...", + "desktop.menu.settings": "Tənzimləmələr", + "desktop.menu.reloadWebview": "Webview-u yenidən yüklə", + "desktop.menu.restart": "Yenidən başlat", + "desktop.menu.exportLogs": "Jurnalları ixrac et...", + "desktop.menu.newSession": "Yeni sessiya", + "desktop.menu.openProject": "Layihəni aç...", + "desktop.menu.newWindow": "Yeni pəncərə", + "desktop.menu.closeWindow": "Pəncərəni bağla", + "desktop.menu.undo": "Geri al", + "desktop.menu.redo": "Yenidən et", + "desktop.menu.cut": "Kəs", + "desktop.menu.copy": "Kopyala", + "desktop.menu.paste": "Yapışdır", + "desktop.menu.delete": "Sil", + "desktop.menu.selectAll": "Hamısını seç", + "desktop.menu.toggleSidebar": "Yan paneli aç/bağla", + "desktop.menu.toggleTerminal": "Terminalı aç/bağla", + "desktop.menu.toggleFileTree": "Fayl ağacını aç/bağla", + "desktop.menu.reload": "Yenidən yüklə", + "desktop.menu.toggleDeveloperTools": "Tərtibatçı alətlərini aç/bağla", + "desktop.menu.actualSize": "Faktiki ölçü", + "desktop.menu.zoomIn": "Böyüt", + "desktop.menu.zoomOut": "Kiçilt", + "desktop.menu.toggleFullScreen": "Tam ekran rejimini aç/bağla", + "desktop.menu.back": "Geri", + "desktop.menu.forward": "İrəli", + "desktop.menu.previousSession": "Əvvəlki sessiya", + "desktop.menu.nextSession": "Növbəti sessiya", + "desktop.menu.previousProject": "Əvvəlki layihə", + "desktop.menu.nextProject": "Növbəti layihə", + "desktop.menu.minimize": "Kiçilt", + "desktop.menu.maximize": "Böyüt", + "desktop.menu.documentation": "OpenCode sənədləri", + "desktop.menu.supportForum": "Dəstək forumu", + "desktop.menu.shareFeedback": "Rəy bildir", + "desktop.menu.reportBug": "Xəta bildir", + "desktop.menu.ariaLabel": "OpenCode menyusu", + "desktop.updater.dialog.checkFailed.message": "Yeniləmələri yoxlamaq mümkün olmadı.", + "desktop.updater.dialog.checkFailed.title": "Yeniləmə xətası", + "desktop.updater.dialog.upToDate.message": "Ən son versiyadan istifadə edirsiniz.", + "desktop.updater.dialog.upToDate.title": "Yeniləmə yoxdur", + "desktop.updater.dialog.ready.message": "{{version}} yeniləməsi endirildi. İndi yenidən başlatmaq istəyirsiniz?", + "desktop.updater.dialog.ready.title": "Yeniləmə hazırdır", + "desktop.updater.dialog.restart": "Yenidən başlat", + "desktop.updater.dialog.later": "Daha sonra", + "desktop.recovery.action.relaunch": "Yenidən işə sal", + "desktop.recovery.action.exportLogs": "Jurnalları ixrac et", + "desktop.recovery.action.keepWaiting": "Gözləməyə davam et", + "desktop.recovery.action.quit": "Çıx", + "desktop.recovery.loadFailed": "OpenCode yüklənə bilmədi", + "desktop.recovery.terminated": "OpenCode pəncərəsinin işi gözlənilmədən dayandı", + "desktop.recovery.unresponsive": "OpenCode cavab vermir", + "desktop.recovery.unresponsive.detail": + "Tətbiqi yenidən işə sala, jurnalları aça və ya gözləməyə davam edə bilərsiniz.", + "desktop.recovery.loadFailed.detail": "Pəncərə: {{window}}\nURL: {{url}}\nXəta: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Pəncərə: {{window}}\nSəbəb: {{reason}}\nKod: {{code}}", + "desktop.recovery.unknown": "", + "desktop.dialog.chooseFolder": "Qovluq seçin", + "desktop.dialog.chooseFile": "Fayl seçin", + "desktop.dialog.saveFile": "Faylı saxla", + "desktop.dialog.files": "Fayllar", + "desktop.server.local": "Yerli server", + "desktop.wsl.error.windowsOnly": "WSL yalnız Windows-da əlçatandır", + "desktop.wsl.error.unavailable": "WSL əlçatan deyil", + "desktop.wsl.error.listInstalled": "Quraşdırılmış WSL distribütivlərini siyahılamaq mümkün olmadı", + "desktop.wsl.error.listOnline": "Onlayn WSL distribütivlərini siyahılamaq mümkün olmadı", + "desktop.wsl.error.executeDistro": "Distribütivdə əmrləri icra etmək mümkün deyil", + "desktop.wsl.error.installWsl": "WSL quraşdırılması uğursuz oldu", + "desktop.wsl.error.installDistro": "Distribütivi quraşdırmaq mümkün olmadı: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode quraşdırılması uğursuz oldu", + "desktop.wsl.error.alreadyAdded": "{{distro}} artıq əlavə edilib", + "desktop.wsl.error.opencodeMissing": "Bu distribütivdə opencode quraşdırılmayıb", + "desktop.wsl.error.opencodeCannotRun": "opencode quraşdırılıb, lakin onu işə salmaq mümkün olmadı", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode {{distro}} distribütivində quraşdırılmayıb", + "desktop.wsl.error.updateVersion": + "OpenCode yeniləməsi tamamlandı, lakin {{distro}} hələ də {{installed}} versiyasını bildirir; gözlənilən versiya: {{expected}}", + "desktop.wsl.error.noVersion": "versiya yoxdur", + "desktop.wsl.error.serverExited": "WSL serveri başladıqdan sonra dayandı (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL serveri işlək vəziyyətə gəlməzdən əvvəl dayandı (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "{{distro}} üçün sidecar sağlamlıq yoxlamasının {{timeout}}ms gözləmə müddəti bitdi", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} üçün {{timeout}}ms gözləmə müddəti bitdi", + "desktop.wsl.error.failedPort": "Portu əldə etmək mümkün olmadı", + "desktop.picker.error.notSelected": "Seçim vasitəsində fayl seçilməyib", + "desktop.picker.error.sizeLimit": "Seçilmiş əlavələr {{limit}} MB limitini aşır", "command.category.suggested": "Təklif olunan", "command.category.view": "Görünüş", "command.category.project": "Layihə", @@ -16,6 +110,7 @@ export const dict = { "command.category.permissions": "İcazələr", "command.category.workspace": "İş sahəsi", "command.category.settings": "Tənzimləmələr", + "command.logs.export": "Jurnalları ixrac et", "theme.scheme.system": "Sistem", "theme.scheme.light": "Açıq", "theme.scheme.dark": "Tünd", @@ -119,6 +214,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Claude Pro/Max ilə daxil ol", "provider.connect.selectMethod": "{{provider}} üçün giriş üsulunu seçin.", "provider.connect.method.apiKey": "API açarı", + "provider.connect.method.browser": "Brauzer", + "provider.connect.method.headless": "Başsız", "provider.connect.status.inProgress": "Avtorizasiya davam edir...", "provider.connect.status.waiting": "Avtorizasiya gözlənilir...", "provider.connect.status.failed": "Avtorizasiya uğursuz oldu: {{error}}", @@ -150,6 +247,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}} qoşuldu", "provider.connect.toast.connected.description": "{{provider}} modelləri artıq istifadə üçün mövcuddur.", "provider.custom.title": "Fərdi provayder", + "provider.custom.unavailable": "Fərdi provayderlər bu serverdə əlçatan deyil", "provider.custom.description.prefix": "OpenAI ilə uyğun provayderi konfiqurasiya edin. ", "provider.custom.description.link": "Provayder konfiqurasiyası sənədləri", "provider.custom.description.suffix": "nə baxın.", @@ -412,6 +510,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "masaüstü {{version}}", "wsl.onboarding.versionMismatch": "Quraşdırılmış versiya masaüstü proqram versiyasına uyğun gəlmir.", "wsl.onboarding.adding": "Əlavə edilir...", + "help.tabs.toast.ariaLabel": "Tablarla tanışlıq. İşinizi və aktiv sessiyalarınızı tablarla təşkil edin", + "help.tabs.toast.dismiss": "Tablar haqqında məlumatı bağla", + "help.tabs.title": "Tablarla tanışlıq", + "help.tabs.description": "İşinizi və aktiv sessiyalarınızı tablarla təşkil edin", + "help.tabs.date": "14 iyul", + "help.tabs.introduction": "OpenCode Desktop indi tablar əsasında qurulub.", + "help.tabs.sessions": + "Yeni sessiyanı tabda başladın və ya layihələrinizdən mövcud sessiyanı açın. Yeni işə başlayanda yeni tab açın, işi bitirdikdə isə onu bağlayın.", + "help.tabs.organize": + "Bir neçə tabı açıq saxlamaq aktiv sessiyalarınızı təşkil etməyi asanlaşdırır. Onları saxlamağı planlaşdırırsınızsa, tablara yadda qalan adlar verin.", + "help.tabs.home": + "Bütün sessiya və layihələrinizi yeni Ana səhifədə tapa bilərsiniz. Sessiyanı seçdikdə o, tabda açılır.", + "help.tabs.persistence": "Tətbiqi yenidən açdıqda tablarınız açıq qalır.", + "help.tabs.worktrees": + "Yeni dizayn hələ Git Worktrees-i dəstəkləmir, lakin bu imkan tezliklə əlavə olunacaq. Əvvəlki tərtibatdan istifadə etməyə davam etmək istəyirsinizsə, Tənzimləmələrdə tərtibatlar arasında keçid edə bilərsiniz. Nəzərə alın ki, yeni tərtibat bir neçə həftədən sonra daimi olacaq.", "server.row.noUsername": "istifadəçi adı yoxdur", "dialog.project.edit.title": "Layihəni redaktə et", "dialog.project.edit.name": "Ad", @@ -707,6 +820,11 @@ export const dict = { "terminal.connectionLost.title": "Bağlantı kəsildi", "terminal.connectionLost.abnormalClose": "WebSocket anormal bağlandı: {{code}}", "terminal.connectionLost.description": "Terminal bağlantısı kəsildi. Bu server yenidən başladıqda baş verə bilər.", + "terminal.connectTicket.csrfError": + "PTY bağlantı bileti mənşə və ya CSRF yoxlaması zamanı rədd edildi. Serverin CORS konfiqurasiyasını yoxlayın.", + "terminal.connectTicket.statusError": "PTY bağlantı bileti {{status}} statusu ilə uğursuz oldu", + "titlebar.update": "Yenilə", + "titlebar.updateVersion": "Yenilə {{version}}", "common.closeTab": "Tabı bağla", "common.dismiss": "Bağla", "common.moreCountSuffix": " (+{{count}} daha)", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 35f7a0ce72b..19cb9a4cab4 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -1,4 +1,104 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Arquivo", + "desktop.menu.edit": "Editar", + "desktop.menu.view": "Visualizar", + "desktop.menu.go": "Ir", + "desktop.menu.window": "Janela", + "desktop.menu.help": "Ajuda", + "desktop.menu.checkForUpdates": "Verificar atualizações...", + "desktop.menu.settings": "Configurações", + "desktop.menu.reloadWebview": "Recarregar WebView", + "desktop.menu.restart": "Reiniciar", + "desktop.menu.exportLogs": "Exportar logs...", + "desktop.menu.newSession": "Nova sessão", + "desktop.menu.openProject": "Abrir projeto...", + "desktop.menu.newWindow": "Nova janela", + "desktop.menu.closeWindow": "Fechar janela", + "desktop.menu.undo": "Desfazer", + "desktop.menu.redo": "Refazer", + "desktop.menu.cut": "Recortar", + "desktop.menu.copy": "Copiar", + "desktop.menu.paste": "Colar", + "desktop.menu.delete": "Excluir", + "desktop.menu.selectAll": "Selecionar tudo", + "desktop.menu.toggleSidebar": "Alternar barra lateral", + "desktop.menu.toggleTerminal": "Alternar terminal", + "desktop.menu.toggleFileTree": "Alternar árvore de arquivos", + "desktop.menu.reload": "Recarregar", + "desktop.menu.toggleDeveloperTools": "Alternar ferramentas de desenvolvedor", + "desktop.menu.actualSize": "Tamanho real", + "desktop.menu.zoomIn": "Ampliar", + "desktop.menu.zoomOut": "Reduzir", + "desktop.menu.toggleFullScreen": "Alternar tela inteira", + "desktop.menu.back": "Voltar", + "desktop.menu.forward": "Avançar", + "desktop.menu.previousSession": "Sessão anterior", + "desktop.menu.nextSession": "Próxima sessão", + "desktop.menu.previousProject": "Projeto anterior", + "desktop.menu.nextProject": "Próximo projeto", + "desktop.menu.minimize": "Minimizar", + "desktop.menu.maximize": "Maximizar", + "desktop.menu.documentation": "Documentação do OpenCode", + "desktop.menu.supportForum": "Fórum de suporte", + "desktop.menu.shareFeedback": "Enviar comentários", + "desktop.menu.reportBug": "Relatar um bug", + "desktop.menu.ariaLabel": "Menu do OpenCode", + + "desktop.updater.dialog.checkFailed.message": "Falha ao verificar atualizações.", + "desktop.updater.dialog.checkFailed.title": "Erro de atualização", + "desktop.updater.dialog.upToDate.message": "Você está em dia.", + "desktop.updater.dialog.upToDate.title": "Nenhuma atualização", + "desktop.updater.dialog.ready.message": "A atualização {{version}} foi baixada. Deseja reiniciar agora?", + "desktop.updater.dialog.ready.title": "Atualização pronta", + "desktop.updater.dialog.restart": "Reiniciar", + "desktop.updater.dialog.later": "Mais tarde", + + "desktop.recovery.action.relaunch": "Reabrir", + "desktop.recovery.action.exportLogs": "Exportar logs", + "desktop.recovery.action.keepWaiting": "Continuar aguardando", + "desktop.recovery.action.quit": "Encerrar", + "desktop.recovery.loadFailed": "Falha ao carregar o OpenCode", + "desktop.recovery.terminated": "A janela do OpenCode foi encerrada inesperadamente", + "desktop.recovery.unresponsive": "O OpenCode não está respondendo", + "desktop.recovery.unresponsive.detail": "Você pode reabrir o aplicativo, abrir os logs ou continuar aguardando.", + "desktop.recovery.loadFailed.detail": "Janela: {{window}}\nURL: {{url}}\nErro: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Janela: {{window}}\nMotivo: {{reason}}\nCódigo: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Escolher uma pasta", + "desktop.dialog.chooseFile": "Escolher um arquivo", + "desktop.dialog.saveFile": "Salvar arquivo", + "desktop.dialog.files": "Arquivos", + "desktop.server.local": "Servidor local", + + "desktop.wsl.error.windowsOnly": "O WSL está disponível somente no Windows", + "desktop.wsl.error.unavailable": "O WSL está indisponível", + "desktop.wsl.error.listInstalled": "Falha ao listar as distribuições WSL instaladas", + "desktop.wsl.error.listOnline": "Falha ao listar as distribuições WSL disponíveis online", + "desktop.wsl.error.executeDistro": "Não é possível executar comandos na distribuição", + "desktop.wsl.error.installWsl": "Falha na instalação do WSL", + "desktop.wsl.error.installDistro": "Falha ao instalar a distribuição: {{distro}}", + "desktop.wsl.error.installOpencode": "Falha na instalação do OpenCode", + "desktop.wsl.error.alreadyAdded": "{{distro}} já foi adicionada", + "desktop.wsl.error.opencodeMissing": "O opencode não está instalado nesta distribuição", + "desktop.wsl.error.opencodeCannotRun": "O opencode está instalado, mas não pôde ser executado", + "desktop.wsl.error.opencodeNotInstalled": "O OpenCode não está instalado em {{distro}}", + "desktop.wsl.error.updateVersion": + "A atualização do OpenCode foi concluída, mas {{distro}} ainda informa a versão {{installed}}; a versão esperada é {{expected}}", + "desktop.wsl.error.noVersion": "sem versão", + "desktop.wsl.error.serverExited": + "O servidor WSL foi encerrado após a inicialização (código={{code}} sinal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "O servidor WSL foi encerrado antes de ficar íntegro (código={{code}} sinal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "O tempo limite da verificação de integridade do sidecar de {{distro}} foi excedido após {{timeout}}ms", + "desktop.wsl.error.commandTimeout": "O tempo limite de {{command}} {{args}} foi excedido após {{timeout}}ms", + "desktop.wsl.error.failedPort": "Falha ao obter a porta", + + "desktop.picker.error.notSelected": "O arquivo não foi selecionado pelo seletor", + "desktop.picker.error.sizeLimit": "Os anexos selecionados excedem o limite de {{limit}} MB", + "command.category.suggested": "Sugerido", "command.category.view": "Visualizar", "command.category.project": "Projeto", @@ -16,6 +116,7 @@ export const dict = { "command.category.permissions": "Permissões", "command.category.workspace": "Espaço de trabalho", "command.category.settings": "Configurações", + "command.logs.export": "Exportar logs", "theme.scheme.system": "Sistema", "theme.scheme.light": "Claro", "theme.scheme.dark": "Escuro", @@ -119,6 +220,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Entrar com Claude Pro/Max", "provider.connect.selectMethod": "Selecionar método de login para {{provider}}.", "provider.connect.method.apiKey": "Chave de API", + "provider.connect.method.browser": "Navegador", + "provider.connect.method.headless": "Sem interface gráfica", "provider.connect.status.inProgress": "Autorização em andamento...", "provider.connect.status.waiting": "Aguardando autorização...", "provider.connect.status.failed": "Autorização falhou: {{error}}", @@ -150,6 +253,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}} conectado", "provider.connect.toast.connected.description": "Modelos do {{provider}} agora estão disponíveis para uso.", "provider.custom.title": "Provedor personalizado", + "provider.custom.unavailable": "Provedores personalizados não estão disponíveis neste servidor", "provider.custom.description.prefix": "Configure um provedor compatível com OpenAI. Veja a ", "provider.custom.description.link": "documentação de configuração do provedor", "provider.custom.description.suffix": ".", @@ -404,6 +508,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "desktop {{version}}", "wsl.onboarding.versionMismatch": "A versão instalada não corresponde à versão do aplicativo para desktop.", "wsl.onboarding.adding": "Adicionando...", + "help.tabs.toast.ariaLabel": "Apresentamos as abas. Organize seu trabalho e suas sessões ativas com abas", + "help.tabs.toast.dismiss": "Dispensar informações sobre as abas", + "help.tabs.title": "Apresentamos as abas", + "help.tabs.description": "Organize seu trabalho e suas sessões ativas com abas", + "help.tabs.date": "14 de julho", + "help.tabs.introduction": "Agora, o OpenCode Desktop é organizado em abas.", + "help.tabs.sessions": + "Inicie uma nova sessão em uma aba ou abra uma sessão existente de qualquer um dos seus projetos. Abra uma nova aba quando começar algo novo e feche-a quando terminar.", + "help.tabs.organize": + "Manter algumas abas abertas facilita a organização das sessões ativas. Renomeie as abas com nomes fáceis de lembrar se pretende mantê-las abertas.", + "help.tabs.home": + "Você encontrará todas as suas sessões e projetos na nova tela Início. Ao selecionar uma sessão, ela será aberta em uma aba.", + "help.tabs.persistence": "Quando você reabrir o aplicativo, suas abas ainda estarão abertas.", + "help.tabs.worktrees": + "O novo design ainda não oferece suporte a Git Worktrees, mas esse recurso estará disponível em breve. Se preferir continuar usando o layout anterior, você poderá alternar entre os layouts nas Configurações. Lembre-se apenas de que o novo layout se tornará permanente em algumas semanas.", "dialog.project.edit.title": "Editar projeto", "dialog.project.edit.name": "Nome", "dialog.project.edit.icon": "Ícone", @@ -664,6 +783,13 @@ export const dict = { "terminal.connectionLost.title": "Conexão perdida", "terminal.connectionLost.description": "A conexão do terminal foi interrompida. Isso pode acontecer quando o servidor reinicia.", + "terminal.connectTicket.csrfError": + "O tíquete de conexão PTY foi rejeitado pelas verificações de origem ou CSRF. Verifique a configuração de CORS do servidor.", + "terminal.connectTicket.statusError": "O tíquete de conexão PTY falhou com o status {{status}}", + + "titlebar.update": "Atualizar", + "titlebar.updateVersion": "Atualizar {{version}}", + "common.closeTab": "Fechar aba", "common.dismiss": "Descartar", "common.requestFailed": "Requisição falhou", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 731d61b9fee..e00f58767d6 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -1,4 +1,104 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Datoteka", + "desktop.menu.edit": "Uredi", + "desktop.menu.view": "Prikaz", + "desktop.menu.go": "Idi", + "desktop.menu.window": "Prozor", + "desktop.menu.help": "Pomoć", + "desktop.menu.checkForUpdates": "Provjeri ima li ažuriranja...", + "desktop.menu.settings": "Postavke", + "desktop.menu.reloadWebview": "Ponovo učitaj Webview", + "desktop.menu.restart": "Ponovo pokreni", + "desktop.menu.exportLogs": "Izvezi zapisnike...", + "desktop.menu.newSession": "Nova sesija", + "desktop.menu.openProject": "Otvori projekat...", + "desktop.menu.newWindow": "Novi prozor", + "desktop.menu.closeWindow": "Zatvori prozor", + "desktop.menu.undo": "Poništi", + "desktop.menu.redo": "Vrati", + "desktop.menu.cut": "Izreži", + "desktop.menu.copy": "Kopiraj", + "desktop.menu.paste": "Zalijepi", + "desktop.menu.delete": "Izbriši", + "desktop.menu.selectAll": "Odaberi sve", + "desktop.menu.toggleSidebar": "Prikaži ili sakrij bočnu traku", + "desktop.menu.toggleTerminal": "Prikaži ili sakrij terminal", + "desktop.menu.toggleFileTree": "Prikaži ili sakrij stablo datoteka", + "desktop.menu.reload": "Ponovo učitaj", + "desktop.menu.toggleDeveloperTools": "Prikaži ili sakrij alate za programere", + "desktop.menu.actualSize": "Stvarna veličina", + "desktop.menu.zoomIn": "Uvećaj", + "desktop.menu.zoomOut": "Umanji", + "desktop.menu.toggleFullScreen": "Prikaži ili sakrij prikaz preko cijelog ekrana", + "desktop.menu.back": "Nazad", + "desktop.menu.forward": "Naprijed", + "desktop.menu.previousSession": "Prethodna sesija", + "desktop.menu.nextSession": "Sljedeća sesija", + "desktop.menu.previousProject": "Prethodni projekat", + "desktop.menu.nextProject": "Sljedeći projekat", + "desktop.menu.minimize": "Minimiziraj", + "desktop.menu.maximize": "Maksimiziraj", + "desktop.menu.documentation": "Dokumentacija za OpenCode", + "desktop.menu.supportForum": "Forum za podršku", + "desktop.menu.shareFeedback": "Pošalji povratne informacije", + "desktop.menu.reportBug": "Prijavi grešku", + "desktop.menu.ariaLabel": "OpenCode meni", + + "desktop.updater.dialog.checkFailed.message": "Provjera ažuriranja nije uspjela.", + "desktop.updater.dialog.checkFailed.title": "Greška pri ažuriranju", + "desktop.updater.dialog.upToDate.message": "Sve je ažurno.", + "desktop.updater.dialog.upToDate.title": "Nema ažuriranja", + "desktop.updater.dialog.ready.message": "Ažuriranje {{version}} je preuzeto. Želiš li sada ponovo pokrenuti aplikaciju?", + "desktop.updater.dialog.ready.title": "Ažuriranje je spremno", + "desktop.updater.dialog.restart": "Ponovo pokreni", + "desktop.updater.dialog.later": "Kasnije", + + "desktop.recovery.action.relaunch": "Ponovo pokreni", + "desktop.recovery.action.exportLogs": "Izvezi zapisnike", + "desktop.recovery.action.keepWaiting": "Nastavi čekati", + "desktop.recovery.action.quit": "Izađi", + "desktop.recovery.loadFailed": "OpenCode se nije uspio učitati", + "desktop.recovery.terminated": "Prozor OpenCode-a neočekivano je zatvoren", + "desktop.recovery.unresponsive": "OpenCode ne reaguje", + "desktop.recovery.unresponsive.detail": + "Možeš ponovo pokrenuti aplikaciju, otvoriti zapisnike ili nastaviti čekati.", + "desktop.recovery.loadFailed.detail": "Prozor: {{window}}\nURL: {{url}}\nGreška: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Prozor: {{window}}\nRazlog: {{reason}}\nKod: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Odaberi fasciklu", + "desktop.dialog.chooseFile": "Odaberi datoteku", + "desktop.dialog.saveFile": "Sačuvaj datoteku", + "desktop.dialog.files": "Datoteke", + "desktop.server.local": "Lokalni server", + + "desktop.wsl.error.windowsOnly": "WSL je dostupan samo u Windowsu", + "desktop.wsl.error.unavailable": "WSL nije dostupan", + "desktop.wsl.error.listInstalled": "Nije uspjelo prikazivanje instaliranih WSL distribucija", + "desktop.wsl.error.listOnline": "Nije uspjelo prikazivanje dostupnih WSL distribucija", + "desktop.wsl.error.executeDistro": "Nije moguće izvršiti komande u distribuciji", + "desktop.wsl.error.installWsl": "Instalacija WSL-a nije uspjela", + "desktop.wsl.error.installDistro": "Instalacija distribucije nije uspjela: {{distro}}", + "desktop.wsl.error.installOpencode": "Instalacija OpenCode-a nije uspjela", + "desktop.wsl.error.alreadyAdded": "Distribucija {{distro}} je već dodana", + "desktop.wsl.error.opencodeMissing": "opencode nije instaliran u ovoj distribuciji", + "desktop.wsl.error.opencodeCannotRun": "opencode je instaliran, ali se ne može pokrenuti", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode nije instaliran u distribuciji {{distro}}", + "desktop.wsl.error.updateVersion": + "Ažuriranje OpenCode-a je završeno, ali {{distro}} i dalje prijavljuje {{installed}}; očekivano je {{expected}}", + "desktop.wsl.error.noVersion": "nema verzije", + "desktop.wsl.error.serverExited": "WSL server se zatvorio nakon pokretanja (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL server se zatvorio prije nego što je postao dostupan (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Isteklo je vrijeme provjere dostupnosti komponente Sidecar za {{distro}} nakon {{timeout}} ms", + "desktop.wsl.error.commandTimeout": "Isteklo je vrijeme za {{command}} {{args}} nakon {{timeout}} ms", + "desktop.wsl.error.failedPort": "Nije uspjelo dohvaćanje porta", + + "desktop.picker.error.notSelected": "Datoteka nije odabrana u izborniku", + "desktop.picker.error.sizeLimit": "Odabrani prilozi premašuju ograničenje od {{limit}} MB", + "command.category.suggested": "Predloženo", "command.category.view": "Prikaz", "command.category.project": "Projekat", @@ -16,6 +116,7 @@ export const dict = { "command.category.permissions": "Dozvole", "command.category.workspace": "Radni prostor", "command.category.settings": "Postavke", + "command.logs.export": "Izvezi zapisnike", "theme.scheme.system": "Sistem", "theme.scheme.light": "Svijetlo", @@ -131,6 +232,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Prijavi se putem Claude Pro/Max", "provider.connect.selectMethod": "Odaberi način prijave za {{provider}}.", "provider.connect.method.apiKey": "API ključ", + "provider.connect.method.browser": "Preglednik", + "provider.connect.method.headless": "Bez grafičkog interfejsa", "provider.connect.status.inProgress": "Autorizacija je u toku...", "provider.connect.status.waiting": "Čekanje na autorizaciju...", "provider.connect.status.failed": "Autorizacija nije uspjela: {{error}}", @@ -163,6 +266,7 @@ export const dict = { "provider.connect.toast.connected.description": "{{provider}} modeli su sada dostupni za korištenje.", "provider.custom.title": "Prilagođeni provajder", + "provider.custom.unavailable": "Prilagođeni provajderi nisu dostupni na ovom serveru", "provider.custom.description.prefix": "Konfiguriši OpenAI-kompatibilnog provajdera. Pogledaj ", "provider.custom.description.link": "dokumentaciju za konfiguraciju provajdera", "provider.custom.description.suffix": ".", @@ -433,6 +537,22 @@ export const dict = { "wsl.onboarding.versionMismatch": "Instalirana verzija ne odgovara verziji desktop aplikacije.", "wsl.onboarding.adding": "Dodavanje...", + "help.tabs.toast.ariaLabel": "Predstavljamo kartice. Organizuj svoj rad i aktivne sesije pomoću kartica", + "help.tabs.toast.dismiss": "Odbaci informacije o karticama", + "help.tabs.title": "Predstavljamo kartice", + "help.tabs.description": "Organizuj svoj rad i aktivne sesije pomoću kartica", + "help.tabs.date": "14. juli", + "help.tabs.introduction": "OpenCode Desktop sada je organizovan oko kartica.", + "help.tabs.sessions": + "Započni novu sesiju u kartici ili otvori postojeću sesiju iz bilo kojeg projekta. Otvori novu karticu kada započinješ nešto novo, a zatvori je kada završiš.", + "help.tabs.organize": + "Nekoliko otvorenih kartica olakšava organizovanje aktivnih sesija. Preimenuj kartice tako da ih možeš lako prepoznati ako ih namjeravaš zadržati.", + "help.tabs.home": + "Sve svoje sesije i projekte pronaći ćeš na novom početnom ekranu. Odabirom sesije ona se otvara u kartici.", + "help.tabs.persistence": "Kada ponovo otvoriš aplikaciju, tvoje kartice ostaju otvorene.", + "help.tabs.worktrees": + "Novi dizajn još ne podržava Git Worktrees, ali podrška stiže uskoro. Ako želiš nastaviti koristiti prethodni raspored, možeš mijenjati rasporede u Postavkama. Imaj na umu da će novi raspored za nekoliko sedmica postati trajan.", + "dialog.project.edit.title": "Uredi projekat", "dialog.project.edit.name": "Naziv", "dialog.project.edit.icon": "Ikonica", @@ -723,6 +843,12 @@ export const dict = { "terminal.connectionLost.title": "Veza prekinuta", "terminal.connectionLost.description": "Veza s terminalom je prekinuta. Ovo se može desiti kada se server ponovo pokrene.", + "terminal.connectTicket.csrfError": + "PTY tiket za povezivanje odbijen je zbog provjera porijekla ili CSRF-a. Provjeri CORS konfiguraciju servera.", + "terminal.connectTicket.statusError": "PTY tiket za povezivanje nije uspio sa statusom {{status}}", + + "titlebar.update": "Ažuriraj", + "titlebar.updateVersion": "Ažuriraj {{version}}", "common.closeTab": "Zatvori karticu", "common.dismiss": "Odbaci", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 517b01d9e6c..899f1b1f861 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -131,6 +131,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Log ind med Claude Pro/Max", "provider.connect.selectMethod": "Vælg loginmetode for {{provider}}.", "provider.connect.method.apiKey": "API-nøgle", + "provider.connect.method.browser": "Browser", + "provider.connect.method.headless": "Browserløs", "provider.connect.status.inProgress": "Godkendelse i gang...", "provider.connect.status.waiting": "Venter på godkendelse...", "provider.connect.status.failed": "Godkendelse mislykkedes: {{error}}", @@ -163,6 +165,7 @@ export const dict = { "provider.connect.toast.connected.description": "Modeller fra {{provider}} er nu tilgængelige.", "provider.custom.title": "Brugerdefineret udbyder", + "provider.custom.unavailable": "Brugerdefinerede udbydere er ikke tilgængelige på denne server", "provider.custom.description.prefix": "Konfigurer en OpenAI-kompatibel udbyder. Se ", "provider.custom.description.link": "dokumentation for udbyderkonfiguration", "provider.custom.description.suffix": ".", @@ -717,6 +720,13 @@ export const dict = { "terminal.connectionLost.title": "Forbindelse mistet", "terminal.connectionLost.description": "Terminalforbindelsen blev afbrudt. Dette kan ske, når serveren genstarter.", + "terminal.connectTicket.csrfError": + "PTY-forbindelsesticket blev afvist ved kontrollen af oprindelse eller CSRF. Kontrollér serverens CORS-konfiguration.", + "terminal.connectTicket.statusError": "PTY-forbindelsesticket mislykkedes med {{status}}", + + "titlebar.update": "Opdater", + "titlebar.updateVersion": "Opdater {{version}}", + "common.closeTab": "Luk fane", "common.dismiss": "Afvis", "common.requestFailed": "Forespørgsel mislykkedes", @@ -1097,4 +1107,122 @@ export const dict = { "error.childStore.persistedProjectIconCreateFailed": "Kunne ikke oprette vedvarende projektikon", "error.childStore.storeCreateFailed": "Kunne ikke oprette lager", "terminal.connectionLost.abnormalClose": "WebSocket lukkede unormalt: {{code}}", + + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Arkiv", + "desktop.menu.edit": "Rediger", + "desktop.menu.view": "Oversigt", + "desktop.menu.go": "Gå", + "desktop.menu.window": "Vindue", + "desktop.menu.help": "Hjælp", + "desktop.menu.checkForUpdates": "Søg efter opdateringer...", + "desktop.menu.settings": "Indstillinger", + "desktop.menu.reloadWebview": "Genindlæs webvisning", + "desktop.menu.restart": "Genstart", + "desktop.menu.exportLogs": "Eksportér logfiler...", + "desktop.menu.newSession": "Ny session", + "desktop.menu.openProject": "Åbn projekt...", + "desktop.menu.newWindow": "Nyt vindue", + "desktop.menu.closeWindow": "Luk vindue", + "desktop.menu.undo": "Fortryd", + "desktop.menu.redo": "Gentag", + "desktop.menu.cut": "Klip", + "desktop.menu.copy": "Kopier", + "desktop.menu.paste": "Sæt ind", + "desktop.menu.delete": "Slet", + "desktop.menu.selectAll": "Vælg alt", + "desktop.menu.toggleSidebar": "Vis eller skjul indholdsoversigt", + "desktop.menu.toggleTerminal": "Vis eller skjul terminal", + "desktop.menu.toggleFileTree": "Vis eller skjul filtræ", + "desktop.menu.reload": "Genindlæs", + "desktop.menu.toggleDeveloperTools": "Vis eller skjul udviklerværktøjer", + "desktop.menu.actualSize": "Faktisk størrelse", + "desktop.menu.zoomIn": "Zoom ind", + "desktop.menu.zoomOut": "Zoom ud", + "desktop.menu.toggleFullScreen": "Slå fuldskærmsvisning til eller fra", + "desktop.menu.back": "Tilbage", + "desktop.menu.forward": "Frem", + "desktop.menu.previousSession": "Forrige session", + "desktop.menu.nextSession": "Næste session", + "desktop.menu.previousProject": "Forrige projekt", + "desktop.menu.nextProject": "Næste projekt", + "desktop.menu.minimize": "Minimer", + "desktop.menu.maximize": "Maksimer", + "desktop.menu.documentation": "OpenCode-dokumentation", + "desktop.menu.supportForum": "Supportforum", + "desktop.menu.shareFeedback": "Giv feedback", + "desktop.menu.reportBug": "Rapportér en fejl", + "desktop.menu.ariaLabel": "OpenCode-menu", + + "desktop.updater.dialog.checkFailed.message": "Søgningen efter opdateringer mislykkedes.", + "desktop.updater.dialog.checkFailed.title": "Opdateringsfejl", + "desktop.updater.dialog.upToDate.message": "Du er opdateret.", + "desktop.updater.dialog.upToDate.title": "Ingen opdateringer", + "desktop.updater.dialog.ready.message": "Opdatering {{version}} er downloadet. Vil du genstarte nu?", + "desktop.updater.dialog.ready.title": "Opdateringen er klar", + "desktop.updater.dialog.restart": "Genstart", + "desktop.updater.dialog.later": "Senere", + + "desktop.recovery.action.relaunch": "Start igen", + "desktop.recovery.action.exportLogs": "Eksportér logfiler", + "desktop.recovery.action.keepWaiting": "Vent fortsat", + "desktop.recovery.action.quit": "Afslut", + "desktop.recovery.loadFailed": "OpenCode kunne ikke indlæses", + "desktop.recovery.terminated": "OpenCode-vinduet blev uventet afsluttet", + "desktop.recovery.unresponsive": "OpenCode svarer ikke", + "desktop.recovery.unresponsive.detail": + "Du kan starte appen igen, åbne logfilerne eller fortsætte med at vente.", + "desktop.recovery.loadFailed.detail": "Vindue: {{window}}\nURL: {{url}}\nFejl: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Vindue: {{window}}\nÅrsag: {{reason}}\nKode: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Vælg en mappe", + "desktop.dialog.chooseFile": "Vælg en fil", + "desktop.dialog.saveFile": "Gem fil", + "desktop.dialog.files": "Filer", + "desktop.server.local": "Lokal server", + + "desktop.wsl.error.windowsOnly": "WSL er kun tilgængelig i Windows", + "desktop.wsl.error.unavailable": "WSL er ikke tilgængelig", + "desktop.wsl.error.listInstalled": "Kunne ikke vise installerede WSL-distributioner", + "desktop.wsl.error.listOnline": "Kunne ikke vise WSL-distributioner på nettet", + "desktop.wsl.error.executeDistro": "Kan ikke udføre kommandoer i distributionen", + "desktop.wsl.error.installWsl": "Installationen af WSL mislykkedes", + "desktop.wsl.error.installDistro": "Kunne ikke installere distributionen: {{distro}}", + "desktop.wsl.error.installOpencode": "Installationen af OpenCode mislykkedes", + "desktop.wsl.error.alreadyAdded": "{{distro}} er allerede tilføjet", + "desktop.wsl.error.opencodeMissing": "opencode er ikke installeret i denne distribution", + "desktop.wsl.error.opencodeCannotRun": "opencode er installeret, men kunne ikke køre", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode er ikke installeret i {{distro}}", + "desktop.wsl.error.updateVersion": + "Opdateringen af OpenCode er fuldført, men {{distro}} rapporterer stadig {{installed}}. Forventet: {{expected}}", + "desktop.wsl.error.noVersion": "ingen version", + "desktop.wsl.error.serverExited": "WSL-serveren blev afsluttet efter opstart (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL-serveren blev afsluttet, før den var klar (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Tilstandskontrollen af sidecar-processen for {{distro}} fik timeout efter {{timeout}} ms", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} fik timeout efter {{timeout}} ms", + "desktop.wsl.error.failedPort": "Kunne ikke hente porten", + + "desktop.picker.error.notSelected": "Filen blev ikke valgt i filvælgeren", + "desktop.picker.error.sizeLimit": "De valgte vedhæftede filer overskrider grænsen på {{limit}} MB", + + "command.logs.export": "Eksportér logfiler", + + "help.tabs.toast.ariaLabel": "Introduktion til faner. Organiser dit arbejde og dine aktive sessioner med faner", + "help.tabs.toast.dismiss": "Luk oplysninger om faner", + "help.tabs.title": "Introduktion til faner", + "help.tabs.description": "Organiser dit arbejde og dine aktive sessioner med faner", + "help.tabs.date": "14. juli", + "help.tabs.introduction": "OpenCode Desktop er nu bygget op omkring faner.", + "help.tabs.sessions": + "Start en ny session i en fane, eller åbn en eksisterende session fra et af dine projekter. Åbn en ny fane, når du går i gang med noget nyt, og luk den, når du er færdig.", + "help.tabs.organize": + "Når du holder nogle få faner åbne, er det nemmere at organisere dine aktive sessioner. Giv fanerne navne, der er nemme at huske, hvis du vil beholde dem.", + "help.tabs.home": + "Du finder alle dine sessioner og projekter på den nye startskærm. Når du vælger en session, åbnes den i en fane.", + "help.tabs.persistence": "Dine faner er stadig åbne, når du åbner appen igen.", + "help.tabs.worktrees": + "Det nye design understøtter endnu ikke Git Worktrees, men understøttelsen kommer snart. Hvis du foretrækker at fortsætte med det tidligere layout, kan du skifte mellem layoutene under Indstillinger. Bemærk, at det nye layout bliver permanent om nogle få uger.", } diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index fc3ff8cd5a1..dfde9633760 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -123,6 +123,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Mit Claude Pro/Max anmelden", "provider.connect.selectMethod": "Anmeldemethode für {{provider}} auswählen.", "provider.connect.method.apiKey": "API-Schlüssel", + "provider.connect.method.browser": "Browser", + "provider.connect.method.headless": "Browserlos", "provider.connect.status.inProgress": "Autorisierung läuft…", "provider.connect.status.waiting": "Warten auf Autorisierung…", "provider.connect.status.failed": "Autorisierung fehlgeschlagen: {{error}}", @@ -154,6 +156,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}} verbunden", "provider.connect.toast.connected.description": "{{provider}}-Modelle sind jetzt verfügbar.", "provider.custom.title": "Benutzerdefinierter Anbieter", + "provider.custom.unavailable": "Benutzerdefinierte Anbieter sind auf diesem Server nicht verfügbar", "provider.custom.description.prefix": "Konfigurieren Sie einen OpenAI-kompatiblen Anbieter. Siehe die ", "provider.custom.description.link": "Dokumentation zur Anbieterkonfiguration", "provider.custom.description.suffix": ".", @@ -670,6 +673,13 @@ export const dict = { "terminal.connectionLost.title": "Verbindung verloren", "terminal.connectionLost.description": "Die Terminalverbindung wurde unterbrochen. Das kann passieren, wenn der Server neu startet.", + "terminal.connectTicket.csrfError": + "Das PTY-Verbindungsticket wurde bei der Ursprungs- oder CSRF-Prüfung abgelehnt. Überprüfen Sie die CORS-Konfiguration des Servers.", + "terminal.connectTicket.statusError": "Das PTY-Verbindungsticket ist mit {{status}} fehlgeschlagen", + + "titlebar.update": "Aktualisieren", + "titlebar.updateVersion": "Aktualisieren {{version}}", + "common.closeTab": "Tab schließen", "common.dismiss": "Verwerfen", "common.requestFailed": "Anfrage fehlgeschlagen", @@ -1042,4 +1052,115 @@ export const dict = { "error.childStore.persistedProjectIconCreateFailed": "Dauerhaftes Projektsymbol konnte nicht erstellt werden", "error.childStore.storeCreateFailed": "Speicher konnte nicht erstellt werden", "terminal.connectionLost.abnormalClose": "WebSocket unerwartet geschlossen: {{code}}", + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Datei", + "desktop.menu.edit": "Bearbeiten", + "desktop.menu.view": "Ansicht", + "desktop.menu.go": "Gehe zu", + "desktop.menu.window": "Fenster", + "desktop.menu.help": "Hilfe", + "desktop.menu.checkForUpdates": "Nach Updates suchen…", + "desktop.menu.settings": "Einstellungen", + "desktop.menu.reloadWebview": "Webview neu laden", + "desktop.menu.restart": "Neu starten", + "desktop.menu.exportLogs": "Protokolle exportieren…", + "desktop.menu.newSession": "Neue Sitzung", + "desktop.menu.openProject": "Projekt öffnen…", + "desktop.menu.newWindow": "Neues Fenster", + "desktop.menu.closeWindow": "Fenster schließen", + "desktop.menu.undo": "Rückgängig", + "desktop.menu.redo": "Wiederholen", + "desktop.menu.cut": "Ausschneiden", + "desktop.menu.copy": "Kopieren", + "desktop.menu.paste": "Einfügen", + "desktop.menu.delete": "Löschen", + "desktop.menu.selectAll": "Alles auswählen", + "desktop.menu.toggleSidebar": "Seitenleiste umschalten", + "desktop.menu.toggleTerminal": "Terminal umschalten", + "desktop.menu.toggleFileTree": "Dateibaum umschalten", + "desktop.menu.reload": "Neu laden", + "desktop.menu.toggleDeveloperTools": "Entwicklertools umschalten", + "desktop.menu.actualSize": "Tatsächliche Größe", + "desktop.menu.zoomIn": "Vergrößern", + "desktop.menu.zoomOut": "Verkleinern", + "desktop.menu.toggleFullScreen": "Vollbildmodus umschalten", + "desktop.menu.back": "Zurück", + "desktop.menu.forward": "Vorwärts", + "desktop.menu.previousSession": "Vorherige Sitzung", + "desktop.menu.nextSession": "Nächste Sitzung", + "desktop.menu.previousProject": "Vorheriges Projekt", + "desktop.menu.nextProject": "Nächstes Projekt", + "desktop.menu.minimize": "Minimieren", + "desktop.menu.maximize": "Maximieren", + "desktop.menu.documentation": "OpenCode-Dokumentation", + "desktop.menu.supportForum": "Supportforum", + "desktop.menu.shareFeedback": "Feedback senden", + "desktop.menu.reportBug": "Fehler melden", + "desktop.menu.ariaLabel": "OpenCode-Menü", + "desktop.updater.dialog.checkFailed.message": "Die Suche nach Updates ist fehlgeschlagen.", + "desktop.updater.dialog.checkFailed.title": "Updatefehler", + "desktop.updater.dialog.upToDate.message": "Sie sind auf dem neuesten Stand.", + "desktop.updater.dialog.upToDate.title": "Keine Updates", + "desktop.updater.dialog.ready.message": "Update {{version}} wurde heruntergeladen. Jetzt neu starten?", + "desktop.updater.dialog.ready.title": "Update bereit", + "desktop.updater.dialog.restart": "Neu starten", + "desktop.updater.dialog.later": "Später", + "desktop.recovery.action.relaunch": "Neu starten", + "desktop.recovery.action.exportLogs": "Protokolle exportieren", + "desktop.recovery.action.keepWaiting": "Weiter warten", + "desktop.recovery.action.quit": "Beenden", + "desktop.recovery.loadFailed": "OpenCode konnte nicht geladen werden", + "desktop.recovery.terminated": "Das OpenCode-Fenster wurde unerwartet beendet", + "desktop.recovery.unresponsive": "OpenCode reagiert nicht", + "desktop.recovery.unresponsive.detail": + "Sie können die App neu starten, die Protokolle öffnen oder weiter warten.", + "desktop.recovery.loadFailed.detail": "Fenster: {{window}}\nURL: {{url}}\nFehler: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Fenster: {{window}}\nGrund: {{reason}}\nCode: {{code}}", + "desktop.recovery.unknown": "", + "desktop.dialog.chooseFolder": "Ordner auswählen", + "desktop.dialog.chooseFile": "Datei auswählen", + "desktop.dialog.saveFile": "Datei speichern", + "desktop.dialog.files": "Dateien", + "desktop.server.local": "Lokaler Server", + "desktop.wsl.error.windowsOnly": "WSL ist nur unter Windows verfügbar", + "desktop.wsl.error.unavailable": "WSL ist nicht verfügbar", + "desktop.wsl.error.listInstalled": "Installierte WSL-Distributionen konnten nicht aufgelistet werden", + "desktop.wsl.error.listOnline": "Verfügbare WSL-Distributionen konnten nicht aufgelistet werden", + "desktop.wsl.error.executeDistro": "In der Distribution können keine Befehle ausgeführt werden", + "desktop.wsl.error.installWsl": "WSL-Installation fehlgeschlagen", + "desktop.wsl.error.installDistro": "Distribution konnte nicht installiert werden: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode-Installation fehlgeschlagen", + "desktop.wsl.error.alreadyAdded": "{{distro}} wurde bereits hinzugefügt", + "desktop.wsl.error.opencodeMissing": "opencode ist in dieser Distribution nicht installiert", + "desktop.wsl.error.opencodeCannotRun": "opencode ist installiert, konnte aber nicht ausgeführt werden", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode ist in {{distro}} nicht installiert", + "desktop.wsl.error.updateVersion": + "Das OpenCode-Update wurde abgeschlossen, aber {{distro}} meldet weiterhin {{installed}}; erwartet wurde {{expected}}", + "desktop.wsl.error.noVersion": "keine Version", + "desktop.wsl.error.serverExited": "WSL-Server wurde nach dem Start beendet (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL-Server wurde beendet, bevor er betriebsbereit war (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Zeitüberschreitung bei der Integritätsprüfung des Sidecars für {{distro}} nach {{timeout}} ms", + "desktop.wsl.error.commandTimeout": "Zeitüberschreitung bei {{command}} {{args}} nach {{timeout}} ms", + "desktop.wsl.error.failedPort": "Port konnte nicht abgerufen werden", + "desktop.picker.error.notSelected": "Im Auswahldialog wurde keine Datei ausgewählt", + "desktop.picker.error.sizeLimit": "Die ausgewählten Anhänge überschreiten das Limit von {{limit}} MB", + "command.logs.export": "Protokolle exportieren", + "help.tabs.toast.ariaLabel": + "Neu: Tabs. Organisieren Sie Ihre Arbeit und aktiven Sitzungen mithilfe von Tabs", + "help.tabs.toast.dismiss": "Informationen zu Tabs schließen", + "help.tabs.title": "Neu: Tabs", + "help.tabs.description": "Organisieren Sie Ihre Arbeit und aktiven Sitzungen mithilfe von Tabs", + "help.tabs.date": "14. Juli", + "help.tabs.introduction": "OpenCode Desktop basiert jetzt auf Tabs.", + "help.tabs.sessions": + "Starten Sie eine neue Sitzung in einem Tab oder öffnen Sie eine vorhandene Sitzung aus einem Ihrer Projekte. Öffnen Sie einen neuen Tab, wenn Sie etwas Neues beginnen, und schließen Sie ihn, wenn Sie fertig sind.", + "help.tabs.organize": + "Wenn Sie einige Tabs geöffnet lassen, können Sie Ihre aktiven Sitzungen leichter organisieren. Benennen Sie Tabs einprägsam, wenn Sie sie länger behalten möchten.", + "help.tabs.home": + "Auf der neuen Startseite finden Sie alle Ihre Sitzungen und Projekte. Wenn Sie eine Sitzung auswählen, wird sie in einem Tab geöffnet.", + "help.tabs.persistence": "Wenn Sie die App erneut öffnen, sind Ihre Tabs weiterhin geöffnet.", + "help.tabs.worktrees": + "Das neue Design unterstützt Git-Worktrees noch nicht. Diese Funktion folgt in Kürze. Wenn Sie das bisherige Layout vorerst weiterverwenden möchten, können Sie in den Einstellungen zwischen den Layouts wechseln. Beachten Sie jedoch, dass das neue Layout in einigen Wochen dauerhaft verwendet wird.", } satisfies Partial> diff --git a/packages/app/src/i18n/desktop-native.test.ts b/packages/app/src/i18n/desktop-native.test.ts new file mode 100644 index 00000000000..04a888db066 --- /dev/null +++ b/packages/app/src/i18n/desktop-native.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from "bun:test" +import { + createDesktopNativeBundle, + DESKTOP_NATIVE_ENGLISH, + DESKTOP_NATIVE_KEYS, + DESKTOP_NATIVE_MAX_PAYLOAD_BYTES, + formatDesktopNativeMessage, + parseDesktopNativeBundle, +} from "./desktop-native" + +describe("desktop native translations", () => { + test("accepts the exact typed bundle", () => { + const bundle = createDesktopNativeBundle("en", (key) => DESKTOP_NATIVE_ENGLISH[key]) + expect(parseDesktopNativeBundle(bundle)).toEqual(bundle) + }) + + test("rejects unsupported locales and mismatched key sets", () => { + const bundle = createDesktopNativeBundle("en", (key) => DESKTOP_NATIVE_ENGLISH[key]) + expect(parseDesktopNativeBundle({ ...bundle, locale: "en-US" })).toBeUndefined() + expect( + parseDesktopNativeBundle({ + ...bundle, + messages: Object.fromEntries(DESKTOP_NATIVE_KEYS.slice(1).map((key) => [key, bundle.messages[key]])), + }), + ).toBeUndefined() + expect(parseDesktopNativeBundle({ ...bundle, messages: { ...bundle.messages, extra: "no" } })).toBeUndefined() + expect( + parseDesktopNativeBundle({ + ...bundle, + messages: { ...bundle.messages, [DESKTOP_NATIVE_KEYS[0]]: "x".repeat(DESKTOP_NATIVE_MAX_PAYLOAD_BYTES) }, + }), + ).toBeUndefined() + expect( + parseDesktopNativeBundle({ ...bundle, messages: { ...bundle.messages, [DESKTOP_NATIVE_KEYS[0]]: 1 } }), + ).toBeUndefined() + }) + + test("interpolates native templates without changing unknown placeholders", () => { + expect(formatDesktopNativeMessage("{{known}} {{unknown}}", { known: "yes" })).toBe("yes {{unknown}}") + }) +}) diff --git a/packages/app/src/i18n/desktop-native.ts b/packages/app/src/i18n/desktop-native.ts new file mode 100644 index 00000000000..3c181c1ad55 --- /dev/null +++ b/packages/app/src/i18n/desktop-native.ts @@ -0,0 +1,175 @@ +export const DESKTOP_NATIVE_LOCALES = [ + "en", + "zh", + "zht", + "ko", + "de", + "es", + "fr", + "da", + "ja", + "pl", + "ru", + "uk", + "bs", + "ar", + "no", + "br", + "th", + "tr", + "hi", + "nl", + "id", + "vi", + "it", + "ur", + "pa", + "az", + "fi", + "sv", +] as const + +export type DesktopNativeLocale = (typeof DESKTOP_NATIVE_LOCALES)[number] + +export const DESKTOP_NATIVE_ENGLISH = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "File", + "desktop.menu.edit": "Edit", + "desktop.menu.view": "View", + "desktop.menu.go": "Go", + "desktop.menu.window": "Window", + "desktop.menu.help": "Help", + "desktop.menu.checkForUpdates": "Check for Updates...", + "desktop.menu.settings": "Settings", + "desktop.menu.reloadWebview": "Reload Webview", + "desktop.menu.restart": "Restart", + "desktop.menu.exportLogs": "Export Logs...", + "desktop.menu.newSession": "New Session", + "desktop.menu.openProject": "Open Project...", + "desktop.menu.newWindow": "New Window", + "desktop.menu.closeWindow": "Close Window", + "desktop.menu.undo": "Undo", + "desktop.menu.redo": "Redo", + "desktop.menu.cut": "Cut", + "desktop.menu.copy": "Copy", + "desktop.menu.paste": "Paste", + "desktop.menu.delete": "Delete", + "desktop.menu.selectAll": "Select All", + "desktop.menu.toggleSidebar": "Toggle Sidebar", + "desktop.menu.toggleTerminal": "Toggle Terminal", + "desktop.menu.toggleFileTree": "Toggle File Tree", + "desktop.menu.reload": "Reload", + "desktop.menu.toggleDeveloperTools": "Toggle Developer Tools", + "desktop.menu.actualSize": "Actual Size", + "desktop.menu.zoomIn": "Zoom In", + "desktop.menu.zoomOut": "Zoom Out", + "desktop.menu.toggleFullScreen": "Toggle Full Screen", + "desktop.menu.back": "Back", + "desktop.menu.forward": "Forward", + "desktop.menu.previousSession": "Previous Session", + "desktop.menu.nextSession": "Next Session", + "desktop.menu.previousProject": "Previous Project", + "desktop.menu.nextProject": "Next Project", + "desktop.menu.minimize": "Minimize", + "desktop.menu.maximize": "Maximize", + "desktop.menu.documentation": "OpenCode Documentation", + "desktop.menu.supportForum": "Support Forum", + "desktop.menu.shareFeedback": "Share Feedback", + "desktop.menu.reportBug": "Report a Bug", + "desktop.menu.ariaLabel": "OpenCode menu", + + "desktop.updater.dialog.checkFailed.message": "Update check failed.", + "desktop.updater.dialog.checkFailed.title": "Update Error", + "desktop.updater.dialog.upToDate.message": "You're up to date.", + "desktop.updater.dialog.upToDate.title": "No Updates", + "desktop.updater.dialog.ready.message": "Update {{version}} downloaded. Restart now?", + "desktop.updater.dialog.ready.title": "Update Ready", + "desktop.updater.dialog.restart": "Restart", + "desktop.updater.dialog.later": "Later", + + "desktop.recovery.action.relaunch": "Relaunch", + "desktop.recovery.action.exportLogs": "Export Logs", + "desktop.recovery.action.keepWaiting": "Keep Waiting", + "desktop.recovery.action.quit": "Quit", + "desktop.recovery.loadFailed": "OpenCode failed to load", + "desktop.recovery.terminated": "OpenCode window terminated unexpectedly", + "desktop.recovery.unresponsive": "OpenCode is not responding", + "desktop.recovery.unresponsive.detail": "You can relaunch the app, open the logs, or keep waiting.", + "desktop.recovery.loadFailed.detail": "Window: {{window}}\nURL: {{url}}\nError: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Window: {{window}}\nReason: {{reason}}\nCode: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Choose a folder", + "desktop.dialog.chooseFile": "Choose a file", + "desktop.dialog.saveFile": "Save file", + "desktop.dialog.files": "Files", + "desktop.server.local": "Local Server", + + "desktop.wsl.error.windowsOnly": "WSL is only available on Windows", + "desktop.wsl.error.unavailable": "WSL is unavailable", + "desktop.wsl.error.listInstalled": "Failed to list installed WSL distros", + "desktop.wsl.error.listOnline": "Failed to list online WSL distros", + "desktop.wsl.error.executeDistro": "Cannot execute commands in distro", + "desktop.wsl.error.installWsl": "WSL installation failed", + "desktop.wsl.error.installDistro": "Failed to install distro: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode installation failed", + "desktop.wsl.error.alreadyAdded": "{{distro}} is already added", + "desktop.wsl.error.opencodeMissing": "opencode is not installed in this distro", + "desktop.wsl.error.opencodeCannotRun": "opencode is installed but could not run", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode is not installed in {{distro}}", + "desktop.wsl.error.updateVersion": + "OpenCode update finished but {{distro}} still reports {{installed}}; expected {{expected}}", + "desktop.wsl.error.noVersion": "no version", + "desktop.wsl.error.serverExited": "WSL server exited after startup (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL server exited before becoming healthy (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": "Sidecar for {{distro}} health check timed out after {{timeout}}ms", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} timed out after {{timeout}}ms", + "desktop.wsl.error.failedPort": "Failed to get port", + + "desktop.picker.error.notSelected": "File was not selected by the picker", + "desktop.picker.error.sizeLimit": "Selected attachments exceed the {{limit}} MB limit", +} as const + +export type DesktopNativeKey = keyof typeof DESKTOP_NATIVE_ENGLISH +export type DesktopNativeMessages = Record +export type DesktopNativeBundle = { locale: DesktopNativeLocale; messages: DesktopNativeMessages } + +export const DESKTOP_NATIVE_KEYS = Object.keys(DESKTOP_NATIVE_ENGLISH) as DesktopNativeKey[] +export const DESKTOP_NATIVE_MAX_PAYLOAD_BYTES = 64 * 1024 + +export function createDesktopNativeBundle( + locale: DesktopNativeLocale, + translate: (key: DesktopNativeKey) => string, +): DesktopNativeBundle { + return { + locale, + messages: Object.fromEntries(DESKTOP_NATIVE_KEYS.map((key) => [key, translate(key)])) as DesktopNativeMessages, + } +} + +export function parseDesktopNativeBundle(value: unknown): DesktopNativeBundle | undefined { + if (!value || typeof value !== "object" || Array.isArray(value)) return undefined + try { + if (new TextEncoder().encode(JSON.stringify(value)).byteLength > DESKTOP_NATIVE_MAX_PAYLOAD_BYTES) return undefined + } catch { + return undefined + } + const bundle = value as { locale?: unknown; messages?: unknown } + if (!DESKTOP_NATIVE_LOCALES.some((locale) => locale === bundle.locale)) return undefined + if (!bundle.messages || typeof bundle.messages !== "object" || Array.isArray(bundle.messages)) return undefined + const messages = bundle.messages as Record + const keys = Object.keys(messages) + if (keys.length !== DESKTOP_NATIVE_KEYS.length) return undefined + if (!DESKTOP_NATIVE_KEYS.every((key) => typeof messages[key] === "string")) return undefined + if (!keys.every((key) => key in DESKTOP_NATIVE_ENGLISH)) return undefined + return bundle as DesktopNativeBundle +} + +export function formatDesktopNativeMessage(message: string, params?: Record) { + if (!params) return message + return message.replace(/\{\{([^{}]+)\}\}/g, (match, key: string) => { + const value = params[key] + return value === undefined ? match : String(value) + }) +} diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 76623f44685..5e24547d74f 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -1,4 +1,7 @@ +import { DESKTOP_NATIVE_ENGLISH } from "./desktop-native" + export const dict = { + ...DESKTOP_NATIVE_ENGLISH, "command.category.suggested": "Suggested", "command.category.view": "View", "command.category.project": "Project", @@ -16,6 +19,7 @@ export const dict = { "command.category.permissions": "Permissions", "command.category.workspace": "Workspace", "command.category.settings": "Settings", + "command.logs.export": "Export logs", "theme.scheme.system": "System", "theme.scheme.light": "Light", @@ -131,6 +135,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Login with Claude Pro/Max", "provider.connect.selectMethod": "Select login method for {{provider}}.", "provider.connect.method.apiKey": "API key", + "provider.connect.method.browser": "Browser", + "provider.connect.method.headless": "Headless", "provider.connect.status.inProgress": "Authorization in progress...", "provider.connect.status.waiting": "Waiting for authorization...", "provider.connect.status.failed": "Authorization failed: {{error}}", @@ -163,6 +169,7 @@ export const dict = { "provider.connect.toast.connected.description": "{{provider}} models are now available to use.", "provider.custom.title": "Custom provider", + "provider.custom.unavailable": "Custom providers are unavailable on this server", "provider.custom.description.prefix": "Configure an OpenAI-compatible provider. See the ", "provider.custom.description.link": "provider config docs", "provider.custom.description.suffix": ".", @@ -437,6 +444,22 @@ export const dict = { "wsl.onboarding.desktopVersion": "desktop {{version}}", "wsl.onboarding.versionMismatch": "Installed version does not match the desktop app version.", "wsl.onboarding.adding": "Adding...", + + "help.tabs.toast.ariaLabel": "Introducing Tabs. Organize your work and active sessions with tabs", + "help.tabs.toast.dismiss": "Dismiss Tabs information", + "help.tabs.title": "Introducing Tabs", + "help.tabs.description": "Organize your work and active sessions with tabs", + "help.tabs.date": "July 14", + "help.tabs.introduction": "OpenCode Desktop is now built around tabs.", + "help.tabs.sessions": + "Start a new session in a tab, or open an existing session from any of your projects. Open a new tab when you're starting something new, and close it when you're done.", + "help.tabs.organize": + "Keeping a few tabs open makes it easier to organize your active sessions. Rename tabs to something memorable if you plan to keep them around.", + "help.tabs.home": + "You'll find all your sessions and projects on the new Home screen. Selecting a session opens it in a tab.", + "help.tabs.persistence": "When you reopen the app, your tabs are still open.", + "help.tabs.worktrees": + "The new design does not support Git Worktrees yet, it's coming soon. So if you'd prefer to continue using the previous layout, you can switch between layouts in Settings. Just keep in mind that the new layout will become permanent in a few weeks.", "server.row.noUsername": "no username", "dialog.project.edit.title": "Edit project", @@ -765,6 +788,12 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket closed abnormally: {{code}}", "terminal.connectionLost.description": "The terminal connection was interrupted. This can happen when the server restarts.", + "terminal.connectTicket.csrfError": + "PTY connect ticket rejected by origin or CSRF checks. Check the server CORS config.", + "terminal.connectTicket.statusError": "PTY connect ticket failed with {{status}}", + + "titlebar.update": "Update", + "titlebar.updateVersion": "Update {{version}}", "common.closeTab": "Close tab", "common.dismiss": "Dismiss", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index d133c4c796d..6fe7d6baf8b 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -1,4 +1,106 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Archivo", + "desktop.menu.edit": "Editar", + "desktop.menu.view": "Ver", + "desktop.menu.go": "Ir", + "desktop.menu.window": "Ventana", + "desktop.menu.help": "Ayuda", + "desktop.menu.checkForUpdates": "Buscar actualizaciones...", + "desktop.menu.settings": "Ajustes", + "desktop.menu.reloadWebview": "Volver a cargar la vista web", + "desktop.menu.restart": "Reiniciar", + "desktop.menu.exportLogs": "Exportar registros...", + "desktop.menu.newSession": "Nueva sesión", + "desktop.menu.openProject": "Abrir proyecto...", + "desktop.menu.newWindow": "Nueva ventana", + "desktop.menu.closeWindow": "Cerrar ventana", + "desktop.menu.undo": "Deshacer", + "desktop.menu.redo": "Rehacer", + "desktop.menu.cut": "Cortar", + "desktop.menu.copy": "Copiar", + "desktop.menu.paste": "Pegar", + "desktop.menu.delete": "Eliminar", + "desktop.menu.selectAll": "Seleccionar todo", + "desktop.menu.toggleSidebar": "Mostrar u ocultar barra lateral", + "desktop.menu.toggleTerminal": "Mostrar u ocultar terminal", + "desktop.menu.toggleFileTree": "Mostrar u ocultar árbol de archivos", + "desktop.menu.reload": "Volver a cargar", + "desktop.menu.toggleDeveloperTools": "Mostrar u ocultar herramientas para desarrolladores", + "desktop.menu.actualSize": "Tamaño real", + "desktop.menu.zoomIn": "Ampliar", + "desktop.menu.zoomOut": "Reducir", + "desktop.menu.toggleFullScreen": "Mostrar u ocultar pantalla completa", + "desktop.menu.back": "Atrás", + "desktop.menu.forward": "Adelante", + "desktop.menu.previousSession": "Sesión anterior", + "desktop.menu.nextSession": "Siguiente sesión", + "desktop.menu.previousProject": "Proyecto anterior", + "desktop.menu.nextProject": "Siguiente proyecto", + "desktop.menu.minimize": "Minimizar", + "desktop.menu.maximize": "Maximizar", + "desktop.menu.documentation": "Documentación de OpenCode", + "desktop.menu.supportForum": "Foro de asistencia", + "desktop.menu.shareFeedback": "Enviar comentarios", + "desktop.menu.reportBug": "Informar de un error", + "desktop.menu.ariaLabel": "Menú de OpenCode", + + "desktop.updater.dialog.checkFailed.message": "No se pudo buscar actualizaciones.", + "desktop.updater.dialog.checkFailed.title": "Error de actualización", + "desktop.updater.dialog.upToDate.message": "Estás al día.", + "desktop.updater.dialog.upToDate.title": "No hay actualizaciones", + "desktop.updater.dialog.ready.message": "Se ha descargado la actualización {{version}}. ¿Quieres reiniciar ahora?", + "desktop.updater.dialog.ready.title": "Actualización lista", + "desktop.updater.dialog.restart": "Reiniciar", + "desktop.updater.dialog.later": "Más tarde", + + "desktop.recovery.action.relaunch": "Volver a iniciar", + "desktop.recovery.action.exportLogs": "Exportar registros", + "desktop.recovery.action.keepWaiting": "Seguir esperando", + "desktop.recovery.action.quit": "Salir", + "desktop.recovery.loadFailed": "No se pudo cargar OpenCode", + "desktop.recovery.terminated": "La ventana de OpenCode se cerró de forma inesperada", + "desktop.recovery.unresponsive": "OpenCode no responde", + "desktop.recovery.unresponsive.detail": + "Puedes volver a iniciar la aplicación, abrir los registros o seguir esperando.", + "desktop.recovery.loadFailed.detail": "Ventana: {{window}}\nURL: {{url}}\nError: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Ventana: {{window}}\nMotivo: {{reason}}\nCódigo: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Elegir una carpeta", + "desktop.dialog.chooseFile": "Elegir un archivo", + "desktop.dialog.saveFile": "Guardar archivo", + "desktop.dialog.files": "Archivos", + "desktop.server.local": "Servidor local", + + "desktop.wsl.error.windowsOnly": "WSL solo está disponible en Windows", + "desktop.wsl.error.unavailable": "WSL no está disponible", + "desktop.wsl.error.listInstalled": "No se pudieron listar las distribuciones WSL instaladas", + "desktop.wsl.error.listOnline": "No se pudieron listar las distribuciones WSL en línea", + "desktop.wsl.error.executeDistro": "No se pueden ejecutar comandos en la distribución", + "desktop.wsl.error.installWsl": "No se pudo instalar WSL", + "desktop.wsl.error.installDistro": "No se pudo instalar la distribución: {{distro}}", + "desktop.wsl.error.installOpencode": "No se pudo instalar OpenCode", + "desktop.wsl.error.alreadyAdded": "Ya se ha añadido {{distro}}", + "desktop.wsl.error.opencodeMissing": "opencode no está instalado en esta distribución", + "desktop.wsl.error.opencodeCannotRun": "opencode está instalado, pero no se pudo ejecutar", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode no está instalado en {{distro}}", + "desktop.wsl.error.updateVersion": + "La actualización de OpenCode finalizó, pero {{distro}} sigue indicando {{installed}}; se esperaba {{expected}}", + "desktop.wsl.error.noVersion": "sin versión", + "desktop.wsl.error.serverExited": + "El servidor WSL se cerró después de iniciarse (código={{code}} señal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "El servidor WSL se cerró antes de estar operativo (código={{code}} señal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Se agotó el tiempo de espera de la comprobación de estado del sidecar de {{distro}} tras {{timeout}} ms", + "desktop.wsl.error.commandTimeout": + "Se agotó el tiempo de espera de {{command}} {{args}} tras {{timeout}} ms", + "desktop.wsl.error.failedPort": "No se pudo obtener el puerto", + + "desktop.picker.error.notSelected": "No se seleccionó ningún archivo en el selector", + "desktop.picker.error.sizeLimit": "Los adjuntos seleccionados superan el límite de {{limit}} MB", + "command.category.suggested": "Sugerido", "command.category.view": "Ver", "command.category.project": "Proyecto", @@ -16,6 +118,7 @@ export const dict = { "command.category.permissions": "Permisos", "command.category.workspace": "Espacio de trabajo", "command.category.settings": "Ajustes", + "command.logs.export": "Exportar registros", "theme.scheme.system": "Sistema", "theme.scheme.light": "Claro", @@ -131,6 +234,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Iniciar sesión con Claude Pro/Max", "provider.connect.selectMethod": "Seleccionar método de inicio de sesión para {{provider}}.", "provider.connect.method.apiKey": "Clave API", + "provider.connect.method.browser": "Navegador", + "provider.connect.method.headless": "Sin navegador", "provider.connect.status.inProgress": "Autorización en progreso...", "provider.connect.status.waiting": "Esperando autorización...", "provider.connect.status.failed": "Autorización fallida: {{error}}", @@ -163,6 +268,7 @@ export const dict = { "provider.connect.toast.connected.description": "Los modelos de {{provider}} ahora están disponibles para usar.", "provider.custom.title": "Proveedor personalizado", + "provider.custom.unavailable": "Los proveedores personalizados no están disponibles en este servidor", "provider.custom.description.prefix": "Configurar un proveedor compatible con OpenAI. Ver la ", "provider.custom.description.link": "documentación de configuración del proveedor", "provider.custom.description.suffix": ".", @@ -434,6 +540,23 @@ export const dict = { "wsl.onboarding.versionMismatch": "La versión instalada no coincide con la versión de la aplicación de escritorio.", "wsl.onboarding.adding": "Añadiendo...", + "help.tabs.toast.ariaLabel": + "Presentamos las pestañas. Organiza tu trabajo y las sesiones activas con pestañas", + "help.tabs.toast.dismiss": "Descartar información sobre las pestañas", + "help.tabs.title": "Presentamos las pestañas", + "help.tabs.description": "Organiza tu trabajo y las sesiones activas con pestañas", + "help.tabs.date": "14 de julio", + "help.tabs.introduction": "OpenCode Desktop ahora se organiza en torno a pestañas.", + "help.tabs.sessions": + "Inicia una nueva sesión en una pestaña o abre una sesión existente de cualquiera de tus proyectos. Abre una pestaña nueva cuando empieces algo nuevo y ciérrala cuando termines.", + "help.tabs.organize": + "Mantener algunas pestañas abiertas facilita la organización de las sesiones activas. Cambia el nombre de las pestañas por uno fácil de recordar si piensas conservarlas.", + "help.tabs.home": + "Encontrarás todas tus sesiones y proyectos en la nueva pantalla Inicio. Al seleccionar una sesión, se abre en una pestaña.", + "help.tabs.persistence": "Cuando vuelvas a abrir la aplicación, las pestañas seguirán abiertas.", + "help.tabs.worktrees": + "El nuevo diseño aún no admite Git Worktrees, pero estarán disponibles pronto. Si prefieres seguir usando el diseño anterior, puedes cambiar de diseño en Ajustes. Ten en cuenta que el nuevo diseño será permanente dentro de unas semanas.", + "dialog.project.edit.title": "Editar proyecto", "dialog.project.edit.name": "Nombre", "dialog.project.edit.icon": "Icono", @@ -728,6 +851,12 @@ export const dict = { "terminal.connectionLost.title": "Conexión perdida", "terminal.connectionLost.description": "La conexión del terminal se interrumpió. Esto puede ocurrir cuando el servidor se reinicia.", + "terminal.connectTicket.csrfError": + "El ticket de conexión PTY fue rechazado por las comprobaciones de origen o CSRF. Comprueba la configuración CORS del servidor.", + "terminal.connectTicket.statusError": "El ticket de conexión PTY falló con {{status}}", + + "titlebar.update": "Actualizar", + "titlebar.updateVersion": "Actualizar {{version}}", "common.closeTab": "Cerrar pestaña", "common.dismiss": "Descartar", diff --git a/packages/app/src/i18n/fi.ts b/packages/app/src/i18n/fi.ts index 573e2526c7e..563b1c60fb8 100644 --- a/packages/app/src/i18n/fi.ts +++ b/packages/app/src/i18n/fi.ts @@ -119,6 +119,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Kirjaudu sisään Claude Pro/Maxilla", "provider.connect.selectMethod": "Valitse kirjautumistapa palvelulle {{provider}}.", "provider.connect.method.apiKey": "API-avain", + "provider.connect.method.browser": "Selain", + "provider.connect.method.headless": "Päätön", "provider.connect.status.inProgress": "Valtuutus käynnissä...", "provider.connect.status.waiting": "Odotetaan valtuutusta...", "provider.connect.status.failed": "Valtuutus epäonnistui: {{error}}", @@ -150,6 +152,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}} yhdistetty", "provider.connect.toast.connected.description": "{{provider}}-mallit ovat nyt käytettävissä.", "provider.custom.title": "Mukautettu palveluntarjoaja", + "provider.custom.unavailable": "Mukautetut palveluntarjoajat eivät ole käytettävissä tällä palvelimella", "provider.custom.description.prefix": "Määritä OpenAI-yhteensopiva palveluntarjoaja. Katso ", "provider.custom.description.link": "palveluntarjoajan asetusdokumentit", "provider.custom.description.suffix": ".", @@ -707,6 +710,11 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket sulkeutui poikkeavasti: {{code}}", "terminal.connectionLost.description": "Terminaaliyhteys katkesi. Näin voi tapahtua, kun palvelin käynnistyy uudelleen.", + "terminal.connectTicket.csrfError": + "PTY-yhteyslippu hylättiin alkuperä- tai CSRF-tarkistuksessa. Tarkista palvelimen CORS-määritys.", + "terminal.connectTicket.statusError": "PTY-yhteyslippu epäonnistui tilakoodilla {{status}}", + "titlebar.update": "Päivitä", + "titlebar.updateVersion": "Päivitä {{version}}", "common.closeTab": "Sulje välilehti", "common.dismiss": "Hylkää", "common.moreCountSuffix": " (+{{count}} muuta)", @@ -1027,4 +1035,117 @@ export const dict = { "workspace.reset.archived.one": "1 istunto arkistoidaan.", "workspace.reset.archived.many": "{{count}} istuntoa arkistoidaan.", "workspace.reset.note": "Tämä palauttaa työtilan vastaamaan oletushaaraa.", + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Tiedosto", + "desktop.menu.edit": "Muokkaa", + "desktop.menu.view": "Näytä", + "desktop.menu.go": "Siirry", + "desktop.menu.window": "Ikkuna", + "desktop.menu.help": "Ohje", + "desktop.menu.checkForUpdates": "Tarkista päivitykset...", + "desktop.menu.settings": "Asetukset", + "desktop.menu.reloadWebview": "Lataa verkkonäkymä uudelleen", + "desktop.menu.restart": "Käynnistä uudelleen", + "desktop.menu.exportLogs": "Vie lokit...", + "desktop.menu.newSession": "Uusi istunto", + "desktop.menu.openProject": "Avaa projekti...", + "desktop.menu.newWindow": "Uusi ikkuna", + "desktop.menu.closeWindow": "Sulje ikkuna", + "desktop.menu.undo": "Kumoa", + "desktop.menu.redo": "Tee uudelleen", + "desktop.menu.cut": "Leikkaa", + "desktop.menu.copy": "Kopioi", + "desktop.menu.paste": "Liitä", + "desktop.menu.delete": "Poista", + "desktop.menu.selectAll": "Valitse kaikki", + "desktop.menu.toggleSidebar": "Näytä tai piilota sivupalkki", + "desktop.menu.toggleTerminal": "Näytä tai piilota terminaali", + "desktop.menu.toggleFileTree": "Näytä tai piilota tiedostopuu", + "desktop.menu.reload": "Lataa uudelleen", + "desktop.menu.toggleDeveloperTools": "Näytä tai piilota kehittäjätyökalut", + "desktop.menu.actualSize": "Todellinen koko", + "desktop.menu.zoomIn": "Lähennä", + "desktop.menu.zoomOut": "Loitonna", + "desktop.menu.toggleFullScreen": "Koko näytön tila", + "desktop.menu.back": "Takaisin", + "desktop.menu.forward": "Eteenpäin", + "desktop.menu.previousSession": "Edellinen istunto", + "desktop.menu.nextSession": "Seuraava istunto", + "desktop.menu.previousProject": "Edellinen projekti", + "desktop.menu.nextProject": "Seuraava projekti", + "desktop.menu.minimize": "Pienennä", + "desktop.menu.maximize": "Suurenna", + "desktop.menu.documentation": "OpenCode-dokumentaatio", + "desktop.menu.supportForum": "Tukifoorumi", + "desktop.menu.shareFeedback": "Anna palautetta", + "desktop.menu.reportBug": "Ilmoita ohjelmavirheestä", + "desktop.menu.ariaLabel": "OpenCode-valikko", + "desktop.updater.dialog.checkFailed.message": "Päivitysten tarkistus epäonnistui.", + "desktop.updater.dialog.checkFailed.title": "Päivitysvirhe", + "desktop.updater.dialog.upToDate.message": "Olet ajan tasalla.", + "desktop.updater.dialog.upToDate.title": "Ei päivityksiä", + "desktop.updater.dialog.ready.message": "Päivitys {{version}} on ladattu. Käynnistetäänkö nyt uudelleen?", + "desktop.updater.dialog.ready.title": "Päivitys valmis", + "desktop.updater.dialog.restart": "Käynnistä uudelleen", + "desktop.updater.dialog.later": "Myöhemmin", + "desktop.recovery.action.relaunch": "Käynnistä uudelleen", + "desktop.recovery.action.exportLogs": "Vie lokit", + "desktop.recovery.action.keepWaiting": "Jatka odottamista", + "desktop.recovery.action.quit": "Lopeta", + "desktop.recovery.loadFailed": "OpenCoden lataaminen epäonnistui", + "desktop.recovery.terminated": "OpenCode-ikkuna sulkeutui odottamatta", + "desktop.recovery.unresponsive": "OpenCode ei vastaa", + "desktop.recovery.unresponsive.detail": + "Voit käynnistää sovelluksen uudelleen, avata lokit tai jatkaa odottamista.", + "desktop.recovery.loadFailed.detail": "Ikkuna: {{window}}\nURL: {{url}}\nVirhe: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Ikkuna: {{window}}\nSyy: {{reason}}\nKoodi: {{code}}", + "desktop.recovery.unknown": "", + "desktop.dialog.chooseFolder": "Valitse kansio", + "desktop.dialog.chooseFile": "Valitse tiedosto", + "desktop.dialog.saveFile": "Tallenna tiedosto", + "desktop.dialog.files": "Tiedostot", + "desktop.server.local": "Paikallinen palvelin", + "desktop.wsl.error.windowsOnly": "WSL on käytettävissä vain Windowsissa", + "desktop.wsl.error.unavailable": "WSL ei ole käytettävissä", + "desktop.wsl.error.listInstalled": "Asennettujen WSL-jakelujen luettelointi epäonnistui", + "desktop.wsl.error.listOnline": "Verkossa saatavilla olevien WSL-jakelujen luettelointi epäonnistui", + "desktop.wsl.error.executeDistro": "Jakelussa ei voi suorittaa komentoja", + "desktop.wsl.error.installWsl": "WSL:n asennus epäonnistui", + "desktop.wsl.error.installDistro": "Jakelun asentaminen epäonnistui: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCoden asennus epäonnistui", + "desktop.wsl.error.alreadyAdded": "{{distro}} on jo lisätty", + "desktop.wsl.error.opencodeMissing": "opencodea ei ole asennettu tähän jakeluun", + "desktop.wsl.error.opencodeCannotRun": "opencode on asennettu, mutta sitä ei voitu suorittaa", + "desktop.wsl.error.opencodeNotInstalled": "OpenCodea ei ole asennettu jakeluun {{distro}}", + "desktop.wsl.error.updateVersion": + "OpenCoden päivitys valmistui, mutta {{distro}} ilmoittaa yhä version {{installed}}; odotettu versio on {{expected}}", + "desktop.wsl.error.noVersion": "ei versiota", + "desktop.wsl.error.serverExited": + "WSL-palvelin sulkeutui käynnistyksen jälkeen (koodi={{code}} signaali={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL-palvelin sulkeutui ennen toimintavalmiutta (koodi={{code}} signaali={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Jakelun {{distro}} sivuprosessin kuntotarkistus aikakatkaistiin {{timeout}} ms:n jälkeen", + "desktop.wsl.error.commandTimeout": + "Komento {{command}} {{args}} aikakatkaistiin {{timeout}} ms:n jälkeen", + "desktop.wsl.error.failedPort": "Portin noutaminen epäonnistui", + "desktop.picker.error.notSelected": "Tiedostovalitsimessa ei valittu tiedostoa", + "desktop.picker.error.sizeLimit": "Valitut liitteet ylittävät {{limit}} Mt:n rajan", + "command.logs.export": "Vie lokit", + "help.tabs.toast.ariaLabel": + "Esittelyssä välilehdet. Järjestä työsi ja aktiiviset istuntosi välilehtien avulla", + "help.tabs.toast.dismiss": "Sulje välilehtien tiedot", + "help.tabs.title": "Esittelyssä välilehdet", + "help.tabs.description": "Järjestä työsi ja aktiiviset istuntosi välilehtien avulla", + "help.tabs.date": "14. heinäkuuta", + "help.tabs.introduction": "OpenCode Desktop perustuu nyt välilehtiin.", + "help.tabs.sessions": + "Aloita uusi istunto välilehdessä tai avaa olemassa oleva istunto mistä tahansa projektistasi. Avaa uusi välilehti, kun aloitat jotain uutta, ja sulje se, kun olet valmis.", + "help.tabs.organize": + "Muutaman välilehden pitäminen avoinna helpottaa aktiivisten istuntojen järjestämistä. Nimeä välilehdet helposti muistettaviksi, jos aiot pitää ne avoinna.", + "help.tabs.home": + "Kaikki istuntosi ja projektisi ovat uudessa etusivunäkymässä. Istunnon valitseminen avaa sen välilehteen.", + "help.tabs.persistence": "Kun avaat sovelluksen uudelleen, välilehtesi ovat yhä avoinna.", + "help.tabs.worktrees": + "Uusi ulkoasu ei vielä tue Git-työpuita, mutta tuki on tulossa pian. Jos haluat jatkaa aiemman ulkoasun käyttöä, voit vaihtaa ulkoasua asetuksissa. Huomaa kuitenkin, että uudesta ulkoasusta tulee pysyvä muutaman viikon kuluttua.", } diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index adba6c8caab..e6347550fca 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -1,4 +1,104 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Fichier", + "desktop.menu.edit": "Édition", + "desktop.menu.view": "Affichage", + "desktop.menu.go": "Aller", + "desktop.menu.window": "Fenêtre", + "desktop.menu.help": "Aide", + "desktop.menu.checkForUpdates": "Rechercher les mises à jour...", + "desktop.menu.settings": "Paramètres", + "desktop.menu.reloadWebview": "Recharger la vue Web", + "desktop.menu.restart": "Redémarrer", + "desktop.menu.exportLogs": "Exporter les journaux...", + "desktop.menu.newSession": "Nouvelle session", + "desktop.menu.openProject": "Ouvrir un projet...", + "desktop.menu.newWindow": "Nouvelle fenêtre", + "desktop.menu.closeWindow": "Fermer la fenêtre", + "desktop.menu.undo": "Annuler", + "desktop.menu.redo": "Rétablir", + "desktop.menu.cut": "Couper", + "desktop.menu.copy": "Copier", + "desktop.menu.paste": "Coller", + "desktop.menu.delete": "Supprimer", + "desktop.menu.selectAll": "Tout sélectionner", + "desktop.menu.toggleSidebar": "Afficher ou masquer la barre latérale", + "desktop.menu.toggleTerminal": "Afficher ou masquer le terminal", + "desktop.menu.toggleFileTree": "Afficher ou masquer l'arborescence des fichiers", + "desktop.menu.reload": "Recharger", + "desktop.menu.toggleDeveloperTools": "Afficher ou masquer les outils de développement", + "desktop.menu.actualSize": "Taille réelle", + "desktop.menu.zoomIn": "Zoom avant", + "desktop.menu.zoomOut": "Zoom arrière", + "desktop.menu.toggleFullScreen": "Activer ou désactiver le mode plein écran", + "desktop.menu.back": "Précédent", + "desktop.menu.forward": "Suivant", + "desktop.menu.previousSession": "Session précédente", + "desktop.menu.nextSession": "Session suivante", + "desktop.menu.previousProject": "Projet précédent", + "desktop.menu.nextProject": "Projet suivant", + "desktop.menu.minimize": "Réduire", + "desktop.menu.maximize": "Agrandir", + "desktop.menu.documentation": "Documentation d'OpenCode", + "desktop.menu.supportForum": "Forum d'assistance", + "desktop.menu.shareFeedback": "Envoyer des commentaires", + "desktop.menu.reportBug": "Signaler un bogue", + "desktop.menu.ariaLabel": "Menu OpenCode", + + "desktop.updater.dialog.checkFailed.message": "La recherche de mises à jour a échoué.", + "desktop.updater.dialog.checkFailed.title": "Erreur de mise à jour", + "desktop.updater.dialog.upToDate.message": "Vous êtes à jour.", + "desktop.updater.dialog.upToDate.title": "Aucune mise à jour", + "desktop.updater.dialog.ready.message": "La mise à jour {{version}} a été téléchargée. Redémarrer maintenant ?", + "desktop.updater.dialog.ready.title": "Mise à jour prête", + "desktop.updater.dialog.restart": "Redémarrer", + "desktop.updater.dialog.later": "Plus tard", + + "desktop.recovery.action.relaunch": "Relancer", + "desktop.recovery.action.exportLogs": "Exporter les journaux", + "desktop.recovery.action.keepWaiting": "Continuer d'attendre", + "desktop.recovery.action.quit": "Quitter", + "desktop.recovery.loadFailed": "Échec du chargement d'OpenCode", + "desktop.recovery.terminated": "La fenêtre OpenCode s'est arrêtée de manière inattendue", + "desktop.recovery.unresponsive": "OpenCode ne répond pas", + "desktop.recovery.unresponsive.detail": + "Vous pouvez relancer l'application, ouvrir les journaux ou continuer d'attendre.", + "desktop.recovery.loadFailed.detail": "Fenêtre : {{window}}\nURL : {{url}}\nErreur : {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Fenêtre : {{window}}\nMotif : {{reason}}\nCode : {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Sélectionner un dossier", + "desktop.dialog.chooseFile": "Sélectionner un fichier", + "desktop.dialog.saveFile": "Enregistrer le fichier", + "desktop.dialog.files": "Fichiers", + "desktop.server.local": "Serveur local", + + "desktop.wsl.error.windowsOnly": "WSL est disponible uniquement sous Windows", + "desktop.wsl.error.unavailable": "WSL est indisponible", + "desktop.wsl.error.listInstalled": "Impossible de répertorier les distributions WSL installées", + "desktop.wsl.error.listOnline": "Impossible de répertorier les distributions WSL en ligne", + "desktop.wsl.error.executeDistro": "Impossible d'exécuter des commandes dans la distribution", + "desktop.wsl.error.installWsl": "Échec de l'installation de WSL", + "desktop.wsl.error.installDistro": "Échec de l'installation de la distribution : {{distro}}", + "desktop.wsl.error.installOpencode": "Échec de l'installation d'OpenCode", + "desktop.wsl.error.alreadyAdded": "{{distro}} a déjà été ajoutée", + "desktop.wsl.error.opencodeMissing": "opencode n'est pas installé dans cette distribution", + "desktop.wsl.error.opencodeCannotRun": "opencode est installé, mais n'a pas pu s'exécuter", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode n'est pas installé dans {{distro}}", + "desktop.wsl.error.updateVersion": + "La mise à jour d'OpenCode est terminée, mais {{distro}} indique toujours {{installed}} au lieu de {{expected}}", + "desktop.wsl.error.noVersion": "aucune version", + "desktop.wsl.error.serverExited": "Le serveur WSL s'est arrêté après son démarrage (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "Le serveur WSL s'est arrêté avant d'être opérationnel (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "La vérification de l'état du processus auxiliaire pour {{distro}} a dépassé le délai de {{timeout}} ms", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} a dépassé le délai de {{timeout}} ms", + "desktop.wsl.error.failedPort": "Impossible d'obtenir le port", + + "desktop.picker.error.notSelected": "Aucun fichier n'a été sélectionné dans le sélecteur", + "desktop.picker.error.sizeLimit": "Les pièces jointes sélectionnées dépassent la limite de {{limit}} Mo", + "command.category.suggested": "Suggéré", "command.category.view": "Affichage", "command.category.project": "Projet", @@ -16,6 +116,7 @@ export const dict = { "command.category.permissions": "Permissions", "command.category.workspace": "Espace de travail", "command.category.settings": "Paramètres", + "command.logs.export": "Exporter les journaux", "theme.scheme.system": "Système", "theme.scheme.light": "Clair", "theme.scheme.dark": "Sombre", @@ -119,6 +220,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Connexion avec Claude Pro/Max", "provider.connect.selectMethod": "Sélectionnez la méthode de connexion pour {{provider}}.", "provider.connect.method.apiKey": "Clé API", + "provider.connect.method.browser": "Navigateur", + "provider.connect.method.headless": "Mode headless", "provider.connect.status.inProgress": "Autorisation en cours...", "provider.connect.status.waiting": "En attente d'autorisation...", "provider.connect.status.failed": "Échec de l'autorisation : {{error}}", @@ -150,6 +253,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}} connecté", "provider.connect.toast.connected.description": "Les modèles {{provider}} sont maintenant disponibles.", "provider.custom.title": "Fournisseur personnalisé", + "provider.custom.unavailable": "Les fournisseurs personnalisés ne sont pas disponibles sur ce serveur", "provider.custom.description.prefix": "Configurez un fournisseur compatible avec OpenAI. Consultez la ", "provider.custom.description.link": "documentation sur la configuration des fournisseurs", "provider.custom.description.suffix": ".", @@ -408,6 +512,22 @@ export const dict = { "wsl.onboarding.desktopVersion": "application de bureau {{version}}", "wsl.onboarding.versionMismatch": "La version installée ne correspond pas à celle de l'application de bureau.", "wsl.onboarding.adding": "Ajout...", + "help.tabs.toast.ariaLabel": + "Présentation des onglets. Organisez votre travail et vos sessions actives à l'aide d'onglets", + "help.tabs.toast.dismiss": "Ignorer les informations sur les onglets", + "help.tabs.title": "Présentation des onglets", + "help.tabs.description": "Organisez votre travail et vos sessions actives à l'aide d'onglets", + "help.tabs.date": "14 juillet", + "help.tabs.introduction": "OpenCode Desktop s'articule désormais autour d'onglets.", + "help.tabs.sessions": + "Démarrez une nouvelle session dans un onglet ou ouvrez une session existante depuis l'un de vos projets. Ouvrez un nouvel onglet lorsque vous commencez une nouvelle tâche et fermez-le une fois celle-ci terminée.", + "help.tabs.organize": + "Garder quelques onglets ouverts facilite l'organisation de vos sessions actives. Si vous comptez les conserver, renommez-les pour pouvoir les reconnaître facilement.", + "help.tabs.home": + "Vous trouverez toutes vos sessions et tous vos projets sur le nouvel écran d'accueil. La sélection d'une session l'ouvre dans un onglet.", + "help.tabs.persistence": "Lorsque vous rouvrez l'application, vos onglets sont toujours ouverts.", + "help.tabs.worktrees": + "La nouvelle interface ne prend pas encore en charge les arbres de travail Git, mais cette fonctionnalité sera bientôt disponible. Si vous préférez continuer à utiliser la mise en page précédente, vous pouvez changer de mise en page dans les paramètres. N'oubliez pas que la nouvelle mise en page deviendra définitive dans quelques semaines.", "dialog.project.edit.title": "Modifier le projet", "dialog.project.edit.name": "Nom", "dialog.project.edit.icon": "Icône", @@ -1049,4 +1169,10 @@ export const dict = { "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}}", + "terminal.connectTicket.csrfError": + "Le ticket de connexion PTY a été rejeté par les contrôles d'origine ou CSRF. Vérifiez la configuration CORS du serveur.", + "terminal.connectTicket.statusError": "Le ticket de connexion PTY a échoué avec le statut {{status}}", + + "titlebar.update": "Mettre à jour", + "titlebar.updateVersion": "Mettre à jour {{version}}", } diff --git a/packages/app/src/i18n/hi.ts b/packages/app/src/i18n/hi.ts index 9a99651555f..aa7f511d174 100644 --- a/packages/app/src/i18n/hi.ts +++ b/packages/app/src/i18n/hi.ts @@ -1,4 +1,104 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "फ़ाइल", + "desktop.menu.edit": "संपादित करें", + "desktop.menu.view": "दृश्य", + "desktop.menu.go": "जाएँ", + "desktop.menu.window": "विंडो", + "desktop.menu.help": "सहायता", + "desktop.menu.checkForUpdates": "अपडेट की जाँच करें...", + "desktop.menu.settings": "सेटिंग्स", + "desktop.menu.reloadWebview": "वेबव्यू पुनः लोड करें", + "desktop.menu.restart": "पुनः आरंभ करें", + "desktop.menu.exportLogs": "लॉग निर्यात करें...", + "desktop.menu.newSession": "नया सेशन", + "desktop.menu.openProject": "प्रोजेक्ट खोलें...", + "desktop.menu.newWindow": "नई विंडो", + "desktop.menu.closeWindow": "विंडो बंद करें", + "desktop.menu.undo": "पूर्ववत करें", + "desktop.menu.redo": "फिर से करें", + "desktop.menu.cut": "काटें", + "desktop.menu.copy": "कॉपी करें", + "desktop.menu.paste": "चिपकाएँ", + "desktop.menu.delete": "हटाएँ", + "desktop.menu.selectAll": "सभी चुनें", + "desktop.menu.toggleSidebar": "साइडबार टॉगल करें", + "desktop.menu.toggleTerminal": "टर्मिनल टॉगल करें", + "desktop.menu.toggleFileTree": "फ़ाइल ट्री टॉगल करें", + "desktop.menu.reload": "पुनः लोड करें", + "desktop.menu.toggleDeveloperTools": "डेवलपर टूल टॉगल करें", + "desktop.menu.actualSize": "वास्तविक आकार", + "desktop.menu.zoomIn": "ज़ूम इन करें", + "desktop.menu.zoomOut": "ज़ूम आउट करें", + "desktop.menu.toggleFullScreen": "पूर्ण स्क्रीन टॉगल करें", + "desktop.menu.back": "पीछे", + "desktop.menu.forward": "आगे", + "desktop.menu.previousSession": "पिछला सेशन", + "desktop.menu.nextSession": "अगला सेशन", + "desktop.menu.previousProject": "पिछला प्रोजेक्ट", + "desktop.menu.nextProject": "अगला प्रोजेक्ट", + "desktop.menu.minimize": "छोटा करें", + "desktop.menu.maximize": "बड़ा करें", + "desktop.menu.documentation": "OpenCode दस्तावेज़", + "desktop.menu.supportForum": "सहायता फ़ोरम", + "desktop.menu.shareFeedback": "प्रतिक्रिया साझा करें", + "desktop.menu.reportBug": "बग रिपोर्ट करें", + "desktop.menu.ariaLabel": "OpenCode मेन्यू", + + "desktop.updater.dialog.checkFailed.message": "अपडेट की जाँच विफल रही।", + "desktop.updater.dialog.checkFailed.title": "अपडेट त्रुटि", + "desktop.updater.dialog.upToDate.message": "आप नवीनतम संस्करण का उपयोग कर रहे हैं।", + "desktop.updater.dialog.upToDate.title": "कोई अपडेट नहीं", + "desktop.updater.dialog.ready.message": "अपडेट {{version}} डाउनलोड हो गया है। अभी पुनः आरंभ करें?", + "desktop.updater.dialog.ready.title": "अपडेट तैयार है", + "desktop.updater.dialog.restart": "पुनः आरंभ करें", + "desktop.updater.dialog.later": "बाद में", + + "desktop.recovery.action.relaunch": "पुनः लॉन्च करें", + "desktop.recovery.action.exportLogs": "लॉग निर्यात करें", + "desktop.recovery.action.keepWaiting": "प्रतीक्षा जारी रखें", + "desktop.recovery.action.quit": "बाहर निकलें", + "desktop.recovery.loadFailed": "OpenCode लोड नहीं हो सका", + "desktop.recovery.terminated": "OpenCode विंडो अनपेक्षित रूप से बंद हो गई", + "desktop.recovery.unresponsive": "OpenCode प्रतिसाद नहीं दे रहा है", + "desktop.recovery.unresponsive.detail": + "आप ऐप को पुनः लॉन्च कर सकते हैं, लॉग खोल सकते हैं या प्रतीक्षा जारी रख सकते हैं।", + "desktop.recovery.loadFailed.detail": "विंडो: {{window}}\nURL: {{url}}\nत्रुटि: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "विंडो: {{window}}\nकारण: {{reason}}\nकोड: {{code}}", + "desktop.recovery.unknown": "<अज्ञात>", + + "desktop.dialog.chooseFolder": "फ़ोल्डर चुनें", + "desktop.dialog.chooseFile": "फ़ाइल चुनें", + "desktop.dialog.saveFile": "फ़ाइल सहेजें", + "desktop.dialog.files": "फ़ाइलें", + "desktop.server.local": "स्थानीय सर्वर", + + "desktop.wsl.error.windowsOnly": "WSL केवल Windows पर उपलब्ध है", + "desktop.wsl.error.unavailable": "WSL उपलब्ध नहीं है", + "desktop.wsl.error.listInstalled": "इंस्टॉल किए गए WSL डिस्ट्रो की सूची नहीं बनाई जा सकी", + "desktop.wsl.error.listOnline": "ऑनलाइन WSL डिस्ट्रो की सूची नहीं बनाई जा सकी", + "desktop.wsl.error.executeDistro": "डिस्ट्रो में कमांड निष्पादित नहीं किए जा सकते", + "desktop.wsl.error.installWsl": "WSL इंस्टॉल नहीं हो सका", + "desktop.wsl.error.installDistro": "डिस्ट्रो इंस्टॉल नहीं हो सका: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode इंस्टॉल नहीं हो सका", + "desktop.wsl.error.alreadyAdded": "{{distro}} पहले ही जोड़ा जा चुका है", + "desktop.wsl.error.opencodeMissing": "इस डिस्ट्रो में opencode इंस्टॉल नहीं है", + "desktop.wsl.error.opencodeCannotRun": "opencode इंस्टॉल है, लेकिन चल नहीं सका", + "desktop.wsl.error.opencodeNotInstalled": "{{distro}} में OpenCode इंस्टॉल नहीं है", + "desktop.wsl.error.updateVersion": + "OpenCode अपडेट पूरा हो गया, लेकिन {{distro}} अब भी {{installed}} रिपोर्ट कर रहा है; अपेक्षित संस्करण {{expected}} है", + "desktop.wsl.error.noVersion": "कोई संस्करण नहीं", + "desktop.wsl.error.serverExited": "WSL सर्वर स्टार्टअप के बाद बंद हो गया (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL सर्वर के सुचारु होने से पहले बंद हो गया (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "{{distro}} के Sidecar की स्वास्थ्य जाँच {{timeout}}ms के बाद समय-सीमा पार कर गई", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} ने {{timeout}}ms के बाद समय-सीमा पार कर दी", + "desktop.wsl.error.failedPort": "पोर्ट प्राप्त नहीं किया जा सका", + + "desktop.picker.error.notSelected": "पिकर में फ़ाइल नहीं चुनी गई", + "desktop.picker.error.sizeLimit": "चुने गए अटैचमेंट {{limit}} MB की सीमा से अधिक हैं", + "command.category.suggested": "सुझाए गए", "command.category.view": "दृश्य", "command.category.project": "प्रोजेक्ट", @@ -16,6 +116,7 @@ export const dict = { "command.category.permissions": "अनुमतियाँ", "command.category.workspace": "वर्कस्पेस", "command.category.settings": "सेटिंग्स", + "command.logs.export": "लॉग निर्यात करें", "theme.scheme.system": "सिस्टम", "theme.scheme.light": "हल्की", "theme.scheme.dark": "गहरी", @@ -119,6 +220,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Claude प्रो/मैक्स के साथ लॉगिन करें", "provider.connect.selectMethod": "{{provider}} के लिए लॉगिन विधि चुनें।", "provider.connect.method.apiKey": "API कुंजी", + "provider.connect.method.browser": "ब्राउज़र", + "provider.connect.method.headless": "हेडलेस मोड", "provider.connect.status.inProgress": "प्राधिकरण प्रगति पर है...", "provider.connect.status.waiting": "प्राधिकरण की प्रतीक्षा हो रही है...", "provider.connect.status.failed": "प्राधिकरण विफल: {{error}}", @@ -149,6 +252,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}} कनेक्ट हो गया", "provider.connect.toast.connected.description": "{{provider}} मॉडल अब उपयोग के लिए उपलब्ध हैं।", "provider.custom.title": "कस्टम प्रोवाइडर", + "provider.custom.unavailable": "इस सर्वर पर कस्टम प्रोवाइडर उपलब्ध नहीं हैं", "provider.custom.description.prefix": "एक OpenAI-संगत प्रोवाइडर कॉन्फ़िगर करें। देखें ", "provider.custom.description.link": "प्रोवाइडर कॉन्फ़िगरेशन दस्तावेज़", "provider.custom.description.suffix": ".", @@ -410,6 +514,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "डेस्कटॉप {{version}}", "wsl.onboarding.versionMismatch": "इंस्टॉल किया गया संस्करण डेस्कटॉप ऐप संस्करण से मेल नहीं खाता।", "wsl.onboarding.adding": "जोड़ा जा रहा है...", + "help.tabs.toast.ariaLabel": "टैब प्रस्तुत हैं। टैब की सहायता से अपना काम और सक्रिय सेशन व्यवस्थित करें", + "help.tabs.toast.dismiss": "टैब की जानकारी हटाएँ", + "help.tabs.title": "टैब प्रस्तुत हैं", + "help.tabs.description": "टैब की सहायता से अपना काम और सक्रिय सेशन व्यवस्थित करें", + "help.tabs.date": "14 जुलाई", + "help.tabs.introduction": "OpenCode Desktop अब टैब पर आधारित है।", + "help.tabs.sessions": + "किसी टैब में नया सेशन शुरू करें या अपने किसी भी प्रोजेक्ट से मौजूदा सेशन खोलें। कुछ नया शुरू करते समय नया टैब खोलें और काम पूरा होने पर उसे बंद कर दें।", + "help.tabs.organize": + "कुछ टैब खुले रखने से सक्रिय सेशन व्यवस्थित करना आसान होता है। यदि आप टैब खुले रखना चाहते हैं, तो उन्हें कोई याद रहने वाला नाम दें।", + "help.tabs.home": + "आपके सभी सेशन और प्रोजेक्ट नई होम स्क्रीन पर मिलेंगे। किसी सेशन को चुनने पर वह टैब में खुलता है।", + "help.tabs.persistence": "ऐप दोबारा खोलने पर भी आपके टैब खुले रहते हैं।", + "help.tabs.worktrees": + "नया डिज़ाइन अभी Git Worktrees का समर्थन नहीं करता है, यह सुविधा जल्द आ रही है। इसलिए यदि आप पिछले लेआउट का उपयोग जारी रखना चाहते हैं, तो सेटिंग्स में लेआउट के बीच स्विच कर सकते हैं। ध्यान रखें कि नया लेआउट कुछ सप्ताह में स्थायी हो जाएगा।", "server.row.noUsername": "कोई उपयोगकर्ता नाम नहीं", "dialog.project.edit.title": "प्रोजेक्ट संपादित करें", "dialog.project.edit.name": "नाम", @@ -704,6 +823,10 @@ export const dict = { "terminal.connectionLost.title": "कनेक्शन टूट गया", "terminal.connectionLost.abnormalClose": "WebSocket असामान्य रूप से बंद हुआ: {{code}}", "terminal.connectionLost.description": "टर्मिनल कनेक्शन बाधित हो गया था। ऐसा तब हो सकता है जब सर्वर पुनरारंभ हो।", + "terminal.connectTicket.csrfError": "ओरिजिन या CSRF जाँच के कारण PTY कनेक्ट टिकट अस्वीकार कर दिया गया। सर्वर का CORS कॉन्फ़िगरेशन जाँचें।", + "terminal.connectTicket.statusError": "PTY कनेक्ट टिकट विफल रहा (स्थिति: {{status}})", + "titlebar.update": "अपडेट करें", + "titlebar.updateVersion": "अपडेट करें {{version}}", "common.closeTab": "टैब बंद करें", "common.dismiss": "हटाएँ", "common.moreCountSuffix": " (+{{count}} और)", diff --git a/packages/app/src/i18n/id.ts b/packages/app/src/i18n/id.ts index fdc176c2c1d..c104d2ad4e4 100644 --- a/packages/app/src/i18n/id.ts +++ b/packages/app/src/i18n/id.ts @@ -1,4 +1,105 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Berkas", + "desktop.menu.edit": "Sunting", + "desktop.menu.view": "Tampilan", + "desktop.menu.go": "Navigasi", + "desktop.menu.window": "Jendela", + "desktop.menu.help": "Bantuan", + "desktop.menu.checkForUpdates": "Periksa pembaruan...", + "desktop.menu.settings": "Pengaturan", + "desktop.menu.reloadWebview": "Muat ulang tampilan web", + "desktop.menu.restart": "Mulai ulang", + "desktop.menu.exportLogs": "Ekspor log...", + "desktop.menu.newSession": "Sesi baru", + "desktop.menu.openProject": "Buka proyek...", + "desktop.menu.newWindow": "Jendela baru", + "desktop.menu.closeWindow": "Tutup jendela", + "desktop.menu.undo": "Urungkan", + "desktop.menu.redo": "Ulangi", + "desktop.menu.cut": "Potong", + "desktop.menu.copy": "Salin", + "desktop.menu.paste": "Tempel", + "desktop.menu.delete": "Hapus", + "desktop.menu.selectAll": "Pilih semua", + "desktop.menu.toggleSidebar": "Alihkan panel samping", + "desktop.menu.toggleTerminal": "Alihkan terminal", + "desktop.menu.toggleFileTree": "Alihkan pohon berkas", + "desktop.menu.reload": "Muat ulang", + "desktop.menu.toggleDeveloperTools": "Alihkan alat pengembang", + "desktop.menu.actualSize": "Ukuran sebenarnya", + "desktop.menu.zoomIn": "Perbesar", + "desktop.menu.zoomOut": "Perkecil", + "desktop.menu.toggleFullScreen": "Alihkan layar penuh", + "desktop.menu.back": "Kembali", + "desktop.menu.forward": "Maju", + "desktop.menu.previousSession": "Sesi sebelumnya", + "desktop.menu.nextSession": "Sesi berikutnya", + "desktop.menu.previousProject": "Proyek sebelumnya", + "desktop.menu.nextProject": "Proyek berikutnya", + "desktop.menu.minimize": "Minimalkan", + "desktop.menu.maximize": "Maksimalkan", + "desktop.menu.documentation": "Dokumentasi OpenCode", + "desktop.menu.supportForum": "Forum dukungan", + "desktop.menu.shareFeedback": "Bagikan umpan balik", + "desktop.menu.reportBug": "Laporkan bug", + "desktop.menu.ariaLabel": "Menu OpenCode", + + "desktop.updater.dialog.checkFailed.message": "Pemeriksaan pembaruan gagal.", + "desktop.updater.dialog.checkFailed.title": "Kesalahan pembaruan", + "desktop.updater.dialog.upToDate.message": "Versi Anda sudah terbaru.", + "desktop.updater.dialog.upToDate.title": "Tidak ada pembaruan", + "desktop.updater.dialog.ready.message": "Pembaruan {{version}} telah diunduh. Mulai ulang sekarang?", + "desktop.updater.dialog.ready.title": "Pembaruan siap", + "desktop.updater.dialog.restart": "Mulai ulang", + "desktop.updater.dialog.later": "Nanti", + + "desktop.recovery.action.relaunch": "Luncurkan ulang", + "desktop.recovery.action.exportLogs": "Ekspor log", + "desktop.recovery.action.keepWaiting": "Tetap tunggu", + "desktop.recovery.action.quit": "Keluar", + "desktop.recovery.loadFailed": "OpenCode gagal dimuat", + "desktop.recovery.terminated": "Jendela OpenCode berhenti secara tidak terduga", + "desktop.recovery.unresponsive": "OpenCode tidak merespons", + "desktop.recovery.unresponsive.detail": + "Anda dapat meluncurkan ulang aplikasi, membuka log, atau tetap menunggu.", + "desktop.recovery.loadFailed.detail": "Jendela: {{window}}\nURL: {{url}}\nKesalahan: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Jendela: {{window}}\nAlasan: {{reason}}\nKode: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Pilih folder", + "desktop.dialog.chooseFile": "Pilih berkas", + "desktop.dialog.saveFile": "Simpan berkas", + "desktop.dialog.files": "Berkas", + + "desktop.server.local": "Server lokal", + + "desktop.wsl.error.windowsOnly": "WSL hanya tersedia di Windows", + "desktop.wsl.error.unavailable": "WSL tidak tersedia", + "desktop.wsl.error.listInstalled": "Gagal menampilkan daftar distro WSL yang terinstal", + "desktop.wsl.error.listOnline": "Gagal menampilkan daftar distro WSL daring", + "desktop.wsl.error.executeDistro": "Tidak dapat menjalankan perintah di distro", + "desktop.wsl.error.installWsl": "Instalasi WSL gagal", + "desktop.wsl.error.installDistro": "Gagal menginstal distro: {{distro}}", + "desktop.wsl.error.installOpencode": "Instalasi OpenCode gagal", + "desktop.wsl.error.alreadyAdded": "{{distro}} sudah ditambahkan", + "desktop.wsl.error.opencodeMissing": "opencode belum terinstal di distro ini", + "desktop.wsl.error.opencodeCannotRun": "opencode telah terinstal, tetapi tidak dapat dijalankan", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode belum terinstal di {{distro}}", + "desktop.wsl.error.updateVersion": + "Pembaruan OpenCode selesai, tetapi {{distro}} masih melaporkan {{installed}}; seharusnya {{expected}}", + "desktop.wsl.error.noVersion": "tanpa versi", + "desktop.wsl.error.serverExited": "Server WSL berhenti setelah dimulai (kode={{code}} sinyal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "Server WSL berhenti sebelum siap (kode={{code}} sinyal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Pemeriksaan kesehatan sidecar untuk {{distro}} kehabisan waktu setelah {{timeout}}ms", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} kehabisan waktu setelah {{timeout}}ms", + "desktop.wsl.error.failedPort": "Gagal mendapatkan porta", + + "desktop.picker.error.notSelected": "Berkas tidak dipilih melalui pemilih", + "desktop.picker.error.sizeLimit": "Lampiran yang dipilih melebihi batas {{limit}} MB", + "command.category.suggested": "Disarankan", "command.category.view": "Tampilan", "command.category.project": "Proyek", @@ -16,6 +117,7 @@ export const dict = { "command.category.permissions": "Izin", "command.category.workspace": "Ruang kerja", "command.category.settings": "Pengaturan", + "command.logs.export": "Ekspor log", "theme.scheme.system": "Sistem", "theme.scheme.light": "Terang", @@ -131,6 +233,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Masuk dengan Claude Pro/Max", "provider.connect.selectMethod": "Pilih metode masuk untuk {{provider}}.", "provider.connect.method.apiKey": "Kunci API", + "provider.connect.method.browser": "Peramban", + "provider.connect.method.headless": "Headless", "provider.connect.status.inProgress": "Otorisasi sedang berlangsung...", "provider.connect.status.waiting": "Menunggu otorisasi...", "provider.connect.status.failed": "Otorisasi gagal: {{error}}", @@ -163,6 +267,7 @@ export const dict = { "provider.connect.toast.connected.description": "Model {{provider}} sekarang tersedia untuk digunakan.", "provider.custom.title": "Penyedia kustom", + "provider.custom.unavailable": "Penyedia kustom tidak tersedia di server ini", "provider.custom.description.prefix": "Konfigurasikan penyedia yang kompatibel dengan OpenAI. Lihat ", "provider.custom.description.link": "dokumen konfigurasi penyedia", "provider.custom.description.suffix": ".", @@ -438,6 +543,22 @@ export const dict = { "wsl.onboarding.desktopVersion": "desktop {{version}}", "wsl.onboarding.versionMismatch": "Versi terinstal tidak cocok dengan versi aplikasi desktop.", "wsl.onboarding.adding": "Menambahkan...", + + "help.tabs.toast.ariaLabel": "Memperkenalkan Tab. Atur pekerjaan dan sesi aktif Anda dengan tab", + "help.tabs.toast.dismiss": "Tutup informasi tentang Tab", + "help.tabs.title": "Memperkenalkan Tab", + "help.tabs.description": "Atur pekerjaan dan sesi aktif Anda dengan tab", + "help.tabs.date": "14 Juli", + "help.tabs.introduction": "OpenCode Desktop kini dirancang dengan tab sebagai pusatnya.", + "help.tabs.sessions": + "Mulai sesi baru di tab, atau buka sesi yang sudah ada dari proyek Anda. Buka tab baru saat Anda memulai sesuatu yang baru, lalu tutup setelah selesai.", + "help.tabs.organize": + "Membiarkan beberapa tab tetap terbuka memudahkan Anda mengatur sesi aktif. Ganti nama tab agar mudah diingat jika Anda berencana mempertahankannya.", + "help.tabs.home": + "Semua sesi dan proyek Anda tersedia di layar Beranda baru. Memilih sesi akan membukanya di tab.", + "help.tabs.persistence": "Saat Anda membuka kembali aplikasi, tab Anda tetap terbuka.", + "help.tabs.worktrees": + "Desain baru belum mendukung Git Worktrees, tetapi dukungan tersebut akan segera hadir. Jika Anda ingin tetap menggunakan tata letak sebelumnya, Anda dapat beralih antartata letak di Pengaturan. Perlu diingat bahwa tata letak baru akan menjadi permanen dalam beberapa minggu.", "server.row.noUsername": "tanpa nama pengguna", "dialog.project.edit.title": "Sunting proyek", @@ -765,6 +886,11 @@ export const dict = { "terminal.connectionLost.title": "Koneksi Terputus", "terminal.connectionLost.abnormalClose": "WebSocket ditutup secara tidak normal: {{code}}", "terminal.connectionLost.description": "Koneksi terminal terputus. Ini bisa terjadi saat server dimulai ulang.", + "terminal.connectTicket.csrfError": + "Tiket koneksi PTY ditolak oleh pemeriksaan asal atau CSRF. Periksa konfigurasi CORS server.", + "terminal.connectTicket.statusError": "Tiket koneksi PTY gagal dengan status {{status}}", + "titlebar.update": "Perbarui", + "titlebar.updateVersion": "Perbarui {{version}}", "common.closeTab": "Tutup tab", "common.dismiss": "Tutup", diff --git a/packages/app/src/i18n/it.ts b/packages/app/src/i18n/it.ts index e044f664bef..56397ce6294 100644 --- a/packages/app/src/i18n/it.ts +++ b/packages/app/src/i18n/it.ts @@ -16,6 +16,7 @@ export const dict = { "command.category.permissions": "Autorizzazioni", "command.category.workspace": "Area di lavoro", "command.category.settings": "Impostazioni", + "command.logs.export": "Esporta log", "theme.scheme.system": "Sistema", "theme.scheme.light": "Chiaro", "theme.scheme.dark": "Scuro", @@ -119,6 +120,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Accedi con Claude Pro/Max", "provider.connect.selectMethod": "Seleziona il metodo di accesso per {{provider}}.", "provider.connect.method.apiKey": "Chiave API", + "provider.connect.method.browser": "Browser", + "provider.connect.method.headless": "Headless", "provider.connect.status.inProgress": "Autorizzazione in corso...", "provider.connect.status.waiting": "In attesa di autorizzazione...", "provider.connect.status.failed": "Autorizzazione fallita: {{error}}", @@ -186,6 +189,7 @@ export const dict = { "provider.custom.error.baseURL.format": "Deve iniziare con http:// o https://", "provider.custom.error.required": "Necessario", "provider.custom.error.duplicate": "Duplicato", + "provider.custom.unavailable": "I provider personalizzati non sono disponibili su questo server", "provider.disconnect.toast.disconnected.title": "{{provider}} disconnesso", "provider.disconnect.toast.disconnected.description": "I modelli {{provider}} non sono più disponibili.", "model.tag.free": "Gratuito", @@ -411,6 +415,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "desktop {{version}}", "wsl.onboarding.versionMismatch": "La versione installata non corrisponde alla versione dell'app desktop.", "wsl.onboarding.adding": "Aggiunta...", + "help.tabs.toast.ariaLabel": "Novità: le schede. Organizza il lavoro e le sessioni attive con le schede", + "help.tabs.toast.dismiss": "Ignora le informazioni sulle schede", + "help.tabs.title": "Novità: le schede", + "help.tabs.description": "Organizza il lavoro e le sessioni attive con le schede", + "help.tabs.date": "14 luglio", + "help.tabs.introduction": "OpenCode Desktop ora è incentrato sulle schede.", + "help.tabs.sessions": + "Avvia una nuova sessione in una scheda oppure apri una sessione esistente da uno dei tuoi progetti. Apri una nuova scheda quando inizi qualcosa di nuovo e chiudila quando hai finito.", + "help.tabs.organize": + "Tenere aperte alcune schede semplifica l'organizzazione delle sessioni attive. Se prevedi di conservarle, rinominale con un nome facile da ricordare.", + "help.tabs.home": + "Troverai tutte le sessioni e i progetti nella nuova schermata Home. Quando selezioni una sessione, questa si apre in una scheda.", + "help.tabs.persistence": "Quando riapri l'app, le schede sono ancora aperte.", + "help.tabs.worktrees": + "Il nuovo design non supporta ancora Git Worktrees, ma il supporto arriverà presto. Se preferisci continuare a utilizzare la disposizione precedente, puoi passare da una disposizione all'altra nelle Impostazioni. Tieni presente che la nuova disposizione diventerà permanente tra alcune settimane.", "server.row.noUsername": "nessun nome utente", "dialog.project.edit.title": "Modifica progetto", "dialog.project.edit.name": "Nome", @@ -712,6 +731,11 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket chiuso in modo anomalo: {{code}}", "terminal.connectionLost.description": "La connessione del terminale è stata interrotta. Ciò può verificarsi al riavvio del server.", + "terminal.connectTicket.csrfError": + "Il ticket di connessione PTY è stato rifiutato dai controlli sull'origine o CSRF. Verifica la configurazione CORS del server.", + "terminal.connectTicket.statusError": "Il ticket di connessione PTY non è riuscito con lo stato {{status}}", + "titlebar.update": "Aggiorna", + "titlebar.updateVersion": "Aggiorna {{version}}", "common.closeTab": "Chiudi scheda", "common.dismiss": "Ignora", "common.moreCountSuffix": " (+ altri {{count}})", @@ -1035,4 +1059,97 @@ export const dict = { "workspace.reset.archived.one": "1 sessione verrà archiviata.", "workspace.reset.archived.many": "Verranno archiviate {{count}} sessioni.", "workspace.reset.note": "L'area di lavoro verrà reimpostata in modo che corrisponda al branch predefinito.", + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "File", + "desktop.menu.edit": "Modifica", + "desktop.menu.view": "Vista", + "desktop.menu.go": "Vai", + "desktop.menu.window": "Finestra", + "desktop.menu.help": "Aiuto", + "desktop.menu.checkForUpdates": "Verifica aggiornamenti...", + "desktop.menu.settings": "Impostazioni", + "desktop.menu.reloadWebview": "Ricarica Webview", + "desktop.menu.restart": "Riavvia", + "desktop.menu.exportLogs": "Esporta log...", + "desktop.menu.newSession": "Nuova sessione", + "desktop.menu.openProject": "Apri progetto...", + "desktop.menu.newWindow": "Nuova finestra", + "desktop.menu.closeWindow": "Chiudi finestra", + "desktop.menu.undo": "Annulla", + "desktop.menu.redo": "Ripristina", + "desktop.menu.cut": "Taglia", + "desktop.menu.copy": "Copia", + "desktop.menu.paste": "Incolla", + "desktop.menu.delete": "Elimina", + "desktop.menu.selectAll": "Seleziona tutto", + "desktop.menu.toggleSidebar": "Mostra o nascondi la barra laterale", + "desktop.menu.toggleTerminal": "Mostra o nascondi il terminale", + "desktop.menu.toggleFileTree": "Mostra o nascondi l'albero dei file", + "desktop.menu.reload": "Ricarica", + "desktop.menu.toggleDeveloperTools": "Mostra o nascondi gli strumenti per sviluppatori", + "desktop.menu.actualSize": "Dimensioni effettive", + "desktop.menu.zoomIn": "Ingrandisci", + "desktop.menu.zoomOut": "Riduci", + "desktop.menu.toggleFullScreen": "Attiva o disattiva lo schermo intero", + "desktop.menu.back": "Indietro", + "desktop.menu.forward": "Avanti", + "desktop.menu.previousSession": "Sessione precedente", + "desktop.menu.nextSession": "Sessione successiva", + "desktop.menu.previousProject": "Progetto precedente", + "desktop.menu.nextProject": "Progetto successivo", + "desktop.menu.minimize": "Riduci a icona", + "desktop.menu.maximize": "Ingrandisci", + "desktop.menu.documentation": "Documentazione di OpenCode", + "desktop.menu.supportForum": "Forum di supporto", + "desktop.menu.shareFeedback": "Invia feedback", + "desktop.menu.reportBug": "Segnala un bug", + "desktop.menu.ariaLabel": "Menu OpenCode", + "desktop.updater.dialog.checkFailed.message": "Impossibile verificare la disponibilità di aggiornamenti.", + "desktop.updater.dialog.checkFailed.title": "Errore di aggiornamento", + "desktop.updater.dialog.upToDate.message": "La versione è aggiornata.", + "desktop.updater.dialog.upToDate.title": "Nessun aggiornamento", + "desktop.updater.dialog.ready.message": "L'aggiornamento {{version}} è stato scaricato. Riavviare ora?", + "desktop.updater.dialog.ready.title": "Aggiornamento pronto", + "desktop.updater.dialog.restart": "Riavvia", + "desktop.updater.dialog.later": "Più tardi", + "desktop.recovery.action.relaunch": "Riapri", + "desktop.recovery.action.exportLogs": "Esporta log", + "desktop.recovery.action.keepWaiting": "Continua ad attendere", + "desktop.recovery.action.quit": "Esci", + "desktop.recovery.loadFailed": "Impossibile caricare OpenCode", + "desktop.recovery.terminated": "La finestra di OpenCode si è chiusa in modo imprevisto", + "desktop.recovery.unresponsive": "OpenCode non risponde", + "desktop.recovery.unresponsive.detail": "Puoi riaprire l'app, aprire i log o continuare ad attendere.", + "desktop.recovery.loadFailed.detail": "Finestra: {{window}}\nURL: {{url}}\nErrore: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Finestra: {{window}}\nMotivo: {{reason}}\nCodice: {{code}}", + "desktop.recovery.unknown": "", + "desktop.dialog.chooseFolder": "Scegli una cartella", + "desktop.dialog.chooseFile": "Scegli un file", + "desktop.dialog.saveFile": "Salva file", + "desktop.dialog.files": "File", + "desktop.server.local": "Server locale", + "desktop.wsl.error.windowsOnly": "WSL è disponibile solo in Windows", + "desktop.wsl.error.unavailable": "WSL non è disponibile", + "desktop.wsl.error.listInstalled": "Impossibile elencare le distribuzioni WSL installate", + "desktop.wsl.error.listOnline": "Impossibile elencare le distribuzioni WSL disponibili online", + "desktop.wsl.error.executeDistro": "Impossibile eseguire comandi nella distribuzione", + "desktop.wsl.error.installWsl": "Installazione di WSL non riuscita", + "desktop.wsl.error.installDistro": "Impossibile installare la distribuzione: {{distro}}", + "desktop.wsl.error.installOpencode": "Installazione di OpenCode non riuscita", + "desktop.wsl.error.alreadyAdded": "{{distro}} è già stata aggiunta", + "desktop.wsl.error.opencodeMissing": "opencode non è installato in questa distribuzione", + "desktop.wsl.error.opencodeCannotRun": "opencode è installato, ma non è stato possibile eseguirlo", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode non è installato in {{distro}}", + "desktop.wsl.error.updateVersion": + "L'aggiornamento di OpenCode è terminato, ma {{distro}} segnala ancora la versione {{installed}}; era prevista la versione {{expected}}", + "desktop.wsl.error.noVersion": "nessuna versione", + "desktop.wsl.error.serverExited": "Il server WSL si è chiuso dopo l'avvio (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "Il server WSL si è chiuso prima di diventare operativo (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Il controllo dello stato del sidecar per {{distro}} ha superato il tempo limite di {{timeout}}ms", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} ha superato il tempo limite di {{timeout}}ms", + "desktop.wsl.error.failedPort": "Impossibile ottenere la porta", + "desktop.picker.error.notSelected": "Il file non è stato selezionato nella finestra di selezione", + "desktop.picker.error.sizeLimit": "Gli allegati selezionati superano il limite di {{limit}} MB", } diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 35ccb9e7aaa..4c4f8a18154 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -1,4 +1,103 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "ファイル", + "desktop.menu.edit": "編集", + "desktop.menu.view": "表示", + "desktop.menu.go": "移動", + "desktop.menu.window": "ウインドウ", + "desktop.menu.help": "ヘルプ", + "desktop.menu.checkForUpdates": "アップデートを確認...", + "desktop.menu.settings": "設定", + "desktop.menu.reloadWebview": "Webビューを再読み込み", + "desktop.menu.restart": "再起動", + "desktop.menu.exportLogs": "ログをエクスポート...", + "desktop.menu.newSession": "新しいセッション", + "desktop.menu.openProject": "プロジェクトを開く...", + "desktop.menu.newWindow": "新しいウインドウ", + "desktop.menu.closeWindow": "ウインドウを閉じる", + "desktop.menu.undo": "取り消す", + "desktop.menu.redo": "やり直す", + "desktop.menu.cut": "切り取り", + "desktop.menu.copy": "コピー", + "desktop.menu.paste": "ペースト", + "desktop.menu.delete": "削除", + "desktop.menu.selectAll": "すべてを選択", + "desktop.menu.toggleSidebar": "サイドバーの表示を切り替える", + "desktop.menu.toggleTerminal": "ターミナルの表示を切り替える", + "desktop.menu.toggleFileTree": "ファイルツリーの表示を切り替える", + "desktop.menu.reload": "再読み込み", + "desktop.menu.toggleDeveloperTools": "開発者ツールの表示を切り替える", + "desktop.menu.actualSize": "実際のサイズ", + "desktop.menu.zoomIn": "拡大", + "desktop.menu.zoomOut": "縮小", + "desktop.menu.toggleFullScreen": "フルスクリーン表示を切り替える", + "desktop.menu.back": "戻る", + "desktop.menu.forward": "進む", + "desktop.menu.previousSession": "前のセッション", + "desktop.menu.nextSession": "次のセッション", + "desktop.menu.previousProject": "前のプロジェクト", + "desktop.menu.nextProject": "次のプロジェクト", + "desktop.menu.minimize": "最小化", + "desktop.menu.maximize": "最大化", + "desktop.menu.documentation": "OpenCodeドキュメント", + "desktop.menu.supportForum": "サポートフォーラム", + "desktop.menu.shareFeedback": "フィードバックを送信", + "desktop.menu.reportBug": "バグを報告", + "desktop.menu.ariaLabel": "OpenCodeメニュー", + + "desktop.updater.dialog.checkFailed.message": "アップデートを確認できませんでした。", + "desktop.updater.dialog.checkFailed.title": "アップデートエラー", + "desktop.updater.dialog.upToDate.message": "最新の状態です。", + "desktop.updater.dialog.upToDate.title": "アップデートはありません", + "desktop.updater.dialog.ready.message": "アップデート{{version}}をダウンロードしました。今すぐ再起動しますか?", + "desktop.updater.dialog.ready.title": "アップデートの準備ができました", + "desktop.updater.dialog.restart": "再起動", + "desktop.updater.dialog.later": "後で", + + "desktop.recovery.action.relaunch": "再起動", + "desktop.recovery.action.exportLogs": "ログをエクスポート", + "desktop.recovery.action.keepWaiting": "待機を続ける", + "desktop.recovery.action.quit": "終了", + "desktop.recovery.loadFailed": "OpenCodeを読み込めませんでした", + "desktop.recovery.terminated": "OpenCodeのウインドウが予期せず終了しました", + "desktop.recovery.unresponsive": "OpenCodeが応答していません", + "desktop.recovery.unresponsive.detail": "アプリを再起動するか、ログを開くか、待機を続けることができます。", + "desktop.recovery.loadFailed.detail": "ウインドウ: {{window}}\nURL: {{url}}\nエラー: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "ウインドウ: {{window}}\n理由: {{reason}}\nコード: {{code}}", + "desktop.recovery.unknown": "<不明>", + + "desktop.dialog.chooseFolder": "フォルダを選択", + "desktop.dialog.chooseFile": "ファイルを選択", + "desktop.dialog.saveFile": "ファイルを保存", + "desktop.dialog.files": "ファイル", + "desktop.server.local": "ローカルサーバー", + + "desktop.wsl.error.windowsOnly": "WSLはWindowsでのみ利用できます", + "desktop.wsl.error.unavailable": "WSLを利用できません", + "desktop.wsl.error.listInstalled": "インストール済みのWSLディストリビューションの一覧を取得できませんでした", + "desktop.wsl.error.listOnline": "オンラインのWSLディストリビューションの一覧を取得できませんでした", + "desktop.wsl.error.executeDistro": "ディストリビューションでコマンドを実行できません", + "desktop.wsl.error.installWsl": "WSLのインストールに失敗しました", + "desktop.wsl.error.installDistro": "ディストリビューションのインストールに失敗しました: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCodeのインストールに失敗しました", + "desktop.wsl.error.alreadyAdded": "{{distro}}はすでに追加されています", + "desktop.wsl.error.opencodeMissing": "このディストリビューションにはopencodeがインストールされていません", + "desktop.wsl.error.opencodeCannotRun": "opencodeはインストールされていますが、実行できませんでした", + "desktop.wsl.error.opencodeNotInstalled": "{{distro}}にはOpenCodeがインストールされていません", + "desktop.wsl.error.updateVersion": + "OpenCodeのアップデートは完了しましたが、{{distro}}から報告されたバージョンはまだ{{installed}}です。想定されるバージョンは{{expected}}です", + "desktop.wsl.error.noVersion": "バージョンなし", + "desktop.wsl.error.serverExited": "WSLサーバーが起動後に終了しました (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSLサーバーが正常に稼働する前に終了しました (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "{{distro}}のSidecarのヘルスチェックが{{timeout}}ms後にタイムアウトしました", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}}が{{timeout}}ms後にタイムアウトしました", + "desktop.wsl.error.failedPort": "ポートを取得できませんでした", + + "desktop.picker.error.notSelected": "ピッカーでファイルが選択されませんでした", + "desktop.picker.error.sizeLimit": "選択した添付ファイルが上限の{{limit}} MBを超えています", + "command.category.suggested": "おすすめ", "command.category.view": "表示", "command.category.project": "プロジェクト", @@ -16,6 +115,7 @@ export const dict = { "command.category.permissions": "権限", "command.category.workspace": "ワークスペース", "command.category.settings": "設定", + "command.logs.export": "ログをエクスポート", "theme.scheme.system": "システム", "theme.scheme.light": "ライト", "theme.scheme.dark": "ダーク", @@ -119,6 +219,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Claude Pro/Maxでログイン", "provider.connect.selectMethod": "{{provider}}のログイン方法を選択してください。", "provider.connect.method.apiKey": "APIキー", + "provider.connect.method.browser": "ブラウザ", + "provider.connect.method.headless": "ヘッドレス モード", "provider.connect.status.inProgress": "認証中...", "provider.connect.status.waiting": "認証を待機中...", "provider.connect.status.failed": "認証に失敗しました: {{error}}", @@ -149,6 +251,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}}が接続されました", "provider.connect.toast.connected.description": "{{provider}}モデルが使用可能になりました。", "provider.custom.title": "カスタムプロバイダー", + "provider.custom.unavailable": "このサーバーではカスタムプロバイダーを利用できません", "provider.custom.description.prefix": "OpenAI互換のプロバイダーを設定します。詳細は", "provider.custom.description.link": "プロバイダー設定ドキュメント", "provider.custom.description.suffix": "をご覧ください。", @@ -403,6 +506,22 @@ export const dict = { "wsl.onboarding.desktopVersion": "デスクトップ {{version}}", "wsl.onboarding.versionMismatch": "インストールされているバージョンがデスクトップアプリのバージョンと一致しません。", "wsl.onboarding.adding": "追加中...", + + "help.tabs.toast.ariaLabel": "タブのご紹介。タブを使って作業と進行中のセッションを整理できます", + "help.tabs.toast.dismiss": "タブに関する案内を閉じる", + "help.tabs.title": "タブのご紹介", + "help.tabs.description": "タブを使って作業と進行中のセッションを整理できます", + "help.tabs.date": "7月14日", + "help.tabs.introduction": "OpenCode Desktopでタブを中心とした操作ができるようになりました。", + "help.tabs.sessions": + "新しいセッションをタブで開始したり、プロジェクトから既存のセッションを開いたりできます。新しい作業を始めるときは新しいタブを開き、作業が終わったら閉じます。", + "help.tabs.organize": + "複数のタブを開いておくと、進行中のセッションを整理しやすくなります。タブを残しておく場合は、覚えやすい名前に変更できます。", + "help.tabs.home": + "新しいホーム画面には、すべてのセッションとプロジェクトが表示されます。セッションを選択するとタブで開きます。", + "help.tabs.persistence": "アプリを再度開いたときも、タブは開いたままです。", + "help.tabs.worktrees": + "新しいデザインはまだGitワークツリーに対応していませんが、近日中に対応する予定です。以前のレイアウトを引き続き使用する場合は、設定でレイアウトを切り替えることができます。新しいレイアウトは数週間後に標準となる予定です。", "dialog.project.edit.title": "プロジェクトを編集", "dialog.project.edit.name": "名前", "dialog.project.edit.icon": "アイコン", @@ -1021,4 +1140,10 @@ export const dict = { "error.childStore.persistedProjectIconCreateFailed": "永続プロジェクトアイコンの作成に失敗しました", "error.childStore.storeCreateFailed": "ストアの作成に失敗しました", "terminal.connectionLost.abnormalClose": "WebSocket が異常終了しました: {{code}}", + "terminal.connectTicket.csrfError": + "PTY接続チケットは、オリジンまたはCSRFチェックによって拒否されました。サーバーのCORS設定を確認してください。", + "terminal.connectTicket.statusError": "PTY接続チケットがステータス {{status}} で失敗しました", + + "titlebar.update": "アップデート", + "titlebar.updateVersion": "アップデート {{version}}", } diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 175b31698b7..7f950ede61b 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -115,6 +115,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Claude Pro/Max로 로그인", "provider.connect.selectMethod": "{{provider}} 로그인 방법 선택", "provider.connect.method.apiKey": "API 키", + "provider.connect.method.browser": "브라우저", + "provider.connect.method.headless": "헤드리스 모드", "provider.connect.status.inProgress": "인증 진행 중...", "provider.connect.status.waiting": "인증 대기 중...", "provider.connect.status.failed": "인증 실패: {{error}}", @@ -145,6 +147,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}} 연결됨", "provider.connect.toast.connected.description": "이제 {{provider}} 모델을 사용할 수 있습니다.", "provider.custom.title": "사용자 지정 공급자", + "provider.custom.unavailable": "이 서버에서는 사용자 지정 공급자를 사용할 수 없습니다", "provider.custom.description.prefix": "OpenAI 호환 공급자를 구성합니다. ", "provider.custom.description.link": "공급자 구성 문서", "provider.custom.description.suffix": "를 참조하세요.", @@ -860,6 +863,12 @@ export const dict = { "error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패", "error.childStore.storeCreateFailed": "저장소 생성 실패", "terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}", + "terminal.connectTicket.csrfError": + "PTY 연결 티켓이 오리진 또는 CSRF 검사에서 거부되었습니다. 서버의 CORS 구성을 확인하세요.", + "terminal.connectTicket.statusError": "PTY 연결 티켓 실패(상태: {{status}})", + + "titlebar.update": "업데이트", + "titlebar.updateVersion": "업데이트 {{version}}", "command.project.previous": "이전 프로젝트", "command.project.next": "다음 프로젝트", @@ -1019,4 +1028,119 @@ export const dict = { "settings.general.row.pinchZoom.description": "트랙패드 핀치 및 Ctrl-스크롤 제스처로 확대/축소 허용", "settings.updates.action.downloading": "다운로드 중...", "settings.updates.action.installing": "설치 중...", + + "command.logs.export": "로그 내보내기", + + "help.tabs.toast.ariaLabel": "탭을 소개합니다. 탭으로 작업과 활성 세션을 정리하세요", + "help.tabs.toast.dismiss": "탭 정보 닫기", + "help.tabs.title": "탭 소개", + "help.tabs.description": "탭으로 작업과 활성 세션을 정리하세요", + "help.tabs.date": "7월 14일", + "help.tabs.introduction": "이제 OpenCode Desktop은 탭을 중심으로 구성됩니다.", + "help.tabs.sessions": + "탭에서 새 세션을 시작하거나 프로젝트에서 기존 세션을 여세요. 새로운 작업을 시작할 때 새 탭을 열고, 작업을 마치면 닫으세요.", + "help.tabs.organize": + "탭을 몇 개 열어 두면 활성 세션을 더 쉽게 정리할 수 있습니다. 탭을 계속 열어 둘 예정이라면 기억하기 쉬운 이름으로 바꾸세요.", + "help.tabs.home": "새 홈 화면에서 모든 세션과 프로젝트를 확인할 수 있습니다. 세션을 선택하면 탭에서 열립니다.", + "help.tabs.persistence": "앱을 다시 열어도 탭은 그대로 열려 있습니다.", + "help.tabs.worktrees": + "새 디자인은 아직 Git Worktrees를 지원하지 않지만 곧 지원할 예정입니다. 이전 레이아웃을 계속 사용하려면 설정에서 레이아웃을 전환할 수 있습니다. 단, 몇 주 후에는 새 레이아웃이 영구 적용됩니다.", + + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "파일", + "desktop.menu.edit": "편집", + "desktop.menu.view": "보기", + "desktop.menu.go": "이동", + "desktop.menu.window": "윈도우", + "desktop.menu.help": "도움말", + "desktop.menu.checkForUpdates": "업데이트 확인...", + "desktop.menu.settings": "설정", + "desktop.menu.reloadWebview": "WebView 새로 고침", + "desktop.menu.restart": "다시 시작", + "desktop.menu.exportLogs": "로그 내보내기...", + "desktop.menu.newSession": "새 세션", + "desktop.menu.openProject": "프로젝트 열기...", + "desktop.menu.newWindow": "새 윈도우", + "desktop.menu.closeWindow": "윈도우 닫기", + "desktop.menu.undo": "실행 취소", + "desktop.menu.redo": "다시 실행", + "desktop.menu.cut": "잘라내기", + "desktop.menu.copy": "복사", + "desktop.menu.paste": "붙여넣기", + "desktop.menu.delete": "삭제", + "desktop.menu.selectAll": "모두 선택", + "desktop.menu.toggleSidebar": "사이드바 표시/숨기기", + "desktop.menu.toggleTerminal": "터미널 표시/숨기기", + "desktop.menu.toggleFileTree": "파일 트리 표시/숨기기", + "desktop.menu.reload": "새로 고침", + "desktop.menu.toggleDeveloperTools": "개발자 도구 표시/숨기기", + "desktop.menu.actualSize": "실제 크기", + "desktop.menu.zoomIn": "확대", + "desktop.menu.zoomOut": "축소", + "desktop.menu.toggleFullScreen": "전체 화면 전환", + "desktop.menu.back": "뒤로", + "desktop.menu.forward": "앞으로", + "desktop.menu.previousSession": "이전 세션", + "desktop.menu.nextSession": "다음 세션", + "desktop.menu.previousProject": "이전 프로젝트", + "desktop.menu.nextProject": "다음 프로젝트", + "desktop.menu.minimize": "최소화", + "desktop.menu.maximize": "최대화", + "desktop.menu.documentation": "OpenCode 문서", + "desktop.menu.supportForum": "지원 포럼", + "desktop.menu.shareFeedback": "피드백 보내기", + "desktop.menu.reportBug": "버그 신고", + "desktop.menu.ariaLabel": "OpenCode 메뉴", + + "desktop.updater.dialog.checkFailed.message": "업데이트를 확인하지 못했습니다.", + "desktop.updater.dialog.checkFailed.title": "업데이트 오류", + "desktop.updater.dialog.upToDate.message": "최신 버전을 사용 중입니다.", + "desktop.updater.dialog.upToDate.title": "업데이트 없음", + "desktop.updater.dialog.ready.message": "업데이트 {{version}} 다운로드가 완료되었습니다. 지금 다시 시작할까요?", + "desktop.updater.dialog.ready.title": "업데이트 준비 완료", + "desktop.updater.dialog.restart": "다시 시작", + "desktop.updater.dialog.later": "나중에", + + "desktop.recovery.action.relaunch": "다시 실행", + "desktop.recovery.action.exportLogs": "로그 내보내기", + "desktop.recovery.action.keepWaiting": "계속 기다리기", + "desktop.recovery.action.quit": "종료", + "desktop.recovery.loadFailed": "OpenCode를 불러오지 못했습니다", + "desktop.recovery.terminated": "OpenCode 윈도우가 예기치 않게 종료되었습니다", + "desktop.recovery.unresponsive": "OpenCode가 응답하지 않습니다", + "desktop.recovery.unresponsive.detail": "앱을 다시 실행하거나 로그를 열거나 계속 기다릴 수 있습니다.", + "desktop.recovery.loadFailed.detail": "윈도우: {{window}}\nURL: {{url}}\n오류: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "윈도우: {{window}}\n원인: {{reason}}\n코드: {{code}}", + "desktop.recovery.unknown": "<알 수 없음>", + + "desktop.dialog.chooseFolder": "폴더 선택", + "desktop.dialog.chooseFile": "파일 선택", + "desktop.dialog.saveFile": "파일 저장", + "desktop.dialog.files": "파일", + "desktop.server.local": "로컬 서버", + + "desktop.wsl.error.windowsOnly": "WSL은 Windows에서만 사용할 수 있습니다", + "desktop.wsl.error.unavailable": "WSL을 사용할 수 없습니다", + "desktop.wsl.error.listInstalled": "설치된 WSL 배포판 목록을 가져오지 못했습니다", + "desktop.wsl.error.listOnline": "온라인 WSL 배포판 목록을 가져오지 못했습니다", + "desktop.wsl.error.executeDistro": "배포판에서 명령을 실행할 수 없습니다", + "desktop.wsl.error.installWsl": "WSL 설치 실패", + "desktop.wsl.error.installDistro": "배포판 설치 실패: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode 설치 실패", + "desktop.wsl.error.alreadyAdded": "{{distro}} 배포판은 이미 추가되었습니다", + "desktop.wsl.error.opencodeMissing": "이 배포판에 opencode가 설치되어 있지 않습니다", + "desktop.wsl.error.opencodeCannotRun": "opencode가 설치되어 있지만 실행할 수 없습니다", + "desktop.wsl.error.opencodeNotInstalled": "{{distro}}에 OpenCode가 설치되어 있지 않습니다", + "desktop.wsl.error.updateVersion": + "OpenCode 업데이트가 완료되었지만 {{distro}}에서 여전히 {{installed}} 버전으로 표시됩니다. 예상 버전: {{expected}}", + "desktop.wsl.error.noVersion": "버전 없음", + "desktop.wsl.error.serverExited": "WSL 서버가 시작 후 종료되었습니다(코드={{code}} 신호={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL 서버가 정상 상태가 되기 전에 종료되었습니다(코드={{code}} 신호={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": "{{distro}} 사이드카의 상태 확인이 {{timeout}}ms 후 시간 초과되었습니다", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} 명령이 {{timeout}}ms 후 시간 초과되었습니다", + "desktop.wsl.error.failedPort": "포트를 가져오지 못했습니다", + + "desktop.picker.error.notSelected": "선택기에서 파일을 선택하지 않았습니다", + "desktop.picker.error.sizeLimit": "선택한 첨부 파일이 {{limit}} MB 제한을 초과합니다", } diff --git a/packages/app/src/i18n/nl.ts b/packages/app/src/i18n/nl.ts index bf141a69f54..7b98ad4f81d 100644 --- a/packages/app/src/i18n/nl.ts +++ b/packages/app/src/i18n/nl.ts @@ -1,4 +1,98 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Bestand", + "desktop.menu.edit": "Bewerken", + "desktop.menu.view": "Weergave", + "desktop.menu.go": "Ga", + "desktop.menu.window": "Venster", + "desktop.menu.help": "Help", + "desktop.menu.checkForUpdates": "Controleren op updates...", + "desktop.menu.settings": "Instellingen", + "desktop.menu.reloadWebview": "Webweergave opnieuw laden", + "desktop.menu.restart": "Opnieuw opstarten", + "desktop.menu.exportLogs": "Logboeken exporteren...", + "desktop.menu.newSession": "Nieuwe sessie", + "desktop.menu.openProject": "Project openen...", + "desktop.menu.newWindow": "Nieuw venster", + "desktop.menu.closeWindow": "Venster sluiten", + "desktop.menu.undo": "Ongedaan maken", + "desktop.menu.redo": "Opnieuw uitvoeren", + "desktop.menu.cut": "Knippen", + "desktop.menu.copy": "Kopiëren", + "desktop.menu.paste": "Plakken", + "desktop.menu.delete": "Verwijderen", + "desktop.menu.selectAll": "Alles selecteren", + "desktop.menu.toggleSidebar": "Zijbalk tonen of verbergen", + "desktop.menu.toggleTerminal": "Terminal tonen of verbergen", + "desktop.menu.toggleFileTree": "Bestandsstructuur tonen of verbergen", + "desktop.menu.reload": "Opnieuw laden", + "desktop.menu.toggleDeveloperTools": "Ontwikkelaarstools tonen of verbergen", + "desktop.menu.actualSize": "Werkelijke grootte", + "desktop.menu.zoomIn": "Inzoomen", + "desktop.menu.zoomOut": "Uitzoomen", + "desktop.menu.toggleFullScreen": "Schermvullende weergave in- of uitschakelen", + "desktop.menu.back": "Terug", + "desktop.menu.forward": "Vooruit", + "desktop.menu.previousSession": "Vorige sessie", + "desktop.menu.nextSession": "Volgende sessie", + "desktop.menu.previousProject": "Vorig project", + "desktop.menu.nextProject": "Volgend project", + "desktop.menu.minimize": "Minimaliseren", + "desktop.menu.maximize": "Maximaliseren", + "desktop.menu.documentation": "OpenCode-documentatie", + "desktop.menu.supportForum": "Supportforum", + "desktop.menu.shareFeedback": "Feedback delen", + "desktop.menu.reportBug": "Een bug melden", + "desktop.menu.ariaLabel": "OpenCode-menu", + "desktop.updater.dialog.checkFailed.message": "Controleren op updates is mislukt.", + "desktop.updater.dialog.checkFailed.title": "Updatefout", + "desktop.updater.dialog.upToDate.message": "Je bent helemaal bijgewerkt.", + "desktop.updater.dialog.upToDate.title": "Geen updates", + "desktop.updater.dialog.ready.message": "Update {{version}} is gedownload. Nu opnieuw opstarten?", + "desktop.updater.dialog.ready.title": "Update gereed", + "desktop.updater.dialog.restart": "Opnieuw opstarten", + "desktop.updater.dialog.later": "Later", + "desktop.recovery.action.relaunch": "Opnieuw openen", + "desktop.recovery.action.exportLogs": "Logboeken exporteren", + "desktop.recovery.action.keepWaiting": "Blijven wachten", + "desktop.recovery.action.quit": "Stoppen", + "desktop.recovery.loadFailed": "OpenCode kan niet worden geladen", + "desktop.recovery.terminated": "Het OpenCode-venster is onverwacht beëindigd", + "desktop.recovery.unresponsive": "OpenCode reageert niet", + "desktop.recovery.unresponsive.detail": + "Je kunt de app opnieuw openen, de logboeken openen of blijven wachten.", + "desktop.recovery.loadFailed.detail": "Venster: {{window}}\nURL: {{url}}\nFout: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Venster: {{window}}\nReden: {{reason}}\nCode: {{code}}", + "desktop.recovery.unknown": "", + "desktop.dialog.chooseFolder": "Kies een map", + "desktop.dialog.chooseFile": "Kies een bestand", + "desktop.dialog.saveFile": "Bestand opslaan", + "desktop.dialog.files": "Bestanden", + "desktop.server.local": "Lokale server", + "desktop.wsl.error.windowsOnly": "WSL is alleen beschikbaar op Windows", + "desktop.wsl.error.unavailable": "WSL is niet beschikbaar", + "desktop.wsl.error.listInstalled": "Kan geïnstalleerde WSL-distributies niet weergeven", + "desktop.wsl.error.listOnline": "Kan online WSL-distributies niet weergeven", + "desktop.wsl.error.executeDistro": "Kan geen opdrachten uitvoeren in de distributie", + "desktop.wsl.error.installWsl": "Installatie van WSL is mislukt", + "desktop.wsl.error.installDistro": "Kan distributie niet installeren: {{distro}}", + "desktop.wsl.error.installOpencode": "Installatie van OpenCode is mislukt", + "desktop.wsl.error.alreadyAdded": "{{distro}} is al toegevoegd", + "desktop.wsl.error.opencodeMissing": "opencode is niet geïnstalleerd in deze distributie", + "desktop.wsl.error.opencodeCannotRun": "opencode is geïnstalleerd, maar kan niet worden uitgevoerd", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode is niet geïnstalleerd in {{distro}}", + "desktop.wsl.error.updateVersion": + "De OpenCode-update is voltooid, maar {{distro}} meldt nog steeds {{installed}}; verwacht: {{expected}}", + "desktop.wsl.error.noVersion": "geen versie", + "desktop.wsl.error.serverExited": "WSL-server is na het opstarten afgesloten (code={{code}} signaal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL-server is afgesloten voordat deze gereed was (code={{code}} signaal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Time-out bij statuscontrole van sidecar voor {{distro}} na {{timeout}} ms", + "desktop.wsl.error.commandTimeout": "Time-out voor {{command}} {{args}} na {{timeout}} ms", + "desktop.wsl.error.failedPort": "Kan poort niet ophalen", + "desktop.picker.error.notSelected": "Er is geen bestand geselecteerd in de bestandskiezer", + "desktop.picker.error.sizeLimit": "De geselecteerde bijlagen overschrijden de limiet van {{limit}} MB", "command.category.suggested": "Voorgesteld", "command.category.view": "Weergave", "command.category.project": "Project", @@ -16,6 +110,7 @@ export const dict = { "command.category.permissions": "Machtigingen", "command.category.workspace": "Werkruimte", "command.category.settings": "Instellingen", + "command.logs.export": "Logboeken exporteren", "theme.scheme.system": "Systeem", "theme.scheme.light": "Licht", "theme.scheme.dark": "Donker", @@ -119,6 +214,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Inloggen met Claude Pro/Max", "provider.connect.selectMethod": "Selecteer de aanmeldingsmethode voor {{provider}}.", "provider.connect.method.apiKey": "API-sleutel", + "provider.connect.method.browser": "Browser", + "provider.connect.method.headless": "Headless", "provider.connect.status.inProgress": "Autorisatie in behandeling...", "provider.connect.status.waiting": "Wachten op autorisatie...", "provider.connect.status.failed": "Autorisatie mislukt: {{error}}", @@ -150,6 +247,7 @@ export const dict = { "provider.connect.toast.connected.title": "Verbonden met {{provider}}", "provider.connect.toast.connected.description": "{{provider}}-modellen zijn nu beschikbaar voor gebruik.", "provider.custom.title": "Aangepaste aanbieder", + "provider.custom.unavailable": "Aangepaste aanbieders zijn niet beschikbaar op deze server", "provider.custom.description.prefix": "Configureer een OpenAI-compatibele aanbieder. Bekijk de ", "provider.custom.description.link": "documentatie over aanbiedersconfiguratie", "provider.custom.description.suffix": ".", @@ -411,6 +509,22 @@ export const dict = { "wsl.onboarding.desktopVersion": "desktop {{version}}", "wsl.onboarding.versionMismatch": "De geïnstalleerde versie komt niet overeen met de versie van de desktop-app.", "wsl.onboarding.adding": "Toevoegen...", + "help.tabs.toast.ariaLabel": + "Introductie van tabbladen. Organiseer je werk en actieve sessies met tabbladen", + "help.tabs.toast.dismiss": "Informatie over tabbladen sluiten", + "help.tabs.title": "Introductie van tabbladen", + "help.tabs.description": "Organiseer je werk en actieve sessies met tabbladen", + "help.tabs.date": "14 juli", + "help.tabs.introduction": "OpenCode Desktop is nu opgebouwd rond tabbladen.", + "help.tabs.sessions": + "Start een nieuwe sessie in een tabblad of open een bestaande sessie vanuit een van je projecten. Open een nieuw tabblad wanneer je aan iets nieuws begint en sluit het wanneer je klaar bent.", + "help.tabs.organize": + "Met enkele geopende tabbladen kun je je actieve sessies gemakkelijker organiseren. Geef tabbladen een herkenbare naam als je ze geopend wilt houden.", + "help.tabs.home": + "Je vindt al je sessies en projecten op het nieuwe startscherm. Wanneer je een sessie selecteert, wordt deze in een tabblad geopend.", + "help.tabs.persistence": "Wanneer je de app opnieuw opent, zijn je tabbladen nog steeds geopend.", + "help.tabs.worktrees": + "Het nieuwe ontwerp ondersteunt Git-worktrees nog niet, maar die ondersteuning komt binnenkort. Als je liever de vorige lay-out blijft gebruiken, kun je in Instellingen tussen de lay-outs schakelen. Houd er rekening mee dat de nieuwe lay-out over enkele weken permanent wordt.", "server.row.noUsername": "geen gebruikersnaam", "dialog.project.edit.title": "Project bewerken", "dialog.project.edit.name": "Naam", @@ -709,6 +823,11 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket abnormaal gesloten: {{code}}", "terminal.connectionLost.description": "De terminalverbinding is onderbroken. Dit kan gebeuren wanneer de server opnieuw wordt opgestart.", + "terminal.connectTicket.csrfError": + "PTY-verbindingsticket geweigerd door oorsprongs- of CSRF-controles. Controleer de CORS-configuratie van de server.", + "terminal.connectTicket.statusError": "PTY-verbindingsticket mislukt met status {{status}}", + "titlebar.update": "Bijwerken", + "titlebar.updateVersion": "Bijwerken {{version}}", "common.closeTab": "Tabblad sluiten", "common.dismiss": "Negeren", "common.moreCountSuffix": " (+{{count}} meer)", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index c52bb87c1cf..f2048f3bd88 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -2,6 +2,105 @@ import { dict as en } from "./en" type Keys = keyof typeof en export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Arkiv", + "desktop.menu.edit": "Rediger", + "desktop.menu.view": "Vis", + "desktop.menu.go": "Gå", + "desktop.menu.window": "Vindu", + "desktop.menu.help": "Hjelp", + "desktop.menu.checkForUpdates": "Søk etter oppdateringer...", + "desktop.menu.settings": "Innstillinger", + "desktop.menu.reloadWebview": "Last inn WebView på nytt", + "desktop.menu.restart": "Start på nytt", + "desktop.menu.exportLogs": "Eksporter logger...", + "desktop.menu.newSession": "Ny sesjon", + "desktop.menu.openProject": "Åpne prosjekt...", + "desktop.menu.newWindow": "Nytt vindu", + "desktop.menu.closeWindow": "Lukk vindu", + "desktop.menu.undo": "Angre", + "desktop.menu.redo": "Gjør om", + "desktop.menu.cut": "Klipp ut", + "desktop.menu.copy": "Kopier", + "desktop.menu.paste": "Lim inn", + "desktop.menu.delete": "Slett", + "desktop.menu.selectAll": "Merk alt", + "desktop.menu.toggleSidebar": "Vis eller skjul sidepanel", + "desktop.menu.toggleTerminal": "Vis eller skjul terminal", + "desktop.menu.toggleFileTree": "Vis eller skjul filtreet", + "desktop.menu.reload": "Last inn på nytt", + "desktop.menu.toggleDeveloperTools": "Vis eller skjul utviklerverktøy", + "desktop.menu.actualSize": "Faktisk størrelse", + "desktop.menu.zoomIn": "Zoom inn", + "desktop.menu.zoomOut": "Zoom ut", + "desktop.menu.toggleFullScreen": "Slå fullskjerm av eller på", + "desktop.menu.back": "Tilbake", + "desktop.menu.forward": "Frem", + "desktop.menu.previousSession": "Forrige sesjon", + "desktop.menu.nextSession": "Neste sesjon", + "desktop.menu.previousProject": "Forrige prosjekt", + "desktop.menu.nextProject": "Neste prosjekt", + "desktop.menu.minimize": "Minimer", + "desktop.menu.maximize": "Maksimer", + "desktop.menu.documentation": "OpenCode-dokumentasjon", + "desktop.menu.supportForum": "Brukerstøtteforum", + "desktop.menu.shareFeedback": "Del tilbakemelding", + "desktop.menu.reportBug": "Rapporter en feil", + "desktop.menu.ariaLabel": "OpenCode-meny", + + "desktop.updater.dialog.checkFailed.message": "Søket etter oppdateringer mislyktes.", + "desktop.updater.dialog.checkFailed.title": "Oppdateringsfeil", + "desktop.updater.dialog.upToDate.message": "Du har den nyeste versjonen.", + "desktop.updater.dialog.upToDate.title": "Ingen oppdateringer", + "desktop.updater.dialog.ready.message": "Oppdateringen {{version}} er lastet ned. Vil du starte på nytt nå?", + "desktop.updater.dialog.ready.title": "Oppdateringen er klar", + "desktop.updater.dialog.restart": "Start på nytt", + "desktop.updater.dialog.later": "Senere", + + "desktop.recovery.action.relaunch": "Start appen på nytt", + "desktop.recovery.action.exportLogs": "Eksporter logger", + "desktop.recovery.action.keepWaiting": "Fortsett å vente", + "desktop.recovery.action.quit": "Avslutt", + "desktop.recovery.loadFailed": "OpenCode kunne ikke lastes inn", + "desktop.recovery.terminated": "OpenCode-vinduet ble uventet avsluttet", + "desktop.recovery.unresponsive": "OpenCode svarer ikke", + "desktop.recovery.unresponsive.detail": "Du kan starte appen på nytt, åpne loggene eller fortsette å vente.", + "desktop.recovery.loadFailed.detail": "Vindu: {{window}}\nURL: {{url}}\nFeil: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Vindu: {{window}}\nÅrsak: {{reason}}\nKode: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Velg en mappe", + "desktop.dialog.chooseFile": "Velg en fil", + "desktop.dialog.saveFile": "Lagre fil", + "desktop.dialog.files": "Filer", + "desktop.server.local": "Lokal server", + + "desktop.wsl.error.windowsOnly": "WSL er bare tilgjengelig på Windows", + "desktop.wsl.error.unavailable": "WSL er utilgjengelig", + "desktop.wsl.error.listInstalled": "Kunne ikke vise installerte WSL-distribusjoner", + "desktop.wsl.error.listOnline": "Kunne ikke vise tilgjengelige WSL-distribusjoner", + "desktop.wsl.error.executeDistro": "Kan ikke kjøre kommandoer i distribusjonen", + "desktop.wsl.error.installWsl": "Installasjonen av WSL mislyktes", + "desktop.wsl.error.installDistro": "Kunne ikke installere distribusjonen: {{distro}}", + "desktop.wsl.error.installOpencode": "Installasjonen av OpenCode mislyktes", + "desktop.wsl.error.alreadyAdded": "{{distro}} er allerede lagt til", + "desktop.wsl.error.opencodeMissing": "opencode er ikke installert i denne distribusjonen", + "desktop.wsl.error.opencodeCannotRun": "opencode er installert, men kunne ikke kjøres", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode er ikke installert i {{distro}}", + "desktop.wsl.error.updateVersion": + "Oppdateringen av OpenCode er fullført, men {{distro}} rapporterer fortsatt {{installed}}; forventet {{expected}}", + "desktop.wsl.error.noVersion": "ingen versjon", + "desktop.wsl.error.serverExited": "WSL-serveren ble avsluttet etter oppstart (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL-serveren ble avsluttet før den var klar (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Tidsavbrudd for tilstandskontrollen av sidecar for {{distro}} etter {{timeout}} ms", + "desktop.wsl.error.commandTimeout": "Tidsavbrudd for {{command}} {{args}} etter {{timeout}} ms", + "desktop.wsl.error.failedPort": "Kunne ikke hente port", + + "desktop.picker.error.notSelected": "Ingen fil ble valgt i filvelgeren", + "desktop.picker.error.sizeLimit": "De valgte vedleggene overskrider grensen på {{limit}} MB", + "command.category.suggested": "Foreslått", "command.category.view": "Visning", "command.category.project": "Prosjekt", @@ -19,6 +118,7 @@ export const dict = { "command.category.permissions": "Tillatelser", "command.category.workspace": "Arbeidsområde", "command.category.settings": "Innstillinger", + "command.logs.export": "Eksporter logger", "theme.scheme.system": "System", "theme.scheme.light": "Lys", @@ -130,6 +230,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Logg inn med Claude Pro/Max", "provider.connect.selectMethod": "Velg innloggingsmetode for {{provider}}.", "provider.connect.method.apiKey": "API-nøkkel", + "provider.connect.method.browser": "Nettleser", + "provider.connect.method.headless": "Uten nettleser", "provider.connect.status.inProgress": "Autorisering pågår...", "provider.connect.status.waiting": "Venter på autorisering...", "provider.connect.status.failed": "Autorisering mislyktes: {{error}}", @@ -162,6 +264,7 @@ export const dict = { "provider.connect.toast.connected.description": "{{provider}}-modeller er nå tilgjengelige.", "provider.custom.title": "Egendefinert leverandør", + "provider.custom.unavailable": "Egendefinerte leverandører er ikke tilgjengelige på denne serveren", "provider.custom.description.prefix": "Konfigurer en OpenAI-kompatibel leverandør. Se ", "provider.custom.description.link": "dokumentasjon for leverandørkonfigurasjon", "provider.custom.description.suffix": ".", @@ -605,6 +708,12 @@ export const dict = { "terminal.connectionLost.title": "Tilkobling mistet", "terminal.connectionLost.description": "Terminalforbindelsen ble avbrutt. Dette kan skje når serveren starter på nytt.", + "terminal.connectTicket.csrfError": + "Tilkoblingsbilletten for PTY ble avvist av opprinnelses- eller CSRF-kontroller. Kontroller serverens CORS-konfigurasjon.", + "terminal.connectTicket.statusError": "Tilkoblingsbilletten for PTY mislyktes med {{status}}", + + "titlebar.update": "Oppdater", + "titlebar.updateVersion": "Oppdater {{version}}", "common.closeTab": "Lukk fane", "common.dismiss": "Avvis", @@ -1038,6 +1147,22 @@ export const dict = { "wsl.onboarding.versionMismatch": "Den installerte versjonen samsvarer ikke med versjonen av skrivebordsappen.", "wsl.onboarding.adding": "Legger til...", + "help.tabs.toast.ariaLabel": "Vi introduserer faner. Organiser arbeidet og de aktive sesjonene dine med faner", + "help.tabs.toast.dismiss": "Avvis informasjon om faner", + "help.tabs.title": "Vi introduserer faner", + "help.tabs.description": "Organiser arbeidet og de aktive sesjonene dine med faner", + "help.tabs.date": "14. juli", + "help.tabs.introduction": "OpenCode Desktop er nå bygget rundt faner.", + "help.tabs.sessions": + "Start en ny sesjon i en fane, eller åpne en eksisterende sesjon fra et av prosjektene dine. Åpne en ny fane når du skal begynne på noe nytt, og lukk den når du er ferdig.", + "help.tabs.organize": + "Når du har noen få faner åpne, blir det enklere å organisere de aktive sesjonene. Gi fanene navn som er lette å huske, hvis du planlegger å beholde dem.", + "help.tabs.home": + "Du finner alle sesjonene og prosjektene dine på den nye Hjem-skjermen. Når du velger en sesjon, åpnes den i en fane.", + "help.tabs.persistence": "Fanene er fortsatt åpne når du åpner appen på nytt.", + "help.tabs.worktrees": + "Det nye oppsettet støtter ikke Git Worktrees ennå, men støtte kommer snart. Hvis du foretrekker å fortsette med det forrige oppsettet, kan du bytte mellom oppsettene i Innstillinger. Vær oppmerksom på at det nye oppsettet blir permanent om noen uker.", + "language.uk": "Українська", "error.page.description.localServerStartup": "Det oppstod en feil under oppstart av den lokale serveren.", diff --git a/packages/app/src/i18n/pa.ts b/packages/app/src/i18n/pa.ts index 05c58b065e2..e7d3e076c1e 100644 --- a/packages/app/src/i18n/pa.ts +++ b/packages/app/src/i18n/pa.ts @@ -1,4 +1,102 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "فائل", + "desktop.menu.edit": "ترمیم", + "desktop.menu.view": "نظارہ", + "desktop.menu.go": "جاؤ", + "desktop.menu.window": "ونڈو", + "desktop.menu.help": "مدد", + "desktop.menu.checkForUpdates": "اپ ڈیٹاں دی پڑتال کرو...", + "desktop.menu.settings": "ترتیبات", + "desktop.menu.reloadWebview": "Webview دوبارہ لوڈ کرو", + "desktop.menu.restart": "دوبارہ شروع کرو", + "desktop.menu.exportLogs": "لاگ برآمد کرو...", + "desktop.menu.newSession": "نواں سیشن", + "desktop.menu.openProject": "پروجیکٹ کھولو...", + "desktop.menu.newWindow": "نویں ونڈو", + "desktop.menu.closeWindow": "ونڈو بند کرو", + "desktop.menu.undo": "کالعدم کرو", + "desktop.menu.redo": "دوبارہ کرو", + "desktop.menu.cut": "کٹ کرو", + "desktop.menu.copy": "کاپی کرو", + "desktop.menu.paste": "چپکاؤ", + "desktop.menu.delete": "مٹاؤ", + "desktop.menu.selectAll": "سارا کجھ چنو", + "desktop.menu.toggleSidebar": "سائڈبار ٹوگل کرو", + "desktop.menu.toggleTerminal": "ٹرمینل ٹوگل کرو", + "desktop.menu.toggleFileTree": "فائل ٹری ٹوگل کرو", + "desktop.menu.reload": "دوبارہ لوڈ کرو", + "desktop.menu.toggleDeveloperTools": "ڈیویلپر ٹولز ٹوگل کرو", + "desktop.menu.actualSize": "اصل سائز", + "desktop.menu.zoomIn": "وڈا کرو", + "desktop.menu.zoomOut": "چھوٹا کرو", + "desktop.menu.toggleFullScreen": "پوری سکرین ٹوگل کرو", + "desktop.menu.back": "پچھے", + "desktop.menu.forward": "اگے", + "desktop.menu.previousSession": "پچھلا سیشن", + "desktop.menu.nextSession": "اگلا سیشن", + "desktop.menu.previousProject": "پچھلا پروجیکٹ", + "desktop.menu.nextProject": "اگلا پروجیکٹ", + "desktop.menu.minimize": "چھوٹا کرو", + "desktop.menu.maximize": "وڈا کرو", + "desktop.menu.documentation": "OpenCode دیاں دستاویزاں", + "desktop.menu.supportForum": "مدد دا فورم", + "desktop.menu.shareFeedback": "اپنی رائے سانجھی کرو", + "desktop.menu.reportBug": "خرابی دی رپورٹ کرو", + "desktop.menu.ariaLabel": "OpenCode مینو", + + "desktop.updater.dialog.checkFailed.message": "اپ ڈیٹ دی پڑتال ناکام ہو گئی۔", + "desktop.updater.dialog.checkFailed.title": "اپ ڈیٹ دی غلطی", + "desktop.updater.dialog.upToDate.message": "تہاڈے کول تازہ ترین ورژن اے۔", + "desktop.updater.dialog.upToDate.title": "کوئی اپ ڈیٹ نئیں", + "desktop.updater.dialog.ready.message": "اپ ڈیٹ {{version}} ڈاؤن لوڈ ہو گئی اے۔ ہن دوبارہ شروع کرنا اے؟", + "desktop.updater.dialog.ready.title": "اپ ڈیٹ تیار اے", + "desktop.updater.dialog.restart": "دوبارہ شروع کرو", + "desktop.updater.dialog.later": "پچھوں", + + "desktop.recovery.action.relaunch": "دوبارہ چلاؤ", + "desktop.recovery.action.exportLogs": "لاگ برآمد کرو", + "desktop.recovery.action.keepWaiting": "اڈیک جاری رکھو", + "desktop.recovery.action.quit": "بند کرو", + "desktop.recovery.loadFailed": "OpenCode لوڈ نئیں ہو سکیا", + "desktop.recovery.terminated": "OpenCode دی ونڈو اچانک بند ہو گئی", + "desktop.recovery.unresponsive": "OpenCode جواب نئیں دے رہیا", + "desktop.recovery.unresponsive.detail": "تسی ایپ دوبارہ چلا سکدے او، لاگ کھول سکدے او یا اڈیک جاری رکھ سکدے او۔", + "desktop.recovery.loadFailed.detail": "ونڈو: {{window}}\nURL: {{url}}\nغلطی: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "ونڈو: {{window}}\nکارن: {{reason}}\nکوڈ: {{code}}", + "desktop.recovery.unknown": "<نامعلوم>", + + "desktop.dialog.chooseFolder": "فولڈر چنو", + "desktop.dialog.chooseFile": "فائل چنو", + "desktop.dialog.saveFile": "فائل محفوظ کرو", + "desktop.dialog.files": "فائلاں", + "desktop.server.local": "مقامی سرور", + + "desktop.wsl.error.windowsOnly": "WSL صرف Windows تے دستیاب اے", + "desktop.wsl.error.unavailable": "WSL دستیاب نئیں", + "desktop.wsl.error.listInstalled": "انسٹال کیتے WSL ڈسٹرو دی فہرست نئیں بن سکی", + "desktop.wsl.error.listOnline": "آن لائن WSL ڈسٹرو دی فہرست نئیں بن سکی", + "desktop.wsl.error.executeDistro": "ڈسٹرو وچ کمانڈاں نئیں چل سکدیاں", + "desktop.wsl.error.installWsl": "WSL دی تنصیب ناکام ہو گئی", + "desktop.wsl.error.installDistro": "ڈسٹرو انسٹال نئیں ہو سکیا: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode دی تنصیب ناکام ہو گئی", + "desktop.wsl.error.alreadyAdded": "{{distro}} پہلے ای شامل اے", + "desktop.wsl.error.opencodeMissing": "اس ڈسٹرو وچ opencode انسٹال نئیں اے", + "desktop.wsl.error.opencodeCannotRun": "opencode انسٹال اے پر چل نئیں سکیا", + "desktop.wsl.error.opencodeNotInstalled": "{{distro}} وچ OpenCode انسٹال نئیں اے", + "desktop.wsl.error.updateVersion": + "OpenCode دی اپ ڈیٹ پوری ہو گئی پر {{distro}} ہلے وی {{installed}} دس رہیا اے؛ {{expected}} چاہیدا سی", + "desktop.wsl.error.noVersion": "کوئی ورژن نئیں", + "desktop.wsl.error.serverExited": "WSL سرور شروع ہون پچھوں بند ہو گیا (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL سرور ٹھیک حالت وچ آون توں پہلاں بند ہو گیا (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": "{{distro}} دے sidecar دی صحت پڑتال لئی مقرر ویلا {{timeout}}ms پچھوں مُک گیا", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} لئی مقرر ویلا {{timeout}}ms پچھوں مُک گیا", + "desktop.wsl.error.failedPort": "پورٹ نئیں مل سکی", + + "desktop.picker.error.notSelected": "چونکار ولوں فائل نئیں چنی گئی", + "desktop.picker.error.sizeLimit": "چنیاں منسلک فائلاں {{limit}} MB دی حد توں ودھ نیں", + "command.category.suggested": "تجویز کیتا گیا", "command.category.view": "نظارہ", "command.category.project": "پروجیکٹ", @@ -16,6 +114,7 @@ export const dict = { "command.category.permissions": "اجازتاں", "command.category.workspace": "ورک سپیس", "command.category.settings": "ترتیبات", + "command.logs.export": "لاگ برآمد کرو", "theme.scheme.system": "نظام", "theme.scheme.light": "ہلکا", "theme.scheme.dark": "گہرا", @@ -119,6 +218,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Claude پرو/میکس دے نال لاگ ان کرو", "provider.connect.selectMethod": "{{provider}} لئی لاگ ان دا طریقہ منتخب کرو۔", "provider.connect.method.apiKey": "API کلید", + "provider.connect.method.browser": "براؤزر", + "provider.connect.method.headless": "ہیڈلیس", "provider.connect.status.inProgress": "اختیار دیندے پئے آں...", "provider.connect.status.waiting": "اختیار ملن دی اڈیک اے...", "provider.connect.status.failed": "اختیار نئیں مل سکیا: {{error}}", @@ -185,6 +286,7 @@ export const dict = { "provider.custom.error.baseURL.format": "http:// یا https:// توں شروع کرنا ضروری اے۔", "provider.custom.error.required": "لازمی اے", "provider.custom.error.duplicate": "دُہرا", + "provider.custom.unavailable": "ایس سرور تے من پسند پرووائیڈر دستیاب نئیں نیں", "provider.disconnect.toast.disconnected.title": "{{provider}} منقطع", "provider.disconnect.toast.disconnected.description": "{{provider}} ماڈل ہن دستیاب نئیں ہن۔", "model.tag.free": "مفت", @@ -411,6 +513,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "ڈیسک ٹاپ {{version}}", "wsl.onboarding.versionMismatch": "انسٹال کیتا گیا ورژن ڈیسک ٹاپ ایپ ورژن نال میل نئیں کھاندا۔", "wsl.onboarding.adding": "شامل کر رہیا واں...", + "help.tabs.toast.ariaLabel": "ٹیباں دی جان پچھان۔ اپنے کم تے چالو سیشناں نوں ٹیباں نال ترتیب دیو", + "help.tabs.toast.dismiss": "ٹیباں دی جانکاری ہٹاؤ", + "help.tabs.title": "ٹیباں دی جان پچھان", + "help.tabs.description": "اپنے کم تے چالو سیشناں نوں ٹیباں نال ترتیب دیو", + "help.tabs.date": "14 جولائی", + "help.tabs.introduction": "OpenCode Desktop ہن ٹیباں دے گرد بنایا گیا اے۔", + "help.tabs.sessions": + "اک ٹیب وچ نواں سیشن شروع کرو یا اپنے کسے وی پروجیکٹ توں موجودہ سیشن کھولو۔ جدوں کجھ نواں شروع کرو تے نواں ٹیب کھولو تے کم پورا ہون تے اینوں بند کر دیو۔", + "help.tabs.organize": + "کجھ ٹیب کھلے رکھن نال چالو سیشناں نوں ترتیب نال رکھنا سوکھا ہوندا اے۔ جے تسی ٹیب سنبھال کے رکھنا اے تے اینوں کوئی چیتے رہن آلا ناں دیو۔", + "help.tabs.home": + "نویں ہوم سکرین تے تہانوں اپنے سارے سیشن تے پروجیکٹ لبھن گے۔ سیشن چنن نال اوہ اک ٹیب وچ کھل جاندا اے۔", + "help.tabs.persistence": "ایپ دوبارہ کھولن تے تہاڈے ٹیب کھلے رہندے نیں۔", + "help.tabs.worktrees": + "نواں ڈیزائن ہلے Git Worktrees نوں سہارا نئیں دیندا، ایہ سہولت چھیتی آ رہی اے۔ جے تسی پچھلا لے آؤٹ ورتدے رہنا چاہندے او تے ترتیبات وچ لے آؤٹ بدل سکدے او۔ بس چیتے رکھو کہ کجھ ہفتیاں پچھوں نواں لے آؤٹ پکا ہو جائے گا۔", "server.row.noUsername": "کوئی ورتن آلا ناں نئیں", "dialog.project.edit.title": "پروجیکٹ وچ ترمیم کرو", "dialog.project.edit.name": "ناں", @@ -703,6 +820,11 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket غیر معمولی طور تے بند کیتا گیا: {{code}}", "terminal.connectionLost.description": "ٹرمینل کنکشن وچ رکاوٹ پے گئی۔ ایہ اودوں ہو سکدا اے جدوں سرور دوبارہ شروع ہوندا اے۔", + "terminal.connectTicket.csrfError": + "PTY کنکشن ٹکٹ نوں ماخذ یا CSRF جانچاں نے رد کر دتا۔ سرور دی CORS کنفگ چیک کرو۔", + "terminal.connectTicket.statusError": "PTY کنکشن ٹکٹ {{status}} سٹیٹس نال ناکام ہو گیا", + "titlebar.update": "اپ ڈیٹ کرو", + "titlebar.updateVersion": "اپ ڈیٹ کرو {{version}}", "common.closeTab": "ٹیب بند کرو", "common.dismiss": "برخاست کرو", "common.moreCountSuffix": " (+{{count}} ہور)", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index c49c28178b9..b46590f8000 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -1,4 +1,105 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Plik", + "desktop.menu.edit": "Edycja", + "desktop.menu.view": "Widok", + "desktop.menu.go": "Przejdź", + "desktop.menu.window": "Okno", + "desktop.menu.help": "Pomoc", + "desktop.menu.checkForUpdates": "Sprawdź aktualizacje...", + "desktop.menu.settings": "Ustawienia", + "desktop.menu.reloadWebview": "Wczytaj ponownie widok internetowy", + "desktop.menu.restart": "Uruchom ponownie", + "desktop.menu.exportLogs": "Eksportuj dzienniki...", + "desktop.menu.newSession": "Nowa sesja", + "desktop.menu.openProject": "Otwórz projekt...", + "desktop.menu.newWindow": "Nowe okno", + "desktop.menu.closeWindow": "Zamknij okno", + "desktop.menu.undo": "Cofnij", + "desktop.menu.redo": "Ponów", + "desktop.menu.cut": "Wytnij", + "desktop.menu.copy": "Kopiuj", + "desktop.menu.paste": "Wklej", + "desktop.menu.delete": "Usuń", + "desktop.menu.selectAll": "Zaznacz wszystko", + "desktop.menu.toggleSidebar": "Przełącz pasek boczny", + "desktop.menu.toggleTerminal": "Przełącz terminal", + "desktop.menu.toggleFileTree": "Przełącz drzewo plików", + "desktop.menu.reload": "Wczytaj ponownie", + "desktop.menu.toggleDeveloperTools": "Przełącz narzędzia deweloperskie", + "desktop.menu.actualSize": "Rozmiar rzeczywisty", + "desktop.menu.zoomIn": "Powiększ", + "desktop.menu.zoomOut": "Pomniejsz", + "desktop.menu.toggleFullScreen": "Przełącz tryb pełnoekranowy", + "desktop.menu.back": "Wstecz", + "desktop.menu.forward": "Dalej", + "desktop.menu.previousSession": "Poprzednia sesja", + "desktop.menu.nextSession": "Następna sesja", + "desktop.menu.previousProject": "Poprzedni projekt", + "desktop.menu.nextProject": "Następny projekt", + "desktop.menu.minimize": "Minimalizuj", + "desktop.menu.maximize": "Maksymalizuj", + "desktop.menu.documentation": "Dokumentacja OpenCode", + "desktop.menu.supportForum": "Forum pomocy technicznej", + "desktop.menu.shareFeedback": "Prześlij opinię", + "desktop.menu.reportBug": "Zgłoś błąd", + "desktop.menu.ariaLabel": "Menu OpenCode", + + "desktop.updater.dialog.checkFailed.message": "Nie udało się sprawdzić dostępności aktualizacji.", + "desktop.updater.dialog.checkFailed.title": "Błąd aktualizacji", + "desktop.updater.dialog.upToDate.message": "Masz najnowszą wersję.", + "desktop.updater.dialog.upToDate.title": "Brak aktualizacji", + "desktop.updater.dialog.ready.message": "Pobrano aktualizację {{version}}. Uruchomić ponownie teraz?", + "desktop.updater.dialog.ready.title": "Aktualizacja jest gotowa", + "desktop.updater.dialog.restart": "Uruchom ponownie", + "desktop.updater.dialog.later": "Później", + + "desktop.recovery.action.relaunch": "Uruchom ponownie", + "desktop.recovery.action.exportLogs": "Eksportuj dzienniki", + "desktop.recovery.action.keepWaiting": "Czekaj dalej", + "desktop.recovery.action.quit": "Zakończ", + "desktop.recovery.loadFailed": "Nie udało się wczytać OpenCode", + "desktop.recovery.terminated": "Okno OpenCode zostało nieoczekiwanie zamknięte", + "desktop.recovery.unresponsive": "OpenCode nie odpowiada", + "desktop.recovery.unresponsive.detail": + "Możesz ponownie uruchomić aplikację, otworzyć dzienniki lub nadal czekać.", + "desktop.recovery.loadFailed.detail": "Okno: {{window}}\nURL: {{url}}\nBłąd: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Okno: {{window}}\nPrzyczyna: {{reason}}\nKod: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Wybierz folder", + "desktop.dialog.chooseFile": "Wybierz plik", + "desktop.dialog.saveFile": "Zapisz plik", + "desktop.dialog.files": "Pliki", + "desktop.server.local": "Serwer lokalny", + + "desktop.wsl.error.windowsOnly": "WSL jest dostępny tylko w systemie Windows", + "desktop.wsl.error.unavailable": "WSL jest niedostępny", + "desktop.wsl.error.listInstalled": "Nie udało się wyświetlić listy zainstalowanych dystrybucji WSL", + "desktop.wsl.error.listOnline": "Nie udało się wyświetlić listy dystrybucji WSL dostępnych online", + "desktop.wsl.error.executeDistro": "Nie można wykonywać poleceń w dystrybucji", + "desktop.wsl.error.installWsl": "Nie udało się zainstalować WSL", + "desktop.wsl.error.installDistro": "Nie udało się zainstalować dystrybucji: {{distro}}", + "desktop.wsl.error.installOpencode": "Nie udało się zainstalować OpenCode", + "desktop.wsl.error.alreadyAdded": "Dystrybucja {{distro}} została już dodana", + "desktop.wsl.error.opencodeMissing": "Program opencode nie jest zainstalowany w tej dystrybucji", + "desktop.wsl.error.opencodeCannotRun": "Program opencode jest zainstalowany, ale nie można go uruchomić", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode nie jest zainstalowany w dystrybucji {{distro}}", + "desktop.wsl.error.updateVersion": + "Aktualizacja OpenCode została ukończona, ale dystrybucja {{distro}} nadal zgłasza wersję {{installed}}; oczekiwano {{expected}}", + "desktop.wsl.error.noVersion": "brak wersji", + "desktop.wsl.error.serverExited": "Serwer WSL zakończył działanie po uruchomieniu (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "Serwer WSL zakończył działanie przed osiągnięciem gotowości (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Przekroczono limit czasu {{timeout}}ms podczas sprawdzania kondycji procesu sidecar dla dystrybucji {{distro}}", + "desktop.wsl.error.commandTimeout": + "Polecenie {{command}} {{args}} przekroczyło limit czasu {{timeout}}ms", + "desktop.wsl.error.failedPort": "Nie udało się uzyskać portu", + + "desktop.picker.error.notSelected": "Nie wybrano pliku w oknie wyboru", + "desktop.picker.error.sizeLimit": "Wybrane załączniki przekraczają limit {{limit}} MB", + "command.category.suggested": "Sugerowane", "command.category.view": "Widok", "command.category.project": "Projekt", @@ -16,6 +117,7 @@ export const dict = { "command.category.permissions": "Uprawnienia", "command.category.workspace": "Przestrzeń robocza", "command.category.settings": "Ustawienia", + "command.logs.export": "Eksportuj dzienniki", "theme.scheme.system": "Systemowy", "theme.scheme.light": "Jasny", "theme.scheme.dark": "Ciemny", @@ -119,6 +221,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Zaloguj się za pomocą konta Claude Pro/Max", "provider.connect.selectMethod": "Wybierz metodę logowania dla {{provider}}.", "provider.connect.method.apiKey": "Klucz API", + "provider.connect.method.browser": "Przeglądarka", + "provider.connect.method.headless": "Bez przeglądarki", "provider.connect.status.inProgress": "Autoryzacja w toku...", "provider.connect.status.waiting": "Oczekiwanie na autoryzację...", "provider.connect.status.failed": "Autoryzacja nie powiodła się: {{error}}", @@ -150,6 +254,7 @@ export const dict = { "provider.connect.toast.connected.title": "Połączono {{provider}}", "provider.connect.toast.connected.description": "Modele {{provider}} są teraz dostępne do użycia.", "provider.custom.title": "Dostawca niestandardowy", + "provider.custom.unavailable": "Niestandardowi dostawcy są niedostępni na tym serwerze", "provider.custom.description.prefix": "Skonfiguruj dostawcę zgodnego z OpenAI. Zobacz ", "provider.custom.description.link": "dokumentację konfiguracji dostawcy", "provider.custom.description.suffix": ".", @@ -405,6 +510,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "aplikacja komputerowa {{version}}", "wsl.onboarding.versionMismatch": "Zainstalowana wersja nie jest zgodna z wersją aplikacji komputerowej.", "wsl.onboarding.adding": "Dodawanie...", + "help.tabs.toast.ariaLabel": "Przedstawiamy karty. Organizuj pracę i aktywne sesje za pomocą kart", + "help.tabs.toast.dismiss": "Odrzuć informacje o kartach", + "help.tabs.title": "Przedstawiamy karty", + "help.tabs.description": "Organizuj pracę i aktywne sesje za pomocą kart", + "help.tabs.date": "14 lipca", + "help.tabs.introduction": "OpenCode Desktop korzysta teraz z układu opartego na kartach.", + "help.tabs.sessions": + "Rozpocznij nową sesję na karcie lub otwórz istniejącą sesję z dowolnego projektu. Otwórz nową kartę, gdy zaczynasz coś nowego, i zamknij ją po zakończeniu.", + "help.tabs.organize": + "Kilka otwartych kart ułatwia organizowanie aktywnych sesji. Jeśli chcesz zachować karty na dłużej, nadaj im łatwe do zapamiętania nazwy.", + "help.tabs.home": + "Wszystkie sesje i projekty znajdziesz na nowym ekranie głównym. Wybranie sesji powoduje otwarcie jej na karcie.", + "help.tabs.persistence": "Po ponownym otwarciu aplikacji karty pozostają otwarte.", + "help.tabs.worktrees": + "Nowy układ nie obsługuje jeszcze drzew roboczych Git, ale ta funkcja pojawi się wkrótce. Jeśli wolisz nadal korzystać z poprzedniego układu, możesz przełączać układy w Ustawieniach. Pamiętaj jednak, że za kilka tygodni nowy układ stanie się jedynym dostępnym.", "dialog.project.edit.title": "Edytuj projekt", "dialog.project.edit.name": "Nazwa", "dialog.project.edit.icon": "Ikona", @@ -667,6 +787,12 @@ export const dict = { "terminal.connectionLost.title": "Utracono połączenie", "terminal.connectionLost.description": "Połączenie z terminalem zostało przerwane. Może się to zdarzyć podczas ponownego uruchamiania serwera.", + "terminal.connectTicket.csrfError": + "Bilet połączenia PTY został odrzucony podczas kontroli źródła lub CSRF. Sprawdź konfigurację CORS serwera.", + "terminal.connectTicket.statusError": "Bilet połączenia PTY zakończył się niepowodzeniem ze statusem {{status}}", + + "titlebar.update": "Aktualizuj", + "titlebar.updateVersion": "Aktualizuj {{version}}", "common.closeTab": "Zamknij kartę", "common.dismiss": "Odrzuć", "common.requestFailed": "Żądanie nie powiodło się", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 4e1877cba95..64909b225d1 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -1,4 +1,106 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Файл", + "desktop.menu.edit": "Правка", + "desktop.menu.view": "Вид", + "desktop.menu.go": "Переход", + "desktop.menu.window": "Окно", + "desktop.menu.help": "Справка", + "desktop.menu.checkForUpdates": "Проверить наличие обновлений...", + "desktop.menu.settings": "Настройки", + "desktop.menu.reloadWebview": "Перезагрузить веб-представление", + "desktop.menu.restart": "Перезапустить", + "desktop.menu.exportLogs": "Экспортировать журналы...", + "desktop.menu.newSession": "Новая сессия", + "desktop.menu.openProject": "Открыть проект...", + "desktop.menu.newWindow": "Новое окно", + "desktop.menu.closeWindow": "Закрыть окно", + "desktop.menu.undo": "Отменить", + "desktop.menu.redo": "Повторить", + "desktop.menu.cut": "Вырезать", + "desktop.menu.copy": "Копировать", + "desktop.menu.paste": "Вставить", + "desktop.menu.delete": "Удалить", + "desktop.menu.selectAll": "Выбрать всё", + "desktop.menu.toggleSidebar": "Переключить боковую панель", + "desktop.menu.toggleTerminal": "Переключить терминал", + "desktop.menu.toggleFileTree": "Переключить дерево файлов", + "desktop.menu.reload": "Перезагрузить", + "desktop.menu.toggleDeveloperTools": "Переключить инструменты разработчика", + "desktop.menu.actualSize": "Фактический размер", + "desktop.menu.zoomIn": "Увеличить", + "desktop.menu.zoomOut": "Уменьшить", + "desktop.menu.toggleFullScreen": "Переключить полноэкранный режим", + "desktop.menu.back": "Назад", + "desktop.menu.forward": "Вперёд", + "desktop.menu.previousSession": "Предыдущая сессия", + "desktop.menu.nextSession": "Следующая сессия", + "desktop.menu.previousProject": "Предыдущий проект", + "desktop.menu.nextProject": "Следующий проект", + "desktop.menu.minimize": "Свернуть", + "desktop.menu.maximize": "Развернуть", + "desktop.menu.documentation": "Документация OpenCode", + "desktop.menu.supportForum": "Форум поддержки", + "desktop.menu.shareFeedback": "Отправить отзыв", + "desktop.menu.reportBug": "Сообщить об ошибке", + "desktop.menu.ariaLabel": "Меню OpenCode", + + "desktop.updater.dialog.checkFailed.message": "Не удалось проверить наличие обновлений.", + "desktop.updater.dialog.checkFailed.title": "Ошибка обновления", + "desktop.updater.dialog.upToDate.message": "У вас установлена последняя версия.", + "desktop.updater.dialog.upToDate.title": "Обновлений нет", + "desktop.updater.dialog.ready.message": "Обновление {{version}} загружено. Перезапустить сейчас?", + "desktop.updater.dialog.ready.title": "Обновление готово", + "desktop.updater.dialog.restart": "Перезапустить", + "desktop.updater.dialog.later": "Позже", + + "desktop.recovery.action.relaunch": "Запустить снова", + "desktop.recovery.action.exportLogs": "Экспортировать журналы", + "desktop.recovery.action.keepWaiting": "Продолжить ожидание", + "desktop.recovery.action.quit": "Выйти", + "desktop.recovery.loadFailed": "Не удалось загрузить OpenCode", + "desktop.recovery.terminated": "Окно OpenCode неожиданно завершило работу", + "desktop.recovery.unresponsive": "OpenCode не отвечает", + "desktop.recovery.unresponsive.detail": + "Можно снова запустить приложение, открыть журналы или продолжить ожидание.", + "desktop.recovery.loadFailed.detail": "Окно: {{window}}\nURL: {{url}}\nОшибка: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Окно: {{window}}\nПричина: {{reason}}\nКод: {{code}}", + "desktop.recovery.unknown": "<неизвестно>", + + "desktop.dialog.chooseFolder": "Выберите папку", + "desktop.dialog.chooseFile": "Выберите файл", + "desktop.dialog.saveFile": "Сохранить файл", + "desktop.dialog.files": "Файлы", + "desktop.server.local": "Локальный сервер", + + "desktop.wsl.error.windowsOnly": "WSL доступна только в Windows", + "desktop.wsl.error.unavailable": "WSL недоступна", + "desktop.wsl.error.listInstalled": "Не удалось получить список установленных дистрибутивов WSL", + "desktop.wsl.error.listOnline": "Не удалось получить список доступных дистрибутивов WSL", + "desktop.wsl.error.executeDistro": "Не удаётся выполнять команды в дистрибутиве", + "desktop.wsl.error.installWsl": "Не удалось установить WSL", + "desktop.wsl.error.installDistro": "Не удалось установить дистрибутив: {{distro}}", + "desktop.wsl.error.installOpencode": "Не удалось установить OpenCode", + "desktop.wsl.error.alreadyAdded": "Дистрибутив {{distro}} уже добавлен", + "desktop.wsl.error.opencodeMissing": "opencode не установлен в этом дистрибутиве", + "desktop.wsl.error.opencodeCannotRun": "opencode установлен, но его не удалось запустить", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode не установлен в {{distro}}", + "desktop.wsl.error.updateVersion": + "Обновление OpenCode завершено, но {{distro}} по-прежнему сообщает версию {{installed}}; ожидалась {{expected}}", + "desktop.wsl.error.noVersion": "нет версии", + "desktop.wsl.error.serverExited": + "Сервер WSL завершил работу после запуска (код={{code}}, сигнал={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "Сервер WSL завершил работу до перехода в рабочее состояние (код={{code}}, сигнал={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Истекло время ожидания проверки работоспособности Sidecar для {{distro}} ({{timeout}} мс)", + "desktop.wsl.error.commandTimeout": + "Истекло время ожидания выполнения {{command}} {{args}} ({{timeout}} мс)", + "desktop.wsl.error.failedPort": "Не удалось получить порт", + + "desktop.picker.error.notSelected": "В средстве выбора не выбран файл", + "desktop.picker.error.sizeLimit": "Размер выбранных вложений превышает лимит {{limit}} МБ", + "command.category.suggested": "Предложено", "command.category.view": "Просмотр", "command.category.project": "Проект", @@ -16,6 +118,7 @@ export const dict = { "command.category.permissions": "Разрешения", "command.category.workspace": "Рабочее пространство", "command.category.settings": "Настройки", + "command.logs.export": "Экспортировать журналы", "theme.scheme.system": "Системная", "theme.scheme.light": "Светлая", @@ -131,6 +234,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Войти с помощью Claude Pro/Max", "provider.connect.selectMethod": "Выберите способ входа для {{provider}}.", "provider.connect.method.apiKey": "Ключ API", + "provider.connect.method.browser": "Браузер", + "provider.connect.method.headless": "Без браузера", "provider.connect.status.inProgress": "Авторизация...", "provider.connect.status.waiting": "Ожидание авторизации...", "provider.connect.status.failed": "Ошибка авторизации: {{error}}", @@ -163,6 +268,7 @@ export const dict = { "provider.connect.toast.connected.description": "Модели {{provider}} теперь доступны.", "provider.custom.title": "Пользовательский провайдер", + "provider.custom.unavailable": "Пользовательские провайдеры недоступны на этом сервере", "provider.custom.description.prefix": "Настройте провайдера, совместимого с OpenAI. См. ", "provider.custom.description.link": "документацию по настройке провайдера", "provider.custom.description.suffix": ".", @@ -432,6 +538,22 @@ export const dict = { "wsl.onboarding.versionMismatch": "Установленная версия не соответствует версии приложения.", "wsl.onboarding.adding": "Добавление...", + "help.tabs.toast.ariaLabel": "Представляем вкладки. Организуйте работу и активные сессии с помощью вкладок", + "help.tabs.toast.dismiss": "Закрыть сведения о вкладках", + "help.tabs.title": "Представляем вкладки", + "help.tabs.description": "Организуйте работу и активные сессии с помощью вкладок", + "help.tabs.date": "14 июля", + "help.tabs.introduction": "Теперь работа в OpenCode Desktop организована с помощью вкладок.", + "help.tabs.sessions": + "Начните новую сессию во вкладке или откройте существующую сессию из любого проекта. Открывайте новую вкладку, когда начинаете новую задачу, и закрывайте её по завершении.", + "help.tabs.organize": + "Несколько открытых вкладок упрощают организацию активных сессий. Если вы планируете оставить вкладки открытыми, присвойте им запоминающиеся названия.", + "help.tabs.home": + "Все сессии и проекты находятся на новом экране «Главная». При выборе сессии она открывается во вкладке.", + "help.tabs.persistence": "После повторного открытия приложения вкладки останутся открытыми.", + "help.tabs.worktrees": + "Новый дизайн пока не поддерживает Git Worktrees, но эта возможность скоро появится. Если вы предпочитаете прежний макет, переключиться между макетами можно в разделе «Настройки». Учтите, что через несколько недель новый макет станет постоянным.", + "dialog.project.edit.title": "Редактировать проект", "dialog.project.edit.name": "Название", "dialog.project.edit.icon": "Значок", @@ -726,6 +848,12 @@ export const dict = { "terminal.connectionLost.title": "Соединение потеряно", "terminal.connectionLost.description": "Соединение с терминалом прервано. Это может произойти при перезапуске сервера.", + "terminal.connectTicket.csrfError": + "Билет подключения PTY отклонён при проверке источника или CSRF. Проверьте конфигурацию CORS сервера.", + "terminal.connectTicket.statusError": "Не удалось получить билет подключения PTY, статус: {{status}}", + + "titlebar.update": "Обновить", + "titlebar.updateVersion": "Обновить {{version}}", "common.closeTab": "Закрыть вкладку", "common.dismiss": "Закрыть", diff --git a/packages/app/src/i18n/sv.ts b/packages/app/src/i18n/sv.ts index 2b76f3e7b6c..401c47bb492 100644 --- a/packages/app/src/i18n/sv.ts +++ b/packages/app/src/i18n/sv.ts @@ -1,4 +1,97 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Arkiv", + "desktop.menu.edit": "Redigera", + "desktop.menu.view": "Visa", + "desktop.menu.go": "Gå", + "desktop.menu.window": "Fönster", + "desktop.menu.help": "Hjälp", + "desktop.menu.checkForUpdates": "Sök efter uppdateringar...", + "desktop.menu.settings": "Inställningar", + "desktop.menu.reloadWebview": "Läs in webbvyn igen", + "desktop.menu.restart": "Starta om", + "desktop.menu.exportLogs": "Exportera loggar...", + "desktop.menu.newSession": "Ny session", + "desktop.menu.openProject": "Öppna projekt...", + "desktop.menu.newWindow": "Nytt fönster", + "desktop.menu.closeWindow": "Stäng fönster", + "desktop.menu.undo": "Ångra", + "desktop.menu.redo": "Gör om", + "desktop.menu.cut": "Klipp ut", + "desktop.menu.copy": "Kopiera", + "desktop.menu.paste": "Klistra in", + "desktop.menu.delete": "Radera", + "desktop.menu.selectAll": "Markera allt", + "desktop.menu.toggleSidebar": "Växla sidofältet", + "desktop.menu.toggleTerminal": "Växla terminal", + "desktop.menu.toggleFileTree": "Växla filträdet", + "desktop.menu.reload": "Läs in igen", + "desktop.menu.toggleDeveloperTools": "Växla utvecklarverktyg", + "desktop.menu.actualSize": "Verklig storlek", + "desktop.menu.zoomIn": "Zooma in", + "desktop.menu.zoomOut": "Zooma ut", + "desktop.menu.toggleFullScreen": "Växla helskärmsläge", + "desktop.menu.back": "Bakåt", + "desktop.menu.forward": "Framåt", + "desktop.menu.previousSession": "Föregående session", + "desktop.menu.nextSession": "Nästa session", + "desktop.menu.previousProject": "Föregående projekt", + "desktop.menu.nextProject": "Nästa projekt", + "desktop.menu.minimize": "Minimera", + "desktop.menu.maximize": "Maximera", + "desktop.menu.documentation": "OpenCode-dokumentation", + "desktop.menu.supportForum": "Supportforum", + "desktop.menu.shareFeedback": "Skicka feedback", + "desktop.menu.reportBug": "Rapportera ett fel", + "desktop.menu.ariaLabel": "OpenCode-meny", + "desktop.updater.dialog.checkFailed.message": "Det gick inte att söka efter uppdateringar.", + "desktop.updater.dialog.checkFailed.title": "Uppdateringsfel", + "desktop.updater.dialog.upToDate.message": "Du har den senaste versionen.", + "desktop.updater.dialog.upToDate.title": "Inga uppdateringar", + "desktop.updater.dialog.ready.message": "Uppdateringen {{version}} har hämtats. Vill du starta om nu?", + "desktop.updater.dialog.ready.title": "Uppdateringen är klar", + "desktop.updater.dialog.restart": "Starta om", + "desktop.updater.dialog.later": "Senare", + "desktop.recovery.action.relaunch": "Starta om appen", + "desktop.recovery.action.exportLogs": "Exportera loggar", + "desktop.recovery.action.keepWaiting": "Fortsätt vänta", + "desktop.recovery.action.quit": "Avsluta", + "desktop.recovery.loadFailed": "OpenCode kunde inte läsas in", + "desktop.recovery.terminated": "OpenCode-fönstret avslutades oväntat", + "desktop.recovery.unresponsive": "OpenCode svarar inte", + "desktop.recovery.unresponsive.detail": "Du kan starta om appen, öppna loggarna eller fortsätta vänta.", + "desktop.recovery.loadFailed.detail": "Fönster: {{window}}\nURL: {{url}}\nFel: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Fönster: {{window}}\nOrsak: {{reason}}\nKod: {{code}}", + "desktop.recovery.unknown": "", + "desktop.dialog.chooseFolder": "Välj en mapp", + "desktop.dialog.chooseFile": "Välj en fil", + "desktop.dialog.saveFile": "Spara fil", + "desktop.dialog.files": "Filer", + "desktop.server.local": "Lokal server", + "desktop.wsl.error.windowsOnly": "WSL är endast tillgängligt i Windows", + "desktop.wsl.error.unavailable": "WSL är inte tillgängligt", + "desktop.wsl.error.listInstalled": "Det gick inte att lista installerade WSL-distributioner", + "desktop.wsl.error.listOnline": "Det gick inte att lista tillgängliga WSL-distributioner", + "desktop.wsl.error.executeDistro": "Det går inte att köra kommandon i distributionen", + "desktop.wsl.error.installWsl": "Installationen av WSL misslyckades", + "desktop.wsl.error.installDistro": "Det gick inte att installera distributionen: {{distro}}", + "desktop.wsl.error.installOpencode": "Installationen av OpenCode misslyckades", + "desktop.wsl.error.alreadyAdded": "{{distro}} har redan lagts till", + "desktop.wsl.error.opencodeMissing": "opencode är inte installerat i den här distributionen", + "desktop.wsl.error.opencodeCannotRun": "opencode är installerat men kunde inte köras", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode är inte installerat i {{distro}}", + "desktop.wsl.error.updateVersion": + "OpenCode-uppdateringen slutfördes, men {{distro}} rapporterar fortfarande {{installed}}. {{expected}} förväntades", + "desktop.wsl.error.noVersion": "ingen version", + "desktop.wsl.error.serverExited": "WSL-servern avslutades efter start (kod={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL-servern avslutades innan den blev felfri (kod={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Tidsgränsen för hälsokontrollen av sidoprocessen för {{distro}} överskreds efter {{timeout}} ms", + "desktop.wsl.error.commandTimeout": "Tidsgränsen för {{command}} {{args}} överskreds efter {{timeout}} ms", + "desktop.wsl.error.failedPort": "Det gick inte att hämta porten", + "desktop.picker.error.notSelected": "Ingen fil valdes i filväljaren", + "desktop.picker.error.sizeLimit": "De valda bilagorna överskrider gränsen på {{limit}} MB", "command.category.suggested": "Föreslagna", "command.category.view": "Visa", "command.category.project": "Projekt", @@ -16,6 +109,7 @@ export const dict = { "command.category.permissions": "Behörigheter", "command.category.workspace": "Arbetsyta", "command.category.settings": "Inställningar", + "command.logs.export": "Exportera loggar", "theme.scheme.system": "System", "theme.scheme.light": "Ljus", "theme.scheme.dark": "Mörk", @@ -119,6 +213,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Logga in med Claude Pro/Max", "provider.connect.selectMethod": "Välj inloggningsmetod för {{provider}}.", "provider.connect.method.apiKey": "API-nyckel", + "provider.connect.method.browser": "Webbläsare", + "provider.connect.method.headless": "Huvudlös", "provider.connect.status.inProgress": "Auktorisering pågår...", "provider.connect.status.waiting": "Väntar på auktorisering...", "provider.connect.status.failed": "Auktorisering misslyckades: {{error}}", @@ -150,6 +246,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}} ansluten", "provider.connect.toast.connected.description": "{{provider}}-modeller är nu tillgängliga.", "provider.custom.title": "Anpassad leverantör", + "provider.custom.unavailable": "Anpassade leverantörer är inte tillgängliga på den här servern", "provider.custom.description.prefix": "Konfigurera en OpenAI-kompatibel leverantör. Se ", "provider.custom.description.link": "dokumentationen om leverantörskonfiguration", "provider.custom.description.suffix": ".", @@ -410,6 +507,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "desktop {{version}}", "wsl.onboarding.versionMismatch": "Den installerade versionen matchar inte versionen av skrivbordsappen.", "wsl.onboarding.adding": "Lägger till...", + "help.tabs.toast.ariaLabel": "Vi presenterar flikar. Ordna ditt arbete och dina aktiva sessioner med flikar", + "help.tabs.toast.dismiss": "Stäng informationen om flikar", + "help.tabs.title": "Vi presenterar flikar", + "help.tabs.description": "Ordna ditt arbete och dina aktiva sessioner med flikar", + "help.tabs.date": "14 juli", + "help.tabs.introduction": "OpenCode Desktop är nu uppbyggt kring flikar.", + "help.tabs.sessions": + "Starta en ny session på en flik eller öppna en befintlig session från något av dina projekt. Öppna en ny flik när du påbörjar något nytt och stäng den när du är klar.", + "help.tabs.organize": + "Genom att hålla några flikar öppna blir det enklare att ordna dina aktiva sessioner. Byt namn på flikarna till något du kommer ihåg om du tänker behålla dem.", + "help.tabs.home": + "Du hittar alla dina sessioner och projekt på den nya hemskärmen. När du väljer en session öppnas den på en flik.", + "help.tabs.persistence": "När du öppnar appen igen är dina flikar fortfarande öppna.", + "help.tabs.worktrees": + "Den nya designen stöder inte Git Worktrees ännu, men stöd kommer snart. Om du föredrar att fortsätta använda den tidigare layouten kan du därför växla mellan layouterna i Inställningar. Tänk bara på att den nya layouten blir permanent om några veckor.", "server.row.noUsername": "inget användarnamn", "dialog.project.edit.title": "Redigera projekt", "dialog.project.edit.name": "Namn", @@ -704,6 +816,11 @@ export const dict = { "terminal.connectionLost.title": "Anslutning förlorad", "terminal.connectionLost.abnormalClose": "WebSocket stängdes onormalt: {{code}}", "terminal.connectionLost.description": "Terminalanslutningen avbröts. Detta kan hända när servern startar om.", + "terminal.connectTicket.csrfError": + "PTY-anslutningsbiljetten avvisades vid ursprungs- eller CSRF-kontrollen. Kontrollera serverns CORS-konfiguration.", + "terminal.connectTicket.statusError": "PTY-anslutningsbiljetten misslyckades med status {{status}}", + "titlebar.update": "Uppdatera", + "titlebar.updateVersion": "Uppdatera {{version}}", "common.closeTab": "Stäng fliken", "common.dismiss": "Avfärda", "common.moreCountSuffix": " (+{{count}} till)", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 0e203eb5336..c1f6e43378e 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -1,4 +1,103 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "ไฟล์", + "desktop.menu.edit": "แก้ไข", + "desktop.menu.view": "มุมมอง", + "desktop.menu.go": "ไป", + "desktop.menu.window": "หน้าต่าง", + "desktop.menu.help": "วิธีใช้", + "desktop.menu.checkForUpdates": "ตรวจหาการอัปเดต...", + "desktop.menu.settings": "การตั้งค่า", + "desktop.menu.reloadWebview": "โหลด Webview ใหม่", + "desktop.menu.restart": "เริ่มการทำงานใหม่", + "desktop.menu.exportLogs": "ส่งออกบันทึก...", + "desktop.menu.newSession": "เซสชันใหม่", + "desktop.menu.openProject": "เปิดโปรเจกต์...", + "desktop.menu.newWindow": "หน้าต่างใหม่", + "desktop.menu.closeWindow": "ปิดหน้าต่าง", + "desktop.menu.undo": "เลิกทำ", + "desktop.menu.redo": "ทำซ้ำ", + "desktop.menu.cut": "ตัด", + "desktop.menu.copy": "คัดลอก", + "desktop.menu.paste": "วาง", + "desktop.menu.delete": "ลบ", + "desktop.menu.selectAll": "เลือกทั้งหมด", + "desktop.menu.toggleSidebar": "สลับแถบด้านข้าง", + "desktop.menu.toggleTerminal": "สลับเทอร์มินัล", + "desktop.menu.toggleFileTree": "สลับแผนผังไฟล์", + "desktop.menu.reload": "โหลดใหม่", + "desktop.menu.toggleDeveloperTools": "สลับเครื่องมือสำหรับนักพัฒนา", + "desktop.menu.actualSize": "ขนาดจริง", + "desktop.menu.zoomIn": "ขยาย", + "desktop.menu.zoomOut": "ย่อ", + "desktop.menu.toggleFullScreen": "สลับโหมดเต็มหน้าจอ", + "desktop.menu.back": "ย้อนกลับ", + "desktop.menu.forward": "ไปข้างหน้า", + "desktop.menu.previousSession": "เซสชันก่อนหน้า", + "desktop.menu.nextSession": "เซสชันถัดไป", + "desktop.menu.previousProject": "โปรเจกต์ก่อนหน้า", + "desktop.menu.nextProject": "โปรเจกต์ถัดไป", + "desktop.menu.minimize": "ย่อหน้าต่าง", + "desktop.menu.maximize": "ขยายหน้าต่าง", + "desktop.menu.documentation": "เอกสาร OpenCode", + "desktop.menu.supportForum": "ฟอรัมสนับสนุน", + "desktop.menu.shareFeedback": "ส่งข้อคิดเห็น", + "desktop.menu.reportBug": "รายงานข้อบกพร่อง", + "desktop.menu.ariaLabel": "เมนู OpenCode", + + "desktop.updater.dialog.checkFailed.message": "การตรวจหาการอัปเดตล้มเหลว", + "desktop.updater.dialog.checkFailed.title": "ข้อผิดพลาดในการอัปเดต", + "desktop.updater.dialog.upToDate.message": "คุณกำลังใช้เวอร์ชันล่าสุด", + "desktop.updater.dialog.upToDate.title": "ไม่มีการอัปเดต", + "desktop.updater.dialog.ready.message": "ดาวน์โหลดการอัปเดต {{version}} แล้ว เริ่มการทำงานใหม่ตอนนี้หรือไม่", + "desktop.updater.dialog.ready.title": "พร้อมอัปเดต", + "desktop.updater.dialog.restart": "เริ่มการทำงานใหม่", + "desktop.updater.dialog.later": "ภายหลัง", + + "desktop.recovery.action.relaunch": "เปิดอีกครั้ง", + "desktop.recovery.action.exportLogs": "ส่งออกบันทึก", + "desktop.recovery.action.keepWaiting": "รอต่อไป", + "desktop.recovery.action.quit": "ออก", + "desktop.recovery.loadFailed": "โหลด OpenCode ไม่สำเร็จ", + "desktop.recovery.terminated": "หน้าต่าง OpenCode หยุดทำงานโดยไม่คาดคิด", + "desktop.recovery.unresponsive": "OpenCode ไม่ตอบสนอง", + "desktop.recovery.unresponsive.detail": "คุณสามารถเปิดแอปอีกครั้ง เปิดบันทึก หรือรอต่อไป", + "desktop.recovery.loadFailed.detail": "หน้าต่าง: {{window}}\nURL: {{url}}\nข้อผิดพลาด: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "หน้าต่าง: {{window}}\nสาเหตุ: {{reason}}\nรหัส: {{code}}", + "desktop.recovery.unknown": "<ไม่ทราบ>", + + "desktop.dialog.chooseFolder": "เลือกโฟลเดอร์", + "desktop.dialog.chooseFile": "เลือกไฟล์", + "desktop.dialog.saveFile": "บันทึกไฟล์", + "desktop.dialog.files": "ไฟล์", + "desktop.server.local": "เซิร์ฟเวอร์ภายในเครื่อง", + + "desktop.wsl.error.windowsOnly": "WSL ใช้งานได้เฉพาะบน Windows เท่านั้น", + "desktop.wsl.error.unavailable": "WSL ไม่พร้อมใช้งาน", + "desktop.wsl.error.listInstalled": "ไม่สามารถแสดงรายการดิสโทร WSL ที่ติดตั้งแล้วได้", + "desktop.wsl.error.listOnline": "ไม่สามารถแสดงรายการดิสโทร WSL แบบออนไลน์ได้", + "desktop.wsl.error.executeDistro": "ไม่สามารถเรียกใช้คำสั่งในดิสโทรได้", + "desktop.wsl.error.installWsl": "การติดตั้ง WSL ล้มเหลว", + "desktop.wsl.error.installDistro": "ไม่สามารถติดตั้งดิสโทรได้: {{distro}}", + "desktop.wsl.error.installOpencode": "การติดตั้ง OpenCode ล้มเหลว", + "desktop.wsl.error.alreadyAdded": "เพิ่ม {{distro}} แล้ว", + "desktop.wsl.error.opencodeMissing": "ไม่ได้ติดตั้ง opencode ในดิสโทรนี้", + "desktop.wsl.error.opencodeCannotRun": "ติดตั้ง opencode แล้ว แต่ไม่สามารถเรียกใช้ได้", + "desktop.wsl.error.opencodeNotInstalled": "ไม่ได้ติดตั้ง OpenCode ใน {{distro}}", + "desktop.wsl.error.updateVersion": + "อัปเดต OpenCode เสร็จแล้ว แต่ {{distro}} ยังคงรายงานเวอร์ชัน {{installed}} ทั้งที่ควรเป็น {{expected}}", + "desktop.wsl.error.noVersion": "ไม่มีเวอร์ชัน", + "desktop.wsl.error.serverExited": "เซิร์ฟเวอร์ WSL หยุดทำงานหลังเริ่มต้น (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "เซิร์ฟเวอร์ WSL หยุดทำงานก่อนพร้อมใช้งาน (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "การตรวจสอบสถานะ Sidecar สำหรับ {{distro}} หมดเวลาหลังจาก {{timeout}}ms", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} หมดเวลาหลังจาก {{timeout}}ms", + "desktop.wsl.error.failedPort": "ไม่สามารถรับพอร์ตได้", + + "desktop.picker.error.notSelected": "ไม่ได้เลือกไฟล์จากตัวเลือกไฟล์", + "desktop.picker.error.sizeLimit": "ไฟล์แนบที่เลือกมีขนาดเกินขีดจำกัด {{limit}} MB", + "command.category.suggested": "แนะนำ", "command.category.view": "มุมมอง", "command.category.project": "โปรเจกต์", @@ -16,6 +115,7 @@ export const dict = { "command.category.permissions": "สิทธิ์", "command.category.workspace": "พื้นที่ทำงาน", "command.category.settings": "การตั้งค่า", + "command.logs.export": "ส่งออกบันทึก", "theme.scheme.system": "ระบบ", "theme.scheme.light": "สว่าง", @@ -131,6 +231,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "เข้าสู่ระบบด้วย Claude Pro/Max", "provider.connect.selectMethod": "เลือกวิธีการเข้าสู่ระบบสำหรับ {{provider}}", "provider.connect.method.apiKey": "คีย์ API", + "provider.connect.method.browser": "เบราว์เซอร์", + "provider.connect.method.headless": "ไม่ใช้เบราว์เซอร์", "provider.connect.status.inProgress": "กำลังอนุญาต...", "provider.connect.status.waiting": "รอการอนุญาต...", "provider.connect.status.failed": "การอนุญาตล้มเหลว: {{error}}", @@ -163,6 +265,7 @@ export const dict = { "provider.connect.toast.connected.description": "โมเดล {{provider}} พร้อมใช้งานแล้ว", "provider.custom.title": "ผู้ให้บริการที่กำหนดเอง", + "provider.custom.unavailable": "ผู้ให้บริการที่กำหนดเองไม่พร้อมใช้งานบนเซิร์ฟเวอร์นี้", "provider.custom.description.prefix": "กำหนดค่าผู้ให้บริการที่เข้ากันได้กับ OpenAI ดู ", "provider.custom.description.link": "เอกสารการกำหนดค่าผู้ให้บริการ", "provider.custom.description.suffix": "", @@ -432,6 +535,22 @@ export const dict = { "wsl.onboarding.versionMismatch": "เวอร์ชันที่ติดตั้งไม่ตรงกับเวอร์ชันของแอปเดสก์ท็อป", "wsl.onboarding.adding": "กำลังเพิ่ม...", + "help.tabs.toast.ariaLabel": "ขอแนะนำแท็บ จัดระเบียบงานและเซสชันที่ใช้งานอยู่ด้วยแท็บ", + "help.tabs.toast.dismiss": "ปิดข้อมูลเกี่ยวกับแท็บ", + "help.tabs.title": "ขอแนะนำแท็บ", + "help.tabs.description": "จัดระเบียบงานและเซสชันที่ใช้งานอยู่ด้วยแท็บ", + "help.tabs.date": "14 กรกฎาคม", + "help.tabs.introduction": "ตอนนี้ OpenCode Desktop สร้างขึ้นโดยใช้แท็บเป็นหลัก", + "help.tabs.sessions": + "เริ่มเซสชันใหม่ในแท็บ หรือเปิดเซสชันที่มีอยู่จากโปรเจกต์ใดก็ได้ เปิดแท็บใหม่เมื่อเริ่มงานใหม่ และปิดเมื่อทำงานเสร็จ", + "help.tabs.organize": + "การเปิดแท็บไว้สองสามแท็บช่วยให้จัดระเบียบเซสชันที่ใช้งานอยู่ได้ง่ายขึ้น เปลี่ยนชื่อแท็บให้จดจำได้ง่ายหากต้องการเก็บแท็บไว้", + "help.tabs.home": + "คุณจะพบเซสชันและโปรเจกต์ทั้งหมดในหน้าจอหน้าหลักแบบใหม่ เมื่อเลือกเซสชัน เซสชันนั้นจะเปิดในแท็บ", + "help.tabs.persistence": "เมื่อเปิดแอปอีกครั้ง แท็บของคุณจะยังคงเปิดอยู่", + "help.tabs.worktrees": + "การออกแบบใหม่ยังไม่รองรับ Git Worktrees แต่จะรองรับเร็ว ๆ นี้ ดังนั้น หากต้องการใช้เค้าโครงก่อนหน้าต่อ คุณสามารถสลับเค้าโครงได้ในการตั้งค่า โปรดทราบว่าเค้าโครงใหม่จะกลายเป็นเค้าโครงถาวรภายในอีกไม่กี่สัปดาห์", + "dialog.project.edit.title": "แก้ไขโปรเจกต์", "dialog.project.edit.name": "ชื่อ", "dialog.project.edit.icon": "ไอคอน", @@ -713,6 +832,12 @@ export const dict = { "terminal.close": "ปิดเทอร์มินัล", "terminal.connectionLost.title": "การเชื่อมต่อขาดหาย", "terminal.connectionLost.description": "การเชื่อมต่อเทอร์มินัลถูกขัดจังหวะ อาจเกิดขึ้นเมื่อเซิร์ฟเวอร์รีสตาร์ท", + "terminal.connectTicket.csrfError": + "ตั๋วเชื่อมต่อ PTY ถูกปฏิเสธโดยการตรวจสอบต้นทางหรือ CSRF โปรดตรวจสอบการกำหนดค่า CORS ของเซิร์ฟเวอร์", + "terminal.connectTicket.statusError": "ตั๋วเชื่อมต่อ PTY ล้มเหลวโดยมีสถานะ {{status}}", + + "titlebar.update": "อัปเดต", + "titlebar.updateVersion": "อัปเดต {{version}}", "common.closeTab": "ปิดแท็บ", "common.dismiss": "ปิด", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index 045f3690d10..a60f7250774 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -3,6 +3,106 @@ import { dict as en } from "./en" type Keys = keyof typeof en export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Dosya", + "desktop.menu.edit": "Düzen", + "desktop.menu.view": "Görünüm", + "desktop.menu.go": "Git", + "desktop.menu.window": "Pencere", + "desktop.menu.help": "Yardım", + "desktop.menu.checkForUpdates": "Güncellemeleri kontrol et...", + "desktop.menu.settings": "Ayarlar", + "desktop.menu.reloadWebview": "Web görünümünü yeniden yükle", + "desktop.menu.restart": "Yeniden başlat", + "desktop.menu.exportLogs": "Günlükleri dışa aktar...", + "desktop.menu.newSession": "Yeni oturum", + "desktop.menu.openProject": "Proje aç...", + "desktop.menu.newWindow": "Yeni pencere", + "desktop.menu.closeWindow": "Pencereyi kapat", + "desktop.menu.undo": "Geri al", + "desktop.menu.redo": "Yinele", + "desktop.menu.cut": "Kes", + "desktop.menu.copy": "Kopyala", + "desktop.menu.paste": "Yapıştır", + "desktop.menu.delete": "Sil", + "desktop.menu.selectAll": "Tümünü seç", + "desktop.menu.toggleSidebar": "Kenar çubuğunu aç/kapat", + "desktop.menu.toggleTerminal": "Terminali aç/kapat", + "desktop.menu.toggleFileTree": "Dosya ağacını aç/kapat", + "desktop.menu.reload": "Yeniden yükle", + "desktop.menu.toggleDeveloperTools": "Geliştirici araçlarını aç/kapat", + "desktop.menu.actualSize": "Gerçek boyut", + "desktop.menu.zoomIn": "Yakınlaştır", + "desktop.menu.zoomOut": "Uzaklaştır", + "desktop.menu.toggleFullScreen": "Tam ekranı aç/kapat", + "desktop.menu.back": "Geri", + "desktop.menu.forward": "İleri", + "desktop.menu.previousSession": "Önceki oturum", + "desktop.menu.nextSession": "Sonraki oturum", + "desktop.menu.previousProject": "Önceki proje", + "desktop.menu.nextProject": "Sonraki proje", + "desktop.menu.minimize": "Simge durumuna küçült", + "desktop.menu.maximize": "Ekranı kapla", + "desktop.menu.documentation": "OpenCode belgeleri", + "desktop.menu.supportForum": "Destek forumu", + "desktop.menu.shareFeedback": "Geri bildirim paylaş", + "desktop.menu.reportBug": "Hata bildir", + "desktop.menu.ariaLabel": "OpenCode menüsü", + + "desktop.updater.dialog.checkFailed.message": "Güncellemeler kontrol edilemedi.", + "desktop.updater.dialog.checkFailed.title": "Güncelleme hatası", + "desktop.updater.dialog.upToDate.message": "En son sürümü kullanıyorsunuz.", + "desktop.updater.dialog.upToDate.title": "Güncelleme yok", + "desktop.updater.dialog.ready.message": "{{version}} güncellemesi indirildi. Şimdi yeniden başlatılsın mı?", + "desktop.updater.dialog.ready.title": "Güncelleme hazır", + "desktop.updater.dialog.restart": "Yeniden başlat", + "desktop.updater.dialog.later": "Daha sonra", + + "desktop.recovery.action.relaunch": "Uygulamayı yeniden başlat", + "desktop.recovery.action.exportLogs": "Günlükleri dışa aktar", + "desktop.recovery.action.keepWaiting": "Beklemeye devam et", + "desktop.recovery.action.quit": "Çık", + "desktop.recovery.loadFailed": "OpenCode yüklenemedi", + "desktop.recovery.terminated": "OpenCode penceresi beklenmedik şekilde sonlandırıldı", + "desktop.recovery.unresponsive": "OpenCode yanıt vermiyor", + "desktop.recovery.unresponsive.detail": + "Uygulamayı yeniden başlatabilir, günlükleri açabilir veya beklemeye devam edebilirsiniz.", + "desktop.recovery.loadFailed.detail": "Pencere: {{window}}\nURL: {{url}}\nHata: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Pencere: {{window}}\nNeden: {{reason}}\nKod: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Bir klasör seçin", + "desktop.dialog.chooseFile": "Bir dosya seçin", + "desktop.dialog.saveFile": "Dosyayı kaydedin", + "desktop.dialog.files": "Dosyalar", + "desktop.server.local": "Yerel sunucu", + + "desktop.wsl.error.windowsOnly": "WSL yalnızca Windows'ta kullanılabilir", + "desktop.wsl.error.unavailable": "WSL kullanılamıyor", + "desktop.wsl.error.listInstalled": "Kurulu WSL dağıtımları listelenemedi", + "desktop.wsl.error.listOnline": "Çevrimiçi WSL dağıtımları listelenemedi", + "desktop.wsl.error.executeDistro": "Dağıtımda komutlar çalıştırılamıyor", + "desktop.wsl.error.installWsl": "WSL kurulamadı", + "desktop.wsl.error.installDistro": "Dağıtım kurulamadı: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode kurulamadı", + "desktop.wsl.error.alreadyAdded": "{{distro}} zaten eklendi", + "desktop.wsl.error.opencodeMissing": "opencode bu dağıtımda kurulu değil", + "desktop.wsl.error.opencodeCannotRun": "opencode kurulu ancak çalıştırılamadı", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode, {{distro}} içinde kurulu değil", + "desktop.wsl.error.updateVersion": + "OpenCode güncellemesi tamamlandı ancak {{distro}} hâlâ {{installed}} sürümünü bildiriyor; beklenen sürüm: {{expected}}", + "desktop.wsl.error.noVersion": "sürüm yok", + "desktop.wsl.error.serverExited": "WSL sunucusu başlatıldıktan sonra kapandı (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL sunucusu kullanıma hazır duruma gelmeden kapandı (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "{{distro}} Sidecar sistem durumu denetimi {{timeout}} ms sonra zaman aşımına uğradı", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} komutu {{timeout}} ms sonra zaman aşımına uğradı", + "desktop.wsl.error.failedPort": "Bağlantı noktası alınamadı", + + "desktop.picker.error.notSelected": "Dosya seçici tarafından dosya seçilmedi", + "desktop.picker.error.sizeLimit": "Seçilen ekler {{limit}} MB sınırını aşıyor", + "command.category.suggested": "Önerilen", "command.category.view": "Görünüm", "command.category.project": "Proje", @@ -20,6 +120,7 @@ export const dict = { "command.category.permissions": "İzinler", "command.category.workspace": "Çalışma Alanı", "command.category.settings": "Ayarlar", + "command.logs.export": "Günlükleri dışa aktar", "theme.scheme.system": "Sistem", "theme.scheme.light": "Açık", @@ -135,6 +236,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Claude Pro/Max ile giriş yap", "provider.connect.selectMethod": "{{provider}} için giriş yöntemini seçin.", "provider.connect.method.apiKey": "API anahtarı", + "provider.connect.method.browser": "Tarayıcı", + "provider.connect.method.headless": "Tarayıcısız", "provider.connect.status.inProgress": "Yetkilendirme devam ediyor...", "provider.connect.status.waiting": "Yetkilendirme bekleniyor...", "provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}", @@ -167,6 +270,7 @@ export const dict = { "provider.connect.toast.connected.description": "{{provider}} modelleri artık kullanımda.", "provider.custom.title": "Özel sağlayıcı", + "provider.custom.unavailable": "Özel sağlayıcılar bu sunucuda kullanılamıyor", "provider.custom.description.prefix": "OpenAI uyumlu bir sağlayıcı yapılandırın. ", "provider.custom.description.link": "sağlayıcı yapılandırma dokümanları", "provider.custom.description.suffix": " sayfasına bakın.", @@ -437,6 +541,22 @@ export const dict = { "wsl.onboarding.versionMismatch": "Kurulu sürüm masaüstü uygulamasının sürümüyle eşleşmiyor.", "wsl.onboarding.adding": "Ekleniyor...", + "help.tabs.toast.ariaLabel": "Sekmelerle tanışın. Çalışmalarınızı ve etkin oturumlarınızı sekmelerle düzenleyin", + "help.tabs.toast.dismiss": "Sekmeler bilgilerini kapat", + "help.tabs.title": "Sekmelerle tanışın", + "help.tabs.description": "Çalışmalarınızı ve etkin oturumlarınızı sekmelerle düzenleyin", + "help.tabs.date": "14 Temmuz", + "help.tabs.introduction": "OpenCode Desktop artık sekmeler üzerine kurulu.", + "help.tabs.sessions": + "Bir sekmede yeni oturum başlatın veya projelerinizden mevcut bir oturumu açın. Yeni bir işe başlarken yeni sekme açın, işiniz bittiğinde sekmeyi kapatın.", + "help.tabs.organize": + "Birkaç sekmeyi açık tutmak, etkin oturumlarınızı düzenlemenizi kolaylaştırır. Sekmeleri açık tutmayı planlıyorsanız onlara akılda kalıcı adlar verin.", + "help.tabs.home": + "Tüm oturumlarınızı ve projelerinizi yeni Ana Sayfa ekranında bulabilirsiniz. Bir oturum seçtiğinizde oturum bir sekmede açılır.", + "help.tabs.persistence": "Uygulamayı yeniden açtığınızda sekmeleriniz açık kalır.", + "help.tabs.worktrees": + "Yeni tasarım henüz Git Worktrees'i desteklemiyor; bu özellik yakında kullanıma sunulacak. Önceki düzeni kullanmaya devam etmek isterseniz Ayarlar'da düzenler arasında geçiş yapabilirsiniz. Yeni düzenin birkaç hafta içinde kalıcı olacağını unutmayın.", + "dialog.project.edit.title": "Projeyi düzenle", "dialog.project.edit.name": "Ad", "dialog.project.edit.icon": "Simge", @@ -728,6 +848,12 @@ export const dict = { "terminal.connectionLost.title": "Bağlantı kesildi", "terminal.connectionLost.description": "Terminal bağlantısı kesildi. Bu durum sunucu yeniden başladığında oluşabilir.", + "terminal.connectTicket.csrfError": + "PTY bağlantı bileti, kaynak veya CSRF denetimleri tarafından reddedildi. Sunucunun CORS yapılandırmasını kontrol edin.", + "terminal.connectTicket.statusError": "PTY bağlantı bileti {{status}} durumuyla başarısız oldu", + + "titlebar.update": "Güncelle", + "titlebar.updateVersion": "Güncelle {{version}}", "common.closeTab": "Sekmeyi kapat", "common.dismiss": "Kapat", diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts index db176eec2cc..4a439e920e1 100644 --- a/packages/app/src/i18n/uk.ts +++ b/packages/app/src/i18n/uk.ts @@ -1,4 +1,104 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Файл", + "desktop.menu.edit": "Редагування", + "desktop.menu.view": "Вигляд", + "desktop.menu.go": "Перехід", + "desktop.menu.window": "Вікно", + "desktop.menu.help": "Довідка", + "desktop.menu.checkForUpdates": "Перевірити наявність оновлень...", + "desktop.menu.settings": "Налаштування", + "desktop.menu.reloadWebview": "Перезавантажити вебподання", + "desktop.menu.restart": "Перезапустити", + "desktop.menu.exportLogs": "Експортувати журнали...", + "desktop.menu.newSession": "Нова сесія", + "desktop.menu.openProject": "Відкрити проєкт...", + "desktop.menu.newWindow": "Нове вікно", + "desktop.menu.closeWindow": "Закрити вікно", + "desktop.menu.undo": "Скасувати", + "desktop.menu.redo": "Повторити", + "desktop.menu.cut": "Вирізати", + "desktop.menu.copy": "Копіювати", + "desktop.menu.paste": "Вставити", + "desktop.menu.delete": "Видалити", + "desktop.menu.selectAll": "Вибрати все", + "desktop.menu.toggleSidebar": "Перемкнути бічну панель", + "desktop.menu.toggleTerminal": "Перемкнути термінал", + "desktop.menu.toggleFileTree": "Перемкнути дерево файлів", + "desktop.menu.reload": "Перезавантажити", + "desktop.menu.toggleDeveloperTools": "Перемкнути інструменти розробника", + "desktop.menu.actualSize": "Фактичний розмір", + "desktop.menu.zoomIn": "Збільшити", + "desktop.menu.zoomOut": "Зменшити", + "desktop.menu.toggleFullScreen": "Перемкнути повноекранний режим", + "desktop.menu.back": "Назад", + "desktop.menu.forward": "Вперед", + "desktop.menu.previousSession": "Попередня сесія", + "desktop.menu.nextSession": "Наступна сесія", + "desktop.menu.previousProject": "Попередній проєкт", + "desktop.menu.nextProject": "Наступний проєкт", + "desktop.menu.minimize": "Згорнути", + "desktop.menu.maximize": "Розгорнути", + "desktop.menu.documentation": "Документація OpenCode", + "desktop.menu.supportForum": "Форум підтримки", + "desktop.menu.shareFeedback": "Надіслати відгук", + "desktop.menu.reportBug": "Повідомити про помилку", + "desktop.menu.ariaLabel": "Меню OpenCode", + + "desktop.updater.dialog.checkFailed.message": "Не вдалося перевірити наявність оновлень.", + "desktop.updater.dialog.checkFailed.title": "Помилка оновлення", + "desktop.updater.dialog.upToDate.message": "У вас найновіша версія.", + "desktop.updater.dialog.upToDate.title": "Оновлень немає", + "desktop.updater.dialog.ready.message": "Оновлення {{version}} завантажено. Перезапустити зараз?", + "desktop.updater.dialog.ready.title": "Оновлення готове", + "desktop.updater.dialog.restart": "Перезапустити", + "desktop.updater.dialog.later": "Пізніше", + + "desktop.recovery.action.relaunch": "Запустити повторно", + "desktop.recovery.action.exportLogs": "Експортувати журнали", + "desktop.recovery.action.keepWaiting": "Продовжити очікування", + "desktop.recovery.action.quit": "Завершити роботу", + "desktop.recovery.loadFailed": "Не вдалося завантажити OpenCode", + "desktop.recovery.terminated": "Роботу вікна OpenCode несподівано завершено", + "desktop.recovery.unresponsive": "OpenCode не відповідає", + "desktop.recovery.unresponsive.detail": + "Ви можете повторно запустити програму, відкрити журнали або продовжити очікування.", + "desktop.recovery.loadFailed.detail": "Вікно: {{window}}\nURL: {{url}}\nПомилка: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Вікно: {{window}}\nПричина: {{reason}}\nКод: {{code}}", + "desktop.recovery.unknown": "<невідомо>", + + "desktop.dialog.chooseFolder": "Виберіть папку", + "desktop.dialog.chooseFile": "Виберіть файл", + "desktop.dialog.saveFile": "Зберегти файл", + "desktop.dialog.files": "Файли", + "desktop.server.local": "Локальний сервер", + + "desktop.wsl.error.windowsOnly": "WSL доступна лише у Windows", + "desktop.wsl.error.unavailable": "WSL недоступна", + "desktop.wsl.error.listInstalled": "Не вдалося отримати список встановлених дистрибутивів WSL", + "desktop.wsl.error.listOnline": "Не вдалося отримати список доступних онлайн дистрибутивів WSL", + "desktop.wsl.error.executeDistro": "Не вдалося виконати команди в дистрибутиві", + "desktop.wsl.error.installWsl": "Не вдалося встановити WSL", + "desktop.wsl.error.installDistro": "Не вдалося встановити дистрибутив: {{distro}}", + "desktop.wsl.error.installOpencode": "Не вдалося встановити OpenCode", + "desktop.wsl.error.alreadyAdded": "{{distro}} уже додано", + "desktop.wsl.error.opencodeMissing": "opencode не встановлено в цьому дистрибутиві", + "desktop.wsl.error.opencodeCannotRun": "opencode встановлено, але його не вдалося запустити", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode не встановлено в {{distro}}", + "desktop.wsl.error.updateVersion": + "Оновлення OpenCode завершено, але {{distro}} усе ще повідомляє про версію {{installed}}; очікувалася {{expected}}", + "desktop.wsl.error.noVersion": "версію не вказано", + "desktop.wsl.error.serverExited": "Сервер WSL завершив роботу після запуску (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "Сервер WSL завершив роботу, не досягнувши працездатного стану (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Час очікування перевірки працездатності sidecar для {{distro}} минув через {{timeout}}мс", + "desktop.wsl.error.commandTimeout": "Час очікування {{command}} {{args}} минув через {{timeout}}мс", + "desktop.wsl.error.failedPort": "Не вдалося отримати порт", + + "desktop.picker.error.notSelected": "Засіб вибору не повернув вибраний файл", + "desktop.picker.error.sizeLimit": "Вибрані вкладення перевищують обмеження в {{limit}} МБ", + "command.category.suggested": "Рекомендовані", "command.category.view": "Вигляд", "command.category.project": "Проєкт", @@ -16,6 +116,7 @@ export const dict = { "command.category.permissions": "Дозволи", "command.category.workspace": "Робоча область", "command.category.settings": "Налаштування", + "command.logs.export": "Експортувати журнали", "theme.scheme.system": "Системна", "theme.scheme.light": "Світла", @@ -131,6 +232,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Увійти з Claude Pro/Max", "provider.connect.selectMethod": "Виберіть спосіб входу для {{provider}}.", "provider.connect.method.apiKey": "Ключ API", + "provider.connect.method.browser": "Браузер", + "provider.connect.method.headless": "Без графічного інтерфейсу", "provider.connect.status.inProgress": "Авторизація виконується...", "provider.connect.status.waiting": "Очікування авторизації...", "provider.connect.status.failed": "Авторизація не вдалася: {{error}}", @@ -163,6 +266,7 @@ export const dict = { "provider.connect.toast.connected.description": "Моделі {{provider}} тепер доступні для використання.", "provider.custom.title": "Користувацький провайдер", + "provider.custom.unavailable": "Користувацькі провайдери недоступні на цьому сервері", "provider.custom.description.prefix": "Налаштуйте провайдера, сумісного з OpenAI. Перегляньте ", "provider.custom.description.link": "документацію з налаштування провайдера", "provider.custom.description.suffix": ".", @@ -439,6 +543,22 @@ export const dict = { "wsl.onboarding.desktopVersion": "десктопний застосунок {{version}}", "wsl.onboarding.versionMismatch": "Встановлена версія не відповідає версії десктопного застосунку.", "wsl.onboarding.adding": "Додавання...", + + "help.tabs.toast.ariaLabel": "Представляємо вкладки. Упорядковуйте роботу й активні сесії за допомогою вкладок", + "help.tabs.toast.dismiss": "Закрити інформацію про вкладки", + "help.tabs.title": "Представляємо вкладки", + "help.tabs.description": "Упорядковуйте роботу й активні сесії за допомогою вкладок", + "help.tabs.date": "14 липня", + "help.tabs.introduction": "OpenCode Desktop тепер побудовано навколо вкладок.", + "help.tabs.sessions": + "Почніть нову сесію у вкладці або відкрийте наявну сесію з будь-якого свого проєкту. Відкривайте нову вкладку, коли починаєте щось нове, і закривайте її після завершення.", + "help.tabs.organize": + "Кілька відкритих вкладок полегшують упорядкування активних сесій. Якщо плануєте залишити вкладки відкритими, перейменуйте їх на щось пам'ятне.", + "help.tabs.home": + "Усі свої сесії та проєкти ви знайдете на новому екрані «Головна». Вибрана сесія відкриється у вкладці.", + "help.tabs.persistence": "Після повторного відкриття програми ваші вкладки залишаться відкритими.", + "help.tabs.worktrees": + "Новий дизайн поки не підтримує робочі дерева Git, але ця можливість незабаром з'явиться. Тож якщо ви бажаєте й надалі користуватися попереднім макетом, перемикатися між макетами можна в налаштуваннях. Зауважте, що за кілька тижнів новий макет стане постійним.", "server.row.noUsername": "без імені користувача", "dialog.project.edit.title": "Редагувати проєкт", @@ -776,6 +896,12 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket закрито аномально: {{code}}", "terminal.connectionLost.description": "З'єднання з терміналом було перервано. Це може статися під час перезапуску сервера.", + "terminal.connectTicket.csrfError": + "Квиток підключення PTY відхилено через перевірку джерела або CSRF. Перевірте конфігурацію CORS сервера.", + "terminal.connectTicket.statusError": "Помилка квитка підключення PTY зі статусом {{status}}", + + "titlebar.update": "Оновити", + "titlebar.updateVersion": "Оновити {{version}}", "common.closeTab": "Закрити вкладку", "common.dismiss": "Відхилити", diff --git a/packages/app/src/i18n/ur.ts b/packages/app/src/i18n/ur.ts index 883553791c9..67f13676d36 100644 --- a/packages/app/src/i18n/ur.ts +++ b/packages/app/src/i18n/ur.ts @@ -1,4 +1,104 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "فائل", + "desktop.menu.edit": "ترمیم", + "desktop.menu.view": "دیکھیں", + "desktop.menu.go": "جائیں", + "desktop.menu.window": "ونڈو", + "desktop.menu.help": "مدد", + "desktop.menu.checkForUpdates": "اپ ڈیٹس کی جانچ کریں...", + "desktop.menu.settings": "ترتیبات", + "desktop.menu.reloadWebview": "ویب ویو دوبارہ لوڈ کریں", + "desktop.menu.restart": "دوبارہ شروع کریں", + "desktop.menu.exportLogs": "لاگز برآمد کریں...", + "desktop.menu.newSession": "نیا سیشن", + "desktop.menu.openProject": "پروجیکٹ کھولیں...", + "desktop.menu.newWindow": "نئی ونڈو", + "desktop.menu.closeWindow": "ونڈو بند کریں", + "desktop.menu.undo": "کالعدم کریں", + "desktop.menu.redo": "دوبارہ کریں", + "desktop.menu.cut": "کاٹیں", + "desktop.menu.copy": "کاپی کریں", + "desktop.menu.paste": "چسپاں کریں", + "desktop.menu.delete": "حذف کریں", + "desktop.menu.selectAll": "سبھی منتخب کریں", + "desktop.menu.toggleSidebar": "سائیڈ بار دکھائیں یا چھپائیں", + "desktop.menu.toggleTerminal": "ٹرمینل دکھائیں یا چھپائیں", + "desktop.menu.toggleFileTree": "فائل ٹری دکھائیں یا چھپائیں", + "desktop.menu.reload": "دوبارہ لوڈ کریں", + "desktop.menu.toggleDeveloperTools": "ڈویلپر ٹولز دکھائیں یا چھپائیں", + "desktop.menu.actualSize": "اصل سائز", + "desktop.menu.zoomIn": "بڑا کریں", + "desktop.menu.zoomOut": "چھوٹا کریں", + "desktop.menu.toggleFullScreen": "پوری اسکرین دکھائیں یا چھپائیں", + "desktop.menu.back": "پیچھے", + "desktop.menu.forward": "آگے", + "desktop.menu.previousSession": "پچھلا سیشن", + "desktop.menu.nextSession": "اگلا سیشن", + "desktop.menu.previousProject": "پچھلا پروجیکٹ", + "desktop.menu.nextProject": "اگلا پروجیکٹ", + "desktop.menu.minimize": "چھوٹا کریں", + "desktop.menu.maximize": "بڑا کریں", + "desktop.menu.documentation": "OpenCode دستاویزات", + "desktop.menu.supportForum": "معاونتی فورم", + "desktop.menu.shareFeedback": "رائے کا اشتراک کریں", + "desktop.menu.reportBug": "خرابی کی اطلاع دیں", + "desktop.menu.ariaLabel": "OpenCode مینو", + + "desktop.updater.dialog.checkFailed.message": "اپ ڈیٹ کی جانچ ناکام ہو گئی۔", + "desktop.updater.dialog.checkFailed.title": "اپ ڈیٹ کی خرابی", + "desktop.updater.dialog.upToDate.message": "آپ کا سافٹ ویئر تازہ ترین ہے۔", + "desktop.updater.dialog.upToDate.title": "کوئی اپ ڈیٹ نہیں", + "desktop.updater.dialog.ready.message": "اپ ڈیٹ {{version}} ڈاؤن لوڈ ہو گئی ہے۔ ابھی دوبارہ شروع کریں؟", + "desktop.updater.dialog.ready.title": "اپ ڈیٹ تیار ہے", + "desktop.updater.dialog.restart": "دوبارہ شروع کریں", + "desktop.updater.dialog.later": "بعد میں", + + "desktop.recovery.action.relaunch": "دوبارہ چلائیں", + "desktop.recovery.action.exportLogs": "لاگز برآمد کریں", + "desktop.recovery.action.keepWaiting": "انتظار جاری رکھیں", + "desktop.recovery.action.quit": "بند کریں", + "desktop.recovery.loadFailed": "OpenCode لوڈ نہیں ہو سکا", + "desktop.recovery.terminated": "OpenCode ونڈو غیر متوقع طور پر بند ہو گئی", + "desktop.recovery.unresponsive": "OpenCode جواب نہیں دے رہا", + "desktop.recovery.unresponsive.detail": + "آپ ایپ کو دوبارہ چلا سکتے ہیں، لاگز کھول سکتے ہیں یا انتظار جاری رکھ سکتے ہیں۔", + "desktop.recovery.loadFailed.detail": "ونڈو: {{window}}\nURL: {{url}}\nخرابی: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "ونڈو: {{window}}\nوجہ: {{reason}}\nکوڈ: {{code}}", + "desktop.recovery.unknown": "<نامعلوم>", + + "desktop.dialog.chooseFolder": "فولڈر منتخب کریں", + "desktop.dialog.chooseFile": "فائل منتخب کریں", + "desktop.dialog.saveFile": "فائل محفوظ کریں", + "desktop.dialog.files": "فائلیں", + "desktop.server.local": "مقامی سرور", + + "desktop.wsl.error.windowsOnly": "WSL صرف Windows پر دستیاب ہے", + "desktop.wsl.error.unavailable": "WSL دستیاب نہیں ہے", + "desktop.wsl.error.listInstalled": "انسٹال شدہ WSL ڈسٹری بیوشنز کی فہرست حاصل نہیں ہو سکی", + "desktop.wsl.error.listOnline": "آن لائن WSL ڈسٹری بیوشنز کی فہرست حاصل نہیں ہو سکی", + "desktop.wsl.error.executeDistro": "ڈسٹری بیوشن میں کمانڈز نہیں چلائی جا سکتیں", + "desktop.wsl.error.installWsl": "WSL کی تنصیب ناکام ہو گئی", + "desktop.wsl.error.installDistro": "ڈسٹری بیوشن انسٹال نہیں ہو سکی: {{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode کی تنصیب ناکام ہو گئی", + "desktop.wsl.error.alreadyAdded": "{{distro}} پہلے ہی شامل ہے", + "desktop.wsl.error.opencodeMissing": "اس ڈسٹری بیوشن میں opencode انسٹال نہیں ہے", + "desktop.wsl.error.opencodeCannotRun": "opencode انسٹال ہے لیکن چل نہیں سکا", + "desktop.wsl.error.opencodeNotInstalled": "{{distro}} میں OpenCode انسٹال نہیں ہے", + "desktop.wsl.error.updateVersion": + "OpenCode کی اپ ڈیٹ مکمل ہو گئی لیکن {{distro}} اب بھی {{installed}} کی اطلاع دے رہا ہے؛ متوقع ورژن {{expected}} ہے", + "desktop.wsl.error.noVersion": "کوئی ورژن نہیں", + "desktop.wsl.error.serverExited": "WSL سرور شروع ہونے کے بعد بند ہو گیا (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL سرور فعال ہونے سے پہلے بند ہو گیا (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "{{distro}} کے سائیڈ کار کی صحت کی جانچ {{timeout}}ms کے بعد مقررہ وقت سے تجاوز کر گئی", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} نے {{timeout}}ms کے بعد مقررہ وقت سے تجاوز کر دیا", + "desktop.wsl.error.failedPort": "پورٹ حاصل نہیں ہو سکی", + + "desktop.picker.error.notSelected": "فائل چنندہ میں کوئی فائل منتخب نہیں کی گئی", + "desktop.picker.error.sizeLimit": "منتخب منسلکات {{limit}} MB کی حد سے زیادہ ہیں", + "command.category.suggested": "تجویز کردہ", "command.category.view": "دیکھیں", "command.category.project": "پروجیکٹ", @@ -16,6 +116,7 @@ export const dict = { "command.category.permissions": "اجازتیں", "command.category.workspace": "کام کی جگہ", "command.category.settings": "ترتیبات", + "command.logs.export": "لاگز برآمد کریں", "theme.scheme.system": "سسٹم", "theme.scheme.light": "روشن", "theme.scheme.dark": "تاریک", @@ -119,6 +220,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Claude Pro/Max کے ساتھ لاگ ان کریں۔", "provider.connect.selectMethod": "{{provider}} کے لیے لاگ ان کا طریقہ منتخب کریں۔", "provider.connect.method.apiKey": "API کلید", + "provider.connect.method.browser": "براؤزر", + "provider.connect.method.headless": "ہیڈ لیس", "provider.connect.status.inProgress": "اجازت حاصل کی جا رہی ہے...", "provider.connect.status.waiting": "اجازت کا انتظار ہے...", "provider.connect.status.failed": "اجازت حاصل نہیں ہو سکی: {{error}}", @@ -186,6 +289,7 @@ export const dict = { "provider.custom.error.baseURL.format": "http:// یا https:// سے شروع ہونا چاہیے", "provider.custom.error.required": "درکار ہے۔", "provider.custom.error.duplicate": "مکرر", + "provider.custom.unavailable": "اس سرور پر اپنی مرضی کے مطابق فراہم کنندگان دستیاب نہیں ہیں۔", "provider.disconnect.toast.disconnected.title": "{{provider}} منقطع ہو گیا۔", "provider.disconnect.toast.disconnected.description": "{{provider}} ماڈلز اب دستیاب نہیں ہیں۔", "model.tag.free": "مفت", @@ -411,6 +515,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "ڈیسک ٹاپ {{version}}", "wsl.onboarding.versionMismatch": "انسٹال کردہ ورژن ڈیسک ٹاپ ایپ ورژن سے مماثل نہیں ہے۔", "wsl.onboarding.adding": "شامل کیا جا رہا ہے...", + "help.tabs.toast.ariaLabel": "ٹیبز کا تعارف۔ اپنے کام اور فعال سیشنز کو ٹیبز کے ذریعے منظم کریں", + "help.tabs.toast.dismiss": "ٹیبز کی معلومات بند کریں", + "help.tabs.title": "ٹیبز کا تعارف", + "help.tabs.description": "اپنے کام اور فعال سیشنز کو ٹیبز کے ذریعے منظم کریں", + "help.tabs.date": "14 جولائی", + "help.tabs.introduction": "OpenCode Desktop اب ٹیبز پر مبنی ہے۔", + "help.tabs.sessions": + "ٹیب میں نیا سیشن شروع کریں یا اپنے کسی بھی پروجیکٹ سے موجودہ سیشن کھولیں۔ کوئی نیا کام شروع کرتے وقت نیا ٹیب کھولیں اور کام مکمل ہونے پر اسے بند کر دیں۔", + "help.tabs.organize": + "چند ٹیبز کھلے رکھنے سے فعال سیشنز کو منظم کرنا آسان ہو جاتا ہے۔ اگر آپ کسی ٹیب کو برقرار رکھنا چاہتے ہیں تو اسے کوئی یاد رہنے والا نام دے دیں۔", + "help.tabs.home": + "نئی ہوم اسکرین پر آپ کو اپنے تمام سیشنز اور پروجیکٹس ملیں گے۔ کسی سیشن کو منتخب کرنے سے وہ ٹیب میں کھل جائے گا۔", + "help.tabs.persistence": "ایپ دوبارہ کھولنے پر آپ کے ٹیبز کھلے رہیں گے۔", + "help.tabs.worktrees": + "نیا ڈیزائن ابھی Git Worktrees کو سپورٹ نہیں کرتا، یہ سہولت جلد آ رہی ہے۔ اگر آپ پچھلا لے آؤٹ استعمال کرنا جاری رکھنا چاہتے ہیں تو ترتیبات میں لے آؤٹس کے درمیان سوئچ کر سکتے ہیں۔ یاد رہے کہ چند ہفتوں میں نیا لے آؤٹ مستقل ہو جائے گا۔", "server.row.noUsername": "کوئی صارف نام نہیں", "dialog.project.edit.title": "پروجیکٹ میں ترمیم کریں۔", "dialog.project.edit.name": "نام", @@ -703,6 +822,11 @@ export const dict = { "terminal.connectionLost.title": "کنکشن ختم ہو گیا۔", "terminal.connectionLost.abnormalClose": "WebSocket غیر معمولی طور پر بند ہوا: {{code}}", "terminal.connectionLost.description": "ٹرمینل کنکشن میں خلل پڑا۔ یہ سرور کے دوبارہ شروع ہونے پر ہوسکتا ہے۔", + "terminal.connectTicket.csrfError": + "PTY کنکشن ٹکٹ کو ماخذ یا CSRF جانچ نے مسترد کر دیا۔ سرور کی CORS تشکیل چیک کریں۔", + "terminal.connectTicket.statusError": "PTY کنکشن ٹکٹ {{status}} اسٹیٹس کے ساتھ ناکام ہو گیا۔", + "titlebar.update": "اپ ڈیٹ کریں", + "titlebar.updateVersion": "اپ ڈیٹ کریں {{version}}", "common.closeTab": "ٹیب بند کریں۔", "common.dismiss": "نظر انداز کریں", "common.moreCountSuffix": " (+{{count}} مزید)", diff --git a/packages/app/src/i18n/vi.ts b/packages/app/src/i18n/vi.ts index 0e846d0fd50..e0787217d3c 100644 --- a/packages/app/src/i18n/vi.ts +++ b/packages/app/src/i18n/vi.ts @@ -1,4 +1,103 @@ export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "Tệp", + "desktop.menu.edit": "Chỉnh sửa", + "desktop.menu.view": "Xem", + "desktop.menu.go": "Đi", + "desktop.menu.window": "Cửa sổ", + "desktop.menu.help": "Trợ giúp", + "desktop.menu.checkForUpdates": "Kiểm tra bản cập nhật...", + "desktop.menu.settings": "Cài đặt", + "desktop.menu.reloadWebview": "Tải lại Webview", + "desktop.menu.restart": "Khởi động lại", + "desktop.menu.exportLogs": "Xuất nhật ký...", + "desktop.menu.newSession": "Phiên mới", + "desktop.menu.openProject": "Mở dự án...", + "desktop.menu.newWindow": "Cửa sổ mới", + "desktop.menu.closeWindow": "Đóng cửa sổ", + "desktop.menu.undo": "Hoàn tác", + "desktop.menu.redo": "Làm lại", + "desktop.menu.cut": "Cắt", + "desktop.menu.copy": "Sao chép", + "desktop.menu.paste": "Dán", + "desktop.menu.delete": "Xóa", + "desktop.menu.selectAll": "Chọn tất cả", + "desktop.menu.toggleSidebar": "Bật/tắt thanh bên", + "desktop.menu.toggleTerminal": "Bật/tắt terminal", + "desktop.menu.toggleFileTree": "Bật/tắt cây tệp", + "desktop.menu.reload": "Tải lại", + "desktop.menu.toggleDeveloperTools": "Bật/tắt công cụ dành cho nhà phát triển", + "desktop.menu.actualSize": "Kích thước thực", + "desktop.menu.zoomIn": "Phóng to", + "desktop.menu.zoomOut": "Thu nhỏ", + "desktop.menu.toggleFullScreen": "Bật/tắt toàn màn hình", + "desktop.menu.back": "Quay lại", + "desktop.menu.forward": "Tiến tới", + "desktop.menu.previousSession": "Phiên trước", + "desktop.menu.nextSession": "Phiên tiếp theo", + "desktop.menu.previousProject": "Dự án trước", + "desktop.menu.nextProject": "Dự án tiếp theo", + "desktop.menu.minimize": "Thu nhỏ", + "desktop.menu.maximize": "Phóng to", + "desktop.menu.documentation": "Tài liệu OpenCode", + "desktop.menu.supportForum": "Diễn đàn hỗ trợ", + "desktop.menu.shareFeedback": "Chia sẻ phản hồi", + "desktop.menu.reportBug": "Báo cáo lỗi", + "desktop.menu.ariaLabel": "Menu OpenCode", + + "desktop.updater.dialog.checkFailed.message": "Không kiểm tra được bản cập nhật.", + "desktop.updater.dialog.checkFailed.title": "Lỗi cập nhật", + "desktop.updater.dialog.upToDate.message": "Bạn đang dùng phiên bản mới nhất.", + "desktop.updater.dialog.upToDate.title": "Không có bản cập nhật", + "desktop.updater.dialog.ready.message": "Đã tải xuống bản cập nhật {{version}}. Khởi động lại ngay?", + "desktop.updater.dialog.ready.title": "Bản cập nhật đã sẵn sàng", + "desktop.updater.dialog.restart": "Khởi động lại", + "desktop.updater.dialog.later": "Để sau", + + "desktop.recovery.action.relaunch": "Khởi chạy lại", + "desktop.recovery.action.exportLogs": "Xuất nhật ký", + "desktop.recovery.action.keepWaiting": "Tiếp tục chờ", + "desktop.recovery.action.quit": "Thoát", + "desktop.recovery.loadFailed": "Không tải được OpenCode", + "desktop.recovery.terminated": "Cửa sổ OpenCode đã bị đóng đột ngột", + "desktop.recovery.unresponsive": "OpenCode không phản hồi", + "desktop.recovery.unresponsive.detail": "Bạn có thể khởi chạy lại ứng dụng, mở nhật ký hoặc tiếp tục chờ.", + "desktop.recovery.loadFailed.detail": "Cửa sổ: {{window}}\nURL: {{url}}\nLỗi: {{code}} {{description}}", + "desktop.recovery.terminated.detail": "Cửa sổ: {{window}}\nLý do: {{reason}}\nMã: {{code}}", + "desktop.recovery.unknown": "", + + "desktop.dialog.chooseFolder": "Chọn thư mục", + "desktop.dialog.chooseFile": "Chọn tệp", + "desktop.dialog.saveFile": "Lưu tệp", + "desktop.dialog.files": "Tệp", + "desktop.server.local": "Máy chủ cục bộ", + + "desktop.wsl.error.windowsOnly": "WSL chỉ khả dụng trên Windows", + "desktop.wsl.error.unavailable": "WSL không khả dụng", + "desktop.wsl.error.listInstalled": "Không liệt kê được các bản phân phối WSL đã cài đặt", + "desktop.wsl.error.listOnline": "Không liệt kê được các bản phân phối WSL trực tuyến", + "desktop.wsl.error.executeDistro": "Không thể thực thi lệnh trong bản phân phối", + "desktop.wsl.error.installWsl": "Không cài đặt được WSL", + "desktop.wsl.error.installDistro": "Không cài đặt được bản phân phối: {{distro}}", + "desktop.wsl.error.installOpencode": "Không cài đặt được OpenCode", + "desktop.wsl.error.alreadyAdded": "{{distro}} đã được thêm", + "desktop.wsl.error.opencodeMissing": "opencode chưa được cài đặt trong bản phân phối này", + "desktop.wsl.error.opencodeCannotRun": "opencode đã được cài đặt nhưng không thể chạy", + "desktop.wsl.error.opencodeNotInstalled": "OpenCode chưa được cài đặt trong {{distro}}", + "desktop.wsl.error.updateVersion": + "Đã cập nhật OpenCode nhưng {{distro}} vẫn báo phiên bản {{installed}}; phiên bản dự kiến là {{expected}}", + "desktop.wsl.error.noVersion": "không có phiên bản", + "desktop.wsl.error.serverExited": "Máy chủ WSL đã thoát sau khi khởi động (mã={{code}} tín hiệu={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "Máy chủ WSL đã thoát trước khi sẵn sàng (mã={{code}} tín hiệu={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": + "Sidecar kiểm tra tình trạng của {{distro}} đã hết thời gian chờ sau {{timeout}}ms", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} đã hết thời gian chờ sau {{timeout}}ms", + "desktop.wsl.error.failedPort": "Không lấy được cổng", + + "desktop.picker.error.notSelected": "Không có tệp nào được chọn trong bộ chọn tệp", + "desktop.picker.error.sizeLimit": "Các tệp đính kèm đã chọn vượt quá giới hạn {{limit}} MB", + "command.category.suggested": "Đề xuất", "command.category.view": "Xem", "command.category.project": "Dự án", @@ -16,6 +115,7 @@ export const dict = { "command.category.permissions": "Quyền", "command.category.workspace": "Không gian làm việc", "command.category.settings": "Cài đặt", + "command.logs.export": "Xuất nhật ký", "theme.scheme.system": "Hệ thống", "theme.scheme.light": "Sáng", "theme.scheme.dark": "Tối", @@ -119,6 +219,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "Đăng nhập bằng Claude Pro/Max", "provider.connect.selectMethod": "Chọn phương thức đăng nhập cho {{provider}}.", "provider.connect.method.apiKey": "Khóa API", + "provider.connect.method.browser": "Trình duyệt", + "provider.connect.method.headless": "Không có giao diện người dùng", "provider.connect.status.inProgress": "Đang ủy quyền...", "provider.connect.status.waiting": "Đang chờ ủy quyền...", "provider.connect.status.failed": "Ủy quyền thất bại: {{error}}", @@ -150,6 +252,7 @@ export const dict = { "provider.connect.toast.connected.title": "{{provider}} đã kết nối", "provider.connect.toast.connected.description": "Các mô hình {{provider}} hiện đã sẵn sàng để sử dụng.", "provider.custom.title": "Nhà cung cấp tùy chỉnh", + "provider.custom.unavailable": "Nhà cung cấp tùy chỉnh không khả dụng trên máy chủ này", "provider.custom.description.prefix": "Định cấu hình nhà cung cấp tương thích OpenAI. Xem ", "provider.custom.description.link": "tài liệu cấu hình nhà cung cấp", "provider.custom.description.suffix": ".", @@ -409,6 +512,21 @@ export const dict = { "wsl.onboarding.desktopVersion": "Desktop {{version}}", "wsl.onboarding.versionMismatch": "Phiên bản đã cài đặt không khớp với phiên bản ứng dụng desktop.", "wsl.onboarding.adding": "Đang thêm...", + "help.tabs.toast.ariaLabel": "Giới thiệu về tab. Sắp xếp công việc và các phiên đang hoạt động bằng tab", + "help.tabs.toast.dismiss": "Bỏ qua thông tin về tab", + "help.tabs.title": "Giới thiệu về tab", + "help.tabs.description": "Sắp xếp công việc và các phiên đang hoạt động bằng tab", + "help.tabs.date": "14 tháng 7", + "help.tabs.introduction": "OpenCode Desktop nay được xây dựng xoay quanh các tab.", + "help.tabs.sessions": + "Bắt đầu phiên mới trong một tab hoặc mở phiên hiện có từ bất kỳ dự án nào của bạn. Hãy mở tab mới khi bắt đầu công việc mới và đóng tab khi hoàn tất.", + "help.tabs.organize": + "Việc mở một vài tab giúp bạn dễ dàng sắp xếp các phiên đang hoạt động. Hãy đổi tên tab thành tên dễ nhớ nếu bạn định giữ tab đó lâu dài.", + "help.tabs.home": + "Bạn sẽ tìm thấy tất cả các phiên và dự án trên màn hình Trang chủ mới. Khi bạn chọn một phiên, phiên đó sẽ mở trong một tab.", + "help.tabs.persistence": "Khi bạn mở lại ứng dụng, các tab vẫn mở.", + "help.tabs.worktrees": + "Thiết kế mới chưa hỗ trợ Git Worktrees, nhưng tính năng này sẽ sớm ra mắt. Vì vậy, nếu muốn tiếp tục sử dụng bố cục trước đó, bạn có thể chuyển đổi giữa các bố cục trong Cài đặt. Xin lưu ý rằng bố cục mới sẽ trở thành cố định sau vài tuần nữa.", "server.row.noUsername": "không có tên người dùng", "dialog.project.edit.title": "Chỉnh sửa dự án", "dialog.project.edit.name": "Tên", @@ -704,6 +822,11 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket đóng bất thường: {{code}}", "terminal.connectionLost.description": "Kết nối terminal bị gián đoạn. Điều này có thể xảy ra khi máy chủ khởi động lại.", + "terminal.connectTicket.csrfError": + "Vé kết nối PTY bị từ chối do kiểm tra nguồn gốc hoặc CSRF. Hãy kiểm tra cấu hình CORS của máy chủ.", + "terminal.connectTicket.statusError": "Vé kết nối PTY thất bại với trạng thái {{status}}", + "titlebar.update": "Cập nhật", + "titlebar.updateVersion": "Cập nhật {{version}}", "common.closeTab": "Đóng tab", "common.dismiss": "Bỏ qua", "common.moreCountSuffix": " (+{{count}} thêm)", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 46d28c37a09..1a725433afb 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -3,6 +3,104 @@ import { dict as en } from "./en" type Keys = keyof typeof en export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "文件", + "desktop.menu.edit": "编辑", + "desktop.menu.view": "视图", + "desktop.menu.go": "转到", + "desktop.menu.window": "窗口", + "desktop.menu.help": "帮助", + "desktop.menu.checkForUpdates": "检查更新...", + "desktop.menu.settings": "设置", + "desktop.menu.reloadWebview": "重新加载 Webview", + "desktop.menu.restart": "重启", + "desktop.menu.exportLogs": "导出日志...", + "desktop.menu.newSession": "新建会话", + "desktop.menu.openProject": "打开项目...", + "desktop.menu.newWindow": "新建窗口", + "desktop.menu.closeWindow": "关闭窗口", + "desktop.menu.undo": "撤销", + "desktop.menu.redo": "重做", + "desktop.menu.cut": "剪切", + "desktop.menu.copy": "复制", + "desktop.menu.paste": "粘贴", + "desktop.menu.delete": "删除", + "desktop.menu.selectAll": "全选", + "desktop.menu.toggleSidebar": "切换侧边栏", + "desktop.menu.toggleTerminal": "切换终端", + "desktop.menu.toggleFileTree": "切换文件树", + "desktop.menu.reload": "重新加载", + "desktop.menu.toggleDeveloperTools": "切换开发人员工具", + "desktop.menu.actualSize": "实际大小", + "desktop.menu.zoomIn": "放大", + "desktop.menu.zoomOut": "缩小", + "desktop.menu.toggleFullScreen": "切换全屏显示", + "desktop.menu.back": "后退", + "desktop.menu.forward": "前进", + "desktop.menu.previousSession": "上一个会话", + "desktop.menu.nextSession": "下一个会话", + "desktop.menu.previousProject": "上一个项目", + "desktop.menu.nextProject": "下一个项目", + "desktop.menu.minimize": "最小化", + "desktop.menu.maximize": "最大化", + "desktop.menu.documentation": "OpenCode 文档", + "desktop.menu.supportForum": "支持论坛", + "desktop.menu.shareFeedback": "提供反馈", + "desktop.menu.reportBug": "报告错误", + "desktop.menu.ariaLabel": "OpenCode 菜单", + + "desktop.updater.dialog.checkFailed.message": "检查更新失败。", + "desktop.updater.dialog.checkFailed.title": "更新错误", + "desktop.updater.dialog.upToDate.message": "你使用的是最新版本。", + "desktop.updater.dialog.upToDate.title": "无可用更新", + "desktop.updater.dialog.ready.message": "已下载更新 {{version}}。是否立即重启?", + "desktop.updater.dialog.ready.title": "更新已就绪", + "desktop.updater.dialog.restart": "重启", + "desktop.updater.dialog.later": "稍后", + + "desktop.recovery.action.relaunch": "重新启动", + "desktop.recovery.action.exportLogs": "导出日志", + "desktop.recovery.action.keepWaiting": "继续等待", + "desktop.recovery.action.quit": "退出", + "desktop.recovery.loadFailed": "OpenCode 加载失败", + "desktop.recovery.terminated": "OpenCode 窗口意外终止", + "desktop.recovery.unresponsive": "OpenCode 未响应", + "desktop.recovery.unresponsive.detail": "你可以重新启动应用、打开日志或继续等待。", + "desktop.recovery.loadFailed.detail": "窗口:{{window}}\nURL:{{url}}\n错误:{{code}} {{description}}", + "desktop.recovery.terminated.detail": "窗口:{{window}}\n原因:{{reason}}\n代码:{{code}}", + "desktop.recovery.unknown": "<未知>", + + "desktop.dialog.chooseFolder": "选择文件夹", + "desktop.dialog.chooseFile": "选择文件", + "desktop.dialog.saveFile": "保存文件", + "desktop.dialog.files": "文件", + "desktop.server.local": "本地服务器", + + "desktop.wsl.error.windowsOnly": "WSL 仅在 Windows 上可用", + "desktop.wsl.error.unavailable": "WSL 不可用", + "desktop.wsl.error.listInstalled": "无法列出已安装的 WSL 发行版", + "desktop.wsl.error.listOnline": "无法列出在线 WSL 发行版", + "desktop.wsl.error.executeDistro": "无法在发行版中执行命令", + "desktop.wsl.error.installWsl": "WSL 安装失败", + "desktop.wsl.error.installDistro": "无法安装发行版:{{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode 安装失败", + "desktop.wsl.error.alreadyAdded": "已添加 {{distro}}", + "desktop.wsl.error.opencodeMissing": "此发行版中未安装 opencode", + "desktop.wsl.error.opencodeCannotRun": "已安装 opencode,但无法运行", + "desktop.wsl.error.opencodeNotInstalled": "{{distro}} 中未安装 OpenCode", + "desktop.wsl.error.updateVersion": + "OpenCode 更新已完成,但 {{distro}} 仍报告版本 {{installed}};预期版本为 {{expected}}", + "desktop.wsl.error.noVersion": "无版本信息", + "desktop.wsl.error.serverExited": "WSL 服务器启动后退出(code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL 服务器在进入正常状态前退出(code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": "{{distro}} 的 Sidecar 运行状况检查在 {{timeout}}ms 后超时", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} 在 {{timeout}}ms 后超时", + "desktop.wsl.error.failedPort": "无法获取端口", + + "desktop.picker.error.notSelected": "文件选择器未选择文件", + "desktop.picker.error.sizeLimit": "所选附件超过 {{limit}} MB 的限制", + "command.category.suggested": "建议", "command.category.view": "视图", "command.category.project": "项目", @@ -20,6 +118,7 @@ export const dict = { "command.category.permissions": "权限", "command.category.workspace": "工作区", "command.category.settings": "设置", + "command.logs.export": "导出日志", "theme.scheme.system": "系统", "theme.scheme.light": "浅色", @@ -157,6 +256,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "使用 Claude Pro/Max 登录", "provider.connect.selectMethod": "选择 {{provider}} 的登录方式。", "provider.connect.method.apiKey": "API 密钥", + "provider.connect.method.browser": "浏览器", + "provider.connect.method.headless": "无头模式", "provider.connect.status.inProgress": "正在授权...", "provider.connect.status.waiting": "等待授权...", "provider.connect.status.failed": "授权失败:{{error}}", @@ -185,6 +286,7 @@ export const dict = { "provider.connect.toast.connected.description": "现在可以使用 {{provider}} 模型了。", "provider.custom.title": "自定义提供商", + "provider.custom.unavailable": "此服务器上无法使用自定义提供商", "provider.custom.description.prefix": "配置与 OpenAI 兼容的提供商。请查看", "provider.custom.description.link": "提供商配置文档", "provider.custom.description.suffix": "。", @@ -448,6 +550,21 @@ export const dict = { "wsl.onboarding.versionMismatch": "已安装版本与桌面应用版本不一致。", "wsl.onboarding.adding": "正在添加...", + "help.tabs.toast.ariaLabel": "标签页功能介绍。使用标签页整理工作和进行中的会话", + "help.tabs.toast.dismiss": "关闭标签页功能介绍", + "help.tabs.title": "标签页功能介绍", + "help.tabs.description": "使用标签页整理工作和进行中的会话", + "help.tabs.date": "7 月 14 日", + "help.tabs.introduction": "OpenCode Desktop 现已采用以标签页为核心的设计。", + "help.tabs.sessions": + "在标签页中开始新会话,或从任意项目打开现有会话。开始处理新任务时打开新标签页,完成后将其关闭。", + "help.tabs.organize": + "保持打开几个标签页,可更轻松地整理进行中的会话。如果打算保留某个标签页,请为其指定一个便于记忆的名称。", + "help.tabs.home": "你可以在新的主页中找到所有会话和项目。选择会话即可在标签页中将其打开。", + "help.tabs.persistence": "重新打开应用时,标签页仍会保持打开。", + "help.tabs.worktrees": + "新设计暂不支持 Git Worktrees,此功能即将推出。如果希望继续使用之前的布局,可在“设置”中切换布局。请注意,新布局将在几周后永久启用。", + "dialog.project.edit.title": "编辑项目", "dialog.project.edit.name": "名称", "dialog.project.edit.icon": "图标", @@ -710,6 +827,11 @@ export const dict = { "terminal.close": "关闭终端", "terminal.connectionLost.title": "连接已丢失", "terminal.connectionLost.description": "终端连接已中断。这可能发生在服务器重启时。", + "terminal.connectTicket.csrfError": "PTY 连接票证因来源或 CSRF 检查被拒绝。请检查服务器 CORS 配置。", + "terminal.connectTicket.statusError": "PTY 连接票证失败,状态为 {{status}}", + + "titlebar.update": "更新", + "titlebar.updateVersion": "更新 {{version}}", "common.closeTab": "关闭标签页", "common.dismiss": "忽略", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index f57ebc823a8..b1b85acd244 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -3,6 +3,104 @@ import { dict as en } from "./en" type Keys = keyof typeof en export const dict = { + "desktop.menu.app": "OpenCode", + "desktop.menu.file": "檔案", + "desktop.menu.edit": "編輯", + "desktop.menu.view": "檢視", + "desktop.menu.go": "前往", + "desktop.menu.window": "視窗", + "desktop.menu.help": "說明", + "desktop.menu.checkForUpdates": "檢查更新...", + "desktop.menu.settings": "設定", + "desktop.menu.reloadWebview": "重新載入 Webview", + "desktop.menu.restart": "重新啟動", + "desktop.menu.exportLogs": "匯出記錄檔...", + "desktop.menu.newSession": "新增工作階段", + "desktop.menu.openProject": "開啟專案...", + "desktop.menu.newWindow": "新增視窗", + "desktop.menu.closeWindow": "關閉視窗", + "desktop.menu.undo": "復原", + "desktop.menu.redo": "重做", + "desktop.menu.cut": "剪下", + "desktop.menu.copy": "複製", + "desktop.menu.paste": "貼上", + "desktop.menu.delete": "刪除", + "desktop.menu.selectAll": "全選", + "desktop.menu.toggleSidebar": "切換側邊欄", + "desktop.menu.toggleTerminal": "切換終端機", + "desktop.menu.toggleFileTree": "切換檔案樹", + "desktop.menu.reload": "重新載入", + "desktop.menu.toggleDeveloperTools": "切換開發人員工具", + "desktop.menu.actualSize": "實際大小", + "desktop.menu.zoomIn": "放大", + "desktop.menu.zoomOut": "縮小", + "desktop.menu.toggleFullScreen": "切換全螢幕", + "desktop.menu.back": "上一頁", + "desktop.menu.forward": "下一頁", + "desktop.menu.previousSession": "上一個工作階段", + "desktop.menu.nextSession": "下一個工作階段", + "desktop.menu.previousProject": "上一個專案", + "desktop.menu.nextProject": "下一個專案", + "desktop.menu.minimize": "最小化", + "desktop.menu.maximize": "最大化", + "desktop.menu.documentation": "OpenCode 文件", + "desktop.menu.supportForum": "支援論壇", + "desktop.menu.shareFeedback": "提供意見回饋", + "desktop.menu.reportBug": "回報錯誤", + "desktop.menu.ariaLabel": "OpenCode 選單", + + "desktop.updater.dialog.checkFailed.message": "檢查更新失敗。", + "desktop.updater.dialog.checkFailed.title": "更新錯誤", + "desktop.updater.dialog.upToDate.message": "目前已是最新版本。", + "desktop.updater.dialog.upToDate.title": "沒有更新", + "desktop.updater.dialog.ready.message": "已下載更新 {{version}}。要立即重新啟動嗎?", + "desktop.updater.dialog.ready.title": "更新已就緒", + "desktop.updater.dialog.restart": "重新啟動", + "desktop.updater.dialog.later": "稍後", + + "desktop.recovery.action.relaunch": "重新啟動應用程式", + "desktop.recovery.action.exportLogs": "匯出記錄檔", + "desktop.recovery.action.keepWaiting": "繼續等待", + "desktop.recovery.action.quit": "結束", + "desktop.recovery.loadFailed": "OpenCode 載入失敗", + "desktop.recovery.terminated": "OpenCode 視窗意外終止", + "desktop.recovery.unresponsive": "OpenCode 沒有回應", + "desktop.recovery.unresponsive.detail": "你可以重新啟動應用程式、開啟記錄檔,或繼續等待。", + "desktop.recovery.loadFailed.detail": "視窗:{{window}}\nURL:{{url}}\n錯誤:{{code}} {{description}}", + "desktop.recovery.terminated.detail": "視窗:{{window}}\n原因:{{reason}}\n代碼:{{code}}", + "desktop.recovery.unknown": "<未知>", + + "desktop.dialog.chooseFolder": "選擇資料夾", + "desktop.dialog.chooseFile": "選擇檔案", + "desktop.dialog.saveFile": "儲存檔案", + "desktop.dialog.files": "檔案", + "desktop.server.local": "本機伺服器", + + "desktop.wsl.error.windowsOnly": "WSL 僅適用於 Windows", + "desktop.wsl.error.unavailable": "WSL 無法使用", + "desktop.wsl.error.listInstalled": "無法列出已安裝的 WSL 發行版", + "desktop.wsl.error.listOnline": "無法列出線上 WSL 發行版", + "desktop.wsl.error.executeDistro": "無法在發行版中執行命令", + "desktop.wsl.error.installWsl": "WSL 安裝失敗", + "desktop.wsl.error.installDistro": "無法安裝發行版:{{distro}}", + "desktop.wsl.error.installOpencode": "OpenCode 安裝失敗", + "desktop.wsl.error.alreadyAdded": "已新增 {{distro}}", + "desktop.wsl.error.opencodeMissing": "此發行版中未安裝 opencode", + "desktop.wsl.error.opencodeCannotRun": "已安裝 opencode,但無法執行", + "desktop.wsl.error.opencodeNotInstalled": "{{distro}} 中未安裝 OpenCode", + "desktop.wsl.error.updateVersion": + "OpenCode 更新完成,但 {{distro}} 回報的版本仍為 {{installed}};預期版本為 {{expected}}", + "desktop.wsl.error.noVersion": "無版本資訊", + "desktop.wsl.error.serverExited": "WSL 伺服器在啟動後結束 (code={{code}} signal={{signal}})", + "desktop.wsl.error.serverExitedBeforeHealthy": + "WSL 伺服器在健全狀態確認前結束 (code={{code}} signal={{signal}}){{output}}", + "desktop.wsl.error.healthTimeout": "{{distro}} 的 Sidecar 健全狀態檢查在 {{timeout}}ms 後逾時", + "desktop.wsl.error.commandTimeout": "{{command}} {{args}} 在 {{timeout}}ms 後逾時", + "desktop.wsl.error.failedPort": "無法取得連接埠", + + "desktop.picker.error.notSelected": "檔案選擇器未選取檔案", + "desktop.picker.error.sizeLimit": "所選附件超過 {{limit}} MB 的限制", + "command.category.suggested": "建議", "command.category.view": "檢視", "command.category.project": "專案", @@ -21,6 +119,7 @@ export const dict = { "command.category.workspace": "工作區", "command.category.settings": "設定", + "command.logs.export": "匯出記錄檔", "theme.scheme.system": "系統", "theme.scheme.light": "淺色", "theme.scheme.dark": "深色", @@ -135,6 +234,8 @@ export const dict = { "provider.connect.title.anthropicProMax": "使用 Claude Pro/Max 登入", "provider.connect.selectMethod": "選擇 {{provider}} 的登入方式。", "provider.connect.method.apiKey": "API 金鑰", + "provider.connect.method.browser": "瀏覽器", + "provider.connect.method.headless": "無頭模式", "provider.connect.status.inProgress": "正在授權...", "provider.connect.status.waiting": "等待授權...", "provider.connect.status.failed": "授權失敗: {{error}}", @@ -164,6 +265,7 @@ export const dict = { "provider.connect.toast.connected.description": "現在可以使用 {{provider}} 模型了。", "provider.custom.title": "自訂提供者", + "provider.custom.unavailable": "此伺服器無法使用自訂提供者", "provider.custom.description.prefix": "設定與 OpenAI 相容的提供者。請參閱", "provider.custom.description.link": "提供者設定文件", "provider.custom.description.suffix": "。", @@ -429,6 +531,21 @@ export const dict = { "wsl.onboarding.versionMismatch": "已安裝版本與桌面應用程式版本不符。", "wsl.onboarding.adding": "新增中...", + "help.tabs.toast.ariaLabel": "分頁功能介紹。使用分頁整理工作和進行中的工作階段", + "help.tabs.toast.dismiss": "關閉分頁功能資訊", + "help.tabs.title": "分頁功能介紹", + "help.tabs.description": "使用分頁整理工作和進行中的工作階段", + "help.tabs.date": "7 月 14 日", + "help.tabs.introduction": "OpenCode Desktop 現在以分頁為核心。", + "help.tabs.sessions": + "在分頁中開始新的工作階段,或從任一專案開啟現有的工作階段。開始新工作時請開啟新分頁,完成後再將其關閉。", + "help.tabs.organize": + "同時開啟幾個分頁,可讓進行中的工作階段更容易整理。如果打算保留分頁,請將其重新命名為容易記住的名稱。", + "help.tabs.home": "你可以在新的首頁找到所有工作階段和專案。選取工作階段即可在分頁中開啟。", + "help.tabs.persistence": "重新開啟應用程式時,分頁仍會保持開啟。", + "help.tabs.worktrees": + "新版設計尚未支援 Git Worktrees,此功能即將推出。如果你偏好繼續使用先前的版面,可以在設定中切換版面。請留意,新版面將在幾週後成為永久版面。", + "dialog.project.edit.title": "編輯專案", "dialog.project.edit.name": "名稱", "dialog.project.edit.icon": "圖示", @@ -708,6 +825,11 @@ export const dict = { "terminal.connectionLost.title": "連線中斷", "terminal.connectionLost.description": "終端機連線已中斷。這可能會在伺服器重新啟動時發生。", + "terminal.connectTicket.csrfError": "PTY 連線票證因來源或 CSRF 檢查而遭拒。請檢查伺服器 CORS 設定。", + "terminal.connectTicket.statusError": "PTY 連線票證失敗,狀態為 {{status}}", + + "titlebar.update": "更新", + "titlebar.updateVersion": "更新 {{version}}", "common.closeTab": "關閉分頁", "common.dismiss": "忽略", "common.requestFailed": "要求失敗", diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts index 8d36086d9fc..0e0ed0286f8 100644 --- a/packages/app/src/index.ts +++ b/packages/app/src/index.ts @@ -8,7 +8,7 @@ export { useTabs } from "./context/tabs" export { useProviders } from "./hooks/use-providers" export { ACCEPTED_FILE_EXTENSIONS, ACCEPTED_FILE_TYPES, filePickerFilters } from "./constants/file-picker" export { useCommand } from "./context/command" -export { loadLocaleDict, normalizeLocale, type Locale } from "./context/language" +export { loadLocaleDict, normalizeLocale, type Locale, useLanguage } from "./context/language" export { useWslServers } from "./wsl/context" export { type DisplayBackend, type FatalRendererErrorLog, type Platform, PlatformProvider } from "./context/platform" export { type UpdaterPlatform, type UpdaterState } from "./updater" diff --git a/packages/app/src/pages/session/composer/session-question-dock.tsx b/packages/app/src/pages/session/composer/session-question-dock.tsx index 941424e247c..1fdcb11ccae 100644 --- a/packages/app/src/pages/session/composer/session-question-dock.tsx +++ b/packages/app/src/pages/session/composer/session-question-dock.tsx @@ -472,7 +472,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit data-answered={answered(i())} disabled={sending()} onClick={() => jump(i())} - aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`} + aria-label={language.t("ui.tool.questions.numbered", { number: i() + 1 })} /> )} diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts index a7742c399e5..ebecde50975 100644 --- a/packages/app/src/utils/persist.ts +++ b/packages/app/src/utils/persist.ts @@ -568,8 +568,9 @@ export function removePersisted( export function persisted( target: string | PersistTarget, store: [Store, SetStoreFunction], + platformOverride?: Platform, ): PersistedWithReady { - const platform = usePlatform() + const platform = platformOverride ?? usePlatform() const config = resolveTarget(typeof target === "string" ? { key: target } : target, platform) const defaults = snapshot(store[0]) diff --git a/packages/app/src/wsl/settings-model.test.ts b/packages/app/src/wsl/settings-model.test.ts index 812852ff10c..c1bbe1db57f 100644 --- a/packages/app/src/wsl/settings-model.test.ts +++ b/packages/app/src/wsl/settings-model.test.ts @@ -48,7 +48,7 @@ describe("WSL server settings presentation", () => { matchesDesktop: null, error: null, }), - ).toBe("Install OpenCode") + ).toBe("wsl.onboarding.installOpencode") expect( wslOpencodeAction({ distro: "Debian", @@ -58,7 +58,7 @@ describe("WSL server settings presentation", () => { matchesDesktop: false, error: null, }), - ).toBe("Update OpenCode") + ).toBe("wsl.onboarding.updateOpencode") expect( wslOpencodeAction({ distro: "Debian", diff --git a/packages/app/src/wsl/settings-model.ts b/packages/app/src/wsl/settings-model.ts index 8a172e0574e..1a653cddb23 100644 --- a/packages/app/src/wsl/settings-model.ts +++ b/packages/app/src/wsl/settings-model.ts @@ -53,8 +53,8 @@ export const wslRuntimeRetryable = (runtime: WslServerRuntime) => export function wslOpencodeAction(check?: WslOpencodeCheck) { if (!check) return - if (!check.resolvedPath) return "Install OpenCode" - if (check.matchesDesktop === false) return "Update OpenCode" + if (!check.resolvedPath) return "wsl.onboarding.installOpencode" + if (check.matchesDesktop === false) return "wsl.onboarding.updateOpencode" } export function wslDistroReady(state: WslServersState | undefined, name: string) { diff --git a/packages/app/src/wsl/settings.tsx b/packages/app/src/wsl/settings.tsx index 82616c6dcf7..901f2476686 100644 --- a/packages/app/src/wsl/settings.tsx +++ b/packages/app/src/wsl/settings.tsx @@ -124,7 +124,7 @@ export function WslServerSettings(props: { disabled={busy() || request.isPending} onClick={() => api && request.mutate(() => api.installOpencode(item.config.distro))} > - {busy() ? language.t("wsl.server.updating") : label()} + {busy() ? language.t("wsl.server.updating") : language.t(label())} )} diff --git a/packages/app/test-browser/prompt-persistence.test.ts b/packages/app/test-browser/prompt-persistence.test.ts index f3d8f8ae4fb..7c3d28226f4 100644 --- a/packages/app/test-browser/prompt-persistence.test.ts +++ b/packages/app/test-browser/prompt-persistence.test.ts @@ -1,10 +1,11 @@ -import { beforeAll, describe, expect, mock, test } from "bun:test" +import { describe, expect, test } from "bun:test" import type { AsyncStorage } from "@solid-primitives/storage" import { createEffect, createRoot } from "solid-js" +import type { Platform } from "@/context/platform" +import { createPromptReady, createPromptSession } from "@/context/prompt-state" import { ServerScope } from "@/utils/server-scope" import { createDraftStore } from "@/utils/draft-store" -let Prompt: typeof import("@/context/prompt") let read: ((value: string | null) => void) | undefined const storage: AsyncStorage = { @@ -17,32 +18,25 @@ const storage: AsyncStorage = { length: Promise.resolve(0), } -beforeAll(async () => { - mock.module("@solidjs/router", () => ({ - useParams: () => ({}), - useSearchParams: () => [{}], - useLocation: () => ({ pathname: "", query: {} }), - useNavigate: () => () => undefined, - })) - mock.module("@opencode-ai/ui/context", () => ({ - createSimpleContext: () => ({ - use: () => undefined, - provider: () => undefined, - }), - })) - mock.module("@/context/platform", () => ({ - usePlatform: () => ({ platform: "desktop", storage: () => storage, draftStore: storage }), - })) - - Prompt = await import("@/context/prompt") -}) +const platform: Platform = { + platform: "web", + openExternal: () => undefined, + restart: async () => undefined, + notify: async () => undefined, + draftStore: { + ...storage, + putBlob: async () => { + throw new Error("putBlob is not used by this test") + }, + }, +} describe("prompt persistence", () => { test("waits for an async draft to hydrate before reporting ready", async () => { await new Promise((resolve, reject) => { createRoot((dispose) => { - const session = Prompt.createPromptSession(ServerScope.local, { draftID: "draft-async" }) - const ready = Prompt.createPromptReady(() => session) + const session = createPromptSession(ServerScope.local, { draftID: "draft-async" }, undefined, platform) + const ready = createPromptReady(() => session) expect(ready()).toBe(false) expect(session.current()[0]).toMatchObject({ type: "text", content: "" }) diff --git a/packages/desktop/AGENTS.md b/packages/desktop/AGENTS.md index 7805ea835f5..72a129af0f0 100644 --- a/packages/desktop/AGENTS.md +++ b/packages/desktop/AGENTS.md @@ -2,3 +2,8 @@ - Renderer process should only call `window.api` from `src/preload`. - Main process should register IPC handlers in `src/main/ipc.ts`. +- NEVER hardcode user-visible English strings in production code. ALWAYS use an i18n key for native menus, picker titles, dialogs, buttons, accessible labels, and displayed errors. +- When migrating existing copy to i18n, preserve the English text byte-for-byte unless the task explicitly requests a copy change. +- NEVER change existing English text or English keys to facilitate translation. English is intentional, designer-written source copy; adapt locale-specific translations and i18n mechanics around it. +- Do not translate from model knowledge alone. Verify terminology and grammar with Unicode CLDR locale/plural data, Microsoft Localization Style Guides and terminology, Apple localization/style guidance and localized platform UI, Mozilla localization style guides, Mozilla Pontoon, and the Firefox localization corpus at `github.com/mozilla-l10n/firefox-l10n`. +- Also use the relevant language authority or official dictionary for the locale (for example RAE/Fundéu, FranceTerme, Duden, TDK, Kotus/Kielitoimiston sanakirja, Språkrådet/Bokmålsordboka, Rada Języka Polskiego/PWN, the Russian and Arabic language academies, the Ukrainian Orthography, Taiwan MOE dictionaries, or the Royal Society of Thailand). Treat the English dictionary as the semantic source of truth and preserve placeholders, code identifiers, product names, and keyboard labels. diff --git a/packages/desktop/src/main/attachment-picker.ts b/packages/desktop/src/main/attachment-picker.ts index 52584d2c3b5..f2080cfe4d5 100644 --- a/packages/desktop/src/main/attachment-picker.ts +++ b/packages/desktop/src/main/attachment-picker.ts @@ -1,5 +1,6 @@ import { randomUUID } from "node:crypto" import { open } from "node:fs/promises" +import { nativeT } from "./native-translations" export const MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024 @@ -18,7 +19,7 @@ export function createPickedFileAuthorizations( async read(sender: number, token: string, path: string) { const selection = selections.get(token) if (selection?.sender !== sender || !selection.paths.delete(path)) - throw new Error("File was not selected by the picker") + throw new Error(nativeT("desktop.picker.error.notSelected")) const bytes = await read(path, selection.remaining) selection.remaining -= bytes.byteLength if (selection.paths.size === 0) selections.delete(token) @@ -33,7 +34,7 @@ export function createPickedFileAuthorizations( export function assertAttachmentBudget(files: { size: number }[]) { const total = files.reduce((sum, file) => sum + file.size, 0) if (total <= MAX_ATTACHMENT_BYTES) return - throw new Error(`Selected attachments exceed the ${MAX_ATTACHMENT_BYTES / 1024 / 1024} MB limit`) + throw new Error(nativeT("desktop.picker.error.sizeLimit", { limit: MAX_ATTACHMENT_BYTES / 1024 / 1024 })) } export async function readAttachment(filePath: string, maxBytes = MAX_ATTACHMENT_BYTES) { @@ -41,7 +42,7 @@ export async function readAttachment(filePath: string, maxBytes = MAX_ATTACHMENT try { const info = await file.stat() if (info.size > maxBytes) - throw new Error(`Selected attachments exceed the ${MAX_ATTACHMENT_BYTES / 1024 / 1024} MB limit`) + throw new Error(nativeT("desktop.picker.error.sizeLimit", { limit: MAX_ATTACHMENT_BYTES / 1024 / 1024 })) const bytes = Buffer.allocUnsafe(info.size) let offset = 0 while (offset < info.size) { diff --git a/packages/desktop/src/main/index.ts b/packages/desktop/src/main/index.ts index e920b60257e..7fb56391a6f 100644 --- a/packages/desktop/src/main/index.ts +++ b/packages/desktop/src/main/index.ts @@ -48,6 +48,7 @@ import { spawnWslSidecar } from "./wsl/sidecar" import { migrate } from "./migrate" import { cleanupStoreFiles } from "./store-cleanup" import { startBackgroundCli } from "./background-cli" +import { setNativeTranslations } from "./native-translations" const APP_NAMES: Record = { dev: "OpenCode Dev", @@ -271,6 +272,14 @@ const main = Effect.gen(function* () { registerRendererProtocol() setDockIcon() const updater = setupAutoUpdater(stopSidecars) + const menuDeps = { + trigger: (id: string) => { + const win = getLastFocusedWindow() + if (win) sendMenuCommand(win, id) + }, + checkForUpdates: () => void showUpdaterDialog(updater, true), + relaunch, + } registerIpcHandlers({ killSidecar: () => killSidecar(), relaunch, @@ -298,6 +307,9 @@ const main = Effect.gen(function* () { setBackgroundColor: (color) => setBackgroundColor(color), exportDebugLogs: () => exportDebugLogs(), recordFatalRendererError: (error) => writeLog("renderer", "fatal renderer error", { ...error }, "error"), + setNativeTranslations: (bundle) => { + if (setNativeTranslations(bundle)) createMenu(menuDeps) + }, }) registerWslIpcHandlers(wslServers) void updater.start() @@ -397,20 +409,7 @@ const main = Effect.gen(function* () { yield* Fiber.await(loadingTask) const windows = restoreMainWindows() - if (windows.length) { - createMenu({ - trigger: (id) => { - const win = getLastFocusedWindow() - if (win) sendMenuCommand(win, id) - }, - checkForUpdates: () => { - void showUpdaterDialog(updater, true) - }, - relaunch: () => { - relaunch() - }, - }) - } + if (windows.length) createMenu(menuDeps) }) Effect.runFork(main) diff --git a/packages/desktop/src/main/ipc.ts b/packages/desktop/src/main/ipc.ts index bd7a3a75540..84531910f77 100644 --- a/packages/desktop/src/main/ipc.ts +++ b/packages/desktop/src/main/ipc.ts @@ -4,6 +4,7 @@ import { basename, join } from "node:path" import { app, BrowserWindow, clipboard, dialog, ipcMain, shell } from "electron" import type { IpcMainEvent, IpcMainInvokeEvent } from "electron" import type { DesktopMenuAction } from "@opencode-ai/app/desktop-menu" +import { parseDesktopNativeBundle, type DesktopNativeBundle } from "@opencode-ai/app/i18n/desktop-native" import type { FatalRendererError, ServerReadyData, TitlebarTheme } from "../preload/types" import { runDesktopMenuAction } from "./desktop-menu-actions" @@ -22,10 +23,11 @@ import { import type { UpdaterController } from "./updater-controller" import { createUpdaterSubscriptions } from "./updater-subscriptions" import { createDesktopDraftStore } from "./draft-store" +import { nativeT } from "./native-translations" const pickerFilters = (ext?: string[]) => { if (!ext || ext.length === 0) return undefined - return [{ name: "Files", extensions: ext }] + return [{ name: nativeT("desktop.dialog.files"), extensions: ext }] } const pickedFiles = createPickedFileAuthorizations() @@ -49,6 +51,7 @@ type Deps = { setBackgroundColor: (color: string) => void exportDebugLogs: () => Promise recordFatalRendererError: (error: FatalRendererError) => Promise | void + setNativeTranslations: (bundle: DesktopNativeBundle) => void } export function registerIpcHandlers(deps: Deps) { @@ -99,6 +102,20 @@ export function registerIpcHandlers(deps: Deps) { ipcMain.handle("record-fatal-renderer-error", (_event: IpcMainInvokeEvent, error: FatalRendererError) => deps.recordFatalRendererError(error), ) + ipcMain.handle("set-native-translations", (event: IpcMainInvokeEvent, value: unknown) => { + const win = BrowserWindow.fromWebContents(event.sender) + if ( + !win || + win.isDestroyed() || + win.webContents !== event.sender || + event.senderFrame !== event.sender.mainFrame + ) { + throw new Error("Invalid native translation sender") + } + const bundle = parseDesktopNativeBundle(value) + if (!bundle) throw new Error("Invalid native translation bundle") + deps.setNativeTranslations(bundle) + }) ipcMain.handle("store-get", (_event: IpcMainInvokeEvent, name: string, key: string) => { try { const store = getStore(name) @@ -142,7 +159,7 @@ export function registerIpcHandlers(deps: Deps) { async (_event: IpcMainInvokeEvent, opts?: { multiple?: boolean; title?: string; defaultPath?: string }) => { const result = await dialog.showOpenDialog({ properties: ["openDirectory", ...(opts?.multiple ? ["multiSelections" as const] : []), "createDirectory"], - title: opts?.title ?? "Choose a folder", + title: opts?.title ?? nativeT("desktop.dialog.chooseFolder"), defaultPath: opts?.defaultPath, }) if (result.canceled) return null @@ -158,7 +175,7 @@ export function registerIpcHandlers(deps: Deps) { ) => { const result = await dialog.showOpenDialog({ properties: ["openFile", ...(opts?.multiple ? ["multiSelections" as const] : [])], - title: opts?.title ?? "Choose a file", + title: opts?.title ?? nativeT("desktop.dialog.chooseFile"), defaultPath: opts?.defaultPath, filters: pickerFilters(opts?.extensions), }) @@ -188,7 +205,7 @@ export function registerIpcHandlers(deps: Deps) { "save-file-picker", async (_event: IpcMainInvokeEvent, opts?: { title?: string; defaultPath?: string }) => { const result = await dialog.showSaveDialog({ - title: opts?.title ?? "Save file", + title: opts?.title ?? nativeT("desktop.dialog.saveFile"), defaultPath: opts?.defaultPath, }) if (result.canceled) return null diff --git a/packages/desktop/src/main/menu.ts b/packages/desktop/src/main/menu.ts index 757583ceba4..ac2da7e547c 100644 --- a/packages/desktop/src/main/menu.ts +++ b/packages/desktop/src/main/menu.ts @@ -10,6 +10,7 @@ import { import { UPDATER_ENABLED } from "./constants" import { runDesktopMenuAction } from "./desktop-menu-actions" import { openExternalURL } from "./windows" +import { nativeT } from "./native-translations" type Deps = { trigger: (id: string) => void @@ -21,9 +22,9 @@ export function createMenu(deps: Deps) { if (process.platform !== "darwin") return const template = DESKTOP_MENU.filter((menu) => desktopMenuVisible(menu, "macos")).map((menu) => { - if (menu.role) return { role: nativeRole(menu.role) } + if (menu.role) return { role: nativeRole(menu.role), label: nativeT(menu.labelKey) } return { - label: menu.label, + label: nativeT(menu.labelKey), submenu: menu.items ?.filter((entry) => desktopMenuVisible(entry, "macos")) .map((entry) => nativeItem(entry, deps)), @@ -35,10 +36,10 @@ export function createMenu(deps: Deps) { function nativeItem(entry: DesktopMenuEntry, deps: Deps): MenuItemConstructorOptions { if (entry.type === "separator") return { type: "separator" } - if (entry.role) return { role: nativeRole(entry.role) } + if (entry.role) return { role: nativeRole(entry.role), label: entry.labelKey ? nativeT(entry.labelKey) : undefined } const item: MenuItemConstructorOptions = { - label: entry.label, + label: entry.labelKey ? nativeT(entry.labelKey) : undefined, accelerator: entry.accelerator?.macos, enabled: entry.enabled === "updater" ? UPDATER_ENABLED : undefined, } diff --git a/packages/desktop/src/main/native-translations.ts b/packages/desktop/src/main/native-translations.ts new file mode 100644 index 00000000000..80e5caf412e --- /dev/null +++ b/packages/desktop/src/main/native-translations.ts @@ -0,0 +1,24 @@ +import { + DESKTOP_NATIVE_ENGLISH, + DESKTOP_NATIVE_KEYS, + formatDesktopNativeMessage, + type DesktopNativeBundle, + type DesktopNativeKey, +} from "@opencode-ai/app/i18n/desktop-native" + +let bundle: DesktopNativeBundle = { locale: "en", messages: { ...DESKTOP_NATIVE_ENGLISH } } + +export function setNativeTranslations(next: DesktopNativeBundle) { + if ( + next.locale === bundle.locale && + DESKTOP_NATIVE_KEYS.every((key) => next.messages[key] === bundle.messages[key]) + ) { + return false + } + bundle = next + return true +} + +export function nativeT(key: DesktopNativeKey, params?: Record) { + return formatDesktopNativeMessage(bundle.messages[key], params) +} diff --git a/packages/desktop/src/main/updater.ts b/packages/desktop/src/main/updater.ts index cd483ed72fe..08350917c1b 100644 --- a/packages/desktop/src/main/updater.ts +++ b/packages/desktop/src/main/updater.ts @@ -5,6 +5,7 @@ import { createUpdaterController, type UpdaterReadyRecord } from "./updater-cont import { getLogger } from "./logging" import { getStore } from "./store" import { setAppQuitting } from "./windows" +import { nativeT } from "./native-translations" const { autoUpdater } = pkg const key = "ready" @@ -63,21 +64,29 @@ export async function showUpdaterDialog(controller: ReturnType { - if (button === "Export Logs") { + type RecoveryAction = "relaunch" | "export-logs" | "keep-waiting" | "quit" + const handle = async (action: RecoveryAction | undefined, wait: boolean) => { + if (action === "export-logs") { const sampling = sampler.stopAndFlush() await exportDebugLogs().catch((error) => writeLog("main", "failed to export debug logs", { error }, "error")) if (wait && sampling) sampler.start() return true } - if (button === "Relaunch") { + if (action === "relaunch") { sampler.stopAndFlush() relaunchHandler() return false } - if (button === "Quit") { + if (action === "quit") { sampler.stopAndFlush() app.quit() } @@ -369,16 +371,26 @@ function wireWindowRecovery(win: BrowserWindow, name: string) { showing = true try { while (!win.isDestroyed()) { - const buttons = wait ? ["Relaunch", "Export Logs", "Keep Waiting"] : ["Relaunch", "Export Logs", "Quit"] + const actions: { id: RecoveryAction; label: string }[] = wait + ? [ + { id: "relaunch", label: nativeT("desktop.recovery.action.relaunch") }, + { id: "export-logs", label: nativeT("desktop.recovery.action.exportLogs") }, + { id: "keep-waiting", label: nativeT("desktop.recovery.action.keepWaiting") }, + ] + : [ + { id: "relaunch", label: nativeT("desktop.recovery.action.relaunch") }, + { id: "export-logs", label: nativeT("desktop.recovery.action.exportLogs") }, + { id: "quit", label: nativeT("desktop.recovery.action.quit") }, + ] const result = await dialog.showMessageBox(win, { type: "warning", - buttons, + buttons: actions.map((action) => action.label), defaultId: 0, cancelId: 2, message, detail, }) - if (await handle(buttons[result.response], wait)) continue + if (await handle(actions[result.response]?.id, wait)) continue return } } finally { @@ -410,8 +422,13 @@ function wireWindowRecovery(win: BrowserWindow, name: string) { if (!isMainFrame || errorCode === -3) return void show( - "OpenCode failed to load", - [`Window: ${name}`, `URL: ${validatedURL}`, `Error: ${errorCode} ${errorDescription}`].join("\n"), + nativeT("desktop.recovery.loadFailed"), + nativeT("desktop.recovery.loadFailed.detail", { + window: name, + url: validatedURL, + code: errorCode, + description: errorDescription, + }), false, ) } @@ -426,15 +443,19 @@ function wireWindowRecovery(win: BrowserWindow, name: string) { sampler.stopAndFlush() writeLog("window", "renderer process gone", { window: name, currentURL: safeWindowURL(win), details }, "error") void show( - "OpenCode window terminated unexpectedly", - [`Window: ${name}`, `Reason: ${details.reason}`, `Code: ${details.exitCode ?? ""}`].join("\n"), + nativeT("desktop.recovery.terminated"), + nativeT("desktop.recovery.terminated.detail", { + window: name, + reason: details.reason, + code: details.exitCode ?? nativeT("desktop.recovery.unknown"), + }), false, ) }) win.on("unresponsive", () => { writeLog("window", "renderer unresponsive", { window: name, currentURL: safeWindowURL(win) }, "error") sampler.start() - void show("OpenCode is not responding", "You can relaunch the app, open the logs, or keep waiting.", true) + void show(nativeT("desktop.recovery.unresponsive"), nativeT("desktop.recovery.unresponsive.detail"), true) }) win.on("responsive", () => { writeLog("window", "renderer responsive", { window: name, currentURL: safeWindowURL(win) }, "error") diff --git a/packages/desktop/src/main/wsl/ipc.ts b/packages/desktop/src/main/wsl/ipc.ts index c3a2271bf6c..839efad5853 100644 --- a/packages/desktop/src/main/wsl/ipc.ts +++ b/packages/desktop/src/main/wsl/ipc.ts @@ -3,6 +3,7 @@ import type { IpcMainInvokeEvent } from "electron" import type { WslServersController } from "./servers" import { requireWslIpcString, requireWslIpcStrings } from "./policy" import type { WslServersState } from "../../preload/types" +import { nativeT } from "../native-translations" export function registerWslIpcHandlers(controller: WslServersController) { if (process.platform !== "win32") { @@ -68,13 +69,13 @@ export function registerWslIpcHandlers(controller: WslServersController) { function registerUnavailableWslIpcHandlers() { const unavailable = () => { - throw new Error("WSL is only available on Windows") + throw new Error(nativeT("desktop.wsl.error.windowsOnly")) } const state = (): WslServersState => ({ runtime: { available: false, version: null, - error: "WSL is only available on Windows", + error: nativeT("desktop.wsl.error.windowsOnly"), }, installed: [], online: [], diff --git a/packages/desktop/src/main/wsl/runtime.ts b/packages/desktop/src/main/wsl/runtime.ts index 458443731bd..4ddf0eea31f 100644 --- a/packages/desktop/src/main/wsl/runtime.ts +++ b/packages/desktop/src/main/wsl/runtime.ts @@ -4,6 +4,7 @@ import { join } from "node:path" import * as pty from "@lydell/node-pty" import type { WslDistroProbe, WslInstalledDistro, WslOnlineDistro, WslRuntimeCheck } from "../../preload/types" import { wslTerminalArgs } from "./policy" +import { nativeT } from "../native-translations" export type WslCommandLine = { stream: "stdout" | "stderr" @@ -68,7 +69,11 @@ function runCommand(command: string, args: string[], opts: RunWslOptions = {}) { } catch { /* ignore */ } - reject(new Error(`${command} ${args.join(" ")} timed out after ${timeoutMs}ms`)) + reject( + new Error( + nativeT("desktop.wsl.error.commandTimeout", { command, args: args.join(" "), timeout: timeoutMs }), + ), + ) }, timeoutMs) let stdout = "" @@ -139,7 +144,11 @@ function runInteractiveCommand(command: string, args: string[], opts: RunWslOpti if (settled) return settled = true cleanup() - reject(new Error(`${command} ${args.join(" ")} timed out after ${timeoutMs}ms`)) + reject( + new Error( + nativeT("desktop.wsl.error.commandTimeout", { command, args: args.join(" "), timeout: timeoutMs }), + ), + ) }, timeoutMs) const abortHandler = () => { @@ -214,7 +223,7 @@ export async function probeWslRuntime(opts?: RunWslOptions): Promise { const result = await installWslRuntimeElevated({ signal: abort.signal }) if (result.code !== 0) { - const message = summarize(result.stderr || result.stdout) || "WSL installation failed" + const message = summarize(result.stderr || result.stdout) || nativeT("desktop.wsl.error.installWsl") throw new Error(message) } const runtime = await probeWslRuntime({ signal: abort.signal }) @@ -339,7 +340,8 @@ export function createWslServersController( await runJob({ kind: "install-distro", distro: name, startedAt: Date.now() }, async (abort) => { const result = await installWslDistro(name, { signal: abort.signal }) if (result.code !== 0) { - const message = summarize(result.stderr || result.stdout) || `Failed to install distro: ${name}` + const message = + summarize(result.stderr || result.stdout) || nativeT("desktop.wsl.error.installDistro", { distro: name }) throw new Error(message) } const distros = await refreshDistroLists({ signal: abort.signal }) @@ -362,7 +364,7 @@ export function createWslServersController( await runJob({ kind: "install-opencode", distro: name, startedAt: Date.now() }, async (abort) => { const result = await installWslOpencode(appVersion, name, { signal: abort.signal }) if (result.code !== 0) { - throw new Error(summarize(result.stderr || result.stdout) || "OpenCode installation failed") + throw new Error(summarize(result.stderr || result.stdout) || nativeT("desktop.wsl.error.installOpencode")) } await refreshOpencodeCheck(name, { signal: abort.signal }) expectOpencodeVersion(state.opencodeChecks[name]?.version ?? null, appVersion, name) @@ -378,7 +380,7 @@ export function createWslServersController( async addServer(distro: string): Promise { const id = wslServerIdForDistro(distro) if (state.servers.some((item) => item.config.id === id)) { - throw new Error(`${distro} is already added`) + throw new Error(nativeT("desktop.wsl.error.alreadyAdded", { distro })) } const config: WslServerConfig = { id, @@ -475,7 +477,7 @@ function opencodeCheck( version: null, expectedVersion, matchesDesktop: null, - error: "opencode is not installed in this distro", + error: nativeT("desktop.wsl.error.opencodeMissing"), } } if (!version) { @@ -485,7 +487,7 @@ function opencodeCheck( version: null, expectedVersion, matchesDesktop: null, - error: "opencode is installed but could not run", + error: nativeT("desktop.wsl.error.opencodeCannotRun"), } } return { @@ -503,7 +505,7 @@ function distroProbeReady(probe: WslDistroProbe | undefined) { } function startupFailure(code: number | null, signal: NodeJS.Signals | null) { - return `WSL server exited after startup (code=${code ?? "null"} signal=${signal ?? "null"})` + return nativeT("desktop.wsl.error.serverExited", { code: code ?? "null", signal: signal ?? "null" }) } // Re-export types used by callers diff --git a/packages/desktop/src/main/wsl/sidecar.ts b/packages/desktop/src/main/wsl/sidecar.ts index 522e575a47e..bd052dd8464 100644 --- a/packages/desktop/src/main/wsl/sidecar.ts +++ b/packages/desktop/src/main/wsl/sidecar.ts @@ -5,6 +5,7 @@ import { app } from "electron" import { checkHealth } from "../server" import { type WslCommandLine, resolveWslOpencode, shellEscape, wslArgs } from "./runtime" import { pollWslHealth } from "./startup" +import { nativeT } from "../native-translations" export type WslSidecar = { listener: { stop: () => void; onExit: (cb: (code: number | null, signal: NodeJS.Signals | null) => void) => void } @@ -18,7 +19,7 @@ export async function spawnWslSidecar( opts: { onLine?: (line: WslCommandLine) => void; healthTimeoutMs?: number } = {}, ): Promise { const opencode = await resolveWslOpencode(distro) - if (!opencode) throw new Error(`OpenCode is not installed in ${distro}`) + if (!opencode) throw new Error(nativeT("desktop.wsl.error.opencodeNotInstalled", { distro })) const port = await allocatePort() const password = randomUUID() @@ -64,7 +65,7 @@ export async function spawnWslSidecar( const timedOut = new Promise( (_, reject) => (timeout = setTimeout( - () => reject(new Error(`Sidecar for ${distro} health check timed out after ${timeoutMs}ms`)), + () => reject(new Error(nativeT("desktop.wsl.error.healthTimeout", { distro, timeout: timeoutMs }))), timeoutMs, )), ) @@ -97,7 +98,7 @@ function allocatePort() { const address = server.address() if (typeof address !== "object" || !address) { server.close() - reject(new Error("Failed to get port")) + reject(new Error(nativeT("desktop.wsl.error.failedPort"))) return } server.close(() => resolve(address.port)) @@ -125,5 +126,9 @@ function forwardLines( function startupFailure(code: number | null, signal: NodeJS.Signals | null, recentOutput: string[]) { const suffix = recentOutput.length ? `\n${recentOutput.join("\n")}` : "" - return `WSL server exited before becoming healthy (code=${code ?? "null"} signal=${signal ?? "null"})${suffix}` + return nativeT("desktop.wsl.error.serverExitedBeforeHealthy", { + code: code ?? "null", + signal: signal ?? "null", + output: suffix, + }) } diff --git a/packages/desktop/src/main/wsl/startup.ts b/packages/desktop/src/main/wsl/startup.ts index bfe7accfed7..7a80c3d613f 100644 --- a/packages/desktop/src/main/wsl/startup.ts +++ b/packages/desktop/src/main/wsl/startup.ts @@ -1,3 +1,5 @@ +import { nativeT } from "../native-translations" + export function wslServerIdsToStartOnInitialize(servers: { id: string }[]) { return servers.map((server) => server.id) } @@ -5,7 +7,11 @@ export function wslServerIdsToStartOnInitialize(servers: { id: string }[]) { export function expectOpencodeVersion(installed: string | null, expected: string, distro = "Debian") { if (installed === expected) return throw new Error( - `OpenCode update finished but ${distro} still reports ${installed ?? "no version"}; expected ${expected}`, + nativeT("desktop.wsl.error.updateVersion", { + distro, + installed: installed ?? nativeT("desktop.wsl.error.noVersion"), + expected, + }), ) } diff --git a/packages/desktop/src/preload/index.ts b/packages/desktop/src/preload/index.ts index 6b8bd4dfa7d..dae18b0716a 100644 --- a/packages/desktop/src/preload/index.ts +++ b/packages/desktop/src/preload/index.ts @@ -132,6 +132,7 @@ const api: ElectronAPI = { exportDebugLogs: () => ipcRenderer.invoke("export-debug-logs"), setForceFocus: (enabled) => ipcRenderer.invoke("set-force-focus", enabled), recordFatalRendererError: (error) => ipcRenderer.invoke("record-fatal-renderer-error", error), + setNativeTranslations: (bundle) => ipcRenderer.invoke("set-native-translations", bundle), } contextBridge.exposeInMainWorld("api", api) diff --git a/packages/desktop/src/preload/types.ts b/packages/desktop/src/preload/types.ts index fbf58f1fd6d..20c39097d31 100644 --- a/packages/desktop/src/preload/types.ts +++ b/packages/desktop/src/preload/types.ts @@ -1,6 +1,7 @@ import type { DesktopMenuAction } from "@opencode-ai/app/desktop-menu" import type { WslServersPlatform } from "@opencode-ai/app/wsl/types" import type { UpdaterState } from "@opencode-ai/app/updater" +import type { DesktopNativeBundle } from "@opencode-ai/app/i18n/desktop-native" export type { WslDistroProbe, WslInstalledDistro, @@ -111,4 +112,5 @@ export type ElectronAPI = { exportDebugLogs: () => Promise setForceFocus: (enabled: boolean) => Promise recordFatalRendererError: (error: FatalRendererError) => Promise + setNativeTranslations: (bundle: DesktopNativeBundle) => Promise } diff --git a/packages/desktop/src/renderer/i18n/index.ts b/packages/desktop/src/renderer/i18n/index.ts index 79cd8978f0a..e9ffa1e4c9d 100644 --- a/packages/desktop/src/renderer/i18n/index.ts +++ b/packages/desktop/src/renderer/i18n/index.ts @@ -89,7 +89,7 @@ export type Locale = | "th" type RawDictionary = typeof appEn & typeof desktopEn -type Dictionary = i18n.Flatten +type Dictionary = Record, string> const LOCALES: readonly Locale[] = [ "en", diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index e1ff8fbfc2e..b5c0254c0e4 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -13,6 +13,7 @@ import { ServerConnection, useCommand, useWslServers, + useLanguage, } from "@opencode-ai/app" import type { UpdaterState } from "@opencode-ai/app/updater" import * as Sentry from "@sentry/solid" @@ -21,7 +22,7 @@ import { createMemoryHistory, MemoryRouter, type BaseRouterProps } from "@solidj import { createEffect, createMemo, createResource, createSignal, onCleanup, Show } from "solid-js" import { render } from "solid-js/web" import pkg from "../../package.json" -import { initI18n, t } from "./i18n" +import { t } from "./i18n" import { initializationData } from "./initialization" import { DesktopFirstLaunchOnboarding } from "./onboarding" import { resetZoom, setPinchZoomEnabled, webviewZoom, zoomIn, zoomOut } from "./webview-zoom" @@ -59,8 +60,6 @@ if (import.meta.env.VITE_SENTRY_DSN) { }) } -void initI18n() - const [updaterState, setUpdaterState] = createSignal({ status: "disabled" }) void window.api.updater.subscribe(setUpdaterState) @@ -175,14 +174,14 @@ const createPlatform = (windowState: DesktopWindowState): Platform => { async openDirectoryPickerDialog(opts) { return window.api.openDirectoryPicker({ multiple: opts?.multiple ?? false, - title: opts?.title ?? t("desktop.dialog.chooseFolder"), + title: opts?.title, }) }, async openAttachmentPickerDialog(opts, onFile) { const result = await window.api.openFilePicker({ multiple: opts?.multiple ?? false, - title: opts?.title ?? t("desktop.dialog.chooseFile"), + title: opts?.title, defaultPath: opts?.defaultPath, extensions: opts?.extensions ?? ACCEPTED_FILE_EXTENSIONS, }) @@ -204,7 +203,7 @@ const createPlatform = (windowState: DesktopWindowState): Platform => { async saveFilePickerDialog(opts) { return window.api.saveFilePicker({ - title: opts?.title ?? t("desktop.dialog.saveFile"), + title: opts?.title, defaultPath: opts?.defaultPath, }) }, @@ -376,6 +375,7 @@ function DesktopRoot(props: { windowState: DesktopWindowState }) { function App() { const wslServers = useWslServers() + const language = useLanguage() const ready = createMemo( () => !defaultServer.loading && !sidecar.loading && !locale.loading && !wslServers.isLoading, ) @@ -384,7 +384,7 @@ function DesktopRoot(props: { windowState: DesktopWindowState }) { const list: ServerConnection.Any[] = [] if (data) { list.push({ - displayName: "Local Server", + displayName: language.t("desktop.server.local"), type: "sidecar", variant: "base", http: { @@ -394,7 +394,7 @@ function DesktopRoot(props: { windowState: DesktopWindowState }) { }, }) } - list.push(...readyWslConnections(wslServers.data)) + list.push(...readyWslConnections(wslServers.data, language.t("wsl.server.label"))) return list }) const effectiveDefaultServer = createMemo(() => @@ -426,7 +426,10 @@ function DesktopRoot(props: { windowState: DesktopWindowState }) { return ( - + void window.api.setNativeTranslations(bundle).catch(() => undefined)} + > {(_) => } diff --git a/packages/desktop/src/renderer/wsl/connections.test.ts b/packages/desktop/src/renderer/wsl/connections.test.ts index 341241a6b6d..057ed51e1d2 100644 --- a/packages/desktop/src/renderer/wsl/connections.test.ts +++ b/packages/desktop/src/renderer/wsl/connections.test.ts @@ -34,6 +34,10 @@ describe("WSL desktop connections", () => { ]) }) + test("uses the renderer translation for the WSL connection label", () => { + expect(readyWslConnections(state("ready"), "Translated WSL")[0]?.label).toBe("Translated WSL") + }) + test("does not block desktop startup on a configured WSL default", () => { const key = "wsl:Debian" expect(availableStartupServer(key, undefined)).toBe("sidecar") diff --git a/packages/desktop/src/renderer/wsl/connections.ts b/packages/desktop/src/renderer/wsl/connections.ts index 17762475343..f5ef6a279f5 100644 --- a/packages/desktop/src/renderer/wsl/connections.ts +++ b/packages/desktop/src/renderer/wsl/connections.ts @@ -1,12 +1,12 @@ import type { WslServersState } from "@opencode-ai/app/wsl/types" -export function readyWslConnections(state?: WslServersState) { +export function readyWslConnections(state?: WslServersState, label = "WSL") { return (state?.servers ?? []).flatMap((item) => { if (item.runtime.kind !== "ready") return [] return [ { displayName: item.config.distro, - label: "WSL", + label, type: "sidecar" as const, variant: "wsl" as const, distro: item.config.distro, diff --git a/packages/enterprise/src/app.tsx b/packages/enterprise/src/app.tsx index 68737ed273c..68510109ee0 100644 --- a/packages/enterprise/src/app.tsx +++ b/packages/enterprise/src/app.tsx @@ -4,13 +4,13 @@ import { Font } from "@opencode-ai/ui/font" import { MetaProvider } from "@solidjs/meta" import { MarkedProvider } from "@opencode-ai/ui/context/marked" import { DialogProvider } from "@opencode-ai/ui/context/dialog" +import { I18nProvider } from "@opencode-ai/ui/context" import { - I18nProvider, pluralCategory, pluralKey, type UiI18nParams, type UiI18nPluralKey, -} from "@opencode-ai/ui/context" +} from "@opencode-ai/ui/context/i18n" import { dict as uiEn } from "@opencode-ai/ui/i18n/en" import { dict as uiZh } from "@opencode-ai/ui/i18n/zh" import { createEffect, createMemo, Suspense, type ParentProps } from "solid-js" diff --git a/packages/session-ui/AGENTS.md b/packages/session-ui/AGENTS.md new file mode 100644 index 00000000000..678ae1bf90d --- /dev/null +++ b/packages/session-ui/AGENTS.md @@ -0,0 +1,7 @@ +## Localization + +- NEVER hardcode user-visible English strings in production code. ALWAYS use an i18n key for visible copy, placeholders, accessible labels, tooltips, menus, dialogs, empty states, and displayed errors. +- When migrating existing copy to i18n, preserve the English text byte-for-byte unless the task explicitly requests a copy change. +- NEVER change existing English text or English keys to facilitate translation. English is intentional, designer-written source copy; adapt locale-specific translations and i18n mechanics around it. +- Do not translate from model knowledge alone. Verify terminology and grammar with Unicode CLDR locale/plural data, Microsoft Localization Style Guides and terminology, Apple localization/style guidance and localized platform UI, Mozilla localization style guides, Mozilla Pontoon, and the Firefox localization corpus at `github.com/mozilla-l10n/firefox-l10n`. +- Also use the relevant language authority or official dictionary for the locale (for example RAE/Fundéu, FranceTerme, Duden, TDK, Kotus/Kielitoimiston sanakirja, Språkrådet/Bokmålsordboka, Rada Języka Polskiego/PWN, the Russian and Arabic language academies, the Ukrainian Orthography, Taiwan MOE dictionaries, or the Royal Society of Thailand). Treat the English dictionary as the semantic source of truth and preserve placeholders, code identifiers, product names, and keyboard labels. diff --git a/packages/session-ui/src/components/message-file.test.ts b/packages/session-ui/src/components/message-file.test.ts index a769ae01bb7..8b6872381ed 100644 --- a/packages/session-ui/src/components/message-file.test.ts +++ b/packages/session-ui/src/components/message-file.test.ts @@ -52,12 +52,12 @@ describe("message-file", () => { }) test("labels attachment types from the basename extension", () => { - expect(typeLabel("list.md", "text/plain")).toBe("Markdown") - expect(typeLabel("/repo/src/main.ts", "text/plain")).toBe("TypeScript") - expect(typeLabel("/tmp/report.pdf", "application/pdf")).toBe("PDF") - expect(typeLabel("notes.xyz", "text/plain")).toBe("XYZ") - expect(typeLabel("/home/user/my.project/Makefile", "text/plain")).toBe("File") - expect(typeLabel(".gitignore", "text/plain")).toBe("File") - expect(typeLabel("/repo/.env", "text/plain")).toBe("File") + expect(typeLabel("list.md", "text/plain", "File")).toBe("Markdown") + expect(typeLabel("/repo/src/main.ts", "text/plain", "File")).toBe("TypeScript") + expect(typeLabel("/tmp/report.pdf", "application/pdf", "File")).toBe("PDF") + expect(typeLabel("notes.xyz", "text/plain", "File")).toBe("XYZ") + expect(typeLabel("/home/user/my.project/Makefile", "text/plain", "File")).toBe("File") + expect(typeLabel(".gitignore", "text/plain", "File")).toBe("File") + expect(typeLabel("/repo/.env", "text/plain", "File")).toBe("File") }) }) diff --git a/packages/session-ui/src/components/message-file.ts b/packages/session-ui/src/components/message-file.ts index e09269f33f0..0b0a6fc3b5f 100644 --- a/packages/session-ui/src/components/message-file.ts +++ b/packages/session-ui/src/components/message-file.ts @@ -23,12 +23,12 @@ const LANGUAGE_NAMES = new Map( // attachments carry text/plain for all text files, so the label comes from the extension; // filename may be an absolute path, so extract the basename before looking for one -export function typeLabel(filename: string, mime: string) { +export function typeLabel(filename: string, mime: string, fallback: string) { if (mime === "application/pdf") return "PDF" const base = getFilename(filename) // idx 0 is a dotfile like .gitignore, not an extension const idx = base.lastIndexOf(".") const suffix = idx <= 0 ? "" : base.slice(idx + 1).toLowerCase() - if (!suffix) return "File" + if (!suffix) return fallback return LANGUAGE_NAMES.get(suffix) ?? suffix.toUpperCase() } diff --git a/packages/session-ui/src/components/message-part.tsx b/packages/session-ui/src/components/message-part.tsx index 5760e0e5717..71ba48c982d 100644 --- a/packages/session-ui/src/components/message-part.tsx +++ b/packages/session-ui/src/components/message-part.tsx @@ -459,10 +459,10 @@ function newLayout() { return typeof document !== "undefined" && document.body.hasAttribute("data-new-layout") } -function webSearchProviderLabel(provider: unknown) { - if (provider === "parallel") return "Parallel Web Search" - if (provider === "exa") return "Exa Web Search" - return "Web Search" +function webSearchProviderLabel(provider: unknown, i18n: ReturnType) { + const name = provider === "parallel" ? "Parallel" : provider === "exa" ? "Exa" : undefined + if (name) return i18n.t("ui.tool.websearch.provider", { provider: name }) + return i18n.t("ui.tool.websearch") } export function getToolInfo( @@ -505,7 +505,7 @@ export function getToolInfo( case "websearch": return { icon: "window-cursor", - title: webSearchProviderLabel(metadata?.provider), + title: webSearchProviderLabel(metadata?.provider, i18n), subtitle: input.query, } case "task": { @@ -1309,7 +1309,7 @@ export function UserMessageDisplay(props: { clickable={!!props.actions?.openAttachment} onClick={() => props.actions?.openAttachment?.(file)} > - {typeLabel(name, file.mime)} + {typeLabel(name, file.mime, i18n.t("ui.common.file"))} ) @@ -1594,7 +1594,11 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { { const value = props.input.query if (typeof value !== "string") return "" return value }) - const title = createMemo(() => webSearchProviderLabel(props.metadata.provider)) + const title = createMemo(() => webSearchProviderLabel(props.metadata.provider, i18n)) return (

- Drop files to attach + {i18n.t("ui.promptInput.dropFiles")}
@@ -133,7 +135,7 @@ export function PromptInputV2(props: PromptInputV2Props) { attachments={props.controller.attachments()} comments={props.controller.comments()} activeCommentID={state.activeContextID} - removeLabel="Remove attachment" + removeLabel={i18n.t("ui.promptInput.removeAttachment")} onAttachmentClick={props.controller.openAttachment} onAttachmentRemove={(attachment) => props.controller.removeAttachment(attachment.id)} onCommentClick={(comment) => props.controller.toggleContext(comment.key)} @@ -151,7 +153,7 @@ export function PromptInputV2(props: PromptInputV2Props) { data-component="prompt-input" role="textbox" aria-multiline="true" - aria-label="Prompt" + aria-label={i18n.t("ui.promptInput.label")} contenteditable={!props.disabled && !props.readOnly} autocapitalize={state.mode === "normal" ? "sentences" : "off"} autocorrect={state.mode === "normal" ? "on" : "off"} @@ -186,7 +188,11 @@ export function PromptInputV2(props: PromptInputV2Props) { classList={{ "font-mono!": state.mode === "shell" }} > {view.placeholder?.() ?? - (state.mode === "shell" ? "Enter shell command..." : "Ask anything, / for commands, @ for context...")} + (state.mode === "shell" ? ( + i18n.t("ui.promptInput.placeholder.shell") + ) : ( + i18n.t("ui.promptInput.placeholder.normal", { slash: "/", at: "@" }) + ))}
@@ -200,13 +206,13 @@ export function PromptInputV2(props: PromptInputV2Props) { > {(control) => ( - + )} {(control) => ( ( 1}> @@ -250,8 +260,8 @@ export function PromptInputV2(props: PromptInputV2Props) { mode={state.mode} stopping={view.submit.stopping()} disabled={!props.controller.canSubmit()} - sendLabel="Send" - stopLabel="Stop" + sendLabel={i18n.t("ui.promptInput.send")} + stopLabel={i18n.t("ui.promptInput.stop")} onSubmit={props.controller.submit} onStop={props.controller.stop} /> @@ -377,6 +387,7 @@ export function PromptInputV2Attachments(props: { onCommentClick?: (comment: PromptInputV2Comment) => void onCommentRemove?: (comment: PromptInputV2Comment) => void }) { + const i18n = useI18n() return ( 0 || (props.comments?.length ?? 0) > 0}>
@@ -404,7 +415,7 @@ export function PromptInputV2Attachments(props: {
@@ -571,7 +582,7 @@ export function PromptInputV2Select(props: { {props.options.find((option) => option.id === props.current)?.label ?? props.current} - + @@ -637,7 +648,7 @@ export function PromptInputV2Popover(props: {
diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index e998e14340c..5e9a54708f3 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -126,7 +126,8 @@ export const dict = { "ui.promptInput.removeAttachment": "Supprimer la pièce jointe", "ui.promptInput.label": "Invite", "ui.promptInput.placeholder.shell": "Entrez une commande shell...", - "ui.promptInput.placeholder.normal": "Demandez n'importe quoi, {{slash}} pour les commandes, {{at}} pour le contexte...", + "ui.promptInput.placeholder.normal": + "Demandez n'importe quoi, {{slash}} pour les commandes, {{at}} pour le contexte...", "ui.promptInput.add": "Ajouter des images et des fichiers", "ui.promptInput.attachments": "Images et fichiers", "ui.promptInput.context": "Contexte", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index 867e6045df7..680eadf7b9f 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -128,7 +128,8 @@ export const dict = { "ui.promptInput.removeAttachment": "Usuń załącznik", "ui.promptInput.label": "Prompt", "ui.promptInput.placeholder.shell": "Wpisz polecenie powłoki...", - "ui.promptInput.placeholder.normal": "Zapytaj o cokolwiek, {{slash}} aby wyświetlić polecenia, {{at}} aby wyświetlić kontekst...", + "ui.promptInput.placeholder.normal": + "Zapytaj o cokolwiek, {{slash}} aby wyświetlić polecenia, {{at}} aby wyświetlić kontekst...", "ui.promptInput.add": "Dodaj obrazy i pliki", "ui.promptInput.attachments": "Obrazy i pliki", "ui.promptInput.context": "Kontekst", From c75e3b5bae57206eac3f3e3986568d7f7a87acb5 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:09:29 +0800 Subject: [PATCH 052/114] fix(app): supply fallback session titles (#40385) --- packages/app/src/utils/session-title.ts | 12 ++++++++++++ packages/app/src/utils/session.test.ts | 20 ++++++++++++++++++++ packages/app/src/utils/session.ts | 3 ++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/app/src/utils/session-title.ts b/packages/app/src/utils/session-title.ts index ca04c010470..9d4c544b990 100644 --- a/packages/app/src/utils/session-title.ts +++ b/packages/app/src/utils/session-title.ts @@ -1,5 +1,17 @@ const pattern = /^(New session|Child session) - \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ +interface Info { + readonly title?: string + readonly parentID?: string + readonly time: { + readonly created: number + } +} + +export function withTimestampedFallback(info: Info) { + return info.title ?? `${info.parentID ? "Child" : "New"} session - ${new Date(info.time.created).toISOString()}` +} + export function sessionTitle(title?: string) { if (!title) return title const match = title.match(pattern) diff --git a/packages/app/src/utils/session.test.ts b/packages/app/src/utils/session.test.ts index b15c23b6604..836a990a565 100644 --- a/packages/app/src/utils/session.test.ts +++ b/packages/app/src/utils/session.test.ts @@ -36,6 +36,14 @@ describe("normalizeSessionInfo", () => { revert: { messageID: "message-1", partID: "part-1", snapshot: "snapshot" }, }) }) + + test("supplies timestamped titles for untitled current sessions", () => { + const root = currentSession("session-1") + const child = currentSession("session-2", "session-1") + + expect(normalizeSessionInfo(root).title).toBe("New session - 1970-01-01T00:00:00.000Z") + expect(normalizeSessionInfo(child).title).toBe("Child session - 1970-01-01T00:00:00.000Z") + }) }) describe("listAllSessions", () => { @@ -92,3 +100,15 @@ function sessionInfo(id: string, archived = false) { location: { directory: "/repo" }, } as SessionInfo } + +function currentSession(id: string, parentID?: string) { + return { + id, + parentID, + projectID: "project-1", + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + time: { created: 0, updated: 0 }, + location: { directory: "/repo" }, + } as SessionInfo +} diff --git a/packages/app/src/utils/session.ts b/packages/app/src/utils/session.ts index faf847967bd..976ecb9bd67 100644 --- a/packages/app/src/utils/session.ts +++ b/packages/app/src/utils/session.ts @@ -1,5 +1,6 @@ import type { SessionApi, SessionInfo, SessionListInput } from "@opencode-ai/client/promise" import type { Session } from "@opencode-ai/sdk/v2/client" +import { withTimestampedFallback } from "./session-title" export function normalizeSessionInfo(input: SessionInfo | Session): Session { if (!("location" in input)) return input @@ -13,7 +14,7 @@ export function normalizeSessionInfo(input: SessionInfo | Session): Session { parentID: input.parentID, cost: input.cost, tokens: input.tokens, - title: input.title, + title: withTimestampedFallback(input), agent: input.agent, model: input.model, version: "", From 8721a2710b37290041b17476e8c1f8bfcd49cbc9 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 07:38:56 +0000 Subject: [PATCH 053/114] fix(app): complete shared RTL primitives (#40388) Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> --- packages/app/src/components/help-button.tsx | 17 +++++++++++------ packages/app/src/components/titlebar.tsx | 2 +- packages/app/src/components/ui/drawer.tsx | 2 +- packages/ui/src/components/icon.css | 5 +++++ packages/ui/src/components/icon.tsx | 6 +++++- packages/ui/src/components/toast.css | 2 +- packages/ui/src/context/i18n.tsx | 11 +++++++++-- packages/ui/src/v2/components/toast-v2.tsx | 5 +++-- 8 files changed, 36 insertions(+), 14 deletions(-) diff --git a/packages/app/src/components/help-button.tsx b/packages/app/src/components/help-button.tsx index 4926a4ef81f..e12b9e2145e 100644 --- a/packages/app/src/components/help-button.tsx +++ b/packages/app/src/components/help-button.tsx @@ -1,5 +1,6 @@ import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" +import { isRTL } from "@kobalte/core/i18n" import { createSignal, Show } from "solid-js" import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer" import { usePlatform } from "@/context/platform" @@ -19,18 +20,19 @@ export function TabsInfoPopup() { const language = useLanguage() const [drawerOpen, setDrawerOpen] = createSignal(false) const windows = () => platform.platform === "desktop" && platform.os === "windows" + const rtl = () => isRTL(language.intl()) return ( - +
) if (props.inline) { return ( - + {content()} ) } return ( - + {content()} ) @@ -475,7 +479,7 @@ export function DebugBar(props: { inline?: boolean } = {}) { "gap-[9px]": !!props.inline, "gap-px": !props.inline, "flex w-full flex-nowrap items-center justify-start": !!props.inline, - "grid-cols-5": !props.inline, + "grid-cols-4": !props.inline, grid: !props.inline, }} > @@ -557,10 +561,25 @@ export function DebugBar(props: { inline?: boolean } = {}) { bad={bad(heap(), 0.8)} dim={state.heap.used === undefined} inline={props.inline} - wide={!platform.setForceFocus} + span={platform.setForceFocus ? 2 : 3} + /> + language.setDirection(language.direction() === "rtl" ? "ltr" : "rtl")} /> {platform.setForceFocus && ( - void toggleFocus()} /> + void toggleFocus()} + /> )}
diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx index adab9ddb72f..8772fd3111a 100644 --- a/packages/app/src/context/language.tsx +++ b/packages/app/src/context/language.tsx @@ -15,9 +15,14 @@ import { } from "@/i18n/desktop-native" export type Locale = DesktopNativeLocale +export type Direction = "ltr" | "rtl" const RTL_LOCALES: ReadonlySet = new Set(["ar", "ur", "pa"]) +function localeDirection(locale: Locale): Direction { + return RTL_LOCALES.has(locale) ? "rtl" : "ltr" +} + type RawDictionary = typeof en & typeof uiEn type Dictionary = i18n.Flatten type PluralKey = @@ -242,6 +247,13 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont const locale = createMemo(() => normalizeLocale(store.locale)) const intl = createMemo(() => INTL[locale()]) + const [layout, setLayout] = createStore({ direction: undefined as Direction | undefined }) + const direction = createMemo(() => layout.direction ?? localeDirection(locale())) + const layoutLocale = createMemo(() => { + if (!layout.direction) return intl() + // Kobalte derives menu direction from locale rather than accepting a direction override. + return layout.direction === "rtl" ? "ar" : "en" + }) const [dict] = createResource(locale, loadDict, { initialValue: dicts.get(initial) ?? base, @@ -270,7 +282,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont if (typeof document !== "object") return const value = locale() document.documentElement.lang = value - document.documentElement.dir = RTL_LOCALES.has(value) ? "rtl" : "ltr" + document.documentElement.dir = direction() document.cookie = cookie(value) }) @@ -287,6 +299,8 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont ready, locale, intl, + direction, + layoutLocale, locales: LOCALES, label, t, @@ -294,6 +308,9 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont setLocale(next: Locale) { setStore("locale", normalizeLocale(next)) }, + setDirection(next: Direction) { + setLayout("direction", next === localeDirection(locale()) ? undefined : next) + }, } }, }) diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 1d2e2ee80f6..f916bb487ae 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -1124,6 +1124,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "كومة JS المستخدمة مقابل حد الكومة. Chromium فقط.", "debugBar.mem.tip": "كومة JS المستخدمة مقابل حد الكومة. {{used}} من {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "فرض أنماط التركيز على جميع العناصر التفاعلية", + "debugBar.focus.on": "تشغيل", + "debugBar.focus.off": "إيقاف", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "فرض اتجاه التخطيط على التطبيق بالكامل دون تغيير اللغة المحددة", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/az.ts b/packages/app/src/i18n/az.ts index 66f4b72ab00..df3506c3d55 100644 --- a/packages/app/src/i18n/az.ts +++ b/packages/app/src/i18n/az.ts @@ -896,6 +896,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "İstifadə olunmuş JS yığını və yığın limiti. Yalnız Chromium.", "debugBar.mem.tip": "İstifadə olunmuş JS yığını və yığın limiti. {{used}} və {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Bütün interaktiv elementlərə fokus üslublarını məcburi tətbiq et", + "debugBar.focus.on": "AÇIQ", + "debugBar.focus.off": "QAPALI", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Seçilmiş dili dəyişmədən bütün tətbiq üçün tərtibat istiqamətini məcburi təyin et", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Masaüstü", "settings.section.server": "Server", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 19cb9a4cab4..90acd260370 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -1134,6 +1134,14 @@ export const dict = { "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}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Forçar estilos de foco em todos os elementos interativos", + "debugBar.focus.on": "LIGADO", + "debugBar.focus.off": "DESLIGADO", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Forçar a direção do layout de todo o aplicativo sem alterar o idioma selecionado", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 207acfc9609..6be962720b8 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -1210,6 +1210,14 @@ export const dict = { "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}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Prisilno primijeni stilove fokusa na sve interaktivne elemente", + "debugBar.focus.on": "UKLJUČENO", + "debugBar.focus.off": "ISKLJUČENO", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Prisilno postavi smjer rasporeda cijele aplikacije bez promjene odabranog jezika", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index f822b6d02e7..8221b31af8e 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -1084,6 +1084,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Brugt JS-heap sammenholdt med heapgrænsen. Kun Chromium.", "debugBar.mem.tip": "Brugt JS-heap sammenholdt med heapgrænsen. {{used}} af {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Gennemtving fokusstile for alle interaktive elementer", + "debugBar.focus.on": "TIL", + "debugBar.focus.off": "FRA", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Gennemtving layoutretningen for hele appen uden at ændre det valgte sprog", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 359d6ff318f..60be89a70a9 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -1029,6 +1029,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Belegter JS-Heap im Verhältnis zum Heap-Limit. Nur Chromium.", "debugBar.mem.tip": "Belegter JS-Heap im Verhältnis zum Heap-Limit. {{used}} von {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Fokusstile für alle interaktiven Elemente erzwingen", + "debugBar.focus.on": "EIN", + "debugBar.focus.off": "AUS", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Layoutrichtung für die gesamte App erzwingen, ohne die ausgewählte Sprache zu ändern", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Strg", "common.key.alt": "Alt", "common.key.shift": "Umschalt", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 5e24547d74f..c37c9c21161 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -868,6 +868,14 @@ export const dict = { "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}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Force focus styles on all interactive elements", + "debugBar.focus.on": "ON", + "debugBar.focus.off": "OFF", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Force the full app layout direction without changing the selected language", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 37d79400639..ef728ec0849 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -1218,6 +1218,14 @@ export const dict = { "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}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Forzar los estilos de foco en todos los elementos interactivos", + "debugBar.focus.on": "SÍ", + "debugBar.focus.off": "NO", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Forzar la dirección del diseño de toda la aplicación sin cambiar el idioma seleccionado", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Mayús", diff --git a/packages/app/src/i18n/fi.ts b/packages/app/src/i18n/fi.ts index 90e14265e46..7955cda59a6 100644 --- a/packages/app/src/i18n/fi.ts +++ b/packages/app/src/i18n/fi.ts @@ -786,6 +786,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Käytetty JS-keko suhteessa keon rajaan. Vain Chromium.", "debugBar.mem.tip": "Käytetty JS-keko suhteessa keon rajaan. {{used}} / {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Pakota kohdistustyylit kaikkiin vuorovaikutteisiin elementteihin", + "debugBar.focus.on": "KÄYTÖSSÄ", + "debugBar.focus.off": "EI KÄYTÖSSÄ", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Pakota koko sovelluksen asettelun suunta valittua kieltä muuttamatta", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Työpöytä", "settings.section.server": "Palvelin", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index e6347550fca..c628ec2f79f 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -1145,6 +1145,14 @@ export const dict = { "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}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Forcer les styles de focus sur tous les éléments interactifs", + "debugBar.focus.on": "ACTIVÉ", + "debugBar.focus.off": "DÉSACTIVÉ", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Forcer le sens de disposition de toute l’application sans changer la langue sélectionnée", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Maj", diff --git a/packages/app/src/i18n/hi.ts b/packages/app/src/i18n/hi.ts index 9def5f9121d..a704e6233ef 100644 --- a/packages/app/src/i18n/hi.ts +++ b/packages/app/src/i18n/hi.ts @@ -895,6 +895,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "उपयोग किया गया JS हीप बनाम हीप सीमा। केवल Chromium।", "debugBar.mem.tip": "उपयोग किया गया JS हीप बनाम हीप सीमा। {{limit}} में से {{used}}।", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "सभी इंटरैक्टिव एलिमेंट पर फ़ोकस स्टाइल ज़बरदस्ती लागू करें", + "debugBar.focus.on": "चालू", + "debugBar.focus.off": "बंद", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "चुनी गई भाषा बदले बिना पूरे ऐप की लेआउट दिशा ज़बरदस्ती सेट करें", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "डेस्कटॉप", "settings.section.server": "सर्वर", diff --git a/packages/app/src/i18n/id.ts b/packages/app/src/i18n/id.ts index 76b117a7f54..e693869089e 100644 --- a/packages/app/src/i18n/id.ts +++ b/packages/app/src/i18n/id.ts @@ -963,6 +963,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Heap JS terpakai vs batas heap. Khusus Chromium.", "debugBar.mem.tip": "Heap JS terpakai vs batas heap. {{used}} dari {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Paksa gaya fokus pada semua elemen interaktif", + "debugBar.focus.on": "AKTIF", + "debugBar.focus.off": "NONAKTIF", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Paksa arah tata letak seluruh aplikasi tanpa mengubah bahasa yang dipilih", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", diff --git a/packages/app/src/i18n/it.ts b/packages/app/src/i18n/it.ts index 56397ce6294..3f4089018dd 100644 --- a/packages/app/src/i18n/it.ts +++ b/packages/app/src/i18n/it.ts @@ -807,6 +807,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Heap JS utilizzato rispetto al limite dell'heap. Solo Chromium.", "debugBar.mem.tip": "Heap JS utilizzato rispetto al limite dell'heap. {{used}} di {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Forza gli stili di focus su tutti gli elementi interattivi", + "debugBar.focus.on": "ON", + "debugBar.focus.off": "OFF", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Forza la direzione del layout dell’intera app senza cambiare la lingua selezionata", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Server", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 2178234b174..37f6d5c5f69 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -1116,6 +1116,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "使用中の JS ヒープ対ヒープ制限。Chromium のみ。", "debugBar.mem.tip": "使用中の JS ヒープ対ヒープ制限。{{limit}} 中 {{used}}。", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "すべてのインタラクティブ要素にフォーカススタイルを強制適用", + "debugBar.focus.on": "オン", + "debugBar.focus.off": "オフ", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "選択中の言語を変更せずに、アプリ全体のレイアウト方向を強制設定", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 7f950ede61b..9d3821660cb 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -840,6 +840,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "사용된 JS 힙 대 힙 제한. Chromium 전용.", "debugBar.mem.tip": "사용된 JS 힙 대 힙 제한. {{limit}} 중 {{used}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "모든 대화형 요소에 포커스 스타일 강제 적용", + "debugBar.focus.on": "켬", + "debugBar.focus.off": "끔", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "선택한 언어를 변경하지 않고 전체 앱의 레이아웃 방향 강제 설정", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/nl.ts b/packages/app/src/i18n/nl.ts index ffdfdd9cfb5..7ac999c7a79 100644 --- a/packages/app/src/i18n/nl.ts +++ b/packages/app/src/i18n/nl.ts @@ -896,6 +896,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Gebruikte JS-heap versus heap-limiet. Alleen Chromium.", "debugBar.mem.tip": "Gebruikte JS-heap versus heaplimiet. {{used}} van {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Dwing focusstijlen af voor alle interactieve elementen", + "debugBar.focus.on": "AAN", + "debugBar.focus.off": "UIT", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Dwing de lay-outrichting voor de hele app af zonder de geselecteerde taal te wijzigen", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Server", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index f2048f3bd88..7ff1ac5dc1d 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -1037,6 +1037,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Brukt JS-heap kontra heap-grense. Kun Chromium.", "debugBar.mem.tip": "Brukt JS-heap kontra heap-grense. {{used}} av {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Tving fokusstiler på alle interaktive elementer", + "debugBar.focus.on": "PÅ", + "debugBar.focus.off": "AV", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Tving layoutretningen for hele appen uten å endre valgt språk", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/pa.ts b/packages/app/src/i18n/pa.ts index 4c09514872d..4e0ff16f87e 100644 --- a/packages/app/src/i18n/pa.ts +++ b/packages/app/src/i18n/pa.ts @@ -893,6 +893,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "جے ایس ہیپ بمقابلہ ہیپ لیمٹ استعمال کیتا گیا۔ صرف Chromium۔", "debugBar.mem.tip": "ورتیا JS ہیپ تے ہیپ دی حد۔ {{limit}} وچوں {{used}}۔", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "سارے انٹرایکٹو عناصر اُتے فوکس سٹائل لازمی لاگو کرو", + "debugBar.focus.on": "چالو", + "debugBar.focus.off": "بند", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "چُنی ہوئی زبان نوں بدلے بغیر پوری ایپ دے لے آؤٹ دی سمت لازمی مقرر کرو", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode ڈیسک ٹاپ", "settings.section.desktop": "ڈیسک ٹاپ", "settings.section.server": "سرور", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 843a5400061..17c9fb639fb 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -1137,6 +1137,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Wykorzystana pamięć sterty JS względem jej limitu. Tylko Chromium.", "debugBar.mem.tip": "Wykorzystana pamięć sterty JS względem jej limitu: {{used}} z {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Wymuś style fokusu dla wszystkich elementów interaktywnych", + "debugBar.focus.on": "WŁ.", + "debugBar.focus.off": "WYŁ.", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Wymuś kierunek układu całej aplikacji bez zmiany wybranego języka", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 5a113e100f5..6d119926148 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -1215,6 +1215,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Используемая куча JS по сравнению с лимитом кучи. Только Chromium.", "debugBar.mem.tip": "Используемая куча JS по сравнению с лимитом кучи. {{used}} из {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Принудительно применить стили фокуса ко всем интерактивным элементам", + "debugBar.focus.on": "ВКЛ.", + "debugBar.focus.off": "ВЫКЛ.", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Принудительно задать направление макета всего приложения, не меняя выбранный язык", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/sv.ts b/packages/app/src/i18n/sv.ts index 401c47bb492..ab5007238b6 100644 --- a/packages/app/src/i18n/sv.ts +++ b/packages/app/src/i18n/sv.ts @@ -892,6 +892,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Använd JS-heap jämfört med heapgränsen. Endast Chromium.", "debugBar.mem.tip": "Använd JS-heap jämfört med heapgränsen. {{used}} av {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Tvinga fram fokusstilar på alla interaktiva element", + "debugBar.focus.on": "PÅ", + "debugBar.focus.off": "AV", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Tvinga fram hela appens layoutriktning utan att ändra valt språk", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Skrivbord", "settings.section.server": "Server", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index b6d5f6ff11c..e87d0db164d 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -1191,6 +1191,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "JS heap ที่ใช้เทียบกับขีดจำกัด heap เฉพาะ Chromium", "debugBar.mem.tip": "JS heap ที่ใช้เทียบกับขีดจำกัด heap {{used}} จาก {{limit}}", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "บังคับใช้สไตล์โฟกัสกับองค์ประกอบแบบโต้ตอบทั้งหมด", + "debugBar.focus.on": "เปิด", + "debugBar.focus.off": "ปิด", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "บังคับกำหนดทิศทางเค้าโครงของทั้งแอปโดยไม่เปลี่ยนภาษาที่เลือก", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index a60f7250774..d0d3a495034 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -1213,6 +1213,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Kullanılan JS yığınının yığın sınırına oranı. Yalnızca Chromium.", "debugBar.mem.tip": "Kullanılan JS yığınının yığın sınırına oranı: {{used}} / {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Tüm etkileşimli öğelere odak stillerini zorla uygula", + "debugBar.focus.on": "AÇIK", + "debugBar.focus.off": "KAPALI", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Seçili dili değiştirmeden tüm uygulamanın düzen yönünü zorla ayarla", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts index 4a439e920e1..fdeb0b83fb9 100644 --- a/packages/app/src/i18n/uk.ts +++ b/packages/app/src/i18n/uk.ts @@ -977,6 +977,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Використана купа JS проти ліміту купи. Тільки Chromium.", "debugBar.mem.tip": "Використана купа JS проти ліміту купи. {{used}} з {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Примусово застосувати стилі фокуса до всіх інтерактивних елементів", + "debugBar.focus.on": "УВІМК.", + "debugBar.focus.off": "ВИМК.", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Примусово задати напрямок макета всієї програми, не змінюючи вибрану мову", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", diff --git a/packages/app/src/i18n/ur.ts b/packages/app/src/i18n/ur.ts index 67f13676d36..661c013ebe7 100644 --- a/packages/app/src/i18n/ur.ts +++ b/packages/app/src/i18n/ur.ts @@ -896,6 +896,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "استعمال شدہ JS ہیپ بمقابلہ ہیپ کی حد۔ صرف Chromium۔", "debugBar.mem.tip": "استعمال شدہ JS ہیپ بمقابلہ ہیپ کی حد۔ {{limit}} میں سے {{used}}۔", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "تمام تفاعلی عناصر پر فوکس اسٹائلز لازماً لاگو کریں", + "debugBar.focus.on": "چالو", + "debugBar.focus.off": "بند", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "منتخب زبان تبدیل کیے بغیر پوری ایپ کے لے آؤٹ کی سمت لازماً مقرر کریں", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode ڈیسک ٹاپ", "settings.section.desktop": "ڈیسک ٹاپ", "settings.section.server": "سرور", diff --git a/packages/app/src/i18n/vi.ts b/packages/app/src/i18n/vi.ts index e0787217d3c..1e243ba17d6 100644 --- a/packages/app/src/i18n/vi.ts +++ b/packages/app/src/i18n/vi.ts @@ -898,6 +898,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "Bộ nhớ heap JS đã dùng so với giới hạn heap. Chỉ hỗ trợ Chromium.", "debugBar.mem.tip": "Đã sử dụng JS heap so với giới hạn heap. {{used}} của {{limit}}.", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "Buộc áp dụng kiểu tiêu điểm cho mọi phần tử tương tác", + "debugBar.focus.on": "BẬT", + "debugBar.focus.off": "TẮT", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "Buộc đặt hướng bố cục cho toàn bộ ứng dụng mà không thay đổi ngôn ngữ đã chọn", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Máy chủ", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 1a725433afb..08bda71a65e 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -1180,6 +1180,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "使用的 JS 堆与堆限制。仅限 Chromium。", "debugBar.mem.tip": "使用的 JS 堆与堆限制。{{used}} / {{limit}}。", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "强制所有交互元素显示焦点样式", + "debugBar.focus.on": "开", + "debugBar.focus.off": "关", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "在不更改所选语言的情况下,强制设置整个应用的布局方向", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index b1b85acd244..78a452070a7 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -1176,6 +1176,14 @@ export const dict = { "debugBar.mem.label": "MEM", "debugBar.mem.tipUnavailable": "使用的 JS 堆積與堆積限制。僅限 Chromium。", "debugBar.mem.tip": "使用的 JS 堆積與堆積限制。{{used}} / {{limit}}。", + "debugBar.focus.label": "FOCUS", + "debugBar.focus.tip": "強制所有互動元素顯示焦點樣式", + "debugBar.focus.on": "開啟", + "debugBar.focus.off": "關閉", + "debugBar.direction.label": "DIR", + "debugBar.direction.tip": "在不變更所選語言的情況下,強制設定整個應用程式的版面配置方向", + "debugBar.direction.ltr": "LTR", + "debugBar.direction.rtl": "RTL", "common.key.ctrl": "Ctrl", "common.key.alt": "Alt", "common.key.shift": "Shift", diff --git a/packages/ui/src/context/i18n.tsx b/packages/ui/src/context/i18n.tsx index e6b76ae2ec1..be7a4d4f1e9 100644 --- a/packages/ui/src/context/i18n.tsx +++ b/packages/ui/src/context/i18n.tsx @@ -18,6 +18,7 @@ export type UiI18nParams = Record export type UiI18n = { locale: Accessor + layoutLocale?: Accessor t: (key: UiI18nKey, params?: UiI18nParams) => string plural: (key: UiI18nPluralKey, count: number, params?: UiI18nParams) => string } @@ -60,7 +61,7 @@ const Context = createContext(fallback) function UiI18nProvider(props: ParentProps<{ value: UiI18n }>) { return ( - + {props.children} ) From 6c97a46dae52a4d88893adac9d7be4b43b7d51ea Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 06:26:48 -0500 Subject: [PATCH 055/114] fix(stats): hide zero-usage countries (#40400) --- packages/stats/app/src/routes/[lab]/[model].tsx | 5 +++-- packages/stats/app/src/routes/index.css | 4 ++-- packages/stats/app/src/routes/index.tsx | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/stats/app/src/routes/[lab]/[model].tsx b/packages/stats/app/src/routes/[lab]/[model].tsx index 40a0ac9c505..3b2bf341fdb 100644 --- a/packages/stats/app/src/routes/[lab]/[model].tsx +++ b/packages/stats/app/src/routes/[lab]/[model].tsx @@ -988,9 +988,10 @@ function GeoWorldMap(props: { const i18n = useI18n() const opacityScale = createMemo(() => scaleSqrt().domain([0, props.maxTokens]).range([0.26, 0.96]).clamp(true)) const countryOpacity = (country: CountryEntry | undefined) => { - if (!country) return 0 + if (!country || country.tokens <= 0) return 0 const opacity = opacityScale()(country.tokens) - if (!props.activeCountry || props.activeCountry === country.country) return opacity + if (props.activeCountry === country.country) return 1 + if (!props.activeCountry) return opacity return Math.max(0.18, opacity * 0.36) } diff --git a/packages/stats/app/src/routes/index.css b/packages/stats/app/src/routes/index.css index b55c3153f2c..c378688490c 100644 --- a/packages/stats/app/src/routes/index.css +++ b/packages/stats/app/src/routes/index.css @@ -2275,7 +2275,7 @@ body { [data-page="stats"] [data-slot="geo-countries"] path[data-active="true"] { fill: color-mix(in srgb, var(--stats-accent) 70%, var(--stats-text)); - opacity: 1; + opacity: var(--geo-country-opacity); } [data-page="stats"] [data-slot="geo-country-markers"] circle { @@ -2292,7 +2292,7 @@ body { [data-page="stats"] [data-slot="geo-country-markers"] circle[data-active="true"] { fill: color-mix(in srgb, var(--stats-accent) 70%, var(--stats-text)); - opacity: 1; + opacity: var(--geo-country-opacity); } [data-page="stats"] [data-slot="geo-borders"] { diff --git a/packages/stats/app/src/routes/index.tsx b/packages/stats/app/src/routes/index.tsx index 7debf5c30b7..c6549b1f613 100644 --- a/packages/stats/app/src/routes/index.tsx +++ b/packages/stats/app/src/routes/index.tsx @@ -1410,9 +1410,10 @@ function GeoWorldMap(props: { const i18n = useI18n() const opacityScale = createMemo(() => scaleSqrt().domain([0, props.maxTokens]).range([0.26, 0.96]).clamp(true)) const countryOpacity = (country: CountryEntry | undefined) => { - if (!country) return 0 + if (!country || country.tokens <= 0) return 0 const opacity = opacityScale()(country.tokens) - if (!props.activeCountry || props.activeCountry === country.country) return opacity + if (props.activeCountry === country.country) return 1 + if (!props.activeCountry) return opacity return Math.max(0.18, opacity * 0.36) } From de1bd36a47f2e6ade10188b525b4e81781147d3f Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:39:32 +1000 Subject: [PATCH 056/114] fix(app): correct RTL layout interactions (#40410) --- packages/app/src/components/file-tree-v2.tsx | 17 ++-- packages/app/src/components/file-tree.tsx | 4 +- .../components/settings-v2/settings-v2.css | 8 +- .../app/src/components/titlebar-tab-nav.css | 23 +++++- packages/app/src/components/titlebar.tsx | 21 ++++- .../src/v2/components/session-review-v2.css | 26 +++--- .../src/v2/components/session-review-v2.tsx | 12 ++- packages/ui/src/components/dropdown-menu.css | 4 +- packages/ui/src/components/icon.tsx | 9 ++- packages/ui/src/components/resize-handle.css | 16 +++- packages/ui/src/components/resize-handle.tsx | 6 +- packages/ui/src/components/scroll-view.css | 2 +- packages/ui/src/components/select.css | 8 +- packages/ui/src/components/switch.css | 4 + packages/ui/src/components/tabs.css | 80 +++++++++++++------ packages/ui/src/components/tabs.tsx | 1 + packages/ui/src/components/text-field.css | 4 +- .../ui/src/v2/components/file-tree-v2.css | 8 +- packages/ui/src/v2/components/select-v2.css | 21 +++-- packages/ui/src/v2/components/switch-v2.css | 8 ++ .../ui/src/v2/components/text-input-v2.css | 12 +-- 21 files changed, 210 insertions(+), 84 deletions(-) diff --git a/packages/app/src/components/file-tree-v2.tsx b/packages/app/src/components/file-tree-v2.tsx index ba1a7aaab46..091e489c32c 100644 --- a/packages/app/src/components/file-tree-v2.tsx +++ b/packages/app/src/components/file-tree-v2.tsx @@ -29,14 +29,14 @@ export type { Kind } from "@/components/file-tree" const INDENT_STEP = 16 -function rowPaddingLeft(level: number, type: FileNode["type"]) { +function rowPaddingStart(level: number, type: FileNode["type"]) { if (type === "directory") return 8 + level * INDENT_STEP if (level === 0) return 8 return 8 + level * INDENT_STEP - INDENT_STEP } -function guideLineLeft(level: number) { - return rowPaddingLeft(level, "directory") + 8 +function guideLineStart(level: number) { + return rowPaddingStart(level, "directory") + 8 } export const kindLabel = (kind: Kind) => { @@ -87,7 +87,7 @@ const FileTreeNodeV2 = ( ...local.classList, [local.class ?? ""]: !!local.class, }} - style={`padding-left: ${rowPaddingLeft(local.level, local.node.type)}px`} + style={`padding-inline-start: ${rowPaddingStart(local.level, local.node.type)}px`} draggable={local.draggable} onDragStart={(event: DragEvent) => { if (!local.draggable) return @@ -99,7 +99,9 @@ const FileTreeNodeV2 = ( {...rest} > {local.children} - {local.node.name} + + {local.node.name} + {(() => { const value = kind() if (!value || local.node.type !== "file") return null @@ -116,7 +118,7 @@ const FileTreeNodeV2 = ( function GuideLines(props: { level: number }) { return ( - {(_, index) =>
} + {(_, index) =>
} ) } @@ -215,7 +217,6 @@ export default function FileTreeV2(props: {
{ if (!local.draggable) return diff --git a/packages/app/src/components/settings-v2/settings-v2.css b/packages/app/src/components/settings-v2/settings-v2.css index c4d47344a26..b73269b496d 100644 --- a/packages/app/src/components/settings-v2/settings-v2.css +++ b/packages/app/src/components/settings-v2/settings-v2.css @@ -287,7 +287,7 @@ } .settings-v2-provider-env-hint { - padding-right: 12px; + padding-inline-end: 12px; font-size: 13px; font-weight: 440; line-height: 1; @@ -311,7 +311,7 @@ line-height: 1; color: var(--v2-text-text-accent); cursor: pointer; - text-align: left; + text-align: start; } .settings-v2-providers-view-all:hover { @@ -361,13 +361,13 @@ } .settings-v2-tab-search [data-slot="text-input-v2-input"] { - padding-right: 28px; + padding-inline-end: 28px; } .settings-v2-tab-search-clear { position: absolute; top: 50%; - right: 6px; + inset-inline-end: 6px; z-index: 1; transform: translateY(-50%); } diff --git a/packages/app/src/components/titlebar-tab-nav.css b/packages/app/src/components/titlebar-tab-nav.css index 13dfbe04fc7..93834b5b968 100644 --- a/packages/app/src/components/titlebar-tab-nav.css +++ b/packages/app/src/components/titlebar-tab-nav.css @@ -1,7 +1,7 @@ [data-titlebar-tab] [data-slot="tab-close"] { position: absolute; top: 4px; - right: 4px; + inset-inline-end: 4px; display: flex; height: 20px; width: 20px; @@ -52,7 +52,7 @@ content: ""; position: absolute; top: 8px; - left: -3.75px; + inset-inline-start: -3.75px; width: 1.5px; height: 12px; border-radius: 9999px; @@ -86,6 +86,23 @@ ); } +[data-titlebar-tab][data-title-overflow="true"]:not([data-editing="true"]):dir(rtl) [data-slot="tab-link"] { + -webkit-mask-image: linear-gradient( + to left, + black 0, + black calc(100% - var(--tab-title-fade-offset) - 16px), + transparent calc(100% - var(--tab-title-fade-offset)), + transparent 100% + ); + mask-image: linear-gradient( + to left, + black 0, + black calc(100% - var(--tab-title-fade-offset) - 16px), + transparent calc(100% - var(--tab-title-fade-offset)), + transparent 100% + ); +} + [data-titlebar-tab][data-title-overflow="true"]:is(:hover, [data-active="true"]):not([data-editing="true"]) [data-slot="tab-link"] { --tab-title-fade-offset: 24px; @@ -93,7 +110,7 @@ [data-titlebar-tab][data-title-overflow="true"]:not(:hover):not([data-active="true"]):not([data-editing="true"]) [data-slot="tab-link"] { - padding-right: 0; + padding-inline-end: 0; } [data-titlebar-tab] [data-slot="tab-title"] { diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index f24caeb51bd..64df40685f2 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -1,4 +1,15 @@ -import { createEffect, createMemo, createResource, createSignal, Match, onMount, Show, Switch, untrack } from "solid-js" +import { + createEffect, + createMemo, + createResource, + createSignal, + Match, + on, + onMount, + Show, + Switch, + untrack, +} from "solid-js" import { createStore } from "solid-js/store" import { useLocation, useNavigate, useParams } from "@solidjs/router" import { IconButton } from "@opencode-ai/ui/icon-button" @@ -42,8 +53,11 @@ export type TitlebarUpdate = { } export function useTitlebarRightMount() { + const language = useLanguage() const [mount, setMount] = createSignal(null) - onMount(() => setMount(document.getElementById("opencode-titlebar-right"))) + const sync = () => setMount(document.getElementById("opencode-titlebar-right")) + onMount(sync) + createEffect(on(language.direction, sync, { defer: true })) return mount } @@ -169,7 +183,8 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl "padding-left": macTrafficLights() ? `${macTrafficLightsBaseWidth / zoom()}px` : 0, width: windows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined, "max-width": windows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined, - "align-self": windows() ? "flex-start" : undefined, + // Native Windows caption controls remain on the physical right in both writing directions. + "margin-right": windows() ? "auto" : undefined, }} data-tauri-drag-region > diff --git a/packages/session-ui/src/v2/components/session-review-v2.css b/packages/session-ui/src/v2/components/session-review-v2.css index 557ef16f6db..ac899be9884 100644 --- a/packages/session-ui/src/v2/components/session-review-v2.css +++ b/packages/session-ui/src/v2/components/session-review-v2.css @@ -30,19 +30,19 @@ flex-shrink: 0; min-height: 0; overflow: hidden; - border-right: 1px solid var(--border-weaker-base, var(--v2-border-border-weak)); + border-inline-end: 1px solid var(--border-weaker-base, var(--v2-border-border-weak)); background: var(--v2-background-bg-base); } [data-component="session-review-v2-sidebar-root"] [data-slot="session-review-v2-sidebar"][aria-hidden="true"] { - border-right-width: 0; + border-inline-end-width: 0; } [data-component="session-review-v2-sidebar-root"] [data-slot="session-review-v2-sidebar"][data-transition]:not([data-resizing]) { transition: width 200ms cubic-bezier(0.22, 1, 0.36, 1), - border-right-width 200ms cubic-bezier(0.22, 1, 0.36, 1); + border-inline-end-width 200ms cubic-bezier(0.22, 1, 0.36, 1); } [data-component="session-review-v2-sidebar-root"] [data-slot="session-review-v2-sidebar-resize"] { @@ -63,7 +63,8 @@ align-items: center; justify-content: space-between; gap: 8px; - padding: 12px 16px 12px 8px; + padding-block: 12px; + padding-inline: 8px 16px; flex-shrink: 0; } @@ -79,7 +80,7 @@ [data-component="session-review-v2-sidebar-root"] [data-slot="session-review-v2-sidebar-title"]:not(:has([data-component="select-v2-root"])) { - margin-left: 8px; + margin-inline-start: 8px; } [data-component="session-review-v2-sidebar-root"] @@ -211,7 +212,7 @@ justify-content: space-between; gap: 8px; padding: 10px 12px; - padding-left: 8px; + padding-inline-start: 8px; flex-shrink: 0; border-bottom: 1px solid var(--border-weaker-base, var(--v2-border-border-weak)); } @@ -231,7 +232,7 @@ align-items: center; gap: 8px; min-width: 0; - padding-right: 8px; + padding-inline-end: 8px; } [data-component="session-review-v2"] [data-slot="session-review-v2-toolbar-title"] { @@ -331,7 +332,7 @@ [data-component="session-review-v2"] [data-slot="session-review-v2-file-diff"] { position: relative; flex-shrink: 0; - margin-left: auto; + margin-inline-start: auto; background-color: var(--v2-background-bg-base); &::before { @@ -339,17 +340,22 @@ position: absolute; top: -8px; bottom: -8px; - right: 100%; + inset-inline-end: 100%; width: 16px; z-index: 1; pointer-events: none; background: linear-gradient(90deg, transparent, var(--v2-background-bg-base)); } + &:dir(rtl)::before { + background: linear-gradient(270deg, transparent, var(--v2-background-bg-base)); + } + &::after { content: ""; position: absolute; - inset: -8px -16px -8px 0; + inset-block: -8px; + inset-inline: 0 -16px; z-index: 0; pointer-events: none; background-color: var(--v2-background-bg-base); diff --git a/packages/session-ui/src/v2/components/session-review-v2.tsx b/packages/session-ui/src/v2/components/session-review-v2.tsx index d2f3b6b8a6e..cb0a6bd8cb1 100644 --- a/packages/session-ui/src/v2/components/session-review-v2.tsx +++ b/packages/session-ui/src/v2/components/session-review-v2.tsx @@ -9,6 +9,7 @@ import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import type { SessionReviewDiffStyle } from "../../components/session-review" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { ScrollView } from "@opencode-ai/ui/scroll-view" +import { useLocale } from "@kobalte/core/i18n" import { makeEventListener } from "@solid-primitives/event-listener" import { Show, createEffect, createMemo, createSignal, type JSX } from "solid-js" import { getWorkerPool } from "../../pierre/worker" @@ -149,6 +150,7 @@ export function SessionReviewV2Sidebar(props: SessionReviewV2SidebarProps) { export function SessionReviewV2(props: SessionReviewV2Props) { const i18n = useI18n() + const locale = useLocale() createEffect(() => { getWorkerPool(props.diffStyle) @@ -175,6 +177,8 @@ export function SessionReviewV2(props: SessionReviewV2Props) { } const canCycle = () => props.files.length > 0 + const previousKey = () => (locale.direction() === "rtl" ? "ArrowRight" : "ArrowLeft") + const nextKey = () => (locale.direction() === "rtl" ? "ArrowLeft" : "ArrowRight") const showCollapsedMeta = () => props.sidebarOpen === false // Memoize slot getters so Show conditions do not instantiate throwaway elements. const title = createMemo(() => props.title) @@ -189,11 +193,11 @@ export function SessionReviewV2(props: SessionReviewV2Props) { // pane is mounted, but never while typing in an input or comment editor. makeEventListener(document, "keydown", (event) => { if (event.defaultPrevented || event.ctrlKey || event.metaKey || event.altKey) return - if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return + if (event.key !== previousKey() && event.key !== nextKey()) return const target = event.target if (target instanceof HTMLElement && (target.isContentEditable || target.closest("input, textarea, select"))) return if (!props.hasDiffs || !canCycle()) return - const file = event.key === "ArrowLeft" ? prev() : next() + const file = event.key === previousKey() ? prev() : next() if (!file) return event.preventDefault() cycle(file) @@ -221,7 +225,7 @@ export function SessionReviewV2(props: SessionReviewV2Props) { value={ <> {i18n.t("ui.sessionReviewV2.previousFile")} - + } > @@ -241,7 +245,7 @@ export function SessionReviewV2(props: SessionReviewV2Props) { value={ <> {i18n.t("ui.sessionReviewV2.nextFile")} - + } > diff --git a/packages/ui/src/components/dropdown-menu.css b/packages/ui/src/components/dropdown-menu.css index edc2eee9a40..f8a9fa60ee3 100644 --- a/packages/ui/src/components/dropdown-menu.css +++ b/packages/ui/src/components/dropdown-menu.css @@ -60,7 +60,7 @@ [data-slot="dropdown-menu-checkbox-item"], [data-slot="dropdown-menu-radio-item"] { - padding-right: 28px; + padding-inline-end: 28px; } [data-slot="dropdown-menu-sub-trigger"] { @@ -76,7 +76,7 @@ width: 16px; height: 16px; position: absolute; - right: 8px; + inset-inline-end: 8px; top: 50%; transform: translateY(-50%); } diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index b46cdc833b0..a16fad0c32a 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -154,7 +154,14 @@ export function Icon(props: IconProps) {
.sticky { - padding-right: 0; + padding-inline-end: 0; [data-component="icon-button-v2"] { position: relative; @@ -800,7 +809,7 @@ body[data-new-layout] &::after { content: ""; position: absolute; - left: 100%; + inset-inline-start: 100%; top: 50%; transform: translateY(-50%); width: 20px; @@ -811,21 +820,35 @@ body[data-new-layout] } } +body[data-new-layout] + #review-panel + [data-component="tabs"] + .session-review-v2-tabs-bar + [data-slot="tabs-list"] + > .sticky:not(.session-review-v2-sidebar-toggle-slot) { + left: auto; + right: auto; + inset-inline-end: 0; +} + body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-tabs-bar [data-slot="tabs-list"] > .session-review-v2-sidebar-toggle-slot.sticky { - padding-right: 0; + left: auto; + right: auto; + inset-inline-start: 0; + padding-inline-end: 0; &::before { content: ""; position: absolute; top: 50%; bottom: auto; - left: auto; - right: 100%; + inset-inline-start: auto; + inset-inline-end: 100%; transform: translateY(-50%); width: 12px; height: 28px; @@ -838,7 +861,8 @@ body[data-new-layout] content: ""; position: absolute; top: 50%; - left: 100%; + inset-inline-start: 100%; + inset-inline-end: auto; transform: translateY(-50%); width: 8px; height: 28px; @@ -846,6 +870,10 @@ body[data-new-layout] background: linear-gradient(90deg, var(--v2-background-bg-base), transparent); } + &:dir(rtl)::after { + background: linear-gradient(270deg, var(--v2-background-bg-base), transparent); + } + [data-component="icon-button-v2"]::after { display: none; } @@ -853,7 +881,7 @@ body[data-new-layout] body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-open-in-app-slot { flex-shrink: 0; - margin-left: auto; + margin-inline-start: auto; } body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-open-in-app { diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx index f46e9bfb30d..0dd5fb3e236 100644 --- a/packages/ui/src/components/tabs.tsx +++ b/packages/ui/src/components/tabs.tsx @@ -80,6 +80,7 @@ function TabsTrigger(props: ParentProps) { > Date: Tue, 4 Aug 2026 07:43:54 -0400 Subject: [PATCH 057/114] docs: add Poolside provider setup (#39187) --- packages/web/src/content/docs/providers.mdx | 64 +++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index 7931d4e03f6..96927f44d2a 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -1885,6 +1885,70 @@ OpenCode Zen is a list of tested and verified models provided by the OpenCode te --- +### Poolside + +[Poolside](https://poolside.ai) provides access to its models through an OpenAI-compatible API. + +1. Head over to [platform.poolside.ai](https://platform.poolside.ai), sign in, open the **API Keys** tab, and click **New key** to generate an API key. + +2. Run the `/connect` command and search for **Poolside**. + + ```txt + /connect + ``` + +3. Enter your Poolside API key. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Run the `/models` command to select a model. + + ```txt + /models + ``` + +#### Poolside deployment + +If your organization runs a Poolside deployment, configure it as a custom OpenAI-compatible provider. Store the API key or token from your Poolside administrator in `~/.secrets/poolside-key`. Use a unique provider ID and add the model IDs available from your deployment. + +```json title="opencode.json" +{ + "$schema": "https://opencode.ai/config.json", + "provider": { + "poolside-deployment": { + "npm": "@ai-sdk/openai-compatible", + "name": "Poolside deployment", + "options": { + "baseURL": "https://poolside.example.com/openai/v1", + "apiKey": "{file:~/.secrets/poolside-key}" + }, + "models": { + "": { + "name": "Poolside deployment model", + "reasoning": true, + "interleaved": { + "field": "reasoning_content" + } + } + } + } + } +} +``` + +Replace `poolside.example.com` with your deployment's host and `` with a model ID returned by its `/openai/v1/models` endpoint. Model availability and thinking behavior depend on the deployment's configuration. + +:::tip +You can also install [Poolside Agent CLI](https://github.com/poolsideai/pool) to use Poolside agents directly from your terminal. +::: + +--- + ### SAP AI Core SAP AI Core provides access to 40+ models from OpenAI, Anthropic, Google, Amazon, Meta, Mistral, and AI21 through a unified platform. From 44614c79c4a3aee2bd033f5d02b6145efca51ba3 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Tue, 4 Aug 2026 17:26:33 +0530 Subject: [PATCH 058/114] fix(acp): drain updates before end turn (#40422) --- packages/opencode/src/acp/event.ts | 97 ++++++++++-- packages/opencode/src/acp/service.ts | 70 +++++---- .../opencode/test/acp/service-session.test.ts | 148 ++++++++++++++++-- 3 files changed, 264 insertions(+), 51 deletions(-) diff --git a/packages/opencode/src/acp/event.ts b/packages/opencode/src/acp/event.ts index 7d05fa6ee58..dec34b8b662 100644 --- a/packages/opencode/src/acp/event.ts +++ b/packages/opencode/src/acp/event.ts @@ -40,7 +40,10 @@ export class Subscription { private readonly abort = new AbortController() private readonly shellSnapshots = new Map() private readonly toolStarts = new Set() + private readonly connectionWaiters = new Set<() => void>() + private readonly idleWaiters = new Map>>() private readonly permission: ACPPermission.Handler + private connected = false private started = false constructor( @@ -63,10 +66,35 @@ export class Subscription { stop() { this.abort.abort() + this.disconnected() + for (const resolve of this.connectionWaiters) resolve() + this.connectionWaiters.clear() + } + + async runUntilIdle(sessionId: string, request: () => Promise) { + await this.waitUntilConnected() + const waiter = signal() + const waiters = this.idleWaiters.get(sessionId) ?? new Set() + waiters.add(waiter) + this.idleWaiters.set(sessionId, waiters) + + try { + // Idle is queued after the turn's events, and this subscription awaits each update in order. + void waiter.promise.catch(() => {}) + const response = await request() + await waiter.promise + return response + } finally { + waiters.delete(waiter) + if (waiters.size === 0) this.idleWaiters.delete(sessionId) + } } async handle(event: Event) { switch (event.type) { + case "session.status": + if (event.properties.status.type === "idle") this.idle(event.properties.sessionID) + return case "permission.asked": this.permission.handle(event) return @@ -115,19 +143,51 @@ export class Subscription { private async run() { while (!this.abort.signal.aborted) { - const events = (await this.input.sdk.global.event({ - signal: this.abort.signal, - })) as GlobalEventStream - - for await (const event of events.stream) { - if (this.abort.signal.aborted) return - if (!event.payload) continue - await this.handle(event.payload).catch(() => {}) - } + await this.consume().catch(() => {}) + this.disconnected() if (!this.abort.signal.aborted) await new Promise((resolve) => setTimeout(resolve, 1000)) } } + private async consume() { + const events = (await this.input.sdk.global.event({ + signal: this.abort.signal, + })) as GlobalEventStream + this.connected = true + for (const resolve of this.connectionWaiters) resolve() + this.connectionWaiters.clear() + + for await (const event of events.stream) { + if (this.abort.signal.aborted) return + if (!event.payload) continue + await this.handle(event.payload).catch(() => {}) + } + } + + private async waitUntilConnected() { + while (!this.connected) { + if (this.abort.signal.aborted) throw new Error("ACP event subscription stopped") + await new Promise((resolve) => this.connectionWaiters.add(resolve)) + } + } + + private disconnected() { + if (!this.connected) return + this.connected = false + const error = new Error("ACP event stream disconnected") + for (const waiters of this.idleWaiters.values()) { + for (const waiter of waiters) waiter.reject(error) + } + this.idleWaiters.clear() + } + + private idle(sessionId: string) { + const waiters = this.idleWaiters.get(sessionId) + if (!waiters) return + this.idleWaiters.delete(sessionId) + for (const waiter of waiters) waiter.resolve() + } + private async handlePartUpdated(event: EventMessagePartUpdated) { const part = event.properties.part const sessionId = part.sessionID || event.properties.sessionID @@ -339,4 +399,23 @@ export class Subscription { } } +function signal() { + const state: { + resolve: () => void + reject: (reason?: unknown) => void + } = { + resolve: () => {}, + reject: () => {}, + } + const promise = new Promise((resolve, reject) => { + state.resolve = resolve + state.reject = reject + }) + return { + promise, + resolve: () => state.resolve(), + reject: (reason?: unknown) => state.reject(reason), + } +} + export * as ACPEvent from "./event" diff --git a/packages/opencode/src/acp/service.ts b/packages/opencode/src/acp/service.ts index 7257efa7658..fe295918978 100644 --- a/packages/opencode/src/acp/service.ts +++ b/packages/opencode/src/acp/service.ts @@ -88,6 +88,8 @@ export function make(input: { ? ACPEvent.start({ sdk: input.sdk, connection: input.connection, session }) : undefined if (events) input.eventSubscription?.(events) + const runUntilIdle = (sessionId: string, fn: () => Promise) => + events ? events.runUntilIdle(sessionId, fn) : fn() const initialize = Effect.fn("ACP.initialize")(function* (params: InitializeRequest) { const started = performance.now() @@ -504,19 +506,21 @@ export function make(input: { if (!command) { const response = yield* request( () => - input.sdk.session.prompt( - { - sessionID: current.id, - model: { - providerID: selected.providerID, - modelID: selected.modelID, + runUntilIdle(current.id, () => + input.sdk.session.prompt( + { + sessionID: current.id, + model: { + providerID: selected.providerID, + modelID: selected.modelID, + }, + ...(variant ? { variant } : {}), + parts, + ...(modeId ? { agent: modeId } : {}), + directory: current.cwd, }, - ...(variant ? { variant } : {}), - parts, - ...(modeId ? { agent: modeId } : {}), - directory: current.cwd, - }, - { throwOnError: true }, + { throwOnError: true }, + ), ), "session", ) @@ -528,17 +532,19 @@ export function make(input: { if (known) { const response = yield* request( () => - input.sdk.session.command( - { - sessionID: current.id, - command: known.name, - arguments: command.args, - model: `${selected.providerID}/${selected.modelID}`, - ...(variant ? { variant } : {}), - ...(modeId ? { agent: modeId } : {}), - directory: current.cwd, - }, - { throwOnError: true }, + runUntilIdle(current.id, () => + input.sdk.session.command( + { + sessionID: current.id, + command: known.name, + arguments: command.args, + model: `${selected.providerID}/${selected.modelID}`, + ...(variant ? { variant } : {}), + ...(modeId ? { agent: modeId } : {}), + directory: current.cwd, + }, + { throwOnError: true }, + ), ), "session", ) @@ -549,14 +555,16 @@ export function make(input: { if (command.name === "compact") { yield* request( () => - input.sdk.session.summarize( - { - sessionID: current.id, - directory: current.cwd, - providerID: selected.providerID, - modelID: selected.modelID, - }, - { throwOnError: true }, + runUntilIdle(current.id, () => + input.sdk.session.summarize( + { + sessionID: current.id, + directory: current.cwd, + providerID: selected.providerID, + modelID: selected.modelID, + }, + { throwOnError: true }, + ), ), "session", ) diff --git a/packages/opencode/test/acp/service-session.test.ts b/packages/opencode/test/acp/service-session.test.ts index 8dd25492c09..d784709dbb2 100644 --- a/packages/opencode/test/acp/service-session.test.ts +++ b/packages/opencode/test/acp/service-session.test.ts @@ -10,7 +10,7 @@ import type { SessionConfigSelectOption, SetSessionConfigOptionResponse, } from "@agentclientprotocol/sdk" -import type { AssistantMessage, OpencodeClient } from "@opencode-ai/sdk/v2" +import type { AssistantMessage, Event, OpencodeClient } from "@opencode-ai/sdk/v2" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { Effect } from "effect" @@ -24,6 +24,54 @@ const modelID = ModelV2.ID.make("test-model") const configuredModelID = ModelV2.ID.make("configured-model") const secondModelID = ModelV2.ID.make("second-model") +function createEventStream() { + const queue: Event[] = [] + const waiters: Array<(event: Event | undefined) => void> = [] + const push = (event: Event) => { + const waiter = waiters.shift() + if (waiter) return waiter(event) + queue.push(event) + } + const stream = async function* (signal?: AbortSignal) { + while (!signal?.aborted) { + const event = queue.shift() + if (event) { + yield { payload: event } + continue + } + const next = await new Promise((resolve) => { + waiters.push(resolve) + signal?.addEventListener("abort", () => resolve(undefined), { once: true }) + }) + if (!next) return + yield { payload: next } + } + } + return { push, stream } +} + +function idleEvent(sessionID: string): Event { + return { + id: `evt_idle_${sessionID}`, + type: "session.status", + properties: { + sessionID, + status: { type: "idle" }, + }, + } +} + +function deferred() { + const state: { resolve?: (value: A) => void } = {} + const promise = new Promise((resolve) => { + state.resolve = resolve + }) + return { + promise, + resolve: (value: A) => state.resolve?.(value), + } +} + const provider: Provider.Info = { id: providerID, name: "Test", @@ -147,6 +195,7 @@ describe("ACP service sessions", () => { options?: { abort?: (input: { sessionID: string }) => Promise<{ data: boolean }> prompt?: (input: unknown) => Promise<{ data: { info: ReturnType } }> + sessionUpdate?: (update: SessionNotification) => Promise }, ) => { const updates: SessionNotification[] = [] @@ -157,6 +206,7 @@ describe("ACP service sessions", () => { const commands: unknown[] = [] const summarizes: unknown[] = [] const usageUpdates: string[] = [] + const events = createEventStream() const sessions = Array.from({ length: 102 }, (_, index) => ({ id: `ses_${index + 1}`, directory: index % 2 === 0 ? "/workspace" : "/other", @@ -164,6 +214,9 @@ describe("ACP service sessions", () => { time: { created: index + 1, updated: index + 1 }, })) const sdk = { + global: { + event: (input?: { signal?: AbortSignal }) => Promise.resolve({ stream: events.stream(input?.signal) }), + }, config: { providers: () => Promise.resolve({ data: { providers: [provider], default: { test: modelID } } }), get: () => Promise.resolve({ data: {} }), @@ -196,11 +249,9 @@ describe("ACP service sessions", () => { data: input.directory ? sessions.filter((session) => session.directory === input.directory) : sessions, }), messages: () => Promise.resolve({ data: messages }), - prompt: - options?.prompt ?? - ((input: unknown) => { - prompts.push(input) - return Promise.resolve({ + prompt: async (input: { sessionID: string }) => { + const response = await (options?.prompt?.(input) ?? + Promise.resolve({ data: { info: assistantInfo({ input: 100, @@ -209,10 +260,14 @@ describe("ACP service sessions", () => { cache: { read: 11, write: 13 }, }), }, - }) - }), - command: (input: unknown) => { + })) + prompts.push(input) + events.push(idleEvent(input.sessionID)) + return response + }, + command: (input: { sessionID: string }) => { commands.push(input) + events.push(idleEvent(input.sessionID)) return Promise.resolve({ data: { info: assistantInfo({ @@ -224,8 +279,9 @@ describe("ACP service sessions", () => { }, }) }, - summarize: (input: unknown) => { + summarize: (input: { sessionID: string }) => { summarizes.push(input) + events.push(idleEvent(input.sessionID)) return Promise.resolve({ data: true }) }, abort: @@ -249,7 +305,7 @@ describe("ACP service sessions", () => { const connection = { sessionUpdate: (update: SessionNotification) => { updates.push(update) - return Promise.resolve() + return options?.sessionUpdate?.(update) ?? Promise.resolve() }, } as Pick const usage = UsageService.Service.of({ @@ -273,6 +329,7 @@ describe("ACP service sessions", () => { commands, summarizes, usageUpdates, + events, } } @@ -1018,6 +1075,75 @@ describe("ACP service sessions", () => { expect(usageUpdates).toEqual([session.sessionId]) }) + it("waits for queued session updates before returning end_turn", async () => { + const called = deferred() + const response = deferred<{ data: { info: ReturnType } }>() + const update = deferred() + const release = deferred() + const order: string[] = [] + const fixture = makeService([], { + prompt: () => { + called.resolve(undefined) + return response.promise + }, + sessionUpdate: (notification) => { + if (notification.update.sessionUpdate !== "agent_thought_chunk") return Promise.resolve() + update.resolve(undefined) + return release.promise.then(() => { + order.push("update") + }) + }, + }) + const session = await Effect.runPromise(fixture.service.newSession({ cwd: "/workspace", mcpServers: [] })) + const result = Effect.runPromise( + fixture.service.prompt({ sessionId: session.sessionId, prompt: [{ type: "text", text: "hello" }] }), + ).then((value) => { + order.push("response") + return value + }) + + await called.promise + fixture.events.push({ + id: "evt_part", + type: "message.part.updated", + properties: { + sessionID: session.sessionId, + time: Date.now(), + part: { + id: "part_reasoning", + sessionID: session.sessionId, + messageID: "msg_assistant", + type: "reasoning", + text: "", + time: { start: Date.now() }, + }, + }, + }) + fixture.events.push({ + id: "evt_delta", + type: "message.part.delta", + properties: { + sessionID: session.sessionId, + messageID: "msg_assistant", + partID: "part_reasoning", + field: "text", + delta: "thinking", + }, + }) + response.resolve({ + data: { + info: assistantInfo({ input: 1, output: 1, reasoning: 1, cache: { read: 0, write: 0 } }), + }, + }) + + await update.promise + expect(order).toEqual([]) + + release.resolve(undefined) + expect((await result).stopReason).toBe("end_turn") + expect(order).toEqual(["update", "response"]) + }) + it("maps assistant prompt errors to request errors instead of end turn", async () => { const { service } = makeService([], { prompt: () => From aefaf140c19e25494da27739ae979f31b8cfe474 Mon Sep 17 00:00:00 2001 From: opencode Date: Tue, 4 Aug 2026 12:12:30 +0000 Subject: [PATCH 059/114] sync release versions for v1.18.13 --- bun.lock | 56 ++++++++++----------- packages/app/package.json | 2 +- packages/cli/package.json | 2 +- packages/codemode/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/console/support/package.json | 2 +- packages/core/package.json | 2 +- packages/desktop/package.json | 2 +- packages/effect-drizzle-sqlite/package.json | 2 +- packages/effect-sqlite-node/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/function/package.json | 2 +- packages/http-recorder/package.json | 2 +- packages/llm/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/server/package.json | 2 +- packages/session-ui/package.json | 2 +- packages/slack/package.json | 2 +- packages/stats/app/package.json | 2 +- packages/stats/core/package.json | 2 +- packages/stats/server/package.json | 2 +- packages/tui/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 30 files changed, 57 insertions(+), 57 deletions(-) diff --git a/bun.lock b/bun.lock index 62dbf794652..0d08cb8a95a 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@corvu/drawer": "catalog:", "@dnd-kit/abstract": "0.5.0", @@ -96,7 +96,7 @@ }, "packages/cli": { "name": "@opencode-ai/cli", - "version": "1.18.12", + "version": "1.18.13", "bin": { "lildax": "./bin/lildax.cjs", }, @@ -144,7 +144,7 @@ }, "packages/codemode": { "name": "@opencode-ai/codemode", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "acorn": "8.15.0", "effect": "catalog:", @@ -158,7 +158,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -194,7 +194,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -221,7 +221,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.48", @@ -243,7 +243,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -267,7 +267,7 @@ }, "packages/console/support": { "name": "@opencode-ai/console-support", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@opencode-ai/console-core": "workspace:*", @@ -287,7 +287,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "1.18.12", + "version": "1.18.13", "bin": { "opencode": "./bin/opencode", }, @@ -381,7 +381,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@zip.js/zip.js": "2.7.62", "drizzle-orm": "catalog:", @@ -435,7 +435,7 @@ }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -449,7 +449,7 @@ }, "packages/effect-sqlite-node": { "name": "@opencode-ai/effect-sqlite-node", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "effect": "catalog:", }, @@ -461,7 +461,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@hono/standard-validator": "catalog:", "@opencode-ai/core": "workspace:*", @@ -493,7 +493,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -509,7 +509,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@effect/platform-node": "4.0.0-beta.83", "@effect/platform-node-shared": "4.0.0-beta.83", @@ -540,7 +540,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@opencode-ai/schema": "workspace:*", "@smithy/eventstream-codec": "4.2.14", @@ -559,7 +559,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.18.12", + "version": "1.18.13", "bin": { "opencode": "./bin/opencode", }, @@ -690,7 +690,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "workspace:*", @@ -766,7 +766,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "cross-spawn": "catalog:", }, @@ -781,7 +781,7 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/protocol": "workspace:*", @@ -796,7 +796,7 @@ }, "packages/session-ui": { "name": "@opencode-ai/session-ui", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz", @@ -836,7 +836,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -849,7 +849,7 @@ }, "packages/stats/app": { "name": "@opencode-ai/stats-app", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@ibm/plex": "6.4.1", "@kobalte/core": "catalog:", @@ -883,7 +883,7 @@ }, "packages/stats/core": { "name": "@opencode-ai/stats-core", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@aws-sdk/client-athena": "3.933.0", "@planetscale/database": "1.19.0", @@ -902,7 +902,7 @@ }, "packages/stats/server": { "name": "@opencode-ai/stats-server", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@aws-sdk/client-firehose": "3.933.0", "@effect/platform-node": "catalog:", @@ -944,7 +944,7 @@ }, "packages/tui": { "name": "@opencode-ai/tui", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", @@ -971,7 +971,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@kobalte/core": "catalog:", "@pierre/diffs": "catalog:", @@ -1022,7 +1022,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 7eb870b5854..1ad643f07a6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.18.12", + "version": "1.18.13", "description": "", "type": "module", "exports": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 1f2ff003a16..66953ce4a96 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/cli", - "version": "1.18.12", + "version": "1.18.13", "type": "module", "license": "MIT", "bin": { diff --git a/packages/codemode/package.json b/packages/codemode/package.json index 9430fb40a8f..ee9ccd423c2 100644 --- a/packages/codemode/package.json +++ b/packages/codemode/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/codemode", - "version": "1.18.12", + "version": "1.18.13", "description": "Effect-native confined code execution over schema-described tools", "private": true, "type": "module", diff --git a/packages/console/app/package.json b/packages/console/app/package.json index ac5acecc7ad..6cb9d9dfa66 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.18.12", + "version": "1.18.13", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 8da3fdbfc2f..21948517f68 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.18.12", + "version": "1.18.13", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index aec90aede30..f9e93a0f4ee 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.18.12", + "version": "1.18.13", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 0787476c4a4..925f89a2e70 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.18.12", + "version": "1.18.13", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/support/package.json b/packages/console/support/package.json index 23d05176581..72d10956129 100644 --- a/packages/console/support/package.json +++ b/packages/console/support/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-support", - "version": "1.18.12", + "version": "1.18.13", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 3dffa1fcb3d..31ae85dfa56 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.12", + "version": "1.18.13", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index f7828298f42..22a4a4e6376 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.18.12", + "version": "1.18.13", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/effect-drizzle-sqlite/package.json b/packages/effect-drizzle-sqlite/package.json index 60c48a8c7c1..6061ac103f5 100644 --- a/packages/effect-drizzle-sqlite/package.json +++ b/packages/effect-drizzle-sqlite/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.12", + "version": "1.18.13", "name": "@opencode-ai/effect-drizzle-sqlite", "type": "module", "license": "MIT", diff --git a/packages/effect-sqlite-node/package.json b/packages/effect-sqlite-node/package.json index f4387259746..ab44b9add75 100644 --- a/packages/effect-sqlite-node/package.json +++ b/packages/effect-sqlite-node/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.12", + "version": "1.18.13", "name": "@opencode-ai/effect-sqlite-node", "type": "module", "license": "MIT", diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 492176fb66b..d5d71410634 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.18.12", + "version": "1.18.13", "private": true, "type": "module", "license": "MIT", diff --git a/packages/function/package.json b/packages/function/package.json index c06de12d562..94f66edfe56 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.18.12", + "version": "1.18.13", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json index aeab78cb4a6..f5120021ac2 100644 --- a/packages/http-recorder/package.json +++ b/packages/http-recorder/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.12", + "version": "1.18.13", "name": "@opencode-ai/http-recorder", "description": "Record and replay Effect HTTP client traffic with deterministic cassettes", "type": "module", diff --git a/packages/llm/package.json b/packages/llm/package.json index 28f9751ac35..50f6cf568f6 100644 --- a/packages/llm/package.json +++ b/packages/llm/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.12", + "version": "1.18.13", "name": "@opencode-ai/llm", "type": "module", "license": "MIT", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 663df3898da..ed11f8ca933 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.12", + "version": "1.18.13", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 60918bdcfc2..7b46a9ccf39 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.18.12", + "version": "1.18.13", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index a42f9915251..007e3f571ac 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.18.12", + "version": "1.18.13", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 0d8d4cf0366..f7c7a0f3037 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/server", - "version": "1.18.12", + "version": "1.18.13", "private": true, "type": "module", "license": "MIT", diff --git a/packages/session-ui/package.json b/packages/session-ui/package.json index da940b879e5..1fbf52314a9 100644 --- a/packages/session-ui/package.json +++ b/packages/session-ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/session-ui", - "version": "1.18.12", + "version": "1.18.13", "private": true, "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 2f5eb2544fd..af88eb2aec4 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.18.12", + "version": "1.18.13", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/stats/app/package.json b/packages/stats/app/package.json index 6aba2a05a52..af8f31e739c 100644 --- a/packages/stats/app/package.json +++ b/packages/stats/app/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-app", - "version": "1.18.12", + "version": "1.18.13", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/core/package.json b/packages/stats/core/package.json index d650e092ee2..a1c22694fde 100644 --- a/packages/stats/core/package.json +++ b/packages/stats/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-core", - "version": "1.18.12", + "version": "1.18.13", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/server/package.json b/packages/stats/server/package.json index 725d79f3355..811a597fb61 100644 --- a/packages/stats/server/package.json +++ b/packages/stats/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-server", - "version": "1.18.12", + "version": "1.18.13", "private": true, "type": "module", "license": "MIT", diff --git a/packages/tui/package.json b/packages/tui/package.json index 5021769870f..43c9cd64182 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/tui", - "version": "1.18.12", + "version": "1.18.13", "private": true, "type": "module", "license": "MIT", diff --git a/packages/ui/package.json b/packages/ui/package.json index 7409562b7e6..ae2a57b4362 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.18.12", + "version": "1.18.13", "type": "module", "license": "MIT", "repository": { diff --git a/packages/web/package.json b/packages/web/package.json index ceb878ef280..4f781d1588b 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.18.12", + "version": "1.18.13", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index ac8ab9e3670..49b3369e244 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.18.12", + "version": "1.18.13", "publisher": "sst-dev", "repository": { "type": "git", From 703d09f3069e9338f701b7ceee319a41c7dccb9c Mon Sep 17 00:00:00 2001 From: James Murdza Date: Tue, 4 Aug 2026 20:43:45 +0700 Subject: [PATCH 060/114] fix(server): don't forward host directory to remote workspace (#40136) Co-authored-by: Claude Opus 4.8 --- packages/opencode/src/server/shared/workspace-routing.ts | 7 +++++++ packages/opencode/test/server/workspace-routing.test.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/packages/opencode/src/server/shared/workspace-routing.ts b/packages/opencode/src/server/shared/workspace-routing.ts index 0edfb2b3054..de4dc672031 100644 --- a/packages/opencode/src/server/shared/workspace-routing.ts +++ b/packages/opencode/src/server/shared/workspace-routing.ts @@ -34,5 +34,12 @@ export function workspaceProxyURL(target: string | URL, requestURL: URL) { proxyURL.search = requestURL.search proxyURL.hash = requestURL.hash proxyURL.searchParams.delete("workspace") + // The `directory` param is the *host's* working directory (e.g. a Windows + // path like `F:\proj`). It is meaningless — and dangerous — on the remote: + // the sandbox would `path.resolve` it against its own cwd, producing a bogus + // path like `/home/daytona/workspace/repo/F:\proj` that does not exist and + // crashes prompt handling. Drop it so the remote falls back to its own + // project root. This mirrors ProxyUtil.headers stripping `x-opencode-directory`. + proxyURL.searchParams.delete("directory") return proxyURL } diff --git a/packages/opencode/test/server/workspace-routing.test.ts b/packages/opencode/test/server/workspace-routing.test.ts index 9ae0f3e632c..29c22038c3b 100644 --- a/packages/opencode/test/server/workspace-routing.test.ts +++ b/packages/opencode/test/server/workspace-routing.test.ts @@ -80,6 +80,13 @@ describe("workspaceProxyURL", () => { expect(result.searchParams.get("keep")).toBe("yes") }) + test("strips the host directory param so the remote resolves its own root", () => { + const url = new URL("http://localhost/session/abc?directory=F%3A%5Cproj&keep=yes") + const result = workspaceProxyURL("http://remote:8080/base", url) + expect(result.searchParams.get("directory")).toBeNull() + expect(result.searchParams.get("keep")).toBe("yes") + }) + test("preserves hash from request", () => { const url = new URL("http://localhost/page#section") const result = workspaceProxyURL("http://remote:8080", url) From f0afb6750e63ee0a60b052914531bde0afb9bc2b Mon Sep 17 00:00:00 2001 From: James Murdza Date: Tue, 4 Aug 2026 20:43:59 +0700 Subject: [PATCH 061/114] fix(server): log upstream 5xx bodies from proxied workspace requests (#40135) Co-authored-by: Claude Opus 4.8 --- .../instance/httpapi/middleware/proxy.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/proxy.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/proxy.ts index e5362f8cbe1..d21ab5647c1 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/proxy.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/proxy.ts @@ -97,6 +97,29 @@ export function http( headers.delete("content-encoding") headers.delete("content-length") + // An upstream 5xx from a remote workspace sandbox arrives here as an opaque + // status — its real cause (and log line) live only inside the sandbox. Buffer + // the small error body, log it locally so it shows up in the host's log, and + // forward it unchanged (preserving content-type so the client can still parse + // the structured error, e.g. its `ref`). + if (response.status >= 500) { + const body = yield* response.text.pipe(Effect.catch(() => Effect.succeed(""))) + const contentType = response.headers["content-type"] ?? "application/json" + headers.delete("content-type") + yield* Effect.logError("workspace proxy upstream error", { + url: url.toString(), + method: request.method, + status: response.status, + body: body.slice(0, 2000), + }) + return HttpServerResponse.text(body, { + status: response.status, + statusText: statusText(response), + headers, + contentType, + }) + } + return HttpServerResponse.stream(response.stream.pipe(Stream.catchCause(() => Stream.empty)), { status: response.status, statusText: statusText(response), From 98dd65cd60f72bb257ae894f655d3cfe67c961d5 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:23:06 +0000 Subject: [PATCH 062/114] fix(app): collapse deletion-only edit parts (#40536) Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> --- .../src/components/message-part.tsx | 11 +--- .../src/components/part-default-open.test.ts | 66 +++++++++++++++++++ .../src/components/part-default-open.ts | 26 ++++++++ 3 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 packages/session-ui/src/components/part-default-open.test.ts create mode 100644 packages/session-ui/src/components/part-default-open.ts diff --git a/packages/session-ui/src/components/message-part.tsx b/packages/session-ui/src/components/message-part.tsx index c5e6cafdae5..28de8847874 100644 --- a/packages/session-ui/src/components/message-part.tsx +++ b/packages/session-ui/src/components/message-part.tsx @@ -61,6 +61,7 @@ import { TextShimmer } from "@opencode-ai/ui/text-shimmer" import { AnimatedCountList } from "./tool-count-summary" import { ToolStatusTitle } from "./tool-status-title" import { patchFiles } from "./apply-patch-file" +import { partDefaultOpen } from "./part-default-open" import { animate } from "motion" import { attached, inline, kind, typeLabel } from "./message-file" import { readPartText } from "./message-part-text" @@ -718,15 +719,7 @@ export function renderable(part: PartType, showReasoningSummaries = true) { return !!PART_MAPPING[part.type] } -function toolDefaultOpen(tool: string, shell = false, edit = false) { - if (tool === "bash" || tool === "shell") return shell - if (tool === "edit" || tool === "write" || tool === "patch" || tool === "apply_patch") return edit -} - -export function partDefaultOpen(part: PartType, shell = false, edit = false) { - if (part.type !== "tool") return - return toolDefaultOpen(part.tool, shell, edit) -} +export { partDefaultOpen } from "./part-default-open" export function AssistantParts(props: { messages: AssistantMessage[] diff --git a/packages/session-ui/src/components/part-default-open.test.ts b/packages/session-ui/src/components/part-default-open.test.ts new file mode 100644 index 00000000000..2fc58154395 --- /dev/null +++ b/packages/session-ui/src/components/part-default-open.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, test } from "bun:test" +import type { Part as PartType } from "@opencode-ai/sdk/v2" +import { partDefaultOpen } from "./part-default-open" + +describe("partDefaultOpen", () => { + test("keeps edited files expanded when enabled", () => { + expect(partDefaultOpen(tool("edit", { filediff: { additions: 1, deletions: 1 } }), false, true)).toBe(true) + }) + + test("collapses deletion-only edits when enabled", () => { + expect(partDefaultOpen(tool("edit", { filediff: { additions: 0, deletions: 1_200 } }), false, true)).toBe(false) + }) + + test("collapses patches containing only deleted files when enabled", () => { + expect( + partDefaultOpen( + tool("apply_patch", { + files: [ + { filePath: "one.ts", type: "delete" }, + { filePath: "two.ts", type: "delete" }, + ], + }), + false, + true, + ), + ).toBe(false) + }) + + test("keeps mixed patches expanded when enabled", () => { + expect( + partDefaultOpen( + tool("apply_patch", { + files: [ + { filePath: "one.ts", type: "delete" }, + { filePath: "two.ts", type: "update" }, + ], + }), + false, + true, + ), + ).toBe(true) + }) + + test("preserves shell defaults", () => { + expect(partDefaultOpen(tool("shell", {}), true, false)).toBe(true) + }) +}) + +function tool(name: string, metadata: Record): PartType { + return { + id: `part_${name}`, + sessionID: "session", + messageID: "message", + type: "tool", + callID: `call_${name}`, + tool: name, + state: { + status: "completed", + input: {}, + output: "", + title: name, + metadata, + time: { start: 0, end: 1 }, + }, + } +} diff --git a/packages/session-ui/src/components/part-default-open.ts b/packages/session-ui/src/components/part-default-open.ts new file mode 100644 index 00000000000..57a66728457 --- /dev/null +++ b/packages/session-ui/src/components/part-default-open.ts @@ -0,0 +1,26 @@ +import type { Part as PartType, ToolPart } from "@opencode-ai/sdk/v2" + +function deletionOnly(part: ToolPart) { + if (!("metadata" in part.state)) return false + const metadata = part.state.metadata + if (!metadata) return false + + const files = metadata.files + if (Array.isArray(files) && files.length > 0) { + return files.every((file) => !!file && typeof file === "object" && "type" in file && file.type === "delete") + } + + const filediff = metadata.filediff + if (!filediff || typeof filediff !== "object") return false + if (!("additions" in filediff) || !("deletions" in filediff)) return false + return filediff.additions === 0 && typeof filediff.deletions === "number" && filediff.deletions > 0 +} + +export function partDefaultOpen(part: PartType, shell = false, edit = false) { + if (part.type !== "tool") return + if (part.tool === "bash" || part.tool === "shell") return shell + if (part.tool === "edit" || part.tool === "write" || part.tool === "patch" || part.tool === "apply_patch") { + if (!edit) return false + return !deletionOnly(part) + } +} From 66fdd51f0d6db8e47e876721c855ea155043b74c Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:01:10 +1000 Subject: [PATCH 063/114] docs: add RTL development skill (#40543) --- .../skills/rtl-aware-development/SKILL.md | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .opencode/skills/rtl-aware-development/SKILL.md diff --git a/.opencode/skills/rtl-aware-development/SKILL.md b/.opencode/skills/rtl-aware-development/SKILL.md new file mode 100644 index 00000000000..595c981ead8 --- /dev/null +++ b/.opencode/skills/rtl-aware-development/SKILL.md @@ -0,0 +1,63 @@ +--- +name: rtl-aware-development +description: OpenCode Desktop should be RTL-aware. Use when implementing or reviewing RTL/LTR behavior in the web app, desktop app, CSS, menus, scrolling, resizing, icons, mixed-direction text, or Electron title bars. +--- + +# RTL-Aware Development + +Treat direction as independent from language. Test English in both directions as well as real RTL and mixed-script content. + +## Guidelines + +- Set `lang` and `dir` on the document, and propagate direction through component providers used by portaled menus and popovers. Do not change the selected locale merely to force RTL. +- Keep DOM and focus order semantic. Flexbox and Grid already follow `dir`; do not add `row-reverse`, CSS `order`, or reversed markup just to mirror a layout. +- Prefer logical CSS for semantic layout. Reserve physical coordinates for pointer positions, canvas geometry, native window controls, and other genuinely physical placement. + +```css +/* Avoid */ +padding-left: 12px; +right: 0; +border-right: 1px solid; +text-align: left; + +/* Prefer */ +padding-inline-start: 12px; +inset-inline-end: 0; +border-inline-end: 1px solid; +text-align: start; +``` + +- Isolate mixed-direction text. Use `dir="auto"` or `` for unknown text; keep code, URLs, IDs, and filesystem paths LTR without forcing the surrounding component LTR. + +```html +README.md C:\src\app.ts +``` + +- Mirror directional meaning, not every image. Back/forward, previous/next, disclosure, indentation, and directional progress may need mirroring. Do not mirror brands, clocks, media controls, charts, or text. Reverse physical gradients, `translateX`, SVG transforms, and animation deltas explicitly. +- Map interactions through direction. `clientX` remains physical; resizing a logical edge needs an RTL-aware delta. Logical previous/next keyboard controls may swap ArrowLeft/ArrowRight. Follow the relevant WAI-ARIA widget pattern. +- Do not assume LTR scrolling. RTL `scrollLeft` can start at `0` and become negative. Prefer `scrollIntoView({ inline: "nearest" })` or a tested direction-normalizing helper. +- For Electron title bars, prefer native caption controls and use `titleBarOverlay` plus `env(titlebar-area-*)` for the safe content rectangle. Keep Windows/macOS native-control avoidance and `trafficLightPosition` physical; keep app navigation inside that rectangle logical. Mark interactive titlebar children `app-region: no-drag`. +- Verify behavior, not screenshots alone. Check computed styles, pseudo-element geometry, hit zones, focus order, keyboard behavior, submenu direction, zoom/scaling, and both LTR and RTL scroll endpoints. + +## Test Matrix + +- English + LTR +- English + forced RTL +- A real RTL locale + RTL +- Mixed RTL/LTR content, long labels, numbers, code, and paths +- Keyboard, pointer resize, scrolling, menus/submenus, and Electron titlebar controls in both directions + +## References + +- [RTL Styling 101, Ahmad Shadeed](https://rtlstyling.com/posts/rtl-styling/) +- [CSS-Tricks: RTL Styling 101](https://css-tricks.com/rtl-styling-101/) +- [CSS-Tricks: CSS Logical Properties and Values](https://css-tricks.com/css-logical-properties-and-values/) +- [W3C: Structural markup and right-to-left text](https://www.w3.org/International/questions/qa-html-dir) +- [W3C: Inline bidirectional markup](https://www.w3.org/International/articles/inline-bidi-markup/) +- [MDN: CSS logical properties and values](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Logical_properties_and_values) +- [MDN: `dir`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/dir) +- [MDN: `scrollLeft`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft) +- [web.dev: Logical properties](https://web.dev/learn/css/logical-properties/) +- [Electron: Custom title bar](https://www.electronjs.org/docs/latest/tutorial/custom-title-bar) +- [WAI-ARIA: Window splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/) +- [Kobalte: I18n Provider](https://kobalte.dev/docs/core/components/i18n-provider/) From cb88db6ce31dfbf52b2462258b42a607758e200a Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:18:44 -0500 Subject: [PATCH 064/114] tweak(opencode): make xAI OAuth device-only to reduce confusion w/ headless environments (#40537) --- packages/opencode/src/plugin/xai.ts | 293 +------------------- packages/opencode/test/plugin/xai.test.ts | 37 +-- packages/web/src/content/docs/providers.mdx | 30 +- 3 files changed, 15 insertions(+), 345 deletions(-) diff --git a/packages/opencode/src/plugin/xai.ts b/packages/opencode/src/plugin/xai.ts index 23233b5df79..a455121d6ae 100644 --- a/packages/opencode/src/plugin/xai.ts +++ b/packages/opencode/src/plugin/xai.ts @@ -1,14 +1,9 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin" import { OAUTH_DUMMY_KEY } from "../auth" -import { createServer } from "http" import { InstallationVersion } from "@opencode-ai/core/installation/version" -import { OauthCallbackPage } from "@opencode-ai/core/oauth/page" -// Public Grok-CLI OAuth client. xAI's auth server rejects loopback OAuth from -// non-allowlisted clients, so we reuse the Grok-CLI client_id that xAI ships -// for desktop OAuth flows. Source of truth: hermes-agent PR #26534. +// Public Grok-CLI OAuth client. const CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828" -const AUTHORIZE_URL = "https://auth.x.ai/oauth2/authorize" const TOKEN_URL = "https://auth.x.ai/oauth2/token" // RFC 8628 device authorization grant. Confirmed exposed by xAI's // /.well-known/openid-configuration as `device_authorization_endpoint` @@ -30,51 +25,15 @@ const DEVICE_CODE_SLOW_DOWN_INCREMENT_MS = 5_000 const DEVICE_CODE_DEFAULT_EXPIRES_MS = 5 * 60 * 1000 const OAUTH_POLLING_SAFETY_MARGIN_MS = 3_000 -// xAI rejects redirect_uris that don't match what was registered for the -// Grok-CLI client. The host:port pair is part of the registration, so we have -// to bind the loopback server to this exact port. -const OAUTH_HOST = "127.0.0.1" -const OAUTH_PORT = 56121 -const OAUTH_REDIRECT_PATH = "/callback" -const REDIRECT_URI = `http://${OAUTH_HOST}:${OAUTH_PORT}${OAUTH_REDIRECT_PATH}` - // Refresh the access token a little before it actually expires so a single // long-running tool call doesn't have to recover from a mid-flight 401. const ACCESS_TOKEN_REFRESH_SKEW_MS = 120_000 interface XaiAuthPluginOptions { - authorizeUrl?: string tokenUrl?: string deviceAuthorizationUrl?: string } -interface PkceCodes { - verifier: string - challenge: string -} - -async function generatePKCE(): Promise { - const verifier = generateRandomString(64) - const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)) - return { verifier, challenge: base64UrlEncode(hash) } -} - -function generateRandomString(length: number): string { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" - return Array.from(crypto.getRandomValues(new Uint8Array(length))) - .map((b) => chars[b % chars.length]) - .join("") -} - -function base64UrlEncode(buffer: ArrayBuffer): string { - const binary = String.fromCharCode(...new Uint8Array(buffer)) - return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") -} - -function generateState(): string { - return base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)).buffer) -} - interface TokenResponse { access_token: string refresh_token: string @@ -115,55 +74,6 @@ export function accessTokenIsExpiring( } } -export function buildAuthorizeUrl( - pkce: PkceCodes, - state: string, - nonce: string, - options: XaiAuthPluginOptions = {}, -): string { - // `plan=generic` opts the consent screen into xAI's generic OAuth plan tier; - // without it, accounts.x.ai rejects loopback OAuth from non-allowlisted - // clients. `referrer=opencode` lets xAI attribute opencode-originated - // logins in their OAuth server logs (best-effort attribution while we - // continue to reuse the Grok-CLI client_id). - const params = new URLSearchParams({ - response_type: "code", - client_id: CLIENT_ID, - redirect_uri: REDIRECT_URI, - scope: SCOPE, - code_challenge: pkce.challenge, - code_challenge_method: "S256", - state, - nonce, - plan: "generic", - referrer: "opencode", - }) - return `${options.authorizeUrl ?? AUTHORIZE_URL}?${params.toString()}` -} - -async function exchangeCodeForTokens( - code: string, - pkce: PkceCodes, - options: XaiAuthPluginOptions = {}, -): Promise { - const response = await fetch(options.tokenUrl ?? TOKEN_URL, { - method: "POST", - headers: authHeaders(), - body: new URLSearchParams({ - grant_type: "authorization_code", - code, - redirect_uri: REDIRECT_URI, - client_id: CLIENT_ID, - code_verifier: pkce.verifier, - }).toString(), - }) - if (!response.ok) { - const detail = await response.text().catch(() => "") - throw new Error(`xAI token exchange failed (${response.status})${detail ? `: ${detail}` : ""}`) - } - return response.json() as Promise -} - async function refreshAccessToken(refreshToken: string, options: XaiAuthPluginOptions = {}): Promise { const response = await fetch(options.tokenUrl ?? TOKEN_URL, { method: "POST", @@ -202,6 +112,7 @@ export async function requestDeviceCode(options: XaiAuthPluginOptions = {}): Pro body: new URLSearchParams({ client_id: CLIENT_ID, scope: SCOPE, + referrer: "opencode", }).toString(), }) if (!response.ok) { @@ -285,170 +196,6 @@ export async function pollDeviceCodeToken( throw new Error("xAI device authorization timed out") } -// CORS allowlist for the loopback callback. The redirect_uri itself is -// already bound to 127.0.0.1 and gated by PKCE+state, so we only accept -// xAI's own auth origins for additional defense-in-depth on the OPTIONS -// preflight. -const CORS_ALLOWED_ORIGINS = new Set(["https://accounts.x.ai", "https://auth.x.ai"]) - -interface PendingOAuth { - pkce: PkceCodes - state: string - resolve: (tokens: TokenResponse) => void - reject: (error: Error) => void -} - -let oauthServer: ReturnType | undefined -let pendingOAuth: PendingOAuth | undefined - -async function startOAuthServer(): Promise<{ port: number; redirectUri: string }> { - if (oauthServer) return { port: OAUTH_PORT, redirectUri: REDIRECT_URI } - - const server = createServer((req, res) => { - const reqUrl = req.url || "/" - const url = new URL(reqUrl, `http://${OAUTH_HOST}:${OAUTH_PORT}`) - - const origin = req.headers["origin"] - const allowOrigin = typeof origin === "string" && CORS_ALLOWED_ORIGINS.has(origin) ? origin : "" - if (allowOrigin) { - res.setHeader("Access-Control-Allow-Origin", allowOrigin) - res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS") - res.setHeader("Access-Control-Allow-Headers", "Content-Type") - res.setHeader("Access-Control-Allow-Private-Network", "true") - res.setHeader("Vary", "Origin") - } - - if (req.method === "OPTIONS") { - res.writeHead(204) - res.end() - return - } - - if (url.pathname === OAUTH_REDIRECT_PATH) { - const code = url.searchParams.get("code") - const state = url.searchParams.get("state") - const error = url.searchParams.get("error") - const errorDescription = url.searchParams.get("error_description") - - if (error) { - const errorMsg = errorDescription || error - pendingOAuth?.reject(new Error(errorMsg)) - pendingOAuth = undefined - res.writeHead(200, { "Content-Type": "text/html" }) - res.end(OauthCallbackPage.error(errorMsg, { provider: "xAI" })) - return - } - - if (!code) { - const errorMsg = "Missing authorization code" - pendingOAuth?.reject(new Error(errorMsg)) - pendingOAuth = undefined - res.writeHead(400, { "Content-Type": "text/html" }) - res.end(OauthCallbackPage.error(errorMsg, { provider: "xAI" })) - return - } - - if (!pendingOAuth || state !== pendingOAuth.state) { - const errorMsg = "Invalid state - potential CSRF attack" - pendingOAuth?.reject(new Error(errorMsg)) - pendingOAuth = undefined - res.writeHead(400, { "Content-Type": "text/html" }) - res.end(OauthCallbackPage.error(errorMsg, { provider: "xAI" })) - return - } - - const current = pendingOAuth - pendingOAuth = undefined - - exchangeCodeForTokens(code, current.pkce) - .then((tokens) => current.resolve(tokens)) - .catch((err) => current.reject(err)) - - res.writeHead(200, { "Content-Type": "text/html" }) - res.end(OauthCallbackPage.success({ provider: "xAI" })) - return - } - - if (url.pathname === "/cancel") { - pendingOAuth?.reject(new Error("Login cancelled")) - pendingOAuth = undefined - res.writeHead(200) - res.end("Login cancelled") - return - } - - res.writeHead(404) - res.end("Not found") - }) - - // listen() failures (e.g. EADDRINUSE because Grok-CLI is bound to the same - // pinned port) must clear `oauthServer` and remove our error listener, - // otherwise the next startOAuthServer() short-circuits on the truthy check - // and returns a redirect_uri pointing at nothing. - await new Promise((resolve, reject) => { - const onError = (err: Error) => { - oauthServer = undefined - reject(err) - } - server.once("error", onError) - server.listen(OAUTH_PORT, OAUTH_HOST, () => { - server.removeListener("error", onError) - // After listen() succeeds, install a permanent log-only listener so - // that subsequent server errors (e.g. accept() failures, socket-level - // errors) don't trip Node's default "unhandled error event = throw" - // behavior and crash the entire opencode process. Matches the silent- - // swallow behavior the Codex plugin gets from its permanent - // `oauthServer!.on("error", reject)`. - resolve() - }) - oauthServer = server - }) - - return { port: OAUTH_PORT, redirectUri: REDIRECT_URI } -} - -function stopOAuthServer() { - if (oauthServer) { - oauthServer.close() - oauthServer = undefined - } -} - -function waitForOAuthCallback(pkce: PkceCodes, state: string): Promise { - // A previous in-flight authorize() that the user abandoned (or that is - // being superseded by a fresh attempt) still owns `pendingOAuth`. Reject - // it eagerly so its caller stops waiting on a state value that can never - // match the next callback. - if (pendingOAuth) { - pendingOAuth.reject(new Error("Superseded by a newer xAI authorize request")) - pendingOAuth = undefined - } - return new Promise((resolve, reject) => { - const timeout = setTimeout( - () => { - if (pendingOAuth) { - pendingOAuth = undefined - reject(new Error("OAuth callback timeout - authorization took too long")) - } - }, - 5 * 60 * 1000, - ) - - pendingOAuth = { - pkce, - state, - resolve: (tokens) => { - clearTimeout(timeout) - resolve(tokens) - }, - reject: (error) => { - clearTimeout(timeout) - reject(error) - }, - } - }) -} - interface RefreshResult { access: string refresh: string @@ -548,40 +295,6 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp } }, methods: [ - { - label: "xAI Grok OAuth (SuperGrok Subscription)", - type: "oauth", - authorize: async () => { - await startOAuthServer() - const pkce = await generatePKCE() - const state = generateState() - const nonce = generateState() - const authUrl = buildAuthorizeUrl(pkce, state, nonce, options) - - const callbackPromise = waitForOAuthCallback(pkce, state) - - return { - url: authUrl, - instructions: "Complete authorization in your browser. This window will close automatically.", - method: "auto" as const, - callback: async () => { - try { - const tokens = await callbackPromise - return { - type: "success" as const, - refresh: tokens.refresh_token, - access: tokens.access_token, - expires: Date.now() + (tokens.expires_in ?? 3600) * 1000, - } - } catch (err) { - return { type: "failed" as const } - } finally { - stopOAuthServer() - } - }, - } - }, - }, { // RFC 8628 device-code flow. The CLI prints a verification URL // and a short user_code that the user enters in a browser on @@ -591,7 +304,7 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp // user's browser. Defends the only attack surface (the polling // loop) with the standard authorization_pending / slow_down // backoff and a hard deadline from xAI's `expires_in`. - label: "xAI Grok OAuth (Headless / Remote / VPS)", + label: "SuperGrok Subscription", type: "oauth", authorize: async () => { const device = await requestDeviceCode(options) diff --git a/packages/opencode/test/plugin/xai.test.ts b/packages/opencode/test/plugin/xai.test.ts index 4139471a8bd..2339a07e91c 100644 --- a/packages/opencode/test/plugin/xai.test.ts +++ b/packages/opencode/test/plugin/xai.test.ts @@ -1,7 +1,6 @@ import { describe, expect, test } from "bun:test" import { accessTokenIsExpiring, - buildAuthorizeUrl, pollDeviceCodeToken, requestDeviceCode, XaiAuthPlugin, @@ -76,32 +75,6 @@ describe("plugin.xai", () => { }) }) - describe("buildAuthorizeUrl", () => { - const pkce = { verifier: "ver", challenge: "chal" } - - test("includes required OAuth + PKCE + OIDC params", () => { - const url = new URL(buildAuthorizeUrl(pkce, "state-abc", "nonce-xyz")) - const params = url.searchParams - - expect(url.origin + url.pathname).toBe("https://auth.x.ai/oauth2/authorize") - expect(params.get("response_type")).toBe("code") - expect(params.get("client_id")).toBe("b1a00492-073a-47ea-816f-4c329264a828") - expect(params.get("redirect_uri")).toBe("http://127.0.0.1:56121/callback") - expect(params.get("scope")).toBe("openid profile email offline_access grok-cli:access api:access") - expect(params.get("code_challenge")).toBe("chal") - expect(params.get("code_challenge_method")).toBe("S256") - expect(params.get("state")).toBe("state-abc") - expect(params.get("nonce")).toBe("nonce-xyz") - expect(params.get("plan")).toBe("generic") - expect(params.get("referrer")).toBe("opencode") - }) - - test("supports endpoint override for local integration tests", () => { - const url = new URL(buildAuthorizeUrl(pkce, "s", "n", { authorizeUrl: "http://127.0.0.1/oauth2/authorize" })) - expect(url.origin + url.pathname).toBe("http://127.0.0.1/oauth2/authorize") - }) - }) - describe("loader", () => { test("returns no options unless stored auth is OAuth and exposes methods in order", async () => { const hooks = await XaiAuthPlugin({} as any) @@ -110,8 +83,7 @@ describe("plugin.xai", () => { await hooks.auth!.loader!(async () => ({ type: "wellknown", key: "k", token: "t" }) as any, {} as any), ).toEqual({}) expect(hooks.auth!.methods.map((m) => [m.type, m.label])).toEqual([ - ["oauth", "xAI Grok OAuth (SuperGrok Subscription)"], - ["oauth", "xAI Grok OAuth (Headless / Remote / VPS)"], + ["oauth", "SuperGrok Subscription"], ["api", "Manually enter API Key"], ]) }) @@ -426,7 +398,7 @@ describe("plugin.xai", () => { const hooks = await XaiAuthPlugin({} as any, serverOptions(server)) const headless = hooks.auth!.methods.find( (m): m is Extract => - m.type === "oauth" && m.label === "xAI Grok OAuth (Headless / Remote / VPS)", + m.type === "oauth" && m.label === "SuperGrok Subscription", )! const result = await headless.authorize!() @@ -450,7 +422,7 @@ describe("plugin.xai", () => { }) const headless = (await XaiAuthPlugin({} as any, serverOptions(server))).auth!.methods.find( (m): m is Extract => - m.type === "oauth" && m.label === "xAI Grok OAuth (Headless / Remote / VPS)", + m.type === "oauth" && m.label === "SuperGrok Subscription", )! expect((await headless.authorize!()).url).toBe("https://x.ai/device") }) @@ -474,6 +446,7 @@ describe("plugin.xai", () => { expect(parsed.get("scope")).toContain("offline_access") expect(parsed.get("scope")).toContain("grok-cli:access") expect(parsed.get("scope")).toContain("api:access") + expect(parsed.get("referrer")).toBe("opencode") await expect( requestDeviceCode({ deviceAuthorizationUrl: new URL("/error", server.url).toString() }), ).rejects.toThrow(/429.*rate limited/) @@ -612,7 +585,7 @@ describe("plugin.xai", () => { }) const headless = (await XaiAuthPlugin({} as any, serverOptions(server))).auth!.methods.find( (m): m is Extract => - m.type === "oauth" && m.label === "xAI Grok OAuth (Headless / Remote / VPS)", + m.type === "oauth" && m.label === "SuperGrok Subscription", )! expect(await ((await headless.authorize!()) as any).callback()).toEqual({ type: "failed" }) }) diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index 96927f44d2a..a5a17de3a34 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -2308,9 +2308,9 @@ Some useful routing options: ### xAI -Three ways to authenticate: a SuperGrok subscription via browser OAuth, the same SuperGrok subscription via a headless device-code flow (for VPS / SSH / Docker), or a pay-as-you-go API key from the xAI console. +Two ways to authenticate: a SuperGrok subscription via device-code OAuth or a pay-as-you-go API key from the xAI console. -#### Option A — SuperGrok OAuth (browser login) +#### Option A — SuperGrok subscription 1. Run the `/connect` command and search for **xAI**. @@ -2318,9 +2318,11 @@ Three ways to authenticate: a SuperGrok subscription via browser OAuth, the same /connect ``` -2. Select **xAI Grok OAuth (SuperGrok Subscription)**. OpenCode opens xAI's consent screen in your browser and waits for the callback on `http://127.0.0.1:56121/callback`. +2. Select **SuperGrok Subscription**. OpenCode opens xAI's verification link with the user code pre-populated when supported. -3. Run the `/models` command to select a Grok model. +3. Approve the consent screen. If xAI asks for a code, enter the user code displayed by OpenCode. OpenCode polls xAI's token endpoint and stores the resulting OAuth tokens once you approve. + +4. Run the `/models` command to select a Grok model. ```txt /models @@ -2328,25 +2330,7 @@ Three ways to authenticate: a SuperGrok subscription via browser OAuth, the same OpenCode refreshes the OAuth access token automatically. Any Grok or X Premium plan that includes Grok API access works; you do not need a separate `XAI_API_KEY`. -#### Option B — SuperGrok device-code (headless / remote server / VPS) - -Use this when OpenCode is running somewhere a browser can't reach the loopback redirect: a VPS, a remote dev box over SSH, inside Docker, in CI, etc. No callback port is opened on the host running OpenCode — instead xAI hands the CLI a short code that you type into a browser on any other device (laptop, phone, …). - -1. Run the `/connect` command on the remote host and search for **xAI**. - - ```txt - /connect - ``` - -2. Select **xAI Grok OAuth (Headless / Remote / VPS)**. OpenCode prints a verification URL and a short user code. - - ```txt - Open https://x.ai/device on any device and enter code: ABCD-1234 - ``` - -3. Open the URL on a device that has a browser (your laptop or phone), enter the code, and approve the consent screen. OpenCode polls xAI's token endpoint and stores the resulting OAuth tokens once you approve. Token refresh works the same as Option A. - -#### Option C — API key +#### Option B — API key 1. Head over to the [xAI console](https://console.x.ai/), create an account, and generate an API key. From 5b4fb1f7701712d5f02fa0220292958474ed9671 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 5 Aug 2026 02:20:04 +0000 Subject: [PATCH 065/114] chore: generate --- packages/opencode/test/plugin/xai.test.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/opencode/test/plugin/xai.test.ts b/packages/opencode/test/plugin/xai.test.ts index 2339a07e91c..59a9bd92056 100644 --- a/packages/opencode/test/plugin/xai.test.ts +++ b/packages/opencode/test/plugin/xai.test.ts @@ -1,10 +1,5 @@ import { describe, expect, test } from "bun:test" -import { - accessTokenIsExpiring, - pollDeviceCodeToken, - requestDeviceCode, - XaiAuthPlugin, -} from "../../src/plugin/xai" +import { accessTokenIsExpiring, pollDeviceCodeToken, requestDeviceCode, XaiAuthPlugin } from "../../src/plugin/xai" import { OAUTH_DUMMY_KEY } from "../../src/auth" function makeJwt(payload: object): string { @@ -397,8 +392,7 @@ describe("plugin.xai", () => { }) const hooks = await XaiAuthPlugin({} as any, serverOptions(server)) const headless = hooks.auth!.methods.find( - (m): m is Extract => - m.type === "oauth" && m.label === "SuperGrok Subscription", + (m): m is Extract => m.type === "oauth" && m.label === "SuperGrok Subscription", )! const result = await headless.authorize!() @@ -421,8 +415,7 @@ describe("plugin.xai", () => { return new Response("unexpected request", { status: 500 }) }) const headless = (await XaiAuthPlugin({} as any, serverOptions(server))).auth!.methods.find( - (m): m is Extract => - m.type === "oauth" && m.label === "SuperGrok Subscription", + (m): m is Extract => m.type === "oauth" && m.label === "SuperGrok Subscription", )! expect((await headless.authorize!()).url).toBe("https://x.ai/device") }) @@ -584,8 +577,7 @@ describe("plugin.xai", () => { return Response.json({ error: "access_denied" }, { status: 400 }) }) const headless = (await XaiAuthPlugin({} as any, serverOptions(server))).auth!.methods.find( - (m): m is Extract => - m.type === "oauth" && m.label === "SuperGrok Subscription", + (m): m is Extract => m.type === "oauth" && m.label === "SuperGrok Subscription", )! expect(await ((await headless.authorize!()) as any).callback()).toEqual({ type: "failed" }) }) From 9f38562237f3ca4e41eb8a04fd776be3f944742e Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:27:01 -0500 Subject: [PATCH 066/114] fix(opencode): include cache writes in ACP usage (#40450) Co-authored-by: Aiden Cline --- packages/opencode/src/acp/service.ts | 2 +- packages/opencode/src/acp/usage.ts | 6 +++++- packages/opencode/test/acp/usage.test.ts | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/acp/service.ts b/packages/opencode/src/acp/service.ts index fe295918978..55fbc9681df 100644 --- a/packages/opencode/src/acp/service.ts +++ b/packages/opencode/src/acp/service.ts @@ -656,7 +656,7 @@ function makeUsageService(sdk: OpencodeClient) { sessionId: params.sessionID, update: { sessionUpdate: "usage_update", - used: message.tokens.input + message.tokens.cache.read, + used: UsageService.contextTokens(message), size, cost: { amount: UsageService.totalSessionCost(messages), currency: "USD" }, }, diff --git a/packages/opencode/src/acp/usage.ts b/packages/opencode/src/acp/usage.ts index 737275768e2..bc17447e614 100644 --- a/packages/opencode/src/acp/usage.ts +++ b/packages/opencode/src/acp/usage.ts @@ -83,6 +83,10 @@ export function messageLoaderFromSDK(sdk: SDK): MessageLoaderInterface { export const messageLoaderLayer = (sdk: SDK) => Layer.succeed(MessageLoader, messageLoaderFromSDK(sdk)) +export function contextTokens(message: AssistantTokenCost): number { + return message.tokens.input + message.tokens.cache.read + message.tokens.cache.write +} + export function buildUsage(message: AssistantTokenCost): Usage { const cachedReadTokens = message.tokens.cache.read const cachedWriteTokens = message.tokens.cache.write @@ -207,7 +211,7 @@ const layer = Layer.effect( sessionId: input.sessionID, update: { sessionUpdate: "usage_update", - used: message.tokens.input + message.tokens.cache.read, + used: contextTokens(message), size, cost: { amount: totalSessionCost(messages), currency: "USD" }, }, diff --git a/packages/opencode/test/acp/usage.test.ts b/packages/opencode/test/acp/usage.test.ts index 06ccfb1f506..e9d3b3c62e3 100644 --- a/packages/opencode/test/acp/usage.test.ts +++ b/packages/opencode/test/acp/usage.test.ts @@ -207,7 +207,7 @@ describe("acp usage", () => { ) }) - it.effect("sends ACP usage_update with context size and cumulative assistant cost", () => { + it.effect("includes cache reads and writes in ACP context usage", () => { const updates: SessionNotification[] = [] return Effect.gen(function* () { const usage = yield* UsageService.Service @@ -222,7 +222,7 @@ describe("acp usage", () => { sessionId: "ses_1", update: { sessionUpdate: "usage_update", - used: 15, + used: 22, size: 128_000, cost: { amount: 3, currency: "USD" }, }, @@ -239,7 +239,7 @@ describe("acp usage", () => { input: 10, output: 20, reasoning: 0, - cache: { read: 5, write: 0 }, + cache: { read: 5, write: 7 }, }, }), ]), From 82a579615939e582519457d28fb44048fba4cbb9 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 5 Aug 2026 12:31:45 +1000 Subject: [PATCH 067/114] test(app): harden flaky e2e synchronization (#40556) --- packages/app/e2e/AGENTS.md | 16 +++++ .../performance/timeline-stability/fixture.ts | 4 +- .../regression/review-line-comment.spec.ts | 59 ++++++++++------- .../session-timeline-transport.spec.ts | 64 +++++++++++++++---- packages/app/e2e/utils/sse-transport.ts | 15 +++-- 5 files changed, 116 insertions(+), 42 deletions(-) create mode 100644 packages/app/e2e/AGENTS.md diff --git a/packages/app/e2e/AGENTS.md b/packages/app/e2e/AGENTS.md new file mode 100644 index 00000000000..e0c6c006021 --- /dev/null +++ b/packages/app/e2e/AGENTS.md @@ -0,0 +1,16 @@ +## Required Reading + +- Before writing, changing, or reviewing E2E tests, ALWAYS read and follow Playwright's official [Best Practices](https://playwright.dev/docs/best-practices), [Auto-waiting](https://playwright.dev/docs/actionability), and [Assertions](https://playwright.dev/docs/test-assertions) guides. +- Use the official [Locators](https://playwright.dev/docs/locators), [Network](https://playwright.dev/docs/network), and [Test Isolation](https://playwright.dev/docs/browser-contexts) guides when those concerns apply. + +## Test Hygiene + +- Test user-visible behavior with isolated, deterministic data and scoped, unique locators. +- Prefer role, label, text, and explicit test-contract locators. Do not use `.first()` or `.last()` merely to silence strictness errors. +- Use locator actions, Playwright auto-waiting, and web-first assertions for observable readiness and outcomes. +- NEVER use `waitForTimeout`, `setTimeout`, sleeps, animation-frame counts, or other wall-clock delays to synchronize a test. Wait for the specific UI state, request, response, event, or application outcome instead. +- Do not treat navigation, a network response, DOM attachment, or visibility alone as proof that asynchronously rendered UI is ready. Assert the state the next action actually requires. +- Register event and network waits before the action that triggers them. +- Do not retry state-changing actions. Retry idempotent readiness checks, then perform the action once and assert its outcome. +- Keep action and assertion timeouts adaptive. Do not use short timeouts as readiness probes or rely on retries to hide flakes. +- Assert exact outcomes and identities so stale state, duplicate rendering, and interactions with the wrong element cannot pass. diff --git a/packages/app/e2e/performance/timeline-stability/fixture.ts b/packages/app/e2e/performance/timeline-stability/fixture.ts index df67da5a662..66edece4092 100644 --- a/packages/app/e2e/performance/timeline-stability/fixture.ts +++ b/packages/app/e2e/performance/timeline-stability/fixture.ts @@ -197,7 +197,9 @@ export async function setupTimeline( ) }, async waitForPart(partID: string) { - await expect(page.locator(`[data-timeline-part-id="${partID}"]`).first()).toBeVisible() + const part = page.locator(`[data-timeline-part-id="${partID}"]`) + await expect(part).toHaveCount(1) + await expect(part).toBeVisible() }, } } diff --git a/packages/app/e2e/regression/review-line-comment.spec.ts b/packages/app/e2e/regression/review-line-comment.spec.ts index 7850f7820ac..852a9e58f5a 100644 --- a/packages/app/e2e/regression/review-line-comment.spec.ts +++ b/packages/app/e2e/regression/review-line-comment.spec.ts @@ -18,6 +18,7 @@ test("opens the comment editor when code is clicked", async ({ page }) => { await line.click() await expect(review.getByRole("textbox")).toBeVisible() + await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 2") }) test("opens the comment editor when a line number is clicked", async ({ page }) => { @@ -27,6 +28,7 @@ test("opens the comment editor when a line number is clicked", async ({ page }) await lineNumber.click() await expect(review.getByRole("textbox")).toBeVisible() + await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 1") }) test("opens the comment editor for a line number range", async ({ page }) => { @@ -36,15 +38,10 @@ test("opens the comment editor for a line number range", async ({ page }) => { await expectAppVisible(start) await expectAppVisible(end) - const from = await start.boundingBox() - const to = await end.boundingBox() - if (!from || !to) throw new Error("Missing line number bounds") - await page.mouse.move(from.x + from.width / 2, from.y + from.height / 2) - await page.mouse.down() - await page.mouse.move(to.x + to.width / 2, to.y + to.height / 2) - await page.mouse.up() + await start.dragTo(end) await expect(review.getByRole("textbox")).toBeVisible() + await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on lines 1-3") }) test("shows a comment button when a line number is hovered", async ({ page }) => { @@ -54,31 +51,40 @@ test("shows a comment button when a line number is hovered", async ({ page }) => const comment = review.getByRole("button", { name: "Comment", exact: true }) await expect(async () => { - await page.mouse.move(0, 0) await lineNumber.hover() - await expect(comment).toBeVisible({ timeout: 500 }) - await comment.click({ timeout: 500 }) - }).toPass() + await expect(lineNumber).toHaveAttribute("data-hovered", "") + await expect(comment).toHaveCount(1) + await expect(comment).toHaveCSS("pointer-events", "auto") + await comment.focus() + await expect(comment).toBeFocused() + }).toPass({ timeout: 10_000 }) + await comment.press("Enter") await expect(review.getByRole("textbox")).toBeVisible() + await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 1") }) test("stages a submitted line comment in the prompt context", async ({ page }) => { - const requests: string[] = [] page.on("request", (request) => { - if (request.method() !== "GET") requests.push(`${request.method()} ${new URL(request.url()).pathname}`) + expect + .soft(request.method(), `unexpected ${request.method()} ${new URL(request.url()).pathname}`) + .toBe("GET") }) const review = page.locator('[data-component="session-review"]') await review.getByText("export const value = 'after'", { exact: true }).click() - await review.getByRole("textbox").fill("Use the existing value instead") - await review.locator('[data-slot="line-comment-action"][data-variant="primary"]').click() + const textbox = review.getByRole("textbox") + await expect(textbox).toBeVisible() + await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 2") + await textbox.fill("Use the existing value instead") + const submit = review.locator('[data-slot="line-comment-action"][data-variant="primary"]') + await expect(submit).toBeEnabled() + await submit.click() await expect(review.getByText("Use the existing value instead", { exact: true })).toBeVisible() await page.getByRole("tab", { name: "Session" }).click() const context = page.getByText("Use the existing value instead", { exact: true }).last() await expect(context).toBeVisible() await expect(context.locator("..")).toContainText("review.ts:2") - expect(requests).toEqual([]) }) async function openReview(page: Page) { @@ -144,15 +150,22 @@ async function openReview(page: Page) { await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) await expectSessionTitle(page, title) - const diffResponse = page.waitForResponse((response) => new URL(response.url()).pathname === "/api/vcs/diff") - await page.getByRole("tab", { name: "Changes" }).click() + const changes = page.getByRole("tab", { name: "Changes" }) + const diffResponse = page.waitForResponse( + (response) => + response.request().method() === "GET" && response.ok() && new URL(response.url()).pathname === "/api/vcs/diff", + ) + await changes.click() expect((await (await diffResponse).json()).data).toHaveLength(1) + await expect(page.getByRole("tab", { selected: true })).toHaveAccessibleName(/Files Changed/) const review = page.locator('[data-component="session-review"]') await expectAppVisible(review) - await review - .getByRole("heading", { name: /review\.ts/ }) - .getByRole("button") - .first() - .click() + const file = review.locator('[data-file="src/review.ts"]') + await expectAppVisible(file) + const trigger = file.getByRole("button", { expanded: false }) + await expect(trigger).toHaveCount(1) + await trigger.click() + await expect(file.getByRole("button", { expanded: true })).toBeVisible() + await expect(file.getByText("export const value = 'after'", { exact: true })).toBeVisible() } diff --git a/packages/app/e2e/regression/session-timeline-transport.spec.ts b/packages/app/e2e/regression/session-timeline-transport.spec.ts index 778ff3a3af9..35980499787 100644 --- a/packages/app/e2e/regression/session-timeline-transport.spec.ts +++ b/packages/app/e2e/regression/session-timeline-transport.spec.ts @@ -1,9 +1,8 @@ -import { expect, test } from "@playwright/test" +import { expect, test, type Page } from "@playwright/test" import { assistantMessage, partUpdated, setupTimeline, - status, textPart, userMessage, } from "../performance/timeline-stability/fixture" @@ -17,7 +16,7 @@ test("keeps one connection open while delivering multiple events", async ({ page await timeline.waitForPart("prt_transport_first") await timeline.waitForPart("prt_transport_second") expect(first.connectionID).toBe(second.connectionID) - expect(await timeline.transport.connections()).toHaveLength(1) + await expect.poll(async () => (await timeline.transport.connections()).length).toBe(1) expect(await timeline.transport.acknowledgements()).toHaveLength(2) }) @@ -51,20 +50,28 @@ test("parses split JSON and a split multibyte code point", async ({ page }) => { }) test("delivers server heartbeat without mutating the timeline", async ({ page }) => { + const sentinelID = "prt_transport_heartbeat_sentinel" const timeline = await setupTimeline(page, { messages: [userMessage(), assistantMessage([textPart("prt_transport_steady", "steady")])], }) - const before = await page.locator("[data-timeline-row]").allTextContents() + await timeline.waitForPart("prt_transport_steady") + const before = await stableTimelineRows(page) - await timeline.transport.heartbeat() - await timeline.settle() + await timeline.transport.writeRaw(": heartbeat\n\n") + await timeline.transport.send(partUpdated(textPart(sentinelID, "heartbeat processed"))) + await timeline.waitForPart(sentinelID) - expect(await page.locator("[data-timeline-row]").allTextContents()).toEqual(before) - expect(await timeline.transport.connections()).toHaveLength(1) + await expect + .poll(async () => { + const rows = await timelineRows(page) + return rows.filter((row) => before.some((item) => item.key === row.key)) + }) + .toEqual(before) + await expect.poll(async () => (await timeline.transport.connections()).length).toBe(1) }) test("reconnects after a clean close", async ({ page }) => { - const timeline = await setupTimeline(page, { eventRetry: 10 }) + const timeline = await setupTimeline(page) const first = await timeline.transport.waitForConnection() await timeline.transport.close() @@ -77,20 +84,21 @@ test("reconnects after a clean close", async ({ page }) => { }) test("reconnects after a stream error", async ({ page }) => { - const timeline = await setupTimeline(page, { eventRetry: 10 }) + const timeline = await setupTimeline(page) const first = await timeline.transport.waitForConnection() await timeline.transport.error("contract failure") const second = await timeline.transport.waitForConnection({ after: first.id }) - await timeline.transport.send(status("busy")) + await timeline.transport.send(partUpdated(textPart("prt_transport_error", "after error"))) + await timeline.waitForPart("prt_transport_error") await expect.poll(async () => (await timeline.transport.connections()).length).toBe(2) expect(second.id).toBeGreaterThan(first.id) expect((await timeline.transport.connections())[0]?.endedBy).toBe("error") }) test("does not request replay when reconnecting the volatile V2 event stream", async ({ page }) => { - const timeline = await setupTimeline(page, { eventRetry: 10, protocol: "v2" }) + const timeline = await setupTimeline(page, { protocol: "v2" }) const first = await timeline.transport.send(partUpdated(textPart("prt_transport_id", "event with id")), { id: "timeline-event-7", }) @@ -112,5 +120,35 @@ test("passes through non-event fetches", async ({ page }) => { }) expect(health).toEqual({ healthy: true }) - expect(await timeline.transport.connections()).toHaveLength(1) + await expect.poll(async () => (await timeline.transport.connections()).length).toBe(1) }) + +async function stableTimelineRows(page: Page) { + let previous: Awaited> | undefined + let stable = 0 + await expect + .poll( + async () => { + const next = await timelineRows(page) + stable = JSON.stringify(next) === JSON.stringify(previous) ? stable + 1 : 0 + previous = next + return stable + }, + { intervals: [50, 50, 100] }, + ) + .toBeGreaterThanOrEqual(2) + return previous! +} + +function timelineRows(page: Page) { + return page.locator("[data-timeline-key]").evaluateAll((elements) => + elements.map((element) => ({ + key: element.getAttribute("data-timeline-key"), + row: element.querySelector("[data-timeline-row]")?.getAttribute("data-timeline-row"), + parts: Array.from(element.querySelectorAll("[data-timeline-part-id]"), (part) => + part.getAttribute("data-timeline-part-id"), + ), + text: element.textContent, + })), + ) +} diff --git a/packages/app/e2e/utils/sse-transport.ts b/packages/app/e2e/utils/sse-transport.ts index b0e3b74c6d9..a0a20a2e17f 100644 --- a/packages/app/e2e/utils/sse-transport.ts +++ b/packages/app/e2e/utils/sse-transport.ts @@ -247,18 +247,23 @@ export async function installSseTransport( return { server, async waitForConnection(input = {}) { - await page.waitForFunction( + const connection = await page.waitForFunction( (after) => { const transport = (window as BrowserTransport).__testSseTransport const connections = transport?.command({ type: "connections" }) as SseConnectionRecord[] | undefined - return connections?.some((connection) => connection.id > after) + return connections?.findLast((connection) => connection.id > after && connection.endedAt === undefined) }, input.after ?? 0, { timeout: input.timeout }, ) - return (await command({ type: "connections" })).findLast( - (connection) => connection.id > (input.after ?? 0), - )! + let result: SseConnectionRecord | undefined + try { + result = await connection.jsonValue() + } finally { + await connection.dispose() + } + if (!result) throw new Error("SSE transport connection disappeared while waiting") + return result }, send(payload, eventOptions) { return command({ type: "send", deliveries: [{ payload, options: eventOptions }], burst: false }) From b8ea3ea091ea2b62b589d43af8b10e811195e39f Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 5 Aug 2026 02:33:08 +0000 Subject: [PATCH 068/114] chore: generate --- packages/app/e2e/regression/review-line-comment.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/app/e2e/regression/review-line-comment.spec.ts b/packages/app/e2e/regression/review-line-comment.spec.ts index 852a9e58f5a..3e50dab65d9 100644 --- a/packages/app/e2e/regression/review-line-comment.spec.ts +++ b/packages/app/e2e/regression/review-line-comment.spec.ts @@ -65,9 +65,7 @@ test("shows a comment button when a line number is hovered", async ({ page }) => test("stages a submitted line comment in the prompt context", async ({ page }) => { page.on("request", (request) => { - expect - .soft(request.method(), `unexpected ${request.method()} ${new URL(request.url()).pathname}`) - .toBe("GET") + expect.soft(request.method(), `unexpected ${request.method()} ${new URL(request.url()).pathname}`).toBe("GET") }) const review = page.locator('[data-component="session-review"]') From 2f17fc9613771af3de3b5a2715b836037d80c4b1 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 5 Aug 2026 12:49:28 +0800 Subject: [PATCH 069/114] docs(zen): add LongCat free model (#40585) --- packages/web/src/content/docs/ar/zen.mdx | 4 ++++ packages/web/src/content/docs/bs/zen.mdx | 4 ++++ packages/web/src/content/docs/da/zen.mdx | 4 ++++ packages/web/src/content/docs/de/zen.mdx | 4 ++++ packages/web/src/content/docs/es/zen.mdx | 4 ++++ packages/web/src/content/docs/fr/zen.mdx | 4 ++++ packages/web/src/content/docs/it/zen.mdx | 4 ++++ packages/web/src/content/docs/ja/zen.mdx | 4 ++++ packages/web/src/content/docs/ko/zen.mdx | 4 ++++ packages/web/src/content/docs/nb/zen.mdx | 4 ++++ packages/web/src/content/docs/pl/zen.mdx | 4 ++++ packages/web/src/content/docs/pt-br/zen.mdx | 4 ++++ packages/web/src/content/docs/ru/zen.mdx | 4 ++++ packages/web/src/content/docs/th/zen.mdx | 4 ++++ packages/web/src/content/docs/tr/zen.mdx | 4 ++++ packages/web/src/content/docs/zen.mdx | 4 ++++ packages/web/src/content/docs/zh-cn/zen.mdx | 4 ++++ packages/web/src/content/docs/zh-tw/zen.mdx | 4 ++++ 18 files changed, 72 insertions(+) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index 0cc94de6efa..86486c4560e 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -112,6 +112,7 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -141,6 +142,7 @@ https://opencode.ai/zen/v1/models | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -217,6 +219,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Laguna S 2.1 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Ling-3.0-flash Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. +- LongCat-2.0 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - North Mini Code Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Nemotron 3 Ultra Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Big Pickle نموذج خفي ومتاح مجانا على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. @@ -275,6 +278,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - Laguna S 2.1 Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - Ling-3.0-flash Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. +- LongCat-2.0 Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - North Mini Code Free: خلال فترته المجانية، قد يُحتفَظ بالبيانات المُجمَّعة وتُستخدم لتحسين النموذج. لا تُرسل بيانات شخصية أو سرية. راجع [شروط الاستخدام](https://cohere.com/terms-of-use) و[سياسة الخصوصية](https://cohere.com/privacy). - Nemotron 3 Ultra Free (نقاط نهاية NVIDIA المجانية): للاستخدام التجريبي فقط — لا ترسل بيانات شخصية أو سرية. يُسجَّل استخدامك لأغراض أمنية ولتحسين منتجات وخدمات NVIDIA. بيانات الجلسة المُسجَّلة لأغراض التحسين غير مرتبطة بهويتك أو بأي مُعرِّف دائم. لمزيد من المعلومات حول ممارسات معالجة البيانات لدينا، راجع [سياسة الخصوصية](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). بتفاعلك مع نقطة النهاية هذه، فإنك توافق على جمعنا لهذه المعلومات وتسجيلها واستخدامها وعلى [شروط خدمة النسخة التجريبية من واجهة NVIDIA API](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 17cf73581b6..9372b8a734f 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -117,6 +117,7 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -148,6 +149,7 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -224,6 +226,7 @@ Besplatni modeli: - MiMo-V2.5 Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Laguna S 2.1 Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Ling-3.0-flash Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. +- LongCat-2.0 Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - North Mini Code Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Nemotron 3 Ultra Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Big Pickle je stealth model koji je besplatan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. @@ -287,6 +290,7 @@ i ne koriste vaše podatke za treniranje modela, uz sljedeće izuzetke: - MiMo-V2.5 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - Laguna S 2.1 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - Ling-3.0-flash Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. +- LongCat-2.0 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - North Mini Code Free: Tokom besplatnog perioda, prikupljeni podaci mogu biti zadržani i korišteni za poboljšanje modela. Nemojte slati lične ili povjerljive podatke. Pogledajte naše [Uslove korištenja](https://cohere.com/terms-of-use) i [Politiku privatnosti](https://cohere.com/privacy). - Nemotron 3 Ultra Free (besplatni NVIDIA endpointi): Samo za probnu upotrebu — nemojte slati lične ili povjerljive podatke. Vaše korištenje se bilježi radi sigurnosti i poboljšanja NVIDIA proizvoda i usluga. Zabilježeni podaci sesije koji se koriste u svrhu poboljšanja nisu povezani s vašim identitetom niti bilo kojim trajnim identifikatorom. Za više informacija o našim praksama obrade podataka pogledajte našu [Politiku privatnosti](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Interakcijom s ovim endpointom pristajete na naše prikupljanje, bilježenje i korištenje takvih informacija te na [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index e200e14e60d..e67c37c3d3a 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -117,6 +117,7 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -148,6 +149,7 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -224,6 +226,7 @@ De gratis modeller: - MiMo-V2.5 Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Laguna S 2.1 Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Ling-3.0-flash Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. +- LongCat-2.0 Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - North Mini Code Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Nemotron 3 Ultra Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Big Pickle er en stealth-model, som er gratis på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. @@ -285,6 +288,7 @@ Alle vores modeller hostes i US. Vores udbydere følger en nul-opbevaringspoliti - MiMo-V2.5 Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - Laguna S 2.1 Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - Ling-3.0-flash Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. +- LongCat-2.0 Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - North Mini Code Free: I gratisperioden kan indsamlede data blive opbevaret og brugt til at forbedre modellen. Indsend ikke personlige eller fortrolige oplysninger. Se vores [Brugsvilkår](https://cohere.com/terms-of-use) og [Privatlivspolitik](https://cohere.com/privacy). - Nemotron 3 Ultra Free (gratis NVIDIA-endpoints): Kun til prøvebrug — indsend ikke personlige eller fortrolige data. Din brug logges af sikkerhedshensyn og for at forbedre NVIDIAs produkter og tjenester. De loggede sessionsdata, der bruges til forbedringsformål, er ikke knyttet til din identitet eller nogen vedvarende identifikator. For mere information om vores databehandlingspraksis, se vores [privatlivspolitik](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Ved at interagere med dette endpoint giver du samtykke til vores indsamling, registrering og brug af sådanne oplysninger samt [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 1fa6cbc7eb4..72cd654da98 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -108,6 +108,7 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -137,6 +138,7 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -213,6 +215,7 @@ Die kostenlosen Modelle: - MiMo-V2.5 Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Laguna S 2.1 Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Ling-3.0-flash Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. +- LongCat-2.0 Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - North Mini Code Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Nemotron 3 Ultra Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Big Pickle ist ein Stealth-Modell, das für begrenzte Zeit kostenlos auf OpenCode verfügbar ist. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. @@ -271,6 +274,7 @@ Alle unsere Modelle werden in den USA gehostet. Unsere Provider folgen einer Zer - MiMo-V2.5 Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - Laguna S 2.1 Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - Ling-3.0-flash Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. +- LongCat-2.0 Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - North Mini Code Free: Während des kostenlosen Zeitraums können erhobene Daten gespeichert und zur Verbesserung des Modells verwendet werden. Übermitteln Sie keine personenbezogenen oder vertraulichen Daten. Weitere Informationen finden Sie in unseren [Nutzungsbedingungen](https://cohere.com/terms-of-use) und unserer [Datenschutzerklärung](https://cohere.com/privacy). - Nemotron 3 Ultra Free (kostenlose NVIDIA-Endpunkte): Nur für Testzwecke — übermitteln Sie keine personenbezogenen oder vertraulichen Daten. Ihre Nutzung wird zu Sicherheitszwecken und zur Verbesserung der Produkte und Dienste von NVIDIA protokolliert. Die zu Verbesserungszwecken protokollierten Sitzungsdaten sind nicht mit Ihrer Identität oder einem dauerhaften Identifikator verknüpft. Weitere Informationen zu unseren Datenverarbeitungspraktiken finden Sie in unserer [Datenschutzrichtlinie](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Durch die Interaktion mit diesem Endpunkt stimmen Sie unserer Erhebung, Aufzeichnung und Nutzung solcher Informationen sowie den [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) zu. - OpenAI APIs: Anfragen werden in Übereinstimmung mit [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 30 Tage lang gespeichert. diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 539d5999f60..67ed496cc36 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -117,6 +117,7 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -148,6 +149,7 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -224,6 +226,7 @@ Los modelos gratuitos: - MiMo-V2.5 Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Laguna S 2.1 Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Ling-3.0-flash Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. +- LongCat-2.0 Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - North Mini Code Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Nemotron 3 Ultra Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Big Pickle es un modelo stealth que es gratuito en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. @@ -285,6 +288,7 @@ Todos nuestros modelos están alojados en US. Nuestros proveedores siguen una po - MiMo-V2.5 Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - Laguna S 2.1 Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - Ling-3.0-flash Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. +- LongCat-2.0 Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - North Mini Code Free: Durante el período gratuito, los datos recopilados podrán conservarse y utilizarse para mejorar el modelo. No envíes datos personales ni confidenciales. Consulta nuestros [Términos de uso](https://cohere.com/terms-of-use) y nuestra [Política de privacidad](https://cohere.com/privacy). - Nemotron 3 Ultra Free (endpoints gratuitos de NVIDIA): Solo para uso de prueba — no envíes datos personales ni confidenciales. Tu uso se registra con fines de seguridad y para mejorar los productos y servicios de NVIDIA. Los datos de sesión registrados con fines de mejora no están vinculados a tu identidad ni a ningún identificador persistente. Para obtener más información sobre nuestras prácticas de procesamiento de datos, consulta nuestra [Política de privacidad](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Al interactuar con este endpoint, aceptas que recopilemos, registremos y usemos dicha información, así como los [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: Las solicitudes se conservan durante 30 días de acuerdo con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 7a5d5bcf7b2..93dc2faefc8 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -108,6 +108,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -137,6 +138,7 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -213,6 +215,7 @@ Les modèles gratuits : - MiMo-V2.5 Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Laguna S 2.1 Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Ling-3.0-flash Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. +- LongCat-2.0 Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - North Mini Code Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Nemotron 3 Ultra Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Big Pickle est un modèle stealth gratuit sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. @@ -271,6 +274,7 @@ Tous nos modèles sont hébergés aux US. Nos fournisseurs suivent une politique - MiMo-V2.5 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - Laguna S 2.1 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - Ling-3.0-flash Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. +- LongCat-2.0 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - North Mini Code Free : Pendant la période de gratuité, les données collectées peuvent être conservées et utilisées pour améliorer le modèle. Ne transmettez aucune donnée personnelle ou confidentielle. Consultez nos [Conditions d’utilisation](https://cohere.com/terms-of-use) et notre [Politique de confidentialité](https://cohere.com/privacy). - Nemotron 3 Ultra Free (endpoints NVIDIA gratuits) : Réservé à un usage d'essai — n'envoyez pas de données personnelles ou confidentielles. Votre utilisation est journalisée à des fins de sécurité et pour améliorer les produits et services de NVIDIA. Les données de session journalisées à des fins d'amélioration ne sont pas liées à votre identité ni à un quelconque identifiant persistant. Pour plus d'informations sur nos pratiques de traitement des données, consultez notre [Politique de confidentialité](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). En interagissant avec cet endpoint, vous consentez à notre collecte, à notre enregistrement et à notre utilisation de ces informations ainsi qu'aux [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs : Les requêtes sont conservées pendant 30 jours conformément à [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 941db5be946..0c28e123fb2 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -117,6 +117,7 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -148,6 +149,7 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -224,6 +226,7 @@ I modelli gratuiti: - MiMo-V2.5 Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Laguna S 2.1 Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Ling-3.0-flash Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. +- LongCat-2.0 Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - North Mini Code Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Nemotron 3 Ultra Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Big Pickle è un modello stealth che è gratuito su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. @@ -285,6 +288,7 @@ Tutti i nostri modelli sono ospitati negli US. I nostri provider seguono una pol - MiMo-V2.5 Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - Laguna S 2.1 Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - Ling-3.0-flash Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. +- LongCat-2.0 Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - North Mini Code Free: Durante il periodo gratuito, i dati raccolti possono essere conservati e utilizzati per migliorare il modello. Non inviare dati personali o riservati. Consulta i nostri [Termini di utilizzo](https://cohere.com/terms-of-use) e la nostra [Informativa sulla privacy](https://cohere.com/privacy). - Nemotron 3 Ultra Free (endpoint NVIDIA gratuiti): solo per uso di prova — non inviare dati personali o riservati. Il tuo utilizzo viene registrato per finalità di sicurezza e per migliorare i prodotti e i servizi di NVIDIA. I dati di sessione registrati a fini di miglioramento non sono collegati alla tua identità né ad alcun identificatore persistente. Per maggiori informazioni sulle nostre pratiche di trattamento dei dati, consulta la nostra [Informativa sulla privacy](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Interagendo con questo endpoint, acconsenti alla nostra raccolta, registrazione e utilizzo di tali informazioni e ai [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: le richieste vengono conservate per 30 giorni in conformità con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 73b8d5a1873..6ca86c54bd3 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -108,6 +108,7 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -137,6 +138,7 @@ https://opencode.ai/zen/v1/models | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -213,6 +215,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Laguna S 2.1 Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Ling-3.0-flash Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 +- LongCat-2.0 Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - North Mini Code Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Nemotron 3 Ultra Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Big Pickle はステルスモデルで、期間限定で OpenCode で無料提供されています。チームはこの期間中にフィードバックを集め、モデルを改善しています。 @@ -271,6 +274,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - Laguna S 2.1 Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - Ling-3.0-flash Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 +- LongCat-2.0 Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - North Mini Code Free: 無料提供期間中、収集されたデータは保持され、モデルの改善に使用される場合があります。個人情報や機密情報を送信しないでください。詳しくは、[利用規約](https://cohere.com/terms-of-use)および[プライバシーポリシー](https://cohere.com/privacy)をご覧ください。 - Nemotron 3 Ultra Free(NVIDIA の無料エンドポイント): 試用専用です — 個人情報や機密データは送信しないでください。お客様の利用は、セキュリティ目的および NVIDIA の製品とサービスの改善のために記録されます。改善目的で記録されたセッションデータは、お客様の身元や永続的な識別子とは関連付けられません。当社のデータ処理慣行の詳細については、[プライバシーポリシー](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)をご覧ください。このエンドポイントを利用することで、お客様はそのような情報の当社による収集、記録、利用、および [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) に同意したものとみなされます。 - OpenAI APIs: リクエストは [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) に従って 30 日間保持されます。 diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 474bd38ff4c..1f81d1428e0 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -108,6 +108,7 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -137,6 +138,7 @@ https://opencode.ai/zen/v1/models | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -213,6 +215,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Laguna S 2.1 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Ling-3.0-flash Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. +- LongCat-2.0 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - North Mini Code Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Nemotron 3 Ultra Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Big Pickle은 한정된 기간 동안 OpenCode에서 무료로 제공되는 stealth model입니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. @@ -271,6 +274,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - Laguna S 2.1 Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - Ling-3.0-flash Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. +- LongCat-2.0 Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - North Mini Code Free: 무료 제공 기간 동안 수집된 데이터는 보관되며 모델 개선에 사용될 수 있습니다. 개인 정보나 기밀 정보를 제출하지 마세요. 자세한 내용은 [이용 약관](https://cohere.com/terms-of-use) 및 [개인정보 처리방침](https://cohere.com/privacy)을 참조하세요. - Nemotron 3 Ultra Free(NVIDIA 무료 엔드포인트): 평가판 전용이며 — 개인 정보나 기밀 데이터는 제출하지 마세요. 사용 내역은 보안 목적과 NVIDIA 제품 및 서비스 개선을 위해 기록됩니다. 개선 목적으로 기록된 세션 데이터는 사용자의 신원이나 영구 식별자와 연결되지 않습니다. 당사의 데이터 처리 관행에 대한 자세한 내용은 [개인정보처리방침](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)을 참조하세요. 이 엔드포인트와 상호 작용함으로써 사용자는 당사가 이러한 정보를 수집, 기록, 사용하는 것과 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)에 동의하게 됩니다. - OpenAI APIs: 요청은 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data)에 따라 30일 동안 보관됩니다. diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 74cc45b3a97..99062b0e5c4 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -117,6 +117,7 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -148,6 +149,7 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -224,6 +226,7 @@ Gratis-modellene: - MiMo-V2.5 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Laguna S 2.1 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Ling-3.0-flash Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. +- LongCat-2.0 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - North Mini Code Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Nemotron 3 Ultra Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Big Pickle er en stealth-modell som er gratis på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. @@ -285,6 +288,7 @@ Alle modellene våre hostes i US. Leverandørene våre følger en policy for zer - MiMo-V2.5 Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - Laguna S 2.1 Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - Ling-3.0-flash Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. +- LongCat-2.0 Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - North Mini Code Free: I gratisperioden kan innsamlede data bli oppbevart og brukt til å forbedre modellen. Ikke send inn personopplysninger eller konfidensielle opplysninger. Se våre [Vilkår for bruk](https://cohere.com/terms-of-use) og vår [Personvernerklæring](https://cohere.com/privacy). - Nemotron 3 Ultra Free (gratis NVIDIA-endepunkter): Kun for prøvebruk — ikke send inn personopplysninger eller konfidensielle data. Bruken din logges av sikkerhetshensyn og for å forbedre NVIDIAs produkter og tjenester. Sesjonsdataene som logges for forbedringsformål, er ikke knyttet til identiteten din eller noen vedvarende identifikator. For mer informasjon om vår databehandlingspraksis, se vår [personvernerklæring](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Ved å samhandle med dette endepunktet samtykker du til at vi samler inn, registrerer og bruker slik informasjon, samt til [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: Forespørsler lagres i 30 dager i samsvar med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index c6c8ba7fb4f..807fc9d202c 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -117,6 +117,7 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -148,6 +149,7 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -224,6 +226,7 @@ Darmowe modele: - MiMo-V2.5 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Laguna S 2.1 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Ling-3.0-flash Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. +- LongCat-2.0 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - North Mini Code Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Nemotron 3 Ultra Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Big Pickle to stealth model, który jest darmowy w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. @@ -285,6 +288,7 @@ Wszystkie nasze modele są hostowane w US. Nasi dostawcy stosują politykę zero - MiMo-V2.5 Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - Laguna S 2.1 Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - Ling-3.0-flash Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. +- LongCat-2.0 Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - North Mini Code Free: W okresie bezpłatnego dostępu zebrane dane mogą być przechowywane i wykorzystywane do ulepszania modelu. Nie przesyłaj danych osobowych ani poufnych. Zapoznaj się z naszym [Regulaminem korzystania](https://cohere.com/terms-of-use) i [Polityką prywatności](https://cohere.com/privacy). - Nemotron 3 Ultra Free (darmowe endpointy NVIDIA): Tylko do użytku próbnego — nie przesyłaj danych osobowych ani poufnych. Twoje korzystanie jest rejestrowane w celach bezpieczeństwa oraz w celu ulepszania produktów i usług NVIDIA. Rejestrowane dane sesji wykorzystywane do celów ulepszania nie są powiązane z Twoją tożsamością ani żadnym trwałym identyfikatorem. Aby uzyskać więcej informacji o naszych praktykach przetwarzania danych, zapoznaj się z naszą [Polityką prywatności](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Korzystając z tego endpointu, wyrażasz zgodę na gromadzenie, rejestrowanie i wykorzystywanie przez nas takich informacji oraz na [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: Żądania są przechowywane przez 30 dni zgodnie z [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index fa19e0e7d2a..2a59aa48e33 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -108,6 +108,7 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -137,6 +138,7 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -213,6 +215,7 @@ Os modelos gratuitos: - MiMo-V2.5 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Laguna S 2.1 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Ling-3.0-flash Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. +- LongCat-2.0 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - North Mini Code Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Nemotron 3 Ultra Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Big Pickle é um modelo stealth que está gratuito no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. @@ -271,6 +274,7 @@ Todos os nossos modelos são hospedados nos US. Nossos provedores seguem uma pol - MiMo-V2.5 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - Laguna S 2.1 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - Ling-3.0-flash Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. +- LongCat-2.0 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - North Mini Code Free: Durante o período gratuito, os dados coletados poderão ser retidos e usados para aprimorar o modelo. Não envie dados pessoais ou confidenciais. Consulte nossos [Termos de Uso](https://cohere.com/terms-of-use) e nossa [Política de Privacidade](https://cohere.com/privacy). - Nemotron 3 Ultra Free (endpoints gratuitos da NVIDIA): Apenas para uso de avaliação — não envie dados pessoais ou confidenciais. Seu uso é registrado para fins de segurança e para melhorar os produtos e serviços da NVIDIA. Os dados de sessão registrados para fins de melhoria não estão vinculados à sua identidade nem a qualquer identificador persistente. Para mais informações sobre nossas práticas de processamento de dados, consulte nossa [Política de Privacidade](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Ao interagir com este endpoint, você consente com a nossa coleta, registro e uso dessas informações e com os [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: As solicitações são retidas por 30 dias de acordo com [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index f9633ac3230..67e7881bdd4 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -117,6 +117,7 @@ OpenCode Zen работает как любой другой провайдер | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -148,6 +149,7 @@ https://opencode.ai/zen/v1/models | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -224,6 +226,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Laguna S 2.1 Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Ling-3.0-flash Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. +- LongCat-2.0 Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - North Mini Code Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Nemotron 3 Ultra Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Big Pickle — это скрытая модель, которая доступна бесплатно в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. @@ -285,6 +288,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - Laguna S 2.1 Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - Ling-3.0-flash Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. +- LongCat-2.0 Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - North Mini Code Free: В течение бесплатного периода собранные данные могут храниться и использоваться для улучшения модели. Не отправляйте персональные или конфиденциальные данные. Ознакомьтесь с нашими [Условиями использования](https://cohere.com/terms-of-use) и [Политикой конфиденциальности](https://cohere.com/privacy). - Nemotron 3 Ultra Free (бесплатные эндпоинты NVIDIA): только для пробного использования — не отправляйте персональные или конфиденциальные данные. Использование логируется в целях безопасности и для улучшения продуктов и сервисов NVIDIA. Логируемые данные сессии, используемые в целях улучшения, не связаны с вашей личностью или каким-либо постоянным идентификатором. Подробнее о наших практиках обработки данных см. в нашей [Политике конфиденциальности](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Взаимодействуя с этим эндпоинтом, вы соглашаетесь на сбор, запись и использование нами такой информации, а также с [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: запросы хранятся 30 дней в соответствии с [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index 2c256704ae9..724b27bdcd9 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -110,6 +110,7 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -139,6 +140,7 @@ https://opencode.ai/zen/v1/models | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -215,6 +217,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Laguna S 2.1 Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Ling-3.0-flash Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล +- LongCat-2.0 Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - North Mini Code Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Nemotron 3 Ultra Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Big Pickle เป็น stealth model ที่ใช้งานฟรีบน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล @@ -273,6 +276,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - Laguna S 2.1 Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - Ling-3.0-flash Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล +- LongCat-2.0 Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - North Mini Code Free: ในช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกเก็บรักษาและนำไปใช้เพื่อปรับปรุงโมเดล โปรดอย่าส่งข้อมูลส่วนบุคคลหรือข้อมูลที่เป็นความลับ ดู[ข้อกำหนดการใช้งาน](https://cohere.com/terms-of-use)และ[นโยบายความเป็นส่วนตัว](https://cohere.com/privacy)ของเรา - Nemotron 3 Ultra Free (endpoint ฟรีของ NVIDIA): ใช้สำหรับการทดลองเท่านั้น — โปรดอย่าส่งข้อมูลส่วนบุคคลหรือข้อมูลลับ การใช้งานของคุณจะถูกบันทึกเพื่อวัตถุประสงค์ด้านความปลอดภัยและเพื่อปรับปรุงผลิตภัณฑ์และบริการของ NVIDIA ข้อมูลเซสชันที่บันทึกไว้เพื่อวัตถุประสงค์ในการปรับปรุงจะไม่เชื่อมโยงกับตัวตนของคุณหรือตัวระบุถาวรใด ๆ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับแนวปฏิบัติในการประมวลผลข้อมูลของเรา โปรดดู [นโยบายความเป็นส่วนตัว](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) ของเรา การโต้ตอบกับ endpoint นี้ถือว่าคุณยินยอมให้เราเก็บรวบรวม บันทึก และใช้ข้อมูลดังกล่าว รวมถึง [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) - OpenAI APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 83f5cfea780..43e08193b6b 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -108,6 +108,7 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -137,6 +138,7 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -213,6 +215,7 @@ Kredi kartı ücretleri maliyet üzerinden yansıtılır (%4.4 + işlem başına - MiMo-V2.5 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Laguna S 2.1 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Ling-3.0-flash Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. +- LongCat-2.0 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - North Mini Code Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Nemotron 3 Ultra Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Big Pickle, sınırlı bir süre için OpenCode'da ücretsiz olan gizli bir modeldir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. @@ -271,6 +274,7 @@ Tüm modellerimiz US'de barındırılıyor. Sağlayıcılarımız zero-retention - MiMo-V2.5 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - Laguna S 2.1 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - Ling-3.0-flash Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. +- LongCat-2.0 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - North Mini Code Free: Ücretsiz kullanım süresi boyunca toplanan veriler saklanabilir ve modeli geliştirmek için kullanılabilir. Kişisel veya gizli veriler göndermeyin. [Kullanım Koşullarımıza](https://cohere.com/terms-of-use) ve [Gizlilik Politikamıza](https://cohere.com/privacy) bakın. - Nemotron 3 Ultra Free (ücretsiz NVIDIA uç noktaları): Yalnızca deneme amaçlıdır — kişisel veya gizli veri göndermeyin. Kullanımınız güvenlik amacıyla ve NVIDIA ürünlerini ve hizmetlerini geliştirmek için kaydedilir. Geliştirme amacıyla kaydedilen oturum verileri kimliğinizle veya herhangi bir kalıcı tanımlayıcıyla ilişkilendirilmez. Veri işleme uygulamalarımız hakkında daha fazla bilgi için [Gizlilik Politikamıza](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) bakın. Bu uç noktayla etkileşime geçerek, bu tür bilgileri toplamamıza, kaydetmemize ve kullanmamıza ve [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) koşullarına onay vermiş olursunuz. - OpenAI APIs: İstekler [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) uyarınca 30 gün boyunca saklanır. diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 6be3aa7d992..efadfc568d1 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -117,6 +117,7 @@ You can also access our models through the following API endpoints. | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -148,6 +149,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -224,6 +226,7 @@ The free models: - MiMo-V2.5 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Laguna S 2.1 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Ling-3.0-flash Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. +- LongCat-2.0 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - North Mini Code Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Nemotron 3 Ultra Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. @@ -285,6 +288,7 @@ All our models are hosted in the US. Our providers follow a zero-retention polic - MiMo-V2.5 Free: During its free period, collected data may be used to improve the model. - Laguna S 2.1 Free: During its free period, collected data may be used to improve the model. - Ling-3.0-flash Free: During its free period, collected data may be used to improve the model. +- LongCat-2.0 Free: During its free period, collected data may be used to improve the model. - North Mini Code Free: During its free period, collected data may be retained and used to improve the model. Do not submit personal or confidential data. See our [Terms of Use](https://cohere.com/terms-of-use) and [Privacy Policy](https://cohere.com/privacy). - Nemotron 3 Ultra Free (NVIDIA free endpoints): Trial use only — do not submit personal or confidential data. Your use is logged for security purposes and to improve NVIDIA products and services. The logged session data for improvement purposes is not linked to your identity or any persistent identifier. For more information about our data processing practices, see our [Privacy Policy](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). By interacting with this endpoint, you consent to our collection, recording, and use of such information and the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index f4bb9cc5022..f4791cc5934 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -108,6 +108,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -137,6 +138,7 @@ https://opencode.ai/zen/v1/models | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -213,6 +215,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Laguna S 2.1 Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Ling-3.0-flash Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 +- LongCat-2.0 Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - North Mini Code Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Nemotron 3 Ultra Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Big Pickle 是一个隐身模型,目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 @@ -271,6 +274,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free:在免费期间,收集的数据可能会被用于改进模型。 - Laguna S 2.1 Free:在免费期间,收集的数据可能会被用于改进模型。 - Ling-3.0-flash Free:在免费期间,收集的数据可能会被用于改进模型。 +- LongCat-2.0 Free:在免费期间,收集的数据可能会被用于改进模型。 - North Mini Code Free:免费期间,所收集的数据可能会被保留并用于改进模型。请勿提交个人或机密数据。请参阅我们的[使用条款](https://cohere.com/terms-of-use)和[隐私政策](https://cohere.com/privacy)。 - Nemotron 3 Ultra Free(NVIDIA 免费端点):仅供试用 — 请勿提交个人或机密数据。出于安全目的以及为改进 NVIDIA 产品和服务,系统会记录你的使用情况。出于改进目的而记录的会话数据不会与你的身份或任何持久标识符相关联。有关我们数据处理实践的更多信息,请参阅我们的[隐私政策](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)。与此端点进行交互,即表示你同意我们收集、记录和使用此类信息,并同意 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)。 - OpenAI APIs:请求会根据 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 60b87c88df1..9f68aeea68a 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -112,6 +112,7 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Laguna S 2.1 Free | laguna-s-2.1-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling-3.0-flash Free | ling-3.0-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| LongCat-2.0 Free | longcat-2.0-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -142,6 +143,7 @@ https://opencode.ai/zen/v1/models | MiMo-V2.5 Free | Free | Free | Free | - | | Laguna S 2.1 Free | Free | Free | Free | - | | Ling-3.0-flash Free | Free | Free | Free | - | +| LongCat-2.0 Free | Free | Free | Free | - | | North Mini Code Free | Free | Free | Free | - | | Nemotron 3 Ultra Free | Free | Free | Free | - | | MiniMax M3 | $0.30 | $1.20 | $0.06 | - | @@ -218,6 +220,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Laguna S 2.1 Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Ling-3.0-flash Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 +- LongCat-2.0 Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - North Mini Code Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Nemotron 3 Ultra Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Big Pickle 是一個隱身模型,在 OpenCode 上限時免費提供。團隊正在利用這段時間收集回饋並改進模型。 @@ -277,6 +280,7 @@ https://opencode.ai/zen/v1/models - MiMo-V2.5 Free: 在免費期間,收集到的資料可能會用於改進模型。 - Laguna S 2.1 Free: 在免費期間,收集到的資料可能會用於改進模型。 - Ling-3.0-flash Free: 在免費期間,收集到的資料可能會用於改進模型。 +- LongCat-2.0 Free: 在免費期間,收集到的資料可能會用於改進模型。 - North Mini Code Free:免費期間,所收集的資料可能會被保留並用於改進模型。請勿提交個人或機密資料。請參閱我們的[使用條款](https://cohere.com/terms-of-use)和[隱私權政策](https://cohere.com/privacy)。 - Nemotron 3 Ultra Free(NVIDIA 免費端點):僅供試用 — 請勿提交個人或機密資料。基於安全目的以及為了改進 NVIDIA 產品與服務,系統會記錄你的使用情況。基於改進目的而記錄的工作階段資料不會與你的身分或任何持久識別碼相關聯。有關我們資料處理實務的更多資訊,請參閱我們的[隱私政策](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)。與此端點進行互動,即表示你同意我們收集、記錄與使用此類資訊,並同意 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)。 - OpenAI APIs: 請求會依據 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 From 4a57013cf8cb163f58638273fd9da8538cd33cb7 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 08:25:06 +0000 Subject: [PATCH 070/114] fix(app): show pending tool details (#40603) Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> --- packages/session-ui/src/components/basic-tool.tsx | 2 +- packages/session-ui/src/components/message-part.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/session-ui/src/components/basic-tool.tsx b/packages/session-ui/src/components/basic-tool.tsx index 2b73db24ed1..efc15c173f9 100644 --- a/packages/session-ui/src/components/basic-tool.tsx +++ b/packages/session-ui/src/components/basic-tool.tsx @@ -204,7 +204,7 @@ export function BasicTool(props: BasicToolProps) { > - + - + {trigger().subtitle} - + {(arg) => {arg}} From f929f8f100581a94f3484b6992d02d57d75fab7f Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:05:19 -0500 Subject: [PATCH 071/114] refactor(opencode): simplify retry error matching (#40694) --- packages/opencode/src/session/retry.ts | 37 +++++++------------- packages/opencode/test/session/retry.test.ts | 9 +++-- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index 4139665bd2b..d1864cb7a8a 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -122,32 +122,19 @@ export function retryable(error: Err, provider: string) { return { message: error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message } } - // Check for rate limit patterns in plain text error messages - const msg = isRecord(error.data) ? error.data.message : undefined - if (typeof msg === "string") { - const lower = msg.toLowerCase() - if ( - lower.includes("rate increased too quickly") || - lower.includes("rate limit") || - lower.includes("too many requests") - ) { - return { message: msg } - } - } - - const json = parseJSON(msg) - if (!json || typeof json !== "object") return undefined - const code = typeof json.code === "string" ? json.code : "" - - if (json.type === "error" && json.error?.type === "too_many_requests") { - return { message: "Too Many Requests" } - } - if (code.includes("exhausted") || code.includes("unavailable")) { - return { message: "Provider is overloaded" } - } - if (json.type === "error" && typeof json.error?.code === "string" && json.error.code.includes("rate_limit")) { - return { message: "Rate Limited" } + const message = isRecord(error.data) ? error.data.message : undefined + if (typeof message !== "string") return undefined + const lower = message.toLowerCase() + if ( + lower.includes("rate increased too quickly") || + lower.includes("rate limit") || + lower.includes("rate_limit") || + lower.includes("too many requests") + ) { + return { message } } + if (lower.includes("too_many_requests")) return { message: "Too Many Requests" } + if (lower.includes("exhausted") || lower.includes("unavailable")) return { message: "Provider is overloaded" } return undefined } diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 30ac879a6a9..0e30a5473a2 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -118,16 +118,21 @@ describe("session.retry.delay", () => { }) describe("session.retry.retryable", () => { - test("maps too_many_requests json messages", () => { + test("retries serialized too_many_requests messages", () => { const error = wrap(JSON.stringify({ type: "error", error: { type: "too_many_requests" } })) expect(SessionRetry.retryable(error, retryProvider)).toEqual({ message: "Too Many Requests" }) }) - test("maps overloaded provider codes", () => { + test("retries serialized overloaded provider codes", () => { const error = wrap(JSON.stringify({ code: "resource_exhausted" })) expect(SessionRetry.retryable(error, retryProvider)).toEqual({ message: "Provider is overloaded" }) }) + test("retries serialized rate_limit messages", () => { + const message = JSON.stringify({ type: "error", error: { code: "rate_limit_exceeded" } }) + expect(SessionRetry.retryable(wrap(message), retryProvider)).toEqual({ message }) + }) + test("does not retry unknown json messages", () => { const error = wrap(JSON.stringify({ error: { message: "no_kv_space" } })) expect(SessionRetry.retryable(error, retryProvider)).toBeUndefined() From 61aefc07593043a2cef6cc870f7267b09483f5c0 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:39:49 -0500 Subject: [PATCH 072/114] fix(opencode): expand retryable error patterns (#40707) --- packages/opencode/src/session/retry.ts | 29 ++++++++++----- packages/opencode/test/session/retry.test.ts | 39 ++++++++++++++++++++ 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index d1864cb7a8a..22399e8703a 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -28,6 +28,15 @@ export const RETRY_BACKOFF_FACTOR = 2 export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout +const RETRYABLE_MESSAGE_PATTERNS = [ + /429|500|502|503|504|524/i, + /rate increased too quickly|rate limit|rate-limit|rate_limit|too many requests/i, + /overloaded|service unavailable|service_unavailable|service-unavailable|internal error|internal_error|internal server error|server error|server_error|server-error|provider returned error|provider_returned_error|provider-returned-error/i, + /terminated|fetch failed|failed to fetch|network error|upstream connect|connection error|connection refused|connection lost|socket connection was closed|socket hang up|reset before headers|getaddrinfo|enotfound|eai_again|econnrefused|econnreset|etimedout/i, + /^timeout$|\b(?:request|response|connection|network|stream|read) (?:timeout|timed out|time out)\b/i, + /try your request again|retry your request|resource exhausted|resource_exhausted/i, +] + function cap(ms: number) { return Math.min(ms, RETRY_MAX_DELAY) } @@ -72,7 +81,12 @@ export function retryable(error: Err, provider: string) { const status = error.data.statusCode // 5xx errors are transient server failures and should always be retried, // even when the provider SDK doesn't explicitly mark them as retryable. - if (!error.data.isRetryable && !(status !== undefined && status >= 500)) return undefined + if ( + !error.data.isRetryable && + !(status !== undefined && status >= 500) && + !matchesRetryableMessage(error.data.message) && + !matchesRetryableMessage(error.data.responseBody) + ) return undefined if (error.data.responseBody?.includes("FreeUsageLimitError")) { return { message: GO_UPSELL_MESSAGE, @@ -125,19 +139,16 @@ export function retryable(error: Err, provider: string) { const message = isRecord(error.data) ? error.data.message : undefined if (typeof message !== "string") return undefined const lower = message.toLowerCase() - if ( - lower.includes("rate increased too quickly") || - lower.includes("rate limit") || - lower.includes("rate_limit") || - lower.includes("too many requests") - ) { - return { message } - } if (lower.includes("too_many_requests")) return { message: "Too Many Requests" } if (lower.includes("exhausted") || lower.includes("unavailable")) return { message: "Provider is overloaded" } + if (matchesRetryableMessage(message)) return { message } return undefined } +function matchesRetryableMessage(value: unknown) { + return typeof value === "string" && RETRYABLE_MESSAGE_PATTERNS.some((pattern) => pattern.test(value)) +} + function str(value: unknown) { if (value === undefined || value === null) return "" return String(value) diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 0e30a5473a2..018f76fc3ea 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -168,6 +168,45 @@ describe("session.retry.retryable", () => { expect(SessionRetry.retryable(error, retryProvider)).toEqual({ message: msg }) }) + test.each([ + "Internal server error", + "internal error", + "server-error", + "Provider returned error", + "provider-returned-error", + "terminated", + "fetch failed", + "connection refused", + "connect ECONNREFUSED", + "request ETIMEDOUT", + "failed to fetch", + "EAI_AGAIN", + "response timed out", + "Please retry your request", + "try your request again", + "upstream returned status 524", + ])("retries matching API error text: %s", (message) => { + expect(SessionRetry.retryable(wrap(message), retryProvider)).toEqual({ message }) + }) + + test("retries hyphenated service-unavailable errors", () => { + expect(SessionRetry.retryable(wrap("service-unavailable"), retryProvider)).toEqual({ + message: "Provider is overloaded", + }) + }) + + test("matches retryable API response bodies", () => { + const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)( + new SessionV1.APIError({ + message: "Request failed", + isRetryable: false, + statusCode: 400, + responseBody: JSON.stringify({ error: { message: "upstream connection refused" } }), + }).toObject(), + ) + expect(SessionRetry.retryable(error, retryProvider)).toEqual({ message: "Request failed" }) + }) + test("retries transport timeout errors", () => { const request = MessageV2.fromError(new ProviderError.HeaderTimeoutError(10000), { providerID }) expect(SessionV1.APIError.isInstance(request)).toBe(true) From 057b5a9dee0b151b18ff5a1164a3cf709389497a Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 5 Aug 2026 19:41:32 +0000 Subject: [PATCH 073/114] chore: generate --- packages/opencode/src/session/retry.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index 22399e8703a..cab48dda633 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -86,7 +86,8 @@ export function retryable(error: Err, provider: string) { !(status !== undefined && status >= 500) && !matchesRetryableMessage(error.data.message) && !matchesRetryableMessage(error.data.responseBody) - ) return undefined + ) + return undefined if (error.data.responseBody?.includes("FreeUsageLimitError")) { return { message: GO_UPSELL_MESSAGE, From b84c63d034c7acbe897bd6515b0042fab58770fc Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:02:39 -0500 Subject: [PATCH 074/114] fix(stats): reduce html payloads --- .../stats/app/src/routes/[lab]/[model].tsx | 193 +++----- packages/stats/app/src/routes/[lab]/index.tsx | 79 ++-- .../stats/app/src/routes/compare-cards.tsx | 2 +- packages/stats/app/src/routes/geo-map.ts | 120 +++++ packages/stats/app/src/routes/index.tsx | 447 +++--------------- 5 files changed, 314 insertions(+), 527 deletions(-) create mode 100644 packages/stats/app/src/routes/geo-map.ts diff --git a/packages/stats/app/src/routes/[lab]/[model].tsx b/packages/stats/app/src/routes/[lab]/[model].tsx index 3b2bf341fdb..0b079e91513 100644 --- a/packages/stats/app/src/routes/[lab]/[model].tsx +++ b/packages/stats/app/src/routes/[lab]/[model].tsx @@ -1,23 +1,17 @@ import { Meta, Title } from "@solidjs/meta" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" -import { geoEquirectangular, geoPath } from "d3-geo" import { scaleSqrt } from "d3-scale" import countryCodesSource from "i18n-iso-countries/codes.json?raw" -import { feature, mesh } from "topojson-client" -import countriesTopologySource from "world-atlas/countries-50m.json?raw" import { getStatsModelData, type CountryEntry, type ModelPeerEntry, type ModelUsagePoint, type StatsModelData, - type UsageRange, } from "@opencode-ai/stats-core/domain/home" import { createAsync, query, useParams } from "@solidjs/router" import { createMemo, createSignal, createUniqueId, For, onMount, Show, type JSX } from "solid-js" import { getRequestEvent } from "solid-js/web" -import type { FeatureCollection, GeometryObject, GeoJsonProperties } from "geojson" -import type { GeometryCollection, Topology } from "topojson-specification" import { LocaleLinks } from "../../component/locale-links" import { useI18n } from "../../context/i18n" import { useLanguage } from "../../context/language" @@ -25,10 +19,10 @@ import { localizedUrl } from "../../lib/language" import { findModelCatalogEntry, formatCatalogLabName, - getModelCatalog, - type ModelCatalog, + loadModelCatalog, type ModelCatalogEntry, } from "../model-catalog" +import { geoMapHeight, geoMapWidth, worldBorderPath, worldCountryMarkers, worldCountryPaths } from "../geo-map" import { SectionHeading } from "../section-heading" import { runStatsEffect } from "../../stats-runtime" import { setStatsPageCacheHeaders } from "../stats-cache" @@ -51,45 +45,41 @@ import { } from "../stats-shell" const statsUnfurlPath = "banner.png" -const geoMapWidth = 960 -const geoMapHeight = 430 const shortMonths = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] as const type IsoCountryCode = readonly [string, string, string] -type WorldCountryProperties = GeoJsonProperties & { name?: string } -type WorldTopology = Topology<{ countries: GeometryCollection }> +type ModelCatalogOption = Pick +type ModelPageCatalog = { + entry: ModelCatalogEntry | null + labs: { id: string; name: string }[] + labModels: ModelCatalogOption[] +} +type StatsModelPageData = Omit & { country: CountryEntry[] } +type ModelPageData = { catalog: ModelPageCatalog; stats: StatsModelPageData | null } const countryNumericIds = new Map( (JSON.parse(countryCodesSource) as IsoCountryCode[]).map((country) => [country[0], country[2]] as const), ) -const worldTopology = JSON.parse(countriesTopologySource) as WorldTopology -const worldCountryGeometries: GeometryCollection = { - ...worldTopology.objects.countries, - geometries: worldTopology.objects.countries.geometries.filter((country) => String(country.id ?? "") !== "010"), -} -const worldCountries = feature(worldTopology, worldCountryGeometries) as FeatureCollection< - GeometryObject, - WorldCountryProperties -> -const worldProjection = geoEquirectangular().fitExtent( - [ - [10, 12], - [geoMapWidth - 10, geoMapHeight - 12], - ], - worldCountries, -) -const worldPath = geoPath(worldProjection) -const worldCountryPaths = worldCountries.features.map((country) => ({ - id: String(country.id ?? "").padStart(3, "0"), - path: worldPath(country) ?? "", - marker: geoCountryMarker(country), -})) -const worldBorderPath = worldPath(mesh(worldTopology, worldCountryGeometries, (a, b) => a !== b)) ?? "" -const getModelData = query(async (lab: string, model: string) => { +const getModelPageData = query(async (labParam: string, modelParam: string) => { "use server" - return runStatsEffect(getStatsModelData(model, lab)) -}, "getStatsModelData") + const catalog = await loadModelCatalog() + const entry = findModelCatalogEntry(catalog, modelParam, labParam) ?? null + const lab = entry?.lab ?? labParam + const model = entry?.slug ?? modelParam + const stats = lab && model ? await runStatsEffect(getStatsModelData(model, lab)) : null + return { + catalog: { + entry, + labs: catalog.labs.map((item) => ({ id: item.id, name: item.name })), + labModels: + catalog.labs + .find((item) => item.id === (entry?.lab ?? providerSlug(labParam))) + ?.models.map((item) => ({ id: item.id, lab: item.lab, slug: item.slug, name: item.name })) ?? [], + }, + stats: stats ? { ...stats, country: stats.country["2M"] } : null, + } satisfies ModelPageData +}, "getStatsModelPageData") export default function StatsModel() { const i18n = useI18n() @@ -99,18 +89,9 @@ export default function StatsModel() { const params = useParams() const labParam = createMemo(() => params.lab ?? "") const modelParam = createMemo(() => params.model ?? "") - const catalog = createAsync(() => getModelCatalog()) - const catalogEntry = createMemo(() => { - const data = catalog() - if (!data) return undefined - return findModelCatalogEntry(data, modelParam(), labParam()) ?? null - }) - const stats = createAsync(() => { - const entry = catalogEntry() - if (catalog() === undefined || entry === undefined) return Promise.resolve(undefined) - if (!entry && (!labParam() || !modelParam())) return Promise.resolve(null) - return getModelData(labParam(), entry?.slug ?? modelParam()) - }) + const page = createAsync(() => getModelPageData(labParam(), modelParam())) + const catalogEntry = createMemo(() => page()?.catalog.entry) + const stats = createMemo(() => page()?.stats) const githubStars = createAsync(() => getGitHubStars()) const [themePreference, setThemePreference] = createSignal("system") const modelName = createMemo(() => catalogEntry()?.name ?? stats()?.model ?? modelParam() ?? i18n.t("model.fallback")) @@ -179,13 +160,13 @@ export default function StatsModel() {
- }> + }> }> <> @@ -193,10 +174,10 @@ export default function StatsModel() { - + props.catalog?.name ?? props.data?.model ?? i18n.t("model.fallback") const weights = () => props.catalog?.weights[0] const labs = () => props.catalogData?.labs ?? [] - const labModels = () => - props.catalogData?.labs.find((lab) => lab.id === providerSlug(labId()))?.models ?? - (props.catalog ? [props.catalog] : []) + const labModels = () => props.catalogData?.labModels ?? (props.catalog ? [props.catalog] : []) return (
) } @@ -1853,12 +1576,10 @@ function MetricBar(props: { value: number; max: number; active: boolean }) { ) } -function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) { +function SessionCostSection(props: { data: SessionCostEntry[] }) { const i18n = useI18n() - const [product, setProduct] = createSignal("Go") const [activeIndex, setActiveIndex] = createSignal(2) - const data = createMemo(() => props.data[product()]) - const visible = createMemo(() => data().slice(0, 16)) + const visible = createMemo(() => props.data.slice(0, 16)) const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(visible().length - 1, 0))) return ( @@ -1877,17 +1598,6 @@ function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) { >
- ) } @@ -1949,11 +1659,6 @@ function SessionCostChart(props: { ) } -function LiveIndicator() { - const i18n = useI18n() - return {i18n.t("chart.live")} -} - function formatTokenCount(value: number) { if (value >= 1_000_000) return `${Number((value / 1_000_000).toFixed(1))}M` return `${Math.round(value / 1_000)}K` From 3355b78d91876104085995ea5e9dc27ede28d21b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 5 Aug 2026 20:06:05 +0000 Subject: [PATCH 075/114] chore: generate --- packages/stats/app/src/routes/[lab]/[model].tsx | 7 +------ packages/stats/app/src/routes/[lab]/index.tsx | 6 +----- packages/stats/app/src/routes/index.tsx | 7 +------ 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/stats/app/src/routes/[lab]/[model].tsx b/packages/stats/app/src/routes/[lab]/[model].tsx index 0b079e91513..c9906a975e9 100644 --- a/packages/stats/app/src/routes/[lab]/[model].tsx +++ b/packages/stats/app/src/routes/[lab]/[model].tsx @@ -16,12 +16,7 @@ import { LocaleLinks } from "../../component/locale-links" import { useI18n } from "../../context/i18n" import { useLanguage } from "../../context/language" import { localizedUrl } from "../../lib/language" -import { - findModelCatalogEntry, - formatCatalogLabName, - loadModelCatalog, - type ModelCatalogEntry, -} from "../model-catalog" +import { findModelCatalogEntry, formatCatalogLabName, loadModelCatalog, type ModelCatalogEntry } from "../model-catalog" import { geoMapHeight, geoMapWidth, worldBorderPath, worldCountryMarkers, worldCountryPaths } from "../geo-map" import { SectionHeading } from "../section-heading" import { runStatsEffect } from "../../stats-runtime" diff --git a/packages/stats/app/src/routes/[lab]/index.tsx b/packages/stats/app/src/routes/[lab]/index.tsx index 8931c2af0cf..3d4a1a663c3 100644 --- a/packages/stats/app/src/routes/[lab]/index.tsx +++ b/packages/stats/app/src/routes/[lab]/index.tsx @@ -152,11 +152,7 @@ export default function StatsLab() { - + usageTotal(item) > 0)} fallback={} > - +
0} From 709c195905c260299facd5b566d9ab537c48c3bc Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:14:03 -0500 Subject: [PATCH 076/114] fix(opencode): preserve compatible stream errors (#40718) --- bun.lock | 1 + package.json | 3 +- .../test/session/processor-effect.test.ts | 49 +++++++++++++++++++ .../@ai-sdk%2Fopenai-compatible@2.0.41.patch | 39 +++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch diff --git a/bun.lock b/bun.lock index 0d08cb8a95a..3ba15ef800b 100644 --- a/bun.lock +++ b/bun.lock @@ -1078,6 +1078,7 @@ "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", "pacote@21.5.0": "patches/pacote@21.5.0.patch", "@dnd-kit/dom@0.5.0": "patches/@dnd-kit%2Fdom@0.5.0.patch", + "@ai-sdk/openai-compatible@2.0.41": "patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch", }, "overrides": { "@opentui/core": "catalog:", diff --git a/package.json b/package.json index 15725c865fc..58712547b4b 100644 --- a/package.json +++ b/package.json @@ -158,6 +158,7 @@ "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch", "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch", - "@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch" + "@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch", + "@ai-sdk/openai-compatible@2.0.41": "patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch" } } diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index 52876054365..d9466c0c2c9 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -604,6 +604,55 @@ it.live("session.processor effect tests retry recognized structured json errors" ), ) +it.live("session.processor effect tests retry OpenAI-compatible midstream server errors", () => + provideTmpdirServer( + ({ dir, llm }) => + Effect.gen(function* () { + const { processors, session, provider } = yield* boot() + + yield* llm.push( + raw({ chunks: [{ error: { type: "server_error", code: "server_error", message: "xxx" } }] }), + ) + yield* llm.text("after") + + const chat = yield* session.create({}) + const parent = yield* user(chat.id, "retry midstream server error") + const msg = yield* assistant(chat.id, parent.id, path.resolve(dir)) + const mdl = yield* provider.getModel(ref.providerID, ref.modelID) + const handle = yield* processors.create({ + assistantMessage: msg, + sessionID: chat.id, + model: mdl, + }) + + const value = yield* handle.process({ + user: { + id: parent.id, + sessionID: chat.id, + role: "user", + time: parent.time, + agent: parent.agent, + model: { providerID: ref.providerID, modelID: ref.modelID }, + } satisfies SessionV1.User, + sessionID: chat.id, + model: mdl, + agent: agent(), + system: [], + messages: [{ role: "user", content: "retry midstream server error" }], + tools: {}, + }) + + const parts = yield* MessageV2.parts(msg.id) + + expect(value).toBe("continue") + expect(yield* llm.calls).toBe(2) + expect(parts.some((part) => part.type === "text" && part.text === "after")).toBe(true) + expect(handle.message.error).toBeUndefined() + }), + { config: (url) => providerCfg(url) }, + ), +) + it.live("session.processor effect tests publish retry status updates", () => provideTmpdirServer( ({ dir, llm }) => diff --git a/patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch b/patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch new file mode 100644 index 00000000000..9f03ec95732 --- /dev/null +++ b/patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch @@ -0,0 +1,39 @@ +diff --git a/dist/index.js b/dist/index.js +index dca128d3a790378c51a24a16d92585178343b278..da75f9d64acd2b607abd15079ce2d03b7a8d675a 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -696,7 +696,7 @@ var OpenAICompatibleChatLanguageModel = class { + finishReason = { unified: "error", raw: void 0 }; + controller.enqueue({ + type: "error", +- error: chunk.value.error.message ++ error: chunk.value.error + }); + return; + } +diff --git a/dist/index.mjs b/dist/index.mjs +index 3b1e1b6bdec5032e3b4fa5ffbcc8cdf3dfe1cc40..eaffc446f80552ea0b26573b89daa4dfc7776e6e 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -683,7 +683,7 @@ var OpenAICompatibleChatLanguageModel = class { + finishReason = { unified: "error", raw: void 0 }; + controller.enqueue({ + type: "error", +- error: chunk.value.error.message ++ error: chunk.value.error + }); + return; + } +diff --git a/src/chat/openai-compatible-chat-language-model.ts b/src/chat/openai-compatible-chat-language-model.ts +index 8c622db23c2d9a7373701f5a1b0c2ba109e24602..643c3db68a6043e097edc1122e0eb53fd13495c5 100644 +--- a/src/chat/openai-compatible-chat-language-model.ts ++++ b/src/chat/openai-compatible-chat-language-model.ts +@@ -442,7 +442,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 { + finishReason = { unified: 'error', raw: undefined }; + controller.enqueue({ + type: 'error', +- error: chunk.value.error.message, ++ error: chunk.value.error, + }); + return; + } From 082fe93e160c042332b3686a006d0f35ce4a6d6e Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 5 Aug 2026 20:16:40 +0000 Subject: [PATCH 077/114] chore: generate --- packages/opencode/test/session/processor-effect.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index d9466c0c2c9..052477d0a2e 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -610,9 +610,7 @@ it.live("session.processor effect tests retry OpenAI-compatible midstream server Effect.gen(function* () { const { processors, session, provider } = yield* boot() - yield* llm.push( - raw({ chunks: [{ error: { type: "server_error", code: "server_error", message: "xxx" } }] }), - ) + yield* llm.push(raw({ chunks: [{ error: { type: "server_error", code: "server_error", message: "xxx" } }] })) yield* llm.text("after") const chat = yield* session.create({}) From b1f8cc04af936c3d3b5de8cb0645c6d249968eb4 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 5 Aug 2026 20:33:25 +0000 Subject: [PATCH 078/114] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 83bc08a630e..6f321d88bf2 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-GRjnvvyj37H36RqiCB7dz5ALAEwvw16izwuk1wsHEpU=", - "aarch64-linux": "sha256-0OIn1o6dpqIQ5XgIMzpenMCMqsYzraaYVJk+te5eINU=", - "aarch64-darwin": "sha256-sQSQcuox78d8wT1lsKYHqVBl11NusiszQ+gu2XYXZi8=", - "x86_64-darwin": "sha256-SINkhMRd4oM1zABSs1Uf3OAzxJ1lgcycl1U4fmi+baY=" + "x86_64-linux": "sha256-uduwrM143NDSc+tXsi4lVVfoMll2a3BDHRUjuO7GB68=", + "aarch64-linux": "sha256-6DUda78XdXY6DP86lIUkweSjys3iG4Y4mo1PiaNuXbg=", + "aarch64-darwin": "sha256-AkJwfLULLZVwwz+XU1QcFUZoIS7oVPCn+n/MXEaxrqE=", + "x86_64-darwin": "sha256-hAxKGdiITTxQ2uujQt6prNjo3NxGAMMeo+9HlMWK6GU=" } } From 146720e197866afc2e799462374c42b51e12857b Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:54:35 -0500 Subject: [PATCH 079/114] fix(stats): improve page speed --- packages/stats/app/src/entry-server.tsx | 12 ++++++- .../stats/app/src/routes/compare-cards.tsx | 1 - packages/stats/app/src/routes/index.css | 36 +++++++++++++------ packages/stats/app/src/routes/index.tsx | 30 ++++------------ packages/stats/app/vite.config.ts | 3 +- 5 files changed, 45 insertions(+), 37 deletions(-) diff --git a/packages/stats/app/src/entry-server.tsx b/packages/stats/app/src/entry-server.tsx index 3cec2cba04d..1ea92a46e93 100644 --- a/packages/stats/app/src/entry-server.tsx +++ b/packages/stats/app/src/entry-server.tsx @@ -1,7 +1,10 @@ // @refresh reload +import type { Asset, PageEvent } from "@solidjs/start" import { createHandler, StartServer } from "@solidjs/start/server" +import ibmPlexMonoMediumLatin1 from "@ibm/plex/IBM-Plex-Mono/fonts/split/woff2/IBMPlexMono-Medium-Latin1.woff2?url" import { getRequestEvent } from "solid-js/web" import { dir, localeFromRequest, tag } from "./lib/language" +import statsStylesheetUrl from "./routes/index.css?url" const statsThemePreloadScript = `;(function () { var preference = "system" @@ -18,8 +21,13 @@ export default createHandler( () => ( { - const event = getRequestEvent() + const event = getRequestEvent() as PageEvent | undefined const locale = event ? localeFromRequest(event.request) : "en" + const stylesheet = (event?.assets as Asset[] | undefined)?.find( + (asset): asset is Extract => + asset.tag === "link" && asset.attrs.rel === "stylesheet", + ) + const stylesheetHref = import.meta.env.DEV ? statsStylesheetUrl : stylesheet?.attrs.href return ( @@ -27,6 +35,8 @@ export default createHandler( + + {stylesheetHref ? : null} {assets} diff --git a/packages/stats/app/src/routes/compare-cards.tsx b/packages/stats/app/src/routes/compare-cards.tsx index 47cc07b9295..10a10558008 100644 --- a/packages/stats/app/src/routes/compare-cards.tsx +++ b/packages/stats/app/src/routes/compare-cards.tsx @@ -86,7 +86,6 @@ function FeaturedComparisonCard(props: { pair: ComparisonPair }) { diff --git a/packages/stats/app/src/routes/index.css b/packages/stats/app/src/routes/index.css index c378688490c..39877378a43 100644 --- a/packages/stats/app/src/routes/index.css +++ b/packages/stats/app/src/routes/index.css @@ -55,7 +55,7 @@ body { --color-background-strong: #161616; --color-background-strong-hover: #242424; --color-text: #5c5c5c; - --color-text-weak: #808080; + --color-text-weak: #707070; --color-text-strong: #161616; --color-text-inverted: #ffffff; --color-border-weak: #0000001a; @@ -66,10 +66,10 @@ body { --stats-line-strong: #00000033; --stats-text: #161616; --stats-muted: #5c5c5c; - --stats-faint: #808080; + --stats-faint: #707070; --stats-theme-icon-active: #3a3a3a; --stats-accent: #3b5cf6; - --stats-accent-text: #6c7dff; + --stats-accent-text: #3b5cf6; --stats-bar-idle: #d4d4d4; --stats-dot: #d4d4d4; --stats-hero-muted: #5c5c5c; @@ -92,6 +92,18 @@ body { display: none !important; } +[data-page="stats"] [data-slot="visually-hidden"] { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + border: 0; +} + [data-page="stats"][data-layout="compare-detail"] { /* The table contains its own wide rows; keep the page itself out of the horizontal scroll chain. */ overflow-x: visible; @@ -7079,7 +7091,7 @@ body { --color-background-strong: #ffffff; --color-background-strong-hover: #eeeeee; --color-text: #d4d4d4; - --color-text-weak: #808080; + --color-text-weak: #a3a3a3; --color-text-strong: #ffffff; --color-text-inverted: #161616; --color-border-weak: #ffffff1a; @@ -7090,11 +7102,12 @@ body { --stats-line-strong: #ffffff33; --stats-text: #ffffff; --stats-muted: #d4d4d4; - --stats-faint: #808080; + --stats-faint: #a3a3a3; + --stats-accent-text: #8190ff; --stats-theme-icon-active: #fafafa; --stats-bar-idle: #303030; --stats-dot: #303030; - --stats-hero-muted: #808080; + --stats-hero-muted: #a3a3a3; --stats-hero-pattern: #303030; --stats-logo-bg: #f1ecec; --stats-logo-fill: #b7b1b1; @@ -7254,7 +7267,7 @@ body { --color-background-strong: #ffffff; --color-background-strong-hover: #eeeeee; --color-text: #d4d4d4; - --color-text-weak: #808080; + --color-text-weak: #a3a3a3; --color-text-strong: #ffffff; --color-text-inverted: #161616; --color-border-weak: #ffffff1a; @@ -7265,11 +7278,12 @@ body { --stats-line-strong: #ffffff33; --stats-text: #ffffff; --stats-muted: #d4d4d4; - --stats-faint: #808080; + --stats-faint: #a3a3a3; + --stats-accent-text: #8190ff; --stats-theme-icon-active: #fafafa; --stats-bar-idle: #303030; --stats-dot: #303030; - --stats-hero-muted: #808080; + --stats-hero-muted: #a3a3a3; --stats-hero-pattern: #303030; --stats-logo-bg: #f1ecec; --stats-logo-fill: #b7b1b1; @@ -8393,7 +8407,7 @@ body { } [data-page="stats"] [data-component="top-models-chart"][data-dense-labels="true"] { - --top-models-mobile-bar-width: 12px; + --top-models-mobile-bar-width: 18px; } [data-page="stats"] [data-component="top-models-chart"][data-dense-labels="true"] [data-slot="top-models-axis"], @@ -8402,7 +8416,7 @@ body { } [data-page="stats"] [data-component="market-share"][data-dense-labels="true"] { - --market-mobile-bar-width: 12px; + --market-mobile-bar-width: 18px; } [data-page="stats"] [data-component="market-share"][data-dense-labels="true"] [data-slot="market-labels"], diff --git a/packages/stats/app/src/routes/index.tsx b/packages/stats/app/src/routes/index.tsx index 28e5cf59a33..398f53f7186 100644 --- a/packages/stats/app/src/routes/index.tsx +++ b/packages/stats/app/src/routes/index.tsx @@ -1,11 +1,7 @@ -import { Link, Meta, Title } from "@solidjs/meta" +import { Meta, Title } from "@solidjs/meta" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { scaleSqrt } from "d3-scale" import countryCodesSource from "i18n-iso-countries/codes.json?raw" -import ibmPlexMonoRegularLatin1 from "@ibm/plex/IBM-Plex-Mono/fonts/split/woff2/IBMPlexMono-Regular-Latin1.woff2?url" -import ibmPlexMonoMediumLatin1 from "@ibm/plex/IBM-Plex-Mono/fonts/split/woff2/IBMPlexMono-Medium-Latin1.woff2?url" -import ibmPlexMonoSemiBoldLatin1 from "@ibm/plex/IBM-Plex-Mono/fonts/split/woff2/IBMPlexMono-SemiBold-Latin1.woff2?url" -import ibmPlexMonoBoldLatin1 from "@ibm/plex/IBM-Plex-Mono/fonts/split/woff2/IBMPlexMono-Bold-Latin1.woff2?url" import { getStatsHomeData, type CacheRatioEntry, @@ -142,10 +138,6 @@ export default function StatsHome() { - - - -
@@ -227,7 +219,8 @@ function Hero(props: { updatedAt: string | null }) { return (
-

+

+ {currentUpdatedLabel()}

+
{(entry) => ( @@ -766,7 +758,7 @@ function Leaderboard(props: { )}
-
+
{(entry) => ( props.onActiveModelChange(props.entry.model)} onPointerLeave={(event) => { if (event.pointerType === "touch") return @@ -956,6 +945,7 @@ function MarketShare(props: { {(day, index) => ( +
{(message) => ( diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index c37c9c21161..26720c8ac0d 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -95,6 +95,8 @@ export const dict = { "command.session.share.description": "Share this session and copy the URL to clipboard", "command.session.unshare": "Unshare session", "command.session.unshare.description": "Stop sharing this session", + "command.session.export": "Export session", + "command.session.export.description": "Export the full session transcript as JSON", "palette.search.placeholder": "Search files, commands, and sessions", "palette.search.placeholder.home": "Search commands and sessions", @@ -489,6 +491,7 @@ export const dict = { "context.systemPrompt.title": "System Prompt", "context.rawMessages.title": "Raw messages", + "context.export.session": "Export session", "context.stats.session": "Session", "context.stats.messages": "Messages", @@ -568,6 +571,11 @@ export const dict = { "toast.session.unshare.failed.title": "Failed to unshare session", "toast.session.unshare.failed.description": "An error occurred while unsharing the session", + "toast.session.export.success.title": "Session exported", + "toast.session.export.success.description": "Saved session to {{filename}}", + "toast.session.export.failed.title": "Failed to export session", + "toast.session.export.failed.description": "An error occurred while exporting the session", + "toast.session.listFailed.title": "Failed to load sessions for {{project}}", "toast.project.reloadFailed.title": "Failed to reload {{project}}", @@ -802,6 +810,7 @@ export const dict = { "common.moreOptions": "More options", "common.learnMore": "Learn more", "common.rename": "Rename", + "common.export": "Export", "common.reset": "Reset", "common.archive": "Archive", "common.delete": "Delete", diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index 7d22ce5e166..e69623179fa 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -53,6 +53,7 @@ import type { UserMessage, } from "@opencode-ai/sdk/v2" import { showToast } from "@/utils/toast" +import { downloadSessionExport, fetchSessionExport, sessionExportFilename } from "@/utils/session-export" import { getDirectory, getFilename } from "@opencode-ai/core/util/path" import { Popover as KobaltePopover } from "@kobalte/core/popover" import { normalize } from "@opencode-ai/session-ui/session-diff" @@ -806,6 +807,29 @@ export function MessageTimeline(props: { navigate(`/${params.dir}/session`) } + const exportSession = async (sessionID: string) => { + try { + const data = await fetchSessionExport({ + sessionID, + client: sdk().client, + }) + const filename = sessionExportFilename(data.info) + downloadSessionExport(filename, data) + showToast({ + variant: "success", + icon: "circle-check", + title: language.t("toast.session.export.success.title"), + description: language.t("toast.session.export.success.description", { filename }), + }) + } catch (err) { + showToast({ + variant: "error", + title: language.t("toast.session.export.failed.title"), + description: err instanceof Error ? err.message : language.t("toast.session.export.failed.description"), + }) + } + } + const archiveSession = async (sessionID: string) => { const session = sync().session.get(sessionID) if (!session) return @@ -1564,6 +1588,9 @@ export function MessageTimeline(props: { + exportSession(id)}> + {language.t("common.export")} + void archiveSession(id)}> {language.t("common.archive")} @@ -1635,6 +1662,9 @@ export function MessageTimeline(props: { {language.t("session.share.action.share")}... + exportSession(id)}> + {language.t("common.export")}... + void archiveSession(id)}> {language.t("common.archive")} diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 12dd96a5e66..cfc302e7753 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -12,10 +12,11 @@ import { useSettings } from "@/context/settings" import { useSync } from "@/context/sync" import { useTerminal } from "@/context/terminal" import { showToast } from "@/utils/toast" +import { downloadSessionExport, fetchSessionExport, sessionExportFilename } from "@/utils/session-export" import { findLast } from "@opencode-ai/core/util/array" import { createSessionTabs } from "@/pages/session/helpers" import { extractPromptFromParts } from "@/utils/prompt" -import { UserMessage } from "@opencode-ai/sdk/v2" +import { Message, Part, UserMessage } from "@opencode-ai/sdk/v2" import { useSessionLayout } from "@/pages/session/session-layout" import { createSessionOwnership } from "./session-ownership" import { useLocal } from "@/context/local" @@ -231,6 +232,31 @@ export const useSessionCommands = (actions: SessionCommandContext) => { ) } + const exportSession = async () => { + const sessionID = params.id + if (!sessionID) return + try { + const data = await fetchSessionExport({ + sessionID, + client: sdk().client, + }) + const filename = sessionExportFilename(data.info) + downloadSessionExport(filename, data) + showToast({ + variant: "success", + icon: "circle-check", + title: language.t("toast.session.export.success.title"), + description: language.t("toast.session.export.success.description", { filename }), + }) + } catch (err) { + showToast({ + variant: "error", + title: language.t("toast.session.export.failed.title"), + description: err instanceof Error ? err.message : language.t("toast.session.export.failed.description"), + }) + } + } + const openFile = () => { void openDialog( () => import("@/components/dialog-select-file"), @@ -458,6 +484,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => { disabled: !params.id || visibleUserMessages().length === 0, onSelect: fork, }), + sessionCommand({ + id: "session.export", + title: language.t("command.session.export"), + description: language.t("command.session.export.description"), + slash: "export", + disabled: !params.id, + onSelect: exportSession, + }), ] const fileCmds = () => { diff --git a/packages/app/src/utils/session-export.test.ts b/packages/app/src/utils/session-export.test.ts new file mode 100644 index 00000000000..ff18a16b7fa --- /dev/null +++ b/packages/app/src/utils/session-export.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, test } from "bun:test" +import { fetchSessionExport, sessionExportFilename } from "./session-export" +import type { Message, Part, Session } from "@opencode-ai/sdk/v2/client" + +describe("sessionExportFilename", () => { + test("generates filename from title", () => { + expect(sessionExportFilename({ id: "ses_123", title: "Clone PR in worktree from fork" })).toBe( + "clone-pr-in-worktree-from-fork.json", + ) + }) + + test("generates filename from slug when title missing", () => { + expect(sessionExportFilename({ id: "ses_123", slug: "my-session-slug" })).toBe("my-session-slug.json") + }) + + test("falls back to id when title and slug are empty", () => { + expect(sessionExportFilename({ id: "ses_123" })).toBe("ses_123.json") + }) +}) + +describe("fetchSessionExport", () => { + test("fetches full transcript from client", async () => { + const session = { id: "ses_1", title: "Test Session" } as Session + const msg = { id: "msg_1", role: "user" } as Message + const part = { id: "prt_1", type: "text", text: "hello" } as Part + const messages = [{ info: msg, parts: [part] }] + + const client = { + session: { + get: async () => ({ data: session }), + messages: async () => ({ data: messages }), + }, + } + + const result = await fetchSessionExport({ + sessionID: "ses_1", + client, + }) + + expect(result).toEqual({ + info: session, + messages, + }) + }) + + test("throws when session not found", async () => { + const client = { + session: { + get: async () => ({ data: null }), + messages: async () => ({ data: [] }), + }, + } + + expect( + fetchSessionExport({ + sessionID: "ses_missing", + client, + }), + ).rejects.toThrow("Session not found: ses_missing") + }) +}) diff --git a/packages/app/src/utils/session-export.ts b/packages/app/src/utils/session-export.ts new file mode 100644 index 00000000000..6eb9f9ab6a7 --- /dev/null +++ b/packages/app/src/utils/session-export.ts @@ -0,0 +1,61 @@ +import type { Message, Part, Session } from "@opencode-ai/sdk/v2/client" + +// Matches the exact `{ info, messages: [{ info, parts }] }` structure produced by `opencode export` CLI +export type SessionExportData = { + info: Session + messages: { + info: Message + parts: Part[] + }[] +} + +export type SessionExportClient = { + session: { + get: (input: { sessionID: string }) => Promise<{ data?: Session | null }> + messages: (input: { sessionID: string }) => Promise<{ data?: SessionExportData["messages"] | null }> + } +} + +export async function fetchSessionExport(input: { + sessionID: string + client: SessionExportClient +}): Promise { + const [sessionRes, messagesRes] = await Promise.all([ + input.client.session.get({ sessionID: input.sessionID }), + input.client.session.messages({ sessionID: input.sessionID }), + ]) + + if (!sessionRes?.data) { + throw new Error(`Session not found: ${input.sessionID}`) + } + if (!messagesRes?.data) { + throw new Error(`Failed to load messages for session: ${input.sessionID}`) + } + + return { + info: sessionRes.data, + messages: messagesRes.data, + } +} + +export function sessionExportFilename(session: { id: string; title?: string; slug?: string }) { + const name = session.title || session.slug || session.id + const clean = name + .toLowerCase() + .replace(/[^a-z0-9_-]+/gi, "-") + .replace(/^-+|-+$/g, "") + return `${clean || session.id}.json` +} + +export function downloadSessionExport(filename: string, data: unknown) { + const json = JSON.stringify(data, null, 2) + const blob = new Blob([json], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = filename + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) +} From b8bd88901a4870ef3a5752840f4e23e11d54e24e Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 6 Aug 2026 01:52:15 +0000 Subject: [PATCH 085/114] chore: generate --- .../app/src/components/session/session-context-tab.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index c08c202fa2e..2f37c33fb37 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -358,7 +358,12 @@ export function SessionContextTab() {
{language.t("context.rawMessages.title")}
- From 847771fe06d89eb41aa5f5ba9ebe6cee1ec6067c Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 6 Aug 2026 03:21:16 -0400 Subject: [PATCH 086/114] fix log processor --- .../console/function/src/log-processor.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/console/function/src/log-processor.ts b/packages/console/function/src/log-processor.ts index ee8743ef154..75daba2b504 100644 --- a/packages/console/function/src/log-processor.ts +++ b/packages/console/function/src/log-processor.ts @@ -54,15 +54,15 @@ export default { console.log(JSON.stringify(data, null, 2)) const lakeIngest = getLakeIngest() - const [honeycomb, lake] = await Promise.all([ - fetch("https://api.honeycomb.io/1/batch/zen", { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-Honeycomb-Team": Resource.HONEYCOMB_API_KEY.value, - }, - body: JSON.stringify(events), - }), + const [lake] = await Promise.all([ + // fetch("https://api.honeycomb.io/1/batch/zen", { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // "X-Honeycomb-Team": Resource.HONEYCOMB_API_KEY.value, + // }, + // body: JSON.stringify(events), + // }), ...(lakeIngest ? [ fetch(lakeIngest.url, { @@ -76,8 +76,8 @@ export default { ] : []), ]) - console.log(honeycomb.status) - console.log(await honeycomb.text()) + // console.log(honeycomb.status) + // console.log(await honeycomb.text()) if (lake) { console.log(lake.status) console.log(await lake.text()) From f6c5afe5a960a252177c1c7ddc280706fd85b7b0 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:25:41 +1000 Subject: [PATCH 087/114] fix(desktop): disable packaged console logging (#40794) Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> --- packages/desktop/src/main/logging.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/desktop/src/main/logging.ts b/packages/desktop/src/main/logging.ts index 8866fd78ef7..b8fc260cb87 100644 --- a/packages/desktop/src/main/logging.ts +++ b/packages/desktop/src/main/logging.ts @@ -189,6 +189,11 @@ async function writeZip(output: string, entries: Entry[]) { } function initConsoleTransport() { + if (app.isPackaged) { + log.transports.console.level = false + return + } + const write = log.transports.console.writeFn.bind(log.transports.console) log.transports.console.writeFn = (options) => { try { From def7220bfc65b84046e597e9be772eae81f663ff Mon Sep 17 00:00:00 2001 From: ayu Date: Thu, 6 Aug 2026 01:23:32 -0700 Subject: [PATCH 088/114] fix(tui): support copying over ssh with `set-clipboard on` tmux config (#30472) Co-authored-by: Simon Klee --- packages/tui/src/clipboard.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/tui/src/clipboard.ts b/packages/tui/src/clipboard.ts index 08f86f9f7a9..2ae29da8889 100644 --- a/packages/tui/src/clipboard.ts +++ b/packages/tui/src/clipboard.ts @@ -23,7 +23,8 @@ function command(command: string, args: string[] = [], input?: string) { function writeOsc52(text: string) { if (!process.stdout.isTTY) return const sequence = `\x1b]52;c;${Buffer.from(text).toString("base64")}\x07` - process.stdout.write(process.env.TMUX || process.env.STY ? `\x1bPtmux;\x1b${sequence}\x1b\\` : sequence) + const passthrough = `\x1bPtmux;\x1b${sequence}\x1b\\` + process.stdout.write(process.env.TMUX ? sequence + passthrough : process.env.STY ? passthrough : sequence) } export async function read() { From b379d71d194704ed42d08e9b736cf2966e3d1a02 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:58:58 -0500 Subject: [PATCH 089/114] fix(stats): update github stars --- packages/stats/app/src/component/model-compare-detail.tsx | 7 ++++++- packages/stats/app/src/routes/[lab]/[model].tsx | 7 ++++++- packages/stats/app/src/routes/[lab]/index.tsx | 7 ++++++- packages/stats/app/src/routes/compare/index.tsx | 7 ++++++- packages/stats/app/src/routes/stats-shell.tsx | 5 +++-- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/packages/stats/app/src/component/model-compare-detail.tsx b/packages/stats/app/src/component/model-compare-detail.tsx index d02723e4614..3790789b58b 100644 --- a/packages/stats/app/src/component/model-compare-detail.tsx +++ b/packages/stats/app/src/component/model-compare-detail.tsx @@ -31,6 +31,7 @@ import { applyThemePreference, Footer, getGitHubStars, + githubLink, Header, isThemePreference, themeStorageKey, @@ -240,7 +241,11 @@ export default function ModelCompareDetailPage(props: ModelCompareDetailPageProp -
+
-
+
}> diff --git a/packages/stats/app/src/routes/[lab]/index.tsx b/packages/stats/app/src/routes/[lab]/index.tsx index 3d4a1a663c3..ab202fc89f7 100644 --- a/packages/stats/app/src/routes/[lab]/index.tsx +++ b/packages/stats/app/src/routes/[lab]/index.tsx @@ -32,6 +32,7 @@ import { applyThemePreference, Footer, getGitHubStars, + githubLink, Header, isThemePreference, themeStorageKey, @@ -141,7 +142,11 @@ export default function StatsLab() { -
+
}> diff --git a/packages/stats/app/src/routes/compare/index.tsx b/packages/stats/app/src/routes/compare/index.tsx index 7aa5cbe4c70..861f6949347 100644 --- a/packages/stats/app/src/routes/compare/index.tsx +++ b/packages/stats/app/src/routes/compare/index.tsx @@ -20,6 +20,7 @@ import { applyThemePreference, Footer, getGitHubStars, + githubLink, Header, isThemePreference, themeStorageKey, @@ -125,7 +126,11 @@ export default function ModelCompareIndex() { -
+
diff --git a/packages/stats/app/src/routes/stats-shell.tsx b/packages/stats/app/src/routes/stats-shell.tsx index 8906dc9d5a7..1aa6e7a26fa 100644 --- a/packages/stats/app/src/routes/stats-shell.tsx +++ b/packages/stats/app/src/routes/stats-shell.tsx @@ -10,7 +10,7 @@ export type HeaderLink = { href: string; label: string } export const githubLink = { href: "https://github.com/anomalyco/opencode", apiHref: "https://api.github.com/repos/anomalyco/opencode", - fallbackStars: "150K", + fallbackStars: "195K", } export const themePreferences = ["dark", "light", "system"] as const export const themeStorageKey = "opencode:stats-theme" @@ -18,7 +18,8 @@ export type ThemePreference = (typeof themePreferences)[number] const compactNumberFormatter = new Intl.NumberFormat("en", { notation: "compact", - maximumFractionDigits: 1, + maximumFractionDigits: 0, + roundingIncrement: 5, }) export const getGitHubStars = query(async () => { From 03bff6500abd09fc469d59e5bd4143d3eb053a94 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:07:33 -0500 Subject: [PATCH 090/114] fix(app): update homepage stats --- packages/console/app/src/config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/console/app/src/config.ts b/packages/console/app/src/config.ts index 2cd039fef7e..78da7eba312 100644 --- a/packages/console/app/src/config.ts +++ b/packages/console/app/src/config.ts @@ -9,8 +9,8 @@ export const config = { github: { repoUrl: "https://github.com/anomalyco/opencode", starsFormatted: { - compact: "160K", - full: "160,000", + compact: "195K", + full: "195,000", }, }, @@ -22,8 +22,8 @@ export const config = { // Static stats (used on landing page) stats: { - contributors: "900", + contributors: "950", commits: "13,000", - monthlyUsers: "7.5M", + monthlyUsers: "16M", }, } as const From dc898ca2c3e64f95fca7e2f737c3cd1eb14ae5ec Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 6 Aug 2026 19:34:26 -0400 Subject: [PATCH 091/114] sync --- packages/console/app/src/routes/zen/util/handler.ts | 3 ++- packages/console/core/src/schema/billing.sql.ts | 1 - packages/console/core/src/schema/referral.sql.ts | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 4d31756161d..a92aaaea35c 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -197,7 +197,8 @@ export async function handler( if (Array.isArray(v)) return [[k, v]] if (typeof v === "object") return [[k, replacer(v)]] if (typeof v === "string") { - if (v === "$workspace") return authInfo?.workspaceID ? [[k, authInfo?.workspaceID]] : [] + if (v === "$workspace") return authInfo?.workspaceID ? [[k, authInfo.workspaceID]] : [] + if (v === "$org") return authInfo?.workspaceID ? [[k, authInfo.workspaceID.replace("wrk_", "org_")]] : [] if (v === "$user") return stickyId ? [[k, stickyId]] : [] if (v.startsWith("$header.")) { const headerValue = input.request.headers.get(v.slice(8)) diff --git a/packages/console/core/src/schema/billing.sql.ts b/packages/console/core/src/schema/billing.sql.ts index b177858f363..915646cf3da 100644 --- a/packages/console/core/src/schema/billing.sql.ts +++ b/packages/console/core/src/schema/billing.sql.ts @@ -53,7 +53,6 @@ export const BillingTable = mysqlTable( ...workspaceIndexes(table), uniqueIndex("global_customer_id").on(table.customerID), uniqueIndex("global_subscription_id").on(table.subscriptionID), - uniqueIndex("global_lite_subscription_id").on(table.liteSubscriptionID), ], ) diff --git a/packages/console/core/src/schema/referral.sql.ts b/packages/console/core/src/schema/referral.sql.ts index be2499ee115..9850c92457d 100644 --- a/packages/console/core/src/schema/referral.sql.ts +++ b/packages/console/core/src/schema/referral.sql.ts @@ -1,4 +1,4 @@ -import { bigint, index, mysqlTable, primaryKey, uniqueIndex, varchar } from "drizzle-orm/mysql-core" +import { bigint, mysqlTable, primaryKey, uniqueIndex, varchar } from "drizzle-orm/mysql-core" import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types" import { workspaceIndexes } from "./workspace.sql" @@ -31,8 +31,5 @@ export const ReferralRewardTable = mysqlTable( amount: bigint("amount", { mode: "number" }).notNull(), timeApplied: utc("time_applied"), }, - (table) => [ - primaryKey({ columns: [table.workspaceID, table.referralID] }), - index("referral_id").on(table.referralID), - ], + (table) => [primaryKey({ columns: [table.workspaceID, table.referralID] })], ) From 69f2cbaa3ab875bd1a1cf4392ea68f207b7966d8 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 6 Aug 2026 23:37:25 +0000 Subject: [PATCH 092/114] chore: generate --- packages/console/app/src/routes/zen/util/handler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index a92aaaea35c..7319295a67b 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -198,7 +198,8 @@ export async function handler( if (typeof v === "object") return [[k, replacer(v)]] if (typeof v === "string") { if (v === "$workspace") return authInfo?.workspaceID ? [[k, authInfo.workspaceID]] : [] - if (v === "$org") return authInfo?.workspaceID ? [[k, authInfo.workspaceID.replace("wrk_", "org_")]] : [] + if (v === "$org") + return authInfo?.workspaceID ? [[k, authInfo.workspaceID.replace("wrk_", "org_")]] : [] if (v === "$user") return stickyId ? [[k, stickyId]] : [] if (v.startsWith("$header.")) { const headerValue = input.request.headers.get(v.slice(8)) From b7f9363393c4caefb413372d9a49111ed8289666 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:11:14 -0500 Subject: [PATCH 093/114] fix(opencode): serialize orphaned compaction history (#40800) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/session/compaction.ts | 51 ++++++++++-- .../opencode/test/session/compaction.test.ts | 77 ++++++++++++++++++- 2 files changed, 119 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index fa439e4efff..7693f5ccfdc 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -49,6 +49,42 @@ type CompletedCompaction = { summary: string | undefined } +const truncate = (value: string) => + value.length <= TOOL_OUTPUT_MAX_CHARS ? value : `${value.slice(0, TOOL_OUTPUT_MAX_CHARS)}\n[truncated]` + +const serialize = (message: SessionV1.WithParts) => { + if (message.info.role === "user") { + const text = message.parts + .filter((part): part is SessionV1.TextPart => part.type === "text" && !part.ignored) + .map((part) => part.text) + .filter(Boolean) + .join("\n") + const files = message.parts.flatMap((part) => + part.type === "file" ? [`[Attached ${part.mime}: ${part.filename ?? "file"}]`] : [], + ) + return [...(text ? [`[User]: ${text}`] : []), ...files].join("\n") + } + return message.parts + .flatMap((part) => { + if (part.type === "text") return part.text ? [`[Assistant]: ${part.text}`] : [] + if (part.type === "reasoning") return part.text ? [`[Assistant reasoning]: ${part.text}`] : [] + if (part.type !== "tool") return [] + const call = `[Assistant tool call]: ${part.tool}(${JSON.stringify(part.state.input)})` + if (part.state.status === "completed") { + const attachments = (part.state.attachments ?? []).map( + (item) => `[Attached ${item.mime}: ${item.filename ?? "file"}]`, + ) + const output = part.state.time.compacted + ? "[Old tool result content cleared]" + : truncate([part.state.output, ...attachments].join("\n")) + return [call, `[Tool result]: ${output}`] + } + if (part.state.status === "error") return [call, `[Tool error]: ${part.state.error}`] + return [call] + }) + .join("\n") +} + function summaryText(message: SessionV1.WithParts) { const text = message.parts .filter((part): part is SessionV1.TextPart => part.type === "text") @@ -348,10 +384,7 @@ const layer = Layer.effect( const nextPrompt = compacting.prompt ?? buildPrompt({ previousSummary, context: compacting.context }) const msgs = structuredClone(selected.head) yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) - const modelMessages = yield* MessageV2.toModelMessagesEffect(msgs, model, { - stripMedia: true, - toolOutputMaxChars: TOOL_OUTPUT_MAX_CHARS, - }) + const conversation = msgs.map(serialize).filter(Boolean).join("\n\n") const ctx = yield* InstanceState.context const msg: SessionV1.Assistant = { id: MessageID.ascending(), @@ -392,10 +425,16 @@ const layer = Layer.effect( tools: {}, system: [], messages: [ - ...modelMessages, { role: "user", - content: [{ type: "text", text: nextPrompt }], + content: [ + { + type: "text", + text: [nextPrompt, "The following is the conversation history:", conversation] + .filter(Boolean) + .join("\n\n"), + }, + ], }, ], model, diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 4a4210cf08b..0dff7354b5b 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -1362,10 +1362,10 @@ describe("session.compaction.process", () => { "summarizes only the head while keeping recent tail out of summary input", () => { const stub = llm() - let captured = "" + let messages: LLM.StreamInput["messages"] = [] stub.push( reply("summary", (input) => { - captured = JSON.stringify(input.messages) + messages = input.messages }), ) return Effect.gen(function* () { @@ -1386,7 +1386,10 @@ describe("session.compaction.process", () => { auto: false, }) - expect(captured).toContain("older context") + const captured = JSON.stringify(messages) + expect(messages).toHaveLength(1) + expect(messages[0]?.role).toBe("user") + expect(captured).toContain("[User]: older context") expect(captured).not.toContain("keep this turn") expect(captured).not.toContain("and this one too") expect(captured).not.toContain("What did we do so far?") @@ -1437,6 +1440,74 @@ describe("session.compaction.process", () => { { git: true }, ) + itCompaction.instance( + "serializes repeated compaction history as one user message", + () => { + const stub = llm() + let captured: LLM.StreamInput["messages"] = [] + stub.push( + reply("summary two", (input) => { + captured = input.messages + }), + ) + + return Effect.gen(function* () { + const ssn = yield* SessionNs.Service + const test = yield* TestInstance + const session = yield* ssn.create({}) + const turn = yield* createUserMessage(session.id, "original request") + const kept = yield* createAssistantMessage(session.id, turn.id, test.directory) + yield* ssn.updatePart({ + id: PartID.ascending(), + messageID: kept.id, + sessionID: session.id, + type: "tool", + callID: "read-call", + tool: "read", + state: { + status: "completed", + input: { filePath: "src/index.ts" }, + output: "file contents", + title: "src/index.ts", + metadata: {}, + time: { start: Date.now(), end: Date.now() }, + }, + }) + + const previous = yield* ssn.updateMessage({ + id: MessageID.ascending(), + role: "user", + model: ref, + sessionID: session.id, + agent: "build", + time: { created: Date.now() }, + }) + yield* ssn.updatePart({ + id: PartID.ascending(), + messageID: previous.id, + sessionID: session.id, + type: "compaction", + auto: false, + tail_start_id: kept.id, + }) + yield* createSummaryAssistantMessage(session.id, previous.id, test.directory, "summary one") + yield* createCompactionMarker(session.id) + + const msgs = MessageV2.filterCompacted(yield* MessageV2.stream(session.id)) + const parent = msgs.at(-1)?.info.id + expect(parent).toBeTruthy() + yield* SessionCompaction.use.process({ parentID: parent!, messages: msgs, sessionID: session.id, auto: false }) + + expect(captured).toHaveLength(1) + expect(captured[0]?.role).toBe("user") + expect(JSON.stringify(captured)).toContain('[Assistant tool call]: read({\\"filePath\\":\\"src/index.ts\\"})') + expect(JSON.stringify(captured)).toContain("[Tool result]: file contents") + expect(JSON.stringify(captured)).not.toContain('\\"role\\":\\"assistant\\"') + }).pipe(withCompaction({ llm: stub.llmLayer, config: cfg({ tail_turns: 0 }) })) + }, + { git: true }, + ) + itCompaction.instance("keeps recent pre-compaction turns across repeated compactions", () => { const stub = llm() stub.push(reply("summary one")) From 8d65dbdd04b39f6d2830a708bfb843b1d006a44e Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 03:06:23 +0000 Subject: [PATCH 094/114] fix(app): complete translation coverage (#40981) Co-authored-by: Hona <10430890+Hona@users.noreply.github.com> --- packages/app/AGENTS.md | 3 +++ .../app/src/components/dialog-usage-exceeded.tsx | 4 +++- packages/app/src/components/titlebar.tsx | 2 +- packages/app/src/i18n/ar.ts | 13 +++++++++++++ packages/app/src/i18n/az.ts | 13 +++++++++++++ packages/app/src/i18n/br.ts | 13 +++++++++++++ packages/app/src/i18n/bs.ts | 13 +++++++++++++ packages/app/src/i18n/da.ts | 13 +++++++++++++ packages/app/src/i18n/de.ts | 13 +++++++++++++ packages/app/src/i18n/en.ts | 1 + packages/app/src/i18n/es.ts | 13 +++++++++++++ packages/app/src/i18n/fi.ts | 13 +++++++++++++ packages/app/src/i18n/fr.ts | 13 +++++++++++++ packages/app/src/i18n/hi.ts | 13 +++++++++++++ packages/app/src/i18n/id.ts | 13 +++++++++++++ packages/app/src/i18n/it.ts | 13 +++++++++++++ packages/app/src/i18n/ja.ts | 13 +++++++++++++ packages/app/src/i18n/ko.ts | 13 +++++++++++++ packages/app/src/i18n/nl.ts | 13 +++++++++++++ packages/app/src/i18n/no.ts | 13 +++++++++++++ packages/app/src/i18n/pa.ts | 13 +++++++++++++ packages/app/src/i18n/parity.test.ts | 2 +- packages/app/src/i18n/pl.ts | 13 +++++++++++++ packages/app/src/i18n/ru.ts | 13 +++++++++++++ packages/app/src/i18n/sv.ts | 13 +++++++++++++ packages/app/src/i18n/th.ts | 13 +++++++++++++ packages/app/src/i18n/tr.ts | 13 +++++++++++++ packages/app/src/i18n/uk.ts | 13 +++++++++++++ packages/app/src/i18n/ur.ts | 13 +++++++++++++ packages/app/src/i18n/vi.ts | 13 +++++++++++++ packages/app/src/i18n/zh.ts | 13 +++++++++++++ packages/app/src/i18n/zht.ts | 13 +++++++++++++ packages/desktop/AGENTS.md | 2 ++ 33 files changed, 362 insertions(+), 3 deletions(-) diff --git a/packages/app/AGENTS.md b/packages/app/AGENTS.md index 2f587ea73d5..4972f2e3a6c 100644 --- a/packages/app/AGENTS.md +++ b/packages/app/AGENTS.md @@ -24,6 +24,9 @@ - NEVER hardcode user-visible English strings in production code. ALWAYS use an i18n key for visible copy, placeholders, accessible labels, tooltips, menus, dialogs, toasts, empty states, and displayed errors. - When migrating existing copy to i18n, preserve the English text byte-for-byte unless the task explicitly requests a copy change. - NEVER change existing English text or English keys to facilitate translation. English is intentional, designer-written source copy; adapt locale-specific translations and i18n mechanics around it. +- Keep locale complexity behind the shared typed i18n APIs. Feature and component code should use `language.t(...)` for ordinary copy and `language.plural(baseKey, count, params)` for count-sensitive copy. It must not inspect the locale, call `Intl.PluralRules`, construct or select plural-category keys such as `.one` or `.other`, or branch on locale-specific grammar. +- Prefer complete translated phrases. Do not concatenate grammatical fragments or make call sites assemble sentences. Keep placeholders to irreducible dynamic values such as names, paths, and counts. +- If a translation cannot be expressed by the current API, deepen the shared language/UI i18n module so one typed call owns locale selection, plural resolution, fallback, and interpolation. Do not leak that machinery into product code. - Do not translate from model knowledge alone. Verify terminology and grammar with Unicode CLDR locale/plural data, Microsoft Localization Style Guides and terminology, Apple localization/style guidance and localized platform UI, Mozilla localization style guides, Mozilla Pontoon, and the Firefox localization corpus at `github.com/mozilla-l10n/firefox-l10n`. - Also use the relevant language authority or official dictionary for the locale (for example RAE/Fundéu, FranceTerme, Duden, TDK, Kotus/Kielitoimiston sanakirja, Språkrådet/Bokmålsordboka, Rada Języka Polskiego/PWN, the Russian and Arabic language academies, the Ukrainian Orthography, Taiwan MOE dictionaries, or the Royal Society of Thailand). Treat the English dictionary as the semantic source of truth and preserve placeholders, code identifiers, product names, and keyboard labels. diff --git a/packages/app/src/components/dialog-usage-exceeded.tsx b/packages/app/src/components/dialog-usage-exceeded.tsx index bf5da751e22..de6451a63b6 100644 --- a/packages/app/src/components/dialog-usage-exceeded.tsx +++ b/packages/app/src/components/dialog-usage-exceeded.tsx @@ -1,4 +1,5 @@ import { usePlatform } from "@/context/platform" +import { useLanguage } from "@/context/language" import { Button } from "@opencode-ai/ui/button" import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" @@ -14,6 +15,7 @@ export type DialogGoUpsellProps = { export function DialogUsageExceeded(props: DialogGoUpsellProps) { const dialog = useDialog() + const language = useLanguage() const platform = usePlatform() const runAction = () => { @@ -32,7 +34,7 @@ export function DialogUsageExceeded(props: DialogGoUpsellProps) {